Skip to content

Commit

Permalink
Rollup merge of rust-lang#82838 - Amanieu:rustdoc_asm, r=nagisa
Browse files Browse the repository at this point in the history
Allow rustdoc to handle asm! of foreign architectures

This allows rustdoc to process code containing `asm!` for architectures other than the current one. Since this never reaches codegen, we just replace target-specific registers and register classes with a dummy one.

Fixes rust-lang#82869
  • Loading branch information
Dylan-DPC authored Mar 16, 2021
2 parents 142028e + ba00ddc commit d0222f1
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 45 deletions.
89 changes: 44 additions & 45 deletions compiler/rustc_ast_lowering/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1331,84 +1331,83 @@ impl<'hir> LoweringContext<'_, 'hir> {
}

fn lower_expr_asm(&mut self, sp: Span, asm: &InlineAsm) -> hir::ExprKind<'hir> {
if self.sess.asm_arch.is_none() {
// Rustdoc needs to support asm! from foriegn architectures: don't try
// lowering the register contraints in this case.
let asm_arch = if self.sess.opts.actually_rustdoc { None } else { self.sess.asm_arch };
if asm_arch.is_none() && !self.sess.opts.actually_rustdoc {
struct_span_err!(self.sess, sp, E0472, "asm! is unsupported on this target").emit();
}
if asm.options.contains(InlineAsmOptions::ATT_SYNTAX)
&& !matches!(
self.sess.asm_arch,
Some(asm::InlineAsmArch::X86 | asm::InlineAsmArch::X86_64)
)
&& !matches!(asm_arch, Some(asm::InlineAsmArch::X86 | asm::InlineAsmArch::X86_64))
&& !self.sess.opts.actually_rustdoc
{
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.
// Lower operands to HIR. We use dummy register classes if an error
// occurs during lowering because we still need to be able to produce a
// valid HIR.
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(
.map(|(op, op_sp)| {
let lower_reg = |reg| match reg {
InlineAsmRegOrRegClass::Reg(s) => {
asm::InlineAsmRegOrRegClass::Reg(if let Some(asm_arch) = asm_arch {
asm::InlineAsmReg::parse(
sess.asm_arch?,
asm_arch,
|feature| sess.target_features.contains(&Symbol::intern(feature)),
&sess.target,
s,
)
.map_err(|e| {
.unwrap_or_else(|e| {
let msg = format!("invalid register `{}`: {}", s.as_str(), e);
sess.struct_span_err(*op_sp, &msg).emit();
asm::InlineAsmReg::Err
})
.ok()?,
),
InlineAsmRegOrRegClass::RegClass(s) => {
asm::InlineAsmRegOrRegClass::RegClass(
asm::InlineAsmRegClass::parse(sess.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()?,
)
}
})
} else {
asm::InlineAsmReg::Err
})
}
InlineAsmRegOrRegClass::RegClass(s) => {
asm::InlineAsmRegOrRegClass::RegClass(if let Some(asm_arch) = asm_arch {
asm::InlineAsmRegClass::parse(asm_arch, s).unwrap_or_else(|e| {
let msg = format!("invalid register class `{}`: {}", s.as_str(), e);
sess.struct_span_err(*op_sp, &msg).emit();
asm::InlineAsmRegClass::Err
})
} else {
asm::InlineAsmRegClass::Err
})
}
};

// lower_reg is executed last because we need to lower all
// sub-expressions even if we throw them away later.
let op = match *op {
InlineAsmOperand::In { reg, ref expr } => hir::InlineAsmOperand::In {
reg: lower_reg(reg),
expr: self.lower_expr_mut(expr),
reg: lower_reg(reg)?,
},
InlineAsmOperand::Out { reg, late, ref expr } => hir::InlineAsmOperand::Out {
reg: lower_reg(reg),
late,
expr: expr.as_ref().map(|expr| self.lower_expr_mut(expr)),
reg: lower_reg(reg)?,
},
InlineAsmOperand::InOut { reg, late, ref expr } => {
hir::InlineAsmOperand::InOut {
reg: lower_reg(reg),
late,
expr: self.lower_expr_mut(expr),
reg: lower_reg(reg)?,
}
}
InlineAsmOperand::SplitInOut { reg, late, ref in_expr, ref out_expr } => {
hir::InlineAsmOperand::SplitInOut {
reg: lower_reg(reg),
late,
in_expr: self.lower_expr_mut(in_expr),
out_expr: out_expr.as_ref().map(|expr| self.lower_expr_mut(expr)),
reg: lower_reg(reg)?,
}
}
InlineAsmOperand::Const { ref expr } => {
Expand All @@ -1418,17 +1417,11 @@ impl<'hir> LoweringContext<'_, 'hir> {
hir::InlineAsmOperand::Sym { expr: self.lower_expr_mut(expr) }
}
};
Some((op, *op_sp))
(op, *op_sp)
})
.collect();

// Stop if there were any errors when lowering the register classes
if operands.len() != asm.operands.len() || sess.asm_arch.is_none() {
return hir::ExprKind::Err;
}

// Validate template modifiers against the register classes for the operands
let asm_arch = sess.asm_arch.unwrap();
for p in &asm.template {
if let InlineAsmTemplatePiece::Placeholder {
operand_idx,
Expand All @@ -1443,7 +1436,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
| hir::InlineAsmOperand::InOut { reg, .. }
| hir::InlineAsmOperand::SplitInOut { reg, .. } => {
let class = reg.reg_class();
let valid_modifiers = class.valid_modifiers(asm_arch);
if class == asm::InlineAsmRegClass::Err {
continue;
}
let valid_modifiers = class.valid_modifiers(asm_arch.unwrap());
if !valid_modifiers.contains(&modifier) {
let mut err = sess.struct_span_err(
placeholder_span,
Expand Down Expand Up @@ -1506,7 +1502,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
// features. We check that at least one type is available for
// the current target.
let reg_class = reg.reg_class();
for &(_, feature) in reg_class.supported_types(asm_arch) {
if reg_class == asm::InlineAsmRegClass::Err {
continue;
}
for &(_, feature) in reg_class.supported_types(asm_arch.unwrap()) {
if let Some(feature) = feature {
if self.sess.target_features.contains(&Symbol::intern(feature)) {
required_features.clear();
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_codegen_llvm/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ fn reg_to_llvm(reg: InlineAsmRegOrRegClass, layout: Option<&TyAndLayout<'tcx>>)
InlineAsmRegClass::SpirV(SpirVInlineAsmRegClass::reg) => {
bug!("LLVM backend does not support SPIR-V")
}
InlineAsmRegClass::Err => unreachable!(),
}
.to_string(),
}
Expand Down Expand Up @@ -594,6 +595,7 @@ fn modifier_to_llvm(
InlineAsmRegClass::SpirV(SpirVInlineAsmRegClass::reg) => {
bug!("LLVM backend does not support SPIR-V")
}
InlineAsmRegClass::Err => unreachable!(),
}
}

Expand Down Expand Up @@ -637,6 +639,7 @@ fn dummy_output_type(cx: &CodegenCx<'ll, 'tcx>, reg: InlineAsmRegClass) -> &'ll
InlineAsmRegClass::SpirV(SpirVInlineAsmRegClass::reg) => {
bug!("LLVM backend does not support SPIR-V")
}
InlineAsmRegClass::Err => unreachable!(),
}
}

Expand Down
14 changes: 14 additions & 0 deletions compiler/rustc_target/src/asm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ pub enum InlineAsmReg {
Mips(MipsInlineAsmReg),
SpirV(SpirVInlineAsmReg),
Wasm(WasmInlineAsmReg),
// Placeholder for invalid register constraints for the current target
Err,
}

impl InlineAsmReg {
Expand All @@ -240,6 +242,7 @@ impl InlineAsmReg {
Self::RiscV(r) => r.name(),
Self::Hexagon(r) => r.name(),
Self::Mips(r) => r.name(),
Self::Err => "<reg>",
}
}

Expand All @@ -251,6 +254,7 @@ impl InlineAsmReg {
Self::RiscV(r) => InlineAsmRegClass::RiscV(r.reg_class()),
Self::Hexagon(r) => InlineAsmRegClass::Hexagon(r.reg_class()),
Self::Mips(r) => InlineAsmRegClass::Mips(r.reg_class()),
Self::Err => InlineAsmRegClass::Err,
}
}

Expand Down Expand Up @@ -309,6 +313,7 @@ impl InlineAsmReg {
Self::RiscV(r) => r.emit(out, arch, modifier),
Self::Hexagon(r) => r.emit(out, arch, modifier),
Self::Mips(r) => r.emit(out, arch, modifier),
Self::Err => unreachable!("Use of InlineAsmReg::Err"),
}
}

Expand All @@ -320,6 +325,7 @@ impl InlineAsmReg {
Self::RiscV(_) => cb(self),
Self::Hexagon(r) => r.overlapping_regs(|r| cb(Self::Hexagon(r))),
Self::Mips(_) => cb(self),
Self::Err => unreachable!("Use of InlineAsmReg::Err"),
}
}
}
Expand All @@ -346,6 +352,8 @@ pub enum InlineAsmRegClass {
Mips(MipsInlineAsmRegClass),
SpirV(SpirVInlineAsmRegClass),
Wasm(WasmInlineAsmRegClass),
// Placeholder for invalid register constraints for the current target
Err,
}

impl InlineAsmRegClass {
Expand All @@ -360,6 +368,7 @@ impl InlineAsmRegClass {
Self::Mips(r) => r.name(),
Self::SpirV(r) => r.name(),
Self::Wasm(r) => r.name(),
Self::Err => rustc_span::symbol::sym::reg,
}
}

Expand All @@ -377,6 +386,7 @@ impl InlineAsmRegClass {
Self::Mips(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::Mips),
Self::SpirV(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::SpirV),
Self::Wasm(r) => r.suggest_class(arch, ty).map(InlineAsmRegClass::Wasm),
Self::Err => unreachable!("Use of InlineAsmRegClass::Err"),
}
}

Expand All @@ -401,6 +411,7 @@ impl InlineAsmRegClass {
Self::Mips(r) => r.suggest_modifier(arch, ty),
Self::SpirV(r) => r.suggest_modifier(arch, ty),
Self::Wasm(r) => r.suggest_modifier(arch, ty),
Self::Err => unreachable!("Use of InlineAsmRegClass::Err"),
}
}

Expand All @@ -421,6 +432,7 @@ impl InlineAsmRegClass {
Self::Mips(r) => r.default_modifier(arch),
Self::SpirV(r) => r.default_modifier(arch),
Self::Wasm(r) => r.default_modifier(arch),
Self::Err => unreachable!("Use of InlineAsmRegClass::Err"),
}
}

Expand All @@ -440,6 +452,7 @@ impl InlineAsmRegClass {
Self::Mips(r) => r.supported_types(arch),
Self::SpirV(r) => r.supported_types(arch),
Self::Wasm(r) => r.supported_types(arch),
Self::Err => unreachable!("Use of InlineAsmRegClass::Err"),
}
}

