From 62d5784a8f5aa8995b42b6fb5b0b2ffa4f8d69bb Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Sun, 1 Mar 2020 09:52:04 +0000 Subject: [PATCH 01/36] Add RISC-V target features --- src/librustc_codegen_llvm/llvm_util.rs | 11 +++++++++++ src/librustc_feature/active.rs | 1 + src/librustc_span/symbol.rs | 1 + src/librustc_typeck/collect.rs | 1 + src/test/ui/target-feature/gate.rs | 1 + 5 files changed, 15 insertions(+) diff --git a/src/librustc_codegen_llvm/llvm_util.rs b/src/librustc_codegen_llvm/llvm_util.rs index a36e95771e8..286d3630181 100644 --- a/src/librustc_codegen_llvm/llvm_util.rs +++ b/src/librustc_codegen_llvm/llvm_util.rs @@ -236,6 +236,15 @@ const POWERPC_WHITELIST: &[(&str, Option)] = &[ const MIPS_WHITELIST: &[(&str, Option)] = &[("fp64", Some(sym::mips_target_feature)), ("msa", Some(sym::mips_target_feature))]; +const RISCV_WHITELIST: &[(&str, Option)] = &[ + ("m", Some(sym::riscv_target_feature)), + ("a", Some(sym::riscv_target_feature)), + ("c", Some(sym::riscv_target_feature)), + ("f", Some(sym::riscv_target_feature)), + ("d", Some(sym::riscv_target_feature)), + ("e", Some(sym::riscv_target_feature)), +]; + const WASM_WHITELIST: &[(&str, Option)] = &[("simd128", Some(sym::wasm_target_feature)), ("atomics", Some(sym::wasm_target_feature))]; @@ -253,6 +262,7 @@ pub fn all_known_features() -> impl Iterator &'static [(&'static str, Opti "hexagon" => HEXAGON_WHITELIST, "mips" | "mips64" => MIPS_WHITELIST, "powerpc" | "powerpc64" => POWERPC_WHITELIST, + "riscv32" | "riscv64" => RISCV_WHITELIST, "wasm32" => WASM_WHITELIST, _ => &[], } diff --git a/src/librustc_feature/active.rs b/src/librustc_feature/active.rs index a1dd7a5ca52..47101ca72b4 100644 --- a/src/librustc_feature/active.rs +++ b/src/librustc_feature/active.rs @@ -228,6 +228,7 @@ declare_features! ( (active, hexagon_target_feature, "1.27.0", Some(44839), None), (active, powerpc_target_feature, "1.27.0", Some(44839), None), (active, mips_target_feature, "1.27.0", Some(44839), None), + (active, riscv_target_feature, "1.27.0", Some(44839), None), (active, avx512_target_feature, "1.27.0", Some(44839), None), (active, mmx_target_feature, "1.27.0", Some(44839), None), (active, sse4a_target_feature, "1.27.0", Some(44839), None), diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs index a61647bfd65..1f351d09bc3 100644 --- a/src/librustc_span/symbol.rs +++ b/src/librustc_span/symbol.rs @@ -607,6 +607,7 @@ symbols! { Result, Return, rhs, + riscv_target_feature, rlib, rotate_left, rotate_right, diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs index 854bd03b264..4bbb36c8742 100644 --- a/src/librustc_typeck/collect.rs +++ b/src/librustc_typeck/collect.rs @@ -2278,6 +2278,7 @@ fn from_target_feature( Some(sym::hexagon_target_feature) => rust_features.hexagon_target_feature, Some(sym::powerpc_target_feature) => rust_features.powerpc_target_feature, Some(sym::mips_target_feature) => rust_features.mips_target_feature, + Some(sym::riscv_target_feature) => rust_features.riscv_target_feature, Some(sym::avx512_target_feature) => rust_features.avx512_target_feature, Some(sym::mmx_target_feature) => rust_features.mmx_target_feature, Some(sym::sse4a_target_feature) => rust_features.sse4a_target_feature, diff --git a/src/test/ui/target-feature/gate.rs b/src/test/ui/target-feature/gate.rs index 2d51cab675e..f738c16673d 100644 --- a/src/test/ui/target-feature/gate.rs +++ b/src/test/ui/target-feature/gate.rs @@ -25,6 +25,7 @@ // gate-test-movbe_target_feature // gate-test-rtm_target_feature // gate-test-f16c_target_feature +// gate-test-riscv_target_feature #[target_feature(enable = "avx512bw")] //~^ ERROR: currently unstable From 989edf6dd908408723003539583643d11cd5b459 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 22 Jan 2020 11:24:31 +0000 Subject: [PATCH 02/36] Add inline asm register definitions to librustc_target --- src/librustc_target/asm/aarch64.rs | 155 ++++++++ src/librustc_target/asm/arm.rs | 253 +++++++++++++ src/librustc_target/asm/mod.rs | 560 +++++++++++++++++++++++++++++ src/librustc_target/asm/riscv.rs | 140 ++++++++ src/librustc_target/asm/x86.rs | 338 +++++++++++++++++ src/librustc_target/lib.rs | 3 +- 6 files changed, 1448 insertions(+), 1 deletion(-) create mode 100644 src/librustc_target/asm/aarch64.rs create mode 100644 src/librustc_target/asm/arm.rs create mode 100644 src/librustc_target/asm/mod.rs create mode 100644 src/librustc_target/asm/riscv.rs create mode 100644 src/librustc_target/asm/x86.rs diff --git a/src/librustc_target/asm/aarch64.rs b/src/librustc_target/asm/aarch64.rs new file mode 100644 index 00000000000..c734344f81f --- /dev/null +++ b/src/librustc_target/asm/aarch64.rs @@ -0,0 +1,155 @@ +use super::{InlineAsmArch, InlineAsmType}; +use rustc_macros::HashStable_Generic; +use std::fmt; + +def_reg_class! { + AArch64 AArch64InlineAsmRegClass { + reg, + vreg, + vreg_low16, + } +} + +impl AArch64InlineAsmRegClass { + pub fn valid_modifiers(self, _arch: super::InlineAsmArch) -> &'static [char] { + match self { + Self::reg => &['w', 'x'], + Self::vreg | Self::vreg_low16 => &['b', 'h', 's', 'd', 'q', 'v'], + } + } + + pub fn suggest_modifier( + self, + _arch: InlineAsmArch, + ty: InlineAsmType, + ) -> Option<(char, &'static str, Option<&'static str>)> { + match self { + Self::reg => { + if ty.size().bits() <= 32 { + Some(('w', "w0", None)) + } else { + None + } + } + Self::vreg | Self::vreg_low16 => match ty.size().bits() { + 8 => Some(('b', "b0", None)), + 16 => Some(('h', "h0", None)), + 32 => Some(('s', "s0", None)), + 64 => Some(('d', "d0", None)), + 128 => Some(('q', "q0", None)), + _ => None, + }, + } + } + + pub fn default_modifier(self, _arch: InlineAsmArch) -> Option<(char, &'static str)> { + match self { + Self::reg => Some(('x', "x0")), + Self::vreg | Self::vreg_low16 => Some(('v', "v0")), + } + } + + pub fn supported_types( + self, + _arch: InlineAsmArch, + ) -> &'static [(InlineAsmType, Option<&'static str>)] { + match self { + Self::reg => types! { _: I8, I16, I32, I64, F32, F64; }, + Self::vreg | Self::vreg_low16 => types! { + "fp": I8, I16, I32, I64, F32, F64, + VecI8(8), VecI16(4), VecI32(2), VecI64(1), VecF32(2), VecF64(1), + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2); + }, + } + } +} + +def_regs! { + AArch64 AArch64InlineAsmReg AArch64InlineAsmRegClass { + x0: reg = ["x0", "w0"], + x1: reg = ["x1", "w1"], + x2: reg = ["x2", "w2"], + x3: reg = ["x3", "w3"], + x4: reg = ["x4", "w4"], + x5: reg = ["x5", "w5"], + x6: reg = ["x6", "w6"], + x7: reg = ["x7", "w7"], + x8: reg = ["x8", "w8"], + x9: reg = ["x9", "w9"], + x10: reg = ["x10", "w10"], + x11: reg = ["x11", "w11"], + x12: reg = ["x12", "w12"], + x13: reg = ["x13", "w13"], + x14: reg = ["x14", "w14"], + x15: reg = ["x15", "w15"], + x16: reg = ["x16", "w16"], + x17: reg = ["x17", "w17"], + x18: reg = ["x18", "w18"], + x19: reg = ["x19", "w19"], + x20: reg = ["x20", "w20"], + x21: reg = ["x21", "w21"], + x22: reg = ["x22", "w22"], + x23: reg = ["x23", "w23"], + x24: reg = ["x24", "w24"], + x25: reg = ["x25", "w25"], + x26: reg = ["x26", "w26"], + x27: reg = ["x27", "w27"], + x28: reg = ["x28", "w28"], + x30: reg = ["x30", "w30", "lr"], + v0: vreg, vreg_low16 = ["v0", "b0", "h0", "s0", "d0", "q0"], + v1: vreg, vreg_low16 = ["v1", "b1", "h1", "s1", "d1", "q1"], + v2: vreg, vreg_low16 = ["v2", "b2", "h2", "s2", "d2", "q2"], + v3: vreg, vreg_low16 = ["v3", "b3", "h3", "s3", "d3", "q3"], + v4: vreg, vreg_low16 = ["v4", "b4", "h4", "s4", "d4", "q4"], + v5: vreg, vreg_low16 = ["v5", "b5", "h5", "s5", "d5", "q5"], + v6: vreg, vreg_low16 = ["v6", "b6", "h6", "s6", "d6", "q6"], + v7: vreg, vreg_low16 = ["v7", "b7", "h7", "s7", "d7", "q7"], + v8: vreg, vreg_low16 = ["v8", "b8", "h8", "s8", "d8", "q8"], + v9: vreg, vreg_low16 = ["v9", "b9", "h9", "s9", "d9", "q9"], + v10: vreg, vreg_low16 = ["v10", "b10", "h10", "s10", "d10", "q10"], + v11: vreg, vreg_low16 = ["v11", "b11", "h11", "s11", "d11", "q11"], + v12: vreg, vreg_low16 = ["v12", "b12", "h12", "s12", "d12", "q12"], + v13: vreg, vreg_low16 = ["v13", "b13", "h13", "s13", "d13", "q13"], + v14: vreg, vreg_low16 = ["v14", "b14", "h14", "s14", "d14", "q14"], + v15: vreg, vreg_low16 = ["v15", "b15", "h15", "s15", "d15", "q15"], + v16: vreg = ["v16", "b16", "h16", "s16", "d16", "q16"], + v17: vreg = ["v17", "b17", "h17", "s17", "d17", "q17"], + v18: vreg = ["v18", "b18", "h18", "s18", "d18", "q18"], + v19: vreg = ["v19", "b19", "h19", "s19", "d19", "q19"], + v20: vreg = ["v20", "b20", "h20", "s20", "d20", "q20"], + v21: vreg = ["v21", "b21", "h21", "s21", "d21", "q21"], + v22: vreg = ["v22", "b22", "h22", "s22", "d22", "q22"], + v23: vreg = ["v23", "b23", "h23", "s23", "d23", "q23"], + v24: vreg = ["v24", "b24", "h24", "s24", "d24", "q24"], + v25: vreg = ["v25", "b25", "h25", "s25", "d25", "q25"], + v26: vreg = ["v26", "b26", "h26", "s26", "d26", "q26"], + v27: vreg = ["v27", "b27", "h27", "s27", "d27", "q27"], + v28: vreg = ["v28", "b28", "h28", "s28", "d28", "q28"], + v29: vreg = ["v29", "b29", "h29", "s29", "d29", "q29"], + v30: vreg = ["v30", "b30", "h30", "s30", "d30", "q30"], + v31: vreg = ["v31", "b31", "h31", "s31", "d31", "q31"], + "the frame pointer cannot be used as an operand for inline asm" = + ["x29", "fp"], + "the stack pointer cannot be used as an operand for inline asm" = + ["sp", "wsp"], + "the zero register cannot be used as an operand for inline asm" = + ["xzr", "wzr"], + } +} + +impl AArch64InlineAsmReg { + pub fn emit( + self, + out: &mut dyn fmt::Write, + _arch: InlineAsmArch, + modifier: Option, + ) -> fmt::Result { + let (prefix, index) = if (self as u32) < Self::v0 as u32 { + (modifier.unwrap_or('x'), self as u32 - Self::x0 as u32) + } else { + (modifier.unwrap_or('v'), self as u32 - Self::v0 as u32) + }; + assert!(index < 32); + write!(out, "{}{}", prefix, index) + } +} diff --git a/src/librustc_target/asm/arm.rs b/src/librustc_target/asm/arm.rs new file mode 100644 index 00000000000..143852db829 --- /dev/null +++ b/src/librustc_target/asm/arm.rs @@ -0,0 +1,253 @@ +use super::{InlineAsmArch, InlineAsmType}; +use rustc_macros::HashStable_Generic; +use std::fmt; + +def_reg_class! { + Arm ArmInlineAsmRegClass { + reg, + reg_thumb, + sreg, + sreg_low16, + dreg, + dreg_low16, + dreg_low8, + qreg, + qreg_low8, + qreg_low4, + } +} + +impl ArmInlineAsmRegClass { + pub fn valid_modifiers(self, _arch: super::InlineAsmArch) -> &'static [char] { + match self { + Self::qreg | Self::qreg_low8 | Self::qreg_low4 => &['e', 'f'], + _ => &[], + } + } + + pub fn suggest_modifier( + self, + _arch: InlineAsmArch, + _ty: InlineAsmType, + ) -> Option<(char, &'static str, Option<&'static str>)> { + None + } + + pub fn default_modifier(self, _arch: InlineAsmArch) -> Option<(char, &'static str)> { + None + } + + pub fn supported_types( + self, + _arch: InlineAsmArch, + ) -> &'static [(InlineAsmType, Option<&'static str>)] { + match self { + Self::reg | Self::reg_thumb => types! { _: I8, I16, I32, F32; }, + Self::sreg | Self::sreg_low16 => types! { "vfp2": I32, F32; }, + Self::dreg | Self::dreg_low16 | Self::dreg_low8 => types! { + "vfp2": I64, F64, VecI8(8), VecI16(4), VecI32(2), VecI64(1), VecF32(2); + }, + Self::qreg | Self::qreg_low8 | Self::qreg_low4 => types! { + "neon": VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4); + }, + } + } +} + +def_regs! { + Arm ArmInlineAsmReg ArmInlineAsmRegClass { + r0: reg, reg_thumb = ["r0", "a1"], + r1: reg, reg_thumb = ["r1", "a2"], + r2: reg, reg_thumb = ["r2", "a3"], + r3: reg, reg_thumb = ["r3", "a4"], + r4: reg, reg_thumb = ["r4", "v1"], + r5: reg, reg_thumb = ["r5", "v2"], + r6: reg, reg_thumb = ["r6", "v3"], + r7: reg, reg_thumb = ["r7", "v4"], + r8: reg = ["r8", "v5"], + r9: reg = ["r9", "v6", "rfp"], + r10: reg = ["r10", "sl"], + r12: reg = ["r12", "ip"], + r14: reg = ["r14", "lr"], + s0: sreg, sreg_low16 = ["s0"], + s1: sreg, sreg_low16 = ["s1"], + s2: sreg, sreg_low16 = ["s2"], + s3: sreg, sreg_low16 = ["s3"], + s4: sreg, sreg_low16 = ["s4"], + s5: sreg, sreg_low16 = ["s5"], + s6: sreg, sreg_low16 = ["s6"], + s7: sreg, sreg_low16 = ["s7"], + s8: sreg, sreg_low16 = ["s8"], + s9: sreg, sreg_low16 = ["s9"], + s10: sreg, sreg_low16 = ["s10"], + s11: sreg, sreg_low16 = ["s11"], + s12: sreg, sreg_low16 = ["s12"], + s13: sreg, sreg_low16 = ["s13"], + s14: sreg, sreg_low16 = ["s14"], + s15: sreg, sreg_low16 = ["s15"], + s16: sreg = ["s16"], + s17: sreg = ["s17"], + s18: sreg = ["s18"], + s19: sreg = ["s19"], + s20: sreg = ["s20"], + s21: sreg = ["s21"], + s22: sreg = ["s22"], + s23: sreg = ["s23"], + s24: sreg = ["s24"], + s25: sreg = ["s25"], + s26: sreg = ["s26"], + s27: sreg = ["s27"], + s28: sreg = ["s28"], + s29: sreg = ["s29"], + s30: sreg = ["s30"], + s31: sreg = ["s31"], + d0: dreg, dreg_low16, dreg_low8 = ["d0"], + d1: dreg, dreg_low16, dreg_low8 = ["d1"], + d2: dreg, dreg_low16, dreg_low8 = ["d2"], + d3: dreg, dreg_low16, dreg_low8 = ["d3"], + d4: dreg, dreg_low16, dreg_low8 = ["d4"], + d5: dreg, dreg_low16, dreg_low8 = ["d5"], + d6: dreg, dreg_low16, dreg_low8 = ["d6"], + d7: dreg, dreg_low16, dreg_low8 = ["d7"], + d8: dreg, dreg_low16 = ["d8"], + d9: dreg, dreg_low16 = ["d9"], + d10: dreg, dreg_low16 = ["d10"], + d11: dreg, dreg_low16 = ["d11"], + d12: dreg, dreg_low16 = ["d12"], + d13: dreg, dreg_low16 = ["d13"], + d14: dreg, dreg_low16 = ["d14"], + d15: dreg, dreg_low16 = ["d15"], + d16: dreg = ["d16"], + d17: dreg = ["d17"], + d18: dreg = ["d18"], + d19: dreg = ["d19"], + d20: dreg = ["d20"], + d21: dreg = ["d21"], + d22: dreg = ["d22"], + d23: dreg = ["d23"], + d24: dreg = ["d24"], + d25: dreg = ["d25"], + d26: dreg = ["d26"], + d27: dreg = ["d27"], + d28: dreg = ["d28"], + d29: dreg = ["d29"], + d30: dreg = ["d30"], + d31: dreg = ["d31"], + q0: qreg, qreg_low8, qreg_low4 = ["q0"], + q1: qreg, qreg_low8, qreg_low4 = ["q1"], + q2: qreg, qreg_low8, qreg_low4 = ["q2"], + q3: qreg, qreg_low8, qreg_low4 = ["q3"], + q4: qreg, qreg_low8 = ["q4"], + q5: qreg, qreg_low8 = ["q5"], + q6: qreg, qreg_low8 = ["q6"], + q7: qreg, qreg_low8 = ["q7"], + q8: qreg = ["q8"], + q9: qreg = ["q9"], + q10: qreg = ["q10"], + q11: qreg = ["q11"], + q12: qreg = ["q12"], + q13: qreg = ["q13"], + q14: qreg = ["q14"], + q15: qreg = ["q15"], + "the frame pointer cannot be used as an operand for inline asm" = + ["r11", "fp"], + "the stack pointer cannot be used as an operand for inline asm" = + ["r13", "sp"], + "the program pointer cannot be used as an operand for inline asm" = + ["r15", "pc"], + } +} + +impl ArmInlineAsmReg { + pub fn emit( + self, + out: &mut dyn fmt::Write, + _arch: InlineAsmArch, + modifier: Option, + ) -> fmt::Result { + // Only qreg is allowed to have modifiers. This should have been + // validated already by now. + if let Some(modifier) = modifier { + let index = self as u32 - Self::q0 as u32; + assert!(index < 16); + let index = index * 2 + (modifier == 'f') as u32; + write!(out, "d{}", index) + } else { + out.write_str(self.name()) + } + } + + pub fn overlapping_regs(self, mut cb: impl FnMut(ArmInlineAsmReg)) { + cb(self); + + macro_rules! reg_conflicts { + ( + $( + $q:ident : $d0:ident $d1:ident : $s0:ident $s1:ident $s2:ident $s3:ident + ),*; + $( + $q_high:ident : $d0_high:ident $d1_high:ident + ),*; + ) => { + match self { + $( + Self::$q => { + cb(Self::$d0); + cb(Self::$d1); + cb(Self::$s0); + cb(Self::$s1); + cb(Self::$s2); + cb(Self::$s3); + } + Self::$d0 => { + cb(Self::$q); + cb(Self::$s0); + cb(Self::$s1); + } + Self::$d1 => { + cb(Self::$q); + cb(Self::$s2); + cb(Self::$s3); + } + Self::$s0 | Self::$s1 => { + cb(Self::$q); + cb(Self::$d0); + } + Self::$s2 | Self::$s3 => { + cb(Self::$q); + cb(Self::$d1); + } + )* + $( + Self::$q_high => { + cb(Self::$d0_high); + cb(Self::$d1_high); + } + Self::$d0_high | Self::$d1_high => { + cb(Self::$q_high); + } + )* + _ => {}, + } + }; + } + reg_conflicts! { + q0 : d0 d1 : s0 s1 s2 s3, + q1 : d2 d3 : s4 s5 s6 s7, + q2 : d4 d5 : s8 s9 s10 s11, + q3 : d6 d7 : s12 s13 s14 s15, + q4 : d8 d9 : s16 s17 s18 s19, + q5 : d10 d11 : s20 s21 s22 s23, + q6 : d12 d13 : s24 s25 s26 s27, + q7 : d14 d15 : s28 s29 s30 s31; + q8 : d16 d17, + q9 : d18 d19, + q10 : d20 d21, + q11 : d22 d23, + q12 : d24 d25, + q13 : d26 d27, + q14 : d28 d29, + q15 : d30 d31; + } + } +} diff --git a/src/librustc_target/asm/mod.rs b/src/librustc_target/asm/mod.rs new file mode 100644 index 00000000000..1f8ff5d6d39 --- /dev/null +++ b/src/librustc_target/asm/mod.rs @@ -0,0 +1,560 @@ +use crate::abi::Size; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_macros::HashStable_Generic; +use rustc_span::{Span, Symbol}; +use std::fmt; +use std::str::FromStr; + +#[macro_use] +macro_rules! def_reg_class { + ($arch:ident $arch_regclass:ident { + $( + $class:ident, + )* + }) => { + #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Debug, Eq, PartialEq, Hash, HashStable_Generic)] + #[allow(non_camel_case_types)] + pub enum $arch_regclass { + $($class,)* + } + + impl $arch_regclass { + pub fn name(self) -> &'static str { + match self { + $(Self::$class => stringify!($class),)* + } + } + + pub fn parse(_arch: super::InlineAsmArch, name: &str) -> Result { + match name { + $( + stringify!($class) => Ok(Self::$class), + )* + _ => Err("unknown register class"), + } + } + } + + pub(super) fn regclass_map() -> rustc_data_structures::fx::FxHashMap< + super::InlineAsmRegClass, + rustc_data_structures::fx::FxHashSet, + > { + use rustc_data_structures::fx::{FxHashMap, FxHashSet}; + use super::InlineAsmRegClass; + let mut map = FxHashMap::default(); + $( + map.insert(InlineAsmRegClass::$arch($arch_regclass::$class), FxHashSet::default()); + )* + map + } + } +} + +#[macro_use] +macro_rules! def_regs { + ($arch:ident $arch_reg:ident $arch_regclass:ident { + $( + $reg:ident: $class:ident $(, $extra_class:ident)* = [$reg_name:literal $(, $alias:literal)*] $(% $filter:ident)?, + )* + $( + $error:literal = [$($bad_reg:literal),+], + )* + }) => { + #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Debug, Eq, PartialEq, Hash, HashStable_Generic)] + #[allow(non_camel_case_types)] + pub enum $arch_reg { + $($reg,)* + } + + impl $arch_reg { + pub fn name(self) -> &'static str { + match self { + $(Self::$reg => $reg_name,)* + } + } + + pub fn reg_class(self) -> $arch_regclass { + match self { + $(Self::$reg => $arch_regclass::$class,)* + } + } + + pub fn parse( + _arch: super::InlineAsmArch, + mut _has_feature: impl FnMut(&str) -> bool, + name: &str, + ) -> Result { + match name { + $( + $($alias)|* | $reg_name => { + $($filter(_arch, &mut _has_feature)?;)? + Ok(Self::$reg) + } + )* + $( + $($bad_reg)|* => Err($error), + )* + _ => Err("unknown register"), + } + } + } + + pub(super) fn fill_reg_map( + _arch: super::InlineAsmArch, + mut _has_feature: impl FnMut(&str) -> bool, + map: &mut rustc_data_structures::fx::FxHashMap< + super::InlineAsmRegClass, + rustc_data_structures::fx::FxHashSet, + >, + ) { + use super::{InlineAsmReg, InlineAsmRegClass}; + $( + if $($filter(_arch, &mut _has_feature).is_ok() &&)? true { + if let Some(set) = map.get_mut(&InlineAsmRegClass::$arch($arch_regclass::$class)) { + set.insert(InlineAsmReg::$arch($arch_reg::$reg)); + } + $( + if let Some(set) = map.get_mut(&InlineAsmRegClass::$arch($arch_regclass::$extra_class)) { + set.insert(InlineAsmReg::$arch($arch_reg::$reg)); + } + )* + } + )* + } + } +} + +#[macro_use] +macro_rules! types { + ( + $(_ : $($ty:expr),+;)? + $($feature:literal: $($ty2:expr),+;)* + ) => { + { + use super::InlineAsmType::*; + &[ + $($( + ($ty, None), + )*)? + $($( + ($ty2, Some($feature)), + )*)* + ] + } + }; +} + +mod aarch64; +mod arm; +mod riscv; +mod x86; + +pub use aarch64::{AArch64InlineAsmReg, AArch64InlineAsmRegClass}; +pub use arm::{ArmInlineAsmReg, ArmInlineAsmRegClass}; +pub use riscv::{RiscVInlineAsmReg, RiscVInlineAsmRegClass}; +pub use x86::{X86InlineAsmReg, X86InlineAsmRegClass}; + +#[derive(Copy, Clone, RustcEncodable, RustcDecodable, Debug, Eq, PartialEq, Hash)] +pub enum InlineAsmArch { + X86, + X86_64, + Arm, + AArch64, + RiscV32, + RiscV64, +} + +impl FromStr for InlineAsmArch { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "x86" => Ok(Self::X86), + "x86_64" => Ok(Self::X86_64), + "arm" => Ok(Self::Arm), + "aarch64" => Ok(Self::AArch64), + "riscv32" => Ok(Self::RiscV32), + "riscv64" => Ok(Self::RiscV64), + _ => Err(()), + } + } +} + +#[derive( + Copy, + Clone, + RustcEncodable, + RustcDecodable, + Debug, + Eq, + PartialEq, + Hash, + HashStable_Generic +)] +pub enum InlineAsmReg { + X86(X86InlineAsmReg), + Arm(ArmInlineAsmReg), + AArch64(AArch64InlineAsmReg), + RiscV(RiscVInlineAsmReg), +} + +impl InlineAsmReg { + pub fn name(self) -> &'static str { + match self { + Self::X86(r) => r.name(), + Self::Arm(r) => r.name(), + Self::AArch64(r) => r.name(), + Self::RiscV(r) => r.name(), + } + } + + pub fn reg_class(self) -> InlineAsmRegClass { + match self { + Self::X86(r) => InlineAsmRegClass::X86(r.reg_class()), + Self::Arm(r) => InlineAsmRegClass::Arm(r.reg_class()), + Self::AArch64(r) => InlineAsmRegClass::AArch64(r.reg_class()), + Self::RiscV(r) => InlineAsmRegClass::RiscV(r.reg_class()), + } + } + + pub fn parse( + arch: InlineAsmArch, + has_feature: impl FnMut(&str) -> bool, + name: Symbol, + ) -> Result { + // FIXME: use direct symbol comparison for register names + name.with(|name| { + Ok(match arch { + InlineAsmArch::X86 | InlineAsmArch::X86_64 => { + Self::X86(X86InlineAsmReg::parse(arch, has_feature, name)?) + } + InlineAsmArch::Arm => Self::Arm(ArmInlineAsmReg::parse(arch, has_feature, name)?), + InlineAsmArch::AArch64 => { + Self::AArch64(AArch64InlineAsmReg::parse(arch, has_feature, name)?) + } + InlineAsmArch::RiscV32 | InlineAsmArch::RiscV64 => { + Self::RiscV(RiscVInlineAsmReg::parse(arch, has_feature, name)?) + } + }) + }) + } + + pub fn emit( + self, + out: &mut dyn fmt::Write, + arch: InlineAsmArch, + modifier: Option, + ) -> fmt::Result { + match self { + Self::X86(r) => r.emit(out, arch, modifier), + Self::Arm(r) => r.emit(out, arch, modifier), + Self::AArch64(r) => r.emit(out, arch, modifier), + Self::RiscV(r) => r.emit(out, arch, modifier), + } + } + + pub fn overlapping_regs(self, mut cb: impl FnMut(InlineAsmReg)) { + match self { + Self::X86(r) => r.overlapping_regs(|r| cb(Self::X86(r))), + Self::Arm(r) => r.overlapping_regs(|r| cb(Self::Arm(r))), + Self::AArch64(_) => cb(self), + Self::RiscV(_) => cb(self), + } + } +} + +#[derive( + Copy, + Clone, + RustcEncodable, + RustcDecodable, + Debug, + Eq, + PartialEq, + Hash, + HashStable_Generic +)] +pub enum InlineAsmRegClass { + X86(X86InlineAsmRegClass), + Arm(ArmInlineAsmRegClass), + AArch64(AArch64InlineAsmRegClass), + RiscV(RiscVInlineAsmRegClass), +} + +impl InlineAsmRegClass { + pub fn name(self) -> &'static str { + match self { + Self::X86(r) => r.name(), + Self::Arm(r) => r.name(), + Self::AArch64(r) => r.name(), + Self::RiscV(r) => r.name(), + } + } + + /// Returns a suggested template modifier to use for this type and an + /// example of a register named formatted with it. Optionally also returns + /// the name of a different register class to use instead. + /// + /// Such suggestions are useful if a type smaller than the full register + /// size is used and a modifier can be used to point to the subregister of + /// the correct size. + pub fn suggest_modifier( + self, + arch: InlineAsmArch, + ty: InlineAsmType, + ) -> Option<(char, &'static str, Option<&'static str>)> { + match self { + Self::X86(r) => r.suggest_modifier(arch, ty), + Self::Arm(r) => r.suggest_modifier(arch, ty), + Self::AArch64(r) => r.suggest_modifier(arch, ty), + Self::RiscV(r) => r.suggest_modifier(arch, ty), + } + } + + /// Returns the default modifier for this register and an example of a + /// register named formatted with it. + /// + /// This is only needed when the register class can suggest a modifier, so + /// that the user can be shown how to get the default behavior without a + /// warning. + pub fn default_modifier(self, arch: InlineAsmArch) -> Option<(char, &'static str)> { + match self { + Self::X86(r) => r.default_modifier(arch), + Self::Arm(r) => r.default_modifier(arch), + Self::AArch64(r) => r.default_modifier(arch), + Self::RiscV(r) => r.default_modifier(arch), + } + } + + /// Returns a list of supported types for this register class, each with a + /// options target feature required to use this type. + pub fn supported_types( + self, + arch: InlineAsmArch, + ) -> &'static [(InlineAsmType, Option<&'static str>)] { + match self { + Self::X86(r) => r.supported_types(arch), + Self::Arm(r) => r.supported_types(arch), + Self::AArch64(r) => r.supported_types(arch), + Self::RiscV(r) => r.supported_types(arch), + } + } + + pub fn parse(arch: InlineAsmArch, name: Symbol) -> Result { + // FIXME: use direct symbol comparison for register class names + name.with(|name| { + Ok(match arch { + InlineAsmArch::X86 | InlineAsmArch::X86_64 => { + Self::X86(X86InlineAsmRegClass::parse(arch, name)?) + } + InlineAsmArch::Arm => Self::Arm(ArmInlineAsmRegClass::parse(arch, name)?), + InlineAsmArch::AArch64 => { + Self::AArch64(AArch64InlineAsmRegClass::parse(arch, name)?) + } + InlineAsmArch::RiscV32 | InlineAsmArch::RiscV64 => { + Self::RiscV(RiscVInlineAsmRegClass::parse(arch, name)?) + } + }) + }) + } + + /// Returns the list of template modifiers that can be used with this + /// register class. + pub fn valid_modifiers(self, arch: InlineAsmArch) -> &'static [char] { + match self { + Self::X86(r) => r.valid_modifiers(arch), + Self::Arm(r) => r.valid_modifiers(arch), + Self::AArch64(r) => r.valid_modifiers(arch), + Self::RiscV(r) => r.valid_modifiers(arch), + } + } +} + +#[derive( + Copy, + Clone, + RustcEncodable, + RustcDecodable, + Debug, + Eq, + PartialEq, + Hash, + HashStable_Generic +)] +pub enum InlineAsmRegOrRegClass { + Reg(InlineAsmReg), + RegClass(InlineAsmRegClass), +} + +impl InlineAsmRegOrRegClass { + pub fn reg_class(self) -> InlineAsmRegClass { + match self { + Self::Reg(r) => r.reg_class(), + Self::RegClass(r) => r, + } + } +} + +impl fmt::Display for InlineAsmRegOrRegClass { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Reg(r) => write!(f, "\"{}\"", r.name()), + Self::RegClass(r) => f.write_str(r.name()), + } + } +} + +bitflags::bitflags! { + #[derive(RustcEncodable, RustcDecodable, HashStable_Generic)] + pub struct InlineAsmOptions: u8 { + const PURE = 1 << 0; + const NOMEM = 1 << 1; + const READONLY = 1 << 2; + const PRESERVES_FLAGS = 1 << 3; + const NORETURN = 1 << 4; + const NOSTACK = 1 << 5; + } +} + +#[derive(Clone, PartialEq, RustcEncodable, RustcDecodable, Debug, HashStable_Generic)] +pub enum InlineAsmTemplatePiece { + String(String), + Placeholder { operand_idx: usize, modifier: Option, span: Span }, +} + +impl fmt::Display for InlineAsmTemplatePiece { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::String(s) => { + for c in s.chars() { + match c { + '{' => f.write_str("{{")?, + '}' => f.write_str("}}")?, + _ => write!(f, "{}", c.escape_debug())?, + } + } + Ok(()) + } + Self::Placeholder { operand_idx, modifier: Some(modifier), .. } => { + write!(f, "{{{}:{}}}", operand_idx, modifier) + } + Self::Placeholder { operand_idx, modifier: None, .. } => { + write!(f, "{{{}}}", operand_idx) + } + } + } +} + +impl InlineAsmTemplatePiece { + /// Rebuilds the asm template string from its pieces. + pub fn to_string(s: &[Self]) -> String { + use fmt::Write; + let mut out = String::new(); + for p in s.iter() { + let _ = write!(out, "{}", p); + } + out + } +} + +/// Set of types which can be used with a particular register class. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum InlineAsmType { + I8, + I16, + I32, + I64, + I128, + F32, + F64, + VecI8(u64), + VecI16(u64), + VecI32(u64), + VecI64(u64), + VecI128(u64), + VecF32(u64), + VecF64(u64), +} + +impl InlineAsmType { + pub fn is_integer(self) -> bool { + match self { + Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128 => true, + _ => false, + } + } + + pub fn size(self) -> Size { + Size::from_bytes(match self { + Self::I8 => 1, + Self::I16 => 2, + Self::I32 => 4, + Self::I64 => 8, + Self::I128 => 16, + Self::F32 => 4, + Self::F64 => 8, + Self::VecI8(n) => n * 1, + Self::VecI16(n) => n * 2, + Self::VecI32(n) => n * 4, + Self::VecI64(n) => n * 8, + Self::VecI128(n) => n * 16, + Self::VecF32(n) => n * 4, + Self::VecF64(n) => n * 8, + }) + } +} + +impl fmt::Display for InlineAsmType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::I8 => f.write_str("i8"), + Self::I16 => f.write_str("i16"), + Self::I32 => f.write_str("i32"), + Self::I64 => f.write_str("i64"), + Self::I128 => f.write_str("i128"), + Self::F32 => f.write_str("f32"), + Self::F64 => f.write_str("f64"), + Self::VecI8(n) => write!(f, "i8x{}", n), + Self::VecI16(n) => write!(f, "i16x{}", n), + Self::VecI32(n) => write!(f, "i32x{}", n), + Self::VecI64(n) => write!(f, "i64x{}", n), + Self::VecI128(n) => write!(f, "i128x{}", n), + Self::VecF32(n) => write!(f, "f32x{}", n), + Self::VecF64(n) => write!(f, "f64x{}", n), + } + } +} + +/// Returns the full set of allocatable registers for a given architecture. +/// +/// The registers are structured as a map containing the set of allocatable +/// registers in each register class. A particular register may be allocatable +/// from multiple register classes, in which case it will appear multiple times +/// in the map. +pub fn allocatable_registers( + arch: InlineAsmArch, + has_feature: impl FnMut(&str) -> bool, +) -> FxHashMap> { + match arch { + InlineAsmArch::X86 | InlineAsmArch::X86_64 => { + let mut map = x86::regclass_map(); + x86::fill_reg_map(arch, has_feature, &mut map); + map + } + InlineAsmArch::Arm => { + let mut map = arm::regclass_map(); + arm::fill_reg_map(arch, has_feature, &mut map); + map + } + InlineAsmArch::AArch64 => { + let mut map = aarch64::regclass_map(); + aarch64::fill_reg_map(arch, has_feature, &mut map); + map + } + InlineAsmArch::RiscV32 | InlineAsmArch::RiscV64 => { + let mut map = riscv::regclass_map(); + riscv::fill_reg_map(arch, has_feature, &mut map); + map + } + } +} diff --git a/src/librustc_target/asm/riscv.rs b/src/librustc_target/asm/riscv.rs new file mode 100644 index 00000000000..c57e277c0cd --- /dev/null +++ b/src/librustc_target/asm/riscv.rs @@ -0,0 +1,140 @@ +use super::{InlineAsmArch, InlineAsmType}; +use rustc_macros::HashStable_Generic; +use std::fmt; + +def_reg_class! { + RiscV RiscVInlineAsmRegClass { + reg, + freg, + } +} + +impl RiscVInlineAsmRegClass { + pub fn valid_modifiers(self, _arch: super::InlineAsmArch) -> &'static [char] { + &[] + } + + pub fn suggest_modifier( + self, + _arch: InlineAsmArch, + _ty: InlineAsmType, + ) -> Option<(char, &'static str, Option<&'static str>)> { + None + } + + pub fn default_modifier(self, _arch: InlineAsmArch) -> Option<(char, &'static str)> { + None + } + + pub fn supported_types( + self, + arch: InlineAsmArch, + ) -> &'static [(InlineAsmType, Option<&'static str>)] { + match self { + Self::reg => { + if arch == InlineAsmArch::RiscV64 { + types! { _: I8, I16, I32, I64, F32, F64; } + } else { + types! { _: I8, I16, I32, F32; } + } + } + Self::freg => types! { "f": F32; "d": F64; }, + } + } +} + +fn not_e( + _arch: InlineAsmArch, + mut has_feature: impl FnMut(&str) -> bool, +) -> Result<(), &'static str> { + if has_feature("e") { + Err("register can't be used with the `e` target feature") + } else { + Ok(()) + } +} + +def_regs! { + RiscV RiscVInlineAsmReg RiscVInlineAsmRegClass { + x1: reg = ["x1", "ra"], + x5: reg = ["x5", "t0"], + x6: reg = ["x6", "t1"], + x7: reg = ["x7", "t2"], + x9: reg = ["x9", "s1"], + x10: reg = ["x10", "a0"], + x11: reg = ["x11", "a1"], + x12: reg = ["x12", "a2"], + x13: reg = ["x13", "a3"], + x14: reg = ["x14", "a4"], + x15: reg = ["x15", "a5"], + x16: reg = ["x16", "a6"] % not_e, + x17: reg = ["x17", "a7"] % not_e, + x18: reg = ["x18", "s2"] % not_e, + x19: reg = ["x19", "s3"] % not_e, + x20: reg = ["x20", "s4"] % not_e, + x21: reg = ["x21", "s5"] % not_e, + x22: reg = ["x22", "s6"] % not_e, + x23: reg = ["x23", "s7"] % not_e, + x24: reg = ["x24", "s8"] % not_e, + x25: reg = ["x25", "s9"] % not_e, + x26: reg = ["x26", "s10"] % not_e, + x27: reg = ["x27", "s11"] % not_e, + x28: reg = ["x28", "t3"] % not_e, + x29: reg = ["x29", "t4"] % not_e, + x30: reg = ["x30", "t5"] % not_e, + x31: reg = ["x31", "t6"] % not_e, + f0: freg = ["f0", "ft0"], + f1: freg = ["f1", "ft1"], + f2: freg = ["f2", "ft2"], + f3: freg = ["f3", "ft3"], + f4: freg = ["f4", "ft4"], + f5: freg = ["f5", "ft5"], + f6: freg = ["f6", "ft6"], + f7: freg = ["f7", "ft7"], + f8: freg = ["f8", "fs0"], + f9: freg = ["f9", "fs1"], + f10: freg = ["f10", "fa0"], + f11: freg = ["f11", "fa1"], + f12: freg = ["f12", "fa2"], + f13: freg = ["f13", "fa3"], + f14: freg = ["f14", "fa4"], + f15: freg = ["f15", "fa5"], + f16: freg = ["f16", "fa6"], + f17: freg = ["f17", "fa7"], + f18: freg = ["f18", "fs2"], + f19: freg = ["f19", "fs3"], + f20: freg = ["f20", "fs4"], + f21: freg = ["f21", "fs5"], + f22: freg = ["f22", "fs6"], + f23: freg = ["f23", "fs7"], + f24: freg = ["f24", "fs8"], + f25: freg = ["f25", "fs9"], + f26: freg = ["f26", "fs10"], + f27: freg = ["f27", "fs11"], + f28: freg = ["f28", "ft8"], + f29: freg = ["f29", "ft9"], + f30: freg = ["f30", "ft10"], + f31: freg = ["f31", "ft11"], + "the frame pointer cannot be used as an operand for inline asm" = + ["x8", "s0", "fp"], + "the stack pointer cannot be used as an operand for inline asm" = + ["x2", "sp"], + "the global pointer cannot be used as an operand for inline asm" = + ["x3", "gp"], + "the thread pointer cannot be used as an operand for inline asm" = + ["x4", "tp"], + "the zero register cannot be used as an operand for inline asm" = + ["x0", "zero"], + } +} + +impl RiscVInlineAsmReg { + pub fn emit( + self, + out: &mut dyn fmt::Write, + _arch: InlineAsmArch, + _modifier: Option, + ) -> fmt::Result { + out.write_str(self.name()) + } +} diff --git a/src/librustc_target/asm/x86.rs b/src/librustc_target/asm/x86.rs new file mode 100644 index 00000000000..7193b1ca8b6 --- /dev/null +++ b/src/librustc_target/asm/x86.rs @@ -0,0 +1,338 @@ +use super::{InlineAsmArch, InlineAsmType}; +use rustc_macros::HashStable_Generic; +use std::fmt; + +def_reg_class! { + X86 X86InlineAsmRegClass { + reg, + reg_abcd, + xmm_reg, + ymm_reg, + zmm_reg, + kreg, + } +} + +impl X86InlineAsmRegClass { + pub fn valid_modifiers(self, arch: super::InlineAsmArch) -> &'static [char] { + match self { + Self::reg => { + if arch == InlineAsmArch::X86_64 { + &['l', 'h', 'x', 'e', 'r'] + } else { + &['x', 'e'] + } + } + Self::reg_abcd => { + if arch == InlineAsmArch::X86_64 { + &['l', 'h', 'x', 'e', 'r'] + } else { + &['l', 'h', 'x', 'e'] + } + } + Self::xmm_reg | Self::ymm_reg | Self::zmm_reg => &['x', 'y', 'z'], + Self::kreg => &[], + } + } + + pub fn suggest_modifier( + self, + arch: InlineAsmArch, + ty: InlineAsmType, + ) -> Option<(char, &'static str, Option<&'static str>)> { + match self { + Self::reg => match ty.size().bits() { + 8 => { + if arch == InlineAsmArch::X86_64 { + Some(('l', "al", None)) + } else { + // Low byte registers require reg_abcd on x86 so we emit + // a suggestion to use that register class instead. + Some(('l', "al", Some("reg_abcd"))) + } + } + 16 => Some(('x', "ax", None)), + 32 if arch == InlineAsmArch::X86_64 => Some(('e', "eax", None)), + _ => None, + }, + Self::reg_abcd => match ty.size().bits() { + 8 => Some(('l', "al", None)), + 16 => Some(('x', "ax", None)), + 32 if arch == InlineAsmArch::X86_64 => Some(('e', "eax", None)), + _ => None, + }, + Self::xmm_reg => None, + Self::ymm_reg => { + if ty.size().bits() <= 128 { + Some(('x', "xmm0", None)) + } else { + None + } + } + Self::zmm_reg => match ty.size().bits() { + 256 => Some(('y', "ymm0", None)), + 512 => None, + _ => Some(('x', "xmm0", None)), + }, + Self::kreg => None, + } + } + + pub fn default_modifier(self, arch: InlineAsmArch) -> Option<(char, &'static str)> { + match self { + Self::reg | Self::reg_abcd => { + if arch == InlineAsmArch::X86_64 { + Some(('r', "rax")) + } else { + Some(('e', "eax")) + } + } + Self::xmm_reg => Some(('x', "xmm0")), + Self::ymm_reg => Some(('y', "ymm0")), + Self::zmm_reg => Some(('z', "zmm0")), + Self::kreg => None, + } + } + + pub fn supported_types( + self, + arch: InlineAsmArch, + ) -> &'static [(InlineAsmType, Option<&'static str>)] { + match self { + Self::reg | Self::reg_abcd => { + if arch == InlineAsmArch::X86_64 { + types! { _: I8, I16, I32, I64, F32, F64; } + } else { + types! { _: I8, I16, I32, F32; } + } + } + Self::xmm_reg => types! { + "sse": I32, I64, F32, F64, + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2); + }, + Self::ymm_reg => types! { + "avx": I32, I64, F32, F64, + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2), + VecI8(32), VecI16(16), VecI32(8), VecI64(4), VecF32(8), VecF64(4); + }, + Self::zmm_reg => types! { + "avx512f": I32, I64, F32, F64, + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2), + VecI8(32), VecI16(16), VecI32(8), VecI64(4), VecF32(8), VecF64(4), + VecI8(64), VecI16(32), VecI32(16), VecI64(8), VecF32(16), VecF64(8); + }, + Self::kreg => types! { + "avx512f": I8, I16; + "avx512bw": I32, I64; + }, + } + } +} + +fn x86_64_only( + arch: InlineAsmArch, + _has_feature: impl FnMut(&str) -> bool, +) -> Result<(), &'static str> { + match arch { + InlineAsmArch::X86 => Err("register is only available on x86_64"), + InlineAsmArch::X86_64 => Ok(()), + _ => unreachable!(), + } +} + +def_regs! { + X86 X86InlineAsmReg X86InlineAsmRegClass { + ax: reg, reg_abcd = ["ax", "al", "eax", "rax"], + bx: reg, reg_abcd = ["bx", "bl", "ebx", "rbx"], + cx: reg, reg_abcd = ["cx", "cl", "ecx", "rcx"], + dx: reg, reg_abcd = ["dx", "dl", "edx", "rdx"], + si: reg = ["si", "sil", "esi", "rsi"], + di: reg = ["di", "dil", "edi", "rdi"], + r8: reg = ["r8", "r8b", "r8w", "r8d"] % x86_64_only, + r9: reg = ["r9", "r9b", "r9w", "r9d"] % x86_64_only, + r10: reg = ["r10", "r10b", "r10w", "r10d"] % x86_64_only, + r11: reg = ["r11", "r11b", "r11w", "r11d"] % x86_64_only, + r12: reg = ["r12", "r12b", "r12w", "r12d"] % x86_64_only, + r13: reg = ["r13", "r13b", "r13w", "r13d"] % x86_64_only, + r14: reg = ["r14", "r14b", "r14w", "r14d"] % x86_64_only, + r15: reg = ["r15", "r15b", "r15w", "r15d"] % x86_64_only, + xmm0: xmm_reg = ["xmm0"], + xmm1: xmm_reg = ["xmm1"], + xmm2: xmm_reg = ["xmm2"], + xmm3: xmm_reg = ["xmm3"], + xmm4: xmm_reg = ["xmm4"], + xmm5: xmm_reg = ["xmm5"], + xmm6: xmm_reg = ["xmm6"], + xmm7: xmm_reg = ["xmm7"], + xmm8: xmm_reg = ["xmm8"] % x86_64_only, + xmm9: xmm_reg = ["xmm9"] % x86_64_only, + xmm10: xmm_reg = ["xmm10"] % x86_64_only, + xmm11: xmm_reg = ["xmm11"] % x86_64_only, + xmm12: xmm_reg = ["xmm12"] % x86_64_only, + xmm13: xmm_reg = ["xmm13"] % x86_64_only, + xmm14: xmm_reg = ["xmm14"] % x86_64_only, + xmm15: xmm_reg = ["xmm15"] % x86_64_only, + ymm0: ymm_reg = ["ymm0"], + ymm1: ymm_reg = ["ymm1"], + ymm2: ymm_reg = ["ymm2"], + ymm3: ymm_reg = ["ymm3"], + ymm4: ymm_reg = ["ymm4"], + ymm5: ymm_reg = ["ymm5"], + ymm6: ymm_reg = ["ymm6"], + ymm7: ymm_reg = ["ymm7"], + ymm8: ymm_reg = ["ymm8"] % x86_64_only, + ymm9: ymm_reg = ["ymm9"] % x86_64_only, + ymm10: ymm_reg = ["ymm10"] % x86_64_only, + ymm11: ymm_reg = ["ymm11"] % x86_64_only, + ymm12: ymm_reg = ["ymm12"] % x86_64_only, + ymm13: ymm_reg = ["ymm13"] % x86_64_only, + ymm14: ymm_reg = ["ymm14"] % x86_64_only, + ymm15: ymm_reg = ["ymm15"] % x86_64_only, + zmm0: zmm_reg = ["zmm0"], + zmm1: zmm_reg = ["zmm1"], + zmm2: zmm_reg = ["zmm2"], + zmm3: zmm_reg = ["zmm3"], + zmm4: zmm_reg = ["zmm4"], + zmm5: zmm_reg = ["zmm5"], + zmm6: zmm_reg = ["zmm6"], + zmm7: zmm_reg = ["zmm7"], + zmm8: zmm_reg = ["zmm8"] % x86_64_only, + zmm9: zmm_reg = ["zmm9"] % x86_64_only, + zmm10: zmm_reg = ["zmm10"] % x86_64_only, + zmm11: zmm_reg = ["zmm11"] % x86_64_only, + zmm12: zmm_reg = ["zmm12"] % x86_64_only, + zmm13: zmm_reg = ["zmm13"] % x86_64_only, + zmm14: zmm_reg = ["zmm14"] % x86_64_only, + zmm15: zmm_reg = ["zmm15"] % x86_64_only, + zmm16: zmm_reg = ["zmm16", "xmm16", "ymm16"] % x86_64_only, + zmm17: zmm_reg = ["zmm17", "xmm17", "ymm17"] % x86_64_only, + zmm18: zmm_reg = ["zmm18", "xmm18", "ymm18"] % x86_64_only, + zmm19: zmm_reg = ["zmm19", "xmm19", "ymm19"] % x86_64_only, + zmm20: zmm_reg = ["zmm20", "xmm20", "ymm20"] % x86_64_only, + zmm21: zmm_reg = ["zmm21", "xmm21", "ymm21"] % x86_64_only, + zmm22: zmm_reg = ["zmm22", "xmm22", "ymm22"] % x86_64_only, + zmm23: zmm_reg = ["zmm23", "xmm23", "ymm23"] % x86_64_only, + zmm24: zmm_reg = ["zmm24", "xmm24", "ymm24"] % x86_64_only, + zmm25: zmm_reg = ["zmm25", "xmm25", "ymm25"] % x86_64_only, + zmm26: zmm_reg = ["zmm26", "xmm26", "ymm26"] % x86_64_only, + zmm27: zmm_reg = ["zmm27", "xmm27", "ymm27"] % x86_64_only, + zmm28: zmm_reg = ["zmm28", "xmm28", "ymm28"] % x86_64_only, + zmm29: zmm_reg = ["zmm29", "xmm29", "ymm29"] % x86_64_only, + zmm30: zmm_reg = ["zmm30", "xmm30", "ymm30"] % x86_64_only, + zmm31: zmm_reg = ["zmm31", "xmm31", "ymm31"] % x86_64_only, + k1: kreg = ["k1"], + k2: kreg = ["k2"], + k3: kreg = ["k3"], + k4: kreg = ["k4"], + k5: kreg = ["k5"], + k6: kreg = ["k6"], + k7: kreg = ["k7"], + "high byte registers are not currently supported as operands for inline asm" = + ["ah", "bh", "ch", "dh"], + "the frame pointer cannot be used as an operand for inline asm" = + ["bp", "bpl", "ebp", "rbp"], + "the stack pointer cannot be used as an operand for inline asm" = + ["sp", "spl", "esp", "rsp"], + "the instruction pointer cannot be used as an operand for inline asm" = + ["ip", "eip", "rip"], + "x87 registers are not currently supported as operands for inline asm" = + ["st", "st(0)", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)"], + "MMX registers are not currently supported as operands for inline asm" = + ["mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7"], + "the k0 AVX mask register cannot be used as an operand for inline asm" = ["k0"], + } +} + +impl X86InlineAsmReg { + pub fn emit( + self, + out: &mut dyn fmt::Write, + arch: InlineAsmArch, + modifier: Option, + ) -> fmt::Result { + let reg_default_modifier = match arch { + InlineAsmArch::X86 => 'e', + InlineAsmArch::X86_64 => 'r', + _ => unreachable!(), + }; + if self as u32 <= Self::dx as u32 { + let root = ['a', 'b', 'c', 'd'][self as usize - Self::ax as usize]; + match modifier.unwrap_or(reg_default_modifier) { + 'l' => write!(out, "{}l", root), + 'h' => write!(out, "{}h", root), + 'x' => write!(out, "{}x", root), + 'e' => write!(out, "e{}x", root), + 'r' => write!(out, "r{}x", root), + _ => unreachable!(), + } + } else if self as u32 <= Self::di as u32 { + let root = ["si", "di"][self as usize - Self::si as usize]; + match modifier.unwrap_or(reg_default_modifier) { + 'l' => write!(out, "{}l", root), + 'x' => write!(out, "{}", root), + 'e' => write!(out, "e{}", root), + 'r' => write!(out, "r{}", root), + _ => unreachable!(), + } + } else if self as u32 <= Self::r15 as u32 { + let index = self as u32 - Self::r8 as u32 + 8; + match modifier.unwrap_or(reg_default_modifier) { + 'l' => write!(out, "r{}b", index), + 'x' => write!(out, "r{}w", index), + 'e' => write!(out, "r{}d", index), + 'r' => write!(out, "r{}", index), + _ => unreachable!(), + } + } else if self as u32 <= Self::xmm15 as u32 { + let prefix = modifier.unwrap_or('x'); + let index = self as u32 - Self::xmm0 as u32; + write!(out, "{}{}", prefix, index) + } else if self as u32 <= Self::ymm15 as u32 { + let prefix = modifier.unwrap_or('y'); + let index = self as u32 - Self::ymm0 as u32; + write!(out, "{}{}", prefix, index) + } else if self as u32 <= Self::zmm31 as u32 { + let prefix = modifier.unwrap_or('z'); + let index = self as u32 - Self::zmm0 as u32; + write!(out, "{}{}", prefix, index) + } else { + let index = self as u32 - Self::k1 as u32 + 1; + write!(out, "k{}", index) + } + } + + pub fn overlapping_regs(self, mut cb: impl FnMut(X86InlineAsmReg)) { + macro_rules! reg_conflicts { + ($($x:ident : $y:ident : $z:ident,)*) => { + match self { + $( + Self::$x | Self::$y | Self::$z => { + cb(Self::$x); + cb(Self::$y); + cb(Self::$z); + } + )* + r => cb(r), + } + }; + } + reg_conflicts! { + xmm0 : ymm0 : zmm0, + xmm1 : ymm1 : zmm1, + xmm2 : ymm2 : zmm2, + xmm3 : ymm3 : zmm3, + xmm4 : ymm4 : zmm4, + xmm5 : ymm5 : zmm5, + xmm6 : ymm6 : zmm6, + xmm7 : ymm7 : zmm7, + xmm8 : ymm8 : zmm8, + xmm9 : ymm9 : zmm9, + xmm10 : ymm10 : zmm10, + xmm11 : ymm11 : zmm11, + xmm12 : ymm12 : zmm12, + xmm13 : ymm13 : zmm13, + xmm14 : ymm14 : zmm14, + xmm15 : ymm15 : zmm15, + } + } +} diff --git a/src/librustc_target/lib.rs b/src/librustc_target/lib.rs index a0229a5daf0..0e634587b4a 100644 --- a/src/librustc_target/lib.rs +++ b/src/librustc_target/lib.rs @@ -25,9 +25,10 @@ extern crate rustc_macros; extern crate log; pub mod abi; +pub mod asm; pub mod spec; /// Requirements for a `StableHashingContext` to be used in this crate. /// This is a hack to allow using the `HashStable_Generic` derive macro /// instead of implementing everything in librustc_middle. -pub trait HashStableContext {} +pub trait HashStableContext: rustc_span::HashStableContext {} From 813a9fc4f121d808c48ebee47a25a773120edd45 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 22 Jan 2020 14:20:27 +0000 Subject: [PATCH 03/36] Add asm! to AST --- Cargo.lock | 2 + src/librustc_ast/Cargo.toml | 1 + src/librustc_ast/ast.rs | 57 ++++++++++++++- src/librustc_ast/mut_visit.rs | 21 ++++++ src/librustc_ast/visit.rs | 21 ++++++ src/librustc_ast_pretty/Cargo.toml | 1 + src/librustc_ast_pretty/pprust.rs | 112 +++++++++++++++++++++++++++++ 7 files changed, 214 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 22baa79dc62..5a9a1116e67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3631,6 +3631,7 @@ dependencies = [ "rustc_lexer", "rustc_macros", "rustc_span", + "rustc_target", "scoped-tls", "serialize", "smallvec 1.4.0", @@ -3678,6 +3679,7 @@ dependencies = [ "log", "rustc_ast", "rustc_span", + "rustc_target", ] [[package]] diff --git a/src/librustc_ast/Cargo.toml b/src/librustc_ast/Cargo.toml index 867bcf79232..020e6a84d45 100644 --- a/src/librustc_ast/Cargo.toml +++ b/src/librustc_ast/Cargo.toml @@ -19,3 +19,4 @@ rustc_index = { path = "../librustc_index" } rustc_lexer = { path = "../librustc_lexer" } rustc_macros = { path = "../librustc_macros" } smallvec = { version = "1.0", features = ["union", "may_dangle"] } +rustc_target = { path = "../librustc_target" } diff --git a/src/librustc_ast/ast.rs b/src/librustc_ast/ast.rs index 15bf4b70e2f..85e9438472a 100644 --- a/src/librustc_ast/ast.rs +++ b/src/librustc_ast/ast.rs @@ -34,6 +34,7 @@ use rustc_serialize::{self, Decoder, Encoder}; use rustc_span::source_map::{respan, Spanned}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{Span, DUMMY_SP}; +use rustc_target::asm::{InlineAsmOptions, InlineAsmTemplatePiece}; use std::convert::TryFrom; use std::fmt; @@ -1121,7 +1122,7 @@ impl Expr { ExprKind::Break(..) => ExprPrecedence::Break, ExprKind::Continue(..) => ExprPrecedence::Continue, ExprKind::Ret(..) => ExprPrecedence::Ret, - ExprKind::LlvmInlineAsm(..) => ExprPrecedence::InlineAsm, + ExprKind::InlineAsm(..) | ExprKind::LlvmInlineAsm(..) => ExprPrecedence::InlineAsm, ExprKind::MacCall(..) => ExprPrecedence::Mac, ExprKind::Struct(..) => ExprPrecedence::Struct, ExprKind::Repeat(..) => ExprPrecedence::Repeat, @@ -1250,6 +1251,8 @@ pub enum ExprKind { /// A `return`, with an optional value to be returned. Ret(Option>), + /// Output of the `asm!()` macro. + InlineAsm(InlineAsm), /// Output of the `llvm_asm!()` macro. LlvmInlineAsm(P), @@ -1864,6 +1867,58 @@ pub enum TraitObjectSyntax { None, } +/// Inline assembly operand explicit register or register class. +/// +/// E.g., `"eax"` as in `asm!("mov eax, 2", out("eax") result)`. +#[derive(Clone, Copy, RustcEncodable, RustcDecodable, Debug)] +pub enum InlineAsmRegOrRegClass { + Reg(Symbol), + RegClass(Symbol), +} + +/// Inline assembly operand. +/// +/// E.g., `out("eax") result` as in `asm!("mov eax, 2", out("eax") result)`. +#[derive(Clone, RustcEncodable, RustcDecodable, Debug)] +pub enum InlineAsmOperand { + In { + reg: InlineAsmRegOrRegClass, + expr: P, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Option>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: P, + }, + SplitInOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_expr: P, + out_expr: Option>, + }, + Const { + expr: P, + }, + Sym { + expr: P, + }, +} + +/// Inline assembly. +/// +/// E.g., `asm!("NOP");`. +#[derive(Clone, RustcEncodable, RustcDecodable, Debug)] +pub struct InlineAsm { + pub template: Vec, + pub operands: Vec<(InlineAsmOperand, Span)>, + pub options: InlineAsmOptions, +} + /// Inline assembly dialect. /// /// E.g., `"intel"` as in `llvm_asm!("mov eax, 2" : "={eax}"(result) : : : "intel")`. diff --git a/src/librustc_ast/mut_visit.rs b/src/librustc_ast/mut_visit.rs index d533aecf2df..2c575c3e288 100644 --- a/src/librustc_ast/mut_visit.rs +++ b/src/librustc_ast/mut_visit.rs @@ -1205,6 +1205,27 @@ pub fn noop_visit_expr(Expr { kind, id, span, attrs }: &mut Expr, ExprKind::Ret(expr) => { visit_opt(expr, |expr| vis.visit_expr(expr)); } + ExprKind::InlineAsm(asm) => { + for (op, _) in &mut asm.operands { + match op { + InlineAsmOperand::In { expr, .. } + | InlineAsmOperand::InOut { expr, .. } + | InlineAsmOperand::Const { expr, .. } + | InlineAsmOperand::Sym { expr, .. } => vis.visit_expr(expr), + InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + vis.visit_expr(expr); + } + } + InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + vis.visit_expr(in_expr); + if let Some(out_expr) = out_expr { + vis.visit_expr(out_expr); + } + } + } + } + } ExprKind::LlvmInlineAsm(asm) => { let LlvmInlineAsm { asm: _, diff --git a/src/librustc_ast/visit.rs b/src/librustc_ast/visit.rs index 63f483663bf..41c02734442 100644 --- a/src/librustc_ast/visit.rs +++ b/src/librustc_ast/visit.rs @@ -818,6 +818,27 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) { } ExprKind::MacCall(ref mac) => visitor.visit_mac(mac), ExprKind::Paren(ref subexpression) => visitor.visit_expr(subexpression), + ExprKind::InlineAsm(ref ia) => { + for (op, _) in &ia.operands { + match op { + InlineAsmOperand::In { expr, .. } + | InlineAsmOperand::InOut { expr, .. } + | InlineAsmOperand::Const { expr, .. } + | InlineAsmOperand::Sym { expr, .. } => visitor.visit_expr(expr), + InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + visitor.visit_expr(expr); + } + } + InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + visitor.visit_expr(in_expr); + if let Some(out_expr) = out_expr { + visitor.visit_expr(out_expr); + } + } + } + } + } ExprKind::LlvmInlineAsm(ref ia) => { for &(_, ref input) in &ia.inputs { visitor.visit_expr(input) diff --git a/src/librustc_ast_pretty/Cargo.toml b/src/librustc_ast_pretty/Cargo.toml index 81d98721089..6c076d2c5b8 100644 --- a/src/librustc_ast_pretty/Cargo.toml +++ b/src/librustc_ast_pretty/Cargo.toml @@ -13,3 +13,4 @@ doctest = false log = "0.4" rustc_span = { path = "../librustc_span" } rustc_ast = { path = "../librustc_ast" } +rustc_target = { path = "../librustc_target" } diff --git a/src/librustc_ast_pretty/pprust.rs b/src/librustc_ast_pretty/pprust.rs index e6803fdd7f1..1f371f15f18 100644 --- a/src/librustc_ast_pretty/pprust.rs +++ b/src/librustc_ast_pretty/pprust.rs @@ -4,6 +4,7 @@ use crate::pp::{self, Breaks}; use rustc_ast::ast::{self, BlockCheckMode, PatKind, RangeEnd, RangeSyntax}; use rustc_ast::ast::{Attribute, GenericArg, MacArgs}; use rustc_ast::ast::{GenericBound, SelfKind, TraitBoundModifier}; +use rustc_ast::ast::{InlineAsmOperand, InlineAsmRegOrRegClass}; use rustc_ast::attr; use rustc_ast::ptr::P; use rustc_ast::token::{self, BinOpToken, DelimToken, Nonterminal, Token, TokenKind}; @@ -14,6 +15,7 @@ use rustc_span::edition::Edition; use rustc_span::source_map::{SourceMap, Spanned}; use rustc_span::symbol::{kw, sym, Ident, IdentPrinter, Symbol}; use rustc_span::{BytePos, FileName, Span}; +use rustc_target::asm::{InlineAsmOptions, InlineAsmTemplatePiece}; use std::borrow::Cow; @@ -2014,6 +2016,116 @@ impl<'a> State<'a> { self.print_expr_maybe_paren(expr, parser::PREC_JUMP); } } + ast::ExprKind::InlineAsm(ref a) => { + enum AsmArg<'a> { + Template(String), + Operand(&'a InlineAsmOperand), + Options(InlineAsmOptions), + } + + let mut args = vec![]; + args.push(AsmArg::Template(InlineAsmTemplatePiece::to_string(&a.template))); + args.extend(a.operands.iter().map(|(o, _)| AsmArg::Operand(o))); + if !a.options.is_empty() { + args.push(AsmArg::Options(a.options)); + } + + self.word("asm!"); + self.popen(); + self.commasep(Consistent, &args, |s, arg| match arg { + AsmArg::Template(template) => s.print_string(&template, ast::StrStyle::Cooked), + AsmArg::Operand(op) => { + let print_reg_or_class = |s: &mut Self, r: &InlineAsmRegOrRegClass| match r + { + InlineAsmRegOrRegClass::Reg(r) => { + s.print_string(&r.as_str(), ast::StrStyle::Cooked) + } + InlineAsmRegOrRegClass::RegClass(r) => s.word(r.to_string()), + }; + match op { + InlineAsmOperand::In { reg, expr } => { + s.word("in"); + s.popen(); + print_reg_or_class(s, reg); + s.pclose(); + s.space(); + s.print_expr(expr); + } + InlineAsmOperand::Out { reg, late, expr } => { + s.word(if *late { "lateout" } else { "out" }); + s.popen(); + print_reg_or_class(s, reg); + s.pclose(); + s.space(); + match expr { + Some(expr) => s.print_expr(expr), + None => s.word("_"), + } + } + InlineAsmOperand::InOut { reg, late, expr } => { + s.word(if *late { "inlateout" } else { "inout" }); + s.popen(); + print_reg_or_class(s, reg); + s.pclose(); + s.space(); + s.print_expr(expr); + } + InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => { + s.word(if *late { "inlateout" } else { "inout" }); + s.popen(); + print_reg_or_class(s, reg); + s.pclose(); + s.space(); + s.print_expr(in_expr); + s.space(); + s.word_space("=>"); + match out_expr { + Some(out_expr) => s.print_expr(out_expr), + None => s.word("_"), + } + } + InlineAsmOperand::Const { expr } => { + s.word("const"); + s.space(); + s.print_expr(expr); + } + InlineAsmOperand::Sym { expr } => { + s.word("sym"); + s.space(); + s.print_expr(expr); + } + } + } + AsmArg::Options(opts) => { + s.word("options"); + s.popen(); + let mut options = vec![]; + if opts.contains(InlineAsmOptions::PURE) { + options.push("pure"); + } + if opts.contains(InlineAsmOptions::NOMEM) { + options.push("nomem"); + } + if opts.contains(InlineAsmOptions::READONLY) { + options.push("readonly"); + } + if opts.contains(InlineAsmOptions::PRESERVES_FLAGS) { + options.push("preserves_flags"); + } + if opts.contains(InlineAsmOptions::NORETURN) { + options.push("noreturn"); + } + if opts.contains(InlineAsmOptions::NOSTACK) { + options.push("nostack"); + } + s.commasep(Inconsistent, &options, |s, &opt| { + s.word(opt); + }); + s.pclose(); + } + }); + self.pclose(); + } ast::ExprKind::LlvmInlineAsm(ref a) => { self.s.word("llvm_asm!"); self.popen(); From a0adf53bc97d6e79c8c2e9b3f8a548bc65c7adf6 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 12 Feb 2020 15:47:43 +0000 Subject: [PATCH 04/36] Implement asm! in librustc_builtin_macros --- src/libfmt_macros/lib.rs | 155 +++++- src/libfmt_macros/tests.rs | 4 +- src/librustc_builtin_macros/asm.rs | 527 ++++++++++++++++++ src/librustc_builtin_macros/format.rs | 119 +--- src/librustc_builtin_macros/lib.rs | 3 +- src/librustc_span/symbol.rs | 18 +- .../traits/on_unimplemented.rs | 6 +- 7 files changed, 712 insertions(+), 120 deletions(-) create mode 100644 src/librustc_builtin_macros/asm.rs diff --git a/src/libfmt_macros/lib.rs b/src/libfmt_macros/lib.rs index 29276eead71..677c027f17b 100644 --- a/src/libfmt_macros/lib.rs +++ b/src/libfmt_macros/lib.rs @@ -27,6 +27,15 @@ use std::string; use rustc_span::{InnerSpan, Symbol}; +/// The type of format string that we are parsing. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ParseMode { + /// A normal format string as per `format_args!`. + Format, + /// An inline assembly template string for `asm!`. + InlineAsm, +} + #[derive(Copy, Clone)] struct InnerOffset(usize); @@ -163,6 +172,7 @@ pub struct ParseError { /// This is a recursive-descent parser for the sake of simplicity, and if /// necessary there's probably lots of room for improvement performance-wise. pub struct Parser<'a> { + mode: ParseMode, input: &'a str, cur: iter::Peekable>, /// Error messages accumulated during parsing @@ -179,6 +189,8 @@ pub struct Parser<'a> { last_opening_brace: Option, /// Whether the source string is comes from `println!` as opposed to `format!` or `print!` append_newline: bool, + /// Whether this formatting string is a literal or it comes from a macro. + is_literal: bool, } impl<'a> Iterator for Parser<'a> { @@ -201,7 +213,9 @@ impl<'a> Iterator for Parser<'a> { if let Some(end) = self.must_consume('}') { let start = self.to_span_index(pos); let end = self.to_span_index(end + 1); - self.arg_places.push(start.to(end)); + if self.is_literal { + self.arg_places.push(start.to(end)); + } } Some(NextArgument(arg)) } @@ -235,10 +249,13 @@ impl<'a> Parser<'a> { pub fn new( s: &'a str, style: Option, - skips: Vec, + snippet: Option, append_newline: bool, + mode: ParseMode, ) -> Parser<'a> { + let (skips, is_literal) = find_skips_from_snippet(snippet, style); Parser { + mode, input: s, cur: s.char_indices().peekable(), errors: vec![], @@ -248,6 +265,7 @@ impl<'a> Parser<'a> { skips, last_opening_brace: None, append_newline, + is_literal, } } @@ -426,7 +444,10 @@ impl<'a> Parser<'a> { /// Parses an `Argument` structure, or what's contained within braces inside the format string. fn argument(&mut self) -> Argument<'a> { let pos = self.position(); - let format = self.format(); + let format = match self.mode { + ParseMode::Format => self.format(), + ParseMode::InlineAsm => self.inline_asm(), + }; // Resolve position after parsing format spec. let pos = match pos { @@ -574,6 +595,36 @@ impl<'a> Parser<'a> { spec } + /// Parses an inline assembly template modifier at the current position, returning the modifier + /// in the `ty` field of the `FormatSpec` struct. + fn inline_asm(&mut self) -> FormatSpec<'a> { + let mut spec = FormatSpec { + fill: None, + align: AlignUnknown, + flags: 0, + precision: CountImplied, + precision_span: None, + width: CountImplied, + width_span: None, + ty: &self.input[..0], + ty_span: None, + }; + if !self.consume(':') { + return spec; + } + + let ty_span_start = self.cur.peek().map(|(pos, _)| *pos); + spec.ty = self.word(); + let ty_span_end = self.cur.peek().map(|(pos, _)| *pos); + if !spec.ty.is_empty() { + spec.ty_span = ty_span_start + .and_then(|s| ty_span_end.map(|e| (s, e))) + .map(|(start, end)| self.to_span_index(start).to(self.to_span_index(end))); + } + + spec + } + /// Parses a `Count` parameter at the current position. This does not check /// for 'CountIsNextParam' because that is only used in precision, not /// width. @@ -652,5 +703,103 @@ impl<'a> Parser<'a> { } } +/// Finds the indices of all characters that have been processed and differ between the actual +/// written code (code snippet) and the `InternedString` that gets processed in the `Parser` +/// in order to properly synthethise the intra-string `Span`s for error diagnostics. +fn find_skips_from_snippet( + snippet: Option, + str_style: Option, +) -> (Vec, bool) { + let snippet = match snippet { + Some(ref s) if s.starts_with('"') || s.starts_with("r#") => s, + _ => return (vec![], false), + }; + + fn find_skips(snippet: &str, is_raw: bool) -> Vec { + let mut eat_ws = false; + let mut s = snippet.chars().enumerate().peekable(); + let mut skips = vec![]; + while let Some((pos, c)) = s.next() { + match (c, s.peek()) { + // skip whitespace and empty lines ending in '\\' + ('\\', Some((next_pos, '\n'))) if !is_raw => { + eat_ws = true; + skips.push(pos); + skips.push(*next_pos); + let _ = s.next(); + } + ('\\', Some((next_pos, '\n' | 'n' | 't'))) if eat_ws => { + skips.push(pos); + skips.push(*next_pos); + let _ = s.next(); + } + (' ' | '\n' | '\t', _) if eat_ws => { + skips.push(pos); + } + ('\\', Some((next_pos, 'n' | 't' | '0' | '\\' | '\'' | '\"'))) => { + skips.push(*next_pos); + let _ = s.next(); + } + ('\\', Some((_, 'x'))) if !is_raw => { + for _ in 0..3 { + // consume `\xAB` literal + if let Some((pos, _)) = s.next() { + skips.push(pos); + } else { + break; + } + } + } + ('\\', Some((_, 'u'))) if !is_raw => { + if let Some((pos, _)) = s.next() { + skips.push(pos); + } + if let Some((next_pos, next_c)) = s.next() { + if next_c == '{' { + skips.push(next_pos); + let mut i = 0; // consume up to 6 hexanumeric chars + closing `}` + while let (Some((next_pos, c)), true) = (s.next(), i < 7) { + if c.is_digit(16) { + skips.push(next_pos); + } else if c == '}' { + skips.push(next_pos); + break; + } else { + break; + } + i += 1; + } + } else if next_c.is_digit(16) { + skips.push(next_pos); + // We suggest adding `{` and `}` when appropriate, accept it here as if + // it were correct + let mut i = 0; // consume up to 6 hexanumeric chars + while let (Some((next_pos, c)), _) = (s.next(), i < 6) { + if c.is_digit(16) { + skips.push(next_pos); + } else { + break; + } + i += 1; + } + } + } + } + _ if eat_ws => { + // `take_while(|c| c.is_whitespace())` + eat_ws = false; + } + _ => {} + } + } + skips + } + + let r_start = str_style.map(|r| r + 1).unwrap_or(0); + let r_end = str_style.map(|r| r).unwrap_or(0); + let s = &snippet[r_start + 1..snippet.len() - r_end - 1]; + (find_skips(s, str_style.is_some()), true) +} + #[cfg(test)] mod tests; diff --git a/src/libfmt_macros/tests.rs b/src/libfmt_macros/tests.rs index 98c2a17f0dd..a0cef78f924 100644 --- a/src/libfmt_macros/tests.rs +++ b/src/libfmt_macros/tests.rs @@ -1,7 +1,7 @@ use super::*; fn same(fmt: &'static str, p: &[Piece<'static>]) { - let parser = Parser::new(fmt, None, vec![], false); + let parser = Parser::new(fmt, None, vec![], false, ParseMode::Format); assert_eq!(parser.collect::>>(), p); } @@ -20,7 +20,7 @@ fn fmtdflt() -> FormatSpec<'static> { } fn musterr(s: &str) { - let mut p = Parser::new(s, None, vec![], false); + let mut p = Parser::new(s, None, vec![], false, ParseMode::Format); p.next(); assert!(!p.errors.is_empty()); } diff --git a/src/librustc_builtin_macros/asm.rs b/src/librustc_builtin_macros/asm.rs new file mode 100644 index 00000000000..943ed42e202 --- /dev/null +++ b/src/librustc_builtin_macros/asm.rs @@ -0,0 +1,527 @@ +use fmt_macros as parse; + +use rustc_ast::ast; +use rustc_ast::ptr::P; +use rustc_ast::token; +use rustc_ast::tokenstream::TokenStream; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_errors::{Applicability, DiagnosticBuilder}; +use rustc_expand::base::{self, *}; +use rustc_parse::parser::Parser; +use rustc_span::symbol::{kw, sym, Symbol}; +use rustc_span::{InnerSpan, Span}; +use rustc_target::asm::{InlineAsmOptions, InlineAsmTemplatePiece}; + +struct AsmArgs { + template: P, + operands: Vec<(ast::InlineAsmOperand, Span)>, + named_args: FxHashMap, + reg_args: FxHashSet, + options: InlineAsmOptions, + options_span: Option, +} + +fn parse_args<'a>( + ecx: &mut ExtCtxt<'a>, + sp: Span, + tts: TokenStream, +) -> Result> { + let mut p = ecx.new_parser_from_tts(tts); + + if p.token == token::Eof { + return Err(ecx.struct_span_err(sp, "requires at least a template string argument")); + } + + // Detect use of the legacy llvm_asm! syntax (which used to be called asm!) + if p.look_ahead(1, |t| *t == token::Colon || *t == token::ModSep) { + let mut err = ecx.struct_span_err(sp, "legacy asm! syntax is no longer supported"); + + // Find the span of the "asm!" so that we can offer an automatic suggestion + let asm_span = sp.from_inner(InnerSpan::new(0, 4)); + if let Ok(s) = ecx.source_map().span_to_snippet(asm_span) { + if s == "asm!" { + err.span_suggestion( + asm_span, + "replace with", + "llvm_asm!".into(), + Applicability::MachineApplicable, + ); + } + } + return Err(err); + } + + let template = p.parse_expr()?; + let mut args = AsmArgs { + template, + operands: vec![], + named_args: FxHashMap::default(), + reg_args: FxHashSet::default(), + options: InlineAsmOptions::empty(), + options_span: None, + }; + + let mut first = true; + while p.token != token::Eof { + if !p.eat(&token::Comma) { + if first { + // After `asm!(""` we always expect *only* a comma... + let mut err = ecx.struct_span_err(p.token.span, "expected token: `,`"); + err.span_label(p.token.span, "expected `,`"); + p.maybe_annotate_with_ascription(&mut err, false); + return Err(err); + } else { + // ...after that delegate to `expect` to also include the other expected tokens. + return Err(p.expect(&token::Comma).err().unwrap()); + } + } + first = false; + if p.token == token::Eof { + break; + } // accept trailing commas + + let span_start = p.token.span; + + // Parse options + if p.eat(&token::Ident(sym::options, false)) { + p.expect(&token::OpenDelim(token::DelimToken::Paren))?; + + while !p.eat(&token::CloseDelim(token::DelimToken::Paren)) { + if p.eat(&token::Ident(sym::pure, false)) { + args.options |= InlineAsmOptions::PURE; + } else if p.eat(&token::Ident(sym::nomem, false)) { + args.options |= InlineAsmOptions::NOMEM; + } else if p.eat(&token::Ident(sym::readonly, false)) { + args.options |= InlineAsmOptions::READONLY; + } else if p.eat(&token::Ident(sym::preserves_flags, false)) { + args.options |= InlineAsmOptions::PRESERVES_FLAGS; + } else if p.eat(&token::Ident(sym::noreturn, false)) { + args.options |= InlineAsmOptions::NORETURN; + } else { + p.expect(&token::Ident(sym::nostack, false))?; + args.options |= InlineAsmOptions::NOSTACK; + } + + // Allow trailing commas + if p.eat(&token::CloseDelim(token::DelimToken::Paren)) { + break; + } + p.expect(&token::Comma)?; + } + + let new_span = span_start.to(p.prev_token.span); + if let Some(options_span) = args.options_span { + ecx.struct_span_err(new_span, "asm options cannot be specified twice") + .span_label(options_span, "previously here") + .span_label(new_span, "duplicate options") + .emit(); + } else { + args.options_span = Some(new_span); + } + continue; + } + + // Parse operand names + let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) { + let (ident, _) = p.token.ident().unwrap(); + p.bump(); + p.expect(&token::Eq)?; + Some(ident.name) + } else { + None + }; + + fn parse_reg<'a>( + p: &mut Parser<'a>, + explicit_reg: &mut bool, + ) -> Result> { + p.expect(&token::OpenDelim(token::DelimToken::Paren))?; + let result = match p.token.kind { + token::Ident(name, false) => ast::InlineAsmRegOrRegClass::RegClass(name), + token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => { + *explicit_reg = true; + ast::InlineAsmRegOrRegClass::Reg(symbol) + } + _ => { + return Err(p.struct_span_err( + p.token.span, + "expected register class or explicit register", + )); + } + }; + p.bump(); + p.expect(&token::CloseDelim(token::DelimToken::Paren))?; + Ok(result) + }; + + let mut explicit_reg = false; + let op = if p.eat(&token::Ident(kw::In, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + let expr = p.parse_expr()?; + ast::InlineAsmOperand::In { reg, expr } + } else if p.eat(&token::Ident(sym::out, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + let expr = if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) }; + ast::InlineAsmOperand::Out { reg, expr, late: false } + } else if p.eat(&token::Ident(sym::lateout, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + let expr = if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) }; + ast::InlineAsmOperand::Out { reg, expr, late: true } + } else if p.eat(&token::Ident(sym::inout, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + let expr = p.parse_expr()?; + if p.eat(&token::FatArrow) { + let out_expr = + if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) }; + ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: false } + } else { + ast::InlineAsmOperand::InOut { reg, expr, late: false } + } + } else if p.eat(&token::Ident(sym::inlateout, false)) { + let reg = parse_reg(&mut p, &mut explicit_reg)?; + let expr = p.parse_expr()?; + if p.eat(&token::FatArrow) { + let out_expr = + if p.eat_keyword(kw::Underscore) { None } else { Some(p.parse_expr()?) }; + ast::InlineAsmOperand::SplitInOut { reg, in_expr: expr, out_expr, late: true } + } else { + ast::InlineAsmOperand::InOut { reg, expr, late: true } + } + } else if p.eat(&token::Ident(kw::Const, false)) { + let expr = p.parse_expr()?; + ast::InlineAsmOperand::Const { expr } + } else { + p.expect(&token::Ident(sym::sym, false))?; + let expr = p.parse_expr()?; + match expr.kind { + ast::ExprKind::Path(..) => {} + _ => { + let err = ecx + .struct_span_err(expr.span, "argument to `sym` must be a path expression"); + return Err(err); + } + } + ast::InlineAsmOperand::Sym { expr } + }; + + let span = span_start.to(p.prev_token.span); + let slot = args.operands.len(); + args.operands.push((op, span)); + + // Validate the order of named, positional & explicit register operands and options. We do + // this at the end once we have the full span of the argument available. + if let Some(options_span) = args.options_span { + ecx.struct_span_err(span, "arguments are not allowed after options") + .span_label(options_span, "previous options") + .span_label(span, "argument") + .emit(); + } + if explicit_reg { + if name.is_some() { + ecx.struct_span_err(span, "explicit register arguments cannot have names").emit(); + } + args.reg_args.insert(slot); + } else if let Some(name) = name { + if let Some(&prev) = args.named_args.get(&name) { + ecx.struct_span_err(span, &format!("duplicate argument named `{}`", name)) + .span_label(args.operands[prev].1, "previously here") + .span_label(span, "duplicate argument") + .emit(); + continue; + } + if !args.reg_args.is_empty() { + let mut err = ecx.struct_span_err( + span, + "named arguments cannot follow explicit register arguments", + ); + err.span_label(span, "named argument"); + for pos in &args.reg_args { + err.span_label(args.operands[*pos].1, "explicit register argument"); + } + err.emit(); + } + args.named_args.insert(name, slot); + } else { + if !args.named_args.is_empty() || !args.reg_args.is_empty() { + let mut err = ecx.struct_span_err( + span, + "positional arguments cannot follow named arguments \ + or explicit register arguments", + ); + err.span_label(span, "positional argument"); + for pos in args.named_args.values() { + err.span_label(args.operands[*pos].1, "named argument"); + } + for pos in &args.reg_args { + err.span_label(args.operands[*pos].1, "explicit register argument"); + } + err.emit(); + } + } + } + + if args.options.contains(InlineAsmOptions::NOMEM) + && args.options.contains(InlineAsmOptions::READONLY) + { + let span = args.options_span.unwrap(); + ecx.struct_span_err(span, "the `nomem` and `readonly` options are mutually exclusive") + .emit(); + } + if args.options.contains(InlineAsmOptions::PURE) + && args.options.contains(InlineAsmOptions::NORETURN) + { + let span = args.options_span.unwrap(); + ecx.struct_span_err(span, "the `pure` and `noreturn` options are mutually exclusive") + .emit(); + } + if args.options.contains(InlineAsmOptions::PURE) + && !args.options.intersects(InlineAsmOptions::NOMEM | InlineAsmOptions::READONLY) + { + let span = args.options_span.unwrap(); + ecx.struct_span_err( + span, + "the `pure` option must be combined with either `nomem` or `readonly`", + ) + .emit(); + } + + let mut have_real_output = false; + let mut outputs_sp = vec![]; + for (op, op_sp) in &args.operands { + match op { + ast::InlineAsmOperand::Out { expr, .. } + | ast::InlineAsmOperand::SplitInOut { out_expr: expr, .. } => { + outputs_sp.push(*op_sp); + have_real_output |= expr.is_some(); + } + ast::InlineAsmOperand::InOut { .. } => { + outputs_sp.push(*op_sp); + have_real_output = true; + } + _ => {} + } + } + if args.options.contains(InlineAsmOptions::PURE) && !have_real_output { + ecx.struct_span_err( + args.options_span.unwrap(), + "asm with `pure` option must have at least one output", + ) + .emit(); + } + if args.options.contains(InlineAsmOptions::NORETURN) && !outputs_sp.is_empty() { + let err = ecx + .struct_span_err(outputs_sp, "asm outputs are not allowed with the `noreturn` option"); + + // Bail out now since this is likely to confuse MIR + return Err(err); + } + + Ok(args) +} + +fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, sp: Span, args: AsmArgs) -> P { + let msg = "asm template must be a string literal"; + let template_sp = args.template.span; + let (template_str, template_style, template_span) = + match expr_to_spanned_string(ecx, args.template, msg) { + Ok(template) => template, + Err(err) => { + if let Some(mut err) = err { + err.emit(); + } + return DummyResult::raw_expr(sp, true); + } + }; + + let str_style = match template_style { + ast::StrStyle::Cooked => None, + ast::StrStyle::Raw(raw) => Some(raw as usize), + }; + + let template_str = &template_str.as_str(); + let template_snippet = ecx.source_map().span_to_snippet(template_sp).ok(); + let mut parser = parse::Parser::new( + template_str, + str_style, + template_snippet, + false, + parse::ParseMode::InlineAsm, + ); + + let mut unverified_pieces = Vec::new(); + while let Some(piece) = parser.next() { + if !parser.errors.is_empty() { + break; + } else { + unverified_pieces.push(piece); + } + } + + if !parser.errors.is_empty() { + let err = parser.errors.remove(0); + let err_sp = template_span.from_inner(err.span); + let mut e = ecx + .struct_span_err(err_sp, &format!("invalid asm template string: {}", err.description)); + e.span_label(err_sp, err.label + " in asm template string"); + if let Some(note) = err.note { + e.note(¬e); + } + if let Some((label, span)) = err.secondary_label { + let err_sp = template_span.from_inner(span); + e.span_label(err_sp, label); + } + e.emit(); + return DummyResult::raw_expr(sp, true); + } + + // Register operands are implicitly used since they are not allowed to be + // referenced in the template string. + let mut used = vec![false; args.operands.len()]; + for pos in &args.reg_args { + used[*pos] = true; + } + + let named_pos: FxHashSet = args.named_args.values().cloned().collect(); + let mut arg_spans = parser.arg_places.iter().map(|span| template_span.from_inner(*span)); + let mut template = vec![]; + for piece in unverified_pieces { + match piece { + parse::Piece::String(s) => template.push(InlineAsmTemplatePiece::String(s.to_string())), + parse::Piece::NextArgument(arg) => { + let span = arg_spans.next().unwrap_or(template_sp); + + let operand_idx = match arg.position { + parse::ArgumentIs(idx) | parse::ArgumentImplicitlyIs(idx) => { + if idx >= args.operands.len() + || named_pos.contains(&idx) + || args.reg_args.contains(&idx) + { + let msg = format!("invalid reference to argument at index {}", idx); + let mut err = ecx.struct_span_err(span, &msg); + err.span_label(span, "from here"); + + let positional_args = + args.operands.len() - args.named_args.len() - args.reg_args.len(); + let positional = if positional_args != args.operands.len() { + "positional " + } else { + "" + }; + let msg = match positional_args { + 0 => format!("no {}arguments were given", positional), + 1 => format!("there is 1 {}argument", positional), + x => format!("there are {} {}arguments", x, positional), + }; + err.note(&msg); + + if named_pos.contains(&idx) { + err.span_label(args.operands[idx].1, "named argument"); + err.span_note( + args.operands[idx].1, + "named arguments cannot be referenced by position", + ); + } else if args.reg_args.contains(&idx) { + err.span_label(args.operands[idx].1, "explicit register argument"); + err.span_note( + args.operands[idx].1, + "explicit register arguments cannot be used in the asm template", + ); + } + err.emit(); + None + } else { + Some(idx) + } + } + parse::ArgumentNamed(name) => match args.named_args.get(&name) { + Some(&idx) => Some(idx), + None => { + let msg = format!("there is no argument named `{}`", name); + ecx.struct_span_err(span, &msg[..]).emit(); + None + } + }, + }; + + let mut chars = arg.format.ty.chars(); + let mut modifier = chars.next(); + if !chars.next().is_none() { + let span = arg + .format + .ty_span + .map(|sp| template_sp.from_inner(sp)) + .unwrap_or(template_sp); + ecx.struct_span_err(span, "asm template modifier must be a single character") + .emit(); + modifier = None; + } + + if let Some(operand_idx) = operand_idx { + used[operand_idx] = true; + template.push(InlineAsmTemplatePiece::Placeholder { + operand_idx, + modifier, + span, + }); + } + } + } + } + + let operands = args.operands; + let unused_operands: Vec<_> = used + .into_iter() + .enumerate() + .filter(|&(_, used)| !used) + .map(|(idx, _)| { + if named_pos.contains(&idx) { + // named argument + (operands[idx].1, "named argument never used") + } else { + // positional argument + (operands[idx].1, "argument never used") + } + }) + .collect(); + match unused_operands.len() { + 0 => {} + 1 => { + let (sp, msg) = unused_operands.into_iter().next().unwrap(); + let mut err = ecx.struct_span_err(sp, msg); + err.span_label(sp, msg); + err.emit(); + } + _ => { + let mut err = ecx.struct_span_err( + unused_operands.iter().map(|&(sp, _)| sp).collect::>(), + "multiple unused asm arguments", + ); + for (sp, msg) in unused_operands { + err.span_label(sp, msg); + } + err.emit(); + } + } + + let inline_asm = ast::InlineAsm { template, operands, options: args.options }; + P(ast::Expr { + id: ast::DUMMY_NODE_ID, + kind: ast::ExprKind::InlineAsm(inline_asm), + span: sp, + attrs: ast::AttrVec::new(), + }) +} + +pub fn expand_asm<'cx>( + ecx: &'cx mut ExtCtxt<'_>, + sp: Span, + tts: TokenStream, +) -> Box { + match parse_args(ecx, sp, tts) { + Ok(args) => MacEager::expr(expand_preparsed_asm(ecx, sp, args)), + Err(mut err) => { + err.emit(); + DummyResult::any(sp) + } + } +} diff --git a/src/librustc_builtin_macros/format.rs b/src/librustc_builtin_macros/format.rs index efce6288198..eed01b262bf 100644 --- a/src/librustc_builtin_macros/format.rs +++ b/src/librustc_builtin_macros/format.rs @@ -108,8 +108,6 @@ struct Context<'a, 'b> { arg_spans: Vec, /// All the formatting arguments that have formatting flags set, in order for diagnostics. arg_with_formatting: Vec>, - /// Whether this formatting string is a literal or it comes from a macro. - is_literal: bool, } /// Parses the arguments from the given list of tokens, returning the diagnostic @@ -324,7 +322,7 @@ impl<'a, 'b> Context<'a, 'b> { /// format string. fn report_invalid_references(&self, numbered_position_args: bool) { let mut e; - let sp = if self.is_literal { + let sp = if !self.arg_spans.is_empty() { // Point at the formatting arguments. MultiSpan::from_spans(self.arg_spans.clone()) } else { @@ -372,7 +370,7 @@ impl<'a, 'b> Context<'a, 'b> { let reg = refs.pop().unwrap(); (format!("arguments {head} and {tail}", head = refs.join(", "), tail = reg,), pos) }; - if !self.is_literal { + if self.arg_spans.is_empty() { sp = MultiSpan::from_span(self.fmtsp); } @@ -502,11 +500,7 @@ impl<'a, 'b> Context<'a, 'b> { } None => { let msg = format!("there is no argument named `{}`", name); - let sp = if self.is_literal { - *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp) - } else { - self.fmtsp - }; + let sp = *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp); let mut err = self.ecx.struct_span_err(sp, &msg[..]); err.emit(); } @@ -892,110 +886,20 @@ pub fn expand_preparsed_format_args( } }; - let (is_literal, fmt_snippet) = match ecx.source_map().span_to_snippet(fmt_sp) { - Ok(s) => (s.starts_with('"') || s.starts_with("r#"), Some(s)), - _ => (false, None), - }; - let str_style = match fmt_style { ast::StrStyle::Cooked => None, ast::StrStyle::Raw(raw) => Some(raw as usize), }; - /// Finds the indices of all characters that have been processed and differ between the actual - /// written code (code snippet) and the `InternedString` that gets processed in the `Parser` - /// in order to properly synthethise the intra-string `Span`s for error diagnostics. - fn find_skips(snippet: &str, is_raw: bool) -> Vec { - let mut eat_ws = false; - let mut s = snippet.chars().enumerate().peekable(); - let mut skips = vec![]; - while let Some((pos, c)) = s.next() { - match (c, s.peek()) { - // skip whitespace and empty lines ending in '\\' - ('\\', Some((next_pos, '\n'))) if !is_raw => { - eat_ws = true; - skips.push(pos); - skips.push(*next_pos); - let _ = s.next(); - } - ('\\', Some((next_pos, '\n' | 'n' | 't'))) if eat_ws => { - skips.push(pos); - skips.push(*next_pos); - let _ = s.next(); - } - (' ' | '\n' | '\t', _) if eat_ws => { - skips.push(pos); - } - ('\\', Some((next_pos, 'n' | 't' | '0' | '\\' | '\'' | '\"'))) => { - skips.push(*next_pos); - let _ = s.next(); - } - ('\\', Some((_, 'x'))) if !is_raw => { - for _ in 0..3 { - // consume `\xAB` literal - if let Some((pos, _)) = s.next() { - skips.push(pos); - } else { - break; - } - } - } - ('\\', Some((_, 'u'))) if !is_raw => { - if let Some((pos, _)) = s.next() { - skips.push(pos); - } - if let Some((next_pos, next_c)) = s.next() { - if next_c == '{' { - skips.push(next_pos); - let mut i = 0; // consume up to 6 hexanumeric chars + closing `}` - while let (Some((next_pos, c)), true) = (s.next(), i < 7) { - if c.is_digit(16) { - skips.push(next_pos); - } else if c == '}' { - skips.push(next_pos); - break; - } else { - break; - } - i += 1; - } - } else if next_c.is_digit(16) { - skips.push(next_pos); - // We suggest adding `{` and `}` when appropriate, accept it here as if - // it were correct - let mut i = 0; // consume up to 6 hexanumeric chars - while let (Some((next_pos, c)), _) = (s.next(), i < 6) { - if c.is_digit(16) { - skips.push(next_pos); - } else { - break; - } - i += 1; - } - } - } - } - _ if eat_ws => { - // `take_while(|c| c.is_whitespace())` - eat_ws = false; - } - _ => {} - } - } - skips - } - - let skips = if let (true, Some(ref snippet)) = (is_literal, fmt_snippet.as_ref()) { - let r_start = str_style.map(|r| r + 1).unwrap_or(0); - let r_end = str_style.map(|r| r).unwrap_or(0); - let s = &snippet[r_start + 1..snippet.len() - r_end - 1]; - find_skips(s, str_style.is_some()) - } else { - vec![] - }; - let fmt_str = &fmt_str.as_str(); // for the suggestions below - let mut parser = parse::Parser::new(fmt_str, str_style, skips, append_newline); + let fmt_snippet = ecx.source_map().span_to_snippet(fmt_sp).ok(); + let mut parser = parse::Parser::new( + fmt_str, + str_style, + fmt_snippet, + append_newline, + parse::ParseMode::Format, + ); let mut unverified_pieces = Vec::new(); while let Some(piece) = parser.next() { @@ -1048,7 +952,6 @@ pub fn expand_preparsed_format_args( invalid_refs: Vec::new(), arg_spans, arg_with_formatting: Vec::new(), - is_literal, }; // This needs to happen *after* the Parser has consumed all pieces to create all the spans diff --git a/src/librustc_builtin_macros/lib.rs b/src/librustc_builtin_macros/lib.rs index cc77bb73c5a..a0f82d65618 100644 --- a/src/librustc_builtin_macros/lib.rs +++ b/src/librustc_builtin_macros/lib.rs @@ -19,6 +19,7 @@ use rustc_expand::proc_macro::BangProcMacro; use rustc_span::edition::Edition; use rustc_span::symbol::{sym, Ident}; +mod asm; mod assert; mod cfg; mod cfg_accessible; @@ -61,7 +62,7 @@ pub fn register_builtin_macros(resolver: &mut dyn Resolver, edition: Edition) { } register_bang! { - asm: llvm_asm::expand_llvm_asm, + asm: asm::expand_asm, assert: assert::expand_assert, cfg: cfg::expand_cfg, column: source_util::expand_column, diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs index 1f351d09bc3..1f0764b06b0 100644 --- a/src/librustc_span/symbol.rs +++ b/src/librustc_span/symbol.rs @@ -376,6 +376,8 @@ symbols! { if_let, if_while_or_patterns, ignore, + inlateout, + inout, impl_header_lifetime_elision, impl_lint_pass, impl_trait_in_bindings, @@ -411,6 +413,7 @@ symbols! { label_break_value, lang, lang_items, + lateout, let_chains, lhs, lib, @@ -495,12 +498,15 @@ symbols! { no_link, no_main, no_mangle, + nomem, non_ascii_idents, None, non_exhaustive, non_modrs_mods, - no_sanitize, + noreturn, no_niche, + no_sanitize, + nostack, no_stack_check, no_start, no_std, @@ -519,11 +525,13 @@ symbols! { option, Option, option_env, + options, opt_out_copy, or, or_patterns, Ord, Ordering, + out, Output, overlapping_marker_traits, packed, @@ -556,6 +564,7 @@ symbols! { pref_align_of, prelude, prelude_import, + preserves_flags, primitive, proc_dash_macro: "proc-macro", proc_macro, @@ -572,6 +581,7 @@ symbols! { profiler_runtime, ptr_offset_from, pub_restricted, + pure, pushpop_unsafe, quad_precision_float, question_mark, @@ -586,6 +596,7 @@ symbols! { raw_identifiers, raw_ref_op, Rc, + readonly, Ready, reason, recursion_limit, @@ -723,6 +734,7 @@ symbols! { sty, sub_with_overflow, suggestion, + sym, sync_trait, target_feature, target_feature_11, @@ -1187,8 +1199,8 @@ pub mod sym { // have a static symbol and therefore are fast. pub fn integer + Copy + ToString>(n: N) -> Symbol { if let Result::Ok(idx) = n.try_into() { - if let Option::Some(&sym) = digits_array.get(idx) { - return sym; + if let Option::Some(&sym_) = digits_array.get(idx) { + return sym_; } } Symbol::intern(&n.to_string()) diff --git a/src/librustc_trait_selection/traits/on_unimplemented.rs b/src/librustc_trait_selection/traits/on_unimplemented.rs index 3fbc0b7f08e..7e66e08f7e6 100644 --- a/src/librustc_trait_selection/traits/on_unimplemented.rs +++ b/src/librustc_trait_selection/traits/on_unimplemented.rs @@ -1,4 +1,4 @@ -use fmt_macros::{Parser, Piece, Position}; +use fmt_macros::{ParseMode, Parser, Piece, Position}; use rustc_ast::ast::{MetaItem, NestedMetaItem}; use rustc_attr as attr; @@ -272,7 +272,7 @@ impl<'tcx> OnUnimplementedFormatString { let name = tcx.item_name(trait_def_id); let generics = tcx.generics_of(trait_def_id); let s = self.0.as_str(); - let parser = Parser::new(&s, None, vec![], false); + let parser = Parser::new(&s, None, None, false, ParseMode::Format); let mut result = Ok(()); for token in parser { match token { @@ -350,7 +350,7 @@ impl<'tcx> OnUnimplementedFormatString { let empty_string = String::new(); let s = self.0.as_str(); - let parser = Parser::new(&s, None, vec![], false); + let parser = Parser::new(&s, None, None, false, ParseMode::Format); let item_context = (options.get(&sym::item_context)).unwrap_or(&empty_string); parser .map(|p| match p { From d5b1501d8c3deda64c5b43ce2c9a0c51dd91169a Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 12 Feb 2020 17:32:41 +0000 Subject: [PATCH 05/36] Add asm! to HIR --- src/librustc_hir/hir.rs | 54 +++++++++++++++++ src/librustc_hir/intravisit.rs | 21 +++++++ src/librustc_hir_pretty/lib.rs | 102 +++++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+) diff --git a/src/librustc_hir/hir.rs b/src/librustc_hir/hir.rs index eafff6705ba..19cea3a3466 100644 --- a/src/librustc_hir/hir.rs +++ b/src/librustc_hir/hir.rs @@ -15,6 +15,7 @@ use rustc_macros::HashStable_Generic; use rustc_span::source_map::{SourceMap, Spanned}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{MultiSpan, Span, DUMMY_SP}; +use rustc_target::asm::{InlineAsmOptions, InlineAsmRegOrRegClass, InlineAsmTemplatePiece}; use rustc_target::spec::abi::Abi; use smallvec::SmallVec; @@ -1391,6 +1392,7 @@ impl Expr<'_> { ExprKind::Break(..) => ExprPrecedence::Break, ExprKind::Continue(..) => ExprPrecedence::Continue, ExprKind::Ret(..) => ExprPrecedence::Ret, + ExprKind::InlineAsm(..) => ExprPrecedence::InlineAsm, ExprKind::LlvmInlineAsm(..) => ExprPrecedence::InlineAsm, ExprKind::Struct(..) => ExprPrecedence::Struct, ExprKind::Repeat(..) => ExprPrecedence::Repeat, @@ -1446,6 +1448,7 @@ impl Expr<'_> { | ExprKind::Ret(..) | ExprKind::Loop(..) | ExprKind::Assign(..) + | ExprKind::InlineAsm(..) | ExprKind::LlvmInlineAsm(..) | ExprKind::AssignOp(..) | ExprKind::Lit(_) @@ -1622,6 +1625,8 @@ pub enum ExprKind<'hir> { /// A `return`, with an optional value to be returned. Ret(Option<&'hir Expr<'hir>>), + /// Inline assembly (from `asm!`), with its outputs and inputs. + InlineAsm(&'hir InlineAsm<'hir>), /// Inline assembly (from `llvm_asm!`), with its outputs and inputs. LlvmInlineAsm(&'hir LlvmInlineAsm<'hir>), @@ -2054,6 +2059,55 @@ pub enum TyKind<'hir> { Err, } +#[derive(RustcEncodable, RustcDecodable, Debug, HashStable_Generic)] +pub enum InlineAsmOperand<'hir> { + In { + reg: InlineAsmRegOrRegClass, + expr: Expr<'hir>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Option>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Expr<'hir>, + }, + SplitInOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_expr: Expr<'hir>, + out_expr: Option>, + }, + Const { + expr: Expr<'hir>, + }, + Sym { + expr: Expr<'hir>, + }, +} + +impl<'hir> InlineAsmOperand<'hir> { + pub fn reg(&self) -> Option { + match *self { + Self::In { reg, .. } + | Self::Out { reg, .. } + | Self::InOut { reg, .. } + | Self::SplitInOut { reg, .. } => Some(reg), + Self::Const { .. } | Self::Sym { .. } => None, + } + } +} + +#[derive(RustcEncodable, RustcDecodable, Debug, HashStable_Generic)] +pub struct InlineAsm<'hir> { + pub template: &'hir [InlineAsmTemplatePiece], + pub operands: &'hir [InlineAsmOperand<'hir>], + pub options: InlineAsmOptions, +} + #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Debug, HashStable_Generic, PartialEq)] pub struct LlvmInlineAsmOutput { pub constraint: Symbol, diff --git a/src/librustc_hir/intravisit.rs b/src/librustc_hir/intravisit.rs index 0270d0de5c7..97601a3e1ac 100644 --- a/src/librustc_hir/intravisit.rs +++ b/src/librustc_hir/intravisit.rs @@ -1157,6 +1157,27 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) ExprKind::Ret(ref optional_expression) => { walk_list!(visitor, visit_expr, optional_expression); } + ExprKind::InlineAsm(ref asm) => { + for op in asm.operands { + match op { + InlineAsmOperand::In { expr, .. } + | InlineAsmOperand::InOut { expr, .. } + | InlineAsmOperand::Const { expr, .. } + | InlineAsmOperand::Sym { expr, .. } => visitor.visit_expr(expr), + InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + visitor.visit_expr(expr); + } + } + InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + visitor.visit_expr(in_expr); + if let Some(out_expr) = out_expr { + visitor.visit_expr(out_expr); + } + } + } + } + } ExprKind::LlvmInlineAsm(ref asm) => { walk_list!(visitor, visit_expr, asm.outputs_exprs); walk_list!(visitor, visit_expr, asm.inputs_exprs); diff --git a/src/librustc_hir_pretty/lib.rs b/src/librustc_hir_pretty/lib.rs index f8a42376d14..918b5901375 100644 --- a/src/librustc_hir_pretty/lib.rs +++ b/src/librustc_hir_pretty/lib.rs @@ -12,6 +12,7 @@ use rustc_hir::{GenericBound, PatKind, RangeEnd, TraitBoundModifier}; use rustc_span::source_map::{SourceMap, Spanned}; use rustc_span::symbol::{kw, Ident, IdentPrinter, Symbol}; use rustc_span::{self, BytePos, FileName}; +use rustc_target::asm::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_target::spec::abi::Abi; use std::borrow::Cow; @@ -1409,6 +1410,107 @@ impl<'a> State<'a> { self.print_expr_maybe_paren(&expr, parser::PREC_JUMP); } } + hir::ExprKind::InlineAsm(ref a) => { + enum AsmArg<'a> { + Template(String), + Operand(&'a hir::InlineAsmOperand<'a>), + Options(InlineAsmOptions), + } + + let mut args = vec![]; + args.push(AsmArg::Template(InlineAsmTemplatePiece::to_string(&a.template))); + args.extend(a.operands.iter().map(|o| AsmArg::Operand(o))); + if !a.options.is_empty() { + args.push(AsmArg::Options(a.options)); + } + + self.word("asm!"); + self.popen(); + self.commasep(Consistent, &args, |s, arg| match arg { + AsmArg::Template(template) => s.print_string(&template, ast::StrStyle::Cooked), + AsmArg::Operand(op) => match op { + hir::InlineAsmOperand::In { reg, expr } => { + s.word("in"); + s.popen(); + s.word(format!("{}", reg)); + s.pclose(); + s.space(); + s.print_expr(expr); + } + hir::InlineAsmOperand::Out { reg, late, expr } => { + s.word(if *late { "lateout" } else { "out" }); + s.popen(); + s.word(format!("{}", reg)); + s.pclose(); + s.space(); + match expr { + Some(expr) => s.print_expr(expr), + None => s.word("_"), + } + } + hir::InlineAsmOperand::InOut { reg, late, expr } => { + s.word(if *late { "inlateout" } else { "inout" }); + s.popen(); + s.word(format!("{}", reg)); + s.pclose(); + s.space(); + s.print_expr(expr); + } + hir::InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => { + s.word(if *late { "inlateout" } else { "inout" }); + s.popen(); + s.word(format!("{}", reg)); + s.pclose(); + s.space(); + s.print_expr(in_expr); + s.space(); + s.word_space("=>"); + match out_expr { + Some(out_expr) => s.print_expr(out_expr), + None => s.word("_"), + } + } + hir::InlineAsmOperand::Const { expr } => { + s.word("const"); + s.space(); + s.print_expr(expr); + } + hir::InlineAsmOperand::Sym { expr } => { + s.word("sym"); + s.space(); + s.print_expr(expr); + } + }, + AsmArg::Options(opts) => { + s.word("options"); + s.popen(); + let mut options = vec![]; + if opts.contains(InlineAsmOptions::PURE) { + options.push("pure"); + } + if opts.contains(InlineAsmOptions::NOMEM) { + options.push("nomem"); + } + if opts.contains(InlineAsmOptions::READONLY) { + options.push("readonly"); + } + if opts.contains(InlineAsmOptions::PRESERVES_FLAGS) { + options.push("preserves_flags"); + } + if opts.contains(InlineAsmOptions::NORETURN) { + options.push("noreturn"); + } + if opts.contains(InlineAsmOptions::NOSTACK) { + options.push("nostack"); + } + s.commasep(Inconsistent, &options, |s, &opt| { + s.word(opt); + }); + s.pclose(); + } + }); + self.pclose(); + } hir::ExprKind::LlvmInlineAsm(ref a) => { let i = &a.inner; self.s.word("llvm_asm!"); From ec1ad61f8877dcb5916b07708dd35bc8651a1541 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 12 Feb 2020 15:48:03 +0000 Subject: [PATCH 06/36] Implement AST lowering for asm! --- src/librustc_ast_lowering/expr.rs | 296 +++++++++++++++++++++++++++++- src/librustc_hir/arena.rs | 5 +- src/librustc_interface/util.rs | 9 +- src/librustc_session/session.rs | 16 ++ 4 files changed, 320 insertions(+), 6 deletions(-) diff --git a/src/librustc_ast_lowering/expr.rs b/src/librustc_ast_lowering/expr.rs index 8da3aa633b8..f713db9a6cc 100644 --- a/src/librustc_ast_lowering/expr.rs +++ b/src/librustc_ast_lowering/expr.rs @@ -3,6 +3,7 @@ use super::{ImplTraitContext, LoweringContext, ParamMode, ParenthesizedGenericAr use rustc_ast::ast::*; use rustc_ast::attr; use rustc_ast::ptr::P as AstP; +use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_data_structures::thin_vec::ThinVec; use rustc_errors::struct_span_err; @@ -10,6 +11,9 @@ use rustc_hir as hir; use rustc_hir::def::Res; use rustc_span::source_map::{respan, DesugaringKind, Span, Spanned}; use rustc_span::symbol::{sym, Ident, Symbol}; +use rustc_target::asm; +use std::collections::hash_map::Entry; +use std::fmt::Write; impl<'hir> LoweringContext<'_, 'hir> { fn lower_exprs(&mut self, exprs: &[AstP]) -> &'hir [hir::Expr<'hir>] { @@ -175,7 +179,8 @@ impl<'hir> LoweringContext<'_, 'hir> { let e = e.as_ref().map(|x| self.lower_expr(x)); hir::ExprKind::Ret(e) } - ExprKind::LlvmInlineAsm(ref asm) => self.lower_expr_asm(asm), + ExprKind::InlineAsm(ref asm) => self.lower_expr_asm(e.span, asm), + ExprKind::LlvmInlineAsm(ref asm) => self.lower_expr_llvm_asm(asm), ExprKind::Struct(ref path, ref fields, ref maybe_expr) => { let maybe_expr = maybe_expr.as_ref().map(|x| self.lower_expr(x)); hir::ExprKind::Struct( @@ -968,7 +973,294 @@ impl<'hir> LoweringContext<'_, 'hir> { result } - fn lower_expr_asm(&mut self, asm: &LlvmInlineAsm) -> hir::ExprKind<'hir> { + fn lower_expr_asm(&mut self, sp: Span, asm: &InlineAsm) -> hir::ExprKind<'hir> { + let asm_arch = if let Some(asm_arch) = self.sess.asm_arch { + asm_arch + } else { + struct_span_err!(self.sess, sp, E0472, "asm! is unsupported on this target").emit(); + return hir::ExprKind::Err; + }; + + // Lower operands to HIR, filter_map skips any operands with invalid + // register classes. + let sess = self.sess; + let operands: Vec<_> = asm + .operands + .iter() + .filter_map(|(op, op_sp)| { + let lower_reg = |reg| { + Some(match reg { + InlineAsmRegOrRegClass::Reg(s) => asm::InlineAsmRegOrRegClass::Reg( + asm::InlineAsmReg::parse( + asm_arch, + |feature| { + self.sess.target_features.contains(&Symbol::intern(feature)) + }, + s, + ) + .map_err(|e| { + let msg = format!("invalid register `{}`: {}", s.as_str(), e); + sess.struct_span_err(*op_sp, &msg).emit(); + }) + .ok()?, + ), + InlineAsmRegOrRegClass::RegClass(s) => { + asm::InlineAsmRegOrRegClass::RegClass( + asm::InlineAsmRegClass::parse(asm_arch, s) + .map_err(|e| { + let msg = format!( + "invalid register class `{}`: {}", + s.as_str(), + e + ); + sess.struct_span_err(*op_sp, &msg).emit(); + }) + .ok()?, + ) + } + }) + }; + let op = match op { + InlineAsmOperand::In { reg, expr } => hir::InlineAsmOperand::In { + reg: lower_reg(*reg)?, + expr: self.lower_expr_mut(expr), + }, + InlineAsmOperand::Out { reg, late, expr } => hir::InlineAsmOperand::Out { + reg: lower_reg(*reg)?, + late: *late, + expr: expr.as_ref().map(|expr| self.lower_expr_mut(expr)), + }, + InlineAsmOperand::InOut { reg, late, expr } => hir::InlineAsmOperand::InOut { + reg: lower_reg(*reg)?, + late: *late, + expr: self.lower_expr_mut(expr), + }, + InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => { + hir::InlineAsmOperand::SplitInOut { + reg: lower_reg(*reg)?, + late: *late, + in_expr: self.lower_expr_mut(in_expr), + out_expr: out_expr.as_ref().map(|expr| self.lower_expr_mut(expr)), + } + } + InlineAsmOperand::Const { expr } => { + hir::InlineAsmOperand::Const { expr: self.lower_expr_mut(expr) } + } + InlineAsmOperand::Sym { expr } => { + hir::InlineAsmOperand::Sym { expr: self.lower_expr_mut(expr) } + } + }; + Some(op) + }) + .collect(); + + // Stop if there were any errors when lowering the register classes + if operands.len() != asm.operands.len() { + return hir::ExprKind::Err; + } + + // Validate template modifiers against the register classes for the operands + for p in &asm.template { + if let asm::InlineAsmTemplatePiece::Placeholder { + operand_idx, + modifier: Some(modifier), + span: placeholder_span, + } = *p + { + let op_sp = asm.operands[operand_idx].1; + match &operands[operand_idx] { + hir::InlineAsmOperand::In { reg, .. } + | hir::InlineAsmOperand::Out { reg, .. } + | hir::InlineAsmOperand::InOut { reg, .. } + | hir::InlineAsmOperand::SplitInOut { reg, .. } => { + let class = reg.reg_class(); + let valid_modifiers = class.valid_modifiers(asm_arch); + if !valid_modifiers.contains(&modifier) { + let mut err = sess.struct_span_err( + placeholder_span, + "invalid asm template modifier for this register class", + ); + err.span_label(placeholder_span, "template modifier"); + err.span_label(op_sp, "argument"); + if !valid_modifiers.is_empty() { + let mut mods = format!("`{}`", valid_modifiers[0]); + for m in &valid_modifiers[1..] { + let _ = write!(mods, ", `{}`", m); + } + err.note(&format!( + "the `{}` register class supports \ + the following template modifiers: {}", + class.name(), + mods + )); + } else { + err.note(&format!( + "the `{}` register class does not support template modifiers", + class.name() + )); + } + err.emit(); + } + } + hir::InlineAsmOperand::Const { .. } => { + let mut err = sess.struct_span_err( + placeholder_span, + "asm template modifiers are not allowed for `const` arguments", + ); + err.span_label(placeholder_span, "template modifier"); + err.span_label(op_sp, "argument"); + err.emit(); + } + hir::InlineAsmOperand::Sym { .. } => { + let mut err = sess.struct_span_err( + placeholder_span, + "asm template modifiers are not allowed for `sym` arguments", + ); + err.span_label(placeholder_span, "template modifier"); + err.span_label(op_sp, "argument"); + err.emit(); + } + } + } + } + + let mut used_input_regs = FxHashMap::default(); + let mut used_output_regs = FxHashMap::default(); + for (idx, op) in operands.iter().enumerate() { + let op_sp = asm.operands[idx].1; + if let Some(reg) = op.reg() { + // Validate register classes against currently enabled target + // features. We check that at least one type is available for + // the current target. + let reg_class = reg.reg_class(); + let mut required_features = vec![]; + for &(_, feature) in reg_class.supported_types(asm_arch) { + if let Some(feature) = feature { + if self.sess.target_features.contains(&Symbol::intern(feature)) { + required_features.clear(); + break; + } else { + required_features.push(feature); + } + } else { + required_features.clear(); + break; + } + } + required_features.sort(); + required_features.dedup(); + match &required_features[..] { + [] => {} + [feature] => { + let msg = format!( + "register class `{}` requires the `{}` target feature", + reg_class.name(), + feature + ); + sess.struct_span_err(op_sp, &msg).emit(); + } + features => { + let msg = format!( + "register class `{}` requires at least one target feature: {}", + reg_class.name(), + features.join(", ") + ); + sess.struct_span_err(op_sp, &msg).emit(); + } + } + + // Check for conflicts between explicit register operands. + if let asm::InlineAsmRegOrRegClass::Reg(reg) = reg { + let (input, output) = match op { + hir::InlineAsmOperand::In { .. } => (true, false), + // Late output do not conflict with inputs, but normal outputs do + hir::InlineAsmOperand::Out { late, .. } => (!late, true), + hir::InlineAsmOperand::InOut { .. } + | hir::InlineAsmOperand::SplitInOut { .. } => (true, true), + hir::InlineAsmOperand::Const { .. } | hir::InlineAsmOperand::Sym { .. } => { + unreachable!() + } + }; + + // Flag to output the error only once per operand + let mut skip = false; + reg.overlapping_regs(|r| { + let mut check = |used_regs: &mut FxHashMap, + input| { + match used_regs.entry(r) { + Entry::Occupied(o) => { + if !skip { + skip = true; + + let idx2 = *o.get(); + let op2 = &operands[idx2]; + let op_sp2 = asm.operands[idx2].1; + let reg2 = match op2.reg() { + Some(asm::InlineAsmRegOrRegClass::Reg(r)) => r, + _ => unreachable!(), + }; + + let msg = format!( + "register `{}` conflicts with register `{}`", + reg.name(), + reg2.name() + ); + let mut err = sess.struct_span_err(op_sp, &msg); + err.span_label( + op_sp, + &format!("register `{}`", reg.name()), + ); + err.span_label( + op_sp2, + &format!("register `{}`", reg2.name()), + ); + + match (op, op2) { + ( + hir::InlineAsmOperand::In { .. }, + hir::InlineAsmOperand::Out { late, .. }, + ) + | ( + hir::InlineAsmOperand::Out { late, .. }, + hir::InlineAsmOperand::In { .. }, + ) => { + assert!(!*late); + let out_op_sp = if input { op_sp2 } else { op_sp }; + let msg = &format!( + "use `lateout` instead of \ + `out` to avoid conflict" + ); + err.span_help(out_op_sp, msg); + } + _ => {} + } + + err.emit(); + } + } + Entry::Vacant(v) => { + v.insert(idx); + } + } + }; + if input { + check(&mut used_input_regs, true); + } + if output { + check(&mut used_output_regs, false); + } + }); + } + } + } + + let operands = self.arena.alloc_from_iter(operands); + let template = self.arena.alloc_from_iter(asm.template.iter().cloned()); + let hir_asm = hir::InlineAsm { template, operands, options: asm.options }; + hir::ExprKind::InlineAsm(self.arena.alloc(hir_asm)) + } + + fn lower_expr_llvm_asm(&mut self, asm: &LlvmInlineAsm) -> hir::ExprKind<'hir> { let inner = hir::LlvmInlineAsmInner { inputs: asm.inputs.iter().map(|&(c, _)| c).collect(), outputs: asm diff --git a/src/librustc_hir/arena.rs b/src/librustc_hir/arena.rs index a0b19f61906..fabb17134a3 100644 --- a/src/librustc_hir/arena.rs +++ b/src/librustc_hir/arena.rs @@ -14,6 +14,8 @@ macro_rules! arena_types { // HIR types [few] hir_krate: rustc_hir::Crate<$tcx>, [] arm: rustc_hir::Arm<$tcx>, + [] asm_operand: rustc_hir::InlineAsmOperand<$tcx>, + [] asm_template: rustc_target::asm::InlineAsmTemplatePiece, [] attribute: rustc_ast::ast::Attribute, [] block: rustc_hir::Block<$tcx>, [] bare_fn_ty: rustc_hir::BareFnTy<$tcx>, @@ -28,7 +30,8 @@ macro_rules! arena_types { [] fn_decl: rustc_hir::FnDecl<$tcx>, [] foreign_item: rustc_hir::ForeignItem<$tcx>, [] impl_item_ref: rustc_hir::ImplItemRef<$tcx>, - [] inline_asm: rustc_hir::LlvmInlineAsm<$tcx>, + [few] inline_asm: rustc_hir::InlineAsm<$tcx>, + [few] llvm_inline_asm: rustc_hir::LlvmInlineAsm<$tcx>, [] local: rustc_hir::Local<$tcx>, [few] macro_def: rustc_hir::MacroDef<$tcx>, [] param: rustc_hir::Param<$tcx>, diff --git a/src/librustc_interface/util.rs b/src/librustc_interface/util.rs index 7637108e18b..5a76802014e 100644 --- a/src/librustc_interface/util.rs +++ b/src/librustc_interface/util.rs @@ -42,12 +42,15 @@ use std::{panic, thread}; /// features is available on the target machine, by querying LLVM. pub fn add_configuration( cfg: &mut CrateConfig, - sess: &Session, + sess: &mut Session, codegen_backend: &dyn CodegenBackend, ) { let tf = sym::target_feature; - cfg.extend(codegen_backend.target_features(sess).into_iter().map(|feat| (tf, Some(feat)))); + let target_features = codegen_backend.target_features(sess); + sess.target_features.extend(target_features.iter().cloned()); + + cfg.extend(target_features.into_iter().map(|feat| (tf, Some(feat)))); if sess.crt_static(None) { cfg.insert((tf, Some(Symbol::intern("crt-static")))); @@ -75,7 +78,7 @@ pub fn create_session( let codegen_backend = get_codegen_backend(&sess); let mut cfg = config::build_configuration(&sess, config::to_crate_config(cfg)); - add_configuration(&mut cfg, &sess, &*codegen_backend); + add_configuration(&mut cfg, &mut sess, &*codegen_backend); sess.parse_sess.config = cfg; (Lrc::new(sess), Lrc::new(codegen_backend), source_map) diff --git a/src/librustc_session/session.rs b/src/librustc_session/session.rs index 847b07f0e46..143401dd3b6 100644 --- a/src/librustc_session/session.rs +++ b/src/librustc_session/session.rs @@ -23,6 +23,7 @@ use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticId, ErrorReported use rustc_span::edition::Edition; use rustc_span::source_map::{self, FileLoader, MultiSpan, RealFileLoader, SourceMap, Span}; use rustc_span::{SourceFileHashAlgorithm, Symbol}; +use rustc_target::asm::InlineAsmArch; use rustc_target::spec::{CodeModel, PanicStrategy, RelocModel, RelroLevel}; use rustc_target::spec::{Target, TargetTriple, TlsModel}; @@ -31,6 +32,7 @@ use std::env; use std::io::Write; use std::num::NonZeroU32; use std::path::PathBuf; +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; @@ -158,6 +160,12 @@ pub struct Session { /// if Rust was built with path remapping to `/rustc/$hash` enabled /// (the `rust.remap-debuginfo` option in `config.toml`). pub real_rust_source_base_dir: Option, + + /// Architecture to use for interpreting asm!. + pub asm_arch: Option, + + /// Set of enabled features for the current target. + pub target_features: FxHashSet, } pub struct PerfStats { @@ -1183,6 +1191,12 @@ pub fn build_session_with_source_map( if candidate.join("src/libstd/lib.rs").is_file() { Some(candidate) } else { None } }; + let asm_arch = if target_cfg.target.options.allow_asm { + InlineAsmArch::from_str(&target_cfg.target.arch).ok() + } else { + None + }; + let sess = Session { target: target_cfg, host, @@ -1223,6 +1237,8 @@ pub fn build_session_with_source_map( ctfe_backtrace, miri_unleashed_features: Lock::new(Default::default()), real_rust_source_base_dir, + asm_arch, + target_features: FxHashSet::default(), }; validate_commandline_args_with_session_available(&sess); From 10510b5820a030a6e8fd1018942f4b46633ec418 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Thu, 13 Feb 2020 11:00:55 +0000 Subject: [PATCH 07/36] HIR passes for asm! --- src/librustc_middle/traits/mod.rs | 2 + .../traits/structural_impls.rs | 1 + src/librustc_passes/intrinsicck.rs | 289 +++++++++++++++++- src/librustc_passes/liveness.rs | 87 ++++++ src/librustc_session/lint/builtin.rs | 7 + .../traits/error_reporting/suggestions.rs | 3 + src/librustc_typeck/check/expr.rs | 68 +++++ src/librustc_typeck/expr_use_visitor.rs | 24 ++ src/librustc_typeck/mem_categorization.rs | 1 + 9 files changed, 469 insertions(+), 13 deletions(-) diff --git a/src/librustc_middle/traits/mod.rs b/src/librustc_middle/traits/mod.rs index 1254174a7a5..adbeac29bdd 100644 --- a/src/librustc_middle/traits/mod.rs +++ b/src/librustc_middle/traits/mod.rs @@ -180,6 +180,8 @@ pub enum ObligationCauseCode<'tcx> { SizedReturnType, /// Yield type must be `Sized`. SizedYieldType, + /// Inline asm operand type must be `Sized`. + InlineAsmSized, /// `[T, ..n]` implies that `T` must be `Copy`. /// If `true`, suggest `const_in_array_repeat_expressions` feature flag. RepeatVec(bool), diff --git a/src/librustc_middle/traits/structural_impls.rs b/src/librustc_middle/traits/structural_impls.rs index 668c84ad5e6..56744283b0c 100644 --- a/src/librustc_middle/traits/structural_impls.rs +++ b/src/librustc_middle/traits/structural_impls.rs @@ -152,6 +152,7 @@ impl<'a, 'tcx> Lift<'tcx> for traits::ObligationCauseCode<'a> { super::SizedArgumentType => Some(super::SizedArgumentType), super::SizedReturnType => Some(super::SizedReturnType), super::SizedYieldType => Some(super::SizedYieldType), + super::InlineAsmSized => Some(super::InlineAsmSized), super::RepeatVec(suggest_flag) => Some(super::RepeatVec(suggest_flag)), super::FieldSized { adt_kind, last } => Some(super::FieldSized { adt_kind, last }), super::ConstSized => Some(super::ConstSized), diff --git a/src/librustc_passes/intrinsicck.rs b/src/librustc_passes/intrinsicck.rs index b407276cfbe..1a9fd30bba0 100644 --- a/src/librustc_passes/intrinsicck.rs +++ b/src/librustc_passes/intrinsicck.rs @@ -1,3 +1,4 @@ +use rustc_ast::ast::{FloatTy, IntTy, UintTy}; use rustc_errors::struct_span_err; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; @@ -7,8 +8,10 @@ use rustc_index::vec::Idx; use rustc_middle::ty::layout::{LayoutError, SizeSkeleton}; use rustc_middle::ty::query::Providers; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_span::{sym, Span}; +use rustc_session::lint; +use rustc_span::{sym, Span, Symbol, DUMMY_SP}; use rustc_target::abi::{Pointer, VariantIdx}; +use rustc_target::asm::{InlineAsmRegOrRegClass, InlineAsmTemplatePiece, InlineAsmType}; use rustc_target::spec::abi::Abi::RustIntrinsic; fn check_mod_intrinsics(tcx: TyCtxt<'_>, module_def_id: DefId) { @@ -119,6 +122,262 @@ impl ExprVisitor<'tcx> { } err.emit() } + + fn is_thin_ptr_ty(&self, ty: Ty<'tcx>) -> bool { + if ty.is_sized(self.tcx.at(DUMMY_SP), self.param_env) { + return true; + } + if let ty::Foreign(..) = ty.kind { + return true; + } + false + } + + fn check_asm_operand_type( + &self, + idx: usize, + reg: InlineAsmRegOrRegClass, + expr: &hir::Expr<'tcx>, + template: &[InlineAsmTemplatePiece], + tied_input: Option<(&hir::Expr<'tcx>, Option)>, + ) -> Option { + // Check the type against the allowed types for inline asm. + let ty = self.tables.expr_ty_adjusted(expr); + let asm_ty_isize = match self.tcx.sess.target.ptr_width { + 16 => InlineAsmType::I16, + 32 => InlineAsmType::I32, + 64 => InlineAsmType::I64, + _ => unreachable!(), + }; + let asm_ty = match ty.kind { + ty::Never | ty::Error => return None, + ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => Some(InlineAsmType::I8), + ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => Some(InlineAsmType::I16), + ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => Some(InlineAsmType::I32), + ty::Int(IntTy::I64) | ty::Uint(UintTy::U64) => Some(InlineAsmType::I64), + ty::Int(IntTy::I128) | ty::Uint(UintTy::U128) => Some(InlineAsmType::I128), + ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => Some(asm_ty_isize), + ty::Float(FloatTy::F32) => Some(InlineAsmType::F32), + ty::Float(FloatTy::F64) => Some(InlineAsmType::F64), + ty::FnPtr(_) => Some(asm_ty_isize), + ty::RawPtr(ty::TypeAndMut { ty, mutbl: _ }) if self.is_thin_ptr_ty(ty) => { + Some(asm_ty_isize) + } + ty::Adt(adt, substs) if adt.repr.simd() => { + let fields = &adt.non_enum_variant().fields; + let elem_ty = fields[0].ty(self.tcx, substs); + match elem_ty.kind { + ty::Never | ty::Error => return None, + ty::Int(IntTy::I8) | ty::Uint(UintTy::U8) => { + Some(InlineAsmType::VecI8(fields.len() as u64)) + } + ty::Int(IntTy::I16) | ty::Uint(UintTy::U16) => { + Some(InlineAsmType::VecI16(fields.len() as u64)) + } + ty::Int(IntTy::I32) | ty::Uint(UintTy::U32) => { + Some(InlineAsmType::VecI32(fields.len() as u64)) + } + ty::Int(IntTy::I64) | ty::Uint(UintTy::U64) => { + Some(InlineAsmType::VecI64(fields.len() as u64)) + } + ty::Int(IntTy::I128) | ty::Uint(UintTy::U128) => { + Some(InlineAsmType::VecI128(fields.len() as u64)) + } + ty::Int(IntTy::Isize) | ty::Uint(UintTy::Usize) => { + Some(match self.tcx.sess.target.ptr_width { + 16 => InlineAsmType::VecI16(fields.len() as u64), + 32 => InlineAsmType::VecI32(fields.len() as u64), + 64 => InlineAsmType::VecI64(fields.len() as u64), + _ => unreachable!(), + }) + } + ty::Float(FloatTy::F32) => Some(InlineAsmType::VecF32(fields.len() as u64)), + ty::Float(FloatTy::F64) => Some(InlineAsmType::VecF64(fields.len() as u64)), + _ => None, + } + } + _ => None, + }; + let asm_ty = match asm_ty { + Some(asm_ty) => asm_ty, + None => { + let msg = &format!("cannot use value of type `{}` for inline assembly", ty); + let mut err = self.tcx.sess.struct_span_err(expr.span, msg); + err.note( + "only integers, floats, SIMD vectors, pointers and function pointers \ + can be used as arguments for inline assembly", + ); + err.emit(); + return None; + } + }; + + // Check that the type implements Copy. The only case where this can + // possibly fail is for SIMD types which don't #[derive(Copy)]. + if !ty.is_copy_modulo_regions(self.tcx, self.param_env, DUMMY_SP) { + let msg = "arguments for inline assembly must be copyable"; + let mut err = self.tcx.sess.struct_span_err(expr.span, msg); + err.note(&format!("`{}` does not implement the Copy trait", ty)); + err.emit(); + } + + // Ideally we wouldn't need to do this, but LLVM's register allocator + // really doesn't like it when tied operands have different types. + // + // This is purely an LLVM limitation, but we have to live with it since + // there is no way to hide this with implicit conversions. + // + // For the purposes of this check we only look at the `InlineAsmType`, + // which means that pointers and integers are treated as identical (modulo + // size). + if let Some((in_expr, Some(in_asm_ty))) = tied_input { + if in_asm_ty != asm_ty { + let msg = &format!("incompatible types for asm inout argument"); + let mut err = self.tcx.sess.struct_span_err(vec![in_expr.span, expr.span], msg); + err.span_label( + in_expr.span, + &format!("type `{}`", self.tables.expr_ty_adjusted(in_expr)), + ); + err.span_label(expr.span, &format!("type `{}`", ty)); + err.note("asm inout arguments must have the same type"); + err.note("unless they are both pointers or integers of the same size"); + err.emit(); + } + + // All of the later checks have already been done on the input, so + // let's not emit errors and warnings twice. + return Some(asm_ty); + } + + // Check the type against the list of types supported by the selected + // register class. + let asm_arch = self.tcx.sess.asm_arch.unwrap(); + let reg_class = reg.reg_class(); + let supported_tys = reg_class.supported_types(asm_arch); + let feature = match supported_tys.iter().find(|&&(t, _)| t == asm_ty) { + Some((_, feature)) => feature, + None => { + let msg = &format!("type `{}` cannot be used with this register class", ty); + let mut err = self.tcx.sess.struct_span_err(expr.span, msg); + let supported_tys: Vec<_> = + supported_tys.iter().map(|(t, _)| t.to_string()).collect(); + err.note(&format!( + "register class `{}` supports these types: {}", + reg_class.name(), + supported_tys.join(", "), + )); + err.emit(); + return Some(asm_ty); + } + }; + + // Check whether the selected type requires a target feature. + if let Some(feature) = feature { + if !self.tcx.sess.target_features.contains(&Symbol::intern(feature)) { + let msg = &format!("`{}` target feature is not enabled", feature); + let mut err = self.tcx.sess.struct_span_err(expr.span, msg); + err.note(&format!( + "this is required to use type `{}` with register class `{}`", + ty, + reg_class.name(), + )); + err.emit(); + return Some(asm_ty); + } + } + + // Check whether a modifier is suggested for using this type. + if let Some((suggested_modifier, suggested_result, switch_reg_class)) = + reg_class.suggest_modifier(asm_arch, asm_ty) + { + // Search for any use of this operand without a modifier and emit + // the suggestion for them. + let mut spans = vec![]; + for piece in template { + if let &InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span } = piece + { + if operand_idx == idx && modifier.is_none() { + spans.push(span); + } + } + } + if !spans.is_empty() { + let (default_modifier, default_result) = + reg_class.default_modifier(asm_arch).unwrap(); + self.tcx.struct_span_lint_hir( + lint::builtin::ASM_SUB_REGISTER, + expr.hir_id, + spans, + |lint| { + let msg = "formatting may not be suitable for sub-register argument"; + let mut err = lint.build(msg); + err.span_label(expr.span, "for this argument"); + if let Some(switch_reg_class) = switch_reg_class { + err.help(&format!( + "use the `{}` modifier with the `{}` register class \ + to have the register formatted as `{}`", + suggested_modifier, switch_reg_class, suggested_result, + )); + } else { + err.help(&format!( + "use the `{}` modifier to have the register formatted as `{}`", + suggested_modifier, suggested_result, + )); + } + err.help(&format!( + "or use the `{}` modifier to keep the default formatting of `{}`", + default_modifier, default_result, + )); + err.emit(); + }, + ); + } + } + + Some(asm_ty) + } + + fn check_asm(&self, asm: &hir::InlineAsm<'tcx>) { + for (idx, op) in asm.operands.iter().enumerate() { + match *op { + hir::InlineAsmOperand::In { reg, ref expr } => { + self.check_asm_operand_type(idx, reg, expr, asm.template, None); + } + hir::InlineAsmOperand::Out { reg, late: _, ref expr } => { + if let Some(expr) = expr { + self.check_asm_operand_type(idx, reg, expr, asm.template, None); + } + } + hir::InlineAsmOperand::InOut { reg, late: _, ref expr } => { + self.check_asm_operand_type(idx, reg, expr, asm.template, None); + } + hir::InlineAsmOperand::SplitInOut { reg, late: _, ref in_expr, ref out_expr } => { + let in_ty = self.check_asm_operand_type(idx, reg, in_expr, asm.template, None); + if let Some(out_expr) = out_expr { + self.check_asm_operand_type( + idx, + reg, + out_expr, + asm.template, + Some((in_expr, in_ty)), + ); + } + } + hir::InlineAsmOperand::Const { ref expr } => { + let ty = self.tables.expr_ty_adjusted(expr); + match ty.kind { + ty::Int(_) | ty::Uint(_) | ty::Float(_) => {} + _ => { + let msg = + "asm `const` arguments must be integer or floating-point values"; + self.tcx.sess.span_err(expr.span, msg); + } + } + } + hir::InlineAsmOperand::Sym { .. } => {} + } + } + } } impl Visitor<'tcx> for ItemVisitor<'tcx> { @@ -146,19 +405,23 @@ impl Visitor<'tcx> for ExprVisitor<'tcx> { } fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { - let res = if let hir::ExprKind::Path(ref qpath) = expr.kind { - self.tables.qpath_res(qpath, expr.hir_id) - } else { - Res::Err - }; - if let Res::Def(DefKind::Fn, did) = res { - if self.def_id_is_transmute(did) { - let typ = self.tables.node_type(expr.hir_id); - let sig = typ.fn_sig(self.tcx); - let from = sig.inputs().skip_binder()[0]; - let to = *sig.output().skip_binder(); - self.check_transmute(expr.span, from, to); + match expr.kind { + hir::ExprKind::Path(ref qpath) => { + let res = self.tables.qpath_res(qpath, expr.hir_id); + if let Res::Def(DefKind::Fn, did) = res { + if self.def_id_is_transmute(did) { + let typ = self.tables.node_type(expr.hir_id); + let sig = typ.fn_sig(self.tcx); + let from = sig.inputs().skip_binder()[0]; + let to = *sig.output().skip_binder(); + self.check_transmute(expr.span, from, to); + } + } } + + hir::ExprKind::InlineAsm(asm) => self.check_asm(asm), + + _ => {} } intravisit::walk_expr(self, expr); diff --git a/src/librustc_passes/liveness.rs b/src/librustc_passes/liveness.rs index 6e7d116ce1d..57d14220c2b 100644 --- a/src/librustc_passes/liveness.rs +++ b/src/librustc_passes/liveness.rs @@ -109,6 +109,7 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_session::lint; use rustc_span::symbol::{sym, Symbol}; use rustc_span::Span; +use rustc_target::asm::InlineAsmOptions; use std::collections::VecDeque; use std::fmt; @@ -531,6 +532,7 @@ fn visit_expr<'tcx>(ir: &mut IrMaps<'tcx>, expr: &'tcx Expr<'tcx>) { | hir::ExprKind::AssignOp(..) | hir::ExprKind::Struct(..) | hir::ExprKind::Repeat(..) + | hir::ExprKind::InlineAsm(..) | hir::ExprKind::LlvmInlineAsm(..) | hir::ExprKind::Box(..) | hir::ExprKind::Yield(..) @@ -1176,6 +1178,64 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> { | hir::ExprKind::Yield(ref e, _) | hir::ExprKind::Repeat(ref e, _) => self.propagate_through_expr(&e, succ), + hir::ExprKind::InlineAsm(ref asm) => { + // Handle non-returning asm + let mut succ = if asm.options.contains(InlineAsmOptions::NORETURN) { + self.s.exit_ln + } else { + succ + }; + + // Do a first pass for writing outputs only + for op in asm.operands.iter().rev() { + match op { + hir::InlineAsmOperand::In { .. } + | hir::InlineAsmOperand::Const { .. } + | hir::InlineAsmOperand::Sym { .. } => {} + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + succ = self.write_place(expr, succ, ACC_WRITE); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + succ = self.write_place(expr, succ, ACC_READ | ACC_WRITE); + } + hir::InlineAsmOperand::SplitInOut { out_expr, .. } => { + if let Some(expr) = out_expr { + succ = self.write_place(expr, succ, ACC_WRITE); + } + } + } + } + + // Then do a second pass for inputs + let mut succ = succ; + for op in asm.operands.iter().rev() { + match op { + hir::InlineAsmOperand::In { expr, .. } + | hir::InlineAsmOperand::Const { expr, .. } + | hir::InlineAsmOperand::Sym { expr, .. } => { + succ = self.propagate_through_expr(expr, succ) + } + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + succ = self.propagate_through_place_components(expr, succ); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + succ = self.propagate_through_place_components(expr, succ); + } + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + if let Some(expr) = out_expr { + succ = self.propagate_through_place_components(expr, succ); + } + succ = self.propagate_through_expr(in_expr, succ); + } + } + } + succ + } + hir::ExprKind::LlvmInlineAsm(ref asm) => { let ia = &asm.inner; let outputs = asm.outputs_exprs; @@ -1397,6 +1457,33 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) { } } + hir::ExprKind::InlineAsm(ref asm) => { + for op in asm.operands { + match op { + hir::InlineAsmOperand::In { expr, .. } + | hir::InlineAsmOperand::Const { expr, .. } + | hir::InlineAsmOperand::Sym { expr, .. } => this.visit_expr(expr), + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + this.check_place(expr); + this.visit_expr(expr); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + this.check_place(expr); + this.visit_expr(expr); + } + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + this.visit_expr(in_expr); + if let Some(out_expr) = out_expr { + this.check_place(out_expr); + this.visit_expr(out_expr); + } + } + } + } + } + hir::ExprKind::LlvmInlineAsm(ref asm) => { for input in asm.inputs_exprs { this.visit_expr(input); diff --git a/src/librustc_session/lint/builtin.rs b/src/librustc_session/lint/builtin.rs index 3ace9ecbd60..3d03e46683e 100644 --- a/src/librustc_session/lint/builtin.rs +++ b/src/librustc_session/lint/builtin.rs @@ -508,6 +508,12 @@ declare_lint! { "detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`", } +declare_lint! { + pub ASM_SUB_REGISTER, + Warn, + "using only a subset of a register for inline asm inputs", +} + declare_lint_pass! { /// Does nothing as a lint pass, but registers some `Lint`s /// that are used by other parts of the compiler. @@ -576,6 +582,7 @@ declare_lint_pass! { INDIRECT_STRUCTURAL_MATCH, SOFT_UNSTABLE, INLINE_NO_SANITIZE, + ASM_SUB_REGISTER, ] } diff --git a/src/librustc_trait_selection/traits/error_reporting/suggestions.rs b/src/librustc_trait_selection/traits/error_reporting/suggestions.rs index c098e44fa06..bd58e86988a 100644 --- a/src/librustc_trait_selection/traits/error_reporting/suggestions.rs +++ b/src/librustc_trait_selection/traits/error_reporting/suggestions.rs @@ -1746,6 +1746,9 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> { ObligationCauseCode::ConstSized => { err.note("constant expressions must have a statically known size"); } + ObligationCauseCode::InlineAsmSized => { + err.note("all inline asm arguments must have a statically known size"); + } ObligationCauseCode::ConstPatternStructural => { err.note("constants used for pattern-matching must derive `PartialEq` and `Eq`"); } diff --git a/src/librustc_typeck/check/expr.rs b/src/librustc_typeck/check/expr.rs index 71e1b32aeaa..0ec2d438c28 100644 --- a/src/librustc_typeck/check/expr.rs +++ b/src/librustc_typeck/check/expr.rs @@ -38,6 +38,7 @@ use rustc_middle::ty::{AdtKind, Visibility}; use rustc_span::hygiene::DesugaringKind; use rustc_span::source_map::Span; use rustc_span::symbol::{kw, sym, Ident, Symbol}; +use rustc_target::asm::InlineAsmOptions; use rustc_trait_selection::traits::{self, ObligationCauseCode}; use std::fmt::Display; @@ -232,6 +233,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.check_expr_addr_of(kind, mutbl, oprnd, expected, expr) } ExprKind::Path(ref qpath) => self.check_expr_path(qpath, expr), + ExprKind::InlineAsm(asm) => self.check_expr_asm(asm), ExprKind::LlvmInlineAsm(ref asm) => { for expr in asm.outputs_exprs.iter().chain(asm.inputs_exprs.iter()) { self.check_expr(expr); @@ -1811,6 +1813,72 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } } + + fn check_expr_asm_operand(&self, expr: &'tcx hir::Expr<'tcx>, is_input: bool) { + let needs = if is_input { Needs::None } else { Needs::MutPlace }; + let ty = self.check_expr_with_needs(expr, needs); + self.require_type_is_sized(ty, expr.span, traits::InlineAsmSized); + + if !is_input && !expr.is_syntactic_place_expr() { + let mut err = self.tcx.sess.struct_span_err(expr.span, "invalid asm output"); + err.span_label(expr.span, "cannot assign to this expression"); + err.emit(); + } + + // If this is an input value, we require its type to be fully resolved + // at this point. This allows us to provide helpful coercions which help + // pass the type whitelist in a later pass. + // + // We don't require output types to be resolved at this point, which + // allows them to be inferred based on how they are used later in the + // function. + if is_input { + let ty = self.structurally_resolved_type(expr.span, &ty); + match ty.kind { + ty::FnDef(..) => { + let fnptr_ty = self.tcx.mk_fn_ptr(ty.fn_sig(self.tcx)); + self.demand_coerce(expr, ty, fnptr_ty, AllowTwoPhase::No); + } + ty::Ref(_, base_ty, mutbl) => { + let ptr_ty = self.tcx.mk_ptr(ty::TypeAndMut { ty: base_ty, mutbl }); + self.demand_coerce(expr, ty, ptr_ty, AllowTwoPhase::No); + } + _ => {} + } + } + } + + fn check_expr_asm(&self, asm: &'tcx hir::InlineAsm<'tcx>) -> Ty<'tcx> { + for op in asm.operands { + match op { + hir::InlineAsmOperand::In { expr, .. } | hir::InlineAsmOperand::Const { expr } => { + self.check_expr_asm_operand(expr, true); + } + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + self.check_expr_asm_operand(expr, false); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + self.check_expr_asm_operand(expr, false); + } + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + self.check_expr_asm_operand(in_expr, true); + if let Some(out_expr) = out_expr { + self.check_expr_asm_operand(out_expr, false); + } + } + hir::InlineAsmOperand::Sym { expr } => { + self.check_expr(expr); + } + } + } + if asm.options.contains(InlineAsmOptions::NORETURN) { + self.tcx.types.never + } else { + self.tcx.mk_unit() + } + } } pub(super) fn ty_kind_suggestion(ty: Ty<'_>) -> Option<&'static str> { diff --git a/src/librustc_typeck/expr_use_visitor.rs b/src/librustc_typeck/expr_use_visitor.rs index cdebd63b594..9ba00faec49 100644 --- a/src/librustc_typeck/expr_use_visitor.rs +++ b/src/librustc_typeck/expr_use_visitor.rs @@ -220,6 +220,30 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> { self.borrow_expr(&base, bk); } + hir::ExprKind::InlineAsm(ref asm) => { + for op in asm.operands { + match op { + hir::InlineAsmOperand::In { expr, .. } + | hir::InlineAsmOperand::Const { expr, .. } + | hir::InlineAsmOperand::Sym { expr, .. } => self.consume_expr(expr), + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + self.mutate_expr(expr); + } + } + hir::InlineAsmOperand::InOut { expr, .. } => { + self.mutate_expr(expr); + } + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + self.consume_expr(in_expr); + if let Some(out_expr) = out_expr { + self.mutate_expr(out_expr); + } + } + } + } + } + hir::ExprKind::LlvmInlineAsm(ref ia) => { for (o, output) in ia.inner.outputs.iter().zip(ia.outputs_exprs) { if o.is_indirect { diff --git a/src/librustc_typeck/mem_categorization.rs b/src/librustc_typeck/mem_categorization.rs index ffe9f1c7d7a..71f3e2d03c9 100644 --- a/src/librustc_typeck/mem_categorization.rs +++ b/src/librustc_typeck/mem_categorization.rs @@ -405,6 +405,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> { | hir::ExprKind::Continue(..) | hir::ExprKind::Struct(..) | hir::ExprKind::Repeat(..) + | hir::ExprKind::InlineAsm(..) | hir::ExprKind::LlvmInlineAsm(..) | hir::ExprKind::Box(..) | hir::ExprKind::Err => Ok(self.cat_rvalue(expr.hir_id, expr.span, expr_ty)), From 1e7b24608631cc1405872631a155fb4e8d52329e Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 14 Feb 2020 18:17:50 +0000 Subject: [PATCH 08/36] Add asm! to MIR --- src/librustc_middle/mir/mod.rs | 112 +++++++++++++++-- src/librustc_middle/mir/type_foldable.rs | 4 + src/librustc_middle/mir/visit.rs | 38 ++++++ src/librustc_middle/ty/structural_impls.rs | 3 + src/librustc_mir/borrow_check/invalidation.rs | 25 +++- src/librustc_mir/borrow_check/mod.rs | 41 ++++++- .../borrow_check/type_check/mod.rs | 8 +- .../dataflow/framework/direction.rs | 6 + .../dataflow/impls/borrowed_locals.rs | 1 + src/librustc_mir/dataflow/impls/borrows.rs | 15 ++- .../dataflow/impls/storage_liveness.rs | 18 +++ .../dataflow/move_paths/builder.rs | 25 ++++ src/librustc_mir/interpret/terminator.rs | 3 + src/librustc_mir/monomorphize/collector.rs | 3 +- .../transform/check_consts/validation.rs | 4 + src/librustc_mir/transform/check_unsafety.rs | 6 + src/librustc_mir/transform/const_prop.rs | 3 +- src/librustc_mir/transform/generator.rs | 3 +- src/librustc_mir/transform/inline.rs | 5 + .../transform/qualify_min_const_fn.rs | 4 + .../transform/remove_noop_landing_pads.rs | 3 +- src/librustc_mir_build/build/expr/as_place.rs | 1 + .../build/expr/as_rvalue.rs | 1 + src/librustc_mir_build/build/expr/category.rs | 3 +- src/librustc_mir_build/build/expr/into.rs | 70 ++++++++++- src/librustc_mir_build/build/scope.rs | 3 +- src/librustc_mir_build/hair/cx/expr.rs | 115 ++++++++++++++++++ src/librustc_mir_build/hair/mod.rs | 39 ++++++ src/librustc_mir_build/lints.rs | 4 + 29 files changed, 544 insertions(+), 22 deletions(-) diff --git a/src/librustc_middle/mir/mod.rs b/src/librustc_middle/mir/mod.rs index 62e6e124981..707e831b3e8 100644 --- a/src/librustc_middle/mir/mod.rs +++ b/src/librustc_middle/mir/mod.rs @@ -28,6 +28,7 @@ use rustc_macros::HashStable; use rustc_serialize::{Decodable, Encodable}; use rustc_span::symbol::Symbol; use rustc_span::{Span, DUMMY_SP}; +use rustc_target::asm::{InlineAsmOptions, InlineAsmRegOrRegClass, InlineAsmTemplatePiece}; use std::borrow::Cow; use std::fmt::{self, Debug, Display, Formatter, Write}; use std::ops::{Index, IndexMut}; @@ -1178,6 +1179,23 @@ pub enum TerminatorKind<'tcx> { /// of the `remove_noop_landing_pads` and `no_landing_pads` passes. unwind: Option, }, + + /// Block ends with an inline assembly block. This is a terminator since + /// inline assembly is allowed to diverge. + InlineAsm { + /// The template for the inline assembly, with placeholders. + template: &'tcx [InlineAsmTemplatePiece], + + /// The operands for the inline assembly, as `Operand`s or `Place`s. + operands: Vec>, + + /// Miscellaneous options for the inline assembly. + options: InlineAsmOptions, + + /// Destination block after the inline assembly returns, unless it is + /// diverging (InlineAsmOptions::NORETURN). + destination: Option, + }, } /// Information about an assertion failure. @@ -1192,6 +1210,34 @@ pub enum AssertKind { ResumedAfterPanic(GeneratorKind), } +#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable, HashStable, TypeFoldable)] +pub enum InlineAsmOperand<'tcx> { + In { + reg: InlineAsmRegOrRegClass, + value: Operand<'tcx>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + place: Option>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_value: Operand<'tcx>, + out_place: Option>, + }, + Const { + value: Operand<'tcx>, + }, + SymFn { + value: Box>, + }, + SymStatic { + value: Box>, + }, +} + /// Type for MIR `Assert` terminator error messages. pub type AssertMessage<'tcx> = AssertKind>; @@ -1242,7 +1288,8 @@ impl<'tcx> TerminatorKind<'tcx> { | GeneratorDrop | Return | Unreachable - | Call { destination: None, cleanup: None, .. } => None.into_iter().chain(&[]), + | Call { destination: None, cleanup: None, .. } + | InlineAsm { destination: None, .. } => None.into_iter().chain(&[]), Goto { target: ref t } | Call { destination: None, cleanup: Some(ref t), .. } | Call { destination: Some((_, ref t)), cleanup: None, .. } @@ -1250,7 +1297,8 @@ impl<'tcx> TerminatorKind<'tcx> { | DropAndReplace { target: ref t, unwind: None, .. } | Drop { target: ref t, unwind: None, .. } | Assert { target: ref t, cleanup: None, .. } - | FalseUnwind { real_target: ref t, unwind: None } => Some(t).into_iter().chain(&[]), + | FalseUnwind { real_target: ref t, unwind: None } + | InlineAsm { destination: Some(ref t), .. } => Some(t).into_iter().chain(&[]), Call { destination: Some((_, ref t)), cleanup: Some(ref u), .. } | Yield { resume: ref t, drop: Some(ref u), .. } | DropAndReplace { target: ref t, unwind: Some(ref u), .. } @@ -1274,7 +1322,8 @@ impl<'tcx> TerminatorKind<'tcx> { | GeneratorDrop | Return | Unreachable - | Call { destination: None, cleanup: None, .. } => None.into_iter().chain(&mut []), + | Call { destination: None, cleanup: None, .. } + | InlineAsm { destination: None, .. } => None.into_iter().chain(&mut []), Goto { target: ref mut t } | Call { destination: None, cleanup: Some(ref mut t), .. } | Call { destination: Some((_, ref mut t)), cleanup: None, .. } @@ -1282,9 +1331,8 @@ impl<'tcx> TerminatorKind<'tcx> { | DropAndReplace { target: ref mut t, unwind: None, .. } | Drop { target: ref mut t, unwind: None, .. } | Assert { target: ref mut t, cleanup: None, .. } - | FalseUnwind { real_target: ref mut t, unwind: None } => { - Some(t).into_iter().chain(&mut []) - } + | FalseUnwind { real_target: ref mut t, unwind: None } + | InlineAsm { destination: Some(ref mut t), .. } => Some(t).into_iter().chain(&mut []), Call { destination: Some((_, ref mut t)), cleanup: Some(ref mut u), .. } | Yield { resume: ref mut t, drop: Some(ref mut u), .. } | DropAndReplace { target: ref mut t, unwind: Some(ref mut u), .. } @@ -1310,7 +1358,8 @@ impl<'tcx> TerminatorKind<'tcx> { | TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } | TerminatorKind::SwitchInt { .. } - | TerminatorKind::FalseEdges { .. } => None, + | TerminatorKind::FalseEdges { .. } + | TerminatorKind::InlineAsm { .. } => None, TerminatorKind::Call { cleanup: ref unwind, .. } | TerminatorKind::Assert { cleanup: ref unwind, .. } | TerminatorKind::DropAndReplace { ref unwind, .. } @@ -1329,7 +1378,8 @@ impl<'tcx> TerminatorKind<'tcx> { | TerminatorKind::GeneratorDrop | TerminatorKind::Yield { .. } | TerminatorKind::SwitchInt { .. } - | TerminatorKind::FalseEdges { .. } => None, + | TerminatorKind::FalseEdges { .. } + | TerminatorKind::InlineAsm { .. } => None, TerminatorKind::Call { cleanup: ref mut unwind, .. } | TerminatorKind::Assert { cleanup: ref mut unwind, .. } | TerminatorKind::DropAndReplace { ref mut unwind, .. } @@ -1544,6 +1594,50 @@ impl<'tcx> TerminatorKind<'tcx> { } FalseEdges { .. } => write!(fmt, "falseEdges"), FalseUnwind { .. } => write!(fmt, "falseUnwind"), + InlineAsm { template, ref operands, options, destination: _ } => { + write!(fmt, "asm!(\"{}\"", InlineAsmTemplatePiece::to_string(template))?; + for op in operands { + write!(fmt, ", ")?; + let print_late = |&late| if late { "late" } else { "" }; + match op { + InlineAsmOperand::In { reg, value } => { + write!(fmt, "in({}) {:?}", reg, value)?; + } + InlineAsmOperand::Out { reg, late, place: Some(place) } => { + write!(fmt, "{}out({}) {:?}", print_late(late), reg, place)?; + } + InlineAsmOperand::Out { reg, late, place: None } => { + write!(fmt, "{}out({}) _", print_late(late), reg)?; + } + InlineAsmOperand::InOut { + reg, + late, + in_value, + out_place: Some(out_place), + } => { + write!( + fmt, + "in{}out({}) {:?} => {:?}", + print_late(late), + reg, + in_value, + out_place + )?; + } + InlineAsmOperand::InOut { reg, late, in_value, out_place: None } => { + write!(fmt, "in{}out({}) {:?} => _", print_late(late), reg, in_value)?; + } + InlineAsmOperand::Const { value } => { + write!(fmt, "const {:?}", value)?; + } + InlineAsmOperand::SymFn { value } + | InlineAsmOperand::SymStatic { value } => { + write!(fmt, "sym {:?}", value)?; + } + } + } + write!(fmt, ", options({:?}))", options) + } } } @@ -1586,6 +1680,8 @@ impl<'tcx> TerminatorKind<'tcx> { FalseEdges { .. } => vec!["real".into(), "imaginary".into()], FalseUnwind { unwind: Some(_), .. } => vec!["real".into(), "cleanup".into()], FalseUnwind { unwind: None, .. } => vec!["real".into()], + InlineAsm { destination: Some(_), .. } => vec!["".into()], + InlineAsm { destination: None, .. } => vec![], } } } diff --git a/src/librustc_middle/mir/type_foldable.rs b/src/librustc_middle/mir/type_foldable.rs index 9520f081b6b..bb7001c1207 100644 --- a/src/librustc_middle/mir/type_foldable.rs +++ b/src/librustc_middle/mir/type_foldable.rs @@ -78,6 +78,9 @@ impl<'tcx> TypeFoldable<'tcx> for Terminator<'tcx> { FalseEdges { real_target, imaginary_target } } FalseUnwind { real_target, unwind } => FalseUnwind { real_target, unwind }, + InlineAsm { template, ref operands, options, destination } => { + InlineAsm { template, operands: operands.fold_with(folder), options, destination } + } }; Terminator { source_info: self.source_info, kind } } @@ -120,6 +123,7 @@ impl<'tcx> TypeFoldable<'tcx> for Terminator<'tcx> { false } } + InlineAsm { ref operands, .. } => operands.visit_with(visitor), Goto { .. } | Resume | Abort diff --git a/src/librustc_middle/mir/visit.rs b/src/librustc_middle/mir/visit.rs index 1f097f24942..02164244771 100644 --- a/src/librustc_middle/mir/visit.rs +++ b/src/librustc_middle/mir/visit.rs @@ -531,6 +531,44 @@ macro_rules! make_mir_visitor { ); } + TerminatorKind::InlineAsm { + template: _, + operands, + options: _, + destination: _, + } => { + for op in operands { + match op { + InlineAsmOperand::In { value, .. } + | InlineAsmOperand::Const { value } => { + self.visit_operand(value, source_location); + } + InlineAsmOperand::Out { place, .. } => { + if let Some(place) = place { + self.visit_place( + place, + PlaceContext::MutatingUse(MutatingUseContext::Store), + source_location, + ); + } + } + InlineAsmOperand::InOut { in_value, out_place, .. } => { + self.visit_operand(in_value, source_location); + if let Some(out_place) = out_place { + self.visit_place( + out_place, + PlaceContext::MutatingUse(MutatingUseContext::Store), + source_location, + ); + } + } + InlineAsmOperand::SymFn { value } + | InlineAsmOperand::SymStatic { value } => { + self.visit_constant(value, source_location); + } + } + } + } } } diff --git a/src/librustc_middle/ty/structural_impls.rs b/src/librustc_middle/ty/structural_impls.rs index 680b7187921..ce0ee5fe53a 100644 --- a/src/librustc_middle/ty/structural_impls.rs +++ b/src/librustc_middle/ty/structural_impls.rs @@ -267,6 +267,9 @@ CloneTypeFoldableAndLiftImpls! { ::rustc_hir::MatchSource, ::rustc_hir::Mutability, ::rustc_hir::Unsafety, + ::rustc_target::asm::InlineAsmOptions, + ::rustc_target::asm::InlineAsmRegOrRegClass, + ::rustc_target::asm::InlineAsmTemplatePiece, ::rustc_target::spec::abi::Abi, crate::mir::Local, crate::mir::Promoted, diff --git a/src/librustc_mir/borrow_check/invalidation.rs b/src/librustc_mir/borrow_check/invalidation.rs index a8c7a959b28..178e3db17cd 100644 --- a/src/librustc_mir/borrow_check/invalidation.rs +++ b/src/librustc_mir/borrow_check/invalidation.rs @@ -1,8 +1,8 @@ use rustc_data_structures::graph::dominators::Dominators; use rustc_middle::mir::visit::Visitor; -use rustc_middle::mir::TerminatorKind; use rustc_middle::mir::{BasicBlock, Body, Location, Place, Rvalue}; use rustc_middle::mir::{BorrowKind, Mutability, Operand}; +use rustc_middle::mir::{InlineAsmOperand, TerminatorKind}; use rustc_middle::mir::{Statement, StatementKind}; use rustc_middle::ty::TyCtxt; @@ -183,6 +183,29 @@ impl<'cx, 'tcx> Visitor<'tcx> for InvalidationGenerator<'cx, 'tcx> { } } } + TerminatorKind::InlineAsm { template: _, ref operands, options: _, destination: _ } => { + for op in operands { + match *op { + InlineAsmOperand::In { reg: _, ref value } + | InlineAsmOperand::Const { ref value } => { + self.consume_operand(location, value); + } + InlineAsmOperand::Out { reg: _, late: _, place, .. } => { + if let Some(place) = place { + self.mutate_place(location, place, Shallow(None), JustWrite); + } + } + InlineAsmOperand::InOut { reg: _, late: _, ref in_value, out_place } => { + self.consume_operand(location, in_value); + if let Some(out_place) = out_place { + self.mutate_place(location, out_place, Shallow(None), JustWrite); + } + } + InlineAsmOperand::SymFn { value: _ } + | InlineAsmOperand::SymStatic { value: _ } => {} + } + } + } TerminatorKind::Goto { target: _ } | TerminatorKind::Abort | TerminatorKind::Unreachable diff --git a/src/librustc_mir/borrow_check/mod.rs b/src/librustc_mir/borrow_check/mod.rs index a3ee49651ba..a0c1d96bb47 100644 --- a/src/librustc_mir/borrow_check/mod.rs +++ b/src/librustc_mir/borrow_check/mod.rs @@ -17,7 +17,7 @@ use rustc_middle::mir::{ }; use rustc_middle::mir::{AggregateKind, BasicBlock, BorrowCheckResult, BorrowKind}; use rustc_middle::mir::{Field, ProjectionElem, Promoted, Rvalue, Statement, StatementKind}; -use rustc_middle::mir::{Terminator, TerminatorKind}; +use rustc_middle::mir::{InlineAsmOperand, Terminator, TerminatorKind}; use rustc_middle::ty::query::Providers; use rustc_middle::ty::{self, RegionVid, TyCtxt}; use rustc_session::lint::builtin::{MUTABLE_BORROW_RESERVATION_CONFLICT, UNUSED_MUT}; @@ -724,6 +724,42 @@ impl<'cx, 'tcx> dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtxt<'cx, 'tc self.mutate_place(loc, (resume_arg, span), Deep, JustWrite, flow_state); } + TerminatorKind::InlineAsm { template: _, ref operands, options: _, destination: _ } => { + for op in operands { + match *op { + InlineAsmOperand::In { reg: _, ref value } + | InlineAsmOperand::Const { ref value } => { + self.consume_operand(loc, (value, span), flow_state); + } + InlineAsmOperand::Out { reg: _, late: _, place, .. } => { + if let Some(place) = place { + self.mutate_place( + loc, + (place, span), + Shallow(None), + JustWrite, + flow_state, + ); + } + } + InlineAsmOperand::InOut { reg: _, late: _, ref in_value, out_place } => { + self.consume_operand(loc, (in_value, span), flow_state); + if let Some(out_place) = out_place { + self.mutate_place( + loc, + (out_place, span), + Shallow(None), + JustWrite, + flow_state, + ); + } + } + InlineAsmOperand::SymFn { value: _ } + | InlineAsmOperand::SymStatic { value: _ } => {} + } + } + } + TerminatorKind::Goto { target: _ } | TerminatorKind::Abort | TerminatorKind::Unreachable @@ -778,7 +814,8 @@ impl<'cx, 'tcx> dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtxt<'cx, 'tc | TerminatorKind::FalseUnwind { real_target: _, unwind: _ } | TerminatorKind::Goto { .. } | TerminatorKind::SwitchInt { .. } - | TerminatorKind::Unreachable => {} + | TerminatorKind::Unreachable + | TerminatorKind::InlineAsm { .. } => {} } } } diff --git a/src/librustc_mir/borrow_check/type_check/mod.rs b/src/librustc_mir/borrow_check/type_check/mod.rs index bad176c603f..d7705b8c6a1 100644 --- a/src/librustc_mir/borrow_check/type_check/mod.rs +++ b/src/librustc_mir/borrow_check/type_check/mod.rs @@ -1548,7 +1548,8 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { | TerminatorKind::Unreachable | TerminatorKind::Drop { .. } | TerminatorKind::FalseEdges { .. } - | TerminatorKind::FalseUnwind { .. } => { + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } => { // no checks needed for these } @@ -1855,6 +1856,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { self.assert_iscleanup(body, block_data, unwind, true); } } + TerminatorKind::InlineAsm { ref destination, .. } => { + if let &Some(target) = destination { + self.assert_iscleanup(body, block_data, target, is_cleanup); + } + } } } diff --git a/src/librustc_mir/dataflow/framework/direction.rs b/src/librustc_mir/dataflow/framework/direction.rs index 76f703ec913..97b14ea771b 100644 --- a/src/librustc_mir/dataflow/framework/direction.rs +++ b/src/librustc_mir/dataflow/framework/direction.rs @@ -482,6 +482,12 @@ impl Direction for Forward { } } + InlineAsm { template: _, operands: _, options: _, destination } => { + if let Some(target) = destination { + propagate(target, exit_state); + } + } + SwitchInt { ref targets, ref values, ref discr, switch_ty: _ } => { let enum_ = discr .place() diff --git a/src/librustc_mir/dataflow/impls/borrowed_locals.rs b/src/librustc_mir/dataflow/impls/borrowed_locals.rs index 6972a81cf1b..f929b2ddde0 100644 --- a/src/librustc_mir/dataflow/impls/borrowed_locals.rs +++ b/src/librustc_mir/dataflow/impls/borrowed_locals.rs @@ -203,6 +203,7 @@ where | TerminatorKind::FalseUnwind { .. } | TerminatorKind::GeneratorDrop | TerminatorKind::Goto { .. } + | TerminatorKind::InlineAsm { .. } | TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::SwitchInt { .. } diff --git a/src/librustc_mir/dataflow/impls/borrows.rs b/src/librustc_mir/dataflow/impls/borrows.rs index 0de8f45720e..dfca270396d 100644 --- a/src/librustc_mir/dataflow/impls/borrows.rs +++ b/src/librustc_mir/dataflow/impls/borrows.rs @@ -317,10 +317,19 @@ impl<'tcx> dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> { fn terminator_effect( &self, - _: &mut impl GenKill, - _: &mir::Terminator<'tcx>, - _: Location, + trans: &mut impl GenKill, + teminator: &mir::Terminator<'tcx>, + _location: Location, ) { + if let mir::TerminatorKind::InlineAsm { operands, .. } = &teminator.kind { + for op in operands { + if let mir::InlineAsmOperand::Out { place: Some(place), .. } + | mir::InlineAsmOperand::InOut { out_place: Some(place), .. } = *op + { + self.kill_borrows_on_place(trans, place); + } + } + } } fn call_return_effect( diff --git a/src/librustc_mir/dataflow/impls/storage_liveness.rs b/src/librustc_mir/dataflow/impls/storage_liveness.rs index 4c784c3f1a1..bbc4942030e 100644 --- a/src/librustc_mir/dataflow/impls/storage_liveness.rs +++ b/src/librustc_mir/dataflow/impls/storage_liveness.rs @@ -183,6 +183,23 @@ impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, // place to have storage *before* the yield, only after. TerminatorKind::Yield { .. } => {} + TerminatorKind::InlineAsm { operands, .. } => { + for op in operands { + match op { + InlineAsmOperand::Out { place, .. } + | InlineAsmOperand::InOut { out_place: place, .. } => { + if let Some(place) = place { + trans.gen(place.local); + } + } + InlineAsmOperand::In { .. } + | InlineAsmOperand::Const { .. } + | InlineAsmOperand::SymFn { .. } + | InlineAsmOperand::SymStatic { .. } => {} + } + } + } + // Nothing to do for these. Match exhaustively so this fails to compile when new // variants are added. TerminatorKind::Call { destination: None, .. } @@ -228,6 +245,7 @@ impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, | TerminatorKind::FalseUnwind { .. } | TerminatorKind::GeneratorDrop | TerminatorKind::Goto { .. } + | TerminatorKind::InlineAsm { .. } | TerminatorKind::Resume | TerminatorKind::Return | TerminatorKind::SwitchInt { .. } diff --git a/src/librustc_mir/dataflow/move_paths/builder.rs b/src/librustc_mir/dataflow/move_paths/builder.rs index ffe84e9b655..0f2760b3f3b 100644 --- a/src/librustc_mir/dataflow/move_paths/builder.rs +++ b/src/librustc_mir/dataflow/move_paths/builder.rs @@ -411,6 +411,31 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> { self.gather_init(destination.as_ref(), InitKind::NonPanicPathOnly); } } + TerminatorKind::InlineAsm { template: _, ref operands, options: _, destination: _ } => { + for op in operands { + match *op { + InlineAsmOperand::In { reg: _, ref value } + | InlineAsmOperand::Const { ref value } => { + self.gather_operand(value); + } + InlineAsmOperand::Out { reg: _, late: _, place, .. } => { + if let Some(place) = place { + self.create_move_path(place); + self.gather_init(place.as_ref(), InitKind::Deep); + } + } + InlineAsmOperand::InOut { reg: _, late: _, ref in_value, out_place } => { + self.gather_operand(in_value); + if let Some(out_place) = out_place { + self.create_move_path(out_place); + self.gather_init(out_place.as_ref(), InitKind::Deep); + } + } + InlineAsmOperand::SymFn { value: _ } + | InlineAsmOperand::SymStatic { value: _ } => {} + } + } + } } } diff --git a/src/librustc_mir/interpret/terminator.rs b/src/librustc_mir/interpret/terminator.rs index 9f9ec0f6a78..b048240ca8d 100644 --- a/src/librustc_mir/interpret/terminator.rs +++ b/src/librustc_mir/interpret/terminator.rs @@ -131,6 +131,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { "{:#?} should have been eliminated by MIR pass", terminator.kind ), + + // Inline assembly can't be interpreted. + InlineAsm { .. } => throw_unsup_format!("inline assembly is not supported"), } Ok(()) diff --git a/src/librustc_mir/monomorphize/collector.rs b/src/librustc_mir/monomorphize/collector.rs index a8094990594..925b8d32966 100644 --- a/src/librustc_mir/monomorphize/collector.rs +++ b/src/librustc_mir/monomorphize/collector.rs @@ -639,7 +639,8 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> { | mir::TerminatorKind::Abort | mir::TerminatorKind::Return | mir::TerminatorKind::Unreachable - | mir::TerminatorKind::Assert { .. } => {} + | mir::TerminatorKind::Assert { .. } + | mir::TerminatorKind::InlineAsm { .. } => {} mir::TerminatorKind::GeneratorDrop | mir::TerminatorKind::Yield { .. } | mir::TerminatorKind::FalseEdges { .. } diff --git a/src/librustc_mir/transform/check_consts/validation.rs b/src/librustc_mir/transform/check_consts/validation.rs index 8c005fdcdbf..987c9e24fc3 100644 --- a/src/librustc_mir/transform/check_consts/validation.rs +++ b/src/librustc_mir/transform/check_consts/validation.rs @@ -603,6 +603,10 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> { } } + TerminatorKind::InlineAsm { .. } => { + self.check_op(ops::InlineAsm); + } + // FIXME: Some of these are only caught by `min_const_fn`, but should error here // instead. TerminatorKind::Abort diff --git a/src/librustc_mir/transform/check_unsafety.rs b/src/librustc_mir/transform/check_unsafety.rs index f7c6396556d..9bcb45f6493 100644 --- a/src/librustc_mir/transform/check_unsafety.rs +++ b/src/librustc_mir/transform/check_unsafety.rs @@ -95,6 +95,12 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> { self.check_target_features(func_id); } } + + TerminatorKind::InlineAsm { .. } => self.require_unsafe( + "use of inline assembly", + "inline assembly is entirely unchecked and can cause undefined behavior", + UnsafetyViolationKind::General, + ), } self.super_terminator(terminator, location); } diff --git a/src/librustc_mir/transform/const_prop.rs b/src/librustc_mir/transform/const_prop.rs index e1be1daca45..f69343f4d75 100644 --- a/src/librustc_mir/transform/const_prop.rs +++ b/src/librustc_mir/transform/const_prop.rs @@ -1014,7 +1014,8 @@ impl<'mir, 'tcx> MutVisitor<'tcx> for ConstPropagator<'mir, 'tcx> { | TerminatorKind::Yield { .. } | TerminatorKind::GeneratorDrop | TerminatorKind::FalseEdges { .. } - | TerminatorKind::FalseUnwind { .. } => {} + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } => {} // Every argument in our function calls can be const propagated. TerminatorKind::Call { ref mut args, .. } => { let mir_opt_level = self.tcx.sess.opts.debugging_opts.mir_opt_level; diff --git a/src/librustc_mir/transform/generator.rs b/src/librustc_mir/transform/generator.rs index 14faa5be02f..4bf2adcd450 100644 --- a/src/librustc_mir/transform/generator.rs +++ b/src/librustc_mir/transform/generator.rs @@ -981,7 +981,8 @@ fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool { | TerminatorKind::Unreachable | TerminatorKind::GeneratorDrop | TerminatorKind::FalseEdges { .. } - | TerminatorKind::FalseUnwind { .. } => {} + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } => {} // Resume will *continue* unwinding, but if there's no other unwinding terminator it // will never be reached. diff --git a/src/librustc_mir/transform/inline.rs b/src/librustc_mir/transform/inline.rs index 632408fde74..35d55c4cb9b 100644 --- a/src/librustc_mir/transform/inline.rs +++ b/src/librustc_mir/transform/inline.rs @@ -800,6 +800,11 @@ impl<'a, 'tcx> MutVisitor<'tcx> for Integrator<'a, 'tcx> { { bug!("False unwinds should have been removed before inlining") } + TerminatorKind::InlineAsm { ref mut destination, .. } => { + if let Some(ref mut tgt) = *destination { + *tgt = self.update_target(*tgt); + } + } } } diff --git a/src/librustc_mir/transform/qualify_min_const_fn.rs b/src/librustc_mir/transform/qualify_min_const_fn.rs index c9982aeaa08..a9d6c95b257 100644 --- a/src/librustc_mir/transform/qualify_min_const_fn.rs +++ b/src/librustc_mir/transform/qualify_min_const_fn.rs @@ -391,5 +391,9 @@ fn check_terminator( TerminatorKind::Assert { cond, expected: _, msg: _, target: _, cleanup: _ } => { check_operand(tcx, cond, span, def_id, body) } + + TerminatorKind::InlineAsm { .. } => { + Err((span, "cannot use inline assembly in const fn".into())) + } } } diff --git a/src/librustc_mir/transform/remove_noop_landing_pads.rs b/src/librustc_mir/transform/remove_noop_landing_pads.rs index a86ef7f2f52..69c0163b649 100644 --- a/src/librustc_mir/transform/remove_noop_landing_pads.rs +++ b/src/librustc_mir/transform/remove_noop_landing_pads.rs @@ -77,7 +77,8 @@ impl RemoveNoopLandingPads { | TerminatorKind::Call { .. } | TerminatorKind::Assert { .. } | TerminatorKind::DropAndReplace { .. } - | TerminatorKind::Drop { .. } => false, + | TerminatorKind::Drop { .. } + | TerminatorKind::InlineAsm { .. } => false, } } diff --git a/src/librustc_mir_build/build/expr/as_place.rs b/src/librustc_mir_build/build/expr/as_place.rs index e08eedc6b6e..16bba6ad147 100644 --- a/src/librustc_mir_build/build/expr/as_place.rs +++ b/src/librustc_mir_build/build/expr/as_place.rs @@ -255,6 +255,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::Return { .. } | ExprKind::Literal { .. } | ExprKind::StaticRef { .. } + | ExprKind::InlineAsm { .. } | ExprKind::LlvmInlineAsm { .. } | ExprKind::Yield { .. } | ExprKind::Call { .. } => { diff --git a/src/librustc_mir_build/build/expr/as_rvalue.rs b/src/librustc_mir_build/build/expr/as_rvalue.rs index d934ba1dc84..0f151909750 100644 --- a/src/librustc_mir_build/build/expr/as_rvalue.rs +++ b/src/librustc_mir_build/build/expr/as_rvalue.rs @@ -252,6 +252,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { | ExprKind::Break { .. } | ExprKind::Continue { .. } | ExprKind::Return { .. } + | ExprKind::InlineAsm { .. } | ExprKind::LlvmInlineAsm { .. } | ExprKind::PlaceTypeAscription { .. } | ExprKind::ValueTypeAscription { .. } => { diff --git a/src/librustc_mir_build/build/expr/category.rs b/src/librustc_mir_build/build/expr/category.rs index f8cae205453..59d3003c9f4 100644 --- a/src/librustc_mir_build/build/expr/category.rs +++ b/src/librustc_mir_build/build/expr/category.rs @@ -51,7 +51,8 @@ impl Category { | ExprKind::Borrow { .. } | ExprKind::AddressOf { .. } | ExprKind::Yield { .. } - | ExprKind::Call { .. } => Some(Category::Rvalue(RvalueFunc::Into)), + | ExprKind::Call { .. } + | ExprKind::InlineAsm { .. } => Some(Category::Rvalue(RvalueFunc::Into)), ExprKind::Array { .. } | ExprKind::Tuple { .. } diff --git a/src/librustc_mir_build/build/expr/into.rs b/src/librustc_mir_build/build/expr/into.rs index 8734d39a6d0..44c79a49f45 100644 --- a/src/librustc_mir_build/build/expr/into.rs +++ b/src/librustc_mir_build/build/expr/into.rs @@ -8,6 +8,7 @@ use rustc_hir as hir; use rustc_middle::mir::*; use rustc_middle::ty::{self, CanonicalUserTypeAnnotation}; use rustc_span::symbol::sym; +use rustc_target::asm::InlineAsmOptions; use rustc_target::spec::abi::Abi; @@ -53,7 +54,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::NeverToAny { source } => { let source = this.hir.mirror(source); let is_call = match source.kind { - ExprKind::Call { .. } => true, + ExprKind::Call { .. } | ExprKind::InlineAsm { .. } => true, _ => false, }; @@ -309,6 +310,73 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); block.unit() } + ExprKind::InlineAsm { template, operands, options } => { + use crate::hair; + use rustc_middle::mir; + let operands = operands + .into_iter() + .map(|op| match op { + hair::InlineAsmOperand::In { reg, expr } => mir::InlineAsmOperand::In { + reg, + value: unpack!(block = this.as_local_operand(block, expr)), + }, + hair::InlineAsmOperand::Out { reg, late, expr } => { + mir::InlineAsmOperand::Out { + reg, + late, + place: expr.map(|expr| unpack!(block = this.as_place(block, expr))), + } + } + hair::InlineAsmOperand::InOut { reg, late, expr } => { + let place = unpack!(block = this.as_place(block, expr)); + mir::InlineAsmOperand::InOut { + reg, + late, + // This works because asm operands must be Copy + in_value: Operand::Copy(place), + out_place: Some(place), + } + } + hair::InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => { + mir::InlineAsmOperand::InOut { + reg, + late, + in_value: unpack!(block = this.as_local_operand(block, in_expr)), + out_place: out_expr.map(|out_expr| { + unpack!(block = this.as_place(block, out_expr)) + }), + } + } + hair::InlineAsmOperand::Const { expr } => mir::InlineAsmOperand::Const { + value: unpack!(block = this.as_local_operand(block, expr)), + }, + hair::InlineAsmOperand::SymFn { expr } => { + mir::InlineAsmOperand::SymFn { value: box this.as_constant(expr) } + } + hair::InlineAsmOperand::SymStatic { expr } => { + mir::InlineAsmOperand::SymStatic { value: box this.as_constant(expr) } + } + }) + .collect(); + + let destination = this.cfg.start_new_block(); + + this.cfg.terminate( + block, + source_info, + TerminatorKind::InlineAsm { + template, + operands, + options, + destination: if options.contains(InlineAsmOptions::NORETURN) { + None + } else { + Some(destination) + }, + }, + ); + destination.unit() + } // These cases don't actually need a destination ExprKind::Assign { .. } diff --git a/src/librustc_mir_build/build/scope.rs b/src/librustc_mir_build/build/scope.rs index 19b983018c9..868fb69abe8 100644 --- a/src/librustc_mir_build/build/scope.rs +++ b/src/librustc_mir_build/build/scope.rs @@ -1388,7 +1388,8 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind { | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } | TerminatorKind::GeneratorDrop - | TerminatorKind::FalseEdges { .. } => { + | TerminatorKind::FalseEdges { .. } + | TerminatorKind::InlineAsm { .. } => { span_bug!(term.source_info.span, "cannot unwind from {:?}", term.kind) } } diff --git a/src/librustc_mir_build/hair/cx/expr.rs b/src/librustc_mir_build/hair/cx/expr.rs index 8d572465d62..99b59d16029 100644 --- a/src/librustc_mir_build/hair/cx/expr.rs +++ b/src/librustc_mir_build/hair/cx/expr.rs @@ -400,6 +400,121 @@ fn make_mirror_unadjusted<'a, 'tcx>( convert_path_expr(cx, expr, res) } + hir::ExprKind::InlineAsm(ref asm) => ExprKind::InlineAsm { + template: asm.template, + operands: asm + .operands + .iter() + .map(|op| { + match *op { + hir::InlineAsmOperand::In { reg, ref expr } => { + InlineAsmOperand::In { reg, expr: expr.to_ref() } + } + hir::InlineAsmOperand::Out { reg, late, ref expr } => { + InlineAsmOperand::Out { + reg, + late, + expr: expr.as_ref().map(|expr| expr.to_ref()), + } + } + hir::InlineAsmOperand::InOut { reg, late, ref expr } => { + InlineAsmOperand::InOut { reg, late, expr: expr.to_ref() } + } + hir::InlineAsmOperand::SplitInOut { + reg, + late, + ref in_expr, + ref out_expr, + } => InlineAsmOperand::SplitInOut { + reg, + late, + in_expr: in_expr.to_ref(), + out_expr: out_expr.as_ref().map(|expr| expr.to_ref()), + }, + hir::InlineAsmOperand::Const { ref expr } => { + InlineAsmOperand::Const { expr: expr.to_ref() } + } + hir::InlineAsmOperand::Sym { ref expr } => { + let qpath = match expr.kind { + hir::ExprKind::Path(ref qpath) => qpath, + _ => span_bug!( + expr.span, + "asm `sym` operand should be a path, found {:?}", + expr.kind + ), + }; + let temp_lifetime = + cx.region_scope_tree.temporary_scope(expr.hir_id.local_id); + let res = cx.tables().qpath_res(qpath, expr.hir_id); + let ty; + match res { + Res::Def(DefKind::Fn, _) | Res::Def(DefKind::AssocFn, _) => { + ty = cx.tables().node_type(expr.hir_id); + let user_ty = user_substs_applied_to_res(cx, expr.hir_id, res); + InlineAsmOperand::SymFn { + expr: Expr { + ty, + temp_lifetime, + span: expr.span, + kind: ExprKind::Literal { + literal: ty::Const::zero_sized(cx.tcx, ty), + user_ty, + }, + } + .to_ref(), + } + } + + Res::Def(DefKind::Static, id) => { + ty = cx.tcx.static_ptr_ty(id); + let ptr = cx.tcx.create_static_alloc(id); + InlineAsmOperand::SymStatic { + expr: Expr { + ty, + temp_lifetime, + span: expr.span, + kind: ExprKind::StaticRef { + literal: ty::Const::from_scalar( + cx.tcx, + Scalar::Ptr(ptr.into()), + ty, + ), + def_id: id, + }, + } + .to_ref(), + } + } + + _ => { + cx.tcx.sess.span_err( + expr.span, + "asm `sym` operand must point to a fn or static", + ); + + // Not a real fn, but we're not reaching codegen anyways... + ty = cx.tcx.types.err; + InlineAsmOperand::SymFn { + expr: Expr { + ty, + temp_lifetime, + span: expr.span, + kind: ExprKind::Literal { + literal: ty::Const::zero_sized(cx.tcx, ty), + user_ty: None, + }, + } + .to_ref(), + } + } + } + } + } + }) + .collect(), + options: asm.options, + }, + hir::ExprKind::LlvmInlineAsm(ref asm) => ExprKind::LlvmInlineAsm { asm: &asm.inner, outputs: asm.outputs_exprs.to_ref(), diff --git a/src/librustc_mir_build/hair/mod.rs b/src/librustc_mir_build/hair/mod.rs index 601e4412512..0c49e698e89 100644 --- a/src/librustc_mir_build/hair/mod.rs +++ b/src/librustc_mir_build/hair/mod.rs @@ -15,6 +15,7 @@ use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::{AdtDef, Const, Ty, UpvarSubsts, UserType}; use rustc_span::Span; use rustc_target::abi::VariantIdx; +use rustc_target::asm::{InlineAsmOptions, InlineAsmRegOrRegClass, InlineAsmTemplatePiece}; crate mod constant; crate mod cx; @@ -277,6 +278,11 @@ crate enum ExprKind<'tcx> { literal: &'tcx Const<'tcx>, def_id: DefId, }, + InlineAsm { + template: &'tcx [InlineAsmTemplatePiece], + operands: Vec>, + options: InlineAsmOptions, + }, LlvmInlineAsm { asm: &'tcx hir::LlvmInlineAsmInner, outputs: Vec>, @@ -335,6 +341,39 @@ impl<'tcx> ExprRef<'tcx> { } } +#[derive(Clone, Debug)] +crate enum InlineAsmOperand<'tcx> { + In { + reg: InlineAsmRegOrRegClass, + expr: ExprRef<'tcx>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: Option>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + expr: ExprRef<'tcx>, + }, + SplitInOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_expr: ExprRef<'tcx>, + out_expr: Option>, + }, + Const { + expr: ExprRef<'tcx>, + }, + SymFn { + expr: ExprRef<'tcx>, + }, + SymStatic { + expr: ExprRef<'tcx>, + }, +} + /////////////////////////////////////////////////////////////////////////// // The Mirror trait diff --git a/src/librustc_mir_build/lints.rs b/src/librustc_mir_build/lints.rs index 69bfad6b139..161023f1613 100644 --- a/src/librustc_mir_build/lints.rs +++ b/src/librustc_mir_build/lints.rs @@ -114,6 +114,10 @@ impl<'mir, 'tcx> TriColorVisitor<&'mir Body<'tcx>> for Search<'mir, 'tcx> { | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive), + // FIXME(Amanieu): I am not 100% sure about this, but it triggers + // a spurious warning otherwise. + TerminatorKind::InlineAsm { .. } => ControlFlow::Break(NonRecursive), + // These do not. TerminatorKind::Assert { .. } | TerminatorKind::Call { .. } From 342a64caef773f142c347441877d0be064d5cdc7 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 17 Feb 2020 22:20:59 +0000 Subject: [PATCH 09/36] Check that asm const operands are actually constants --- src/librustc_mir/transform/promote_consts.rs | 81 ++++++++++++++++---- 1 file changed, 65 insertions(+), 16 deletions(-) diff --git a/src/librustc_mir/transform/promote_consts.rs b/src/librustc_mir/transform/promote_consts.rs index 6dade3c8dca..13a8b9a1000 100644 --- a/src/librustc_mir/transform/promote_consts.rs +++ b/src/librustc_mir/transform/promote_consts.rs @@ -113,6 +113,9 @@ pub enum Candidate { /// the attribute currently provides the semantic requirement that arguments /// must be constant. Argument { bb: BasicBlock, index: usize }, + + /// `const` operand in asm!. + InlineAsm { bb: BasicBlock, index: usize }, } impl Candidate { @@ -120,7 +123,7 @@ impl Candidate { fn forces_explicit_promotion(&self) -> bool { match self { Candidate::Ref(_) | Candidate::Repeat(_) => false, - Candidate::Argument { .. } => true, + Candidate::Argument { .. } | Candidate::InlineAsm { .. } => true, } } } @@ -216,25 +219,39 @@ impl<'tcx> Visitor<'tcx> for Collector<'_, 'tcx> { fn visit_terminator_kind(&mut self, kind: &TerminatorKind<'tcx>, location: Location) { self.super_terminator_kind(kind, location); - if let TerminatorKind::Call { ref func, .. } = *kind { - if let ty::FnDef(def_id, _) = func.ty(self.ccx.body, self.ccx.tcx).kind { - let fn_sig = self.ccx.tcx.fn_sig(def_id); - if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = fn_sig.abi() { - let name = self.ccx.tcx.item_name(def_id); - // FIXME(eddyb) use `#[rustc_args_required_const(2)]` for shuffles. - if name.as_str().starts_with("simd_shuffle") { - self.candidates.push(Candidate::Argument { bb: location.block, index: 2 }); + match *kind { + TerminatorKind::Call { ref func, .. } => { + if let ty::FnDef(def_id, _) = func.ty(self.ccx.body, self.ccx.tcx).kind { + let fn_sig = self.ccx.tcx.fn_sig(def_id); + if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = fn_sig.abi() { + let name = self.ccx.tcx.item_name(def_id); + // FIXME(eddyb) use `#[rustc_args_required_const(2)]` for shuffles. + if name.as_str().starts_with("simd_shuffle") { + self.candidates + .push(Candidate::Argument { bb: location.block, index: 2 }); - return; // Don't double count `simd_shuffle` candidates + return; // Don't double count `simd_shuffle` candidates + } } - } - if let Some(constant_args) = args_required_const(self.ccx.tcx, def_id) { - for index in constant_args { - self.candidates.push(Candidate::Argument { bb: location.block, index }); + if let Some(constant_args) = args_required_const(self.ccx.tcx, def_id) { + for index in constant_args { + self.candidates.push(Candidate::Argument { bb: location.block, index }); + } } } } + TerminatorKind::InlineAsm { ref operands, .. } => { + for (index, op) in operands.iter().enumerate() { + match op { + InlineAsmOperand::Const { .. } => { + self.candidates.push(Candidate::InlineAsm { bb: location.block, index }) + } + _ => {} + } + } + } + _ => {} } } @@ -402,6 +419,18 @@ impl<'tcx> Validator<'_, 'tcx> { _ => bug!(), } } + Candidate::InlineAsm { bb, index } => { + assert!(self.explicit); + + let terminator = self.body[bb].terminator(); + match &terminator.kind { + TerminatorKind::InlineAsm { operands, .. } => match &operands[index] { + InlineAsmOperand::Const { value } => self.validate_operand(value), + _ => bug!(), + }, + _ => bug!(), + } + } } } @@ -747,7 +776,9 @@ pub fn validate_candidates( } match candidate { - Candidate::Argument { bb, index } if !is_promotable => { + Candidate::Argument { bb, index } | Candidate::InlineAsm { bb, index } + if !is_promotable => + { let span = ccx.body[bb].terminator().source_info.span; let msg = format!("argument {} is required to be a constant", index + 1); ccx.tcx.sess.span_err(span, &msg); @@ -1024,6 +1055,24 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> { _ => bug!(), } } + Candidate::InlineAsm { bb, index } => { + let terminator = blocks[bb].terminator_mut(); + match terminator.kind { + TerminatorKind::InlineAsm { ref mut operands, .. } => { + match &mut operands[index] { + InlineAsmOperand::Const { ref mut value } => { + let ty = value.ty(local_decls, self.tcx); + let span = terminator.source_info.span; + + Rvalue::Use(mem::replace(value, promoted_operand(ty, span))) + } + _ => bug!(), + } + } + + _ => bug!(), + } + } } }; @@ -1080,7 +1129,7 @@ pub fn promote_candidates<'tcx>( } } } - Candidate::Argument { .. } => {} + Candidate::Argument { .. } | Candidate::InlineAsm { .. } => {} } // Declare return place local so that `mir::Body::new` doesn't complain. From abed45ff9fa3e68f2a32ca12e012f95b9153f4df Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 17 Feb 2020 21:36:01 +0000 Subject: [PATCH 10/36] Implement asm! codegen --- src/librustc_codegen_llvm/asm.rs | 478 +++++++++++++++++++++++- src/librustc_codegen_llvm/llvm/ffi.rs | 2 + src/librustc_codegen_ssa/mir/analyze.rs | 3 +- src/librustc_codegen_ssa/mir/block.rs | 93 +++++ src/librustc_codegen_ssa/traits/asm.rs | 41 ++ src/librustc_codegen_ssa/traits/mod.rs | 2 +- src/rustllvm/RustWrapper.cpp | 4 + src/rustllvm/rustllvm.h | 2 + 8 files changed, 608 insertions(+), 17 deletions(-) diff --git a/src/librustc_codegen_llvm/asm.rs b/src/librustc_codegen_llvm/asm.rs index 30bf3ce7528..21322511c99 100644 --- a/src/librustc_codegen_llvm/asm.rs +++ b/src/librustc_codegen_llvm/asm.rs @@ -1,14 +1,21 @@ use crate::builder::Builder; use crate::context::CodegenCx; use crate::llvm; +use crate::type_::Type; use crate::type_of::LayoutLlvmExt; use crate::value::Value; +use rustc_ast::ast::LlvmAsmDialect; use rustc_codegen_ssa::mir::operand::OperandValue; use rustc_codegen_ssa::mir::place::PlaceRef; use rustc_codegen_ssa::traits::*; +use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; +use rustc_middle::span_bug; +use rustc_middle::ty::layout::TyAndLayout; use rustc_span::Span; +use rustc_target::abi::*; +use rustc_target::asm::*; use libc::{c_char, c_uint}; use log::debug; @@ -40,7 +47,7 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> { indirect_outputs.push(operand.immediate()); } } else { - output_types.push(place.layout.llvm_type(self.cx())); + output_types.push(place.layout.llvm_type(self.cx)); } } if !indirect_outputs.is_empty() { @@ -89,6 +96,7 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> { ia.volatile, ia.alignstack, ia.dialect, + span, ); if r.is_none() { return false; @@ -102,22 +110,210 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> { OperandValue::Immediate(v).store(self, place); } - // Store mark in a metadata node so we can map LLVM errors - // back to source locations. See #17552. - unsafe { - let key = "srcloc"; - let kind = llvm::LLVMGetMDKindIDInContext( - self.llcx, - key.as_ptr() as *const c_char, - key.len() as c_uint, - ); + true + } - let val: &'ll Value = self.const_i32(span.ctxt().outer_expn().as_u32() as i32); + fn codegen_inline_asm( + &mut self, + template: &[InlineAsmTemplatePiece], + operands: &[InlineAsmOperandRef<'tcx, Self>], + options: InlineAsmOptions, + span: Span, + ) { + let asm_arch = self.tcx.sess.asm_arch.unwrap(); - llvm::LLVMSetMetadata(r, kind, llvm::LLVMMDNodeInContext(self.llcx, &val, 1)); + // Collect the types of output operands + let mut constraints = vec![]; + let mut output_types = vec![]; + let mut op_idx = FxHashMap::default(); + for (idx, op) in operands.iter().enumerate() { + match *op { + InlineAsmOperandRef::Out { reg, late, place } => { + let ty = if let Some(place) = place { + llvm_fixup_output_type(self.cx, reg.reg_class(), &place.layout) + } else { + // If the output is discarded, we don't really care what + // type is used. We're just using this to tell LLVM to + // reserve the register. + dummy_output_type(self.cx, reg.reg_class()) + }; + output_types.push(ty); + op_idx.insert(idx, constraints.len()); + let prefix = if late { "=" } else { "=&" }; + constraints.push(format!("{}{}", prefix, reg_to_llvm(reg))); + } + InlineAsmOperandRef::InOut { reg, late, in_value, out_place } => { + let ty = if let Some(ref out_place) = out_place { + llvm_fixup_output_type(self.cx, reg.reg_class(), &out_place.layout) + } else { + // LLVM required tied operands to have the same type, + // so we just use the type of the input. + llvm_fixup_output_type(self.cx, reg.reg_class(), &in_value.layout) + }; + output_types.push(ty); + op_idx.insert(idx, constraints.len()); + let prefix = if late { "=" } else { "=&" }; + constraints.push(format!("{}{}", prefix, reg_to_llvm(reg))); + } + _ => {} + } } - true + // Collect input operands + let mut inputs = vec![]; + for (idx, op) in operands.iter().enumerate() { + match *op { + InlineAsmOperandRef::In { reg, value } => { + let value = + llvm_fixup_input(self, value.immediate(), reg.reg_class(), &value.layout); + inputs.push(value); + op_idx.insert(idx, constraints.len()); + constraints.push(reg_to_llvm(reg)); + } + InlineAsmOperandRef::InOut { reg, late: _, in_value, out_place: _ } => { + let value = llvm_fixup_input( + self, + in_value.immediate(), + reg.reg_class(), + &in_value.layout, + ); + inputs.push(value); + constraints.push(format!("{}", op_idx[&idx])); + } + InlineAsmOperandRef::SymFn { instance } => { + inputs.push(self.cx.get_fn(instance)); + op_idx.insert(idx, constraints.len()); + constraints.push("s".to_string()); + } + InlineAsmOperandRef::SymStatic { def_id } => { + inputs.push(self.cx.get_static(def_id)); + op_idx.insert(idx, constraints.len()); + constraints.push("s".to_string()); + } + _ => {} + } + } + + // Build the template string + let mut template_str = String::new(); + for piece in template { + match *piece { + InlineAsmTemplatePiece::String(ref s) => { + if s.contains('$') { + for c in s.chars() { + if c == '$' { + template_str.push_str("$$"); + } else { + template_str.push(c); + } + } + } else { + template_str.push_str(s) + } + } + InlineAsmTemplatePiece::Placeholder { operand_idx, modifier, span: _ } => { + match operands[operand_idx] { + InlineAsmOperandRef::In { reg, .. } + | InlineAsmOperandRef::Out { reg, .. } + | InlineAsmOperandRef::InOut { reg, .. } => { + let modifier = modifier_to_llvm(asm_arch, reg.reg_class(), modifier); + if let Some(modifier) = modifier { + template_str.push_str(&format!( + "${{{}:{}}}", + op_idx[&operand_idx], modifier + )); + } else { + template_str.push_str(&format!("${{{}}}", op_idx[&operand_idx])); + } + } + InlineAsmOperandRef::Const { ref string } => { + // Const operands get injected directly into the template + template_str.push_str(string); + } + InlineAsmOperandRef::SymFn { .. } + | InlineAsmOperandRef::SymStatic { .. } => { + // Only emit the raw symbol name + template_str.push_str(&format!("${{{}:c}}", op_idx[&operand_idx])); + } + } + } + } + } + + if !options.contains(InlineAsmOptions::PRESERVES_FLAGS) { + match asm_arch { + InlineAsmArch::AArch64 | InlineAsmArch::Arm => { + constraints.push("~{cc}".to_string()); + } + InlineAsmArch::X86 | InlineAsmArch::X86_64 => { + constraints.extend_from_slice(&[ + "~{dirflag}".to_string(), + "~{fpsr}".to_string(), + "~{flags}".to_string(), + ]); + } + InlineAsmArch::RiscV32 | InlineAsmArch::RiscV64 => {} + } + } + if !options.contains(InlineAsmOptions::NOMEM) { + // This is actually ignored by LLVM, but it's probably best to keep + // it just in case. LLVM instead uses the ReadOnly/ReadNone + // attributes on the call instruction to optimize. + constraints.push("~{memory}".to_string()); + } + let volatile = !options.contains(InlineAsmOptions::PURE); + let alignstack = !options.contains(InlineAsmOptions::NOSTACK); + let output_type = match &output_types[..] { + [] => self.type_void(), + [ty] => ty, + tys => self.type_struct(&tys, false), + }; + let dialect = match asm_arch { + InlineAsmArch::X86 | InlineAsmArch::X86_64 => LlvmAsmDialect::Intel, + _ => LlvmAsmDialect::Att, + }; + let result = inline_asm_call( + self, + &template_str, + &constraints.join(","), + &inputs, + output_type, + volatile, + alignstack, + dialect, + span, + ) + .unwrap_or_else(|| span_bug!(span, "LLVM asm constraint validation failed")); + + if options.contains(InlineAsmOptions::PURE) { + if options.contains(InlineAsmOptions::NOMEM) { + llvm::Attribute::ReadNone.apply_callsite(llvm::AttributePlace::Function, result); + } else if options.contains(InlineAsmOptions::READONLY) { + llvm::Attribute::ReadOnly.apply_callsite(llvm::AttributePlace::Function, result); + } + } else { + if options.contains(InlineAsmOptions::NOMEM) { + llvm::Attribute::InaccessibleMemOnly + .apply_callsite(llvm::AttributePlace::Function, result); + } else { + // LLVM doesn't have an attribute to represent ReadOnly + SideEffect + } + } + + // Write results to outputs + for (idx, op) in operands.iter().enumerate() { + if let InlineAsmOperandRef::Out { reg, place: Some(place), .. } + | InlineAsmOperandRef::InOut { reg, out_place: Some(place), .. } = *op + { + let value = if output_types.len() == 1 { + result + } else { + self.extract_value(result, op_idx[&idx] as u64) + }; + let value = llvm_fixup_output(self, value, reg.reg_class(), &place.layout); + OperandValue::Immediate(value).store(self, place); + } + } } } @@ -138,7 +334,8 @@ fn inline_asm_call( output: &'ll llvm::Type, volatile: bool, alignstack: bool, - dia: ::rustc_ast::ast::LlvmAsmDialect, + dia: LlvmAsmDialect, + span: Span, ) -> Option<&'ll Value> { let volatile = if volatile { llvm::True } else { llvm::False }; let alignstack = if alignstack { llvm::True } else { llvm::False }; @@ -168,10 +365,261 @@ fn inline_asm_call( alignstack, llvm::AsmDialect::from_generic(dia), ); - Some(bx.call(v, inputs, None)) + let call = bx.call(v, inputs, None); + + // Store mark in a metadata node so we can map LLVM errors + // back to source locations. See #17552. + let key = "srcloc"; + let kind = llvm::LLVMGetMDKindIDInContext( + bx.llcx, + key.as_ptr() as *const c_char, + key.len() as c_uint, + ); + + let val: &'ll Value = bx.const_i32(span.ctxt().outer_expn().as_u32() as i32); + llvm::LLVMSetMetadata(call, kind, llvm::LLVMMDNodeInContext(bx.llcx, &val, 1)); + + Some(call) } else { // LLVM has detected an issue with our constraints, bail out None } } } + +/// Converts a register class to an LLVM constraint code. +fn reg_to_llvm(reg: InlineAsmRegOrRegClass) -> String { + match reg { + InlineAsmRegOrRegClass::Reg(reg) => format!("{{{}}}", reg.name()), + InlineAsmRegOrRegClass::RegClass(reg) => match reg { + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::reg) => "r", + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg) => "w", + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16) => "x", + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg) => "r", + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg_thumb) => "l", + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low16) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low8) => "t", + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg_low16) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low8) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low4) => "x", + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg) => "w", + InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg) => "r", + InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => "f", + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg) => "r", + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd) => "Q", + InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg) + | InlineAsmRegClass::X86(X86InlineAsmRegClass::ymm_reg) => "x", + InlineAsmRegClass::X86(X86InlineAsmRegClass::zmm_reg) => "v", + InlineAsmRegClass::X86(X86InlineAsmRegClass::kreg) => "^Yk", + } + .to_string(), + } +} + +/// Converts a modifier into LLVM's equivalent modifier. +fn modifier_to_llvm( + arch: InlineAsmArch, + reg: InlineAsmRegClass, + modifier: Option, +) -> Option { + match reg { + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::reg) => modifier, + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg) + | InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16) => { + if modifier == Some('v') { None } else { modifier } + } + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::reg_thumb) => None, + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::sreg_low16) => None, + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low16) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::dreg_low8) => Some('P'), + InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low8) + | InlineAsmRegClass::Arm(ArmInlineAsmRegClass::qreg_low4) => { + if modifier.is_none() { + Some('q') + } else { + modifier + } + } + InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg) + | InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => None, + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg) + | InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd) => match modifier { + None if arch == InlineAsmArch::X86_64 => Some('q'), + None => Some('k'), + Some('l') => Some('b'), + Some('h') => Some('h'), + Some('x') => Some('w'), + Some('e') => Some('k'), + Some('r') => Some('q'), + _ => unreachable!(), + }, + 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, + } +} + +/// 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(cx: &CodegenCx<'ll, 'tcx>, reg: InlineAsmRegClass) -> &'ll Type { + match reg { + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::reg) => cx.type_i32(), + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg) + | InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16) => { + cx.type_vector(cx.type_i64(), 2) + } + 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) => { + cx.type_vector(cx.type_i64(), 2) + } + InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::reg) => cx.type_i32(), + InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => cx.type_f32(), + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg) + | InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd) => cx.type_i32(), + InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg) + | InlineAsmRegClass::X86(X86InlineAsmRegClass::ymm_reg) + | InlineAsmRegClass::X86(X86InlineAsmRegClass::zmm_reg) => cx.type_f32(), + InlineAsmRegClass::X86(X86InlineAsmRegClass::kreg) => cx.type_i16(), + } +} + +/// Helper function to get the LLVM type for a Scalar. Pointers are returned as +/// the equivalent integer type. +fn llvm_asm_scalar_type(cx: &CodegenCx<'ll, 'tcx>, scalar: &Scalar) -> &'ll Type { + match scalar.value { + Primitive::Int(Integer::I8, _) => cx.type_i8(), + Primitive::Int(Integer::I16, _) => cx.type_i16(), + Primitive::Int(Integer::I32, _) => cx.type_i32(), + Primitive::Int(Integer::I64, _) => cx.type_i64(), + Primitive::F32 => cx.type_f32(), + Primitive::F64 => cx.type_f64(), + Primitive::Pointer => cx.type_isize(), + _ => unreachable!(), + } +} + +/// Fix up an input value to work around LLVM bugs. +fn llvm_fixup_input( + bx: &mut Builder<'a, 'll, 'tcx>, + mut value: &'ll Value, + reg: InlineAsmRegClass, + layout: &TyAndLayout<'tcx>, +) -> &'ll Value { + match (reg, &layout.abi) { + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg), Abi::Scalar(s)) => { + if let Primitive::Int(Integer::I8, _) = s.value { + let vec_ty = bx.cx.type_vector(bx.cx.type_i8(), 8); + bx.insert_element(bx.const_undef(vec_ty), value, bx.const_i32(0)) + } else { + value + } + } + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), Abi::Scalar(s)) => { + let elem_ty = llvm_asm_scalar_type(bx.cx, s); + let count = 16 / layout.size.bytes(); + let vec_ty = bx.cx.type_vector(elem_ty, count); + if let Primitive::Pointer = s.value { + value = bx.ptrtoint(value, bx.cx.type_isize()); + } + bx.insert_element(bx.const_undef(vec_ty), value, bx.const_i32(0)) + } + ( + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), + Abi::Vector { element, count }, + ) if layout.size.bytes() == 8 => { + let elem_ty = llvm_asm_scalar_type(bx.cx, element); + let vec_ty = bx.cx.type_vector(elem_ty, *count); + let indices: Vec<_> = (0..count * 2).map(|x| bx.const_i32(x as i32)).collect(); + bx.shuffle_vector(value, bx.const_undef(vec_ty), bx.const_vector(&indices)) + } + _ => value, + } +} + +/// Fix up an output value to work around LLVM bugs. +fn llvm_fixup_output( + bx: &mut Builder<'a, 'll, 'tcx>, + mut value: &'ll Value, + reg: InlineAsmRegClass, + layout: &TyAndLayout<'tcx>, +) -> &'ll Value { + match (reg, &layout.abi) { + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg), Abi::Scalar(s)) => { + if let Primitive::Int(Integer::I8, _) = s.value { + bx.extract_element(value, bx.const_i32(0)) + } else { + value + } + } + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), Abi::Scalar(s)) => { + value = bx.extract_element(value, bx.const_i32(0)); + if let Primitive::Pointer = s.value { + value = bx.inttoptr(value, layout.llvm_type(bx.cx)); + } + value + } + ( + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), + Abi::Vector { element, count }, + ) if layout.size.bytes() == 8 => { + let elem_ty = llvm_asm_scalar_type(bx.cx, element); + let vec_ty = bx.cx.type_vector(elem_ty, *count * 2); + let indices: Vec<_> = (0..*count).map(|x| bx.const_i32(x as i32)).collect(); + bx.shuffle_vector(value, bx.const_undef(vec_ty), bx.const_vector(&indices)) + } + _ => value, + } +} + +/// Output type to use for llvm_fixup_output. +fn llvm_fixup_output_type( + cx: &CodegenCx<'ll, 'tcx>, + reg: InlineAsmRegClass, + layout: &TyAndLayout<'tcx>, +) -> &'ll Type { + match (reg, &layout.abi) { + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg), Abi::Scalar(s)) => { + if let Primitive::Int(Integer::I8, _) = s.value { + cx.type_vector(cx.type_i8(), 8) + } else { + layout.llvm_type(cx) + } + } + (InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), Abi::Scalar(s)) => { + let elem_ty = llvm_asm_scalar_type(cx, s); + let count = 16 / layout.size.bytes(); + cx.type_vector(elem_ty, count) + } + ( + InlineAsmRegClass::AArch64(AArch64InlineAsmRegClass::vreg_low16), + Abi::Vector { element, count }, + ) if layout.size.bytes() == 8 => { + let elem_ty = llvm_asm_scalar_type(cx, element); + cx.type_vector(elem_ty, count * 2) + } + _ => layout.llvm_type(cx), + } +} diff --git a/src/librustc_codegen_llvm/llvm/ffi.rs b/src/librustc_codegen_llvm/llvm/ffi.rs index 21dfc113351..9cb0f0e0c2e 100644 --- a/src/librustc_codegen_llvm/llvm/ffi.rs +++ b/src/librustc_codegen_llvm/llvm/ffi.rs @@ -124,6 +124,8 @@ pub enum Attribute { NonLazyBind = 23, OptimizeNone = 24, ReturnsTwice = 25, + ReadNone = 26, + InaccessibleMemOnly = 27, } /// LLVMIntPredicate diff --git a/src/librustc_codegen_ssa/mir/analyze.rs b/src/librustc_codegen_ssa/mir/analyze.rs index 9fcaf2818e8..5e3a37e20bd 100644 --- a/src/librustc_codegen_ssa/mir/analyze.rs +++ b/src/librustc_codegen_ssa/mir/analyze.rs @@ -358,7 +358,8 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec { /* nothing to do */ } + | TerminatorKind::FalseUnwind { .. } + | TerminatorKind::InlineAsm { .. } => { /* nothing to do */ } TerminatorKind::Call { cleanup: unwind, .. } | TerminatorKind::Assert { cleanup: unwind, .. } | TerminatorKind::DropAndReplace { unwind, .. } diff --git a/src/librustc_codegen_ssa/mir/block.rs b/src/librustc_codegen_ssa/mir/block.rs index dfb1656a6e0..f8c5caa440c 100644 --- a/src/librustc_codegen_ssa/mir/block.rs +++ b/src/librustc_codegen_ssa/mir/block.rs @@ -9,6 +9,7 @@ use crate::meth; use crate::traits::*; use crate::MemFlags; +use rustc_ast::ast; use rustc_hir::lang_items; use rustc_index::vec::Idx; use rustc_middle::mir; @@ -914,6 +915,98 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { mir::TerminatorKind::FalseEdges { .. } | mir::TerminatorKind::FalseUnwind { .. } => { bug!("borrowck false edges in codegen") } + + mir::TerminatorKind::InlineAsm { template, ref operands, options, ref destination } => { + let span = terminator.source_info.span; + + let operands: Vec<_> = operands + .iter() + .map(|op| match *op { + mir::InlineAsmOperand::In { reg, ref value } => { + let value = self.codegen_operand(&mut bx, value); + InlineAsmOperandRef::In { reg, value } + } + mir::InlineAsmOperand::Out { reg, late, ref place } => { + let place = + place.map(|place| self.codegen_place(&mut bx, place.as_ref())); + InlineAsmOperandRef::Out { reg, late, place } + } + mir::InlineAsmOperand::InOut { reg, late, ref in_value, ref out_place } => { + let in_value = self.codegen_operand(&mut bx, in_value); + let out_place = out_place + .map(|out_place| self.codegen_place(&mut bx, out_place.as_ref())); + InlineAsmOperandRef::InOut { reg, late, in_value, out_place } + } + mir::InlineAsmOperand::Const { ref value } => { + if let mir::Operand::Constant(constant) = value { + let const_value = + self.eval_mir_constant(constant).unwrap_or_else(|_| { + span_bug!(span, "asm const cannot be resolved") + }); + let ty = constant.literal.ty; + let value = const_value + .try_to_bits_for_ty(bx.tcx(), ty::ParamEnv::reveal_all(), ty) + .unwrap_or_else(|| { + span_bug!(span, "asm const has non-scalar value") + }); + let string = match ty.kind { + ty::Uint(_) => value.to_string(), + ty::Int(int_ty) => { + match int_ty.normalize(bx.tcx().sess.target.ptr_width) { + ast::IntTy::I8 => (value as i8).to_string(), + ast::IntTy::I16 => (value as i16).to_string(), + ast::IntTy::I32 => (value as i32).to_string(), + ast::IntTy::I64 => (value as i64).to_string(), + ast::IntTy::I128 => (value as i128).to_string(), + ast::IntTy::Isize => unreachable!(), + } + } + ty::Float(ast::FloatTy::F32) => { + f32::from_bits(value as u32).to_string() + } + ty::Float(ast::FloatTy::F64) => { + f64::from_bits(value as u64).to_string() + } + _ => span_bug!(span, "asm const has bad type {}", ty), + }; + InlineAsmOperandRef::Const { string } + } else { + span_bug!(span, "asm const is not a constant"); + } + } + mir::InlineAsmOperand::SymFn { ref value } => { + if let ty::FnDef(def_id, substs) = value.literal.ty.kind { + let instance = ty::Instance::resolve( + bx.tcx(), + ty::ParamEnv::reveal_all(), + def_id, + substs, + ) + .unwrap() + .unwrap(); + InlineAsmOperandRef::SymFn { instance } + } else { + span_bug!(span, "invalid type for asm sym (fn)"); + } + } + mir::InlineAsmOperand::SymStatic { ref value } => { + if let Some(def_id) = value.check_static_ptr(bx.tcx()) { + InlineAsmOperandRef::SymStatic { def_id } + } else { + span_bug!(span, "invalid type for asm sym (static)"); + } + } + }) + .collect(); + + bx.codegen_inline_asm(template, &operands, options, span); + + if let Some(target) = destination { + helper.funclet_br(self, &mut bx, *target); + } else { + bx.unreachable(); + } + } } } diff --git a/src/librustc_codegen_ssa/traits/asm.rs b/src/librustc_codegen_ssa/traits/asm.rs index 1cdfd3ae356..cb2b3545c4c 100644 --- a/src/librustc_codegen_ssa/traits/asm.rs +++ b/src/librustc_codegen_ssa/traits/asm.rs @@ -1,7 +1,39 @@ use super::BackendTypes; +use crate::mir::operand::OperandRef; use crate::mir::place::PlaceRef; +use rustc_hir::def_id::DefId; use rustc_hir::{GlobalAsm, LlvmInlineAsmInner}; +use rustc_middle::ty::Instance; use rustc_span::Span; +use rustc_target::asm::{InlineAsmOptions, InlineAsmRegOrRegClass, InlineAsmTemplatePiece}; + +#[derive(Debug)] +pub enum InlineAsmOperandRef<'tcx, B: BackendTypes + ?Sized> { + In { + reg: InlineAsmRegOrRegClass, + value: OperandRef<'tcx, B::Value>, + }, + Out { + reg: InlineAsmRegOrRegClass, + late: bool, + place: Option>, + }, + InOut { + reg: InlineAsmRegOrRegClass, + late: bool, + in_value: OperandRef<'tcx, B::Value>, + out_place: Option>, + }, + Const { + string: String, + }, + SymFn { + instance: Instance<'tcx>, + }, + SymStatic { + def_id: DefId, + }, +} pub trait AsmBuilderMethods<'tcx>: BackendTypes { /// Take an inline assembly expression and splat it out via LLVM @@ -12,6 +44,15 @@ pub trait AsmBuilderMethods<'tcx>: BackendTypes { inputs: Vec, span: Span, ) -> bool; + + /// Take an inline assembly expression and splat it out via LLVM + fn codegen_inline_asm( + &mut self, + template: &[InlineAsmTemplatePiece], + operands: &[InlineAsmOperandRef<'tcx, Self>], + options: InlineAsmOptions, + span: Span, + ); } pub trait AsmMethods { diff --git a/src/librustc_codegen_ssa/traits/mod.rs b/src/librustc_codegen_ssa/traits/mod.rs index f4c09a33285..6b782731d53 100644 --- a/src/librustc_codegen_ssa/traits/mod.rs +++ b/src/librustc_codegen_ssa/traits/mod.rs @@ -28,7 +28,7 @@ mod type_; mod write; pub use self::abi::AbiBuilderMethods; -pub use self::asm::{AsmBuilderMethods, AsmMethods}; +pub use self::asm::{AsmBuilderMethods, AsmMethods, InlineAsmOperandRef}; pub use self::backend::{Backend, BackendTypes, CodegenBackend, ExtraBackendMethods}; pub use self::builder::{BuilderMethods, OverflowOp}; pub use self::consts::ConstMethods; diff --git a/src/rustllvm/RustWrapper.cpp b/src/rustllvm/RustWrapper.cpp index 28efc8bf5dd..b988d06871b 100644 --- a/src/rustllvm/RustWrapper.cpp +++ b/src/rustllvm/RustWrapper.cpp @@ -203,6 +203,10 @@ static Attribute::AttrKind fromRust(LLVMRustAttribute Kind) { return Attribute::OptimizeNone; case ReturnsTwice: return Attribute::ReturnsTwice; + case ReadNone: + return Attribute::ReadNone; + case InaccessibleMemOnly: + return Attribute::InaccessibleMemOnly; } report_fatal_error("bad AttributeKind"); } diff --git a/src/rustllvm/rustllvm.h b/src/rustllvm/rustllvm.h index c3f0d174d4b..da48048113b 100644 --- a/src/rustllvm/rustllvm.h +++ b/src/rustllvm/rustllvm.h @@ -82,6 +82,8 @@ enum LLVMRustAttribute { NonLazyBind = 23, OptimizeNone = 24, ReturnsTwice = 25, + ReadNone = 26, + InaccessibleMemOnly = 27, }; typedef struct OpaqueRustString *RustStringRef; From 8ab0f2d3c5a85563b98c4896116e3d53154fff9c Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Thu, 20 Feb 2020 09:19:48 +0000 Subject: [PATCH 11/36] Add tests for asm! --- src/test/assembly/asm/aarch64-modifiers.rs | 144 ++++ src/test/assembly/asm/aarch64-types.rs | 380 ++++++++++ src/test/assembly/asm/arm-modifiers.rs | 149 ++++ src/test/assembly/asm/arm-types.rs | 413 +++++++++++ src/test/assembly/asm/riscv-modifiers.rs | 58 ++ src/test/assembly/asm/riscv-types.rs | 134 ++++ src/test/assembly/asm/x86-modifiers.rs | 210 ++++++ src/test/assembly/asm/x86-types.rs | 701 ++++++++++++++++++ src/test/codegen/asm-options.rs | 96 +++ src/test/pretty/asm.pp | 25 + src/test/pretty/asm.rs | 19 + .../{asm-clobbers.rs => llvm-asm-clobbers.rs} | 0 .../{asm-options.rs => llvm-asm-options.rs} | 0 src/test/ui/asm/bad-options.rs | 18 + src/test/ui/asm/bad-options.stderr | 32 + src/test/ui/asm/bad-reg.rs | 57 ++ src/test/ui/asm/bad-reg.stderr | 148 ++++ src/test/ui/asm/bad-template.rs | 26 + src/test/ui/asm/bad-template.stderr | 86 +++ src/test/ui/asm/noreturn.rs | 17 + src/test/ui/asm/parse-error.rs | 53 ++ src/test/ui/asm/parse-error.stderr | 146 ++++ src/test/ui/asm/rustfix-asm.fixed | 16 + src/test/ui/asm/rustfix-asm.rs | 16 + src/test/ui/asm/rustfix-asm.stderr | 18 + src/test/ui/asm/type-check-1.rs | 25 + src/test/ui/asm/type-check-1.stderr | 45 ++ src/test/ui/asm/type-check-2.rs | 104 +++ src/test/ui/asm/type-check-2.stderr | 133 ++++ src/test/ui/asm/type-check-3.rs | 68 ++ src/test/ui/asm/type-check-3.stderr | 123 +++ src/test/ui/feature-gates/feature-gate-asm.rs | 1 - .../ui/feature-gates/feature-gate-asm.stderr | 12 +- .../ui/feature-gates/feature-gate-asm2.rs | 1 - .../ui/feature-gates/feature-gate-asm2.stderr | 12 +- .../llvm-asm-concat-src.rs} | 0 .../llvm-asm-in-moved.rs} | 0 .../llvm-asm-in-out-operand.rs} | 0 .../llvm-asm-indirect-memory.rs} | 0 .../llvm-asm-out-assign.rs} | 0 .../macros/macro-expanded-include/foo/mod.rs | 2 +- .../ui/macros/macro-expanded-include/test.rs | 2 +- src/test/ui/macros/macros-nonfatal-errors.rs | 3 +- .../ui/macros/macros-nonfatal-errors.stderr | 34 +- src/test/ui/target-feature/gate.stderr | 2 +- 45 files changed, 3489 insertions(+), 40 deletions(-) create mode 100644 src/test/assembly/asm/aarch64-modifiers.rs create mode 100644 src/test/assembly/asm/aarch64-types.rs create mode 100644 src/test/assembly/asm/arm-modifiers.rs create mode 100644 src/test/assembly/asm/arm-types.rs create mode 100644 src/test/assembly/asm/riscv-modifiers.rs create mode 100644 src/test/assembly/asm/riscv-types.rs create mode 100644 src/test/assembly/asm/x86-modifiers.rs create mode 100644 src/test/assembly/asm/x86-types.rs create mode 100644 src/test/codegen/asm-options.rs create mode 100644 src/test/pretty/asm.pp create mode 100644 src/test/pretty/asm.rs rename src/test/pretty/{asm-clobbers.rs => llvm-asm-clobbers.rs} (100%) rename src/test/pretty/{asm-options.rs => llvm-asm-options.rs} (100%) create mode 100644 src/test/ui/asm/bad-options.rs create mode 100644 src/test/ui/asm/bad-options.stderr create mode 100644 src/test/ui/asm/bad-reg.rs create mode 100644 src/test/ui/asm/bad-reg.stderr create mode 100644 src/test/ui/asm/bad-template.rs create mode 100644 src/test/ui/asm/bad-template.stderr create mode 100644 src/test/ui/asm/noreturn.rs create mode 100644 src/test/ui/asm/parse-error.rs create mode 100644 src/test/ui/asm/parse-error.stderr create mode 100644 src/test/ui/asm/rustfix-asm.fixed create mode 100644 src/test/ui/asm/rustfix-asm.rs create mode 100644 src/test/ui/asm/rustfix-asm.stderr create mode 100644 src/test/ui/asm/type-check-1.rs create mode 100644 src/test/ui/asm/type-check-1.stderr create mode 100644 src/test/ui/asm/type-check-2.rs create mode 100644 src/test/ui/asm/type-check-2.stderr create mode 100644 src/test/ui/asm/type-check-3.rs create mode 100644 src/test/ui/asm/type-check-3.stderr rename src/test/ui/{asm-concat-src.rs => llvm-asm/llvm-asm-concat-src.rs} (100%) rename src/test/ui/{asm-in-moved.rs => llvm-asm/llvm-asm-in-moved.rs} (100%) rename src/test/ui/{asm-in-out-operand.rs => llvm-asm/llvm-asm-in-out-operand.rs} (100%) rename src/test/ui/{asm-indirect-memory.rs => llvm-asm/llvm-asm-indirect-memory.rs} (100%) rename src/test/ui/{asm-out-assign.rs => llvm-asm/llvm-asm-out-assign.rs} (100%) diff --git a/src/test/assembly/asm/aarch64-modifiers.rs b/src/test/assembly/asm/aarch64-modifiers.rs new file mode 100644 index 00000000000..d4519ca7ffa --- /dev/null +++ b/src/test/assembly/asm/aarch64-modifiers.rs @@ -0,0 +1,144 @@ +// assembly-output: emit-asm +// compile-flags: -O +// compile-flags: --target aarch64-unknown-linux-gnu + +#![feature(no_core, lang_items, rustc_attrs)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +impl Copy for i32 {} + +macro_rules! check { + ($func:ident $reg:ident $code:literal) => { + // -O and extern "C" guarantee that the selected register is always r0/s0/d0/q0 + #[no_mangle] + pub unsafe extern "C" fn $func() -> i32 { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!($code, out($reg) y); + y + } + }; +} + +// CHECK-LABEL: reg: +// CHECK: //APP +// CHECK: mov x0, x0 +// CHECK: //NO_APP +check!(reg reg "mov {0}, {0}"); + +// CHECK-LABEL: reg_w: +// CHECK: //APP +// CHECK: mov w0, w0 +// CHECK: //NO_APP +check!(reg_w reg "mov {0:w}, {0:w}"); + +// CHECK-LABEL: reg_x: +// CHECK: //APP +// CHECK: mov x0, x0 +// CHECK: //NO_APP +check!(reg_x reg "mov {0:x}, {0:x}"); + +// CHECK-LABEL: vreg: +// CHECK: //APP +// CHECK: add v0.4s, v0.4s, v0.4s +// CHECK: //NO_APP +check!(vreg vreg "add {0}.4s, {0}.4s, {0}.4s"); + +// CHECK-LABEL: vreg_b: +// CHECK: //APP +// CHECK: ldr b0, [x0] +// CHECK: //NO_APP +check!(vreg_b vreg "ldr {:b}, [x0]"); + +// CHECK-LABEL: vreg_h: +// CHECK: //APP +// CHECK: ldr h0, [x0] +// CHECK: //NO_APP +check!(vreg_h vreg "ldr {:h}, [x0]"); + +// CHECK-LABEL: vreg_s: +// CHECK: //APP +// CHECK: ldr s0, [x0] +// CHECK: //NO_APP +check!(vreg_s vreg "ldr {:s}, [x0]"); + +// CHECK-LABEL: vreg_d: +// CHECK: //APP +// CHECK: ldr d0, [x0] +// CHECK: //NO_APP +check!(vreg_d vreg "ldr {:d}, [x0]"); + +// CHECK-LABEL: vreg_q: +// CHECK: //APP +// CHECK: ldr q0, [x0] +// CHECK: //NO_APP +check!(vreg_q vreg "ldr {:q}, [x0]"); + +// CHECK-LABEL: vreg_v: +// CHECK: //APP +// CHECK: add v0.4s, v0.4s, v0.4s +// CHECK: //NO_APP +check!(vreg_v vreg "add {0:v}.4s, {0:v}.4s, {0:v}.4s"); + +// CHECK-LABEL: vreg_low16: +// CHECK: //APP +// CHECK: add v0.4s, v0.4s, v0.4s +// CHECK: //NO_APP +check!(vreg_low16 vreg_low16 "add {0}.4s, {0}.4s, {0}.4s"); + +// CHECK-LABEL: vreg_low16_b: +// CHECK: //APP +// CHECK: ldr b0, [x0] +// CHECK: //NO_APP +check!(vreg_low16_b vreg_low16 "ldr {:b}, [x0]"); + +// CHECK-LABEL: vreg_low16_h: +// CHECK: //APP +// CHECK: ldr h0, [x0] +// CHECK: //NO_APP +check!(vreg_low16_h vreg_low16 "ldr {:h}, [x0]"); + +// CHECK-LABEL: vreg_low16_s: +// CHECK: //APP +// CHECK: ldr s0, [x0] +// CHECK: //NO_APP +check!(vreg_low16_s vreg_low16 "ldr {:s}, [x0]"); + +// CHECK-LABEL: vreg_low16_d: +// CHECK: //APP +// CHECK: ldr d0, [x0] +// CHECK: //NO_APP +check!(vreg_low16_d vreg_low16 "ldr {:d}, [x0]"); + +// CHECK-LABEL: vreg_low16_q: +// CHECK: //APP +// CHECK: ldr q0, [x0] +// CHECK: //NO_APP +check!(vreg_low16_q vreg_low16 "ldr {:q}, [x0]"); + +// CHECK-LABEL: vreg_low16_v: +// CHECK: //APP +// CHECK: add v0.4s, v0.4s, v0.4s +// CHECK: //NO_APP +check!(vreg_low16_v vreg_low16 "add {0:v}.4s, {0:v}.4s, {0:v}.4s"); diff --git a/src/test/assembly/asm/aarch64-types.rs b/src/test/assembly/asm/aarch64-types.rs new file mode 100644 index 00000000000..8b763462e4a --- /dev/null +++ b/src/test/assembly/asm/aarch64-types.rs @@ -0,0 +1,380 @@ +// assembly-output: emit-asm +// compile-flags: --target aarch64-unknown-linux-gnu + +#![feature(no_core, lang_items, rustc_attrs, repr_simd)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register, non_camel_case_types)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +type ptr = *mut u8; + +#[repr(simd)] +pub struct i8x8(i8, i8, i8, i8, i8, i8, i8, i8); +#[repr(simd)] +pub struct i16x4(i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x2(i32, i32); +#[repr(simd)] +pub struct i64x1(i64); +#[repr(simd)] +pub struct f32x2(f32, f32); +#[repr(simd)] +pub struct f64x1(f64); +#[repr(simd)] +pub struct i8x16(i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8); +#[repr(simd)] +pub struct i16x8(i16, i16, i16, i16, i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x4(i32, i32, i32, i32); +#[repr(simd)] +pub struct i64x2(i64, i64); +#[repr(simd)] +pub struct f32x4(f32, f32, f32, f32); +#[repr(simd)] +pub struct f64x2(f64, f64); + +impl Copy for i8 {} +impl Copy for i16 {} +impl Copy for i32 {} +impl Copy for f32 {} +impl Copy for i64 {} +impl Copy for f64 {} +impl Copy for ptr {} +impl Copy for i8x8 {} +impl Copy for i16x4 {} +impl Copy for i32x2 {} +impl Copy for i64x1 {} +impl Copy for f32x2 {} +impl Copy for f64x1 {} +impl Copy for i8x16 {} +impl Copy for i16x8 {} +impl Copy for i32x4 {} +impl Copy for i64x2 {} +impl Copy for f32x4 {} +impl Copy for f64x2 {} + +extern "C" { + fn extern_func(); + static extern_static: u8; +} + +// CHECK-LABEL: sym_fn: +// CHECK: //APP +// CHECK: bl extern_func +// CHECK: //NO_APP +#[no_mangle] +pub unsafe fn sym_fn() { + asm!("bl {}", sym extern_func); +} + +// CHECK-LABEL: sym_static: +// CHECK: //APP +// CHECK: adr x0, extern_static +// CHECK: //NO_APP +#[no_mangle] +pub unsafe fn sym_static() { + asm!("adr x0, {}", sym extern_static); +} + +macro_rules! check { + ($func:ident $ty:ident $class:ident $mov:literal $modifier:literal) => { + #[no_mangle] + pub unsafe fn $func(x: $ty) -> $ty { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!( + concat!($mov, " {:", $modifier, "}, {:", $modifier, "}"), + out($class) y, + in($class) x + ); + y + } + }; +} + +// CHECK-LABEL: reg_i8: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_i8 i8 reg "mov" ""); + +// CHECK-LABEL: reg_i16: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_i16 i16 reg "mov" ""); + +// CHECK-LABEL: reg_i32: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_i32 i32 reg "mov" ""); + +// CHECK-LABEL: reg_f32: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_f32 f32 reg "mov" ""); + +// CHECK-LABEL: reg_i64: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_i64 i64 reg "mov" ""); + +// CHECK-LABEL: reg_f64: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_f64 f64 reg "mov" ""); + +// CHECK-LABEL: reg_ptr: +// CHECK: //APP +// CHECK: mov x{{[0-9]+}}, x{{[0-9]+}} +// CHECK: //NO_APP +check!(reg_ptr ptr reg "mov" ""); + +// CHECK-LABEL: vreg_i8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i8 i8 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i16: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i16 i16 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i32: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i32 i32 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f32: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f32 f32 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i64: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i64 i64 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f64: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f64 f64 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_ptr: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_ptr ptr vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i8x8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i8x8 i8x8 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i16x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i16x4 i16x4 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i32x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i32x2 i32x2 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i64x1: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i64x1 i64x1 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f32x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f32x2 f32x2 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f64x1: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f64x1 f64x1 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i8x16: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i8x16 i8x16 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i16x8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i16x8 i16x8 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i32x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i32x4 i32x4 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_i64x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_i64x2 i64x2 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f32x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f32x4 f32x4 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_f64x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_f64x2 f64x2 vreg "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i8 i8 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i16: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i16 i16 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f32: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f32 f32 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i64: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i64 i64 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f64: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f64 f64 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_ptr: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_ptr ptr vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i8x8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i8x8 i8x8 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i16x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i16x4 i16x4 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i32x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i32x2 i32x2 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i64x1: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i64x1 i64x1 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f32x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f32x2 f32x2 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f64x1: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f64x1 f64x1 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i8x16: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i8x16 i8x16 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i16x8: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i16x8 i16x8 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i32x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i32x4 i32x4 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_i64x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_i64x2 i64x2 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f32x4: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f32x4 f32x4 vreg_low16 "fmov" "s"); + +// CHECK-LABEL: vreg_low16_f64x2: +// CHECK: //APP +// CHECK: fmov s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: //NO_APP +check!(vreg_low16_f64x2 f64x2 vreg_low16 "fmov" "s"); diff --git a/src/test/assembly/asm/arm-modifiers.rs b/src/test/assembly/asm/arm-modifiers.rs new file mode 100644 index 00000000000..cefab9c4a67 --- /dev/null +++ b/src/test/assembly/asm/arm-modifiers.rs @@ -0,0 +1,149 @@ +// assembly-output: emit-asm +// compile-flags: -O +// compile-flags: --target armv7-unknown-linux-gnueabihf +// compile-flags: -C target-feature=+neon + +#![feature(no_core, lang_items, rustc_attrs, repr_simd)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register, non_camel_case_types)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +#[repr(simd)] +pub struct f32x4(f32, f32, f32, f32); + +impl Copy for i32 {} +impl Copy for f32 {} +impl Copy for f64 {} +impl Copy for f32x4 {} + +macro_rules! check { + ($func:ident $modifier:literal $reg:ident $ty:ident $mov:literal) => { + // -O and extern "C" guarantee that the selected register is always r0/s0/d0/q0 + #[no_mangle] + pub unsafe extern "C" fn $func() -> $ty { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {0:", $modifier, "}, {0:", $modifier, "}"), out($reg) y); + y + } + }; +} + +// CHECK-LABEL: reg: +// CHECK: @APP +// CHECK: mov r0, r0 +// CHECK: @NO_APP +check!(reg "" reg i32 "mov"); + +// CHECK-LABEL: reg_thumb: +// CHECK: @APP +// CHECK: mov r0, r0 +// CHECK: @NO_APP +check!(reg_thumb "" reg_thumb i32 "mov"); + +// CHECK-LABEL: sreg: +// CHECK: @APP +// CHECK: vmov.f32 s0, s0 +// CHECK: @NO_APP +check!(sreg "" sreg f32 "vmov.f32"); + +// CHECK-LABEL: sreg_low16: +// CHECK: @APP +// CHECK: vmov.f32 s0, s0 +// CHECK: @NO_APP +check!(sreg_low16 "" sreg_low16 f32 "vmov.f32"); + +// CHECK-LABEL: dreg: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(dreg "" dreg f64 "vmov.f64"); + +// CHECK-LABEL: dreg_low16: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(dreg_low16 "" dreg_low16 f64 "vmov.f64"); + +// CHECK-LABEL: dreg_low8: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(dreg_low8 "" dreg_low8 f64 "vmov.f64"); + +// CHECK-LABEL: qreg: +// CHECK: @APP +// CHECK: vorr q0, q0, q0 +// CHECK: @NO_APP +check!(qreg "" qreg f32x4 "vmov"); + +// CHECK-LABEL: qreg_e: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(qreg_e "e" qreg f32x4 "vmov.f64"); + +// CHECK-LABEL: qreg_f: +// CHECK: @APP +// CHECK: vmov.f64 d1, d1 +// CHECK: @NO_APP +check!(qreg_f "f" qreg f32x4 "vmov.f64"); + +// CHECK-LABEL: qreg_low8: +// CHECK: @APP +// CHECK: vorr q0, q0, q0 +// CHECK: @NO_APP +check!(qreg_low8 "" qreg_low8 f32x4 "vmov"); + +// CHECK-LABEL: qreg_low8_e: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(qreg_low8_e "e" qreg_low8 f32x4 "vmov.f64"); + +// CHECK-LABEL: qreg_low8_f: +// CHECK: @APP +// CHECK: vmov.f64 d1, d1 +// CHECK: @NO_APP +check!(qreg_low8_f "f" qreg_low8 f32x4 "vmov.f64"); + +// CHECK-LABEL: qreg_low4: +// CHECK: @APP +// CHECK: vorr q0, q0, q0 +// CHECK: @NO_APP +check!(qreg_low4 "" qreg_low4 f32x4 "vmov"); + +// CHECK-LABEL: qreg_low4_e: +// CHECK: @APP +// CHECK: vmov.f64 d0, d0 +// CHECK: @NO_APP +check!(qreg_low4_e "e" qreg_low4 f32x4 "vmov.f64"); + +// CHECK-LABEL: qreg_low4_f: +// CHECK: @APP +// CHECK: vmov.f64 d1, d1 +// CHECK: @NO_APP +check!(qreg_low4_f "f" qreg_low4 f32x4 "vmov.f64"); diff --git a/src/test/assembly/asm/arm-types.rs b/src/test/assembly/asm/arm-types.rs new file mode 100644 index 00000000000..729adae66f6 --- /dev/null +++ b/src/test/assembly/asm/arm-types.rs @@ -0,0 +1,413 @@ +// assembly-output: emit-asm +// compile-flags: --target armv7-unknown-linux-gnueabihf +// compile-flags: -C target-feature=+neon + +#![feature(no_core, lang_items, rustc_attrs, repr_simd)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register, non_camel_case_types)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +type ptr = *mut u8; + +#[repr(simd)] +pub struct i8x8(i8, i8, i8, i8, i8, i8, i8, i8); +#[repr(simd)] +pub struct i16x4(i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x2(i32, i32); +#[repr(simd)] +pub struct i64x1(i64); +#[repr(simd)] +pub struct f32x2(f32, f32); +#[repr(simd)] +pub struct i8x16(i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8); +#[repr(simd)] +pub struct i16x8(i16, i16, i16, i16, i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x4(i32, i32, i32, i32); +#[repr(simd)] +pub struct i64x2(i64, i64); +#[repr(simd)] +pub struct f32x4(f32, f32, f32, f32); + +impl Copy for i8 {} +impl Copy for i16 {} +impl Copy for i32 {} +impl Copy for f32 {} +impl Copy for i64 {} +impl Copy for f64 {} +impl Copy for ptr {} +impl Copy for i8x8 {} +impl Copy for i16x4 {} +impl Copy for i32x2 {} +impl Copy for i64x1 {} +impl Copy for f32x2 {} +impl Copy for i8x16 {} +impl Copy for i16x8 {} +impl Copy for i32x4 {} +impl Copy for i64x2 {} +impl Copy for f32x4 {} + +extern "C" { + fn extern_func(); + static extern_static: u8; +} + +// CHECK-LABEL: sym_fn: +// CHECK: @APP +// CHECK: bl extern_func +// CHECK: @NO_APP +#[no_mangle] +pub unsafe fn sym_fn() { + asm!("bl {}", sym extern_func); +} + +// CHECK-LABEL: sym_static: +// CHECK: @APP +// CHECK: adr r0, extern_static +// CHECK: @NO_APP +#[no_mangle] +pub unsafe fn sym_static() { + asm!("adr r0, {}", sym extern_static); +} + +macro_rules! check { + ($func:ident $ty:ident $class:ident $mov:literal) => { + #[no_mangle] + pub unsafe fn $func(x: $ty) -> $ty { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {}, {}"), out($class) y, in($class) x); + y + } + }; +} + +// CHECK-LABEL: reg_i8: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_i8 i8 reg "mov"); + +// CHECK-LABEL: reg_i16: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_i16 i16 reg "mov"); + +// CHECK-LABEL: reg_i32: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_i32 i32 reg "mov"); + +// CHECK-LABEL: reg_f32: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_f32 f32 reg "mov"); + +// CHECK-LABEL: reg_ptr: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_ptr ptr reg "mov"); + +// CHECK-LABEL: reg_thumb_i8: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_thumb_i8 i8 reg_thumb "mov"); + +// CHECK-LABEL: reg_thumb_i16: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_thumb_i16 i16 reg_thumb "mov"); + +// CHECK-LABEL: reg_thumb_i32: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_thumb_i32 i32 reg_thumb "mov"); + +// CHECK-LABEL: reg_thumb_f32: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_thumb_f32 f32 reg_thumb "mov"); + +// CHECK-LABEL: reg_thumb_ptr: +// CHECK: @APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: @NO_APP +check!(reg_thumb_ptr ptr reg_thumb "mov"); + +// CHECK-LABEL: sreg_i32: +// CHECK: @APP +// CHECK: vmov.f32 s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: @NO_APP +check!(sreg_i32 i32 sreg "vmov.f32"); + +// CHECK-LABEL: sreg_f32: +// CHECK: @APP +// CHECK: vmov.f32 s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: @NO_APP +check!(sreg_f32 f32 sreg "vmov.f32"); + +// CHECK-LABEL: sreg_ptr: +// CHECK: @APP +// CHECK: vmov.f32 s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: @NO_APP +check!(sreg_ptr ptr sreg "vmov.f32"); + +// CHECK-LABEL: sreg_low16_i32: +// CHECK: @APP +// CHECK: vmov.f32 s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: @NO_APP +check!(sreg_low16_i32 i32 sreg_low16 "vmov.f32"); + +// CHECK-LABEL: sreg_low16_f32: +// CHECK: @APP +// CHECK: vmov.f32 s{{[0-9]+}}, s{{[0-9]+}} +// CHECK: @NO_APP +check!(sreg_low16_f32 f32 sreg_low16 "vmov.f32"); + +// CHECK-LABEL: dreg_i64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_i64 i64 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_f64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_f64 f64 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_i8x8: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_i8x8 i8x8 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_i16x4: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_i16x4 i16x4 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_i32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_i32x2 i32x2 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_i64x1: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_i64x1 i64x1 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_f32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_f32x2 f32x2 dreg "vmov.f64"); + +// CHECK-LABEL: dreg_low16_i64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_i64 i64 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_f64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_f64 f64 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_i8x8: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_i8x8 i8x8 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_i16x4: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_i16x4 i16x4 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_i32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_i32x2 i32x2 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_i64x1: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_i64x1 i64x1 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low16_f32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low16_f32x2 f32x2 dreg_low16 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_i64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_i64 i64 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_f64: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_f64 f64 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_i8x8: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_i8x8 i8x8 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_i16x4: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_i16x4 i16x4 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_i32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_i32x2 i32x2 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_i64x1: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_i64x1 i64x1 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: dreg_low8_f32x2: +// CHECK: @APP +// CHECK: vmov.f64 d{{[0-9]+}}, d{{[0-9]+}} +// CHECK: @NO_APP +check!(dreg_low8_f32x2 f32x2 dreg_low8 "vmov.f64"); + +// CHECK-LABEL: qreg_i8x16: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_i8x16 i8x16 qreg "vmov"); + +// CHECK-LABEL: qreg_i16x8: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_i16x8 i16x8 qreg "vmov"); + +// CHECK-LABEL: qreg_i32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_i32x4 i32x4 qreg "vmov"); + +// CHECK-LABEL: qreg_i64x2: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_i64x2 i64x2 qreg "vmov"); + +// CHECK-LABEL: qreg_f32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_f32x4 f32x4 qreg "vmov"); + +// CHECK-LABEL: qreg_low8_i8x16: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low8_i8x16 i8x16 qreg_low8 "vmov"); + +// CHECK-LABEL: qreg_low8_i16x8: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low8_i16x8 i16x8 qreg_low8 "vmov"); + +// CHECK-LABEL: qreg_low8_i32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low8_i32x4 i32x4 qreg_low8 "vmov"); + +// CHECK-LABEL: qreg_low8_i64x2: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low8_i64x2 i64x2 qreg_low8 "vmov"); + +// CHECK-LABEL: qreg_low8_f32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low8_f32x4 f32x4 qreg_low8 "vmov"); + +// CHECK-LABEL: qreg_low4_i8x16: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low4_i8x16 i8x16 qreg_low4 "vmov"); + +// CHECK-LABEL: qreg_low4_i16x8: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low4_i16x8 i16x8 qreg_low4 "vmov"); + +// CHECK-LABEL: qreg_low4_i32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low4_i32x4 i32x4 qreg_low4 "vmov"); + +// CHECK-LABEL: qreg_low4_i64x2: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low4_i64x2 i64x2 qreg_low4 "vmov"); + +// CHECK-LABEL: qreg_low4_f32x4: +// CHECK: @APP +// CHECK: vorr q{{[0-9]+}}, q{{[0-9]+}}, q{{[0-9]+}} +// CHECK: @NO_APP +check!(qreg_low4_f32x4 f32x4 qreg_low4 "vmov"); diff --git a/src/test/assembly/asm/riscv-modifiers.rs b/src/test/assembly/asm/riscv-modifiers.rs new file mode 100644 index 00000000000..622d0c0dc5e --- /dev/null +++ b/src/test/assembly/asm/riscv-modifiers.rs @@ -0,0 +1,58 @@ +// assembly-output: emit-asm +// compile-flags: -O +// compile-flags: --target riscv64gc-unknown-linux-gnu +// compile-flags: -C target-feature=+f + +#![feature(no_core, lang_items, rustc_attrs)] +#![crate_type = "rlib"] +#![no_core] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +impl Copy for f32 {} + +macro_rules! check { + ($func:ident $modifier:literal $reg:ident $mov:literal) => { + // -O and extern "C" guarantee that the selected register is always r0/s0/d0/q0 + #[no_mangle] + pub unsafe extern "C" fn $func() -> f32 { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {0:", $modifier, "}, {0:", $modifier, "}"), out($reg) y); + y + } + }; +} + +// CHECK-LABEL: reg: +// CHECK: #APP +// CHECK: mv a0, a0 +// CHECK: #NO_APP +check!(reg "" reg "mv"); + +// CHECK-LABEL: freg: +// CHECK: #APP +// CHECK: fmv.s fa0, fa0 +// CHECK: #NO_APP +check!(freg "" freg "fmv.s"); diff --git a/src/test/assembly/asm/riscv-types.rs b/src/test/assembly/asm/riscv-types.rs new file mode 100644 index 00000000000..5c10753c765 --- /dev/null +++ b/src/test/assembly/asm/riscv-types.rs @@ -0,0 +1,134 @@ +// revisions: riscv64 riscv32 +// assembly-output: emit-asm +//[riscv64] compile-flags: --target riscv64imac-unknown-none-elf +//[riscv32] compile-flags: --target riscv32imac-unknown-none-elf +// compile-flags: -C target-feature=+d + +#![feature(no_core, lang_items, rustc_attrs)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +type ptr = *mut u8; + +impl Copy for i8 {} +impl Copy for i16 {} +impl Copy for i32 {} +impl Copy for f32 {} +impl Copy for i64 {} +impl Copy for f64 {} +impl Copy for ptr {} + +extern "C" { + fn extern_func(); + static extern_static: u8; +} + +// CHECK-LABEL: sym_fn: +// CHECK: #APP +// CHECK: call extern_func +// CHECK: #NO_APP +#[no_mangle] +pub unsafe fn sym_fn() { + asm!("call {}", sym extern_func); +} + +// CHECK-LABEL: sym_static: +// CHECK: #APP +// CHECK: lb t0, extern_static +// CHECK: #NO_APP +#[no_mangle] +pub unsafe fn sym_static() { + asm!("lb t0, {}", sym extern_static); +} + +macro_rules! check { + ($func:ident $ty:ident $class:ident $mov:literal) => { + #[no_mangle] + pub unsafe fn $func(x: $ty) -> $ty { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {}, {}"), out($class) y, in($class) x); + y + } + }; +} + +// CHECK-LABEL: reg_i8: +// CHECK: #APP +// CHECK: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_i8 i8 reg "mv"); + +// CHECK-LABEL: reg_i16: +// CHECK: #APP +// CHECK: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_i16 i16 reg "mv"); + +// CHECK-LABEL: reg_i32: +// CHECK: #APP +// CHECK: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_i32 i32 reg "mv"); + +// CHECK-LABEL: reg_f32: +// CHECK: #APP +// CHECK: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_f32 f32 reg "mv"); + +// riscv64-LABEL: reg_i64: +// riscv64: #APP +// riscv64: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// riscv64: #NO_APP +#[cfg(riscv64)] +check!(reg_i64 i64 reg "mv"); + +// riscv64-LABEL: reg_f64: +// riscv64: #APP +// riscv64: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// riscv64: #NO_APP +#[cfg(riscv64)] +check!(reg_f64 f64 reg "mv"); + +// CHECK-LABEL: reg_ptr: +// CHECK: #APP +// CHECK: mv {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_ptr ptr reg "mv"); + +// CHECK-LABEL: freg_f32: +// CHECK: #APP +// CHECK: fmv.s f{{[a-z0-9]+}}, f{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(freg_f32 f32 freg "fmv.s"); + +// CHECK-LABEL: freg_f64: +// CHECK: #APP +// CHECK: fmv.d f{{[a-z0-9]+}}, f{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(freg_f64 f64 freg "fmv.d"); diff --git a/src/test/assembly/asm/x86-modifiers.rs b/src/test/assembly/asm/x86-modifiers.rs new file mode 100644 index 00000000000..460e22aa69e --- /dev/null +++ b/src/test/assembly/asm/x86-modifiers.rs @@ -0,0 +1,210 @@ +// revisions: x86_64 i686 +// assembly-output: emit-asm +// compile-flags: -O +//[x86_64] compile-flags: --target x86_64-unknown-linux-gnu +//[i686] compile-flags: --target i686-unknown-linux-gnu +// compile-flags: -C llvm-args=--x86-asm-syntax=intel +// compile-flags: -C target-feature=+avx512bw + +#![feature(no_core, lang_items, rustc_attrs)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +impl Copy for i32 {} + +macro_rules! check { + ($func:ident $modifier:literal $reg:ident $mov:literal) => { + // -O and extern "C" guarantee that the selected register is always ax/xmm0 + #[no_mangle] + pub unsafe extern "C" fn $func() -> i32 { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {0:", $modifier, "}, {0:", $modifier, "}"), out($reg) y); + y + } + }; +} + +// CHECK-LABEL: reg: +// CHECK: #APP +// x86_64: mov rax, rax +// i686: mov eax, eax +// CHECK: #NO_APP +check!(reg "" reg "mov"); + +// x86_64-LABEL: reg_l: +// x86_64: #APP +// x86_64: mov al, al +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_l "l" reg "mov"); + +// x86_64-LABEL: reg_h: +// x86_64: #APP +// x86_64: mov ah, ah +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_h "h" reg "mov"); + +// CHECK-LABEL: reg_x: +// CHECK: #APP +// CHECK: mov ax, ax +// CHECK: #NO_APP +check!(reg_x "x" reg "mov"); + +// CHECK-LABEL: reg_e: +// CHECK: #APP +// CHECK: mov eax, eax +// CHECK: #NO_APP +check!(reg_e "e" reg "mov"); + +// x86_64-LABEL: reg_r: +// x86_64: #APP +// x86_64: mov rax, rax +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_r "r" reg "mov"); + +// CHECK-LABEL: reg_abcd: +// CHECK: #APP +// x86_64: mov rax, rax +// i686: mov eax, eax +// CHECK: #NO_APP +check!(reg_abcd "" reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_l: +// CHECK: #APP +// CHECK: mov al, al +// CHECK: #NO_APP +check!(reg_abcd_l "l" reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_h: +// CHECK: #APP +// CHECK: mov ah, ah +// CHECK: #NO_APP +check!(reg_abcd_h "h" reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_x: +// CHECK: #APP +// CHECK: mov ax, ax +// CHECK: #NO_APP +check!(reg_abcd_x "x" reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_e: +// CHECK: #APP +// CHECK: mov eax, eax +// CHECK: #NO_APP +check!(reg_abcd_e "e" reg_abcd "mov"); + +// x86_64-LABEL: reg_abcd_r: +// x86_64: #APP +// x86_64: mov rax, rax +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_abcd_r "r" reg_abcd "mov"); + +// CHECK-LABEL: xmm_reg +// CHECK: #APP +// CHECK: movaps xmm0, xmm0 +// CHECK: #NO_APP +check!(xmm_reg "" xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_x +// CHECK: #APP +// CHECK: movaps xmm0, xmm0 +// CHECK: #NO_APP +check!(xmm_reg_x "x" xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_y +// CHECK: #APP +// CHECK: vmovaps ymm0, ymm0 +// CHECK: #NO_APP +check!(xmm_reg_y "y" xmm_reg "vmovaps"); + +// CHECK-LABEL: xmm_reg_z +// CHECK: #APP +// CHECK: vmovaps zmm0, zmm0 +// CHECK: #NO_APP +check!(xmm_reg_z "z" xmm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg +// CHECK: #APP +// CHECK: movaps ymm0, ymm0 +// CHECK: #NO_APP +check!(ymm_reg "" ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_x +// CHECK: #APP +// CHECK: movaps xmm0, xmm0 +// CHECK: #NO_APP +check!(ymm_reg_x "x" ymm_reg "movaps"); + +// CHECK-LABEL: ymm_reg_y +// CHECK: #APP +// CHECK: vmovaps ymm0, ymm0 +// CHECK: #NO_APP +check!(ymm_reg_y "y" ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_z +// CHECK: #APP +// CHECK: vmovaps zmm0, zmm0 +// CHECK: #NO_APP +check!(ymm_reg_z "z" ymm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg +// CHECK: #APP +// CHECK: movaps zmm0, zmm0 +// CHECK: #NO_APP +check!(zmm_reg "" zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_x +// CHECK: #APP +// CHECK: movaps xmm0, xmm0 +// CHECK: #NO_APP +check!(zmm_reg_x "x" zmm_reg "movaps"); + +// CHECK-LABEL: zmm_reg_y +// CHECK: #APP +// CHECK: vmovaps ymm0, ymm0 +// CHECK: #NO_APP +check!(zmm_reg_y "y" zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_z +// CHECK: #APP +// CHECK: vmovaps zmm0, zmm0 +// CHECK: #NO_APP +check!(zmm_reg_z "z" zmm_reg "vmovaps"); + +// Note: we don't have any way of ensuring that k1 is actually the register +// chosen by the register allocator, so this check may fail if a different +// register is chosen. + +// CHECK-LABEL: kreg: +// CHECK: #APP +// CHECK: kmovb k1, k1 +// CHECK: #NO_APP +check!(kreg "" kreg "kmovb"); diff --git a/src/test/assembly/asm/x86-types.rs b/src/test/assembly/asm/x86-types.rs new file mode 100644 index 00000000000..d2819ac88e7 --- /dev/null +++ b/src/test/assembly/asm/x86-types.rs @@ -0,0 +1,701 @@ +// revisions: x86_64 i686 +// assembly-output: emit-asm +//[x86_64] compile-flags: --target x86_64-unknown-linux-gnu +//[i686] compile-flags: --target i686-unknown-linux-gnu +// compile-flags: -C llvm-args=--x86-asm-syntax=intel +// compile-flags: -C target-feature=+avx512bw + +#![feature(no_core, lang_items, rustc_attrs, repr_simd)] +#![crate_type = "rlib"] +#![no_core] +#![allow(asm_sub_register, non_camel_case_types)] + +#[rustc_builtin_macro] +macro_rules! asm { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! concat { + () => {}; +} +#[rustc_builtin_macro] +macro_rules! stringify { + () => {}; +} + +#[lang = "sized"] +trait Sized {} +#[lang = "copy"] +trait Copy {} + +type ptr = *mut u8; + +#[repr(simd)] +pub struct i8x16(i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8); +#[repr(simd)] +pub struct i16x8(i16, i16, i16, i16, i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x4(i32, i32, i32, i32); +#[repr(simd)] +pub struct i64x2(i64, i64); +#[repr(simd)] +pub struct f32x4(f32, f32, f32, f32); +#[repr(simd)] +pub struct f64x2(f64, f64); + +#[repr(simd)] +pub struct i8x32( + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, +); +#[repr(simd)] +pub struct i16x16(i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16, i16); +#[repr(simd)] +pub struct i32x8(i32, i32, i32, i32, i32, i32, i32, i32); +#[repr(simd)] +pub struct i64x4(i64, i64, i64, i64); +#[repr(simd)] +pub struct f32x8(f32, f32, f32, f32, f32, f32, f32, f32); +#[repr(simd)] +pub struct f64x4(f64, f64, f64, f64); + +#[repr(simd)] +pub struct i8x64( + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, + i8, +); +#[repr(simd)] +pub struct i16x32( + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, + i16, +); +#[repr(simd)] +pub struct i32x16(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32); +#[repr(simd)] +pub struct i64x8(i64, i64, i64, i64, i64, i64, i64, i64); +#[repr(simd)] +pub struct f32x16(f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32); +#[repr(simd)] +pub struct f64x8(f64, f64, f64, f64, f64, f64, f64, f64); + +impl Copy for i8 {} +impl Copy for i16 {} +impl Copy for i32 {} +impl Copy for f32 {} +impl Copy for i64 {} +impl Copy for f64 {} +impl Copy for ptr {} +impl Copy for i8x16 {} +impl Copy for i16x8 {} +impl Copy for i32x4 {} +impl Copy for i64x2 {} +impl Copy for f32x4 {} +impl Copy for f64x2 {} +impl Copy for i8x32 {} +impl Copy for i16x16 {} +impl Copy for i32x8 {} +impl Copy for i64x4 {} +impl Copy for f32x8 {} +impl Copy for f64x4 {} +impl Copy for i8x64 {} +impl Copy for i16x32 {} +impl Copy for i32x16 {} +impl Copy for i64x8 {} +impl Copy for f32x16 {} +impl Copy for f64x8 {} + +extern "C" { + fn extern_func(); + static extern_static: u8; +} + +// CHECK-LABEL: sym_fn: +// CHECK: #APP +// CHECK: call extern_func +// CHECK: #NO_APP +#[no_mangle] +pub unsafe fn sym_fn() { + asm!("call {}", sym extern_func); +} + +// CHECK-LABEL: sym_static: +// CHECK: #APP +// CHECK: mov al, byte ptr [extern_static] +// CHECK: #NO_APP +#[no_mangle] +pub unsafe fn sym_static() { + asm!("mov al, byte ptr [{}]", sym extern_static); +} + +macro_rules! check { + ($func:ident $ty:ident $class:ident $mov:literal) => { + #[no_mangle] + pub unsafe fn $func(x: $ty) -> $ty { + // Hack to avoid function merging + extern "Rust" { + fn dont_merge(s: &str); + } + dont_merge(stringify!($func)); + + let y; + asm!(concat!($mov, " {}, {}"), out($class) y, in($class) x); + y + } + }; +} + +// CHECK-LABEL: reg_i8: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_i8 i8 reg "mov"); + +// CHECK-LABEL: reg_i16: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_i16 i16 reg "mov"); + +// CHECK-LABEL: reg_i32: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_i32 i32 reg "mov"); + +// CHECK-LABEL: reg_f32: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_f32 f32 reg "mov"); + +// x86_64-LABEL: reg_i64: +// x86_64: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_i64 i64 reg "mov"); + +// x86_64-LABEL: reg_f64: +// x86_64: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_f64 f64 reg "mov"); + +// CHECK-LABEL: reg_ptr: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_ptr ptr reg "mov"); + +// CHECK-LABEL: reg_abcd_i8: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_abcd_i8 i8 reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_i16: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_abcd_i16 i16 reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_i32: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_abcd_i32 i32 reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_f32: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_abcd_f32 f32 reg_abcd "mov"); + +// x86_64-LABEL: reg_abcd_i64: +// x86_64: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_abcd_i64 i64 reg_abcd "mov"); + +// x86_64-LABEL: reg_abcd_f64: +// x86_64: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// x86_64: #NO_APP +#[cfg(x86_64)] +check!(reg_abcd_f64 f64 reg_abcd "mov"); + +// CHECK-LABEL: reg_abcd_ptr: +// CHECK: #APP +// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} +// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_abcd_ptr ptr reg_abcd "mov"); + +// CHECK-LABEL: xmm_reg_i32: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i32 i32 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_f32: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_f32 f32 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_i64: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i64 i64 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_f64: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_f64 f64 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_ptr: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_ptr ptr xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_i8x16: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i8x16 i8x16 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_i16x8: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i16x8 i16x8 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_i32x4: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i32x4 i32x4 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_i64x2: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_i64x2 i64x2 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_f32x4: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_f32x4 f32x4 xmm_reg "movaps"); + +// CHECK-LABEL: xmm_reg_f64x2: +// CHECK: #APP +// CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} +// CHECK: #NO_APP +check!(xmm_reg_f64x2 f64x2 xmm_reg "movaps"); + +// CHECK-LABEL: ymm_reg_i32: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i32 i32 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f32: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f32 f32 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i64: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i64 i64 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f64: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f64 f64 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_ptr: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_ptr ptr ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i8x16: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i8x16 i8x16 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i16x8: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i16x8 i16x8 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i32x4: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i32x4 i32x4 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i64x2: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i64x2 i64x2 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f32x4: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f32x4 f32x4 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f64x2: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f64x2 f64x2 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i8x32: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i8x32 i8x32 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i16x16: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i16x16 i16x16 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i32x8: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i32x8 i32x8 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_i64x4: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_i64x4 i64x4 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f32x8: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f32x8 f32x8 ymm_reg "vmovaps"); + +// CHECK-LABEL: ymm_reg_f64x4: +// CHECK: #APP +// CHECK: vmovaps ymm{{[0-9]+}}, ymm{{[0-9]+}} +// CHECK: #NO_APP +check!(ymm_reg_f64x4 f64x4 ymm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i32: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i32 i32 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f32: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f32 f32 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i64: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i64 i64 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f64: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f64 f64 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_ptr: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_ptr ptr zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i8x16: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i8x16 i8x16 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i16x8: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i16x8 i16x8 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i32x4: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i32x4 i32x4 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i64x2: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i64x2 i64x2 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f32x4: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f32x4 f32x4 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f64x2: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f64x2 f64x2 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i8x32: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i8x32 i8x32 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i16x16: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i16x16 i16x16 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i32x8: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i32x8 i32x8 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i64x4: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i64x4 i64x4 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f32x8: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f32x8 f32x8 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f64x4: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f64x4 f64x4 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i8x64: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i8x64 i8x64 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i16x32: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i16x32 i16x32 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i32x16: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i32x16 i32x16 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_i64x8: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_i64x8 i64x8 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f32x16: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f32x16 f32x16 zmm_reg "vmovaps"); + +// CHECK-LABEL: zmm_reg_f64x8: +// CHECK: #APP +// CHECK: vmovaps zmm{{[0-9]+}}, zmm{{[0-9]+}} +// CHECK: #NO_APP +check!(zmm_reg_f64x8 f64x8 zmm_reg "vmovaps"); + +// CHECK-LABEL: kreg_i8: +// CHECK: #APP +// CHECK: kmovb k{{[0-9]+}}, k{{[0-9]+}} +// CHECK: #NO_APP +check!(kreg_i8 i8 kreg "kmovb"); + +// CHECK-LABEL: kreg_i16: +// CHECK: #APP +// CHECK: kmovw k{{[0-9]+}}, k{{[0-9]+}} +// CHECK: #NO_APP +check!(kreg_i16 i16 kreg "kmovw"); + +// CHECK-LABEL: kreg_i32: +// CHECK: #APP +// CHECK: kmovd k{{[0-9]+}}, k{{[0-9]+}} +// CHECK: #NO_APP +check!(kreg_i32 i32 kreg "kmovd"); + +// CHECK-LABEL: kreg_i64: +// CHECK: #APP +// CHECK: kmovq k{{[0-9]+}}, k{{[0-9]+}} +// CHECK: #NO_APP +check!(kreg_i64 i64 kreg "kmovq"); + +// CHECK-LABEL: kreg_ptr: +// CHECK: #APP +// CHECK: kmovq k{{[0-9]+}}, k{{[0-9]+}} +// CHECK: #NO_APP +check!(kreg_ptr ptr kreg "kmovq"); diff --git a/src/test/codegen/asm-options.rs b/src/test/codegen/asm-options.rs new file mode 100644 index 00000000000..21e7eb43796 --- /dev/null +++ b/src/test/codegen/asm-options.rs @@ -0,0 +1,96 @@ +// compile-flags: -O +// only-x86_64 + +#![crate_type = "rlib"] +#![feature(asm)] + +// CHECK-LABEL: @pure +// CHECK-NOT: asm +// CHECK: ret void +#[no_mangle] +pub unsafe fn pure(x: i32) { + let y: i32; + asm!("", out("ax") y, in("bx") x, options(pure, nomem)); +} + +// CHECK-LABEL: @noreturn +// CHECK: call void asm +// CHECK-NEXT: unreachable +#[no_mangle] +pub unsafe fn noreturn() { + asm!("", options(noreturn)); +} + +pub static mut VAR: i32 = 0; +pub static mut DUMMY_OUTPUT: i32 = 0; + +// CHECK-LABEL: @readonly +// CHECK: call i32 asm +// CHECK: ret i32 1 +#[no_mangle] +pub unsafe fn readonly() -> i32 { + VAR = 1; + asm!("", out("ax") DUMMY_OUTPUT, options(pure, readonly)); + VAR +} + +// CHECK-LABEL: @not_readonly +// CHECK: call i32 asm +// CHECK: ret i32 % +#[no_mangle] +pub unsafe fn not_readonly() -> i32 { + VAR = 1; + asm!("", out("ax") DUMMY_OUTPUT, options()); + VAR +} + +// CHECK-LABEL: @nomem +// CHECK-NOT: store +// CHECK: call i32 asm +// CHECK: store +// CHECK: ret i32 2 +#[no_mangle] +pub unsafe fn nomem() -> i32 { + VAR = 1; + asm!("", out("ax") DUMMY_OUTPUT, options(pure, nomem)); + VAR = 2; + VAR +} + +// CHECK-LABEL: @nomem_nopure +// CHECK-NOT: store +// CHECK: call i32 asm +// CHECK: store +// CHECK: ret i32 2 +#[no_mangle] +pub unsafe fn nomem_nopure() -> i32 { + VAR = 1; + asm!("", out("ax") DUMMY_OUTPUT, options(nomem)); + VAR = 2; + VAR +} + +// CHECK-LABEL: @not_nomem +// CHECK: store +// CHECK: call i32 asm +// CHECK: store +// CHECK: ret i32 2 +#[no_mangle] +pub unsafe fn not_nomem() -> i32 { + VAR = 1; + asm!("", out("ax") DUMMY_OUTPUT, options(pure, readonly)); + VAR = 2; + VAR +} + +// CHECK-LABEL: @dont_remove_nonpure +// CHECK: call void asm +// CHECK: call void asm +// CHECK: call void asm +// CHECK: ret void +#[no_mangle] +pub unsafe fn dont_remove_nonpure() { + asm!("", options()); + asm!("", options(nomem)); + asm!("", options(readonly)); +} diff --git a/src/test/pretty/asm.pp b/src/test/pretty/asm.pp new file mode 100644 index 00000000000..1723e1cc1cb --- /dev/null +++ b/src/test/pretty/asm.pp @@ -0,0 +1,25 @@ +#![feature(prelude_import)] +#![no_std] +#![feature(asm)] +#[prelude_import] +use ::std::prelude::v1::*; +#[macro_use] +extern crate std; + +// pretty-mode:expanded +// pp-exact:asm.pp + +pub fn main() { + let a: i32; + let mut b = 4i32; + unsafe { + asm!(""); + asm!(""); + asm!("", options(nomem, nostack)); + asm!("{0}", in(reg) 4); + asm!("{0}", out(reg) a); + asm!("{0}", inout(reg) b); + asm!("{0} {1}", out(reg) _, inlateout(reg) b => _); + asm!("", out("al") _, lateout("rbx") _); + } +} diff --git a/src/test/pretty/asm.rs b/src/test/pretty/asm.rs new file mode 100644 index 00000000000..9812f1d97e5 --- /dev/null +++ b/src/test/pretty/asm.rs @@ -0,0 +1,19 @@ +#![feature(asm)] + +// pretty-mode:expanded +// pp-exact:asm.pp + +pub fn main() { + let a: i32; + let mut b = 4i32; + unsafe { + asm!(""); + asm!("", options()); + asm!("", options(nostack, nomem)); + asm!("{}", in(reg) 4); + asm!("{0}", out(reg) a); + asm!("{name}", name = inout(reg) b); + asm!("{} {}", out(reg) _, inlateout(reg) b => _); + asm!("", out("al") _, lateout("rbx") _); + } +} diff --git a/src/test/pretty/asm-clobbers.rs b/src/test/pretty/llvm-asm-clobbers.rs similarity index 100% rename from src/test/pretty/asm-clobbers.rs rename to src/test/pretty/llvm-asm-clobbers.rs diff --git a/src/test/pretty/asm-options.rs b/src/test/pretty/llvm-asm-options.rs similarity index 100% rename from src/test/pretty/asm-options.rs rename to src/test/pretty/llvm-asm-options.rs diff --git a/src/test/ui/asm/bad-options.rs b/src/test/ui/asm/bad-options.rs new file mode 100644 index 00000000000..755fc2ca238 --- /dev/null +++ b/src/test/ui/asm/bad-options.rs @@ -0,0 +1,18 @@ +// only-x86_64 + +#![feature(asm)] + +fn main() { + let mut foo = 0; + unsafe { + asm!("", options(nomem, readonly)); + //~^ ERROR the `nomem` and `readonly` options are mutually exclusive + asm!("", options(pure, nomem, noreturn)); + //~^ ERROR the `pure` and `noreturn` options are mutually exclusive + //~^^ ERROR asm with `pure` option must have at least one output + asm!("{}", in(reg) foo, options(pure, nomem)); + //~^ ERROR asm with `pure` option must have at least one output + asm!("{}", out(reg) foo, options(noreturn)); + //~^ ERROR asm outputs are not allowed with the `noreturn` option + } +} diff --git a/src/test/ui/asm/bad-options.stderr b/src/test/ui/asm/bad-options.stderr new file mode 100644 index 00000000000..c5e8e2ccf44 --- /dev/null +++ b/src/test/ui/asm/bad-options.stderr @@ -0,0 +1,32 @@ +error: the `nomem` and `readonly` options are mutually exclusive + --> $DIR/bad-options.rs:8:18 + | +LL | asm!("", options(nomem, readonly)); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: the `pure` and `noreturn` options are mutually exclusive + --> $DIR/bad-options.rs:10:18 + | +LL | asm!("", options(pure, nomem, noreturn)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: asm with `pure` option must have at least one output + --> $DIR/bad-options.rs:10:18 + | +LL | asm!("", options(pure, nomem, noreturn)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: asm with `pure` option must have at least one output + --> $DIR/bad-options.rs:13:33 + | +LL | asm!("{}", in(reg) foo, options(pure, nomem)); + | ^^^^^^^^^^^^^^^^^^^^ + +error: asm outputs are not allowed with the `noreturn` option + --> $DIR/bad-options.rs:15:20 + | +LL | asm!("{}", out(reg) foo, options(noreturn)); + | ^^^^^^^^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/test/ui/asm/bad-reg.rs b/src/test/ui/asm/bad-reg.rs new file mode 100644 index 00000000000..ed7faa4b156 --- /dev/null +++ b/src/test/ui/asm/bad-reg.rs @@ -0,0 +1,57 @@ +// only-x86_64 +// compile-flags: -C target-feature=+avx2 + +#![feature(asm)] + +fn main() { + let mut foo = 0; + let mut bar = 0; + unsafe { + // Bad register/register class + + asm!("{}", in(foo) foo); + //~^ ERROR invalid register class `foo`: unknown register class + asm!("", in("foo") foo); + //~^ ERROR invalid register `foo`: unknown register + asm!("{:z}", in(reg) foo); + //~^ ERROR invalid asm template modifier for this register class + asm!("{:r}", in(xmm_reg) foo); + //~^ ERROR invalid asm template modifier for this register class + asm!("{:a}", const 0); + //~^ ERROR asm template modifiers are not allowed for `const` arguments + asm!("{:a}", sym main); + //~^ ERROR asm template modifiers are not allowed for `sym` arguments + asm!("{}", in(zmm_reg) foo); + //~^ ERROR register class `zmm_reg` requires the `avx512f` target feature + asm!("", in("zmm0") foo); + //~^ ERROR register class `zmm_reg` requires the `avx512f` target feature + asm!("", in("ah") foo); + //~^ ERROR invalid register `ah`: high byte registers are not currently supported + asm!("", in("ebp") foo); + //~^ ERROR invalid register `ebp`: the frame pointer cannot be used as an operand + asm!("", in("rsp") foo); + //~^ ERROR invalid register `rsp`: the stack pointer cannot be used as an operand + asm!("", in("ip") foo); + //~^ ERROR invalid register `ip`: the instruction pointer cannot be used as an operand + asm!("", in("st(2)") foo); + //~^ ERROR invalid register `st(2)`: x87 registers are not currently supported as operands + asm!("", in("mm0") foo); + //~^ ERROR invalid register `mm0`: MMX registers are not currently supported as operands + asm!("", in("k0") foo); + //~^ ERROR invalid register `k0`: the k0 AVX mask register cannot be used as an operand + + // Explicit register conflicts + // (except in/lateout which don't conflict) + + asm!("", in("eax") foo, in("al") bar); + //~^ ERROR register `ax` conflicts with register `ax` + asm!("", in("rax") foo, out("rax") bar); + //~^ ERROR register `ax` conflicts with register `ax` + asm!("", in("al") foo, lateout("al") bar); + asm!("", in("xmm0") foo, in("ymm0") bar); + //~^ ERROR register `ymm0` conflicts with register `xmm0` + asm!("", in("xmm0") foo, out("ymm0") bar); + //~^ ERROR register `ymm0` conflicts with register `xmm0` + asm!("", in("xmm0") foo, lateout("ymm0") bar); + } +} diff --git a/src/test/ui/asm/bad-reg.stderr b/src/test/ui/asm/bad-reg.stderr new file mode 100644 index 00000000000..a9d872dae41 --- /dev/null +++ b/src/test/ui/asm/bad-reg.stderr @@ -0,0 +1,148 @@ +error: invalid register class `foo`: unknown register class + --> $DIR/bad-reg.rs:12:20 + | +LL | asm!("{}", in(foo) foo); + | ^^^^^^^^^^^ + +error: invalid register `foo`: unknown register + --> $DIR/bad-reg.rs:14:18 + | +LL | asm!("", in("foo") foo); + | ^^^^^^^^^^^^^ + +error: invalid asm template modifier for this register class + --> $DIR/bad-reg.rs:16:15 + | +LL | asm!("{:z}", in(reg) foo); + | ^^^^ ----------- argument + | | + | template modifier + | + = note: the `reg` register class supports the following template modifiers: `l`, `h`, `x`, `e`, `r` + +error: invalid asm template modifier for this register class + --> $DIR/bad-reg.rs:18:15 + | +LL | asm!("{:r}", in(xmm_reg) foo); + | ^^^^ --------------- argument + | | + | template modifier + | + = note: the `xmm_reg` register class supports the following template modifiers: `x`, `y`, `z` + +error: asm template modifiers are not allowed for `const` arguments + --> $DIR/bad-reg.rs:20:15 + | +LL | asm!("{:a}", const 0); + | ^^^^ ------- argument + | | + | template modifier + +error: asm template modifiers are not allowed for `sym` arguments + --> $DIR/bad-reg.rs:22:15 + | +LL | asm!("{:a}", sym main); + | ^^^^ -------- argument + | | + | template modifier + +error: register class `zmm_reg` requires the `avx512f` target feature + --> $DIR/bad-reg.rs:24:20 + | +LL | asm!("{}", in(zmm_reg) foo); + | ^^^^^^^^^^^^^^^ + +error: register class `zmm_reg` requires the `avx512f` target feature + --> $DIR/bad-reg.rs:26:18 + | +LL | asm!("", in("zmm0") foo); + | ^^^^^^^^^^^^^^ + +error: invalid register `ah`: high byte registers are not currently supported as operands for inline asm + --> $DIR/bad-reg.rs:28:18 + | +LL | asm!("", in("ah") foo); + | ^^^^^^^^^^^^ + +error: invalid register `ebp`: the frame pointer cannot be used as an operand for inline asm + --> $DIR/bad-reg.rs:30:18 + | +LL | asm!("", in("ebp") foo); + | ^^^^^^^^^^^^^ + +error: invalid register `rsp`: the stack pointer cannot be used as an operand for inline asm + --> $DIR/bad-reg.rs:32:18 + | +LL | asm!("", in("rsp") foo); + | ^^^^^^^^^^^^^ + +error: invalid register `ip`: the instruction pointer cannot be used as an operand for inline asm + --> $DIR/bad-reg.rs:34:18 + | +LL | asm!("", in("ip") foo); + | ^^^^^^^^^^^^ + +error: invalid register `st(2)`: x87 registers are not currently supported as operands for inline asm + --> $DIR/bad-reg.rs:36:18 + | +LL | asm!("", in("st(2)") foo); + | ^^^^^^^^^^^^^^^ + +error: invalid register `mm0`: MMX registers are not currently supported as operands for inline asm + --> $DIR/bad-reg.rs:38:18 + | +LL | asm!("", in("mm0") foo); + | ^^^^^^^^^^^^^ + +error: invalid register `k0`: the k0 AVX mask register cannot be used as an operand for inline asm + --> $DIR/bad-reg.rs:40:18 + | +LL | asm!("", in("k0") foo); + | ^^^^^^^^^^^^ + +error: register `ax` conflicts with register `ax` + --> $DIR/bad-reg.rs:46:33 + | +LL | asm!("", in("eax") foo, in("al") bar); + | ------------- ^^^^^^^^^^^^ register `ax` + | | + | register `ax` + +error: register `ax` conflicts with register `ax` + --> $DIR/bad-reg.rs:48:33 + | +LL | asm!("", in("rax") foo, out("rax") bar); + | ------------- ^^^^^^^^^^^^^^ register `ax` + | | + | register `ax` + | +help: use `lateout` instead of `out` to avoid conflict + --> $DIR/bad-reg.rs:48:18 + | +LL | asm!("", in("rax") foo, out("rax") bar); + | ^^^^^^^^^^^^^ + +error: register `ymm0` conflicts with register `xmm0` + --> $DIR/bad-reg.rs:51:34 + | +LL | asm!("", in("xmm0") foo, in("ymm0") bar); + | -------------- ^^^^^^^^^^^^^^ register `ymm0` + | | + | register `xmm0` + +error: register `ymm0` conflicts with register `xmm0` + --> $DIR/bad-reg.rs:53:34 + | +LL | asm!("", in("xmm0") foo, out("ymm0") bar); + | -------------- ^^^^^^^^^^^^^^^ register `ymm0` + | | + | register `xmm0` + | +help: use `lateout` instead of `out` to avoid conflict + --> $DIR/bad-reg.rs:53:18 + | +LL | asm!("", in("xmm0") foo, out("ymm0") bar); + | ^^^^^^^^^^^^^^ + +error: aborting due to 19 previous errors + diff --git a/src/test/ui/asm/bad-template.rs b/src/test/ui/asm/bad-template.rs new file mode 100644 index 00000000000..0b333eca1ab --- /dev/null +++ b/src/test/ui/asm/bad-template.rs @@ -0,0 +1,26 @@ +// only-x86_64 + +#![feature(asm)] + +fn main() { + let mut foo = 0; + unsafe { + asm!("{}"); + //~^ ERROR invalid reference to argument at index 0 + asm!("{1}", in(reg) foo); + //~^ ERROR invalid reference to argument at index 1 + //~^^ ERROR argument never used + asm!("{a}"); + //~^ ERROR there is no argument named `a` + asm!("{}", a = in(reg) foo); + //~^ ERROR invalid reference to argument at index 0 + //~^^ ERROR argument never used + asm!("{1}", a = in(reg) foo); + //~^ ERROR invalid reference to argument at index 1 + //~^^ ERROR named argument never used + asm!("{}", in("eax") foo); + //~^ ERROR invalid reference to argument at index 0 + asm!("{:foo}", in(reg) foo); + //~^ ERROR asm template modifier must be a single character + } +} diff --git a/src/test/ui/asm/bad-template.stderr b/src/test/ui/asm/bad-template.stderr new file mode 100644 index 00000000000..2de76ef8241 --- /dev/null +++ b/src/test/ui/asm/bad-template.stderr @@ -0,0 +1,86 @@ +error: invalid reference to argument at index 0 + --> $DIR/bad-template.rs:8:15 + | +LL | asm!("{}"); + | ^^ from here + | + = note: no arguments were given + +error: invalid reference to argument at index 1 + --> $DIR/bad-template.rs:10:15 + | +LL | asm!("{1}", in(reg) foo); + | ^^^ from here + | + = note: there is 1 argument + +error: argument never used + --> $DIR/bad-template.rs:10:21 + | +LL | asm!("{1}", in(reg) foo); + | ^^^^^^^^^^^ argument never used + +error: there is no argument named `a` + --> $DIR/bad-template.rs:13:15 + | +LL | asm!("{a}"); + | ^^^ + +error: invalid reference to argument at index 0 + --> $DIR/bad-template.rs:15:15 + | +LL | asm!("{}", a = in(reg) foo); + | ^^ --------------- named argument + | | + | from here + | + = note: no positional arguments were given +note: named arguments cannot be referenced by position + --> $DIR/bad-template.rs:15:20 + | +LL | asm!("{}", a = in(reg) foo); + | ^^^^^^^^^^^^^^^ + +error: named argument never used + --> $DIR/bad-template.rs:15:20 + | +LL | asm!("{}", a = in(reg) foo); + | ^^^^^^^^^^^^^^^ named argument never used + +error: invalid reference to argument at index 1 + --> $DIR/bad-template.rs:18:15 + | +LL | asm!("{1}", a = in(reg) foo); + | ^^^ from here + | + = note: no positional arguments were given + +error: named argument never used + --> $DIR/bad-template.rs:18:21 + | +LL | asm!("{1}", a = in(reg) foo); + | ^^^^^^^^^^^^^^^ named argument never used + +error: invalid reference to argument at index 0 + --> $DIR/bad-template.rs:21:15 + | +LL | asm!("{}", in("eax") foo); + | ^^ ------------- explicit register argument + | | + | from here + | + = note: no positional arguments were given +note: explicit register arguments cannot be used in the asm template + --> $DIR/bad-template.rs:21:20 + | +LL | asm!("{}", in("eax") foo); + | ^^^^^^^^^^^^^ + +error: asm template modifier must be a single character + --> $DIR/bad-template.rs:23:17 + | +LL | asm!("{:foo}", in(reg) foo); + | ^^^ + +error: aborting due to 10 previous errors + diff --git a/src/test/ui/asm/noreturn.rs b/src/test/ui/asm/noreturn.rs new file mode 100644 index 00000000000..5e1ee93bfb0 --- /dev/null +++ b/src/test/ui/asm/noreturn.rs @@ -0,0 +1,17 @@ +// only-x86_64 +// check-pass + +#![feature(asm, never_type)] +#![crate_type = "rlib"] + +pub unsafe fn asm1() { + let _: () = asm!(""); +} + +pub unsafe fn asm2() { + let _: ! = asm!("", options(noreturn)); +} + +pub unsafe fn asm3() -> ! { + asm!("", options(noreturn)); +} diff --git a/src/test/ui/asm/parse-error.rs b/src/test/ui/asm/parse-error.rs new file mode 100644 index 00000000000..e6566866b22 --- /dev/null +++ b/src/test/ui/asm/parse-error.rs @@ -0,0 +1,53 @@ +// only-x86_64 + +#![feature(asm)] + +fn main() { + let mut foo = 0; + let mut bar = 0; + unsafe { + asm!(); + //~^ ERROR requires at least a template string argument + asm!(foo); + //~^ ERROR asm template must be a string literal + asm!("{}" foo); + //~^ ERROR expected token: `,` + asm!("{}", foo); + //~^ ERROR expected one of + asm!("{}", in foo); + //~^ ERROR expected `(`, found `foo` + asm!("{}", in(reg foo)); + //~^ ERROR expected `)`, found `foo` + asm!("{}", in(reg)); + //~^ ERROR expected expression, found end of macro arguments + asm!("{}", inout(=) foo => bar); + //~^ ERROR expected register class or explicit register + asm!("{}", inout(reg) foo =>); + //~^ ERROR expected expression, found end of macro arguments + asm!("{}", in(reg) foo => bar); + //~^ ERROR expected one of `!`, `,`, `.`, `::`, `?`, `{`, or an operator, found `=>` + asm!("{}", sym foo + bar); + //~^ ERROR argument to `sym` must be a path expression + asm!("", options(foo)); + //~^ ERROR expected one of + asm!("", options(nomem foo)); + //~^ ERROR expected one of + asm!("", options(nomem, foo)); + //~^ ERROR expected one of + asm!("", options(), options()); + //~^ ERROR asm options cannot be specified twice + asm!("{}", options(), const foo); + //~^ ERROR arguments are not allowed after options + asm!("{a}", a = const foo, a = const bar); + //~^ ERROR duplicate argument named `a` + //~^^ ERROR argument never used + asm!("", a = in("eax") foo); + //~^ ERROR explicit register arguments cannot have names + asm!("{a}", in("eax") foo, a = const bar); + //~^ ERROR named arguments cannot follow explicit register arguments + asm!("{a}", in("eax") foo, a = const bar); + //~^ ERROR named arguments cannot follow explicit register arguments + asm!("{1}", in("eax") foo, const bar); + //~^ ERROR positional arguments cannot follow named arguments or explicit register arguments + } +} diff --git a/src/test/ui/asm/parse-error.stderr b/src/test/ui/asm/parse-error.stderr new file mode 100644 index 00000000000..a927ce13858 --- /dev/null +++ b/src/test/ui/asm/parse-error.stderr @@ -0,0 +1,146 @@ +error: requires at least a template string argument + --> $DIR/parse-error.rs:9:9 + | +LL | asm!(); + | ^^^^^^^ + +error: asm template must be a string literal + --> $DIR/parse-error.rs:11:14 + | +LL | asm!(foo); + | ^^^ + +error: expected token: `,` + --> $DIR/parse-error.rs:13:19 + | +LL | asm!("{}" foo); + | ^^^ expected `,` + +error: expected one of `const`, `in`, `inlateout`, `inout`, `lateout`, `options`, `out`, or `sym`, found `foo` + --> $DIR/parse-error.rs:15:20 + | +LL | asm!("{}", foo); + | ^^^ expected one of 8 possible tokens + +error: expected `(`, found `foo` + --> $DIR/parse-error.rs:17:23 + | +LL | asm!("{}", in foo); + | ^^^ expected `(` + +error: expected `)`, found `foo` + --> $DIR/parse-error.rs:19:27 + | +LL | asm!("{}", in(reg foo)); + | ^^^ expected `)` + +error: expected expression, found end of macro arguments + --> $DIR/parse-error.rs:21:27 + | +LL | asm!("{}", in(reg)); + | ^ expected expression + +error: expected register class or explicit register + --> $DIR/parse-error.rs:23:26 + | +LL | asm!("{}", inout(=) foo => bar); + | ^ + +error: expected expression, found end of macro arguments + --> $DIR/parse-error.rs:25:37 + | +LL | asm!("{}", inout(reg) foo =>); + | ^ expected expression + +error: expected one of `!`, `,`, `.`, `::`, `?`, `{`, or an operator, found `=>` + --> $DIR/parse-error.rs:27:32 + | +LL | asm!("{}", in(reg) foo => bar); + | ^^ expected one of 7 possible tokens + +error: argument to `sym` must be a path expression + --> $DIR/parse-error.rs:29:24 + | +LL | asm!("{}", sym foo + bar); + | ^^^^^^^^^ + +error: expected one of `)`, `nomem`, `noreturn`, `nostack`, `preserves_flags`, `pure`, or `readonly`, found `foo` + --> $DIR/parse-error.rs:31:26 + | +LL | asm!("", options(foo)); + | ^^^ expected one of 7 possible tokens + +error: expected one of `)` or `,`, found `foo` + --> $DIR/parse-error.rs:33:32 + | +LL | asm!("", options(nomem foo)); + | ^^^ expected one of `)` or `,` + +error: expected one of `)`, `nomem`, `noreturn`, `nostack`, `preserves_flags`, `pure`, or `readonly`, found `foo` + --> $DIR/parse-error.rs:35:33 + | +LL | asm!("", options(nomem, foo)); + | ^^^ expected one of 7 possible tokens + +error: asm options cannot be specified twice + --> $DIR/parse-error.rs:37:29 + | +LL | asm!("", options(), options()); + | --------- ^^^^^^^^^ duplicate options + | | + | previously here + +error: arguments are not allowed after options + --> $DIR/parse-error.rs:39:31 + | +LL | asm!("{}", options(), const foo); + | --------- ^^^^^^^^^ argument + | | + | previous options + +error: duplicate argument named `a` + --> $DIR/parse-error.rs:41:36 + | +LL | asm!("{a}", a = const foo, a = const bar); + | ------------- ^^^^^^^^^^^^^ duplicate argument + | | + | previously here + +error: argument never used + --> $DIR/parse-error.rs:41:36 + | +LL | asm!("{a}", a = const foo, a = const bar); + | ^^^^^^^^^^^^^ argument never used + +error: explicit register arguments cannot have names + --> $DIR/parse-error.rs:44:18 + | +LL | asm!("", a = in("eax") foo); + | ^^^^^^^^^^^^^^^^^ + +error: named arguments cannot follow explicit register arguments + --> $DIR/parse-error.rs:46:36 + | +LL | asm!("{a}", in("eax") foo, a = const bar); + | ------------- ^^^^^^^^^^^^^ named argument + | | + | explicit register argument + +error: named arguments cannot follow explicit register arguments + --> $DIR/parse-error.rs:48:36 + | +LL | asm!("{a}", in("eax") foo, a = const bar); + | ------------- ^^^^^^^^^^^^^ named argument + | | + | explicit register argument + +error: positional arguments cannot follow named arguments or explicit register arguments + --> $DIR/parse-error.rs:50:36 + | +LL | asm!("{1}", in("eax") foo, const bar); + | ------------- ^^^^^^^^^ positional argument + | | + | explicit register argument + +error: aborting due to 22 previous errors + diff --git a/src/test/ui/asm/rustfix-asm.fixed b/src/test/ui/asm/rustfix-asm.fixed new file mode 100644 index 00000000000..c9271059810 --- /dev/null +++ b/src/test/ui/asm/rustfix-asm.fixed @@ -0,0 +1,16 @@ +// run-rustfix +// only-x86_64 + +#![feature(asm, llvm_asm)] + +fn main() { + unsafe { + let x = 1; + let y: i32; + llvm_asm!("" :: "r" (x)); + //~^ ERROR legacy asm! syntax is no longer supported + llvm_asm!("" : "=r" (y)); + //~^ ERROR legacy asm! syntax is no longer supported + let _ = y; + } +} diff --git a/src/test/ui/asm/rustfix-asm.rs b/src/test/ui/asm/rustfix-asm.rs new file mode 100644 index 00000000000..a108595ca1b --- /dev/null +++ b/src/test/ui/asm/rustfix-asm.rs @@ -0,0 +1,16 @@ +// run-rustfix +// only-x86_64 + +#![feature(asm, llvm_asm)] + +fn main() { + unsafe { + let x = 1; + let y: i32; + asm!("" :: "r" (x)); + //~^ ERROR legacy asm! syntax is no longer supported + asm!("" : "=r" (y)); + //~^ ERROR legacy asm! syntax is no longer supported + let _ = y; + } +} diff --git a/src/test/ui/asm/rustfix-asm.stderr b/src/test/ui/asm/rustfix-asm.stderr new file mode 100644 index 00000000000..28675b51d15 --- /dev/null +++ b/src/test/ui/asm/rustfix-asm.stderr @@ -0,0 +1,18 @@ +error: legacy asm! syntax is no longer supported + --> $DIR/rustfix-asm.rs:10:9 + | +LL | asm!("" :: "r" (x)); + | ----^^^^^^^^^^^^^^^^ + | | + | help: replace with: `llvm_asm!` + +error: legacy asm! syntax is no longer supported + --> $DIR/rustfix-asm.rs:12:9 + | +LL | asm!("" : "=r" (y)); + | ----^^^^^^^^^^^^^^^^ + | | + | help: replace with: `llvm_asm!` + +error: aborting due to 2 previous errors + diff --git a/src/test/ui/asm/type-check-1.rs b/src/test/ui/asm/type-check-1.rs new file mode 100644 index 00000000000..7880382c3b7 --- /dev/null +++ b/src/test/ui/asm/type-check-1.rs @@ -0,0 +1,25 @@ +// only-x86_64 + +#![feature(asm)] + +fn main() { + unsafe { + // Outputs must be place expressions + + asm!("{}", in(reg) 1 + 2); + asm!("{}", out(reg) 1 + 2); + //~^ ERROR invalid asm output + asm!("{}", inout(reg) 1 + 2); + //~^ ERROR invalid asm output + + // Operands must be sized + + let v: [u64; 3] = [0, 1, 2]; + asm!("{}", in(reg) v[..]); + //~^ ERROR the size for values of type `[u64]` cannot be known at compilation time + asm!("{}", out(reg) v[..]); + //~^ ERROR the size for values of type `[u64]` cannot be known at compilation time + asm!("{}", inout(reg) v[..]); + //~^ ERROR the size for values of type `[u64]` cannot be known at compilation time + } +} diff --git a/src/test/ui/asm/type-check-1.stderr b/src/test/ui/asm/type-check-1.stderr new file mode 100644 index 00000000000..7c9c041f457 --- /dev/null +++ b/src/test/ui/asm/type-check-1.stderr @@ -0,0 +1,45 @@ +error: invalid asm output + --> $DIR/type-check-1.rs:10:29 + | +LL | asm!("{}", out(reg) 1 + 2); + | ^^^^^ cannot assign to this expression + +error: invalid asm output + --> $DIR/type-check-1.rs:12:31 + | +LL | asm!("{}", inout(reg) 1 + 2); + | ^^^^^ cannot assign to this expression + +error[E0277]: the size for values of type `[u64]` cannot be known at compilation time + --> $DIR/type-check-1.rs:18:28 + | +LL | asm!("{}", in(reg) v[..]); + | ^^^^^ doesn't have a size known at compile-time + | + = help: the trait `std::marker::Sized` is not implemented for `[u64]` + = note: to learn more, visit + = note: all inline asm arguments must have a statically known size + +error[E0277]: the size for values of type `[u64]` cannot be known at compilation time + --> $DIR/type-check-1.rs:20:29 + | +LL | asm!("{}", out(reg) v[..]); + | ^^^^^ doesn't have a size known at compile-time + | + = help: the trait `std::marker::Sized` is not implemented for `[u64]` + = note: to learn more, visit + = note: all inline asm arguments must have a statically known size + +error[E0277]: the size for values of type `[u64]` cannot be known at compilation time + --> $DIR/type-check-1.rs:22:31 + | +LL | asm!("{}", inout(reg) v[..]); + | ^^^^^ doesn't have a size known at compile-time + | + = help: the trait `std::marker::Sized` is not implemented for `[u64]` + = note: to learn more, visit + = note: all inline asm arguments must have a statically known size + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/src/test/ui/asm/type-check-2.rs b/src/test/ui/asm/type-check-2.rs new file mode 100644 index 00000000000..1652e9e4c9f --- /dev/null +++ b/src/test/ui/asm/type-check-2.rs @@ -0,0 +1,104 @@ +// only-x86_64 + +#![feature(asm, repr_simd, never_type)] + +#[repr(simd)] +struct SimdNonCopy(f32, f32, f32, f32); + +fn main() { + unsafe { + // Inputs must be initialized + + let x: u64; + asm!("{}", in(reg) x); + //~^ ERROR use of possibly-uninitialized variable: `x` + let mut y: u64; + asm!("{}", inout(reg) y); + //~^ ERROR use of possibly-uninitialized variable: `y` + let _ = y; + + // Outputs require mutable places + + let v: Vec = vec![0, 1, 2]; + asm!("{}", in(reg) v[0]); + asm!("{}", out(reg) v[0]); + //~^ ERROR cannot borrow `v` as mutable, as it is not declared as mutable + asm!("{}", inout(reg) v[0]); + //~^ ERROR cannot borrow `v` as mutable, as it is not declared as mutable + + // Const operands must be integer or floats, and must be constants. + + let x = 0; + const C: i32 = 0; + const fn const_foo(x: i32) -> i32 { + x + } + const fn const_bar(x: T) -> T { + x + } + asm!("{}", const 0i32); + asm!("{}", const 0f32); + asm!("{}", const 0 as *mut u8); + //~^ ERROR asm `const` arguments must be integer or floating-point values + asm!("{}", const &0); + //~^ ERROR asm `const` arguments must be integer or floating-point values + asm!("{}", const x); + //~^ ERROR argument 1 is required to be a constant + asm!("{}", const const_foo(0)); + asm!("{}", const const_foo(x)); + //~^ ERROR argument 1 is required to be a constant + asm!("{}", const const_bar(0)); + asm!("{}", const const_bar(x)); + //~^ ERROR argument 1 is required to be a constant + + // Sym operands must point to a function or static + + static S: i32 = 0; + asm!("{}", sym S); + asm!("{}", sym main); + asm!("{}", sym C); + //~^ ERROR asm `sym` operand must point to a fn or static + asm!("{}", sym x); + //~^ ERROR asm `sym` operand must point to a fn or static + + // Register operands must be Copy + + asm!("{}", in(xmm_reg) SimdNonCopy(0.0, 0.0, 0.0, 0.0)); + //~^ ERROR arguments for inline assembly must be copyable + + // Register operands must be integers, floats, SIMD vectors, pointers or + // function pointers. + + asm!("{}", in(reg) 0i64); + asm!("{}", in(reg) 0f64); + asm!("{}", in(xmm_reg) std::arch::x86_64::_mm_setzero_ps()); + asm!("{}", in(reg) 0 as *const u8); + asm!("{}", in(reg) 0 as *mut u8); + asm!("{}", in(reg) main as fn()); + asm!("{}", in(reg) |x: i32| x); + //~^ ERROR cannot use value of type + asm!("{}", in(reg) vec![0]); + //~^ ERROR cannot use value of type `std::vec::Vec` for inline assembly + asm!("{}", in(reg) (1, 2, 3)); + //~^ ERROR cannot use value of type `(i32, i32, i32)` for inline assembly + asm!("{}", in(reg) [1, 2, 3]); + //~^ ERROR cannot use value of type `[i32; 3]` for inline assembly + + // Register inputs (but not outputs) allow references and function types + + let mut f = main; + let mut r = &mut 0; + asm!("{}", in(reg) f); + asm!("{}", inout(reg) f); + //~^ ERROR cannot use value of type `fn() {main}` for inline assembly + asm!("{}", in(reg) r); + asm!("{}", inout(reg) r); + //~^ ERROR cannot use value of type `&mut i32` for inline assembly + let _ = (f, r); + + // Type checks ignore never type + + let u: ! = unreachable!(); + asm!("{}", in(reg) u); + } +} diff --git a/src/test/ui/asm/type-check-2.stderr b/src/test/ui/asm/type-check-2.stderr new file mode 100644 index 00000000000..dc7949534f1 --- /dev/null +++ b/src/test/ui/asm/type-check-2.stderr @@ -0,0 +1,133 @@ +error: asm `const` arguments must be integer or floating-point values + --> $DIR/type-check-2.rs:41:26 + | +LL | asm!("{}", const 0 as *mut u8); + | ^^^^^^^^^^^^ + +error: asm `const` arguments must be integer or floating-point values + --> $DIR/type-check-2.rs:43:26 + | +LL | asm!("{}", const &0); + | ^^ + +error: arguments for inline assembly must be copyable + --> $DIR/type-check-2.rs:66:32 + | +LL | asm!("{}", in(xmm_reg) SimdNonCopy(0.0, 0.0, 0.0, 0.0)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `SimdNonCopy` does not implement the Copy trait + +error: cannot use value of type `[closure@$DIR/type-check-2.rs:78:28: 78:38]` for inline assembly + --> $DIR/type-check-2.rs:78:28 + | +LL | asm!("{}", in(reg) |x: i32| x); + | ^^^^^^^^^^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + +error: cannot use value of type `std::vec::Vec` for inline assembly + --> $DIR/type-check-2.rs:80:28 + | +LL | asm!("{}", in(reg) vec![0]); + | ^^^^^^^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot use value of type `(i32, i32, i32)` for inline assembly + --> $DIR/type-check-2.rs:82:28 + | +LL | asm!("{}", in(reg) (1, 2, 3)); + | ^^^^^^^^^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + +error: cannot use value of type `[i32; 3]` for inline assembly + --> $DIR/type-check-2.rs:84:28 + | +LL | asm!("{}", in(reg) [1, 2, 3]); + | ^^^^^^^^^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + +error: cannot use value of type `fn() {main}` for inline assembly + --> $DIR/type-check-2.rs:92:31 + | +LL | asm!("{}", inout(reg) f); + | ^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + +error: cannot use value of type `&mut i32` for inline assembly + --> $DIR/type-check-2.rs:95:31 + | +LL | asm!("{}", inout(reg) r); + | ^ + | + = note: only integers, floats, SIMD vectors, pointers and function pointers can be used as arguments for inline assembly + +error: asm `sym` operand must point to a fn or static + --> $DIR/type-check-2.rs:59:24 + | +LL | asm!("{}", sym C); + | ^ + +error: asm `sym` operand must point to a fn or static + --> $DIR/type-check-2.rs:61:24 + | +LL | asm!("{}", sym x); + | ^ + +error: argument 1 is required to be a constant + --> $DIR/type-check-2.rs:45:9 + | +LL | asm!("{}", const x); + | ^^^^^^^^^^^^^^^^^^^^ + +error: argument 1 is required to be a constant + --> $DIR/type-check-2.rs:48:9 + | +LL | asm!("{}", const const_foo(x)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: argument 1 is required to be a constant + --> $DIR/type-check-2.rs:51:9 + | +LL | asm!("{}", const const_bar(x)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0381]: use of possibly-uninitialized variable: `x` + --> $DIR/type-check-2.rs:13:28 + | +LL | asm!("{}", in(reg) x); + | ^ use of possibly-uninitialized `x` + +error[E0381]: use of possibly-uninitialized variable: `y` + --> $DIR/type-check-2.rs:16:9 + | +LL | asm!("{}", inout(reg) y); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ use of possibly-uninitialized `y` + +error[E0596]: cannot borrow `v` as mutable, as it is not declared as mutable + --> $DIR/type-check-2.rs:24:29 + | +LL | let v: Vec = vec![0, 1, 2]; + | - help: consider changing this to be mutable: `mut v` +LL | asm!("{}", in(reg) v[0]); +LL | asm!("{}", out(reg) v[0]); + | ^ cannot borrow as mutable + +error[E0596]: cannot borrow `v` as mutable, as it is not declared as mutable + --> $DIR/type-check-2.rs:26:31 + | +LL | let v: Vec = vec![0, 1, 2]; + | - help: consider changing this to be mutable: `mut v` +... +LL | asm!("{}", inout(reg) v[0]); + | ^ cannot borrow as mutable + +error: aborting due to 18 previous errors + +Some errors have detailed explanations: E0381, E0596. +For more information about an error, try `rustc --explain E0381`. diff --git a/src/test/ui/asm/type-check-3.rs b/src/test/ui/asm/type-check-3.rs new file mode 100644 index 00000000000..750d28026d7 --- /dev/null +++ b/src/test/ui/asm/type-check-3.rs @@ -0,0 +1,68 @@ +// only-x86_64 +// compile-flags: -C target-feature=+avx512f + +#![feature(asm)] + +use std::arch::x86_64::{_mm256_setzero_ps, _mm_setzero_ps}; + +fn main() { + unsafe { + // Types must be in the whitelist for the register class + + asm!("{}", in(reg) 0i128); + //~^ ERROR type `i128` cannot be used with this register class + asm!("{}", in(reg) _mm_setzero_ps()); + //~^ ERROR type `std::arch::x86_64::__m128` cannot be used with this register class + asm!("{}", in(reg) _mm256_setzero_ps()); + //~^ ERROR type `std::arch::x86_64::__m256` cannot be used with this register class + asm!("{}", in(xmm_reg) 0u8); + //~^ ERROR type `u8` cannot be used with this register class + asm!("{:e}", in(reg) 0i32); + asm!("{}", in(xmm_reg) 0i32); + asm!("{:e}", in(reg) 0f32); + asm!("{}", in(xmm_reg) 0f32); + asm!("{}", in(xmm_reg) _mm_setzero_ps()); + asm!("{:x}", in(ymm_reg) _mm_setzero_ps()); + asm!("{}", in(kreg) 0u16); + asm!("{}", in(kreg) 0u64); + //~^ ERROR `avx512bw` target feature is not enabled + + // Template modifier suggestions for sub-registers + + asm!("{0} {0}", in(reg) 0i8); + //~^ WARN formatting may not be suitable for sub-register argument + asm!("{0} {0:x}", in(reg) 0i16); + //~^ WARN formatting may not be suitable for sub-register argument + asm!("{}", in(reg) 0i32); + //~^ WARN formatting may not be suitable for sub-register argument + asm!("{}", in(reg) 0i64); + asm!("{}", in(ymm_reg) 0i64); + //~^ WARN formatting may not be suitable for sub-register argument + asm!("{}", in(ymm_reg) _mm256_setzero_ps()); + asm!("{:l}", in(reg) 0i8); + asm!("{:l}", in(reg) 0i16); + asm!("{:l}", in(reg) 0i32); + asm!("{:l}", in(reg) 0i64); + asm!("{:x}", in(ymm_reg) 0i64); + asm!("{:x}", in(ymm_reg) _mm256_setzero_ps()); + + // Split inout operands must have compatible types + + let mut val_i8: i8; + let mut val_f32: f32; + let mut val_u32: u32; + let mut val_u64: u64; + let mut val_ptr: *mut u8; + asm!("{:r}", inout(reg) 0u8 => val_i8); + asm!("{:r}", inout(reg) 0u16 => val_i8); + //~^ ERROR incompatible types for asm inout argument + asm!("{:r}", inout(reg) 0u32 => val_f32); + //~^ ERROR incompatible types for asm inout argument + asm!("{:r}", inout(reg) 0u32 => val_ptr); + //~^ ERROR incompatible types for asm inout argument + asm!("{:r}", inout(reg) main => val_u32); + //~^ ERROR incompatible types for asm inout argument + asm!("{:r}", inout(reg) 0u64 => val_ptr); + asm!("{:r}", inout(reg) main => val_u64); + } +} diff --git a/src/test/ui/asm/type-check-3.stderr b/src/test/ui/asm/type-check-3.stderr new file mode 100644 index 00000000000..e4018ca1d42 --- /dev/null +++ b/src/test/ui/asm/type-check-3.stderr @@ -0,0 +1,123 @@ +error: type `i128` cannot be used with this register class + --> $DIR/type-check-3.rs:12:28 + | +LL | asm!("{}", in(reg) 0i128); + | ^^^^^ + | + = note: register class `reg` supports these types: i8, i16, i32, i64, f32, f64 + +error: type `std::arch::x86_64::__m128` cannot be used with this register class + --> $DIR/type-check-3.rs:14:28 + | +LL | asm!("{}", in(reg) _mm_setzero_ps()); + | ^^^^^^^^^^^^^^^^ + | + = note: register class `reg` supports these types: i8, i16, i32, i64, f32, f64 + +error: type `std::arch::x86_64::__m256` cannot be used with this register class + --> $DIR/type-check-3.rs:16:28 + | +LL | asm!("{}", in(reg) _mm256_setzero_ps()); + | ^^^^^^^^^^^^^^^^^^^ + | + = note: register class `reg` supports these types: i8, i16, i32, i64, f32, f64 + +error: type `u8` cannot be used with this register class + --> $DIR/type-check-3.rs:18:32 + | +LL | asm!("{}", in(xmm_reg) 0u8); + | ^^^ + | + = note: register class `xmm_reg` supports these types: i32, i64, f32, f64, i8x16, i16x8, i32x4, i64x2, f32x4, f64x2 + +error: `avx512bw` target feature is not enabled + --> $DIR/type-check-3.rs:27:29 + | +LL | asm!("{}", in(kreg) 0u64); + | ^^^^ + | + = note: this is required to use type `u64` with register class `kreg` + +warning: formatting may not be suitable for sub-register argument + --> $DIR/type-check-3.rs:32:15 + | +LL | asm!("{0} {0}", in(reg) 0i8); + | ^^^ ^^^ --- for this argument + | + = note: `#[warn(asm_sub_register)]` on by default + = help: use the `l` modifier to have the register formatted as `al` + = help: or use the `r` modifier to keep the default formatting of `rax` + +warning: formatting may not be suitable for sub-register argument + --> $DIR/type-check-3.rs:34:15 + | +LL | asm!("{0} {0:x}", in(reg) 0i16); + | ^^^ ---- for this argument + | + = help: use the `x` modifier to have the register formatted as `ax` + = help: or use the `r` modifier to keep the default formatting of `rax` + +warning: formatting may not be suitable for sub-register argument + --> $DIR/type-check-3.rs:36:15 + | +LL | asm!("{}", in(reg) 0i32); + | ^^ ---- for this argument + | + = help: use the `e` modifier to have the register formatted as `eax` + = help: or use the `r` modifier to keep the default formatting of `rax` + +warning: formatting may not be suitable for sub-register argument + --> $DIR/type-check-3.rs:39:15 + | +LL | asm!("{}", in(ymm_reg) 0i64); + | ^^ ---- for this argument + | + = help: use the `x` modifier to have the register formatted as `xmm0` + = help: or use the `y` modifier to keep the default formatting of `ymm0` + +error: incompatible types for asm inout argument + --> $DIR/type-check-3.rs:57:33 + | +LL | asm!("{:r}", inout(reg) 0u16 => val_i8); + | ^^^^ ^^^^^^ type `i8` + | | + | type `u16` + | + = note: asm inout arguments must have the same type + = note: unless they are both pointers or integers of the same size + +error: incompatible types for asm inout argument + --> $DIR/type-check-3.rs:59:33 + | +LL | asm!("{:r}", inout(reg) 0u32 => val_f32); + | ^^^^ ^^^^^^^ type `f32` + | | + | type `u32` + | + = note: asm inout arguments must have the same type + = note: unless they are both pointers or integers of the same size + +error: incompatible types for asm inout argument + --> $DIR/type-check-3.rs:61:33 + | +LL | asm!("{:r}", inout(reg) 0u32 => val_ptr); + | ^^^^ ^^^^^^^ type `*mut u8` + | | + | type `u32` + | + = note: asm inout arguments must have the same type + = note: unless they are both pointers or integers of the same size + +error: incompatible types for asm inout argument + --> $DIR/type-check-3.rs:63:33 + | +LL | asm!("{:r}", inout(reg) main => val_u32); + | ^^^^ ^^^^^^^ type `u32` + | | + | type `fn()` + | + = note: asm inout arguments must have the same type + = note: unless they are both pointers or integers of the same size + +error: aborting due to 9 previous errors; 4 warnings emitted + diff --git a/src/test/ui/feature-gates/feature-gate-asm.rs b/src/test/ui/feature-gates/feature-gate-asm.rs index 70f5845550d..7eeeb4bc4e2 100644 --- a/src/test/ui/feature-gates/feature-gate-asm.rs +++ b/src/test/ui/feature-gates/feature-gate-asm.rs @@ -3,7 +3,6 @@ fn main() { unsafe { asm!(""); //~ ERROR inline assembly is not stable enough - //~^ WARN use of deprecated item 'asm' llvm_asm!(""); //~ ERROR inline assembly is not stable enough } } diff --git a/src/test/ui/feature-gates/feature-gate-asm.stderr b/src/test/ui/feature-gates/feature-gate-asm.stderr index 9d4d7b53955..1f9eaa5632e 100644 --- a/src/test/ui/feature-gates/feature-gate-asm.stderr +++ b/src/test/ui/feature-gates/feature-gate-asm.stderr @@ -8,7 +8,7 @@ LL | asm!(""); = help: add `#![feature(asm)]` to the crate attributes to enable error[E0658]: use of unstable library feature 'llvm_asm': inline assembly is not stable enough for use and is subject to change - --> $DIR/feature-gate-asm.rs:7:9 + --> $DIR/feature-gate-asm.rs:6:9 | LL | llvm_asm!(""); | ^^^^^^^^ @@ -16,14 +16,6 @@ LL | llvm_asm!(""); = note: see issue #70173 for more information = help: add `#![feature(llvm_asm)]` to the crate attributes to enable -warning: use of deprecated item 'asm': the syntax of asm! will change soon, use llvm_asm! to avoid breakage - --> $DIR/feature-gate-asm.rs:5:9 - | -LL | asm!(""); - | ^^^ help: replace the use of the deprecated item: `llvm_asm` - | - = note: `#[warn(deprecated)]` on by default - -error: aborting due to 2 previous errors; 1 warning emitted +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/feature-gates/feature-gate-asm2.rs b/src/test/ui/feature-gates/feature-gate-asm2.rs index e3e86592a48..666a4894f62 100644 --- a/src/test/ui/feature-gates/feature-gate-asm2.rs +++ b/src/test/ui/feature-gates/feature-gate-asm2.rs @@ -3,7 +3,6 @@ fn main() { unsafe { println!("{:?}", asm!("")); //~ ERROR inline assembly is not stable - //~^ WARN use of deprecated item 'asm' println!("{:?}", llvm_asm!("")); //~ ERROR inline assembly is not stable } } diff --git a/src/test/ui/feature-gates/feature-gate-asm2.stderr b/src/test/ui/feature-gates/feature-gate-asm2.stderr index a3c8116d6b1..17ba66e9842 100644 --- a/src/test/ui/feature-gates/feature-gate-asm2.stderr +++ b/src/test/ui/feature-gates/feature-gate-asm2.stderr @@ -8,7 +8,7 @@ LL | println!("{:?}", asm!("")); = help: add `#![feature(asm)]` to the crate attributes to enable error[E0658]: use of unstable library feature 'llvm_asm': inline assembly is not stable enough for use and is subject to change - --> $DIR/feature-gate-asm2.rs:7:26 + --> $DIR/feature-gate-asm2.rs:6:26 | LL | println!("{:?}", llvm_asm!("")); | ^^^^^^^^ @@ -16,14 +16,6 @@ LL | println!("{:?}", llvm_asm!("")); = note: see issue #70173 for more information = help: add `#![feature(llvm_asm)]` to the crate attributes to enable -warning: use of deprecated item 'asm': the syntax of asm! will change soon, use llvm_asm! to avoid breakage - --> $DIR/feature-gate-asm2.rs:5:26 - | -LL | println!("{:?}", asm!("")); - | ^^^ help: replace the use of the deprecated item: `llvm_asm` - | - = note: `#[warn(deprecated)]` on by default - -error: aborting due to 2 previous errors; 1 warning emitted +error: aborting due to 2 previous errors For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/asm-concat-src.rs b/src/test/ui/llvm-asm/llvm-asm-concat-src.rs similarity index 100% rename from src/test/ui/asm-concat-src.rs rename to src/test/ui/llvm-asm/llvm-asm-concat-src.rs diff --git a/src/test/ui/asm-in-moved.rs b/src/test/ui/llvm-asm/llvm-asm-in-moved.rs similarity index 100% rename from src/test/ui/asm-in-moved.rs rename to src/test/ui/llvm-asm/llvm-asm-in-moved.rs diff --git a/src/test/ui/asm-in-out-operand.rs b/src/test/ui/llvm-asm/llvm-asm-in-out-operand.rs similarity index 100% rename from src/test/ui/asm-in-out-operand.rs rename to src/test/ui/llvm-asm/llvm-asm-in-out-operand.rs diff --git a/src/test/ui/asm-indirect-memory.rs b/src/test/ui/llvm-asm/llvm-asm-indirect-memory.rs similarity index 100% rename from src/test/ui/asm-indirect-memory.rs rename to src/test/ui/llvm-asm/llvm-asm-indirect-memory.rs diff --git a/src/test/ui/asm-out-assign.rs b/src/test/ui/llvm-asm/llvm-asm-out-assign.rs similarity index 100% rename from src/test/ui/asm-out-assign.rs rename to src/test/ui/llvm-asm/llvm-asm-out-assign.rs diff --git a/src/test/ui/macros/macro-expanded-include/foo/mod.rs b/src/test/ui/macros/macro-expanded-include/foo/mod.rs index f0eb92b2be8..a8bfa0299f6 100644 --- a/src/test/ui/macros/macro-expanded-include/foo/mod.rs +++ b/src/test/ui/macros/macro-expanded-include/foo/mod.rs @@ -5,5 +5,5 @@ macro_rules! m { } macro_rules! n { - () => { unsafe { llvm_asm!(include_str!("file.txt")); } } + () => { unsafe { asm!(include_str!("file.txt")); } } } diff --git a/src/test/ui/macros/macro-expanded-include/test.rs b/src/test/ui/macros/macro-expanded-include/test.rs index abf83a5c6ff..f1a71059a89 100644 --- a/src/test/ui/macros/macro-expanded-include/test.rs +++ b/src/test/ui/macros/macro-expanded-include/test.rs @@ -1,6 +1,6 @@ // ignore-emscripten no llvm_asm! support // build-pass (FIXME(62277): could be check-pass?) -#![feature(llvm_asm)] +#![feature(asm)] #![allow(unused)] #[macro_use] diff --git a/src/test/ui/macros/macros-nonfatal-errors.rs b/src/test/ui/macros/macros-nonfatal-errors.rs index cc96a5bff52..0a496c9dc3d 100644 --- a/src/test/ui/macros/macros-nonfatal-errors.rs +++ b/src/test/ui/macros/macros-nonfatal-errors.rs @@ -3,13 +3,14 @@ // test that errors in a (selection) of macros don't kill compilation // immediately, so that we get more errors listed at a time. -#![feature(llvm_asm)] +#![feature(asm, llvm_asm)] #![feature(trace_macros, concat_idents)] #[derive(Default)] //~ ERROR enum OrDeriveThis {} fn main() { + asm!(invalid); //~ ERROR llvm_asm!(invalid); //~ ERROR concat_idents!("not", "idents"); //~ ERROR diff --git a/src/test/ui/macros/macros-nonfatal-errors.stderr b/src/test/ui/macros/macros-nonfatal-errors.stderr index f416c70123c..6ef757a55b8 100644 --- a/src/test/ui/macros/macros-nonfatal-errors.stderr +++ b/src/test/ui/macros/macros-nonfatal-errors.stderr @@ -6,44 +6,50 @@ LL | #[derive(Default)] | = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) +error: asm template must be a string literal + --> $DIR/macros-nonfatal-errors.rs:13:10 + | +LL | asm!(invalid); + | ^^^^^^^ + error: inline assembly must be a string literal - --> $DIR/macros-nonfatal-errors.rs:13:15 + --> $DIR/macros-nonfatal-errors.rs:14:15 | LL | llvm_asm!(invalid); | ^^^^^^^ error: concat_idents! requires ident args. - --> $DIR/macros-nonfatal-errors.rs:15:5 + --> $DIR/macros-nonfatal-errors.rs:16:5 | LL | concat_idents!("not", "idents"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: argument must be a string literal - --> $DIR/macros-nonfatal-errors.rs:17:17 + --> $DIR/macros-nonfatal-errors.rs:18:17 | LL | option_env!(invalid); | ^^^^^^^ error: expected string literal - --> $DIR/macros-nonfatal-errors.rs:18:10 + --> $DIR/macros-nonfatal-errors.rs:19:10 | LL | env!(invalid); | ^^^^^^^ error: expected string literal - --> $DIR/macros-nonfatal-errors.rs:19:10 + --> $DIR/macros-nonfatal-errors.rs:20:10 | LL | env!(foo, abr, baz); | ^^^ error: environment variable `RUST_HOPEFULLY_THIS_DOESNT_EXIST` not defined - --> $DIR/macros-nonfatal-errors.rs:20:5 + --> $DIR/macros-nonfatal-errors.rs:21:5 | LL | env!("RUST_HOPEFULLY_THIS_DOESNT_EXIST"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: format argument must be a string literal - --> $DIR/macros-nonfatal-errors.rs:22:13 + --> $DIR/macros-nonfatal-errors.rs:23:13 | LL | format!(invalid); | ^^^^^^^ @@ -54,19 +60,19 @@ LL | format!("{}", invalid); | ^^^^^ error: argument must be a string literal - --> $DIR/macros-nonfatal-errors.rs:24:14 + --> $DIR/macros-nonfatal-errors.rs:25:14 | LL | include!(invalid); | ^^^^^^^ error: argument must be a string literal - --> $DIR/macros-nonfatal-errors.rs:26:18 + --> $DIR/macros-nonfatal-errors.rs:27:18 | LL | include_str!(invalid); | ^^^^^^^ error: couldn't read $DIR/i'd be quite surprised if a file with this name existed: $FILE_NOT_FOUND_MSG (os error 2) - --> $DIR/macros-nonfatal-errors.rs:27:5 + --> $DIR/macros-nonfatal-errors.rs:28:5 | LL | include_str!("i'd be quite surprised if a file with this name existed"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -74,13 +80,13 @@ LL | include_str!("i'd be quite surprised if a file with this name existed") = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) error: argument must be a string literal - --> $DIR/macros-nonfatal-errors.rs:28:20 + --> $DIR/macros-nonfatal-errors.rs:29:20 | LL | include_bytes!(invalid); | ^^^^^^^ error: couldn't read $DIR/i'd be quite surprised if a file with this name existed: $FILE_NOT_FOUND_MSG (os error 2) - --> $DIR/macros-nonfatal-errors.rs:29:5 + --> $DIR/macros-nonfatal-errors.rs:30:5 | LL | include_bytes!("i'd be quite surprised if a file with this name existed"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -88,11 +94,11 @@ LL | include_bytes!("i'd be quite surprised if a file with this name existed = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) error: trace_macros! accepts only `true` or `false` - --> $DIR/macros-nonfatal-errors.rs:31:5 + --> $DIR/macros-nonfatal-errors.rs:32:5 | LL | trace_macros!(invalid); | ^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 14 previous errors +error: aborting due to 15 previous errors For more information about this error, try `rustc --explain E0665`. diff --git a/src/test/ui/target-feature/gate.stderr b/src/test/ui/target-feature/gate.stderr index 848538a4e92..2384a00aa47 100644 --- a/src/test/ui/target-feature/gate.stderr +++ b/src/test/ui/target-feature/gate.stderr @@ -1,5 +1,5 @@ error[E0658]: the target feature `avx512bw` is currently unstable - --> $DIR/gate.rs:29:18 + --> $DIR/gate.rs:30:18 | LL | #[target_feature(enable = "avx512bw")] | ^^^^^^^^^^^^^^^^^^^ From ff97db1e54dde5418f55f1c727e1c37257c4a6ab Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Thu, 19 Mar 2020 07:41:43 +0000 Subject: [PATCH 12/36] Apply review feedback --- src/librustc_builtin_macros/asm.rs | 125 +++++++++++++++------------- src/librustc_passes/intrinsicck.rs | 17 +++- src/librustc_target/asm/aarch64.rs | 27 +++--- src/librustc_target/asm/arm.rs | 21 +++-- src/librustc_target/asm/mod.rs | 2 +- src/librustc_target/asm/riscv.rs | 20 ++--- src/librustc_target/asm/x86.rs | 61 ++++++++------ src/test/ui/asm/parse-error.rs | 5 +- src/test/ui/asm/parse-error.stderr | 34 ++++++-- src/test/ui/asm/type-check-3.stderr | 12 +-- 10 files changed, 185 insertions(+), 139 deletions(-) diff --git a/src/librustc_builtin_macros/asm.rs b/src/librustc_builtin_macros/asm.rs index 943ed42e202..c9a3401cac0 100644 --- a/src/librustc_builtin_macros/asm.rs +++ b/src/librustc_builtin_macros/asm.rs @@ -80,47 +80,14 @@ fn parse_args<'a>( break; } // accept trailing commas - let span_start = p.token.span; - // Parse options if p.eat(&token::Ident(sym::options, false)) { - p.expect(&token::OpenDelim(token::DelimToken::Paren))?; - - while !p.eat(&token::CloseDelim(token::DelimToken::Paren)) { - if p.eat(&token::Ident(sym::pure, false)) { - args.options |= InlineAsmOptions::PURE; - } else if p.eat(&token::Ident(sym::nomem, false)) { - args.options |= InlineAsmOptions::NOMEM; - } else if p.eat(&token::Ident(sym::readonly, false)) { - args.options |= InlineAsmOptions::READONLY; - } else if p.eat(&token::Ident(sym::preserves_flags, false)) { - args.options |= InlineAsmOptions::PRESERVES_FLAGS; - } else if p.eat(&token::Ident(sym::noreturn, false)) { - args.options |= InlineAsmOptions::NORETURN; - } else { - p.expect(&token::Ident(sym::nostack, false))?; - args.options |= InlineAsmOptions::NOSTACK; - } - - // Allow trailing commas - if p.eat(&token::CloseDelim(token::DelimToken::Paren)) { - break; - } - p.expect(&token::Comma)?; - } - - let new_span = span_start.to(p.prev_token.span); - if let Some(options_span) = args.options_span { - ecx.struct_span_err(new_span, "asm options cannot be specified twice") - .span_label(options_span, "previously here") - .span_label(new_span, "duplicate options") - .emit(); - } else { - args.options_span = Some(new_span); - } + parse_options(&mut p, &mut args)?; continue; } + let span_start = p.token.span; + // Parse operand names let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) { let (ident, _) = p.token.ident().unwrap(); @@ -131,29 +98,6 @@ fn parse_args<'a>( None }; - fn parse_reg<'a>( - p: &mut Parser<'a>, - explicit_reg: &mut bool, - ) -> Result> { - p.expect(&token::OpenDelim(token::DelimToken::Paren))?; - let result = match p.token.kind { - token::Ident(name, false) => ast::InlineAsmRegOrRegClass::RegClass(name), - token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => { - *explicit_reg = true; - ast::InlineAsmRegOrRegClass::Reg(symbol) - } - _ => { - return Err(p.struct_span_err( - p.token.span, - "expected register class or explicit register", - )); - } - }; - p.bump(); - p.expect(&token::CloseDelim(token::DelimToken::Paren))?; - Ok(result) - }; - let mut explicit_reg = false; let op = if p.eat(&token::Ident(kw::In, false)) { let reg = parse_reg(&mut p, &mut explicit_reg)?; @@ -319,6 +263,69 @@ fn parse_args<'a>( Ok(args) } +fn parse_options<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> Result<(), DiagnosticBuilder<'a>> { + let span_start = p.prev_token.span; + + p.expect(&token::OpenDelim(token::DelimToken::Paren))?; + + while !p.eat(&token::CloseDelim(token::DelimToken::Paren)) { + if p.eat(&token::Ident(sym::pure, false)) { + args.options |= InlineAsmOptions::PURE; + } else if p.eat(&token::Ident(sym::nomem, false)) { + args.options |= InlineAsmOptions::NOMEM; + } else if p.eat(&token::Ident(sym::readonly, false)) { + args.options |= InlineAsmOptions::READONLY; + } else if p.eat(&token::Ident(sym::preserves_flags, false)) { + args.options |= InlineAsmOptions::PRESERVES_FLAGS; + } else if p.eat(&token::Ident(sym::noreturn, false)) { + args.options |= InlineAsmOptions::NORETURN; + } else { + p.expect(&token::Ident(sym::nostack, false))?; + args.options |= InlineAsmOptions::NOSTACK; + } + + // Allow trailing commas + if p.eat(&token::CloseDelim(token::DelimToken::Paren)) { + break; + } + p.expect(&token::Comma)?; + } + + let new_span = span_start.to(p.prev_token.span); + if let Some(options_span) = args.options_span { + p.struct_span_err(new_span, "asm options cannot be specified multiple times") + .span_label(options_span, "previously here") + .span_label(new_span, "duplicate options") + .emit(); + } else { + args.options_span = Some(new_span); + } + + Ok(()) +} + +fn parse_reg<'a>( + p: &mut Parser<'a>, + explicit_reg: &mut bool, +) -> Result> { + p.expect(&token::OpenDelim(token::DelimToken::Paren))?; + let result = match p.token.kind { + token::Ident(name, false) => ast::InlineAsmRegOrRegClass::RegClass(name), + token::Literal(token::Lit { kind: token::LitKind::Str, symbol, suffix: _ }) => { + *explicit_reg = true; + ast::InlineAsmRegOrRegClass::Reg(symbol) + } + _ => { + return Err( + p.struct_span_err(p.token.span, "expected register class or explicit register") + ); + } + }; + p.bump(); + p.expect(&token::CloseDelim(token::DelimToken::Paren))?; + Ok(result) +} + fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, sp: Span, args: AsmArgs) -> P { let msg = "asm template must be a string literal"; let template_sp = args.template.span; diff --git a/src/librustc_passes/intrinsicck.rs b/src/librustc_passes/intrinsicck.rs index 1a9fd30bba0..b98ed99d04e 100644 --- a/src/librustc_passes/intrinsicck.rs +++ b/src/librustc_passes/intrinsicck.rs @@ -239,8 +239,10 @@ impl ExprVisitor<'tcx> { &format!("type `{}`", self.tables.expr_ty_adjusted(in_expr)), ); err.span_label(expr.span, &format!("type `{}`", ty)); - err.note("asm inout arguments must have the same type"); - err.note("unless they are both pointers or integers of the same size"); + err.note( + "asm inout arguments must have the same type, \ + unless they are both pointers or integers of the same size", + ); err.emit(); } @@ -271,7 +273,16 @@ impl ExprVisitor<'tcx> { } }; - // Check whether the selected type requires a target feature. + // Check whether the selected type requires a target feature. Note that + // this is different from the feature check we did earlier in AST + // lowering. While AST lowering checked that this register class is + // usable at all with the currently enabled features, some types may + // only be usable with a register class when a certain feature is + // enabled. We check this here since it depends on the results of typeck. + // + // Also note that this check isn't run when the operand type is never + // (!). In that case we still need the earlier check in AST lowering to + // verify that the register class is usable at all. if let Some(feature) = feature { if !self.tcx.sess.target_features.contains(&Symbol::intern(feature)) { let msg = &format!("`{}` target feature is not enabled", feature); diff --git a/src/librustc_target/asm/aarch64.rs b/src/librustc_target/asm/aarch64.rs index c734344f81f..16bc5d670d8 100644 --- a/src/librustc_target/asm/aarch64.rs +++ b/src/librustc_target/asm/aarch64.rs @@ -24,13 +24,10 @@ impl AArch64InlineAsmRegClass { ty: InlineAsmType, ) -> Option<(char, &'static str, Option<&'static str>)> { match self { - Self::reg => { - if ty.size().bits() <= 32 { - Some(('w', "w0", None)) - } else { - None - } - } + Self::reg => match ty.size().bits() { + 64 => None, + _ => Some(('w', "w0", None)), + }, Self::vreg | Self::vreg_low16 => match ty.size().bits() { 8 => Some(('b', "b0", None)), 16 => Some(('h', "h0", None)), @@ -57,8 +54,8 @@ impl AArch64InlineAsmRegClass { Self::reg => types! { _: I8, I16, I32, I64, F32, F64; }, Self::vreg | Self::vreg_low16 => types! { "fp": I8, I16, I32, I64, F32, F64, - VecI8(8), VecI16(4), VecI32(2), VecI64(1), VecF32(2), VecF64(1), - VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2); + VecI8(8), VecI16(4), VecI32(2), VecI64(1), VecF32(2), VecF64(1), + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2); }, } } @@ -128,12 +125,12 @@ def_regs! { v29: vreg = ["v29", "b29", "h29", "s29", "d29", "q29"], v30: vreg = ["v30", "b30", "h30", "s30", "d30", "q30"], v31: vreg = ["v31", "b31", "h31", "s31", "d31", "q31"], - "the frame pointer cannot be used as an operand for inline asm" = - ["x29", "fp"], - "the stack pointer cannot be used as an operand for inline asm" = - ["sp", "wsp"], - "the zero register cannot be used as an operand for inline asm" = - ["xzr", "wzr"], + #error = ["x29", "fp"] => + "the frame pointer cannot be used as an operand for inline asm", + #error = ["sp", "wsp"] => + "the stack pointer cannot be used as an operand for inline asm", + #error = ["xzr", "wzr"] => + "the zero register cannot be used as an operand for inline asm", } } diff --git a/src/librustc_target/asm/arm.rs b/src/librustc_target/asm/arm.rs index 143852db829..0ceb15e297f 100644 --- a/src/librustc_target/asm/arm.rs +++ b/src/librustc_target/asm/arm.rs @@ -149,12 +149,12 @@ def_regs! { q13: qreg = ["q13"], q14: qreg = ["q14"], q15: qreg = ["q15"], - "the frame pointer cannot be used as an operand for inline asm" = - ["r11", "fp"], - "the stack pointer cannot be used as an operand for inline asm" = - ["r13", "sp"], - "the program pointer cannot be used as an operand for inline asm" = - ["r15", "pc"], + #error = ["r11", "fp"] => + "the frame pointer cannot be used as an operand for inline asm", + #error = ["r13", "sp"] => + "the stack pointer cannot be used as an operand for inline asm", + #error = ["r15", "pc"] => + "the program pointer cannot be used as an operand for inline asm", } } @@ -231,6 +231,15 @@ impl ArmInlineAsmReg { } }; } + + // ARM's floating-point register file is interesting in that it can be + // viewed as 16 128-bit registers, 32 64-bit registers or 32 32-bit + // registers. Because these views overlap, the registers of different + // widths will conflict (e.g. d0 overlaps with s0 and s1, and q1 + // overlaps with d2 and d3). + // + // See section E1.3.1 of the ARM Architecture Reference Manual for + // ARMv8-A for more details. reg_conflicts! { q0 : d0 d1 : s0 s1 s2 s3, q1 : d2 d3 : s4 s5 s6 s7, diff --git a/src/librustc_target/asm/mod.rs b/src/librustc_target/asm/mod.rs index 1f8ff5d6d39..dda6e7cd5cd 100644 --- a/src/librustc_target/asm/mod.rs +++ b/src/librustc_target/asm/mod.rs @@ -57,7 +57,7 @@ macro_rules! def_regs { $reg:ident: $class:ident $(, $extra_class:ident)* = [$reg_name:literal $(, $alias:literal)*] $(% $filter:ident)?, )* $( - $error:literal = [$($bad_reg:literal),+], + #error = [$($bad_reg:literal),+] => $error:literal, )* }) => { #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Debug, Eq, PartialEq, Hash, HashStable_Generic)] diff --git a/src/librustc_target/asm/riscv.rs b/src/librustc_target/asm/riscv.rs index c57e277c0cd..f9b132c2087 100644 --- a/src/librustc_target/asm/riscv.rs +++ b/src/librustc_target/asm/riscv.rs @@ -115,16 +115,16 @@ def_regs! { f29: freg = ["f29", "ft9"], f30: freg = ["f30", "ft10"], f31: freg = ["f31", "ft11"], - "the frame pointer cannot be used as an operand for inline asm" = - ["x8", "s0", "fp"], - "the stack pointer cannot be used as an operand for inline asm" = - ["x2", "sp"], - "the global pointer cannot be used as an operand for inline asm" = - ["x3", "gp"], - "the thread pointer cannot be used as an operand for inline asm" = - ["x4", "tp"], - "the zero register cannot be used as an operand for inline asm" = - ["x0", "zero"], + #error = ["x8", "s0", "fp"] => + "the frame pointer cannot be used as an operand for inline asm", + #error = ["x2", "sp"] => + "the stack pointer cannot be used as an operand for inline asm", + #error = ["x3", "gp"] => + "the global pointer cannot be used as an operand for inline asm", + #error = ["x4", "tp"] => + "the thread pointer cannot be used as an operand for inline asm" , + #error = ["x0", "zero"] => + "the zero register cannot be used as an operand for inline asm", } } diff --git a/src/librustc_target/asm/x86.rs b/src/librustc_target/asm/x86.rs index 7193b1ca8b6..d10bcb40ba0 100644 --- a/src/librustc_target/asm/x86.rs +++ b/src/librustc_target/asm/x86.rs @@ -62,16 +62,13 @@ impl X86InlineAsmRegClass { _ => None, }, Self::xmm_reg => None, - Self::ymm_reg => { - if ty.size().bits() <= 128 { - Some(('x', "xmm0", None)) - } else { - None - } - } + Self::ymm_reg => match ty.size().bits() { + 256 => None, + _ => Some(('x', "xmm0", None)), + }, Self::zmm_reg => match ty.size().bits() { - 256 => Some(('y', "ymm0", None)), 512 => None, + 256 => Some(('y', "ymm0", None)), _ => Some(('x', "xmm0", None)), }, Self::kreg => None, @@ -108,18 +105,18 @@ impl X86InlineAsmRegClass { } Self::xmm_reg => types! { "sse": I32, I64, F32, F64, - VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2); + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2); }, Self::ymm_reg => types! { "avx": I32, I64, F32, F64, - VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2), - VecI8(32), VecI16(16), VecI32(8), VecI64(4), VecF32(8), VecF64(4); + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2), + VecI8(32), VecI16(16), VecI32(8), VecI64(4), VecF32(8), VecF64(4); }, Self::zmm_reg => types! { "avx512f": I32, I64, F32, F64, - VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2), - VecI8(32), VecI16(16), VecI32(8), VecI64(4), VecF32(8), VecF64(4), - VecI8(64), VecI16(32), VecI32(16), VecI64(8), VecF32(16), VecF64(8); + VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2), + VecI8(32), VecI16(16), VecI32(8), VecI64(4), VecF32(8), VecF64(4), + VecI8(64), VecI16(32), VecI32(16), VecI64(8), VecF32(16), VecF64(8); }, Self::kreg => types! { "avx512f": I8, I16; @@ -227,19 +224,20 @@ def_regs! { k5: kreg = ["k5"], k6: kreg = ["k6"], k7: kreg = ["k7"], - "high byte registers are not currently supported as operands for inline asm" = - ["ah", "bh", "ch", "dh"], - "the frame pointer cannot be used as an operand for inline asm" = - ["bp", "bpl", "ebp", "rbp"], - "the stack pointer cannot be used as an operand for inline asm" = - ["sp", "spl", "esp", "rsp"], - "the instruction pointer cannot be used as an operand for inline asm" = - ["ip", "eip", "rip"], - "x87 registers are not currently supported as operands for inline asm" = - ["st", "st(0)", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)"], - "MMX registers are not currently supported as operands for inline asm" = - ["mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7"], - "the k0 AVX mask register cannot be used as an operand for inline asm" = ["k0"], + #error = ["ah", "bh", "ch", "dh"] => + "high byte registers are not currently supported as operands for inline asm", + #error = ["bp", "bpl", "ebp", "rbp"] => + "the frame pointer cannot be used as an operand for inline asm", + #error = ["sp", "spl", "esp", "rsp"] => + "the stack pointer cannot be used as an operand for inline asm", + #error = ["ip", "eip", "rip"] => + "the instruction pointer cannot be used as an operand for inline asm", + #error = ["st", "st(0)", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)"] => + "x87 registers are not currently supported as operands for inline asm", + #error = ["mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7"] => + "MMX registers are not currently supported as operands for inline asm", + #error = ["k0"] => + "the k0 AVX mask register cannot be used as an operand for inline asm", } } @@ -316,6 +314,15 @@ impl X86InlineAsmReg { } }; } + + // XMM*, YMM* and ZMM* are all different views of the same register. + // + // See section 15.5 of the combined Intel® 64 and IA-32 Architectures + // Software Developer’s Manual for more details. + // + // We don't need to specify conflicts for [x,y,z]mm[16-31] since these + // registers are only available with AVX-512, so we just specify them + // as aliases directly. reg_conflicts! { xmm0 : ymm0 : zmm0, xmm1 : ymm1 : zmm1, diff --git a/src/test/ui/asm/parse-error.rs b/src/test/ui/asm/parse-error.rs index e6566866b22..2b1f018f364 100644 --- a/src/test/ui/asm/parse-error.rs +++ b/src/test/ui/asm/parse-error.rs @@ -35,7 +35,10 @@ fn main() { asm!("", options(nomem, foo)); //~^ ERROR expected one of asm!("", options(), options()); - //~^ ERROR asm options cannot be specified twice + //~^ ERROR asm options cannot be specified multiple times + asm!("", options(), options(), options()); + //~^ ERROR asm options cannot be specified multiple times + //~^^ ERROR asm options cannot be specified multiple times asm!("{}", options(), const foo); //~^ ERROR arguments are not allowed after options asm!("{a}", a = const foo, a = const bar); diff --git a/src/test/ui/asm/parse-error.stderr b/src/test/ui/asm/parse-error.stderr index a927ce13858..94eb9d29862 100644 --- a/src/test/ui/asm/parse-error.stderr +++ b/src/test/ui/asm/parse-error.stderr @@ -82,7 +82,7 @@ error: expected one of `)`, `nomem`, `noreturn`, `nostack`, `preserves_flags`, ` LL | asm!("", options(nomem, foo)); | ^^^ expected one of 7 possible tokens -error: asm options cannot be specified twice +error: asm options cannot be specified multiple times --> $DIR/parse-error.rs:37:29 | LL | asm!("", options(), options()); @@ -90,8 +90,24 @@ LL | asm!("", options(), options()); | | | previously here +error: asm options cannot be specified multiple times + --> $DIR/parse-error.rs:39:29 + | +LL | asm!("", options(), options(), options()); + | --------- ^^^^^^^^^ duplicate options + | | + | previously here + +error: asm options cannot be specified multiple times + --> $DIR/parse-error.rs:39:40 + | +LL | asm!("", options(), options(), options()); + | --------- ^^^^^^^^^ duplicate options + | | + | previously here + error: arguments are not allowed after options - --> $DIR/parse-error.rs:39:31 + --> $DIR/parse-error.rs:42:31 | LL | asm!("{}", options(), const foo); | --------- ^^^^^^^^^ argument @@ -99,7 +115,7 @@ LL | asm!("{}", options(), const foo); | previous options error: duplicate argument named `a` - --> $DIR/parse-error.rs:41:36 + --> $DIR/parse-error.rs:44:36 | LL | asm!("{a}", a = const foo, a = const bar); | ------------- ^^^^^^^^^^^^^ duplicate argument @@ -107,19 +123,19 @@ LL | asm!("{a}", a = const foo, a = const bar); | previously here error: argument never used - --> $DIR/parse-error.rs:41:36 + --> $DIR/parse-error.rs:44:36 | LL | asm!("{a}", a = const foo, a = const bar); | ^^^^^^^^^^^^^ argument never used error: explicit register arguments cannot have names - --> $DIR/parse-error.rs:44:18 + --> $DIR/parse-error.rs:47:18 | LL | asm!("", a = in("eax") foo); | ^^^^^^^^^^^^^^^^^ error: named arguments cannot follow explicit register arguments - --> $DIR/parse-error.rs:46:36 + --> $DIR/parse-error.rs:49:36 | LL | asm!("{a}", in("eax") foo, a = const bar); | ------------- ^^^^^^^^^^^^^ named argument @@ -127,7 +143,7 @@ LL | asm!("{a}", in("eax") foo, a = const bar); | explicit register argument error: named arguments cannot follow explicit register arguments - --> $DIR/parse-error.rs:48:36 + --> $DIR/parse-error.rs:51:36 | LL | asm!("{a}", in("eax") foo, a = const bar); | ------------- ^^^^^^^^^^^^^ named argument @@ -135,12 +151,12 @@ LL | asm!("{a}", in("eax") foo, a = const bar); | explicit register argument error: positional arguments cannot follow named arguments or explicit register arguments - --> $DIR/parse-error.rs:50:36 + --> $DIR/parse-error.rs:53:36 | LL | asm!("{1}", in("eax") foo, const bar); | ------------- ^^^^^^^^^ positional argument | | | explicit register argument -error: aborting due to 22 previous errors +error: aborting due to 24 previous errors diff --git a/src/test/ui/asm/type-check-3.stderr b/src/test/ui/asm/type-check-3.stderr index e4018ca1d42..ccc795d1013 100644 --- a/src/test/ui/asm/type-check-3.stderr +++ b/src/test/ui/asm/type-check-3.stderr @@ -83,8 +83,7 @@ LL | asm!("{:r}", inout(reg) 0u16 => val_i8); | | | type `u16` | - = note: asm inout arguments must have the same type - = note: unless they are both pointers or integers of the same size + = note: asm inout arguments must have the same type, unless they are both pointers or integers of the same size error: incompatible types for asm inout argument --> $DIR/type-check-3.rs:59:33 @@ -94,8 +93,7 @@ LL | asm!("{:r}", inout(reg) 0u32 => val_f32); | | | type `u32` | - = note: asm inout arguments must have the same type - = note: unless they are both pointers or integers of the same size + = note: asm inout arguments must have the same type, unless they are both pointers or integers of the same size error: incompatible types for asm inout argument --> $DIR/type-check-3.rs:61:33 @@ -105,8 +103,7 @@ LL | asm!("{:r}", inout(reg) 0u32 => val_ptr); | | | type `u32` | - = note: asm inout arguments must have the same type - = note: unless they are both pointers or integers of the same size + = note: asm inout arguments must have the same type, unless they are both pointers or integers of the same size error: incompatible types for asm inout argument --> $DIR/type-check-3.rs:63:33 @@ -116,8 +113,7 @@ LL | asm!("{:r}", inout(reg) main => val_u32); | | | type `fn()` | - = note: asm inout arguments must have the same type - = note: unless they are both pointers or integers of the same size + = note: asm inout arguments must have the same type, unless they are both pointers or integers of the same size error: aborting due to 9 previous errors; 4 warnings emitted From 93e2946d0c0cccd69ee03390adca7ec4f71a2113 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 24 Apr 2020 21:16:00 +0100 Subject: [PATCH 13/36] Un-deprecate asm! macro --- src/libcore/macros/mod.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/libcore/macros/mod.rs b/src/libcore/macros/mod.rs index 14bfa8bab89..bca979ca0cb 100644 --- a/src/libcore/macros/mod.rs +++ b/src/libcore/macros/mod.rs @@ -1296,14 +1296,6 @@ pub(crate) mod builtin { issue = "70173", reason = "inline assembly is not stable enough for use and is subject to change" )] - #[cfg_attr( - not(bootstrap), - rustc_deprecated( - since = "1.44.0", - reason = "the syntax of asm! will change soon, use llvm_asm! to avoid breakage", - suggestion = "llvm_asm", - ) - )] #[rustc_builtin_macro] #[macro_export] macro_rules! asm { From 7dfa486d4a18b7a6e514e589771f52c43eff4f3b Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 29 Apr 2020 00:45:58 +0100 Subject: [PATCH 14/36] Add support for high byte registers on x86 --- src/librustc_codegen_llvm/asm.rs | 3 + src/librustc_passes/intrinsicck.rs | 24 +++-- src/librustc_target/asm/aarch64.rs | 18 ++-- src/librustc_target/asm/arm.rs | 6 +- src/librustc_target/asm/mod.rs | 17 +++- src/librustc_target/asm/riscv.rs | 6 +- src/librustc_target/asm/x86.rs | 141 ++++++++++++++++++++-------- src/test/assembly/asm/x86-types.rs | 20 ++-- src/test/ui/asm/bad-reg.rs | 4 +- src/test/ui/asm/bad-reg.stderr | 36 +++---- src/test/ui/asm/type-check-3.rs | 15 +-- src/test/ui/asm/type-check-3.stderr | 31 +++--- 12 files changed, 198 insertions(+), 123 deletions(-) diff --git a/src/librustc_codegen_llvm/asm.rs b/src/librustc_codegen_llvm/asm.rs index 21322511c99..20abfbcf405 100644 --- a/src/librustc_codegen_llvm/asm.rs +++ b/src/librustc_codegen_llvm/asm.rs @@ -409,6 +409,7 @@ fn reg_to_llvm(reg: InlineAsmRegOrRegClass) -> String { InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => "f", InlineAsmRegClass::X86(X86InlineAsmRegClass::reg) => "r", InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd) => "Q", + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_byte) => "r", InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg) | InlineAsmRegClass::X86(X86InlineAsmRegClass::ymm_reg) => "x", InlineAsmRegClass::X86(X86InlineAsmRegClass::zmm_reg) => "v", @@ -459,6 +460,7 @@ fn modifier_to_llvm( 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) { @@ -499,6 +501,7 @@ fn dummy_output_type(cx: &CodegenCx<'ll, 'tcx>, reg: InlineAsmRegClass) -> &'ll InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => 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::xmm_reg) | InlineAsmRegClass::X86(X86InlineAsmRegClass::ymm_reg) | InlineAsmRegClass::X86(X86InlineAsmRegClass::zmm_reg) => cx.type_f32(), diff --git a/src/librustc_passes/intrinsicck.rs b/src/librustc_passes/intrinsicck.rs index b98ed99d04e..9e144f86cd8 100644 --- a/src/librustc_passes/intrinsicck.rs +++ b/src/librustc_passes/intrinsicck.rs @@ -268,6 +268,12 @@ impl ExprVisitor<'tcx> { reg_class.name(), supported_tys.join(", "), )); + if let Some(suggest) = reg_class.suggest_class(asm_arch, asm_ty) { + err.help(&format!( + "consider using the `{}` register class instead", + suggest.name() + )); + } err.emit(); return Some(asm_ty); } @@ -298,7 +304,7 @@ impl ExprVisitor<'tcx> { } // Check whether a modifier is suggested for using this type. - if let Some((suggested_modifier, suggested_result, switch_reg_class)) = + if let Some((suggested_modifier, suggested_result)) = reg_class.suggest_modifier(asm_arch, asm_ty) { // Search for any use of this operand without a modifier and emit @@ -323,18 +329,10 @@ impl ExprVisitor<'tcx> { let msg = "formatting may not be suitable for sub-register argument"; let mut err = lint.build(msg); err.span_label(expr.span, "for this argument"); - if let Some(switch_reg_class) = switch_reg_class { - err.help(&format!( - "use the `{}` modifier with the `{}` register class \ - to have the register formatted as `{}`", - suggested_modifier, switch_reg_class, suggested_result, - )); - } else { - err.help(&format!( - "use the `{}` modifier to have the register formatted as `{}`", - suggested_modifier, suggested_result, - )); - } + err.help(&format!( + "use the `{}` modifier to have the register formatted as `{}`", + suggested_modifier, suggested_result, + )); err.help(&format!( "or use the `{}` modifier to keep the default formatting of `{}`", default_modifier, default_result, diff --git a/src/librustc_target/asm/aarch64.rs b/src/librustc_target/asm/aarch64.rs index 16bc5d670d8..e7c9edea765 100644 --- a/src/librustc_target/asm/aarch64.rs +++ b/src/librustc_target/asm/aarch64.rs @@ -18,22 +18,26 @@ impl AArch64InlineAsmRegClass { } } + pub fn suggest_class(self, _arch: InlineAsmArch, _ty: InlineAsmType) -> Option { + None + } + pub fn suggest_modifier( self, _arch: InlineAsmArch, ty: InlineAsmType, - ) -> Option<(char, &'static str, Option<&'static str>)> { + ) -> Option<(char, &'static str)> { match self { Self::reg => match ty.size().bits() { 64 => None, - _ => Some(('w', "w0", None)), + _ => Some(('w', "w0")), }, Self::vreg | Self::vreg_low16 => match ty.size().bits() { - 8 => Some(('b', "b0", None)), - 16 => Some(('h', "h0", None)), - 32 => Some(('s', "s0", None)), - 64 => Some(('d', "d0", None)), - 128 => Some(('q', "q0", None)), + 8 => Some(('b', "b0")), + 16 => Some(('h', "h0")), + 32 => Some(('s', "s0")), + 64 => Some(('d', "d0")), + 128 => Some(('q', "q0")), _ => None, }, } diff --git a/src/librustc_target/asm/arm.rs b/src/librustc_target/asm/arm.rs index 0ceb15e297f..1798b2a0949 100644 --- a/src/librustc_target/asm/arm.rs +++ b/src/librustc_target/asm/arm.rs @@ -25,11 +25,15 @@ impl ArmInlineAsmRegClass { } } + pub fn suggest_class(self, _arch: InlineAsmArch, _ty: InlineAsmType) -> Option { + None + } + pub fn suggest_modifier( self, _arch: InlineAsmArch, _ty: InlineAsmType, - ) -> Option<(char, &'static str, Option<&'static str>)> { + ) -> Option<(char, &'static str)> { None } diff --git a/src/librustc_target/asm/mod.rs b/src/librustc_target/asm/mod.rs index dda6e7cd5cd..f1e8457cacc 100644 --- a/src/librustc_target/asm/mod.rs +++ b/src/librustc_target/asm/mod.rs @@ -291,9 +291,20 @@ impl InlineAsmRegClass { } } + /// Returns a suggested register class to use for this type. This is called + /// after type checking via `supported_types` fails to give a better error + /// message to the user. + pub fn suggest_class(self, arch: InlineAsmArch, ty: InlineAsmType) -> Option { + match self { + Self::X86(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::X86), + Self::Arm(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::Arm), + Self::AArch64(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::AArch64), + Self::RiscV(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::RiscV), + } + } + /// Returns a suggested template modifier to use for this type and an - /// example of a register named formatted with it. Optionally also returns - /// the name of a different register class to use instead. + /// example of a register named formatted with it. /// /// Such suggestions are useful if a type smaller than the full register /// size is used and a modifier can be used to point to the subregister of @@ -302,7 +313,7 @@ impl InlineAsmRegClass { self, arch: InlineAsmArch, ty: InlineAsmType, - ) -> Option<(char, &'static str, Option<&'static str>)> { + ) -> Option<(char, &'static str)> { match self { Self::X86(r) => r.suggest_modifier(arch, ty), Self::Arm(r) => r.suggest_modifier(arch, ty), diff --git a/src/librustc_target/asm/riscv.rs b/src/librustc_target/asm/riscv.rs index f9b132c2087..7da30cc8875 100644 --- a/src/librustc_target/asm/riscv.rs +++ b/src/librustc_target/asm/riscv.rs @@ -14,11 +14,15 @@ impl RiscVInlineAsmRegClass { &[] } + pub fn suggest_class(self, _arch: InlineAsmArch, _ty: InlineAsmType) -> Option { + None + } + pub fn suggest_modifier( self, _arch: InlineAsmArch, _ty: InlineAsmType, - ) -> Option<(char, &'static str, Option<&'static str>)> { + ) -> Option<(char, &'static str)> { None } diff --git a/src/librustc_target/asm/x86.rs b/src/librustc_target/asm/x86.rs index d10bcb40ba0..6eb99b0180f 100644 --- a/src/librustc_target/asm/x86.rs +++ b/src/librustc_target/asm/x86.rs @@ -6,6 +6,7 @@ def_reg_class! { X86 X86InlineAsmRegClass { reg, reg_abcd, + reg_byte, xmm_reg, ymm_reg, zmm_reg, @@ -30,46 +31,45 @@ impl X86InlineAsmRegClass { &['l', 'h', 'x', 'e'] } } + Self::reg_byte => &[], Self::xmm_reg | Self::ymm_reg | Self::zmm_reg => &['x', 'y', 'z'], Self::kreg => &[], } } + pub fn suggest_class(self, _arch: InlineAsmArch, ty: InlineAsmType) -> Option { + match self { + Self::reg | Self::reg_abcd if ty.size().bits() == 8 => Some(Self::reg_byte), + _ => None, + } + } + pub fn suggest_modifier( self, arch: InlineAsmArch, ty: InlineAsmType, - ) -> Option<(char, &'static str, Option<&'static str>)> { + ) -> Option<(char, &'static str)> { match self { Self::reg => match ty.size().bits() { - 8 => { - if arch == InlineAsmArch::X86_64 { - Some(('l', "al", None)) - } else { - // Low byte registers require reg_abcd on x86 so we emit - // a suggestion to use that register class instead. - Some(('l', "al", Some("reg_abcd"))) - } - } - 16 => Some(('x', "ax", None)), - 32 if arch == InlineAsmArch::X86_64 => Some(('e', "eax", None)), + 16 => Some(('x', "ax")), + 32 if arch == InlineAsmArch::X86_64 => Some(('e', "eax")), _ => None, }, Self::reg_abcd => match ty.size().bits() { - 8 => Some(('l', "al", None)), - 16 => Some(('x', "ax", None)), - 32 if arch == InlineAsmArch::X86_64 => Some(('e', "eax", None)), + 16 => Some(('x', "ax")), + 32 if arch == InlineAsmArch::X86_64 => Some(('e', "eax")), _ => None, }, + Self::reg_byte => None, Self::xmm_reg => None, Self::ymm_reg => match ty.size().bits() { 256 => None, - _ => Some(('x', "xmm0", None)), + _ => Some(('x', "xmm0")), }, Self::zmm_reg => match ty.size().bits() { 512 => None, - 256 => Some(('y', "ymm0", None)), - _ => Some(('x', "xmm0", None)), + 256 => Some(('y', "ymm0")), + _ => Some(('x', "xmm0")), }, Self::kreg => None, } @@ -84,6 +84,7 @@ impl X86InlineAsmRegClass { Some(('e', "eax")) } } + Self::reg_byte => None, Self::xmm_reg => Some(('x', "xmm0")), Self::ymm_reg => Some(('y', "ymm0")), Self::zmm_reg => Some(('z', "zmm0")), @@ -98,11 +99,12 @@ impl X86InlineAsmRegClass { match self { Self::reg | Self::reg_abcd => { if arch == InlineAsmArch::X86_64 { - types! { _: I8, I16, I32, I64, F32, F64; } + types! { _: I16, I32, I64, F32, F64; } } else { - types! { _: I8, I16, I32, F32; } + types! { _: I16, I32, F32; } } } + Self::reg_byte => types! { _: I8; }, Self::xmm_reg => types! { "sse": I32, I64, F32, F64, VecI8(16), VecI16(8), VecI32(4), VecI64(2), VecF32(4), VecF64(2); @@ -139,20 +141,38 @@ fn x86_64_only( def_regs! { X86 X86InlineAsmReg X86InlineAsmRegClass { - ax: reg, reg_abcd = ["ax", "al", "eax", "rax"], - bx: reg, reg_abcd = ["bx", "bl", "ebx", "rbx"], - cx: reg, reg_abcd = ["cx", "cl", "ecx", "rcx"], - dx: reg, reg_abcd = ["dx", "dl", "edx", "rdx"], - si: reg = ["si", "sil", "esi", "rsi"], - di: reg = ["di", "dil", "edi", "rdi"], - r8: reg = ["r8", "r8b", "r8w", "r8d"] % x86_64_only, - r9: reg = ["r9", "r9b", "r9w", "r9d"] % x86_64_only, - r10: reg = ["r10", "r10b", "r10w", "r10d"] % x86_64_only, - r11: reg = ["r11", "r11b", "r11w", "r11d"] % x86_64_only, - r12: reg = ["r12", "r12b", "r12w", "r12d"] % x86_64_only, - r13: reg = ["r13", "r13b", "r13w", "r13d"] % x86_64_only, - r14: reg = ["r14", "r14b", "r14w", "r14d"] % x86_64_only, - r15: reg = ["r15", "r15b", "r15w", "r15d"] % x86_64_only, + ax: reg, reg_abcd = ["ax", "eax", "rax"], + bx: reg, reg_abcd = ["bx", "ebx", "rbx"], + cx: reg, reg_abcd = ["cx", "ecx", "rcx"], + dx: reg, reg_abcd = ["dx", "edx", "rdx"], + si: reg = ["si", "esi", "rsi"], + di: reg = ["di", "edi", "rdi"], + r8: reg = ["r8", "r8w", "r8d"] % x86_64_only, + r9: reg = ["r9", "r9w", "r9d"] % x86_64_only, + r10: reg = ["r10", "r10w", "r10d"] % x86_64_only, + r11: reg = ["r11", "r11w", "r11d"] % x86_64_only, + r12: reg = ["r12", "r12w", "r12d"] % x86_64_only, + r13: reg = ["r13", "r13w", "r13d"] % x86_64_only, + r14: reg = ["r14", "r14w", "r14d"] % x86_64_only, + r15: reg = ["r15", "r15w", "r15d"] % x86_64_only, + al: reg_byte = ["al"], + ah: reg_byte = ["ah"], + bl: reg_byte = ["bl"], + bh: reg_byte = ["bh"], + cl: reg_byte = ["cl"], + ch: reg_byte = ["ch"], + dl: reg_byte = ["dl"], + dh: reg_byte = ["dh"], + sil: reg_byte = ["sil"] % x86_64_only, + dil: reg_byte = ["dil"] % x86_64_only, + r8b: reg_byte = ["r8b"] % x86_64_only, + r9b: reg_byte = ["r9b"] % x86_64_only, + r10b: reg_byte = ["r10b"] % x86_64_only, + r11b: reg_byte = ["r11b"] % x86_64_only, + r12b: reg_byte = ["r12b"] % x86_64_only, + r13b: reg_byte = ["r13b"] % x86_64_only, + r14b: reg_byte = ["r14b"] % x86_64_only, + r15b: reg_byte = ["r15b"] % x86_64_only, xmm0: xmm_reg = ["xmm0"], xmm1: xmm_reg = ["xmm1"], xmm2: xmm_reg = ["xmm2"], @@ -224,8 +244,6 @@ def_regs! { k5: kreg = ["k5"], k6: kreg = ["k6"], k7: kreg = ["k7"], - #error = ["ah", "bh", "ch", "dh"] => - "high byte registers are not currently supported as operands for inline asm", #error = ["bp", "bpl", "ebp", "rbp"] => "the frame pointer cannot be used as an operand for inline asm", #error = ["sp", "spl", "esp", "rsp"] => @@ -281,6 +299,8 @@ impl X86InlineAsmReg { 'r' => write!(out, "r{}", index), _ => unreachable!(), } + } else if self as u32 <= Self::r15b as u32 { + out.write_str(self.name()) } else if self as u32 <= Self::xmm15 as u32 { let prefix = modifier.unwrap_or('x'); let index = self as u32 - Self::xmm0 as u32; @@ -301,8 +321,39 @@ impl X86InlineAsmReg { pub fn overlapping_regs(self, mut cb: impl FnMut(X86InlineAsmReg)) { macro_rules! reg_conflicts { - ($($x:ident : $y:ident : $z:ident,)*) => { + ( + $( + $w:ident : $l:ident $h:ident + ),*; + $( + $w2:ident : $l2:ident + ),*; + $( + $x:ident : $y:ident : $z:ident + ),*; + ) => { match self { + $( + Self::$w => { + cb(Self::$w); + cb(Self::$l); + cb(Self::$h); + } + Self::$l => { + cb(Self::$w); + cb(Self::$l); + } + Self::$h => { + cb(Self::$w); + cb(Self::$h); + } + )* + $( + Self::$w2 | Self::$l2 => { + cb(Self::$w2); + cb(Self::$l2); + } + )* $( Self::$x | Self::$y | Self::$z => { cb(Self::$x); @@ -324,6 +375,20 @@ impl X86InlineAsmReg { // registers are only available with AVX-512, so we just specify them // as aliases directly. reg_conflicts! { + ax : al ah, + bx : bl bh, + cx : cl ch, + dx : dl dh; + si : sil, + di : dil, + r8 : r8b, + r9 : r9b, + r10 : r10b, + r11 : r11b, + r12 : r12b, + r13 : r13b, + r14 : r14b, + r15 : r15b; xmm0 : ymm0 : zmm0, xmm1 : ymm1 : zmm1, xmm2 : ymm2 : zmm2, @@ -339,7 +404,7 @@ impl X86InlineAsmReg { xmm12 : ymm12 : zmm12, xmm13 : ymm13 : zmm13, xmm14 : ymm14 : zmm14, - xmm15 : ymm15 : zmm15, + xmm15 : ymm15 : zmm15; } } } diff --git a/src/test/assembly/asm/x86-types.rs b/src/test/assembly/asm/x86-types.rs index d2819ac88e7..508d6801580 100644 --- a/src/test/assembly/asm/x86-types.rs +++ b/src/test/assembly/asm/x86-types.rs @@ -266,13 +266,6 @@ macro_rules! check { }; } -// CHECK-LABEL: reg_i8: -// CHECK: #APP -// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} -// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} -// CHECK: #NO_APP -check!(reg_i8 i8 reg "mov"); - // CHECK-LABEL: reg_i16: // CHECK: #APP // x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} @@ -315,13 +308,6 @@ check!(reg_f64 f64 reg "mov"); // CHECK: #NO_APP check!(reg_ptr ptr reg "mov"); -// CHECK-LABEL: reg_abcd_i8: -// CHECK: #APP -// x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} -// i686: mov e{{[a-z0-9]+}}, e{{[a-z0-9]+}} -// CHECK: #NO_APP -check!(reg_abcd_i8 i8 reg_abcd "mov"); - // CHECK-LABEL: reg_abcd_i16: // CHECK: #APP // x86_64: mov r{{[a-z0-9]+}}, r{{[a-z0-9]+}} @@ -364,6 +350,12 @@ check!(reg_abcd_f64 f64 reg_abcd "mov"); // CHECK: #NO_APP check!(reg_abcd_ptr ptr reg_abcd "mov"); +// CHECK-LABEL: reg_byte: +// CHECK: #APP +// CHECK: mov {{[a-z0-9]+}}, {{[a-z0-9]+}} +// CHECK: #NO_APP +check!(reg_byte i8 reg_byte "mov"); + // CHECK-LABEL: xmm_reg_i32: // CHECK: #APP // CHECK: movaps xmm{{[0-9]+}}, xmm{{[0-9]+}} diff --git a/src/test/ui/asm/bad-reg.rs b/src/test/ui/asm/bad-reg.rs index ed7faa4b156..016ea9329c4 100644 --- a/src/test/ui/asm/bad-reg.rs +++ b/src/test/ui/asm/bad-reg.rs @@ -25,8 +25,6 @@ fn main() { //~^ ERROR register class `zmm_reg` requires the `avx512f` target feature asm!("", in("zmm0") foo); //~^ ERROR register class `zmm_reg` requires the `avx512f` target feature - asm!("", in("ah") foo); - //~^ ERROR invalid register `ah`: high byte registers are not currently supported asm!("", in("ebp") foo); //~^ ERROR invalid register `ebp`: the frame pointer cannot be used as an operand asm!("", in("rsp") foo); @@ -44,7 +42,7 @@ fn main() { // (except in/lateout which don't conflict) asm!("", in("eax") foo, in("al") bar); - //~^ ERROR register `ax` conflicts with register `ax` + //~^ ERROR register `al` conflicts with register `ax` asm!("", in("rax") foo, out("rax") bar); //~^ ERROR register `ax` conflicts with register `ax` asm!("", in("al") foo, lateout("al") bar); diff --git a/src/test/ui/asm/bad-reg.stderr b/src/test/ui/asm/bad-reg.stderr index a9d872dae41..a1423f0e9c1 100644 --- a/src/test/ui/asm/bad-reg.stderr +++ b/src/test/ui/asm/bad-reg.stderr @@ -58,58 +58,52 @@ error: register class `zmm_reg` requires the `avx512f` target feature LL | asm!("", in("zmm0") foo); | ^^^^^^^^^^^^^^ -error: invalid register `ah`: high byte registers are not currently supported as operands for inline asm - --> $DIR/bad-reg.rs:28:18 - | -LL | asm!("", in("ah") foo); - | ^^^^^^^^^^^^ - error: invalid register `ebp`: the frame pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:30:18 + --> $DIR/bad-reg.rs:28:18 | LL | asm!("", in("ebp") foo); | ^^^^^^^^^^^^^ error: invalid register `rsp`: the stack pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:32:18 + --> $DIR/bad-reg.rs:30:18 | LL | asm!("", in("rsp") foo); | ^^^^^^^^^^^^^ error: invalid register `ip`: the instruction pointer cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:34:18 + --> $DIR/bad-reg.rs:32:18 | LL | asm!("", in("ip") foo); | ^^^^^^^^^^^^ error: invalid register `st(2)`: x87 registers are not currently supported as operands for inline asm - --> $DIR/bad-reg.rs:36:18 + --> $DIR/bad-reg.rs:34:18 | LL | asm!("", in("st(2)") foo); | ^^^^^^^^^^^^^^^ error: invalid register `mm0`: MMX registers are not currently supported as operands for inline asm - --> $DIR/bad-reg.rs:38:18 + --> $DIR/bad-reg.rs:36:18 | LL | asm!("", in("mm0") foo); | ^^^^^^^^^^^^^ error: invalid register `k0`: the k0 AVX mask register cannot be used as an operand for inline asm - --> $DIR/bad-reg.rs:40:18 + --> $DIR/bad-reg.rs:38:18 | LL | asm!("", in("k0") foo); | ^^^^^^^^^^^^ -error: register `ax` conflicts with register `ax` - --> $DIR/bad-reg.rs:46:33 +error: register `al` conflicts with register `ax` + --> $DIR/bad-reg.rs:44:33 | LL | asm!("", in("eax") foo, in("al") bar); - | ------------- ^^^^^^^^^^^^ register `ax` + | ------------- ^^^^^^^^^^^^ register `al` | | | register `ax` error: register `ax` conflicts with register `ax` - --> $DIR/bad-reg.rs:48:33 + --> $DIR/bad-reg.rs:46:33 | LL | asm!("", in("rax") foo, out("rax") bar); | ------------- ^^^^^^^^^^^^^^ register `ax` @@ -117,13 +111,13 @@ LL | asm!("", in("rax") foo, out("rax") bar); | register `ax` | help: use `lateout` instead of `out` to avoid conflict - --> $DIR/bad-reg.rs:48:18 + --> $DIR/bad-reg.rs:46:18 | LL | asm!("", in("rax") foo, out("rax") bar); | ^^^^^^^^^^^^^ error: register `ymm0` conflicts with register `xmm0` - --> $DIR/bad-reg.rs:51:34 + --> $DIR/bad-reg.rs:49:34 | LL | asm!("", in("xmm0") foo, in("ymm0") bar); | -------------- ^^^^^^^^^^^^^^ register `ymm0` @@ -131,7 +125,7 @@ LL | asm!("", in("xmm0") foo, in("ymm0") bar); | register `xmm0` error: register `ymm0` conflicts with register `xmm0` - --> $DIR/bad-reg.rs:53:34 + --> $DIR/bad-reg.rs:51:34 | LL | asm!("", in("xmm0") foo, out("ymm0") bar); | -------------- ^^^^^^^^^^^^^^^ register `ymm0` @@ -139,10 +133,10 @@ LL | asm!("", in("xmm0") foo, out("ymm0") bar); | register `xmm0` | help: use `lateout` instead of `out` to avoid conflict - --> $DIR/bad-reg.rs:53:18 + --> $DIR/bad-reg.rs:51:18 | LL | asm!("", in("xmm0") foo, out("ymm0") bar); | ^^^^^^^^^^^^^^ -error: aborting due to 19 previous errors +error: aborting due to 18 previous errors diff --git a/src/test/ui/asm/type-check-3.rs b/src/test/ui/asm/type-check-3.rs index 750d28026d7..5de15fe4906 100644 --- a/src/test/ui/asm/type-check-3.rs +++ b/src/test/ui/asm/type-check-3.rs @@ -29,7 +29,7 @@ fn main() { // Template modifier suggestions for sub-registers - asm!("{0} {0}", in(reg) 0i8); + asm!("{0} {0}", in(reg) 0i16); //~^ WARN formatting may not be suitable for sub-register argument asm!("{0} {0:x}", in(reg) 0i16); //~^ WARN formatting may not be suitable for sub-register argument @@ -39,23 +39,26 @@ fn main() { asm!("{}", in(ymm_reg) 0i64); //~^ WARN formatting may not be suitable for sub-register argument asm!("{}", in(ymm_reg) _mm256_setzero_ps()); - asm!("{:l}", in(reg) 0i8); asm!("{:l}", in(reg) 0i16); asm!("{:l}", in(reg) 0i32); asm!("{:l}", in(reg) 0i64); asm!("{:x}", in(ymm_reg) 0i64); asm!("{:x}", in(ymm_reg) _mm256_setzero_ps()); + // Suggest different register class for type + + asm!("{}", in(reg) 0i8); + //~^ ERROR type `i8` cannot be used with this register class + asm!("{}", in(reg_byte) 0i8); + // Split inout operands must have compatible types - let mut val_i8: i8; + let mut val_i16: i16; let mut val_f32: f32; let mut val_u32: u32; let mut val_u64: u64; let mut val_ptr: *mut u8; - asm!("{:r}", inout(reg) 0u8 => val_i8); - asm!("{:r}", inout(reg) 0u16 => val_i8); - //~^ ERROR incompatible types for asm inout argument + asm!("{:r}", inout(reg) 0u16 => val_i16); asm!("{:r}", inout(reg) 0u32 => val_f32); //~^ ERROR incompatible types for asm inout argument asm!("{:r}", inout(reg) 0u32 => val_ptr); diff --git a/src/test/ui/asm/type-check-3.stderr b/src/test/ui/asm/type-check-3.stderr index ccc795d1013..01dbe78db88 100644 --- a/src/test/ui/asm/type-check-3.stderr +++ b/src/test/ui/asm/type-check-3.stderr @@ -4,7 +4,7 @@ error: type `i128` cannot be used with this register class LL | asm!("{}", in(reg) 0i128); | ^^^^^ | - = note: register class `reg` supports these types: i8, i16, i32, i64, f32, f64 + = note: register class `reg` supports these types: i16, i32, i64, f32, f64 error: type `std::arch::x86_64::__m128` cannot be used with this register class --> $DIR/type-check-3.rs:14:28 @@ -12,7 +12,7 @@ error: type `std::arch::x86_64::__m128` cannot be used with this register class LL | asm!("{}", in(reg) _mm_setzero_ps()); | ^^^^^^^^^^^^^^^^ | - = note: register class `reg` supports these types: i8, i16, i32, i64, f32, f64 + = note: register class `reg` supports these types: i16, i32, i64, f32, f64 error: type `std::arch::x86_64::__m256` cannot be used with this register class --> $DIR/type-check-3.rs:16:28 @@ -20,7 +20,7 @@ error: type `std::arch::x86_64::__m256` cannot be used with this register class LL | asm!("{}", in(reg) _mm256_setzero_ps()); | ^^^^^^^^^^^^^^^^^^^ | - = note: register class `reg` supports these types: i8, i16, i32, i64, f32, f64 + = note: register class `reg` supports these types: i16, i32, i64, f32, f64 error: type `u8` cannot be used with this register class --> $DIR/type-check-3.rs:18:32 @@ -41,11 +41,11 @@ LL | asm!("{}", in(kreg) 0u64); warning: formatting may not be suitable for sub-register argument --> $DIR/type-check-3.rs:32:15 | -LL | asm!("{0} {0}", in(reg) 0i8); - | ^^^ ^^^ --- for this argument +LL | asm!("{0} {0}", in(reg) 0i16); + | ^^^ ^^^ ---- for this argument | = note: `#[warn(asm_sub_register)]` on by default - = help: use the `l` modifier to have the register formatted as `al` + = help: use the `x` modifier to have the register formatted as `ax` = help: or use the `r` modifier to keep the default formatting of `rax` warning: formatting may not be suitable for sub-register argument @@ -75,18 +75,17 @@ LL | asm!("{}", in(ymm_reg) 0i64); = help: use the `x` modifier to have the register formatted as `xmm0` = help: or use the `y` modifier to keep the default formatting of `ymm0` -error: incompatible types for asm inout argument - --> $DIR/type-check-3.rs:57:33 +error: type `i8` cannot be used with this register class + --> $DIR/type-check-3.rs:50:28 | -LL | asm!("{:r}", inout(reg) 0u16 => val_i8); - | ^^^^ ^^^^^^ type `i8` - | | - | type `u16` +LL | asm!("{}", in(reg) 0i8); + | ^^^ | - = note: asm inout arguments must have the same type, unless they are both pointers or integers of the same size + = note: register class `reg` supports these types: i16, i32, i64, f32, f64 + = help: consider using the `reg_byte` register class instead error: incompatible types for asm inout argument - --> $DIR/type-check-3.rs:59:33 + --> $DIR/type-check-3.rs:62:33 | LL | asm!("{:r}", inout(reg) 0u32 => val_f32); | ^^^^ ^^^^^^^ type `f32` @@ -96,7 +95,7 @@ LL | asm!("{:r}", inout(reg) 0u32 => val_f32); = note: asm inout arguments must have the same type, unless they are both pointers or integers of the same size error: incompatible types for asm inout argument - --> $DIR/type-check-3.rs:61:33 + --> $DIR/type-check-3.rs:64:33 | LL | asm!("{:r}", inout(reg) 0u32 => val_ptr); | ^^^^ ^^^^^^^ type `*mut u8` @@ -106,7 +105,7 @@ LL | asm!("{:r}", inout(reg) 0u32 => val_ptr); = note: asm inout arguments must have the same type, unless they are both pointers or integers of the same size error: incompatible types for asm inout argument - --> $DIR/type-check-3.rs:63:33 + --> $DIR/type-check-3.rs:66:33 | LL | asm!("{:r}", inout(reg) main => val_u32); | ^^^^ ^^^^^^^ type `u32` From 19a0d14b5c5bc9d2f9a9c305a00844ad2b0d62f5 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 1 May 2020 20:51:54 +0100 Subject: [PATCH 15/36] Add notes about functions that are not currently used --- src/librustc_target/asm/mod.rs | 8 ++++++-- src/librustc_target/asm/riscv.rs | 1 + src/librustc_target/asm/x86.rs | 23 +++++++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/librustc_target/asm/mod.rs b/src/librustc_target/asm/mod.rs index f1e8457cacc..2a0c51f2afd 100644 --- a/src/librustc_target/asm/mod.rs +++ b/src/librustc_target/asm/mod.rs @@ -87,7 +87,7 @@ macro_rules! def_regs { match name { $( $($alias)|* | $reg_name => { - $($filter(_arch, &mut _has_feature)?;)? + $($filter(_arch, &mut _has_feature, false)?;)? Ok(Self::$reg) } )* @@ -109,7 +109,7 @@ macro_rules! def_regs { ) { use super::{InlineAsmReg, InlineAsmRegClass}; $( - if $($filter(_arch, &mut _has_feature).is_ok() &&)? true { + if $($filter(_arch, &mut _has_feature, true).is_ok() &&)? true { if let Some(set) = map.get_mut(&InlineAsmRegClass::$arch($arch_regclass::$class)) { set.insert(InlineAsmReg::$arch($arch_reg::$reg)); } @@ -239,6 +239,8 @@ impl InlineAsmReg { }) } + // NOTE: This function isn't used at the moment, but is needed to support + // falling back to an external assembler. pub fn emit( self, out: &mut dyn fmt::Write, @@ -542,6 +544,8 @@ impl fmt::Display for InlineAsmType { /// registers in each register class. A particular register may be allocatable /// from multiple register classes, in which case it will appear multiple times /// in the map. +// NOTE: This function isn't used at the moment, but is needed to support +// falling back to an external assembler. pub fn allocatable_registers( arch: InlineAsmArch, has_feature: impl FnMut(&str) -> bool, diff --git a/src/librustc_target/asm/riscv.rs b/src/librustc_target/asm/riscv.rs index 7da30cc8875..3ff542247ff 100644 --- a/src/librustc_target/asm/riscv.rs +++ b/src/librustc_target/asm/riscv.rs @@ -50,6 +50,7 @@ impl RiscVInlineAsmRegClass { fn not_e( _arch: InlineAsmArch, mut has_feature: impl FnMut(&str) -> bool, + _allocating: bool, ) -> Result<(), &'static str> { if has_feature("e") { Err("register can't be used with the `e` target feature") diff --git a/src/librustc_target/asm/x86.rs b/src/librustc_target/asm/x86.rs index 6eb99b0180f..065bb14966f 100644 --- a/src/librustc_target/asm/x86.rs +++ b/src/librustc_target/asm/x86.rs @@ -131,6 +131,7 @@ impl X86InlineAsmRegClass { fn x86_64_only( arch: InlineAsmArch, _has_feature: impl FnMut(&str) -> bool, + _allocating: bool, ) -> Result<(), &'static str> { match arch { InlineAsmArch::X86 => Err("register is only available on x86_64"), @@ -139,6 +140,20 @@ fn x86_64_only( } } +fn high_byte( + arch: InlineAsmArch, + _has_feature: impl FnMut(&str) -> bool, + allocating: bool, +) -> Result<(), &'static str> { + match arch { + InlineAsmArch::X86_64 if allocating => { + // The error message isn't actually used... + Err("high byte registers are not allocated by reg_byte") + } + _ => Ok(()), + } +} + def_regs! { X86 X86InlineAsmReg X86InlineAsmRegClass { ax: reg, reg_abcd = ["ax", "eax", "rax"], @@ -156,13 +171,13 @@ def_regs! { r14: reg = ["r14", "r14w", "r14d"] % x86_64_only, r15: reg = ["r15", "r15w", "r15d"] % x86_64_only, al: reg_byte = ["al"], - ah: reg_byte = ["ah"], + ah: reg_byte = ["ah"] % high_byte, bl: reg_byte = ["bl"], - bh: reg_byte = ["bh"], + bh: reg_byte = ["bh"] % high_byte, cl: reg_byte = ["cl"], - ch: reg_byte = ["ch"], + ch: reg_byte = ["ch"] % high_byte, dl: reg_byte = ["dl"], - dh: reg_byte = ["dh"], + dh: reg_byte = ["dh"] % high_byte, sil: reg_byte = ["sil"] % x86_64_only, dil: reg_byte = ["dil"] % x86_64_only, r8b: reg_byte = ["r8b"] % x86_64_only, From 3590f4cf5745b609fbdbb8b4249f93a3df81c80e Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 1 May 2020 23:23:55 +0100 Subject: [PATCH 16/36] Work around more LLVM limitations --- src/librustc_codegen_llvm/asm.rs | 77 +++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/librustc_codegen_llvm/asm.rs b/src/librustc_codegen_llvm/asm.rs index 20abfbcf405..861a756a2ef 100644 --- a/src/librustc_codegen_llvm/asm.rs +++ b/src/librustc_codegen_llvm/asm.rs @@ -409,7 +409,7 @@ fn reg_to_llvm(reg: InlineAsmRegOrRegClass) -> String { InlineAsmRegClass::RiscV(RiscVInlineAsmRegClass::freg) => "f", InlineAsmRegClass::X86(X86InlineAsmRegClass::reg) => "r", InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd) => "Q", - InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_byte) => "r", + InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_byte) => "q", InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg) | InlineAsmRegClass::X86(X86InlineAsmRegClass::ymm_reg) => "x", InlineAsmRegClass::X86(X86InlineAsmRegClass::zmm_reg) => "v", @@ -558,6 +558,31 @@ fn llvm_fixup_input( let indices: Vec<_> = (0..count * 2).map(|x| bx.const_i32(x as i32)).collect(); bx.shuffle_vector(value, bx.const_undef(vec_ty), bx.const_vector(&indices)) } + (InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd), Abi::Scalar(s)) + if s.value == Primitive::F64 => + { + bx.bitcast(value, bx.cx.type_i64()) + } + ( + InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg | X86InlineAsmRegClass::zmm_reg), + Abi::Vector { .. }, + ) if layout.size.bytes() == 64 => bx.bitcast(value, bx.cx.type_vector(bx.cx.type_f64(), 8)), + ( + InlineAsmRegClass::Arm( + ArmInlineAsmRegClass::sreg_low16 + | ArmInlineAsmRegClass::dreg_low8 + | ArmInlineAsmRegClass::qreg_low4 + | ArmInlineAsmRegClass::dreg + | ArmInlineAsmRegClass::qreg, + ), + Abi::Scalar(s), + ) => { + if let Primitive::Int(Integer::I32, _) = s.value { + bx.bitcast(value, bx.cx.type_f32()) + } else { + value + } + } _ => value, } } @@ -593,6 +618,31 @@ fn llvm_fixup_output( let indices: Vec<_> = (0..*count).map(|x| bx.const_i32(x as i32)).collect(); bx.shuffle_vector(value, bx.const_undef(vec_ty), bx.const_vector(&indices)) } + (InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd), Abi::Scalar(s)) + if s.value == Primitive::F64 => + { + bx.bitcast(value, bx.cx.type_f64()) + } + ( + InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg | X86InlineAsmRegClass::zmm_reg), + Abi::Vector { .. }, + ) if layout.size.bytes() == 64 => bx.bitcast(value, layout.llvm_type(bx.cx)), + ( + InlineAsmRegClass::Arm( + ArmInlineAsmRegClass::sreg_low16 + | ArmInlineAsmRegClass::dreg_low8 + | ArmInlineAsmRegClass::qreg_low4 + | ArmInlineAsmRegClass::dreg + | ArmInlineAsmRegClass::qreg, + ), + Abi::Scalar(s), + ) => { + if let Primitive::Int(Integer::I32, _) = s.value { + bx.bitcast(value, bx.cx.type_i32()) + } else { + value + } + } _ => value, } } @@ -623,6 +673,31 @@ fn llvm_fixup_output_type( let elem_ty = llvm_asm_scalar_type(cx, element); cx.type_vector(elem_ty, count * 2) } + (InlineAsmRegClass::X86(X86InlineAsmRegClass::reg_abcd), Abi::Scalar(s)) + if s.value == Primitive::F64 => + { + cx.type_i64() + } + ( + InlineAsmRegClass::X86(X86InlineAsmRegClass::xmm_reg | X86InlineAsmRegClass::zmm_reg), + Abi::Vector { .. }, + ) if layout.size.bytes() == 64 => cx.type_vector(cx.type_f64(), 8), + ( + InlineAsmRegClass::Arm( + ArmInlineAsmRegClass::sreg_low16 + | ArmInlineAsmRegClass::dreg_low8 + | ArmInlineAsmRegClass::qreg_low4 + | ArmInlineAsmRegClass::dreg + | ArmInlineAsmRegClass::qreg, + ), + Abi::Scalar(s), + ) => { + if let Primitive::Int(Integer::I32, _) = s.value { + cx.type_f32() + } else { + layout.llvm_type(cx) + } + } _ => layout.llvm_type(cx), } } From 08822546a5656bc5a60831513be75e3e24ca54c0 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 1 May 2020 23:59:15 +0100 Subject: [PATCH 17/36] Implement att_syntax option --- src/librustc_ast_lowering/expr.rs | 9 +++++++++ src/librustc_ast_pretty/pprust.rs | 3 +++ src/librustc_builtin_macros/asm.rs | 6 ++++-- src/librustc_codegen_llvm/asm.rs | 6 +++++- src/librustc_hir_pretty/lib.rs | 3 +++ src/librustc_span/symbol.rs | 1 + src/librustc_target/asm/mod.rs | 1 + src/test/ui/asm/parse-error.stderr | 8 ++++---- 8 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/librustc_ast_lowering/expr.rs b/src/librustc_ast_lowering/expr.rs index f713db9a6cc..72ca8418f83 100644 --- a/src/librustc_ast_lowering/expr.rs +++ b/src/librustc_ast_lowering/expr.rs @@ -980,6 +980,15 @@ impl<'hir> LoweringContext<'_, 'hir> { struct_span_err!(self.sess, sp, E0472, "asm! is unsupported on this target").emit(); return hir::ExprKind::Err; }; + if asm.options.contains(asm::InlineAsmOptions::ATT_SYNTAX) { + match asm_arch { + asm::InlineAsmArch::X86 | asm::InlineAsmArch::X86_64 => {} + _ => self + .sess + .struct_span_err(sp, "the `att_syntax` option is only supported on x86") + .emit(), + } + } // Lower operands to HIR, filter_map skips any operands with invalid // register classes. diff --git a/src/librustc_ast_pretty/pprust.rs b/src/librustc_ast_pretty/pprust.rs index 1f371f15f18..432f8d47276 100644 --- a/src/librustc_ast_pretty/pprust.rs +++ b/src/librustc_ast_pretty/pprust.rs @@ -2118,6 +2118,9 @@ impl<'a> State<'a> { if opts.contains(InlineAsmOptions::NOSTACK) { options.push("nostack"); } + if opts.contains(InlineAsmOptions::ATT_SYNTAX) { + options.push("att_syntax"); + } s.commasep(Inconsistent, &options, |s, &opt| { s.word(opt); }); diff --git a/src/librustc_builtin_macros/asm.rs b/src/librustc_builtin_macros/asm.rs index c9a3401cac0..bc068cdcde0 100644 --- a/src/librustc_builtin_macros/asm.rs +++ b/src/librustc_builtin_macros/asm.rs @@ -279,9 +279,11 @@ fn parse_options<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> Result<(), Diagn args.options |= InlineAsmOptions::PRESERVES_FLAGS; } else if p.eat(&token::Ident(sym::noreturn, false)) { args.options |= InlineAsmOptions::NORETURN; - } else { - p.expect(&token::Ident(sym::nostack, false))?; + } else if p.eat(&token::Ident(sym::nostack, false)) { args.options |= InlineAsmOptions::NOSTACK; + } else { + p.expect(&token::Ident(sym::att_syntax, false))?; + args.options |= InlineAsmOptions::ATT_SYNTAX; } // Allow trailing commas diff --git a/src/librustc_codegen_llvm/asm.rs b/src/librustc_codegen_llvm/asm.rs index 861a756a2ef..0ad304d5a67 100644 --- a/src/librustc_codegen_llvm/asm.rs +++ b/src/librustc_codegen_llvm/asm.rs @@ -269,7 +269,11 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> { tys => self.type_struct(&tys, false), }; let dialect = match asm_arch { - InlineAsmArch::X86 | InlineAsmArch::X86_64 => LlvmAsmDialect::Intel, + InlineAsmArch::X86 | InlineAsmArch::X86_64 + if !options.contains(InlineAsmOptions::ATT_SYNTAX) => + { + LlvmAsmDialect::Intel + } _ => LlvmAsmDialect::Att, }; let result = inline_asm_call( diff --git a/src/librustc_hir_pretty/lib.rs b/src/librustc_hir_pretty/lib.rs index 918b5901375..312b8edfe23 100644 --- a/src/librustc_hir_pretty/lib.rs +++ b/src/librustc_hir_pretty/lib.rs @@ -1503,6 +1503,9 @@ impl<'a> State<'a> { if opts.contains(InlineAsmOptions::NOSTACK) { options.push("nostack"); } + if opts.contains(InlineAsmOptions::ATT_SYNTAX) { + options.push("att_syntax"); + } s.commasep(Inconsistent, &options, |s, &opt| { s.word(opt); }); diff --git a/src/librustc_span/symbol.rs b/src/librustc_span/symbol.rs index 1f0764b06b0..a38f5949204 100644 --- a/src/librustc_span/symbol.rs +++ b/src/librustc_span/symbol.rs @@ -160,6 +160,7 @@ symbols! { attr, attributes, attr_literals, + att_syntax, augmented_assignments, automatically_derived, avx512_target_feature, diff --git a/src/librustc_target/asm/mod.rs b/src/librustc_target/asm/mod.rs index 2a0c51f2afd..5bdfdfefeda 100644 --- a/src/librustc_target/asm/mod.rs +++ b/src/librustc_target/asm/mod.rs @@ -426,6 +426,7 @@ bitflags::bitflags! { const PRESERVES_FLAGS = 1 << 3; const NORETURN = 1 << 4; const NOSTACK = 1 << 5; + const ATT_SYNTAX = 1 << 6; } } diff --git a/src/test/ui/asm/parse-error.stderr b/src/test/ui/asm/parse-error.stderr index 94eb9d29862..fa422f56bec 100644 --- a/src/test/ui/asm/parse-error.stderr +++ b/src/test/ui/asm/parse-error.stderr @@ -64,11 +64,11 @@ error: argument to `sym` must be a path expression LL | asm!("{}", sym foo + bar); | ^^^^^^^^^ -error: expected one of `)`, `nomem`, `noreturn`, `nostack`, `preserves_flags`, `pure`, or `readonly`, found `foo` +error: expected one of `)`, `att_syntax`, `nomem`, `noreturn`, `nostack`, `preserves_flags`, `pure`, or `readonly`, found `foo` --> $DIR/parse-error.rs:31:26 | LL | asm!("", options(foo)); - | ^^^ expected one of 7 possible tokens + | ^^^ expected one of 8 possible tokens error: expected one of `)` or `,`, found `foo` --> $DIR/parse-error.rs:33:32 @@ -76,11 +76,11 @@ error: expected one of `)` or `,`, found `foo` LL | asm!("", options(nomem foo)); | ^^^ expected one of `)` or `,` -error: expected one of `)`, `nomem`, `noreturn`, `nostack`, `preserves_flags`, `pure`, or `readonly`, found `foo` +error: expected one of `)`, `att_syntax`, `nomem`, `noreturn`, `nostack`, `preserves_flags`, `pure`, or `readonly`, found `foo` --> $DIR/parse-error.rs:35:33 | LL | asm!("", options(nomem, foo)); - | ^^^ expected one of 7 possible tokens + | ^^^ expected one of 8 possible tokens error: asm options cannot be specified multiple times --> $DIR/parse-error.rs:37:29 From 9ac4ef40d43c24120314baa7861b2c6086a8e67e Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Sat, 2 May 2020 00:28:51 +0100 Subject: [PATCH 18/36] Update llvm-project submodule --- src/llvm-project | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm-project b/src/llvm-project index 3ba91917e52..e7aa4d5ac99 160000 --- a/src/llvm-project +++ b/src/llvm-project @@ -1 +1 @@ -Subproject commit 3ba91917e52bd66ac37161ad4a1bc87d32aa2e18 +Subproject commit e7aa4d5ac9950ac77c970609c83e66f9ef65c763 From ddcdea45b6cda159a5a1fbf729105b788ec725e0 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 6 May 2020 11:41:37 +0100 Subject: [PATCH 19/36] The h modifier is only supported by reg_abcd --- src/librustc_target/asm/x86.rs | 2 +- src/test/assembly/asm/x86-modifiers.rs | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/librustc_target/asm/x86.rs b/src/librustc_target/asm/x86.rs index 065bb14966f..c3dd7bc8e7b 100644 --- a/src/librustc_target/asm/x86.rs +++ b/src/librustc_target/asm/x86.rs @@ -19,7 +19,7 @@ impl X86InlineAsmRegClass { match self { Self::reg => { if arch == InlineAsmArch::X86_64 { - &['l', 'h', 'x', 'e', 'r'] + &['l', 'x', 'e', 'r'] } else { &['x', 'e'] } diff --git a/src/test/assembly/asm/x86-modifiers.rs b/src/test/assembly/asm/x86-modifiers.rs index 460e22aa69e..1670744291f 100644 --- a/src/test/assembly/asm/x86-modifiers.rs +++ b/src/test/assembly/asm/x86-modifiers.rs @@ -63,13 +63,6 @@ check!(reg "" reg "mov"); #[cfg(x86_64)] check!(reg_l "l" reg "mov"); -// x86_64-LABEL: reg_h: -// x86_64: #APP -// x86_64: mov ah, ah -// x86_64: #NO_APP -#[cfg(x86_64)] -check!(reg_h "h" reg "mov"); - // CHECK-LABEL: reg_x: // CHECK: #APP // CHECK: mov ax, ax From 330bdf89b114a22f2509f27f5e2f4a9db309d2a4 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 6 May 2020 12:04:22 +0100 Subject: [PATCH 20/36] Disable asm tests on system llvm --- src/test/assembly/asm/aarch64-modifiers.rs | 1 + src/test/assembly/asm/aarch64-types.rs | 1 + src/test/assembly/asm/arm-modifiers.rs | 1 + src/test/assembly/asm/arm-types.rs | 1 + src/test/assembly/asm/riscv-modifiers.rs | 1 + src/test/assembly/asm/riscv-types.rs | 1 + src/test/assembly/asm/x86-types.rs | 1 + 7 files changed, 7 insertions(+) diff --git a/src/test/assembly/asm/aarch64-modifiers.rs b/src/test/assembly/asm/aarch64-modifiers.rs index d4519ca7ffa..c2484e9b6d0 100644 --- a/src/test/assembly/asm/aarch64-modifiers.rs +++ b/src/test/assembly/asm/aarch64-modifiers.rs @@ -1,3 +1,4 @@ +// no-system-llvm // assembly-output: emit-asm // compile-flags: -O // compile-flags: --target aarch64-unknown-linux-gnu diff --git a/src/test/assembly/asm/aarch64-types.rs b/src/test/assembly/asm/aarch64-types.rs index 8b763462e4a..ce2f0082a06 100644 --- a/src/test/assembly/asm/aarch64-types.rs +++ b/src/test/assembly/asm/aarch64-types.rs @@ -1,3 +1,4 @@ +// no-system-llvm // assembly-output: emit-asm // compile-flags: --target aarch64-unknown-linux-gnu diff --git a/src/test/assembly/asm/arm-modifiers.rs b/src/test/assembly/asm/arm-modifiers.rs index cefab9c4a67..b71503d0a53 100644 --- a/src/test/assembly/asm/arm-modifiers.rs +++ b/src/test/assembly/asm/arm-modifiers.rs @@ -1,3 +1,4 @@ +// no-system-llvm // assembly-output: emit-asm // compile-flags: -O // compile-flags: --target armv7-unknown-linux-gnueabihf diff --git a/src/test/assembly/asm/arm-types.rs b/src/test/assembly/asm/arm-types.rs index 729adae66f6..1e338f56c4d 100644 --- a/src/test/assembly/asm/arm-types.rs +++ b/src/test/assembly/asm/arm-types.rs @@ -1,3 +1,4 @@ +// no-system-llvm // assembly-output: emit-asm // compile-flags: --target armv7-unknown-linux-gnueabihf // compile-flags: -C target-feature=+neon diff --git a/src/test/assembly/asm/riscv-modifiers.rs b/src/test/assembly/asm/riscv-modifiers.rs index 622d0c0dc5e..8c816e3220b 100644 --- a/src/test/assembly/asm/riscv-modifiers.rs +++ b/src/test/assembly/asm/riscv-modifiers.rs @@ -1,3 +1,4 @@ +// no-system-llvm // assembly-output: emit-asm // compile-flags: -O // compile-flags: --target riscv64gc-unknown-linux-gnu diff --git a/src/test/assembly/asm/riscv-types.rs b/src/test/assembly/asm/riscv-types.rs index 5c10753c765..449213471cc 100644 --- a/src/test/assembly/asm/riscv-types.rs +++ b/src/test/assembly/asm/riscv-types.rs @@ -1,3 +1,4 @@ +// no-system-llvm // revisions: riscv64 riscv32 // assembly-output: emit-asm //[riscv64] compile-flags: --target riscv64imac-unknown-none-elf diff --git a/src/test/assembly/asm/x86-types.rs b/src/test/assembly/asm/x86-types.rs index 508d6801580..de2e67c421f 100644 --- a/src/test/assembly/asm/x86-types.rs +++ b/src/test/assembly/asm/x86-types.rs @@ -1,3 +1,4 @@ +// no-system-llvm // revisions: x86_64 i686 // assembly-output: emit-asm //[x86_64] compile-flags: --target x86_64-unknown-linux-gnu From a656349b5560a42ad9d260307b6c37af9beb3a23 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 6 May 2020 14:46:01 +0100 Subject: [PATCH 21/36] Move InlineAsmTemplatePiece and InlineAsmOptions to librustc_ast --- Cargo.lock | 2 +- src/librustc_ast/Cargo.toml | 2 +- src/librustc_ast/ast.rs | 55 ++++++++++++++++++++- src/librustc_ast_lowering/expr.rs | 4 +- src/librustc_ast_pretty/pprust.rs | 2 +- src/librustc_builtin_macros/asm.rs | 41 ++++++++-------- src/librustc_codegen_llvm/asm.rs | 1 + src/librustc_codegen_ssa/traits/asm.rs | 3 +- src/librustc_hir/arena.rs | 2 +- src/librustc_hir/hir.rs | 3 +- src/librustc_hir_pretty/lib.rs | 19 ++++---- src/librustc_middle/mir/mod.rs | 3 +- src/librustc_middle/ty/structural_impls.rs | 4 +- src/librustc_mir_build/build/expr/into.rs | 2 +- src/librustc_mir_build/hair/mod.rs | 3 +- src/librustc_passes/intrinsicck.rs | 4 +- src/librustc_passes/liveness.rs | 2 +- src/librustc_target/asm/mod.rs | 56 +--------------------- src/librustc_target/lib.rs | 2 +- src/librustc_typeck/check/expr.rs | 3 +- 20 files changed, 108 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a9a1116e67..969f7a041d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3625,13 +3625,13 @@ dependencies = [ name = "rustc_ast" version = "0.0.0" dependencies = [ + "bitflags", "log", "rustc_data_structures", "rustc_index", "rustc_lexer", "rustc_macros", "rustc_span", - "rustc_target", "scoped-tls", "serialize", "smallvec 1.4.0", diff --git a/src/librustc_ast/Cargo.toml b/src/librustc_ast/Cargo.toml index 020e6a84d45..7d105f9e886 100644 --- a/src/librustc_ast/Cargo.toml +++ b/src/librustc_ast/Cargo.toml @@ -19,4 +19,4 @@ rustc_index = { path = "../librustc_index" } rustc_lexer = { path = "../librustc_lexer" } rustc_macros = { path = "../librustc_macros" } smallvec = { version = "1.0", features = ["union", "may_dangle"] } -rustc_target = { path = "../librustc_target" } +bitflags = "1.2.1" diff --git a/src/librustc_ast/ast.rs b/src/librustc_ast/ast.rs index 85e9438472a..7ff83572507 100644 --- a/src/librustc_ast/ast.rs +++ b/src/librustc_ast/ast.rs @@ -34,7 +34,6 @@ use rustc_serialize::{self, Decoder, Encoder}; use rustc_span::source_map::{respan, Spanned}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{Span, DUMMY_SP}; -use rustc_target::asm::{InlineAsmOptions, InlineAsmTemplatePiece}; use std::convert::TryFrom; use std::fmt; @@ -1876,6 +1875,60 @@ pub enum InlineAsmRegOrRegClass { RegClass(Symbol), } +bitflags::bitflags! { + #[derive(RustcEncodable, RustcDecodable, HashStable_Generic)] + pub struct InlineAsmOptions: u8 { + const PURE = 1 << 0; + const NOMEM = 1 << 1; + const READONLY = 1 << 2; + const PRESERVES_FLAGS = 1 << 3; + const NORETURN = 1 << 4; + const NOSTACK = 1 << 5; + const ATT_SYNTAX = 1 << 6; + } +} + +#[derive(Clone, PartialEq, RustcEncodable, RustcDecodable, Debug, HashStable_Generic)] +pub enum InlineAsmTemplatePiece { + String(String), + Placeholder { operand_idx: usize, modifier: Option, span: Span }, +} + +impl fmt::Display for InlineAsmTemplatePiece { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::String(s) => { + for c in s.chars() { + match c { + '{' => f.write_str("{{")?, + '}' => f.write_str("}}")?, + _ => write!(f, "{}", c.escape_debug())?, + } + } + Ok(()) + } + Self::Placeholder { operand_idx, modifier: Some(modifier), .. } => { + write!(f, "{{{}:{}}}", operand_idx, modifier) + } + Self::Placeholder { operand_idx, modifier: None, .. } => { + write!(f, "{{{}}}", operand_idx) + } + } + } +} + +impl InlineAsmTemplatePiece { + /// Rebuilds the asm template string from its pieces. + pub fn to_string(s: &[Self]) -> String { + use fmt::Write; + let mut out = String::new(); + for p in s.iter() { + let _ = write!(out, "{}", p); + } + out + } +} + /// Inline assembly operand. /// /// E.g., `out("eax") result` as in `asm!("mov eax, 2", out("eax") result)`. diff --git a/src/librustc_ast_lowering/expr.rs b/src/librustc_ast_lowering/expr.rs index 72ca8418f83..856387421d9 100644 --- a/src/librustc_ast_lowering/expr.rs +++ b/src/librustc_ast_lowering/expr.rs @@ -980,7 +980,7 @@ impl<'hir> LoweringContext<'_, 'hir> { struct_span_err!(self.sess, sp, E0472, "asm! is unsupported on this target").emit(); return hir::ExprKind::Err; }; - if asm.options.contains(asm::InlineAsmOptions::ATT_SYNTAX) { + if asm.options.contains(InlineAsmOptions::ATT_SYNTAX) { match asm_arch { asm::InlineAsmArch::X86 | asm::InlineAsmArch::X86_64 => {} _ => self @@ -1070,7 +1070,7 @@ impl<'hir> LoweringContext<'_, 'hir> { // Validate template modifiers against the register classes for the operands for p in &asm.template { - if let asm::InlineAsmTemplatePiece::Placeholder { + if let InlineAsmTemplatePiece::Placeholder { operand_idx, modifier: Some(modifier), span: placeholder_span, diff --git a/src/librustc_ast_pretty/pprust.rs b/src/librustc_ast_pretty/pprust.rs index 432f8d47276..872126646f3 100644 --- a/src/librustc_ast_pretty/pprust.rs +++ b/src/librustc_ast_pretty/pprust.rs @@ -5,6 +5,7 @@ use rustc_ast::ast::{self, BlockCheckMode, PatKind, RangeEnd, RangeSyntax}; use rustc_ast::ast::{Attribute, GenericArg, MacArgs}; use rustc_ast::ast::{GenericBound, SelfKind, TraitBoundModifier}; use rustc_ast::ast::{InlineAsmOperand, InlineAsmRegOrRegClass}; +use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_ast::attr; use rustc_ast::ptr::P; use rustc_ast::token::{self, BinOpToken, DelimToken, Nonterminal, Token, TokenKind}; @@ -15,7 +16,6 @@ use rustc_span::edition::Edition; use rustc_span::source_map::{SourceMap, Spanned}; use rustc_span::symbol::{kw, sym, Ident, IdentPrinter, Symbol}; use rustc_span::{BytePos, FileName, Span}; -use rustc_target::asm::{InlineAsmOptions, InlineAsmTemplatePiece}; use std::borrow::Cow; diff --git a/src/librustc_builtin_macros/asm.rs b/src/librustc_builtin_macros/asm.rs index bc068cdcde0..c2973924897 100644 --- a/src/librustc_builtin_macros/asm.rs +++ b/src/librustc_builtin_macros/asm.rs @@ -10,14 +10,13 @@ use rustc_expand::base::{self, *}; use rustc_parse::parser::Parser; use rustc_span::symbol::{kw, sym, Symbol}; use rustc_span::{InnerSpan, Span}; -use rustc_target::asm::{InlineAsmOptions, InlineAsmTemplatePiece}; struct AsmArgs { template: P, operands: Vec<(ast::InlineAsmOperand, Span)>, named_args: FxHashMap, reg_args: FxHashSet, - options: InlineAsmOptions, + options: ast::InlineAsmOptions, options_span: Option, } @@ -57,7 +56,7 @@ fn parse_args<'a>( operands: vec![], named_args: FxHashMap::default(), reg_args: FxHashSet::default(), - options: InlineAsmOptions::empty(), + options: ast::InlineAsmOptions::empty(), options_span: None, }; @@ -204,22 +203,22 @@ fn parse_args<'a>( } } - if args.options.contains(InlineAsmOptions::NOMEM) - && args.options.contains(InlineAsmOptions::READONLY) + if args.options.contains(ast::InlineAsmOptions::NOMEM) + && args.options.contains(ast::InlineAsmOptions::READONLY) { let span = args.options_span.unwrap(); ecx.struct_span_err(span, "the `nomem` and `readonly` options are mutually exclusive") .emit(); } - if args.options.contains(InlineAsmOptions::PURE) - && args.options.contains(InlineAsmOptions::NORETURN) + if args.options.contains(ast::InlineAsmOptions::PURE) + && args.options.contains(ast::InlineAsmOptions::NORETURN) { let span = args.options_span.unwrap(); ecx.struct_span_err(span, "the `pure` and `noreturn` options are mutually exclusive") .emit(); } - if args.options.contains(InlineAsmOptions::PURE) - && !args.options.intersects(InlineAsmOptions::NOMEM | InlineAsmOptions::READONLY) + if args.options.contains(ast::InlineAsmOptions::PURE) + && !args.options.intersects(ast::InlineAsmOptions::NOMEM | ast::InlineAsmOptions::READONLY) { let span = args.options_span.unwrap(); ecx.struct_span_err( @@ -245,14 +244,14 @@ fn parse_args<'a>( _ => {} } } - if args.options.contains(InlineAsmOptions::PURE) && !have_real_output { + if args.options.contains(ast::InlineAsmOptions::PURE) && !have_real_output { ecx.struct_span_err( args.options_span.unwrap(), "asm with `pure` option must have at least one output", ) .emit(); } - if args.options.contains(InlineAsmOptions::NORETURN) && !outputs_sp.is_empty() { + if args.options.contains(ast::InlineAsmOptions::NORETURN) && !outputs_sp.is_empty() { let err = ecx .struct_span_err(outputs_sp, "asm outputs are not allowed with the `noreturn` option"); @@ -270,20 +269,20 @@ fn parse_options<'a>(p: &mut Parser<'a>, args: &mut AsmArgs) -> Result<(), Diagn while !p.eat(&token::CloseDelim(token::DelimToken::Paren)) { if p.eat(&token::Ident(sym::pure, false)) { - args.options |= InlineAsmOptions::PURE; + args.options |= ast::InlineAsmOptions::PURE; } else if p.eat(&token::Ident(sym::nomem, false)) { - args.options |= InlineAsmOptions::NOMEM; + args.options |= ast::InlineAsmOptions::NOMEM; } else if p.eat(&token::Ident(sym::readonly, false)) { - args.options |= InlineAsmOptions::READONLY; + args.options |= ast::InlineAsmOptions::READONLY; } else if p.eat(&token::Ident(sym::preserves_flags, false)) { - args.options |= InlineAsmOptions::PRESERVES_FLAGS; + args.options |= ast::InlineAsmOptions::PRESERVES_FLAGS; } else if p.eat(&token::Ident(sym::noreturn, false)) { - args.options |= InlineAsmOptions::NORETURN; + args.options |= ast::InlineAsmOptions::NORETURN; } else if p.eat(&token::Ident(sym::nostack, false)) { - args.options |= InlineAsmOptions::NOSTACK; + args.options |= ast::InlineAsmOptions::NOSTACK; } else { p.expect(&token::Ident(sym::att_syntax, false))?; - args.options |= InlineAsmOptions::ATT_SYNTAX; + args.options |= ast::InlineAsmOptions::ATT_SYNTAX; } // Allow trailing commas @@ -395,7 +394,9 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, sp: Span, args: AsmArgs) -> P template.push(InlineAsmTemplatePiece::String(s.to_string())), + parse::Piece::String(s) => { + template.push(ast::InlineAsmTemplatePiece::String(s.to_string())) + } parse::Piece::NextArgument(arg) => { let span = arg_spans.next().unwrap_or(template_sp); @@ -467,7 +468,7 @@ fn expand_preparsed_asm(ecx: &mut ExtCtxt<'_>, sp: Span, args: AsmArgs) -> P { diff --git a/src/librustc_hir/arena.rs b/src/librustc_hir/arena.rs index fabb17134a3..6ba39666607 100644 --- a/src/librustc_hir/arena.rs +++ b/src/librustc_hir/arena.rs @@ -15,7 +15,7 @@ macro_rules! arena_types { [few] hir_krate: rustc_hir::Crate<$tcx>, [] arm: rustc_hir::Arm<$tcx>, [] asm_operand: rustc_hir::InlineAsmOperand<$tcx>, - [] asm_template: rustc_target::asm::InlineAsmTemplatePiece, + [] asm_template: rustc_ast::ast::InlineAsmTemplatePiece, [] attribute: rustc_ast::ast::Attribute, [] block: rustc_hir::Block<$tcx>, [] bare_fn_ty: rustc_hir::BareFnTy<$tcx>, diff --git a/src/librustc_hir/hir.rs b/src/librustc_hir/hir.rs index 19cea3a3466..ef398ab25d3 100644 --- a/src/librustc_hir/hir.rs +++ b/src/librustc_hir/hir.rs @@ -7,6 +7,7 @@ use rustc_ast::ast::{self, CrateSugar, LlvmAsmDialect}; use rustc_ast::ast::{AttrVec, Attribute, FloatTy, IntTy, Label, LitKind, StrStyle, UintTy}; pub use rustc_ast::ast::{BorrowKind, ImplPolarity, IsAuto}; pub use rustc_ast::ast::{CaptureBy, Movability, Mutability}; +use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_ast::node_id::NodeMap; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::FxHashSet; @@ -15,7 +16,7 @@ use rustc_macros::HashStable_Generic; use rustc_span::source_map::{SourceMap, Spanned}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; use rustc_span::{MultiSpan, Span, DUMMY_SP}; -use rustc_target::asm::{InlineAsmOptions, InlineAsmRegOrRegClass, InlineAsmTemplatePiece}; +use rustc_target::asm::InlineAsmRegOrRegClass; use rustc_target::spec::abi::Abi; use smallvec::SmallVec; diff --git a/src/librustc_hir_pretty/lib.rs b/src/librustc_hir_pretty/lib.rs index 312b8edfe23..8eb19cbb65a 100644 --- a/src/librustc_hir_pretty/lib.rs +++ b/src/librustc_hir_pretty/lib.rs @@ -12,7 +12,6 @@ use rustc_hir::{GenericBound, PatKind, RangeEnd, TraitBoundModifier}; use rustc_span::source_map::{SourceMap, Spanned}; use rustc_span::symbol::{kw, Ident, IdentPrinter, Symbol}; use rustc_span::{self, BytePos, FileName}; -use rustc_target::asm::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_target::spec::abi::Abi; use std::borrow::Cow; @@ -1414,11 +1413,11 @@ impl<'a> State<'a> { enum AsmArg<'a> { Template(String), Operand(&'a hir::InlineAsmOperand<'a>), - Options(InlineAsmOptions), + Options(ast::InlineAsmOptions), } let mut args = vec![]; - args.push(AsmArg::Template(InlineAsmTemplatePiece::to_string(&a.template))); + args.push(AsmArg::Template(ast::InlineAsmTemplatePiece::to_string(&a.template))); args.extend(a.operands.iter().map(|o| AsmArg::Operand(o))); if !a.options.is_empty() { args.push(AsmArg::Options(a.options)); @@ -1485,25 +1484,25 @@ impl<'a> State<'a> { s.word("options"); s.popen(); let mut options = vec![]; - if opts.contains(InlineAsmOptions::PURE) { + if opts.contains(ast::InlineAsmOptions::PURE) { options.push("pure"); } - if opts.contains(InlineAsmOptions::NOMEM) { + if opts.contains(ast::InlineAsmOptions::NOMEM) { options.push("nomem"); } - if opts.contains(InlineAsmOptions::READONLY) { + if opts.contains(ast::InlineAsmOptions::READONLY) { options.push("readonly"); } - if opts.contains(InlineAsmOptions::PRESERVES_FLAGS) { + if opts.contains(ast::InlineAsmOptions::PRESERVES_FLAGS) { options.push("preserves_flags"); } - if opts.contains(InlineAsmOptions::NORETURN) { + if opts.contains(ast::InlineAsmOptions::NORETURN) { options.push("noreturn"); } - if opts.contains(InlineAsmOptions::NOSTACK) { + if opts.contains(ast::InlineAsmOptions::NOSTACK) { options.push("nostack"); } - if opts.contains(InlineAsmOptions::ATT_SYNTAX) { + if opts.contains(ast::InlineAsmOptions::ATT_SYNTAX) { options.push("att_syntax"); } s.commasep(Inconsistent, &options, |s, &opt| { diff --git a/src/librustc_middle/mir/mod.rs b/src/librustc_middle/mir/mod.rs index 707e831b3e8..8247338ae0f 100644 --- a/src/librustc_middle/mir/mod.rs +++ b/src/librustc_middle/mir/mod.rs @@ -19,6 +19,7 @@ use rustc_target::abi::VariantIdx; use polonius_engine::Atom; pub use rustc_ast::ast::Mutability; +use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::graph::dominators::{dominators, Dominators}; use rustc_data_structures::graph::{self, GraphSuccessors}; @@ -28,7 +29,7 @@ use rustc_macros::HashStable; use rustc_serialize::{Decodable, Encodable}; use rustc_span::symbol::Symbol; use rustc_span::{Span, DUMMY_SP}; -use rustc_target::asm::{InlineAsmOptions, InlineAsmRegOrRegClass, InlineAsmTemplatePiece}; +use rustc_target::asm::InlineAsmRegOrRegClass; use std::borrow::Cow; use std::fmt::{self, Debug, Display, Formatter, Write}; use std::ops::{Index, IndexMut}; diff --git a/src/librustc_middle/ty/structural_impls.rs b/src/librustc_middle/ty/structural_impls.rs index ce0ee5fe53a..6d4b394f196 100644 --- a/src/librustc_middle/ty/structural_impls.rs +++ b/src/librustc_middle/ty/structural_impls.rs @@ -259,6 +259,8 @@ CloneTypeFoldableAndLiftImpls! { String, crate::middle::region::Scope, ::rustc_ast::ast::FloatTy, + ::rustc_ast::ast::InlineAsmOptions, + ::rustc_ast::ast::InlineAsmTemplatePiece, ::rustc_ast::ast::NodeId, ::rustc_span::symbol::Symbol, ::rustc_hir::def::Res, @@ -267,9 +269,7 @@ CloneTypeFoldableAndLiftImpls! { ::rustc_hir::MatchSource, ::rustc_hir::Mutability, ::rustc_hir::Unsafety, - ::rustc_target::asm::InlineAsmOptions, ::rustc_target::asm::InlineAsmRegOrRegClass, - ::rustc_target::asm::InlineAsmTemplatePiece, ::rustc_target::spec::abi::Abi, crate::mir::Local, crate::mir::Promoted, diff --git a/src/librustc_mir_build/build/expr/into.rs b/src/librustc_mir_build/build/expr/into.rs index 44c79a49f45..ff3c7ee3ee8 100644 --- a/src/librustc_mir_build/build/expr/into.rs +++ b/src/librustc_mir_build/build/expr/into.rs @@ -3,12 +3,12 @@ use crate::build::expr::category::{Category, RvalueFunc}; use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; use crate::hair::*; +use rustc_ast::ast::InlineAsmOptions; use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_middle::mir::*; use rustc_middle::ty::{self, CanonicalUserTypeAnnotation}; use rustc_span::symbol::sym; -use rustc_target::asm::InlineAsmOptions; use rustc_target::spec::abi::Abi; diff --git a/src/librustc_mir_build/hair/mod.rs b/src/librustc_mir_build/hair/mod.rs index 0c49e698e89..aba7a7a1b42 100644 --- a/src/librustc_mir_build/hair/mod.rs +++ b/src/librustc_mir_build/hair/mod.rs @@ -5,6 +5,7 @@ //! structures. use self::cx::Cx; +use rustc_ast::ast::{InlineAsmOptions, InlineAsmTemplatePiece}; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_middle::infer::canonical::Canonical; @@ -15,7 +16,7 @@ use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::{AdtDef, Const, Ty, UpvarSubsts, UserType}; use rustc_span::Span; use rustc_target::abi::VariantIdx; -use rustc_target::asm::{InlineAsmOptions, InlineAsmRegOrRegClass, InlineAsmTemplatePiece}; +use rustc_target::asm::InlineAsmRegOrRegClass; crate mod constant; crate mod cx; diff --git a/src/librustc_passes/intrinsicck.rs b/src/librustc_passes/intrinsicck.rs index 9e144f86cd8..93344e907c3 100644 --- a/src/librustc_passes/intrinsicck.rs +++ b/src/librustc_passes/intrinsicck.rs @@ -1,4 +1,4 @@ -use rustc_ast::ast::{FloatTy, IntTy, UintTy}; +use rustc_ast::ast::{FloatTy, InlineAsmTemplatePiece, IntTy, UintTy}; use rustc_errors::struct_span_err; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; @@ -11,7 +11,7 @@ use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_session::lint; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; use rustc_target::abi::{Pointer, VariantIdx}; -use rustc_target::asm::{InlineAsmRegOrRegClass, InlineAsmTemplatePiece, InlineAsmType}; +use rustc_target::asm::{InlineAsmRegOrRegClass, InlineAsmType}; use rustc_target::spec::abi::Abi::RustIntrinsic; fn check_mod_intrinsics(tcx: TyCtxt<'_>, module_def_id: DefId) { diff --git a/src/librustc_passes/liveness.rs b/src/librustc_passes/liveness.rs index 57d14220c2b..21512c566e1 100644 --- a/src/librustc_passes/liveness.rs +++ b/src/librustc_passes/liveness.rs @@ -96,6 +96,7 @@ use self::LiveNodeKind::*; use self::VarKind::*; +use rustc_ast::ast::InlineAsmOptions; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::Applicability; use rustc_hir as hir; @@ -109,7 +110,6 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_session::lint; use rustc_span::symbol::{sym, Symbol}; use rustc_span::Span; -use rustc_target::asm::InlineAsmOptions; use std::collections::VecDeque; use std::fmt; diff --git a/src/librustc_target/asm/mod.rs b/src/librustc_target/asm/mod.rs index 5bdfdfefeda..05aa85ecb74 100644 --- a/src/librustc_target/asm/mod.rs +++ b/src/librustc_target/asm/mod.rs @@ -1,7 +1,7 @@ use crate::abi::Size; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_macros::HashStable_Generic; -use rustc_span::{Span, Symbol}; +use rustc_span::Symbol; use std::fmt; use std::str::FromStr; @@ -417,60 +417,6 @@ impl fmt::Display for InlineAsmRegOrRegClass { } } -bitflags::bitflags! { - #[derive(RustcEncodable, RustcDecodable, HashStable_Generic)] - pub struct InlineAsmOptions: u8 { - const PURE = 1 << 0; - const NOMEM = 1 << 1; - const READONLY = 1 << 2; - const PRESERVES_FLAGS = 1 << 3; - const NORETURN = 1 << 4; - const NOSTACK = 1 << 5; - const ATT_SYNTAX = 1 << 6; - } -} - -#[derive(Clone, PartialEq, RustcEncodable, RustcDecodable, Debug, HashStable_Generic)] -pub enum InlineAsmTemplatePiece { - String(String), - Placeholder { operand_idx: usize, modifier: Option, span: Span }, -} - -impl fmt::Display for InlineAsmTemplatePiece { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::String(s) => { - for c in s.chars() { - match c { - '{' => f.write_str("{{")?, - '}' => f.write_str("}}")?, - _ => write!(f, "{}", c.escape_debug())?, - } - } - Ok(()) - } - Self::Placeholder { operand_idx, modifier: Some(modifier), .. } => { - write!(f, "{{{}:{}}}", operand_idx, modifier) - } - Self::Placeholder { operand_idx, modifier: None, .. } => { - write!(f, "{{{}}}", operand_idx) - } - } - } -} - -impl InlineAsmTemplatePiece { - /// Rebuilds the asm template string from its pieces. - pub fn to_string(s: &[Self]) -> String { - use fmt::Write; - let mut out = String::new(); - for p in s.iter() { - let _ = write!(out, "{}", p); - } - out - } -} - /// Set of types which can be used with a particular register class. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum InlineAsmType { diff --git a/src/librustc_target/lib.rs b/src/librustc_target/lib.rs index 0e634587b4a..c2cdd2fd3ec 100644 --- a/src/librustc_target/lib.rs +++ b/src/librustc_target/lib.rs @@ -31,4 +31,4 @@ pub mod spec; /// Requirements for a `StableHashingContext` to be used in this crate. /// This is a hack to allow using the `HashStable_Generic` derive macro /// instead of implementing everything in librustc_middle. -pub trait HashStableContext: rustc_span::HashStableContext {} +pub trait HashStableContext {} diff --git a/src/librustc_typeck/check/expr.rs b/src/librustc_typeck/check/expr.rs index 0ec2d438c28..266e9b21d69 100644 --- a/src/librustc_typeck/check/expr.rs +++ b/src/librustc_typeck/check/expr.rs @@ -38,7 +38,6 @@ use rustc_middle::ty::{AdtKind, Visibility}; use rustc_span::hygiene::DesugaringKind; use rustc_span::source_map::Span; use rustc_span::symbol::{kw, sym, Ident, Symbol}; -use rustc_target::asm::InlineAsmOptions; use rustc_trait_selection::traits::{self, ObligationCauseCode}; use std::fmt::Display; @@ -1873,7 +1872,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } } - if asm.options.contains(InlineAsmOptions::NORETURN) { + if asm.options.contains(ast::InlineAsmOptions::NORETURN) { self.tcx.types.never } else { self.tcx.mk_unit() From 1c6a9351d8cc7c96cfe1f24fd00723d60b00e863 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 6 May 2020 15:03:53 +0100 Subject: [PATCH 22/36] Handle InlineAsm in clippy --- src/tools/clippy/clippy_lints/src/loops.rs | 18 +++++- .../clippy/clippy_lints/src/utils/author.rs | 4 ++ .../clippy_lints/src/utils/hir_utils.rs | 55 ++++++++++++++++++- .../clippy_lints/src/utils/inspector.rs | 27 ++++++++- .../clippy/clippy_lints/src/utils/sugg.rs | 2 + src/tools/clippy/clippy_lints/src/write.rs | 5 +- 6 files changed, 104 insertions(+), 7 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/loops.rs b/src/tools/clippy/clippy_lints/src/loops.rs index 84e8a010738..38a5829b3f7 100644 --- a/src/tools/clippy/clippy_lints/src/loops.rs +++ b/src/tools/clippy/clippy_lints/src/loops.rs @@ -16,8 +16,8 @@ use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::intravisit::{walk_block, walk_expr, walk_pat, walk_stmt, NestedVisitorMap, Visitor}; use rustc_hir::{ - def_id, BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, LoopSource, - MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind, + def_id, BinOpKind, BindingAnnotation, Block, BorrowKind, Expr, ExprKind, GenericArg, HirId, InlineAsmOperand, + LoopSource, MatchSource, Mutability, Node, Pat, PatKind, QPath, Stmt, StmtKind, }; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -693,6 +693,20 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { NeverLoopResult::AlwaysBreak } }, + ExprKind::InlineAsm(ref asm) => asm + .operands + .iter() + .map(|o| match o { + InlineAsmOperand::In { expr, .. } + | InlineAsmOperand::InOut { expr, .. } + | InlineAsmOperand::Const { expr } + | InlineAsmOperand::Sym { expr } => never_loop_expr(expr, main_loop_id), + InlineAsmOperand::Out { expr, .. } => never_loop_expr_all(&mut expr.iter(), main_loop_id), + InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + never_loop_expr_all(&mut once(in_expr).chain(out_expr.iter()), main_loop_id) + }, + }) + .fold(NeverLoopResult::Otherwise, combine_both), ExprKind::Struct(_, _, None) | ExprKind::Yield(_, _) | ExprKind::Closure(_, _, _, _, _) diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs index 74601008dca..bbcf396eef7 100644 --- a/src/tools/clippy/clippy_lints/src/utils/author.rs +++ b/src/tools/clippy/clippy_lints/src/utils/author.rs @@ -469,6 +469,10 @@ impl<'tcx> Visitor<'tcx> for PrintVisitor { println!("Ret(None) = {};", current); } }, + ExprKind::InlineAsm(_) => { + println!("InlineAsm(_) = {};", current); + println!(" // unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment"); + }, ExprKind::LlvmInlineAsm(_) => { println!("LlvmInlineAsm(_) = {};", current); println!(" // unimplemented: `ExprKind::LlvmInlineAsm` is not further destructured at the moment"); diff --git a/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs b/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs index bd7da57c665..92c27e79452 100644 --- a/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs +++ b/src/tools/clippy/clippy_lints/src/utils/hir_utils.rs @@ -1,10 +1,11 @@ use crate::consts::{constant_context, constant_simple}; use crate::utils::differing_macro_contexts; +use rustc_ast::ast::InlineAsmTemplatePiece; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; use rustc_hir::{ BinOpKind, Block, BlockCheckMode, BodyId, BorrowKind, CaptureBy, Expr, ExprKind, Field, FnRetTy, GenericArg, - GenericArgs, Guard, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path, PathSegment, QPath, Stmt, StmtKind, Ty, - TyKind, TypeBinding, + GenericArgs, Guard, InlineAsmOperand, Lifetime, LifetimeName, ParamName, Pat, PatKind, Path, PathSegment, QPath, + Stmt, StmtKind, Ty, TyKind, TypeBinding, }; use rustc_lint::LateContext; use rustc_middle::ich::StableHashingContextProvider; @@ -474,6 +475,56 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_expr(a); self.hash_expr(i); }, + ExprKind::InlineAsm(ref asm) => { + for piece in asm.template { + match piece { + InlineAsmTemplatePiece::String(s) => s.hash(&mut self.s), + InlineAsmTemplatePiece::Placeholder { + operand_idx, + modifier, + span: _, + } => { + operand_idx.hash(&mut self.s); + modifier.hash(&mut self.s); + }, + } + } + asm.options.hash(&mut self.s); + for op in asm.operands { + match op { + InlineAsmOperand::In { reg, expr } => { + reg.hash(&mut self.s); + self.hash_expr(expr); + }, + InlineAsmOperand::Out { reg, late, expr } => { + reg.hash(&mut self.s); + late.hash(&mut self.s); + if let Some(expr) = expr { + self.hash_expr(expr); + } + }, + InlineAsmOperand::InOut { reg, late, expr } => { + reg.hash(&mut self.s); + late.hash(&mut self.s); + self.hash_expr(expr); + }, + InlineAsmOperand::SplitInOut { + reg, + late, + in_expr, + out_expr, + } => { + reg.hash(&mut self.s); + late.hash(&mut self.s); + self.hash_expr(in_expr); + if let Some(out_expr) = out_expr { + self.hash_expr(out_expr); + } + }, + InlineAsmOperand::Const { expr } | InlineAsmOperand::Sym { expr } => self.hash_expr(expr), + } + } + }, ExprKind::LlvmInlineAsm(..) | ExprKind::Err => {}, ExprKind::Lit(ref l) => { l.node.hash(&mut self.s); diff --git a/src/tools/clippy/clippy_lints/src/utils/inspector.rs b/src/tools/clippy/clippy_lints/src/utils/inspector.rs index 7e8c61ba24a..748c11fac64 100644 --- a/src/tools/clippy/clippy_lints/src/utils/inspector.rs +++ b/src/tools/clippy/clippy_lints/src/utils/inspector.rs @@ -1,7 +1,7 @@ //! checks for attributes use crate::utils::get_attr; -use rustc_ast::ast::Attribute; +use rustc_ast::ast::{Attribute, InlineAsmTemplatePiece}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::Session; @@ -282,6 +282,31 @@ fn print_expr(cx: &LateContext<'_, '_>, expr: &hir::Expr<'_>, indent: usize) { print_expr(cx, e, indent + 1); } }, + hir::ExprKind::InlineAsm(ref asm) => { + println!("{}InlineAsm", ind); + println!("{}template: {}", ind, InlineAsmTemplatePiece::to_string(asm.template)); + println!("{}options: {:?}", ind, asm.options); + println!("{}operands:", ind); + for op in asm.operands { + match op { + hir::InlineAsmOperand::In { expr, .. } => print_expr(cx, expr, indent + 1), + hir::InlineAsmOperand::Out { expr, .. } => { + if let Some(expr) = expr { + print_expr(cx, expr, indent + 1); + } + }, + hir::InlineAsmOperand::InOut { expr, .. } => print_expr(cx, expr, indent + 1), + hir::InlineAsmOperand::SplitInOut { in_expr, out_expr, .. } => { + print_expr(cx, in_expr, indent + 1); + if let Some(out_expr) = out_expr { + print_expr(cx, out_expr, indent + 1); + } + }, + hir::InlineAsmOperand::Const { expr } => print_expr(cx, expr, indent + 1), + hir::InlineAsmOperand::Sym { expr } => print_expr(cx, expr, indent + 1), + } + } + }, hir::ExprKind::LlvmInlineAsm(ref asm) => { let inputs = &asm.inputs_exprs; let outputs = &asm.outputs_exprs; diff --git a/src/tools/clippy/clippy_lints/src/utils/sugg.rs b/src/tools/clippy/clippy_lints/src/utils/sugg.rs index a8fe637d3d9..4ebe2e2852f 100644 --- a/src/tools/clippy/clippy_lints/src/utils/sugg.rs +++ b/src/tools/clippy/clippy_lints/src/utils/sugg.rs @@ -108,6 +108,7 @@ impl<'a> Sugg<'a> { | hir::ExprKind::Call(..) | hir::ExprKind::Field(..) | hir::ExprKind::Index(..) + | hir::ExprKind::InlineAsm(..) | hir::ExprKind::LlvmInlineAsm(..) | hir::ExprKind::Lit(..) | hir::ExprKind::Loop(..) @@ -150,6 +151,7 @@ impl<'a> Sugg<'a> { | ast::ExprKind::Field(..) | ast::ExprKind::ForLoop(..) | ast::ExprKind::Index(..) + | ast::ExprKind::InlineAsm(..) | ast::ExprKind::LlvmInlineAsm(..) | ast::ExprKind::Lit(..) | ast::ExprKind::Loop(..) diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs index 26bf463bd29..dfa6223f1b9 100644 --- a/src/tools/clippy/clippy_lints/src/write.rs +++ b/src/tools/clippy/clippy_lints/src/write.rs @@ -353,7 +353,8 @@ impl Write { is_write: bool, ) -> (Option, Option) { use fmt_macros::{ - AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, Parser, Piece, + AlignUnknown, ArgumentImplicitlyIs, ArgumentIs, ArgumentNamed, CountImplied, FormatSpec, ParseMode, Parser, + Piece, }; let tts = tts.clone(); @@ -376,7 +377,7 @@ impl Write { }; let tmp = fmtstr.symbol.as_str(); let mut args = vec![]; - let mut fmt_parser = Parser::new(&tmp, None, Vec::new(), false); + let mut fmt_parser = Parser::new(&tmp, None, None, false, ParseMode::Format); while let Some(piece) = fmt_parser.next() { if !fmt_parser.errors.is_empty() { return (None, expr); From f10803c81cf6417c2ebe982519fa7c4502cab2cf Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 8 May 2020 00:57:22 +0100 Subject: [PATCH 23/36] Minor fixes --- src/librustc_codegen_ssa/mir/block.rs | 3 ++- src/librustc_feature/active.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/librustc_codegen_ssa/mir/block.rs b/src/librustc_codegen_ssa/mir/block.rs index f8c5caa440c..923fcd326a2 100644 --- a/src/librustc_codegen_ssa/mir/block.rs +++ b/src/librustc_codegen_ssa/mir/block.rs @@ -975,7 +975,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } } mir::InlineAsmOperand::SymFn { ref value } => { - if let ty::FnDef(def_id, substs) = value.literal.ty.kind { + let literal = self.monomorphize(&value.literal); + if let ty::FnDef(def_id, substs) = literal.ty.kind { let instance = ty::Instance::resolve( bx.tcx(), ty::ParamEnv::reveal_all(), diff --git a/src/librustc_feature/active.rs b/src/librustc_feature/active.rs index 47101ca72b4..7738a576ef7 100644 --- a/src/librustc_feature/active.rs +++ b/src/librustc_feature/active.rs @@ -228,7 +228,6 @@ declare_features! ( (active, hexagon_target_feature, "1.27.0", Some(44839), None), (active, powerpc_target_feature, "1.27.0", Some(44839), None), (active, mips_target_feature, "1.27.0", Some(44839), None), - (active, riscv_target_feature, "1.27.0", Some(44839), None), (active, avx512_target_feature, "1.27.0", Some(44839), None), (active, mmx_target_feature, "1.27.0", Some(44839), None), (active, sse4a_target_feature, "1.27.0", Some(44839), None), @@ -239,6 +238,7 @@ declare_features! ( (active, movbe_target_feature, "1.34.0", Some(44839), None), (active, rtm_target_feature, "1.35.0", Some(44839), None), (active, f16c_target_feature, "1.36.0", Some(44839), None), + (active, riscv_target_feature, "1.45.0", Some(44839), None), // ------------------------------------------------------------------------- // feature-group-end: actual feature gates (target features) From 2aa9aaada5f7ebd0236bf7ebdbbce6ec8b684239 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 8 May 2020 01:15:56 +0100 Subject: [PATCH 24/36] Add borrow-check test --- src/test/ui/asm/bad-reg.stderr | 2 +- src/test/ui/asm/type-check-4.rs | 23 +++++++++++++++++++++++ src/test/ui/asm/type-check-4.stderr | 26 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/test/ui/asm/type-check-4.rs create mode 100644 src/test/ui/asm/type-check-4.stderr diff --git a/src/test/ui/asm/bad-reg.stderr b/src/test/ui/asm/bad-reg.stderr index a1423f0e9c1..c6b7d310dfa 100644 --- a/src/test/ui/asm/bad-reg.stderr +++ b/src/test/ui/asm/bad-reg.stderr @@ -18,7 +18,7 @@ LL | asm!("{:z}", in(reg) foo); | | | template modifier | - = note: the `reg` register class supports the following template modifiers: `l`, `h`, `x`, `e`, `r` + = note: the `reg` register class supports the following template modifiers: `l`, `x`, `e`, `r` error: invalid asm template modifier for this register class --> $DIR/bad-reg.rs:18:15 diff --git a/src/test/ui/asm/type-check-4.rs b/src/test/ui/asm/type-check-4.rs new file mode 100644 index 00000000000..2be627c1165 --- /dev/null +++ b/src/test/ui/asm/type-check-4.rs @@ -0,0 +1,23 @@ +// only-x86_64 + +#![feature(asm)] + +fn main() { + unsafe { + // Can't output to borrowed values. + + let mut a = 0isize; + let p = &a; + asm!("{}", out(reg) a); + //~^ cannot assign to `a` because it is borrowed + println!("{}", p); + + // Can't read from mutable borrowed values. + + let mut a = 0isize; + let p = &mut a; + asm!("{}", in(reg) a); + //~^ cannot use `a` because it was mutably borrowed + println!("{}", p); + } +} diff --git a/src/test/ui/asm/type-check-4.stderr b/src/test/ui/asm/type-check-4.stderr new file mode 100644 index 00000000000..8035bbefc1a --- /dev/null +++ b/src/test/ui/asm/type-check-4.stderr @@ -0,0 +1,26 @@ +error[E0506]: cannot assign to `a` because it is borrowed + --> $DIR/type-check-4.rs:11:9 + | +LL | let p = &a; + | -- borrow of `a` occurs here +LL | asm!("{}", out(reg) a); + | ^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `a` occurs here +LL | +LL | println!("{}", p); + | - borrow later used here + +error[E0503]: cannot use `a` because it was mutably borrowed + --> $DIR/type-check-4.rs:19:28 + | +LL | let p = &mut a; + | ------ borrow of `a` occurs here +LL | asm!("{}", in(reg) a); + | ^ use of borrowed `a` +LL | +LL | println!("{}", p); + | - borrow later used here + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0503, E0506. +For more information about an error, try `rustc --explain E0503`. From 6ba9696f5ebb91a562e03f72075866a2f846d4c9 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 8 May 2020 15:36:34 +0100 Subject: [PATCH 25/36] Add documentation for asm! --- .../unstable-book/src/library-features/asm.md | 678 ++++++++++++++++++ src/libcore/macros/mod.rs | 15 +- src/librustc_error_codes/error_codes/E0660.md | 4 +- src/librustc_error_codes/error_codes/E0661.md | 4 +- src/librustc_error_codes/error_codes/E0662.md | 4 +- src/librustc_error_codes/error_codes/E0663.md | 4 +- src/librustc_error_codes/error_codes/E0664.md | 4 +- 7 files changed, 695 insertions(+), 18 deletions(-) create mode 100644 src/doc/unstable-book/src/library-features/asm.md diff --git a/src/doc/unstable-book/src/library-features/asm.md b/src/doc/unstable-book/src/library-features/asm.md new file mode 100644 index 00000000000..bc55cd4a5bd --- /dev/null +++ b/src/doc/unstable-book/src/library-features/asm.md @@ -0,0 +1,678 @@ +# `asm` + +The tracking issue for this feature is: [#72016] + +[#72016]: https://github.com/rust-lang/rust/issues/72016 + +------------------------ + +For extremely low-level manipulations and performance reasons, one +might wish to control the CPU directly. Rust supports using inline +assembly to do this via the `asm!` macro. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Rust provides support for inline assembly via the `asm!` macro. +It can be used to embed handwritten assembly in the assembly output generated by the compiler. +Generally this should not be necessary, but might be where the required performance or timing +cannot be otherwise achieved. Accessing low level hardware primitives, e.g. in kernel code, may also demand this functionality. + +> **Note**: the examples here are given in x86/x86-64 assembly, but ARM, AArch64 and RISC-V are also supported. + +## Basic usage + +Let us start with the simplest possible example: + +```rust +unsafe { + asm!("nop"); +} +``` + +This will insert a NOP (no operation) instruction into the assembly generated by the compiler. +Note that all `asm!` invocations have to be inside an `unsafe` block, as they could insert +arbitrary instructions and break various invariants. The instructions to be inserted are listed +in the first argument of the `asm!` macro as a string literal. + +## Inputs and outputs + +Now inserting an instruction that does nothing is rather boring. Let us do something that +actually acts on data: + +```rust +let x: u64; +unsafe { + asm!("mov {}, 5", out(reg) x); +} +assert_eq!(x, 5); +``` + +This will write the value `5` into the `u64` variable `x`. +You can see that the string literal we use to specify instructions is actually a template string. +It is governed by the same rules as Rust [format strings][format-syntax]. +The arguments that are inserted into the template however look a bit different then you may +be familiar with. First we need to specify if the variable is an input or an output of the +inline assembly. In this case it is an output. We declared this by writing `out`. +We also need to specify in what kind of register the assembly expects the variable. +In this case we put it in an arbitrary general purpose register by specifying `reg`. +The compiler will choose an appropriate register to insert into +the template and will read the variable from there after the inline assembly finishes executing. + +Let us see another example that also uses an input: + +```rust +let i: u64 = 3; +let o: u64; +unsafe { + asm!(" + mov {0}, {1} + add {0}, {number} + ", out(reg) o, in(reg) i, number = const 5); +} +assert_eq!(o, 8); +``` + +This will add `5` to the input in variable `i` and write the result to variable `o`. +The particular way this assembly does this is first copying the value from `i` to the output, +and then adding `5` to it. + +The example shows a few things: + +First we can see that inputs are declared by writing `in` instead of `out`. + +Second one of our operands has a type we haven't seen yet, `const`. +This tells the compiler to expand this argument to value directly inside the assembly template. +This is only possible for constants and literals. + +Third we can see that we can specify an argument number, or name as in any format string. +For inline assembly templates this is particularly useful as arguments are often used more than once. +For more complex inline assembly using this facility is generally recommended, as it improves +readability, and allows reordering instructions without changing the argument order. + +We can further refine the above example to avoid the `mov` instruction: + +```rust +let mut x: u64 = 3; +unsafe { + asm!("add {0}, {number}", inout(reg) x, number = const 5); +} +assert_eq!(x, 8); +``` + +We can see that `inout` is used to specify an argument that is both input and output. +This is different from specifying an input and output separately in that it is guaranteed to assign both to the same register. + +It is also possible to specify different variables for the input and output parts of an `inout` operand: + +```rust +let x: u64 = 3; +let y: u64; +unsafe { + asm!("add {0}, {number}", inout(reg) x => y, number = const 5); +} +assert_eq!(y, 8); +``` + +## Late output operands + +The Rust compiler is conservative with its allocation of operands. It is assumed that an `out` +can be written at any time, and can therefore not share its location with any other argument. +However, to guarantee optimal performance it is important to use as few registers as possible, +so they won't have to be saved and reloaded around the inline assembly block. +To achieve this Rust provides a `lateout` specifier. This can be used on any output that is +written only after all inputs have been consumed. +There is also a `inlateout` variant of this specifier. + +Here is an example where `inlateout` *cannot* be used: + +```rust +let mut a: u64 = 4; +let b: u64 = 4; +let c: u64 = 4; +unsafe { + asm!(" + add {0}, {1} + add {0}, {2} + ", inout(reg) a, in(reg) b, in(reg) c); +} +assert_eq!(a, 12); +``` + +Here the compiler is free to allocate the same register for inputs `b` and `c` since it knows they have the same value. However it must allocate a separate register for `a` since it uses `inout` and not `inlateout`. If `inlateout` was used, then `a` and `c` could be allocated to the same register, in which case the first instruction to overwrite the value of `c` and cause the assembly code to produce the wrong result. + +However the following example can use `inlateout` since the output is only modified after all input registers have been read: + +```rust +let mut a: u64 = 4; +let b: u64 = 4; +unsafe { + asm!("add {0}, {1}", inlateout(reg) a, in(reg) b); +} +assert_eq!(a, 8); +``` + +As you can see, this assembly fragment will still work correctly if `a` and `b` are assigned to the same register. + +## Explicit register operands + +Some instructions require that the operands be in a specific register. +Therefore, Rust inline assembly provides some more specific constraint specifiers. +While `reg` is generally available on any architecture, these are highly architecture specific. E.g. for x86 the general purpose registers `eax`, `ebx`, `ecx`, `edx`, `ebp`, `esi`, and `edi` +among others can be addressed by their name. + +```rust +unsafe { + asm!("out 0x64, rax", in("rax") cmd); +} +``` + +In this example we call the `out` instruction to output the content of the `cmd` variable +to port `0x64`. Since the `out` instruction only accepts `rax` (and its sub registers) as operand +we had to use the `rax` constraint specifier. + +Note that unlike other operand types, explicit register operands cannot be used in the template string: you can't use `{}` and should write the register name directly instead. Also, they must appear at the end of the operand list after all other operand types. + +Consider this example which uses the x86 `mul` instruction: + +```rust +fn mul(a: u64, b: u64) -> u128 { + let lo: u64; + let hi: u64; + + unsafe { + asm!( + // The x86 mul instruction takes rax as an implicit input and writes + // the 128-bit result of the multiplication to rax:rdx. + "mul {}", + in(reg) a, + inlateout("rax") b => lo, + lateout("rdx") hi + ); + } + + hi as u128 << 64 + lo as u128 +} +``` + +This uses the `mul` instruction to multiply two 64-bit inputs with a 128-bit result. +The only explicit operand is a register, that we fill from the variable `a`. +The second operand is implicit, and must be the `rax` register, which we fill from the variable `b`. +The lower 64 bits of the result are stored in `rax` from which we fill the variable `lo`. +The higher 64 bits are stored in `rdx` from which we fill the variable `hi`. + +## Clobbered registers + +In many cases inline assembly will modify state that is not needed as an output. +Usually this is either because we have to use a scratch register in the assembly, +or instructions modify state that we don't need to further examine. +This state is generally referred to as being "clobbered". +We need to tell the compiler about this since it may need to save and restore this state +around the inline assembly block. + +```rust +let ebx: u64; +let ecx: u64; + +unsafe { + asm!( + "cpuid", + // EAX 4 selects the "Deterministic Cache Parameters" CPUID leaf + inout("eax") 4 => _, + // ECX 0 selects the L0 cache information. + inout("ecx") 0 => ecx, + lateout("ebx") ebx, + lateout("edx") _ + ); +} + +println!( + "L1 Cache: {}", + ((ebx >> 22) + 1) * (((ebx >> 12) & 0x3ff) + 1) * ((ebx & 0xfff) + 1) * (ecx + 1) +); +``` + +In the example above we use the `cpuid` instruction to get the L1 cache size. +This instruction writes to `eax`, `ebx`, `ecx`, and `edx`, but for the cache size we only care about the contents of `ebx` and `ecx`. + +However we still need to tell the compiler that `eax` and `edx` have been modified so that it can save any values that were in these registers before the asm. This is done by declaring these as outputs but with `_` instead of a variable name, which indicates that the output value is to be discarded. + +This can also be used with a general register class (e.g. `reg`) to obtain a scratch register for use inside the asm code: + +```rust +// Multiply x by 6 using shifts and adds +let mut x: u64 = 4; +unsafe { + asm!(" + mov {tmp}, {x} + shl {tmp}, 1 + shl {x}, 2 + add {x}, {tmp} + ", x = inout(reg) x, tmp = out(reg) _); +} +assert_eq!(x, 4 * 6); +``` + +## Symbol operands + +A special operand type, `sym`, allows you to use the symbol name of a `fn` or `static` in inline assembly code. +This allows you to call a function or access a global variable without needing to keep its address in a register. + +```rust +extern "C" fn foo(arg: i32) { + println!("arg = {}", arg); +} + +fn call_foo(arg: i32) { + unsafe { + asm!( + "call {}" + sym foo, + // 1st argument in rdi, which is caller-saved + inout("rdi") arg => _, + // All caller-saved registers must be marked as clobberred + out("rax") _, out("rcx") _, out("rdx") _, out("rsi") _, + out("r8") _, out("r9") _, out("r10") _, out("r11") _, + out("xmm0") _, out("xmm1") _, out("xmm2") _, out("xmm3") _, + out("xmm4") _, out("xmm5") _, out("xmm6") _, out("xmm7") _, + out("xmm8") _, out("xmm9") _, out("xmm10") _, out("xmm11") _, + out("xmm12") _, out("xmm13") _, out("xmm14") _, out("xmm15") _, + ) + } +} +``` + +Note that the `fn` or `static` item does not need to be public or `#[no_mangle]`: +the compiler will automatically insert the appropriate mangled symbol name into the assembly code. + +## Register template modifiers + +In some cases, fine control is needed over the way a register name is formatted when inserted into the template string. This is needed when an architecture's assembly language has several names for the same register, each typically being a "view" over a subset of the register (e.g. the low 32 bits of a 64-bit register). + +By default the compiler will always choose the name that refers to the full register size (e.g. `rax` on x86-64, `eax` on x86, etc). + +This default can be overriden by using modifiers on the template string operands, just like you would with format strings: + +```rust +let mut x: u16 = 0xab; + +unsafe { + asm!("mov {0:h}, {0:b}", inout(reg_abcd) x); +} + +assert_eq!(x, 0xabab); +``` + +In this example, we use the `reg_abcd` register class to restrict the register allocator to the 4 legacy x86 register (`ax`, `bx`, `cx`, `dx`) of which the first two bytes can be addressed independently. + +Let us assume that the register allocator has chosen to allocate `x` in the `ax` register. +The `h` modifier will emit the register name for the high byte of that register and the `b` modifier will emit the register name for the low byte. The asm code will therefore be expanded as `mov ah, al` which copies the low byte of the value into the high byte. + +If you use a smaller data type (e.g. `u16`) with an operand and forget the use template modifiers, the compiler will emit a warning and suggest the correct modifier to use. + +## Options + +By default, an inline assembly block is treated the same way as an external FFI function call with a custom calling convention: it may read/write memory, have observable side effects, etc. However in many cases, it is desirable to give the compiler more information about what the assembly code is actually doing so that it can optimize better. + +Let's take our previous example of an `add` instruction: + +```rust +let mut a: u64 = 4; +let b: u64 = 4; +unsafe { + asm!( + "add {0}, {1}", + inlateout(reg) a, in(reg) b, + options(pure, nomem, nostack) + ); +} +assert_eq!(a, 8); +``` + +Options can be provided as an optional final argument to the `asm!` macro. We specified three options here: +- `pure` means that the asm code has no observable side effects and that its output depends only on its inputs. This allows the compiler optimizer to call the inline asm fewer times or even eliminate it entirely. +- `nomem` means that the asm code does not read or write to memory. By default the compiler will assume that inline assembly can read or write any memory address that is accessible to it (e.g. through a pointer passed as an operand, or a global). +- `nostack` means that the asm code does not push any data onto the stack. This allows the compiler to use optimizations such as the stack red zone on x86-64 to avoid stack pointer adjustments. + +These allow the compiler to better optimize code using `asm!`, for example by eliminating pure `asm!` blocks whose outputs are not needed. + +See the reference for the full list of available options and their effects. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +Inline assembler is implemented as an unsafe macro `asm!()`. +The first argument to this macro is a template string literal used to build the final assembly. +The following arguments specify input and output operands. +When required, options are specified as the final argument. + +The following ABNF specifies the general syntax: + +``` +dir_spec := "in" / "out" / "lateout" / "inout" / "inlateout" +reg_spec := / "" +operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_" +reg_operand := dir_spec "(" reg_spec ")" operand_expr +operand := reg_operand / "const" const_expr / "sym" path +option := "pure" / "nomem" / "readonly" / "preserves_flags" / "noreturn" / "att_syntax" +options := "options(" option *["," option] [","] ")" +asm := "asm!(" format_string *("," [ident "="] operand) ["," options] [","] ")" +``` + +The macro will initially be supported only on ARM, AArch64, x86, x86-64 and RISC-V targets. Support for more targets may be added in the future. The compiler will emit an error if `asm!` is used on an unsupported target. + +[format-syntax]: https://doc.rust-lang.org/std/fmt/#syntax + +## Template string + +The assembler template uses the same syntax as [format strings][format-syntax] (i.e. placeholders are specified by curly braces). The corresponding arguments are accessed in order, by index, or by name. However, implicit named arguments (introduced by [RFC #2795][rfc-2795]) are not supported. + +As with format strings, named arguments must appear after positional arguments. Explicit register operands must appear at the end of the operand list, after any named arguments if any. Explicit register operands cannot be used by placeholders in the template string. All other operands must appear at least once in the template string, otherwise a compiler error is generated. + +The exact assembly code syntax is target-specific and opaque to the compiler except for the way operands are substituted into the template string to form the code passed to the assembler. + +The 4 targets specified in this RFC (x86, ARM, AArch64, RISC-V) all use the assembly code syntax of the GNU assembler (GAS). On x86, the `.intel_syntax noprefix` mode of GAS is used by default. On ARM, the `.syntax unified` mode is used. These targets impose an additional restriction on the assembly code: any assembler state (e.g. the current section which can be changed with `.section`) must be restored to its original value at the end of the asm string. Assembly code that does not conform to the GAS syntax will result in assembler-specific behavior. + +[rfc-2795]: https://github.com/rust-lang/rfcs/pull/2795 + +## Operand type + +Several types of operands are supported: + +* `in() ` + - `` can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string. + - The allocated register will contain the value of `` at the start of the asm code. + - The allocated register must contain the same value at the end of the asm code (except if a `lateout` is allocated to the same register). +* `out() ` + - `` can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string. + - The allocated register will contain an undefined value at the start of the asm code. + - `` must be a (possibly uninitialized) place expression, to which the contents of the allocated register is written to at the end of the asm code. + - An underscore (`_`) may be specified instead of an expression, which will cause the contents of the register to be discarded at the end of the asm code (effectively acting as a clobber). +* `lateout() ` + - Identical to `out` except that the register allocator can reuse a register allocated to an `in`. + - You should only write to the register after all inputs are read, otherwise you may clobber an input. +* `inout() ` + - `` can refer to a register class or an explicit register. The allocated register name is substituted into the asm template string. + - The allocated register will contain the value of `` at the start of the asm code. + - `` must be a mutable initialized place expression, to which the contents of the allocated register is written to at the end of the asm code. +* `inout() => ` + - Same as `inout` except that the initial value of the register is taken from the value of ``. + - `` must be a (possibly uninitialized) place expression, to which the contents of the allocated register is written to at the end of the asm code. + - An underscore (`_`) may be specified instead of an expression for ``, which will cause the contents of the register to be discarded at the end of the asm code (effectively acting as a clobber). + - `` and `` may have different types. +* `inlateout() ` / `inlateout() => ` + - Identical to `inout` except that the register allocator can reuse a register allocated to an `in` (this can happen if the compiler knows the `in` has the same initial value as the `inlateout`). + - You should only write to the register after all inputs are read, otherwise you may clobber an input. +* `const ` + - `` must be an integer or floating-point constant expression. + - The value of the expression is formatted as a string and substituted directly into the asm template string. +* `sym ` + - `` must refer to a `fn` or `static`. + - A mangled symbol name referring to the item is substituted into the asm template string. + - The substituted string does not include any modifiers (e.g. GOT, PLT, relocations, etc). + - `` is allowed to point to a `#[thread_local]` static, in which case the asm code can combine the symbol with relocations (e.g. `@plt`, `@TPOFF`) to read from thread-local data. + +## Register operands + +Input and output operands can be specified either as an explicit register or as a register class from which the register allocator can select a register. Explicit registers are specified as string literals (e.g. `"eax"`) while register classes are specified as identifiers (e.g. `reg`). Using string literals for register names enables support for architectures that use special characters in register names, such as MIPS (`$0`, `$1`, etc). + +Note that explicit registers treat register aliases (e.g. `r14` vs `lr` on ARM) and smaller views of a register (e.g. `eax` vs `rax`) as equivalent to the base register. It is a compile-time error to use the same explicit register for two input operands or two output operands. Additionally, it is also a compile-time error to use overlapping registers (e.g. ARM VFP) in input operands or in output operands. + +Only the following types are allowed as operands for inline assembly: +- Integers (signed and unsigned) +- Floating-point numbers +- Pointers (thin only) +- Function pointers +- SIMD vectors (structs defined with `#[repr(simd)]` and which implement `Copy`) + +Here is the list of currently supported register classes: + +| Architecture | Register class | Registers | LLVM constraint code | +| ------------ | -------------- | --------- | -------------------- | +| x86 | `reg` | `ax`, `bx`, `cx`, `dx`, `si`, `di`, `r[8-15]` (x86-64 only) | `r` | +| x86 | `reg_abcd` | `ax`, `bx`, `cx`, `dx` | `Q` | +| x86-32 | `reg_byte` | `al`, `bl`, `cl`, `dl`, `ah`, `bh`, `ch`, `dh` | `q` | +| x86-64 | `reg_byte` | `al`, `bl`, `cl`, `dl`, `sil`, `dil`, `r[8-15]b`, `ah`\*, `bh`\*, `ch`\*, `dh`\* | `q` | +| x86 | `xmm_reg` | `xmm[0-7]` (x86) `xmm[0-15]` (x86-64) | `x` | +| x86 | `ymm_reg` | `ymm[0-7]` (x86) `ymm[0-15]` (x86-64) | `x` | +| x86 | `zmm_reg` | `zmm[0-7]` (x86) `zmm[0-31]` (x86-64) | `v` | +| x86 | `kreg` | `k[1-7]` | `Yk` | +| AArch64 | `reg` | `x[0-28]`, `x30` | `r` | +| AArch64 | `vreg` | `v[0-31]` | `w` | +| AArch64 | `vreg_low16` | `v[0-15]` | `x` | +| ARM | `reg` | `r[0-r10]`, `r12`, `r14` | `r` | +| ARM (Thumb) | `reg_thumb` | `r[0-r7]` | `l` | +| ARM (ARM) | `reg_thumb` | `r[0-r10]`, `r12`, `r14` | `l` | +| ARM | `sreg` | `s[0-31]` | `t` | +| ARM | `sreg_low16` | `s[0-15]` | `x` | +| ARM | `dreg` | `d[0-31]` | `w` | +| ARM | `dreg_low16` | `d[0-15]` | `t` | +| ARM | `dreg_low8` | `d[0-8]` | `x` | +| ARM | `qreg` | `q[0-15]` | `w` | +| ARM | `qreg_low8` | `q[0-7]` | `t` | +| ARM | `qreg_low4` | `q[0-3]` | `x` | +| RISC-V | `reg` | `x1`, `x[5-7]`, `x[9-15]`, `x[16-31]` (non-RV32E) | `r` | +| RISC-V | `freg` | `f[0-31]` | `f` | + +> **Note**: On x86 we treat `reg_byte` differently from `reg` because the compiler can allocate `al` and `ah` separately whereas `reg` reserves the whole register. +> +> Note #2: On x86-64 the high byte registers (e.g. `ah`) are only available when used as an explicit register. Specifying the `reg_byte` register class for an operand will always allocate a low byte register. + +Additional register classes may be added in the future based on demand (e.g. MMX, x87, etc). + +Each register class has constraints on which value types they can be used with. This is necessary because the way a value is loaded into a register depends on its type. For example, on big-endian systems, loading a `i32x4` and a `i8x16` into a SIMD register may result in different register contents even if the byte-wise memory representation of both values is identical. The availability of supported types for a particular register class may depend on what target features are currently enabled. + +| Architecture | Register class | Target feature | Allowed types | +| ------------ | -------------- | -------------- | ------------- | +| x86-32 | `reg` | None | `i16`, `i32`, `f32` | +| x86-64 | `reg` | None | `i16`, `i32`, `f32`, `i64`, `f64` | +| x86 | `reg_byte` | None | `i8` | +| x86 | `xmm_reg` | `sse` | `i32`, `f32`, `i64`, `f64`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2` | +| x86 | `ymm_reg` | `avx` | `i32`, `f32`, `i64`, `f64`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2`
`i8x32`, `i16x16`, `i32x8`, `i64x4`, `f32x8`, `f64x4` | +| x86 | `zmm_reg` | `avx512f` | `i32`, `f32`, `i64`, `f64`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2`
`i8x32`, `i16x16`, `i32x8`, `i64x4`, `f32x8`, `f64x4`
`i8x64`, `i16x32`, `i32x16`, `i64x8`, `f32x16`, `f64x8` | +| x86 | `kreg` | `axv512f` | `i8`, `i16` | +| x86 | `kreg` | `axv512bw` | `i32`, `i64` | +| AArch64 | `reg` | None | `i8`, `i16`, `i32`, `f32`, `i64`, `f64` | +| AArch64 | `vreg` | `fp` | `i8`, `i16`, `i32`, `f32`, `i64`, `f64`,
`i8x8`, `i16x4`, `i32x2`, `i64x1`, `f32x2`, `f64x1`,
`i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4`, `f64x2` | +| ARM | `reg` | None | `i8`, `i16`, `i32`, `f32` | +| ARM | `sreg` | `vfp2` | `i32`, `f32` | +| ARM | `dreg` | `vfp2` | `i64`, `f64`, `i8x8`, `i16x4`, `i32x2`, `i64x1`, `f32x2` | +| ARM | `qreg` | `neon` | `i8x16`, `i16x8`, `i32x4`, `i64x2`, `f32x4` | +| RISC-V32 | `reg` | None | `i8`, `i16`, `i32`, `f32` | +| RISC-V64 | `reg` | None | `i8`, `i16`, `i32`, `f32`, `i64`, `f64` | +| RISC-V | `freg` | `f` | `f32` | +| RISC-V | `freg` | `d` | `f64` | + +> **Note**: For the purposes of the above table pointers, function pointers and `isize`/`usize` are treated as the equivalent integer type (`i16`/`i32`/`i64` depending on the target). + +If a value is of a smaller size than the register it is allocated in then the upper bits of that register will have an undefined value for inputs and will be ignored for outputs. The only exception is the `freg` register class on RISC-V where `f32` values are NaN-boxed in a `f64` as required by the RISC-V architecture. + +When separate input and output expressions are specified for an `inout` operand, both expressions must have the same type. The only exception is if both operands are pointers or integers, in which case they are only required to have the same size. This restriction exists because the register allocators in LLVM and GCC sometimes cannot handle tied operands with different types. + +## Register names + +Some registers have multiple names. These are all treated by the compiler as identical to the base register name. Here is the list of all supported register aliases: + +| Architecture | Base register | Aliases | +| ------------ | ------------- | ------- | +| x86 | `ax` | `eax`, `rax` | +| x86 | `bx` | `ebx`, `rbx` | +| x86 | `cx` | `ecx`, `rcx` | +| x86 | `dx` | `edx`, `rdx` | +| x86 | `si` | `esi`, `rsi` | +| x86 | `di` | `edi`, `rdi` | +| x86 | `bp` | `bpl`, `ebp`, `rbp` | +| x86 | `sp` | `spl`, `esp`, `rsp` | +| x86 | `ip` | `eip`, `rip` | +| x86 | `st(0)` | `st` | +| x86 | `r[8-15]` | `r[8-15]b`, `r[8-15]w`, `r[8-15]d` | +| x86 | `xmm[0-31]` | `ymm[0-31]`, `zmm[0-31]` | +| AArch64 | `x[0-30]` | `w[0-30]` | +| AArch64 | `x29` | `fp` | +| AArch64 | `x30` | `lr` | +| AArch64 | `sp` | `wsp` | +| AArch64 | `xzr` | `wzr` | +| AArch64 | `v[0-31]` | `b[0-31]`, `h[0-31]`, `s[0-31]`, `d[0-31]`, `q[0-31]` | +| ARM | `r[0-3]` | `a[1-4]` | +| ARM | `r[4-9]` | `v[1-6]` | +| ARM | `r9` | `rfp` | +| ARM | `r10` | `sl` | +| ARM | `r11` | `fp` | +| ARM | `r12` | `ip` | +| ARM | `r13` | `sp` | +| ARM | `r14` | `lr` | +| ARM | `r15` | `pc` | +| RISC-V | `x0` | `zero` | +| RISC-V | `x1` | `ra` | +| RISC-V | `x2` | `sp` | +| RISC-V | `x3` | `gp` | +| RISC-V | `x4` | `tp` | +| RISC-V | `x[5-7]` | `t[0-2]` | +| RISC-V | `x8` | `fp`, `s0` | +| RISC-V | `x9` | `s1` | +| RISC-V | `x[10-17]` | `a[0-7]` | +| RISC-V | `x[18-27]` | `s[2-11]` | +| RISC-V | `x[28-31]` | `t[3-6]` | +| RISC-V | `f[0-7]` | `ft[0-7]` | +| RISC-V | `f[8-9]` | `fs[0-1]` | +| RISC-V | `f[10-17]` | `fa[0-7]` | +| RISC-V | `f[18-27]` | `fs[2-11]` | +| RISC-V | `f[28-31]` | `ft[8-11]` | + +Some registers cannot be used for input or output operands: + +| Architecture | Unsupported register | Reason | +| ------------ | -------------------- | ------ | +| All | `sp` | The stack pointer must be restored to its original value at the end of an asm code block. | +| All | `bp` (x86), `r11` (ARM), `x29` (AArch64), `x8` (RISC-V) | The frame pointer cannot be used as an input or output. | +| x86 | `k0` | This is a constant zero register which can't be modified. | +| x86 | `ip` | This is the program counter, not a real register. | +| x86 | `mm[0-7]` | MMX registers are not currently supported (but may be in the future). | +| x86 | `st([0-7])` | x87 registers are not currently supported (but may be in the future). | +| AArch64 | `xzr` | This is a constant zero register which can't be modified. | +| ARM | `pc` | This is the program counter, not a real register. | +| RISC-V | `x0` | This is a constant zero register which can't be modified. | +| RISC-V | `gp`, `tp` | These registers are reserved and cannot be used as inputs or outputs. | + +## Template modifiers + +The placeholders can be augmented by modifiers which are specified after the `:` in the curly braces. These modifiers do not affect register allocation, but change the way operands are formatted when inserted into the template string. Only one modifier is allowed per template placeholder. + +The supported modifiers are a subset of LLVM's (and GCC's) [asm template argument modifiers][llvm-argmod], but do not use the same letter codes. + +| Architecture | Register class | Modifier | Example output | LLVM modifier | +| ------------ | -------------- | -------- | -------------- | ------------- | +| x86-32 | `reg` | None | `eax` | `k` | +| x86-64 | `reg` | None | `rax` | `q` | +| x86-32 | `reg_abcd` | `l` | `al` | `b` | +| x86-64 | `reg` | `l` | `al` | `b` | +| x86 | `reg_abcd` | `h` | `ah` | `h` | +| x86 | `reg` | `x` | `ax` | `w` | +| x86 | `reg` | `e` | `eax` | `k` | +| x86-64 | `reg` | `r` | `rax` | `q` | +| x86 | `reg_byte` | None | `al` / `ah` | None | +| x86 | `xmm_reg` | None | `xmm0` | `x` | +| x86 | `ymm_reg` | None | `ymm0` | `t` | +| x86 | `zmm_reg` | None | `zmm0` | `g` | +| x86 | `*mm_reg` | `x` | `xmm0` | `x` | +| x86 | `*mm_reg` | `y` | `ymm0` | `t` | +| x86 | `*mm_reg` | `z` | `zmm0` | `g` | +| x86 | `kreg` | None | `k1` | None | +| AArch64 | `reg` | None | `x0` | `x` | +| AArch64 | `reg` | `w` | `w0` | `w` | +| AArch64 | `reg` | `x` | `x0` | `x` | +| AArch64 | `vreg` | None | `v0` | None | +| AArch64 | `vreg` | `v` | `v0` | None | +| AArch64 | `vreg` | `b` | `b0` | `b` | +| AArch64 | `vreg` | `h` | `h0` | `h` | +| AArch64 | `vreg` | `s` | `s0` | `s` | +| AArch64 | `vreg` | `d` | `d0` | `d` | +| AArch64 | `vreg` | `q` | `q0` | `q` | +| ARM | `reg` | None | `r0` | None | +| ARM | `sreg` | None | `s0` | None | +| ARM | `dreg` | None | `d0` | `P` | +| ARM | `qreg` | None | `q0` | `q` | +| ARM | `qreg` | `e` / `f` | `d0` / `d1` | `e` / `f` | +| RISC-V | `reg` | None | `x1` | None | +| RISC-V | `freg` | None | `f0` | None | + +> Notes: +> - on ARM `e` / `f`: this prints the low or high doubleword register name of a NEON quad (128-bit) register. +> - on x86: our behavior for `reg` with no modifiers differs from what GCC does. GCC will infer the modifier based on the operand value type, while we default to the full register size. +> - on x86 `xmm_reg`: the `x`, `t` and `g` LLVM modifiers are not yet implemented in LLVM (they are supported by GCC only), but this should be a simple change. + +As stated in the previous section, passing an input value smaller than the register width will result in the upper bits of the register containing undefined values. This is not a problem if the inline asm only accesses the lower bits of the register, which can be done by using a template modifier to use a subregister name in the asm code (e.g. `ax` instead of `rax`). Since this an easy pitfall, the compiler will suggest a template modifier to use where appropriate given the input type. If all references to an operand already have modifiers then the warning is suppressed for that operand. + +[llvm-argmod]: http://llvm.org/docs/LangRef.html#asm-template-argument-modifiers + +## Options + +Flags are used to further influence the behavior of the inline assembly block. +Currently the following options are defined: +- `pure`: The `asm` block has no side effects, and its outputs depend only on its direct inputs (i.e. the values themselves, not what they point to) or values read from memory (unless the `nomem` options is also set). This allows the compiler to execute the `asm` block fewer times than specified in the program (e.g. by hoisting it out of a loop) or even eliminate it entirely if the outputs are not used. +- `nomem`: The `asm` blocks does not read or write to any memory. This allows the compiler to cache the values of modified global variables in registers across the `asm` block since it knows that they are not read or written to by the `asm`. +- `readonly`: The `asm` block does not write to any memory. This allows the compiler to cache the values of unmodified global variables in registers across the `asm` block since it knows that they are not written to by the `asm`. +- `preserves_flags`: The `asm` block does not modify the flags register (defined in the [rules][rules] below). This allows the compiler to avoid recomputing the condition flags after the `asm` block. +- `noreturn`: The `asm` block never returns, and its return type is defined as `!` (never). Behavior is undefined if execution falls through past the end of the asm code. A `noreturn` asm block behaves just like a function which doesn't return; notably, local variables in scope are not dropped before it is invoked. +- `nostack`: The `asm` block does not push data to the stack, or write to the stack red-zone (if supported by the target). If this option is *not* used then the stack pointer is guaranteed to be suitably aligned (according to the target ABI) for a function call. +- `att_syntax`: This option is only valid on x86, and causes the assembler to use the `.att_syntax prefix` mode of the GNU assembler. Register operands are substituted in with a leading `%`. + +The compiler performs some additional checks on options: +- The `nomem` and `readonly` options are mutually exclusive: it is a compile-time error to specify both. +- The `pure` option must be combined with either the `nomem` or `readonly` options, otherwise a compile-time error is emitted. +- It is a compile-time error to specify `pure` on an asm block with no outputs or only discarded outputs (`_`). +- It is a compile-time error to specify `noreturn` on an asm block with outputs. + +## Rules for inline assembly +[rules]: #rules + +- Any registers not specified as inputs will contain an undefined value on entry to the asm block. + - An "undefined value" in the context of inline assembly means that the register can (non-deterministically) have any one of the possible values allowed by the architecture. Notably it is not the same as an LLVM `undef` which can have a different value every time you read it (since such a concept does not exist in assembly code). +- Any registers not specified as outputs must have the same value upon exiting the asm block as they had on entry, otherwise behavior is undefined. + - This only applies to registers which can be specified as an input or output. Other registers follow target-specific rules. + - Note that a `lateout` may be allocated to the same register as an `in`, in which case this rule does not apply. Code should not rely on this however since it depends on the results of register allocation. +- Behavior is undefined if execution unwinds out of an asm block. + - This also applies if the assembly code calls a function which then unwinds. +- The set of memory locations that assembly code is allowed the read and write are the same as those allowed for an FFI function. + - Refer to the unsafe code guidelines for the exact rules. + - If the `readonly` option is set, then only memory reads are allowed. + - If the `nomem` option is set then no reads or writes to memory are allowed. + - These rules do not apply to memory which is private to the asm code, such as stack space allocated within the asm block. +- The compiler cannot assume that the instructions in the asm are the ones that will actually end up executed. + - This effectively means that the compiler must treat the `asm!` as a black box and only take the interface specification into account, not the instructions themselves. + - Runtime code patching is allowed, via target-specific mechanisms (outside the scope of this RFC). +- Unless the `nostack` option is set, asm code is allowed to use stack space below the stack pointer. + - On entry to the asm block the stack pointer is guaranteed to be suitably aligned (according to the target ABI) for a function call. + - You are responsible for making sure you don't overflow the stack (e.g. use stack probing to ensure you hit a guard page). + - You should adjust the stack pointer when allocating stack memory as required by the target ABI. + - The stack pointer must be restored to its original value before leaving the asm block. +- If the `noreturn` option is set then behavior is undefined if execution falls through to the end of the asm block. +- If the `pure` option is set then behavior is undefined if the `asm` has side-effects other than its direct outputs. Behavior is also undefined if two executions of the `asm` code with the same inputs result in different outputs. + - When used with the `nomem` option, "inputs" are just the direct inputs of the `asm!`. + - When used with the `readonly` option, "inputs" comprise the direct inputs of the `asm!` and any memory that the `asm!` block is allowed to read. +- These flags registers must be restored upon exiting the asm block if the `preserves_flags` option is set: + - x86 + - Status flags in `EFLAGS` (CF, PF, AF, ZF, SF, OF). + - Direction flag in `EFLAGS` (DF). + - Floating-point status word (all). + - Floating-point exception flags in `MXCSR` (PE, UE, OE, ZE, DE, IE). + - ARM + - Condition flags in `CPSR` (N, Z, C, V) + - Saturation flag in `CPSR` (Q) + - Greater than or equal flags in `CPSR` (GE). + - Condition flags in `FPSCR` (N, Z, C, V) + - Saturation flag in `FPSCR` (QC) + - Floating-point exception flags in `FPSCR` (IDC, IXC, UFC, OFC, DZC, IOC). + - AArch64 + - Condition flags (`NZCV` register). + - Floating-point status (`FPSR` register). + - RISC-V + - Floating-point exception flags in `fcsr` (`fflags`). +- The requirement of restoring the stack pointer and non-output registers to their original value only applies when exiting an `asm!` block. + - This means that `asm!` blocks that never return (even if not marked `noreturn`) don't need to preserve these registers. + - When returning to a different `asm!` block than you entered (e.g. for context switching), these registers must contain the value they had upon entering the `asm!` block that you are *exiting*. + - You cannot exit an `asm!` block that has not been entered. Neither can you exit an `asm!` block that has already been exited. + - You are responsible for switching any target-specific state (e.g. thread-local storage, stack bounds). + - The set of memory locations that you may access is the intersection of those allowed by the `asm!` blocks you entered and exited. + +> **Note**: As a general rule, these are the flags which are *not* preserved when performing a function call. diff --git a/src/libcore/macros/mod.rs b/src/libcore/macros/mod.rs index bca979ca0cb..1d4d7013e94 100644 --- a/src/libcore/macros/mod.rs +++ b/src/libcore/macros/mod.rs @@ -1293,22 +1293,21 @@ pub(crate) mod builtin { /// [unstable book]: ../unstable-book/library-features/asm.html #[unstable( feature = "asm", - issue = "70173", + issue = "72016", reason = "inline assembly is not stable enough for use and is subject to change" )] #[rustc_builtin_macro] #[macro_export] macro_rules! asm { - ("assembly template" - : $("output"(operand),)* - : $("input"(operand),)* - : $("clobbers",)* - : $("options",)*) => { + ("assembly template", + $(operands,)* + $(options($(option),*))? + ) => { /* compiler built-in */ }; } - /// Inline assembly. + /// LLVM-style inline assembly. /// /// Read the [unstable book] for the usage. /// @@ -1316,7 +1315,7 @@ pub(crate) mod builtin { #[unstable( feature = "llvm_asm", issue = "70173", - reason = "inline assembly is not stable enough for use and is subject to change" + reason = "LLVM-style inline assembly will never be stabilized, prefer using asm! instead" )] #[rustc_builtin_macro] #[macro_export] diff --git a/src/librustc_error_codes/error_codes/E0660.md b/src/librustc_error_codes/error_codes/E0660.md index 732de6a3818..fccd1b96f60 100644 --- a/src/librustc_error_codes/error_codes/E0660.md +++ b/src/librustc_error_codes/error_codes/E0660.md @@ -7,6 +7,6 @@ llvm_asm!("nop" "nop"); ``` Considering that this would be a long explanation, we instead recommend you -take a look at the [`asm`] chapter of the Unstable book: +take a look at the [`llvm_asm`] chapter of the Unstable book: -[asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html +[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html diff --git a/src/librustc_error_codes/error_codes/E0661.md b/src/librustc_error_codes/error_codes/E0661.md index 3d5cd90f6a3..f1debee7a18 100644 --- a/src/librustc_error_codes/error_codes/E0661.md +++ b/src/librustc_error_codes/error_codes/E0661.md @@ -8,6 +8,6 @@ llvm_asm!("nop" : "r"(a)); ``` Considering that this would be a long explanation, we instead recommend you -take a look at the [`asm`] chapter of the Unstable book: +take a look at the [`llvm_asm`] chapter of the Unstable book: -[asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html +[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html diff --git a/src/librustc_error_codes/error_codes/E0662.md b/src/librustc_error_codes/error_codes/E0662.md index 9858abd10c5..d4765f078b0 100644 --- a/src/librustc_error_codes/error_codes/E0662.md +++ b/src/librustc_error_codes/error_codes/E0662.md @@ -11,6 +11,6 @@ llvm_asm!("xor %eax, %eax" ``` Considering that this would be a long explanation, we instead recommend you -take a look at the [`asm`] chapter of the Unstable book: +take a look at the [`llvm_asm`] chapter of the Unstable book: -[asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html +[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html diff --git a/src/librustc_error_codes/error_codes/E0663.md b/src/librustc_error_codes/error_codes/E0663.md index 36b87be7d6e..d5a85b275db 100644 --- a/src/librustc_error_codes/error_codes/E0663.md +++ b/src/librustc_error_codes/error_codes/E0663.md @@ -11,6 +11,6 @@ llvm_asm!("xor %eax, %eax" ``` Considering that this would be a long explanation, we instead recommend you -take a look at the [`asm`] chapter of the Unstable book: +take a look at the [`llvm_asm`] chapter of the Unstable book: -[asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html +[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html diff --git a/src/librustc_error_codes/error_codes/E0664.md b/src/librustc_error_codes/error_codes/E0664.md index 33924b14e6d..ce9c9491df3 100644 --- a/src/librustc_error_codes/error_codes/E0664.md +++ b/src/librustc_error_codes/error_codes/E0664.md @@ -11,6 +11,6 @@ llvm_asm!("mov $$0x200, %eax" ``` Considering that this would be a long explanation, we instead recommend you -take a look at the [`asm`] chapter of the Unstable book: +take a look at the [`llvm_asm`] chapter of the Unstable book: -[asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/asm.html +[llvm_asm]: https://doc.rust-lang.org/stable/unstable-book/library-features/llvm-asm.html From ac1fb93fce892f52a5b563b2fcb364f6a603fc0d Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 8 May 2020 16:37:57 +0100 Subject: [PATCH 26/36] Fix feature gate tests --- src/test/ui/feature-gates/feature-gate-asm.rs | 6 ++++-- src/test/ui/feature-gates/feature-gate-asm.stderr | 6 +++--- src/test/ui/feature-gates/feature-gate-asm2.rs | 6 ++++-- src/test/ui/feature-gates/feature-gate-asm2.stderr | 6 +++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/test/ui/feature-gates/feature-gate-asm.rs b/src/test/ui/feature-gates/feature-gate-asm.rs index 7eeeb4bc4e2..4eb72031d51 100644 --- a/src/test/ui/feature-gates/feature-gate-asm.rs +++ b/src/test/ui/feature-gates/feature-gate-asm.rs @@ -2,7 +2,9 @@ fn main() { unsafe { - asm!(""); //~ ERROR inline assembly is not stable enough - llvm_asm!(""); //~ ERROR inline assembly is not stable enough + asm!(""); + //~^ ERROR inline assembly is not stable enough + llvm_asm!(""); + //~^ ERROR LLVM-style inline assembly will never be stabilized } } diff --git a/src/test/ui/feature-gates/feature-gate-asm.stderr b/src/test/ui/feature-gates/feature-gate-asm.stderr index 1f9eaa5632e..a71643e0d33 100644 --- a/src/test/ui/feature-gates/feature-gate-asm.stderr +++ b/src/test/ui/feature-gates/feature-gate-asm.stderr @@ -4,11 +4,11 @@ error[E0658]: use of unstable library feature 'asm': inline assembly is not stab LL | asm!(""); | ^^^ | - = note: see issue #70173 for more information + = note: see issue #72016 for more information = help: add `#![feature(asm)]` to the crate attributes to enable -error[E0658]: use of unstable library feature 'llvm_asm': inline assembly is not stable enough for use and is subject to change - --> $DIR/feature-gate-asm.rs:6:9 +error[E0658]: use of unstable library feature 'llvm_asm': LLVM-style inline assembly will never be stabilized, prefer using asm! instead + --> $DIR/feature-gate-asm.rs:7:9 | LL | llvm_asm!(""); | ^^^^^^^^ diff --git a/src/test/ui/feature-gates/feature-gate-asm2.rs b/src/test/ui/feature-gates/feature-gate-asm2.rs index 666a4894f62..8bd7226aca7 100644 --- a/src/test/ui/feature-gates/feature-gate-asm2.rs +++ b/src/test/ui/feature-gates/feature-gate-asm2.rs @@ -2,7 +2,9 @@ fn main() { unsafe { - println!("{:?}", asm!("")); //~ ERROR inline assembly is not stable - println!("{:?}", llvm_asm!("")); //~ ERROR inline assembly is not stable + println!("{:?}", asm!("")); + //~^ ERROR inline assembly is not stable enough + println!("{:?}", llvm_asm!("")); + //~^ ERROR LLVM-style inline assembly will never be stabilized } } diff --git a/src/test/ui/feature-gates/feature-gate-asm2.stderr b/src/test/ui/feature-gates/feature-gate-asm2.stderr index 17ba66e9842..a8022cb72e0 100644 --- a/src/test/ui/feature-gates/feature-gate-asm2.stderr +++ b/src/test/ui/feature-gates/feature-gate-asm2.stderr @@ -4,11 +4,11 @@ error[E0658]: use of unstable library feature 'asm': inline assembly is not stab LL | println!("{:?}", asm!("")); | ^^^ | - = note: see issue #70173 for more information + = note: see issue #72016 for more information = help: add `#![feature(asm)]` to the crate attributes to enable -error[E0658]: use of unstable library feature 'llvm_asm': inline assembly is not stable enough for use and is subject to change - --> $DIR/feature-gate-asm2.rs:6:26 +error[E0658]: use of unstable library feature 'llvm_asm': LLVM-style inline assembly will never be stabilized, prefer using asm! instead + --> $DIR/feature-gate-asm2.rs:7:26 | LL | println!("{:?}", llvm_asm!("")); | ^^^^^^^^ From 46db0dfe8c5f26c687b5ce269b55ad1aa6d3dc95 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 8 May 2020 18:14:06 +0100 Subject: [PATCH 27/36] Fix tests --- src/libfmt_macros/tests.rs | 4 ++-- src/test/assembly/asm/x86-modifiers.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libfmt_macros/tests.rs b/src/libfmt_macros/tests.rs index a0cef78f924..9932c1df7a9 100644 --- a/src/libfmt_macros/tests.rs +++ b/src/libfmt_macros/tests.rs @@ -1,7 +1,7 @@ use super::*; fn same(fmt: &'static str, p: &[Piece<'static>]) { - let parser = Parser::new(fmt, None, vec![], false, ParseMode::Format); + let parser = Parser::new(fmt, None, None, false, ParseMode::Format); assert_eq!(parser.collect::>>(), p); } @@ -20,7 +20,7 @@ fn fmtdflt() -> FormatSpec<'static> { } fn musterr(s: &str) { - let mut p = Parser::new(s, None, vec![], false, ParseMode::Format); + let mut p = Parser::new(s, None, None, false, ParseMode::Format); p.next(); assert!(!p.errors.is_empty()); } diff --git a/src/test/assembly/asm/x86-modifiers.rs b/src/test/assembly/asm/x86-modifiers.rs index 1670744291f..e538167cd46 100644 --- a/src/test/assembly/asm/x86-modifiers.rs +++ b/src/test/assembly/asm/x86-modifiers.rs @@ -1,3 +1,4 @@ +// no-system-llvm // revisions: x86_64 i686 // assembly-output: emit-asm // compile-flags: -O From 9215ead1ee94bef156c109693c9aa5bb4981d098 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 8 May 2020 21:00:33 +0100 Subject: [PATCH 28/36] Fix handling on InlineAsm for the unconditional recursion lint. --- src/librustc_mir_build/lints.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/librustc_mir_build/lints.rs b/src/librustc_mir_build/lints.rs index 161023f1613..dbafc98fb50 100644 --- a/src/librustc_mir_build/lints.rs +++ b/src/librustc_mir_build/lints.rs @@ -114,9 +114,14 @@ impl<'mir, 'tcx> TriColorVisitor<&'mir Body<'tcx>> for Search<'mir, 'tcx> { | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } => ControlFlow::Break(NonRecursive), - // FIXME(Amanieu): I am not 100% sure about this, but it triggers - // a spurious warning otherwise. - TerminatorKind::InlineAsm { .. } => ControlFlow::Break(NonRecursive), + // A diverging InlineAsm is treated as non-recursing + TerminatorKind::InlineAsm { destination, .. } => { + if destination.is_some() { + ControlFlow::Continue + } else { + ControlFlow::Break(NonRecursive) + } + } // These do not. TerminatorKind::Assert { .. } From 6f8be8cc8c826a4d2759e26c6514fcfcf076a105 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Fri, 8 May 2020 21:05:22 +0100 Subject: [PATCH 29/36] Fix docs --- .../unstable-book/src/library-features/asm.md | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/doc/unstable-book/src/library-features/asm.md b/src/doc/unstable-book/src/library-features/asm.md index bc55cd4a5bd..9ce6d1caa1e 100644 --- a/src/doc/unstable-book/src/library-features/asm.md +++ b/src/doc/unstable-book/src/library-features/asm.md @@ -25,6 +25,7 @@ cannot be otherwise achieved. Accessing low level hardware primitives, e.g. in k Let us start with the simplest possible example: ```rust +# #![feature(asm)] unsafe { asm!("nop"); } @@ -41,6 +42,7 @@ Now inserting an instruction that does nothing is rather boring. Let us do somet actually acts on data: ```rust +# #![feature(asm)] let x: u64; unsafe { asm!("mov {}, 5", out(reg) x); @@ -62,6 +64,7 @@ the template and will read the variable from there after the inline assembly fin Let us see another example that also uses an input: ```rust +# #![feature(asm)] let i: u64 = 3; let o: u64; unsafe { @@ -93,6 +96,7 @@ readability, and allows reordering instructions without changing the argument or We can further refine the above example to avoid the `mov` instruction: ```rust +# #![feature(asm)] let mut x: u64 = 3; unsafe { asm!("add {0}, {number}", inout(reg) x, number = const 5); @@ -106,6 +110,7 @@ This is different from specifying an input and output separately in that it is g It is also possible to specify different variables for the input and output parts of an `inout` operand: ```rust +# #![feature(asm)] let x: u64 = 3; let y: u64; unsafe { @@ -127,6 +132,7 @@ There is also a `inlateout` variant of this specifier. Here is an example where `inlateout` *cannot* be used: ```rust +# #![feature(asm)] let mut a: u64 = 4; let b: u64 = 4; let c: u64 = 4; @@ -144,6 +150,7 @@ Here the compiler is free to allocate the same register for inputs `b` and `c` s However the following example can use `inlateout` since the output is only modified after all input registers have been read: ```rust +# #![feature(asm)] let mut a: u64 = 4; let b: u64 = 4; unsafe { @@ -161,21 +168,24 @@ Therefore, Rust inline assembly provides some more specific constraint specifier While `reg` is generally available on any architecture, these are highly architecture specific. E.g. for x86 the general purpose registers `eax`, `ebx`, `ecx`, `edx`, `ebp`, `esi`, and `edi` among others can be addressed by their name. -```rust +```rust,no_run +# #![feature(asm)] +let cmd = 0xd1; unsafe { - asm!("out 0x64, rax", in("rax") cmd); + asm!("out 0x64, eax", in("eax") cmd); } ``` In this example we call the `out` instruction to output the content of the `cmd` variable -to port `0x64`. Since the `out` instruction only accepts `rax` (and its sub registers) as operand -we had to use the `rax` constraint specifier. +to port `0x64`. Since the `out` instruction only accepts `eax` (and its sub registers) as operand +we had to use the `eax` constraint specifier. Note that unlike other operand types, explicit register operands cannot be used in the template string: you can't use `{}` and should write the register name directly instead. Also, they must appear at the end of the operand list after all other operand types. Consider this example which uses the x86 `mul` instruction: ```rust +# #![feature(asm)] fn mul(a: u64, b: u64) -> u128 { let lo: u64; let hi: u64; @@ -191,7 +201,7 @@ fn mul(a: u64, b: u64) -> u128 { ); } - hi as u128 << 64 + lo as u128 + (hi as u128) << 64 + lo as u128 } ``` @@ -211,8 +221,9 @@ We need to tell the compiler about this since it may need to save and restore th around the inline assembly block. ```rust -let ebx: u64; -let ecx: u64; +# #![feature(asm)] +let ebx: u32; +let ecx: u32; unsafe { asm!( @@ -240,6 +251,7 @@ However we still need to tell the compiler that `eax` and `edx` have been modifi This can also be used with a general register class (e.g. `reg`) to obtain a scratch register for use inside the asm code: ```rust +# #![feature(asm)] // Multiply x by 6 using shifts and adds let mut x: u64 = 4; unsafe { @@ -259,6 +271,7 @@ A special operand type, `sym`, allows you to use the symbol name of a `fn` or `s This allows you to call a function or access a global variable without needing to keep its address in a register. ```rust +# #![feature(asm)] extern "C" fn foo(arg: i32) { println!("arg = {}", arg); } @@ -266,7 +279,7 @@ extern "C" fn foo(arg: i32) { fn call_foo(arg: i32) { unsafe { asm!( - "call {}" + "call {}", sym foo, // 1st argument in rdi, which is caller-saved inout("rdi") arg => _, @@ -294,10 +307,11 @@ By default the compiler will always choose the name that refers to the full regi This default can be overriden by using modifiers on the template string operands, just like you would with format strings: ```rust +# #![feature(asm)] let mut x: u16 = 0xab; unsafe { - asm!("mov {0:h}, {0:b}", inout(reg_abcd) x); + asm!("mov {0:h}, {0:l}", inout(reg_abcd) x); } assert_eq!(x, 0xabab); @@ -306,7 +320,7 @@ assert_eq!(x, 0xabab); In this example, we use the `reg_abcd` register class to restrict the register allocator to the 4 legacy x86 register (`ax`, `bx`, `cx`, `dx`) of which the first two bytes can be addressed independently. Let us assume that the register allocator has chosen to allocate `x` in the `ax` register. -The `h` modifier will emit the register name for the high byte of that register and the `b` modifier will emit the register name for the low byte. The asm code will therefore be expanded as `mov ah, al` which copies the low byte of the value into the high byte. +The `h` modifier will emit the register name for the high byte of that register and the `l` modifier will emit the register name for the low byte. The asm code will therefore be expanded as `mov ah, al` which copies the low byte of the value into the high byte. If you use a smaller data type (e.g. `u16`) with an operand and forget the use template modifiers, the compiler will emit a warning and suggest the correct modifier to use. @@ -317,6 +331,7 @@ By default, an inline assembly block is treated the same way as an external FFI Let's take our previous example of an `add` instruction: ```rust +# #![feature(asm)] let mut a: u64 = 4; let b: u64 = 4; unsafe { @@ -348,7 +363,7 @@ When required, options are specified as the final argument. The following ABNF specifies the general syntax: -``` +```ignore dir_spec := "in" / "out" / "lateout" / "inout" / "inlateout" reg_spec := / "" operand_expr := expr / "_" / expr "=>" expr / expr "=>" "_" @@ -612,7 +627,7 @@ Currently the following options are defined: - `pure`: The `asm` block has no side effects, and its outputs depend only on its direct inputs (i.e. the values themselves, not what they point to) or values read from memory (unless the `nomem` options is also set). This allows the compiler to execute the `asm` block fewer times than specified in the program (e.g. by hoisting it out of a loop) or even eliminate it entirely if the outputs are not used. - `nomem`: The `asm` blocks does not read or write to any memory. This allows the compiler to cache the values of modified global variables in registers across the `asm` block since it knows that they are not read or written to by the `asm`. - `readonly`: The `asm` block does not write to any memory. This allows the compiler to cache the values of unmodified global variables in registers across the `asm` block since it knows that they are not written to by the `asm`. -- `preserves_flags`: The `asm` block does not modify the flags register (defined in the [rules][rules] below). This allows the compiler to avoid recomputing the condition flags after the `asm` block. +- `preserves_flags`: The `asm` block does not modify the flags register (defined in the rules below). This allows the compiler to avoid recomputing the condition flags after the `asm` block. - `noreturn`: The `asm` block never returns, and its return type is defined as `!` (never). Behavior is undefined if execution falls through past the end of the asm code. A `noreturn` asm block behaves just like a function which doesn't return; notably, local variables in scope are not dropped before it is invoked. - `nostack`: The `asm` block does not push data to the stack, or write to the stack red-zone (if supported by the target). If this option is *not* used then the stack pointer is guaranteed to be suitably aligned (according to the target ABI) for a function call. - `att_syntax`: This option is only valid on x86, and causes the assembler to use the `.att_syntax prefix` mode of the GNU assembler. Register operands are substituted in with a leading `%`. @@ -624,7 +639,6 @@ The compiler performs some additional checks on options: - It is a compile-time error to specify `noreturn` on an asm block with outputs. ## Rules for inline assembly -[rules]: #rules - Any registers not specified as inputs will contain an undefined value on entry to the asm block. - An "undefined value" in the context of inline assembly means that the register can (non-deterministically) have any one of the possible values allowed by the architecture. Notably it is not the same as an LLVM `undef` which can have a different value every time you read it (since such a concept does not exist in assembly code). From cecffdc1d74718444b6654b120dcd57dcbad018f Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 11 May 2020 17:53:32 +0100 Subject: [PATCH 30/36] Fix const handling and add tests for const operands --- src/librustc_codegen_ssa/mir/block.rs | 219 +++++++++++++++----------- src/test/ui/asm/const.rs | 56 +++++++ 2 files changed, 184 insertions(+), 91 deletions(-) create mode 100644 src/test/ui/asm/const.rs diff --git a/src/librustc_codegen_ssa/mir/block.rs b/src/librustc_codegen_ssa/mir/block.rs index 923fcd326a2..b487ed8dea8 100644 --- a/src/librustc_codegen_ssa/mir/block.rs +++ b/src/librustc_codegen_ssa/mir/block.rs @@ -13,6 +13,7 @@ use rustc_ast::ast; use rustc_hir::lang_items; use rustc_index::vec::Idx; use rustc_middle::mir; +use rustc_middle::mir::interpret::{AllocId, ConstValue, Pointer, Scalar}; use rustc_middle::mir::AssertKind; use rustc_middle::ty::layout::{FnAbiExt, HasTyCtxt}; use rustc_middle::ty::{self, Instance, Ty, TypeFoldable}; @@ -821,6 +822,123 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { cleanup, ); } + + fn codegen_asm_terminator( + &mut self, + helper: TerminatorCodegenHelper<'tcx>, + mut bx: Bx, + terminator: &mir::Terminator<'tcx>, + template: &[ast::InlineAsmTemplatePiece], + operands: &[mir::InlineAsmOperand<'tcx>], + options: ast::InlineAsmOptions, + destination: Option, + ) { + let span = terminator.source_info.span; + + let operands: Vec<_> = operands + .iter() + .map(|op| match *op { + mir::InlineAsmOperand::In { reg, ref value } => { + let value = self.codegen_operand(&mut bx, value); + InlineAsmOperandRef::In { reg, value } + } + mir::InlineAsmOperand::Out { reg, late, ref place } => { + let place = place.map(|place| self.codegen_place(&mut bx, place.as_ref())); + InlineAsmOperandRef::Out { reg, late, place } + } + mir::InlineAsmOperand::InOut { reg, late, ref in_value, ref out_place } => { + let in_value = self.codegen_operand(&mut bx, in_value); + let out_place = + out_place.map(|out_place| self.codegen_place(&mut bx, out_place.as_ref())); + InlineAsmOperandRef::InOut { reg, late, in_value, out_place } + } + mir::InlineAsmOperand::Const { ref value } => { + if let mir::Operand::Constant(constant) = value { + let const_value = self + .eval_mir_constant(constant) + .unwrap_or_else(|_| span_bug!(span, "asm const cannot be resolved")); + let ty = constant.literal.ty; + let size = bx.layout_of(ty).size; + let scalar = match const_value { + // Promoted constants are evaluated into a ByRef instead of a Scalar, + // but we want the scalar value here. + ConstValue::ByRef { alloc, offset } => { + let ptr = Pointer::new(AllocId(0), offset); + alloc + .read_scalar(&bx, ptr, size) + .and_then(|s| s.not_undef()) + .unwrap_or_else(|e| { + bx.tcx().sess.span_err( + span, + &format!("Could not evaluate asm const: {}", e), + ); + + // We are erroring out, just emit a dummy constant. + Scalar::from_u64(0) + }) + } + _ => span_bug!(span, "expected ByRef for promoted asm const"), + }; + let value = scalar.assert_bits(size); + let string = match ty.kind { + ty::Uint(_) => value.to_string(), + ty::Int(int_ty) => { + match int_ty.normalize(bx.tcx().sess.target.ptr_width) { + ast::IntTy::I8 => (value as i8).to_string(), + ast::IntTy::I16 => (value as i16).to_string(), + ast::IntTy::I32 => (value as i32).to_string(), + ast::IntTy::I64 => (value as i64).to_string(), + ast::IntTy::I128 => (value as i128).to_string(), + ast::IntTy::Isize => unreachable!(), + } + } + ty::Float(ast::FloatTy::F32) => { + f32::from_bits(value as u32).to_string() + } + ty::Float(ast::FloatTy::F64) => { + f64::from_bits(value as u64).to_string() + } + _ => span_bug!(span, "asm const has bad type {}", ty), + }; + InlineAsmOperandRef::Const { string } + } else { + span_bug!(span, "asm const is not a constant"); + } + } + mir::InlineAsmOperand::SymFn { ref value } => { + let literal = self.monomorphize(&value.literal); + if let ty::FnDef(def_id, substs) = literal.ty.kind { + let instance = ty::Instance::resolve( + bx.tcx(), + ty::ParamEnv::reveal_all(), + def_id, + substs, + ) + .unwrap() + .unwrap(); + InlineAsmOperandRef::SymFn { instance } + } else { + span_bug!(span, "invalid type for asm sym (fn)"); + } + } + mir::InlineAsmOperand::SymStatic { ref value } => { + if let Some(def_id) = value.check_static_ptr(bx.tcx()) { + InlineAsmOperandRef::SymStatic { def_id } + } else { + span_bug!(span, "invalid type for asm sym (static)"); + } + } + }) + .collect(); + + bx.codegen_inline_asm(template, &operands, options, span); + + if let Some(target) = destination { + helper.funclet_br(self, &mut bx, target); + } else { + bx.unreachable(); + } + } } impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { @@ -916,97 +1034,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { bug!("borrowck false edges in codegen") } - mir::TerminatorKind::InlineAsm { template, ref operands, options, ref destination } => { - let span = terminator.source_info.span; - - let operands: Vec<_> = operands - .iter() - .map(|op| match *op { - mir::InlineAsmOperand::In { reg, ref value } => { - let value = self.codegen_operand(&mut bx, value); - InlineAsmOperandRef::In { reg, value } - } - mir::InlineAsmOperand::Out { reg, late, ref place } => { - let place = - place.map(|place| self.codegen_place(&mut bx, place.as_ref())); - InlineAsmOperandRef::Out { reg, late, place } - } - mir::InlineAsmOperand::InOut { reg, late, ref in_value, ref out_place } => { - let in_value = self.codegen_operand(&mut bx, in_value); - let out_place = out_place - .map(|out_place| self.codegen_place(&mut bx, out_place.as_ref())); - InlineAsmOperandRef::InOut { reg, late, in_value, out_place } - } - mir::InlineAsmOperand::Const { ref value } => { - if let mir::Operand::Constant(constant) = value { - let const_value = - self.eval_mir_constant(constant).unwrap_or_else(|_| { - span_bug!(span, "asm const cannot be resolved") - }); - let ty = constant.literal.ty; - let value = const_value - .try_to_bits_for_ty(bx.tcx(), ty::ParamEnv::reveal_all(), ty) - .unwrap_or_else(|| { - span_bug!(span, "asm const has non-scalar value") - }); - let string = match ty.kind { - ty::Uint(_) => value.to_string(), - ty::Int(int_ty) => { - match int_ty.normalize(bx.tcx().sess.target.ptr_width) { - ast::IntTy::I8 => (value as i8).to_string(), - ast::IntTy::I16 => (value as i16).to_string(), - ast::IntTy::I32 => (value as i32).to_string(), - ast::IntTy::I64 => (value as i64).to_string(), - ast::IntTy::I128 => (value as i128).to_string(), - ast::IntTy::Isize => unreachable!(), - } - } - ty::Float(ast::FloatTy::F32) => { - f32::from_bits(value as u32).to_string() - } - ty::Float(ast::FloatTy::F64) => { - f64::from_bits(value as u64).to_string() - } - _ => span_bug!(span, "asm const has bad type {}", ty), - }; - InlineAsmOperandRef::Const { string } - } else { - span_bug!(span, "asm const is not a constant"); - } - } - mir::InlineAsmOperand::SymFn { ref value } => { - let literal = self.monomorphize(&value.literal); - if let ty::FnDef(def_id, substs) = literal.ty.kind { - let instance = ty::Instance::resolve( - bx.tcx(), - ty::ParamEnv::reveal_all(), - def_id, - substs, - ) - .unwrap() - .unwrap(); - InlineAsmOperandRef::SymFn { instance } - } else { - span_bug!(span, "invalid type for asm sym (fn)"); - } - } - mir::InlineAsmOperand::SymStatic { ref value } => { - if let Some(def_id) = value.check_static_ptr(bx.tcx()) { - InlineAsmOperandRef::SymStatic { def_id } - } else { - span_bug!(span, "invalid type for asm sym (static)"); - } - } - }) - .collect(); - - bx.codegen_inline_asm(template, &operands, options, span); - - if let Some(target) = destination { - helper.funclet_br(self, &mut bx, *target); - } else { - bx.unreachable(); - } + mir::TerminatorKind::InlineAsm { template, ref operands, options, destination } => { + self.codegen_asm_terminator( + helper, + bx, + terminator, + template, + operands, + options, + destination, + ); } } } diff --git a/src/test/ui/asm/const.rs b/src/test/ui/asm/const.rs new file mode 100644 index 00000000000..e08da24f44a --- /dev/null +++ b/src/test/ui/asm/const.rs @@ -0,0 +1,56 @@ +// no-system-llvm +// only-x86_64 +// run-pass + +#![feature(asm)] + +use std::mem::size_of; + +trait Proj { + const C: usize; +} +impl Proj for i8 { + const C: usize = 8; +} +impl Proj for i16 { + const C: usize = 16; +} + +const fn constfn(x: usize) -> usize { + x +} + +fn generic() { + unsafe { + let a: usize; + asm!("mov {}, {}", out(reg) a, const size_of::()); + assert_eq!(a, size_of::()); + + let b: usize; + asm!("mov {}, {}", out(reg) b, const size_of::() + constfn(5)); + assert_eq!(b, size_of::() + 5); + + let c: usize; + asm!("mov {}, {}", out(reg) c, const T::C); + assert_eq!(c, T::C); + } +} + +fn main() { + unsafe { + let a: usize; + asm!("mov {}, {}", out(reg) a, const 5); + assert_eq!(a, 5); + + let b: usize; + asm!("mov {}, {}", out(reg) b, const constfn(5)); + assert_eq!(b, 5); + + let c: usize; + asm!("mov {}, {}", out(reg) c, const constfn(5) + constfn(5)); + assert_eq!(c, 10); + } + + generic::(); + generic::(); +} From 3233565cdfd10819005e923d58efcc4d4f7949c5 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 11 May 2020 21:48:21 +0100 Subject: [PATCH 31/36] Mark asm unstable book doctests as allow_fail since they don't work with system LLVM --- .../unstable-book/src/library-features/asm.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/doc/unstable-book/src/library-features/asm.md b/src/doc/unstable-book/src/library-features/asm.md index 9ce6d1caa1e..bca5281b0b5 100644 --- a/src/doc/unstable-book/src/library-features/asm.md +++ b/src/doc/unstable-book/src/library-features/asm.md @@ -24,7 +24,7 @@ cannot be otherwise achieved. Accessing low level hardware primitives, e.g. in k Let us start with the simplest possible example: -```rust +```rust,allow_fail # #![feature(asm)] unsafe { asm!("nop"); @@ -41,7 +41,7 @@ in the first argument of the `asm!` macro as a string literal. Now inserting an instruction that does nothing is rather boring. Let us do something that actually acts on data: -```rust +```rust,allow_fail # #![feature(asm)] let x: u64; unsafe { @@ -63,7 +63,7 @@ the template and will read the variable from there after the inline assembly fin Let us see another example that also uses an input: -```rust +```rust,allow_fail # #![feature(asm)] let i: u64 = 3; let o: u64; @@ -95,7 +95,7 @@ readability, and allows reordering instructions without changing the argument or We can further refine the above example to avoid the `mov` instruction: -```rust +```rust,allow_fail # #![feature(asm)] let mut x: u64 = 3; unsafe { @@ -109,7 +109,7 @@ This is different from specifying an input and output separately in that it is g It is also possible to specify different variables for the input and output parts of an `inout` operand: -```rust +```rust,allow_fail # #![feature(asm)] let x: u64 = 3; let y: u64; @@ -131,7 +131,7 @@ There is also a `inlateout` variant of this specifier. Here is an example where `inlateout` *cannot* be used: -```rust +```rust,allow_fail # #![feature(asm)] let mut a: u64 = 4; let b: u64 = 4; @@ -149,7 +149,7 @@ Here the compiler is free to allocate the same register for inputs `b` and `c` s However the following example can use `inlateout` since the output is only modified after all input registers have been read: -```rust +```rust,allow_fail # #![feature(asm)] let mut a: u64 = 4; let b: u64 = 4; @@ -168,7 +168,7 @@ Therefore, Rust inline assembly provides some more specific constraint specifier While `reg` is generally available on any architecture, these are highly architecture specific. E.g. for x86 the general purpose registers `eax`, `ebx`, `ecx`, `edx`, `ebp`, `esi`, and `edi` among others can be addressed by their name. -```rust,no_run +```rust,allow_fail,no_run # #![feature(asm)] let cmd = 0xd1; unsafe { @@ -184,7 +184,7 @@ Note that unlike other operand types, explicit register operands cannot be used Consider this example which uses the x86 `mul` instruction: -```rust +```rust,allow_fail # #![feature(asm)] fn mul(a: u64, b: u64) -> u128 { let lo: u64; @@ -220,7 +220,7 @@ This state is generally referred to as being "clobbered". We need to tell the compiler about this since it may need to save and restore this state around the inline assembly block. -```rust +```rust,allow_fail # #![feature(asm)] let ebx: u32; let ecx: u32; @@ -250,7 +250,7 @@ However we still need to tell the compiler that `eax` and `edx` have been modifi This can also be used with a general register class (e.g. `reg`) to obtain a scratch register for use inside the asm code: -```rust +```rust,allow_fail # #![feature(asm)] // Multiply x by 6 using shifts and adds let mut x: u64 = 4; @@ -270,7 +270,7 @@ assert_eq!(x, 4 * 6); A special operand type, `sym`, allows you to use the symbol name of a `fn` or `static` in inline assembly code. This allows you to call a function or access a global variable without needing to keep its address in a register. -```rust +```rust,allow_fail # #![feature(asm)] extern "C" fn foo(arg: i32) { println!("arg = {}", arg); @@ -306,7 +306,7 @@ By default the compiler will always choose the name that refers to the full regi This default can be overriden by using modifiers on the template string operands, just like you would with format strings: -```rust +```rust,allow_fail # #![feature(asm)] let mut x: u16 = 0xab; @@ -330,7 +330,7 @@ By default, an inline assembly block is treated the same way as an external FFI Let's take our previous example of an `add` instruction: -```rust +```rust,allow_fail # #![feature(asm)] let mut a: u64 = 4; let b: u64 = 4; From 5a20f396727a6f5713f9f6ffa7f0ed18b296558a Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Tue, 12 May 2020 15:24:24 +0100 Subject: [PATCH 32/36] Update compiler_builtins to 0.1.28 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 969f7a041d9..6d898661dcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -635,9 +635,9 @@ dependencies = [ [[package]] name = "compiler_builtins" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f18416546abfbf8d801c555a0e99524453e7214f9cc9107ad49de3d5948ccc" +checksum = "439a6fab343b1dab347823537734a5cd4ae6ae2000b465ab886f64cdb723bd14" dependencies = [ "cc", "rustc-std-workspace-core", From 62ff543c36fbfccf152fc3869c26767f908995c7 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Thu, 14 May 2020 22:09:32 +0100 Subject: [PATCH 33/36] Simplify register name output for x86 --- src/librustc_target/asm/x86.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/librustc_target/asm/x86.rs b/src/librustc_target/asm/x86.rs index c3dd7bc8e7b..ed51b526414 100644 --- a/src/librustc_target/asm/x86.rs +++ b/src/librustc_target/asm/x86.rs @@ -297,7 +297,7 @@ impl X86InlineAsmReg { _ => unreachable!(), } } else if self as u32 <= Self::di as u32 { - let root = ["si", "di"][self as usize - Self::si as usize]; + let root = self.name(); match modifier.unwrap_or(reg_default_modifier) { 'l' => write!(out, "{}l", root), 'x' => write!(out, "{}", root), @@ -306,12 +306,12 @@ impl X86InlineAsmReg { _ => unreachable!(), } } else if self as u32 <= Self::r15 as u32 { - let index = self as u32 - Self::r8 as u32 + 8; + let root = self.name(); match modifier.unwrap_or(reg_default_modifier) { - 'l' => write!(out, "r{}b", index), - 'x' => write!(out, "r{}w", index), - 'e' => write!(out, "r{}d", index), - 'r' => write!(out, "r{}", index), + 'l' => write!(out, "{}b", root), + 'x' => write!(out, "{}w", root), + 'e' => write!(out, "{}d", root), + 'r' => out.write_str(root), _ => unreachable!(), } } else if self as u32 <= Self::r15b as u32 { @@ -329,8 +329,7 @@ impl X86InlineAsmReg { let index = self as u32 - Self::zmm0 as u32; write!(out, "{}{}", prefix, index) } else { - let index = self as u32 - Self::k1 as u32 + 1; - write!(out, "k{}", index) + out.write_str(self.name()) } } From 32471f4d876bee61217034d9426bc6c63959832e Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 18 May 2020 14:35:40 +0100 Subject: [PATCH 34/36] Update LLVM submodule --- src/llvm-project | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm-project b/src/llvm-project index e7aa4d5ac99..246dfcd1c86 160000 --- a/src/llvm-project +++ b/src/llvm-project @@ -1 +1 @@ -Subproject commit e7aa4d5ac9950ac77c970609c83e66f9ef65c763 +Subproject commit 246dfcd1c864d4eab417f74d3599f061d01cb3ae From 09efea5f542512eb7d6e70cd8c9f74d0230f8b50 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 18 May 2020 14:39:07 +0100 Subject: [PATCH 35/36] Update unstable book documentation with the latest RFC text --- src/doc/unstable-book/src/library-features/asm.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/doc/unstable-book/src/library-features/asm.md b/src/doc/unstable-book/src/library-features/asm.md index bca5281b0b5..0b68991fce2 100644 --- a/src/doc/unstable-book/src/library-features/asm.md +++ b/src/doc/unstable-book/src/library-features/asm.md @@ -427,6 +427,8 @@ Several types of operands are supported: - The substituted string does not include any modifiers (e.g. GOT, PLT, relocations, etc). - `` is allowed to point to a `#[thread_local]` static, in which case the asm code can combine the symbol with relocations (e.g. `@plt`, `@TPOFF`) to read from thread-local data. +Operand expressions are evaluated from left to right, just like function call arguments. After the `asm!` has executed, outputs are written to in left to right order. This is significant if two outputs point to the same place: that place will contain the value of the rightmost output. + ## Register operands Input and output operands can be specified either as an explicit register or as a register class from which the register allocator can select a register. Explicit registers are specified as string literals (e.g. `"eax"`) while register classes are specified as identifiers (e.g. `reg`). Using string literals for register names enables support for architectures that use special characters in register names, such as MIPS (`$0`, `$1`, etc). @@ -438,7 +440,7 @@ Only the following types are allowed as operands for inline assembly: - Floating-point numbers - Pointers (thin only) - Function pointers -- SIMD vectors (structs defined with `#[repr(simd)]` and which implement `Copy`) +- SIMD vectors (structs defined with `#[repr(simd)]` and which implement `Copy`). This includes architecture-specific vector types defined in `std::arch` such as `__m128` (x86) or `int8x16_t` (ARM). Here is the list of currently supported register classes: @@ -667,7 +669,6 @@ The compiler performs some additional checks on options: - These flags registers must be restored upon exiting the asm block if the `preserves_flags` option is set: - x86 - Status flags in `EFLAGS` (CF, PF, AF, ZF, SF, OF). - - Direction flag in `EFLAGS` (DF). - Floating-point status word (all). - Floating-point exception flags in `MXCSR` (PE, UE, OE, ZE, DE, IE). - ARM @@ -682,11 +683,17 @@ The compiler performs some additional checks on options: - Floating-point status (`FPSR` register). - RISC-V - Floating-point exception flags in `fcsr` (`fflags`). +- On x86, the direction flag (DF in `EFLAGS`) is clear on entry to an asm block and must be clear on exit. + - Behavior is undefined if the direction flag is set on exiting an asm block. - The requirement of restoring the stack pointer and non-output registers to their original value only applies when exiting an `asm!` block. - This means that `asm!` blocks that never return (even if not marked `noreturn`) don't need to preserve these registers. - When returning to a different `asm!` block than you entered (e.g. for context switching), these registers must contain the value they had upon entering the `asm!` block that you are *exiting*. - You cannot exit an `asm!` block that has not been entered. Neither can you exit an `asm!` block that has already been exited. - You are responsible for switching any target-specific state (e.g. thread-local storage, stack bounds). - The set of memory locations that you may access is the intersection of those allowed by the `asm!` blocks you entered and exited. +- You cannot assume that an `asm!` block will appear exactly once in the output binary. The compiler is allowed to instantiate multiple copies of the `asm!` block, for example when the function containing it is inlined in multiple places. + - As a consequence, you should only use [local labels] inside inline assembly code. Defining symbols in assembly code may lead to assembler and/or linker errors due to duplicate symbol definitions. -> **Note**: As a general rule, these are the flags which are *not* preserved when performing a function call. +> **Note**: As a general rule, the flags covered by `preserves_flags` are those which are *not* preserved when performing a function call. + +[local labels]: https://sourceware.org/binutils/docs/as/Symbol-Names.html#Local-Labels From 1cfdc7ed0c97a082ca7e638d4114590d03a059a9 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Tue, 19 May 2020 18:25:41 +0100 Subject: [PATCH 36/36] Update dlmalloc dependency to 0.1.4 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d898661dcd..6295b40e976 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1012,9 +1012,9 @@ dependencies = [ [[package]] name = "dlmalloc" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f283302e035e61c23f2b86b3093e8c6273a4c3125742d6087e96ade001ca5e63" +checksum = "35055b1021724f4eb5262eb49130eebff23fc59fc5a14160e05faad8eeb36673" dependencies = [ "compiler_builtins", "libc",