HIR passes for asm!

This commit is contained in:
Amanieu d'Antras 2020-02-13 11:00:55 +00:00
parent ec1ad61f88
commit 10510b5820
9 changed files with 469 additions and 13 deletions

View file

@ -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),

View file

@ -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),

View file

@ -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<InlineAsmType>)>,
) -> Option<InlineAsmType> {
// 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);

View file

@ -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);

View file

@ -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,
]
}

View file

@ -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`");
}

View file

@ -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> {

View file

@ -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 {

View file

@ -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)),