Expand Down Expand Up @@ -476,6 +489,7 @@ impl InlineAsmRegClass {
Self::Mips(r) => r.valid_modifiers(arch),
Self::SpirV(r) => r.valid_modifiers(arch),
Self::Wasm(r) => r.valid_modifiers(arch),
Self::Err => unreachable!("Use of InlineAsmRegClass::Err"),
}
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/test/rustdoc/asm-foreign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Make sure rustdoc accepts asm! for a foreign architecture.

#![feature(asm)]

// @has asm_foreign/fn.aarch64.html
pub unsafe fn aarch64(a: f64, b: f64) -> f64 {
let c;
asm!("add {:d}, {:d}, d0", out(vreg) c, in(vreg) a, in("d0") {
|| {};
b
});
c
}

// @has asm_foreign/fn.x86.html
pub unsafe fn x86(a: f64, b: f64) -> f64 {
let c;
asm!("addsd {}, {}, xmm0", out(xmm_reg) c, in(xmm_reg) a, in("xmm0") b);
c
}
11 changes: 11 additions & 0 deletions src/test/rustdoc/asm-foreign2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// only-aarch64
// Make sure rustdoc accepts options(att_syntax) asm! on non-x86 targets.

#![feature(asm)]

// @has asm_foreign2/fn.x86.html
pub unsafe fn x86(x: i64) -> i64 {
let y;
asm!("movq {}, {}", in(reg) x, out(reg) y, options(att_syntax));
y
}
23 changes: 23 additions & 0 deletions src/test/ui/issues/issue-82869.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// only-x86_64
// Make sure rustc doesn't ICE on asm! for a foreign architecture.

#![feature(asm)]
#![crate_type = "rlib"]

pub unsafe fn aarch64(a: f64, b: f64) -> f64 {
let c;
asm!("add {:d}, {:d}, d0", out(vreg) c, in(vreg) a, in("d0") {
|| {};
b
});
//~^^^^ invalid register class
//~^^^^^ invalid register class
//~^^^^^^ invalid register
c
}

pub unsafe fn x86(a: f64, b: f64) -> f64 {
let c;
asm!("addsd {}, {}, xmm0", out(xmm_reg) c, in(xmm_reg) a, in("xmm0") b);
c
}
24 changes: 24 additions & 0 deletions src/test/ui/issues/issue-82869.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
error: invalid register class `vreg`: unknown register class
--> $DIR/issue-82869.rs:9:32
|
LL | asm!("add {:d}, {:d}, d0", out(vreg) c, in(vreg) a, in("d0") {
| ^^^^^^^^^^^

error: invalid register class `vreg`: unknown register class
--> $DIR/issue-82869.rs:9:45
|
LL | asm!("add {:d}, {:d}, d0", out(vreg) c, in(vreg) a, in("d0") {
| ^^^^^^^^^^

error: invalid register `d0`: unknown register
--> $DIR/issue-82869.rs:9:57
|
LL | asm!("add {:d}, {:d}, d0", out(vreg) c, in(vreg) a, in("d0") {
| _________________________________________________________^
LL | | || {};
LL | | b
LL | | });
| |_____^

error: aborting due to 3 previous errors

0 comments on commit d0222f1

Please sign in to comment.