diff --git a/src/expr.rs b/src/expr.rs index e1865c8afc2..534070e80e0 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1859,11 +1859,9 @@ pub(crate) enum RhsAssignKind<'ast> { impl<'ast> RhsAssignKind<'ast> { // TODO(calebcartwright) - // Preemptive addition for handling RHS with chains, not yet utilized. // It may make more sense to construct the chain first and then check // whether there are actually chain elements. - #[allow(dead_code)] - fn is_chain(&self) -> bool { + pub(crate) fn is_chain(&self) -> bool { match self { RhsAssignKind::Expr(kind, _) => { matches!( diff --git a/src/macros.rs b/src/macros.rs index fdbe3374615..78d5bb9fa4a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -9,6 +9,8 @@ // List-like invocations with parentheses will be formatted as function calls, // and those with brackets will be formatted as array literals. +use std::borrow::Cow; +use std::cell::Cell; use std::collections::HashMap; use std::panic::{catch_unwind, AssertUnwindSafe}; @@ -28,15 +30,18 @@ use crate::config::lists::*; use crate::expr::{rewrite_array, rewrite_assign_rhs, RhsAssignKind}; use crate::lists::{itemize_list, write_list, ListFormatting}; use crate::overflow; +use crate::parse::macros::asm::{parse_asm, AsmArgs}; use crate::parse::macros::lazy_static::parse_lazy_static; -use crate::parse::macros::{parse_expr, parse_macro_args, ParsedMacroArgs}; +use crate::parse::macros::{ + parse_expr, parse_macro_args, token_stream_ends_with_comma, ParsedMacroArgs, +}; use crate::rewrite::{Rewrite, RewriteContext}; use crate::shape::{Indent, Shape}; use crate::source_map::SpanUtils; use crate::spanned::Spanned; use crate::utils::{ - format_visibility, indent_next_line, is_empty_line, mk_sp, remove_trailing_white_spaces, - rewrite_ident, trim_left_preserve_layout, wrap_str, NodeIdExt, + first_line_width, format_visibility, indent_next_line, is_empty_line, mk_sp, + remove_trailing_white_spaces, rewrite_ident, trim_left_preserve_layout, wrap_str, NodeIdExt, }; use crate::visitor::FmtVisitor; @@ -177,6 +182,635 @@ pub(crate) fn rewrite_macro( } } +/// True if the snippet would fit on the line taking into account indentation and overhead. +fn fit_on_current_line(snippet: &str, shape: Shape, overhead: usize) -> bool { + let one_line_length = snippet.len() + shape.indent.width() + overhead; + one_line_length <= shape.width +} + +fn rewrite_asm_templates( + asm_args: &AsmArgs<'_>, + context: &RewriteContext<'_>, + shape: Shape, + trailing_comma: bool, + opener: &str, + closer: &str, +) -> Option { + // the terminator and next_span_start are dependant on whether we have operands, + // clobber_abis, or options. + let (terminator, next_span_start) = if let Some((_, span)) = asm_args.operands().first() { + (context.snippet(*span), span.lo()) + } else if let Some((_, span)) = asm_args.clobber_abis().first() { + (context.snippet(*span), span.lo()) + } else if let Some(expr) = asm_args.options().first() { + (context.snippet(expr.span), expr.span.lo()) + } else { + (closer, asm_args.mac().span().hi()) + }; + + // Use a cell::Cell, so we can share and mutate this state in the get_item_string Fn closure + let should_indent_after_asm_label = Cell::new(false); + + let templalte_items = itemize_list( + context.snippet_provider, + asm_args.templates().iter(), + terminator, + ",", + |t| t.span.lo(), + |t| t.span.hi(), + |t| { + // It's a little easier to work with the template strings if we trim + // whitespace and the leading and trailing quotation marks. + let template = context + .snippet(t.span) + .trim_matches(|c: char| c == '\"' || c.is_whitespace()); + + let is_label = template.ends_with(":") | template.starts_with("."); + + if !is_label && should_indent_after_asm_label.get() { + Some(format!("\" {}\"", template)) + } else { + should_indent_after_asm_label.set(is_label); + Some(format!("\"{}\"", template)) + } + }, + context + .snippet_provider + .span_after(asm_args.mac().span(), opener), + next_span_start, + false, + ) + .collect::>(); + + let fmt = ListFormatting::new(shape, context.config) + .separator(",") + .trailing_separator(if trailing_comma { + SeparatorTactic::Always + } else { + SeparatorTactic::Never + }); + + write_list(&templalte_items, &fmt) +} + +fn get_asm_operand_named_argument<'a>( + span: Span, + context: &'a RewriteContext<'_>, +) -> Option<&'a str> { + let snippet = context.snippet(span); + let assignment = snippet.find("="); + let arrow = snippet.find("=>"); + + match (assignment, arrow) { + (Some(assignemnt), Some(arrow)) if assignemnt != arrow => { + let (argument, _) = snippet.split_once("=")?; + Some(argument.trim()) + } + (Some(_), None) => { + let (argument, _) = snippet.split_once("=")?; + Some(argument.trim()) + } + _ => None + } +} + +fn rewriten_operand_width( + named_argument: Option<&str>, + operand: &str, + in_expr: &str, + out_expr: Option<&str>, +) -> usize { + if let Some(named_argument) = named_argument { + let arg_len = named_argument.len(); + + if let Some(out_expr) = out_expr { + // +8 to account for spaces, operators, and commas: + // `argument = operand in_expr => out_expr,` + // if there is an out_expr we consider the entire length, becuase if we're too + // long we need to break before the => + arg_len + operand.len() + in_expr.len() + out_expr.len() + 8 + } else { + // +5 to account for spaces, operators, and comma: `argument = operand in_expr,` + arg_len + operand.len() + first_line_width(in_expr) + 5 + } + } else { + if let Some(out_expr) = out_expr { + // +6 to account for spaces and operator: `operand in_expr => out_expr,` + // if there is an out_expr we consider the entire length, becuase if we're too + // long we need to break before the => + operand.len() + in_expr.len() + out_expr.len() + 6 + } else { + // +2 to account for space and comma: `operand expr,` + operand.len() + first_line_width(in_expr) + 2 + } + } +} + +fn rewrite_asm_split_inout_operand( + mut result: String, + named_argument: Option<&str>, + operand_name: &str, + reg: &ast::InlineAsmRegOrRegClass, + in_expr: &ptr::P, + out_expr: Option<&ptr::P>, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let operand = rewrite_register(operand_name, reg); + +fn rewrite_asm_out_operand( + mut result: String, + reg: &str, + expr: &Option>, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + result.push_str(reg); + if let Some(expr) = expr.as_ref() { + let (indentation, expr) = indentation_for_inout_operand_expr(reg, expr, context, shape)?; + result.push_str(&indentation); + result.push_str(&expr); + } + Some(result) +} + +fn rewrite_asm_split_inout_operand( + mut result: String, + reg: &str, + in_expr: &ptr::P, + out_expr: &Option>, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let in_expr = in_expr.rewrite(context, shape)?; + result.push_str(reg); + result.push(' '); + result.push_str(&in_expr); + if let Some(out_expr) = out_expr.as_ref() { + let shape = shape.block_indent(context.config.tab_spaces()); + let out_expr = out_expr.rewrite(context, shape)?; + result.push_str(&(shape.indent).to_string_with_newline(context.config)); + result.push_str("=> "); + result.push_str(&out_expr); + } + Some(result) +} + +fn rewrite_asm_const_operand( + mut result: String, + expr: &ptr::P, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let expr = expr.rewrite(context, shape)?; + result.push_str("const "); + result.push_str(&expr); + Some(result) +} + + +/// Format simple asm operand which are followed by an expression, +/// and optionally preceded by an assignemnt expression. +fn rewrite_simple_asm_operand( + mut result: String, + named_argument: Option<&str>, + operand_name: &str, + reg: Option<&ast::InlineAsmRegOrRegClass>, + expr: Option<&ptr::P>, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let expression = if let Some(expr) = expr { + Cow::Owned(expr.rewrite(context, shape)?) + } else { + // expr should only ever be none if we're rewriting a out or lateout operand + Cow::Borrowed("_") + }; + + let operand = if let Some(reg) = reg { + Cow::Owned(rewrite_register(operand_name, reg)) + } else { + Cow::Borrowed(operand_name) + }; + + let content_width = rewriten_operand_width(named_argument, &operand, &expression, None); + let nested_shape = shape.block_indent(context.config.tab_spaces()); + + let is_const = operand_name == "const"; + let is_sym = operand_name == "sym"; + + if content_width + shape.indent.width() <= shape.width { + // Best case scenario everything fits on one line + result.push_str(&operand); + result.push_str(" "); + result.push_str(&expression); + return Some(result); + } + + if is_const || is_sym { + if named_argument.is_some() { + // There is a named argument so lets see if we fit on the next line. + result.push_str(&nested_shape.to_string_with_newline(context.config)); + } + // Although we don't fit on one line there isn't any recoomendation for breaking + // const and sym operand + result.push_str(&operand); + result.push_str(" "); + result.push_str(&expression); + return Some(result); + } + + if named_argument.is_none() { + // No named argument so we know we have to break after the operand. + result.push_str(&operand); + result.push_str(&nested_shape.to_string_with_newline(context.config)); + if expr.is_none() { + // this just pushes "_" + result.push_str(&expression); + } else { + result.push_str(&expr.unwrap().rewrite(context, nested_shape)?); + } + + } else { + // There is a named argument so lets see if we fit on the next line. + result.push_str(&nested_shape.to_string_with_newline(context.config)); + result.push_str(&operand); + let content_width = rewriten_operand_width(None, &operand, &expression, None); + if content_width + shape.indent.width() <= shape.width { + result.push_str(" "); + result.push_str(&expression); + } else { + // we didn't fit on the next line either, so we need to break after the operand + let deeply_nested_shape = nested_shape.block_indent(context.config.tab_spaces()); + result.push_str(&deeply_nested_shape.to_string_with_newline(context.config)); + + if expr.is_none() { + result.push_str(&expression); + } else { + result.push_str(&expr.unwrap().rewrite(context, deeply_nested_shape)?); + } + } + } + + Some(result) +} + +/// wrapper for ast::InlineAsmOperand, which we can more conveniently implement +/// rewrite::Rewrite for, since we have access to the entire span for the operand. +struct InlineAsmOperand<'a> { + operand: &'a ast::InlineAsmOperand, + span: Span, +} + +fn rewrite_register(operand: &str, register: &ast::InlineAsmRegOrRegClass) -> String { + let mut result = String::from(operand); + result.push('('); + use ast::InlineAsmRegOrRegClass::*; + match register { + Reg(symbol) => { + result.push('\"'); + result.push_str(symbol.as_str()); + result.push('\"'); + } + RegClass(symbol) => result.push_str(symbol.as_str()), + } + result.push(')'); + result +} + +impl<'a> Rewrite for InlineAsmOperand<'a> { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let mut result = String::new(); + let named_argument = get_asm_operand_named_argument(self.span, context); + + if let Some(argument) = named_argument { + result.push_str(argument); + result.push_str(" ="); + } + + use ast::InlineAsmOperand::*; + match self.operand { + In { reg, expr } => { + rewrite_simple_asm_operand( + result, + named_argument, + "in", + Some(reg), + Some(expr), + context, + shape, + ) + } + Out { reg, late, expr } => { + let operand_name = if *late { "lateout" } else { "out" }; + rewrite_simple_asm_operand( + result, + named_argument, + operand_name, + Some(reg), + expr.as_ref(), + context, + shape, + ) + } + InOut { reg, expr, late } => { + let operand_name = if *late { "inlateout" } else { "inout" }; + rewrite_simple_asm_operand( + result, + named_argument, + operand_name, + Some(reg), + Some(expr), + context, + shape, + ) + } + SplitInOut { + reg, late, in_expr, out_expr, + } => { + + let operand_name = if *late { "inlateout" } else { "inout" }; + rewrite_asm_split_inout_operand( + result, + named_argument, + operand_name, + reg, + in_expr, + out_expr.as_ref(), + context, + shape, + ) + } + Const { anon_const } => { + rewrite_simple_asm_operand( + result, + named_argument, + "const", + None, + Some(&anon_const.value), + context, + shape, + ) + } + Sym { expr } => { + rewrite_simple_asm_operand( + result, + named_argument, + "sym", + None, + Some(expr), + context, + shape, + ) + } + } + } +} + +fn rewrite_asm_operands( + asm_args: &AsmArgs<'_>, + context: &RewriteContext<'_>, + shape: Shape, + trailing_comma: bool, + closer: &str, +) -> Option { + // There should always be at least one proceeding asm template + let prev_span_end = asm_args.templates().last()?.span.hi(); + + let (terminator, next_span_start) = if let Some((_, span)) = asm_args.clobber_abis().first() { + ("clobber_abi", span.lo()) + } else if let Some(expr) = asm_args.options().first() { + ("options", expr.span.lo()) + } else { + (closer, asm_args.mac().span().hi()) + }; + + let iter = asm_args + .operands() + .iter() + .map(|(operand, span)| InlineAsmOperand { + operand, + span: span.clone(), + }); + + let operands = itemize_list( + context.snippet_provider, + iter, + terminator, + ",", + |operand| operand.span.lo(), + |operand| operand.span.hi(), + |operand| operand.rewrite(context, shape), + prev_span_end, + next_span_start, + false, + ) + .collect::>(); + + let fmt = ListFormatting::new(shape, context.config) + .separator(",") + .tactic(DefinitiveListTactic::Vertical) + .trailing_separator(if trailing_comma { + SeparatorTactic::Always + } else { + SeparatorTactic::Never + }); + + write_list(&operands, &fmt) +} + +fn rewrite_asm_clobber_abis( + asm_args: &AsmArgs<'_>, + context: &RewriteContext<'_>, + shape: Shape, + trailing_comma: bool, + closer: &str, +) -> Option { + // previous span is either the operands or the templates. + let prev_span_end = if let Some((_, span)) = asm_args.operands().last() { + span.hi() + } else { + // there should alwys be at least one template. + asm_args.templates().last()?.span.hi() + }; + + // terminator and next_span_start are dependant on whether there are asm options or not. + let (terminator, next_span_start) = if let Some(expr) = asm_args.options().first() { + ("options", expr.span.lo()) + } else { + (closer, asm_args.mac().span().hi()) + }; + + let clobber_abis = itemize_list( + context.snippet_provider, + asm_args.clobber_abis().iter(), + terminator, + ",", + |(_, span)| span.lo(), + |(_, span)| span.hi(), + |(_, span)| { + // There isn't any guidance on how to break clobber_abis. + Some(context.snippet(*span).trim().to_owned()) + }, + prev_span_end, + next_span_start, + false, + ) + .collect::>(); + + let fmt = ListFormatting::new(shape, context.config) + .separator(",") + .tactic(DefinitiveListTactic::Vertical) + .trailing_separator(if trailing_comma { + SeparatorTactic::Always + } else { + SeparatorTactic::Never + }); + + write_list(&clobber_abis, &fmt) +} + +fn rewrite_asm_options( + asm_args: &AsmArgs<'_>, + context: &RewriteContext<'_>, + shape: Shape, + trailing_comma: bool, + closer: &str, +) -> Option { + // previous span is either the clober_abis, operands, or the templates. + let prev_span_end = if let Some((_, span)) = asm_args.clobber_abis().last() { + span.hi() + } else if let Some((_, span)) = asm_args.operands().last() { + span.hi() + } else { + // there should alwys be at least one template. + asm_args.templates().last()?.span.hi() + }; + + let items = itemize_list( + context.snippet_provider, + asm_args.options().iter(), + closer, + ",", + |expr| expr.span.lo(), + |expr| expr.span.hi(), + |expr| expr.rewrite(context, shape), + prev_span_end, + asm_args.mac().span().hi(), + false, + ) + .collect::>(); + + let fmt = ListFormatting::new(shape, context.config) + .separator(",") + .tactic(DefinitiveListTactic::Vertical) + .trailing_separator(if trailing_comma { + SeparatorTactic::Always + } else { + SeparatorTactic::Never + }); + + write_list(&items, &fmt) +} + +/// There is a special style guide for rewriting the ``asm!()`` macro. +/// See +fn rewrite_asm_macro( + mac: &ast::MacCall, + extra_ident: Option, + context: &RewriteContext<'_>, + shape: Shape, + style: DelimToken, + position: MacroPosition, + trailing_comma: bool, +) -> Option { + let asm_args = parse_asm(context, mac)?; + + let mut result = String::with_capacity(1024); + let macro_name = rewrite_macro_name(context, &mac.path, extra_ident); + result.push_str(¯o_name); + + let (opener, closer) = match style { + DelimToken::Paren => { + result.push_str("("); + ("(", ")") + } + DelimToken::Brace => { + result.push_str(" {"); + ("{", "}") + } + DelimToken::Bracket => { + result.push_str("["); + ("[", "]") + } + _ => unreachable!(), + }; + + let nested_shape = shape + .block_indent(context.config.tab_spaces()) + .with_max_width(context.config); + + let snippet = context.snippet(mac.span()); + let macro_is_too_long = !fit_on_current_line(snippet, shape, 0); + let mutiple_templates = asm_args.templates().len() > 1; + let has_operands = !asm_args.operands().is_empty(); + let has_clobber_abis = !asm_args.clobber_abis().is_empty(); + let has_options = !asm_args.options().is_empty(); + let must_add_newline = + macro_is_too_long || mutiple_templates || has_operands || has_clobber_abis || has_options; + + let templates = rewrite_asm_templates( + &asm_args, + context, + nested_shape, + trailing_comma || has_operands || has_clobber_abis || has_options, + opener, + closer, + )?; + + if must_add_newline { + result.push_str(&nested_shape.indent.to_string_with_newline(context.config)); + result.push_str(&templates); + } else { + result.push_str(&templates); + } + + if has_operands { + result.push_str(&nested_shape.indent.to_string_with_newline(context.config)); + let trailing_comma = trailing_comma || has_clobber_abis || has_options; + let operands = + rewrite_asm_operands(&asm_args, context, nested_shape, trailing_comma, closer)?; + result.push_str(&operands); + } + + if has_clobber_abis { + result.push_str(&nested_shape.indent.to_string_with_newline(context.config)); + let trailing_comma = trailing_comma || has_options; + let clobber_abis = + rewrite_asm_clobber_abis(&asm_args, context, nested_shape, trailing_comma, closer)?; + result.push_str(&clobber_abis); + } + + if has_options { + result.push_str(&nested_shape.indent.to_string_with_newline(context.config)); + let options = + rewrite_asm_options(&asm_args, context, nested_shape, trailing_comma, closer)?; + result.push_str(&options); + } + + if must_add_newline { + result.push_str(&shape.indent.to_string_with_newline(context.config)); + } + + result.push_str(closer); + + if matches!(position, MacroPosition::Item) && mac.args.need_semicolon() { + result.push(';') + } + + Some(result) +} + fn rewrite_macro_inner( mac: &ast::MacCall, extra_ident: Option, @@ -219,6 +853,21 @@ fn rewrite_macro_inner( _ => unreachable!(), }; } + + // Format well-known asm! macro + if macro_name.ends_with("asm!") { + let trailing_comma = token_stream_ends_with_comma(mac.args.inner_tokens()); + return rewrite_asm_macro( + mac, + extra_ident, + context, + shape, + style, + position, + trailing_comma, + ); + } + // Format well-known macros which cannot be parsed as a valid AST. if macro_name == "lazy_static!" && !has_comment { if let success @ Some(..) = format_lazy_static(context, shape, ts.trees().collect()) { diff --git a/src/parse/macros/asm.rs b/src/parse/macros/asm.rs index cc9fb5072ce..f1607699de4 100644 --- a/src/parse/macros/asm.rs +++ b/src/parse/macros/asm.rs @@ -1,11 +1,76 @@ -use rustc_ast::ast; -use rustc_builtin_macros::asm::{parse_asm_args, AsmArgs}; +use rustc_ast::token::TokenKind; +use rustc_ast::{ast, ptr}; +use rustc_builtin_macros::asm::{parse_asm_args, AsmArgs as RawAsmArgs}; +use rustc_span::{Span, Symbol}; use crate::rewrite::RewriteContext; -#[allow(dead_code)] -pub(crate) fn parse_asm(context: &RewriteContext<'_>, mac: &ast::MacCall) -> Option { +pub(crate) struct AsmArgs<'a> { + mac: &'a ast::MacCall, + asm_args: RawAsmArgs, + options: Vec>, +} + +pub(crate) fn parse_asm<'a>( + context: &RewriteContext<'_>, + mac: &'a ast::MacCall, +) -> Option> { let ts = mac.args.inner_tokens(); let mut parser = super::build_parser(context, ts); - parse_asm_args(&mut parser, context.parse_sess.inner(), mac.span(), false).ok() + let asm_args = + parse_asm_args(&mut parser, context.parse_sess.inner(), mac.span(), false).ok()?; + let options = parse_asm_options(context, mac, &asm_args)?; + Some(AsmArgs { + mac, + asm_args, + options, + }) +} + +/// Parse asm options as ast::Expr. +fn parse_asm_options( + context: &RewriteContext<'_>, + mac: &ast::MacCall, + asm_args: &RawAsmArgs, +) -> Option>> { + if asm_args.options_spans.is_empty() { + return Some(Vec::new()); + } + + let tokens = mac.args.inner_tokens(); + let mut parser = super::build_parser(context, tokens); + let mut output = Vec::with_capacity(asm_args.options_spans.len()); + + let symbol = Symbol::intern("options"); + while parser.token.kind != TokenKind::Eof { + if parser.token.is_ident_named(symbol) { + // We should almost certainly be able to pasrse the asm options as a function call + output.push(parser.parse_expr().ok()?) + } else { + parser.bump(); + } + } + Some(output) +} + +impl<'a> AsmArgs<'a> { + pub(crate) fn mac(&self) -> &'a ast::MacCall { + self.mac + } + + pub(crate) fn templates(&self) -> &Vec> { + &self.asm_args.templates + } + + pub(crate) fn operands(&self) -> &Vec<(ast::InlineAsmOperand, Span)> { + &self.asm_args.operands + } + + pub(crate) fn clobber_abis(&self) -> &Vec<(Symbol, Span)> { + &self.asm_args.clobber_abis + } + + pub(crate) fn options(&self) -> &Vec> { + &self.options + } } diff --git a/src/parse/macros/mod.rs b/src/parse/macros/mod.rs index 2e9ce1d35f4..e4cd7ca4365 100644 --- a/src/parse/macros/mod.rs +++ b/src/parse/macros/mod.rs @@ -1,5 +1,5 @@ use rustc_ast::token::{DelimToken, TokenKind}; -use rustc_ast::tokenstream::TokenStream; +use rustc_ast::tokenstream::{TokenStream, TokenTree}; use rustc_ast::{ast, ptr}; use rustc_parse::parser::{ForceCollect, Parser}; use rustc_parse::{stream_to_parser, MACRO_ARGUMENTS}; @@ -94,6 +94,15 @@ fn check_keyword<'a, 'b: 'a>(parser: &'a mut Parser<'b>) -> Option { None } +/// Check if the token stream ends with a trailing comma +pub(crate) fn token_stream_ends_with_comma(tokens: TokenStream) -> bool { + if let Some(TokenTree::Token(token)) = tokens.into_trees().last() { + matches!(token.kind, TokenKind::Comma) + } else { + false + } +} + pub(crate) fn parse_macro_args( context: &RewriteContext<'_>, tokens: TokenStream, diff --git a/tests/source/asm/empty_asm_call.rs b/tests/source/asm/empty_asm_call.rs new file mode 100644 index 00000000000..4c05d8909fb --- /dev/null +++ b/tests/source/asm/empty_asm_call.rs @@ -0,0 +1,5 @@ +asm!(); + +asm![]; + +asm!{} \ No newline at end of file diff --git a/tests/source/asm/fully_qualified_asm_call.rs b/tests/source/asm/fully_qualified_asm_call.rs new file mode 100644 index 00000000000..5273c47e036 --- /dev/null +++ b/tests/source/asm/fully_qualified_asm_call.rs @@ -0,0 +1,11 @@ +core::arch::asm!( + "nop" +); + +core::arch::asm![ + "nop" +]; + +core::arch::asm!{ + "nop" +} diff --git a/tests/source/asm/multiple_templates.rs b/tests/source/asm/multiple_templates.rs new file mode 100644 index 00000000000..92aa0b8fe4a --- /dev/null +++ b/tests/source/asm/multiple_templates.rs @@ -0,0 +1,11 @@ +asm!("instruction1", "instruction2", "instruction3",); + +asm!("instruction1", "instruction2", "instruction3"); + +asm!["instruction1", "instruction2", "instruction3",]; + +asm!["instruction1", "instruction2", "instruction3"]; + +asm! {"instruction1", "instruction2", "instruction3",} + +asm! {"instruction1", "instruction2", "instruction3"} diff --git a/tests/source/asm/nested_asm_macro.rs b/tests/source/asm/nested_asm_macro.rs new file mode 100644 index 00000000000..bfdcd4db6c3 --- /dev/null +++ b/tests/source/asm/nested_asm_macro.rs @@ -0,0 +1,59 @@ +#![feature(asm)] +extern "C" fn foo(arg: i32) -> i32 { + println!("arg = {}", arg); + arg * 2 +} + +fn call_with_parens(arg: i32) -> i32 { + unsafe { + let result; + asm!( + "call {}", + sym foo, + // 1st argument in rdi + in("rdi") arg, + // Return value in rax + out("rax") result, + // Mark all registers which are not preserved by the "C" calling + // convention as clobbered. + clobber_abi("C"), + ); + result + } +} + +fn call_with_brackets(arg: i32) -> i32 { + unsafe { + let result; + asm![ + "call {}", + sym foo, + // 1st argument in rdi + in("rdi") arg, + // Return value in rax + out("rax") result, + // Mark all registers which are not preserved by the "C" calling + // convention as clobbered. + clobber_abi("C"), + ]; + result + } +} + +fn call_with_braces(arg: i32) -> i32 { + unsafe { + let result; + asm! { + "call {}", + sym foo, + // 1st argument in rdi + in("rdi") arg, + // Return value in rax + out("rax") result, + // Mark all registers which are not preserved by the "C" calling + // convention as clobbered. + clobber_abi("C"), + } + result + } +} diff --git a/tests/source/asm/rust_unstable_book_examples.rs b/tests/source/asm/rust_unstable_book_examples.rs new file mode 100644 index 00000000000..55c21ee5476 --- /dev/null +++ b/tests/source/asm/rust_unstable_book_examples.rs @@ -0,0 +1,141 @@ +// collection of examples from The Rust Unstable Book found at +// https://doc.rust-lang.org/unstable-book/library-features/asm.html + +// # Basic usage +// Let us start with the simplest possible example +asm!("nop"); + +// # Inputs and outputs +// This will write the value 5 into the u64 variable x +asm!("mov {}, 5", out(reg) x); + +// This will add 5 to the input in variable i and write the result to variable o +asm!("mov {0}, {1}", "add {0}, {number}", out(reg) o, in(reg) i, number = const 5,); + +// We can further refine the above example to avoid the mov instruction +// We can see that inout is used to specify an argument that is both input and output +asm!("add {0}, {number}", inout(reg) x, number = const 5); + +// It is also possible to specify different variables for the input and output parts of an +// inout operand +asm!("add {0}, {number}", inout(reg) x => y, number = const 5); + +// # Late output operands +// To guarantee optimal performance it is important to use as few registers as possible. +// 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. +asm!("add {0}, {1}", inlateout(reg) a, in(reg) b); + +// Here is an example where inlateout cannot be used: +asm!("add {0}, {1}", "add {0}, {2}", inout(reg) a, in(reg) b, in(reg) c); + +// # Explicit register operands +// Some instructions require that the operands be in a specific register +// Therefore, Rust inline assembly provides some more specific constraint specifiers. +asm!("out 0x64, eax", in("eax") cmd); + +// Consider this example which uses the x86 mul instruction: +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 +); + +// # Clobbered registers +// In many cases inline assembly will modify state that is not needed as an output. +// 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. +// In the example below 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 _ +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") _, +); + +// This can also be used with a general register class (e.g. reg) to obtain a scratch register +// for use inside the asm code: +asm!( +"mov {tmp}, {x}", +"shl {tmp}, 1", +"shl {x}, 2", +"add {x}, {tmp}", +x = inout(reg) x, +tmp = out(reg) _, +); + +// # Symbol operands and ABI clobbers +// A special operand type, sym, allows you to use the symbol name of a fn or static in inline +// assembly code. 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 +extern "C" fn foo(arg: i32) -> i32 { + println!("arg = {}", arg); + arg * 2 +} +asm!( +"call {}", +sym foo, +// 1st argument in rdi +in("rdi") arg, +// Return value in rax +out("rax") result, +// Mark all registers which are not preserved by the "C" calling +// convention as clobbered. +clobber_abi("C"), +); + +// # 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: +// 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) +asm!("mov {0:h}, {0:l}", inout(reg_abcd) x); + +// # Memory address operands +// Sometimes assembly instructions require operands passed via memory addresses/memory locations. +// You have to manually use the memory address syntax specified by the target architecture. +// For example, on x86/x86_64 using intel assembly syntax, you should wrap inputs/outputs in +// [] to indicate they are memory operands: +asm!("fldcw [{}]", in(reg) &control, options(nostack)); + +// # Labels +// you should only use GNU assembler numeric local labels inside inline assembly code. +// https://sourceware.org/binutils/docs/as/Symbol-Names.html#Local-Labels +asm!( + "mov {0}, 10", + "2:", + "sub {0}, 1", + "cmp {0}, 3", + "jle 2f", + "jmp 2b", + "2:", + "add {0}, 2", + out(reg) a +); + +// # 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. +// Options can be provided as an optional final argument to the asm! macro. +asm!("add {0}, {1}", inlateout(reg) a, in(reg) b, options(pure, nomem, nostack)); diff --git a/tests/source/asm/single_template.rs b/tests/source/asm/single_template.rs new file mode 100644 index 00000000000..2a119937f10 --- /dev/null +++ b/tests/source/asm/single_template.rs @@ -0,0 +1,25 @@ +// rustfmt-max_width: 31 + +asm!("nop"); + +asm!("can't fit it on one line"); + +asm!("nop",); + +asm!("can't fit it on one line",); + +asm!["nop"]; + +asm!["can't fit it on one line"]; + +asm!["nop",]; + +asm!["can't fit it on one line",]; + +asm! {"nop"} + +asm! {"can't fit it on one line"} + +asm! {"nop",} + +asm! {"can't fit it on one line",} diff --git a/tests/source/asm/with_clobber_abis.rs b/tests/source/asm/with_clobber_abis.rs new file mode 100644 index 00000000000..5a725915aeb --- /dev/null +++ b/tests/source/asm/with_clobber_abis.rs @@ -0,0 +1,13 @@ +// The clobber_abi argument to asm! tells the compiler to automatically insert the +// necessary clobber operands according to the given calling convention ABI +asm!("some asm template", clobber_abi("C")); + +asm!("some asm template", clobber_abi("C"),); + +asm!["some asm template", clobber_abi("C")]; + +asm!["some asm template", clobber_abi("C"),]; + +asm!{"some asm template", clobber_abi("C")} + +asm!{"some asm template", clobber_abi("C"),} diff --git a/tests/source/asm/with_operands.rs b/tests/source/asm/with_operands.rs new file mode 100644 index 00000000000..7743e68bab8 --- /dev/null +++ b/tests/source/asm/with_operands.rs @@ -0,0 +1,140 @@ +// rustfmt-max_width: 50 + +// Refer to Operand Types +// https://doc.rust-lang.org/unstable-book/library-features/asm.html#operand-type +// To come up with valid operand expression examples. + +asm!( + "instruction {}", + // case 1 + inout(reg) very_long_expression => very_long_out_expression, + inlateout(reg) very_long_expression => very_long_out_expression, + // case 2 + inout(reg) function_name().method_name(method_arg).further_chained_method() => very_long_out_expression, + inlateout(reg) function_name().method_name(method_arg).further_chained_method() => very_long_out_expression, + // case 3 + inout(reg) long_function_name(long_function_argument_expression) => very_long_out_expression, + inlateout(reg) long_function_name(long_function_argument_expression) => very_long_out_expression, + // case 4 + inout(reg) very_long_expression => function_name().method_name(method_arg).further_chained_method(), + inlateout(reg) very_long_expression => function_name().method_name(method_arg).further_chained_method(), + // case 5 + inout(reg) function_name().method_name(method_arg).further_chained_method() => function_name().method_name(method_arg).further_chained_method(), + inlateout(reg) function_name().method_name(method_arg).further_chained_method() => function_name().method_name(method_arg).further_chained_method(), + // case 6 + inout(reg) long_function_name(long_function_argument_expression) => function_name().method_name(method_arg).further_chained_method(), + inlateout(reg) long_function_name(long_function_argument_expression) => function_name().method_name(method_arg).further_chained_method(), + // case 7 + inout(reg) very_long_expression => long_function_name(long_function_argument_expression), + inlateout(reg) very_long_expression => long_function_name(long_function_argument_expression), + // case 8 + inout(reg) function_name().method_name(method_arg).further_chained_method() => long_function_name(long_function_argument_expression), + inlateout(reg) function_name().method_name(method_arg).further_chained_method() => long_function_name(long_function_argument_expression), + // case 9 + inout(reg) long_function_name(long_function_argument_expression) => long_function_name(long_function_argument_expression), + inlateout(reg) long_function_name(long_function_argument_expression) => long_function_name(long_function_argument_expression), +); + +asm!( + "instruction {}", + // case 1 + aaaaaaaaaaaaaaaaaaaa = inout(reg) very_long_expression => very_long_out_expression, + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) very_long_expression => very_long_out_expression, + // case 2 + aaaaaaaaaaaaaaaaaaaa = inout(reg) function_name().method_name(method_arg).further_chained_method() => very_long_out_expression, + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) function_name().method_name(method_arg).further_chained_method() => very_long_out_expression, + // case 3 + aaaaaaaaaaaaaaaaaaaa = inout(reg) long_function_name(long_function_argument_expression) => very_long_out_expression, + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) long_function_name(long_function_argument_expression) => very_long_out_expression, + // case 4 + aaaaaaaaaaaaaaaaaaaa = inout(reg) very_long_expression => function_name().method_name(method_arg).further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) very_long_expression => function_name().method_name(method_arg).further_chained_method(), + // case 5 + aaaaaaaaaaaaaaaaaaaa = inout(reg) function_name().method_name(method_arg).further_chained_method() => function_name().method_name(method_arg).further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) function_name().method_name(method_arg).further_chained_method() => function_name().method_name(method_arg).further_chained_method(), + // case 6 + aaaaaaaaaaaaaaaaaaaa = inout(reg) long_function_name(long_function_argument_expression) => function_name().method_name(method_arg).further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) long_function_name(long_function_argument_expression) => function_name().method_name(method_arg).further_chained_method(), + // case 7 + aaaaaaaaaaaaaaaaaaaa = inout(reg) very_long_expression => long_function_name(long_function_argument_expression), + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) very_long_expression => long_function_name(long_function_argument_expression), + // case 8 + aaaaaaaaaaaaaaaaaaaa = inout(reg) function_name().method_name(method_arg).further_chained_method() => long_function_name(long_function_argument_expression), + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) function_name().method_name(method_arg).further_chained_method() => long_function_name(long_function_argument_expression), + // case 9 + aaaaaaaaaaaaaaaaaaaa = inout(reg) long_function_name(long_function_argument_expression) => long_function_name(long_function_argument_expression), + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) long_function_name(long_function_argument_expression) => long_function_name(long_function_argument_expression), +); + +asm!( + "instruction {}", + in(reg) extremely_long_unbreakable_expression, + out(reg) extremely_long_unbreakable_expression, + lateout(reg) extremely_long_unbreakable_expression, + inout(reg) extremely_long_unbreakable_expression, + inlateout(reg) extremely_long_unbreakable_expression, + const extremely_long_unbreakable_expression, + sym extremely_long_unbreakable_symbol_name, +); + +asm!( + "instruction {}", + in(reg) function_name().method_name(method_arg).further_chained_method(), + out(reg) function_name().method_name(method_arg).further_chained_method(), + lateout(reg) function_name().method_name(method_arg).further_chained_method(), + inout(reg) function_name().method_name(method_arg).further_chained_method(), + inlateout(reg) function_name().method_name(method_arg).further_chained_method(), + const function_name().method_name(method_arg).further_chained_method(), +); + +asm!( + "instruction {}", + in(reg) long_function_name(long_function_argument_expression), + out(reg) long_function_name(long_function_argument_expression), + lateout(reg) long_function_name(long_function_argument_expression), + inout(reg) long_function_name(long_function_argument_expression), + inlateout(reg) long_function_name(long_function_argument_expression), + const long_function_name(long_function_argument_expression), +); + +asm!( + "instruction {}", + aaaaaaaaaaaaaaaaaaaa = in(reg) extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = out(reg) extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = lateout(reg) extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = inout(reg) extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = const extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = sym extremely_long_unbreakable_symbol_name, +); + +asm!( + "instruction {}", + aaaaaaaaaaaaaaaaaaaa=in(reg)extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa=out(reg)extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa=lateout(reg) extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa=inout(reg) extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa=inlateout(reg) extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa=const extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa=sym extremely_long_unbreakable_symbol_name, +); + +asm!( + "instruction {}", + aaaaaaaaaaaaaaaaaaaa = in(reg) function_name().method_name(method_arg).further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = out(reg) function_name().method_name(method_arg).further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = lateout(reg) function_name().method_name(method_arg).further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = inout(reg) function_name().method_name(method_arg).further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) function_name().method_name(method_arg).further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = const function_name().method_name(method_arg).further_chained_method(), +); + +asm!( + "instruction {}", + aaaaaaaaaaaaaaaaaaaa = in(reg) long_function_name(long_function_argument_expression), + aaaaaaaaaaaaaaaaaaaa = out(reg) long_function_name(long_function_argument_expression), + aaaaaaaaaaaaaaaaaaaa = lateout(reg) long_function_name(long_function_argument_expression), + aaaaaaaaaaaaaaaaaaaa = inout(reg) long_function_name(long_function_argument_expression), + aaaaaaaaaaaaaaaaaaaa = inlateout(reg) long_function_name(long_function_argument_expression), + aaaaaaaaaaaaaaaaaaaa = const long_function_name(long_function_argument_expression), +); diff --git a/tests/source/asm/with_options.rs b/tests/source/asm/with_options.rs new file mode 100644 index 00000000000..0d461065c6b --- /dev/null +++ b/tests/source/asm/with_options.rs @@ -0,0 +1,45 @@ +// rustfmt-max_width: 30 + +// Options can be provided as an optional final argument to the asm! macro + +// with trailing commas +asm!("some asm template", options(nostack),); + +asm!("some asm template", options(pure, nomem, nostack),); + +asm!("some asm template", options(pure, nomem, nostack, preserves_flags),); + +// without trailing commas +asm!("some asm template", options(nostack)); + +asm!("some asm template", options(pure, nomem, nostack)); + +asm!("some asm template", options(pure, nomem, nostack, preserves_flags)); + +// with trailing commas +asm!["some asm template", options(nostack),]; + +asm!["some asm template", options(pure, nomem, nostack),]; + +asm!["some asm template", options(pure, nomem, nostack, preserves_flags),]; + +// without trailing commas +asm!["some asm template", options(nostack)]; + +asm!["some asm template", options(pure, nomem, nostack)]; + +asm!["some asm template", options(pure, nomem, nostack, preserves_flags)]; + +// with trailing commas +asm! {"some asm template", options(nostack),} + +asm! {"some asm template", options(pure, nomem, nostack),} + +asm! {"some asm template", options(pure, nomem, nostack, preserves_flags),} + +// without trailing commas +asm! {"some asm template", options(nostack)} + +asm! {"some asm template", options(pure, nomem, nostack)} + +asm! {"some asm template", options(pure, nomem, nostack, preserves_flags)} diff --git a/tests/source/asm/x86_labels.rs b/tests/source/asm/x86_labels.rs new file mode 100644 index 00000000000..98b0937ea27 --- /dev/null +++ b/tests/source/asm/x86_labels.rs @@ -0,0 +1,68 @@ +// x86 Assembly Language Reference Manual: +// https://docs.oracle.com/cd/E19120-01/open.solaris/817-5477/esqaq/index.html + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm!("label1:", "instruction1", "instruction2", "label2:", "instruction3", "instruction4"); + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm!(".lable1", "instruction1", "instruction2", ".label2", "instruction3", "instruction4"); + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm!("0:", "instruction1", "instruction2", "9:", "instruction3", "instruction4"); + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm!("label1:", "instruction1", "instruction2", "label2:", "instruction3", "instruction4",); + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm!(".lable1", "instruction1", "instruction2", ".label2", "instruction3", "instruction4",); + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm!("0:", "instruction1", "instruction2", "9:", "instruction3", "instruction4",); + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm!["label1:", "instruction1", "instruction2", "label2:", "instruction3", "instruction4"]; + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm![".lable1", "instruction1", "instruction2", ".label2", "instruction3", "instruction4"]; + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm!["0:", "instruction1", "instruction2", "9:", "instruction3", "instruction4"]; + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm!["label1:", "instruction1", "instruction2", "label2:", "instruction3", "instruction4",]; + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm![".lable1", "instruction1", "instruction2", ".label2", "instruction3", "instruction4",]; + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm!["0:", "instruction1", "instruction2", "9:", "instruction3", "instruction4",]; + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm!{"label1:", "instruction1", "instruction2", "label2:", "instruction3", "instruction4"} + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm!{".lable1", "instruction1", "instruction2", ".label2", "instruction3", "instruction4"} + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm!{"0:", "instruction1", "instruction2", "9:", "instruction3", "instruction4"} + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm!{"label1:", "instruction1", "instruction2", "label2:", "instruction3", "instruction4",} + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm!{".lable1", "instruction1", "instruction2", ".label2", "instruction3", "instruction4",} + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm!{"0:", "instruction1", "instruction2", "9:", "instruction3", "instruction4",} \ No newline at end of file diff --git a/tests/target/asm/empty_asm_call.rs b/tests/target/asm/empty_asm_call.rs new file mode 100644 index 00000000000..64b9fb1dcd9 --- /dev/null +++ b/tests/target/asm/empty_asm_call.rs @@ -0,0 +1,5 @@ +asm!(); + +asm![]; + +asm! {} diff --git a/tests/target/asm/fully_qualified_asm_call.rs b/tests/target/asm/fully_qualified_asm_call.rs new file mode 100644 index 00000000000..e38437ec9ff --- /dev/null +++ b/tests/target/asm/fully_qualified_asm_call.rs @@ -0,0 +1,5 @@ +core::arch::asm!("nop"); + +core::arch::asm!["nop"]; + +core::arch::asm! {"nop"} diff --git a/tests/target/asm/multiple_templates.rs b/tests/target/asm/multiple_templates.rs new file mode 100644 index 00000000000..983138a974f --- /dev/null +++ b/tests/target/asm/multiple_templates.rs @@ -0,0 +1,35 @@ +asm!( + "instruction1", + "instruction2", + "instruction3", +); + +asm!( + "instruction1", + "instruction2", + "instruction3" +); + +asm![ + "instruction1", + "instruction2", + "instruction3", +]; + +asm![ + "instruction1", + "instruction2", + "instruction3" +]; + +asm! { + "instruction1", + "instruction2", + "instruction3", +} + +asm! { + "instruction1", + "instruction2", + "instruction3" +} diff --git a/tests/target/asm/nested_asm_macro.rs b/tests/target/asm/nested_asm_macro.rs new file mode 100644 index 00000000000..8079300f692 --- /dev/null +++ b/tests/target/asm/nested_asm_macro.rs @@ -0,0 +1,59 @@ +#![feature(asm)] +extern "C" fn foo(arg: i32) -> i32 { + println!("arg = {}", arg); + arg * 2 +} + +fn call_with_parens(arg: i32) -> i32 { + unsafe { + let result; + asm!( + "call {}", + sym foo, + // 1st argument in rdi + in("rdi") arg, + // Return value in rax + out("rax") result, + // Mark all registers which are not preserved by the "C" calling + // convention as clobbered. + clobber_abi("C"), + ); + result + } +} + +fn call_with_brackets(arg: i32) -> i32 { + unsafe { + let result; + asm![ + "call {}", + sym foo, + // 1st argument in rdi + in("rdi") arg, + // Return value in rax + out("rax") result, + // Mark all registers which are not preserved by the "C" calling + // convention as clobbered. + clobber_abi("C"), + ]; + result + } +} + +fn call_with_braces(arg: i32) -> i32 { + unsafe { + let result; + asm! { + "call {}", + sym foo, + // 1st argument in rdi + in("rdi") arg, + // Return value in rax + out("rax") result, + // Mark all registers which are not preserved by the "C" calling + // convention as clobbered. + clobber_abi("C"), + } + result + } +} diff --git a/tests/target/asm/rust_unstable_book_examples.rs b/tests/target/asm/rust_unstable_book_examples.rs new file mode 100644 index 00000000000..b0add5e1cf3 --- /dev/null +++ b/tests/target/asm/rust_unstable_book_examples.rs @@ -0,0 +1,183 @@ +// collection of examples from The Rust Unstable Book found at +// https://doc.rust-lang.org/unstable-book/library-features/asm.html + +// # Basic usage +// Let us start with the simplest possible example +asm!("nop"); + +// # Inputs and outputs +// This will write the value 5 into the u64 variable x +asm!( + "mov {}, 5", + out(reg) x +); + +// This will add 5 to the input in variable i and write the result to variable o +asm!( + "mov {0}, {1}", + "add {0}, {number}", + out(reg) o, + in(reg) i, + number = const 5, +); + +// We can further refine the above example to avoid the mov instruction +// We can see that inout is used to specify an argument that is both input and output +asm!( + "add {0}, {number}", + inout(reg) x, + number = const 5 +); + +// It is also possible to specify different variables for the input and output parts of an +// inout operand +asm!( + "add {0}, {number}", + inout(reg) x => y, + number = const 5 +); + +// # Late output operands +// To guarantee optimal performance it is important to use as few registers as possible. +// 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. +asm!( + "add {0}, {1}", + inlateout(reg) a, + in(reg) b +); + +// Here is an example where inlateout cannot be used: +asm!( + "add {0}, {1}", + "add {0}, {2}", + inout(reg) a, + in(reg) b, + in(reg) c +); + +// # Explicit register operands +// Some instructions require that the operands be in a specific register +// Therefore, Rust inline assembly provides some more specific constraint specifiers. +asm!( + "out 0x64, eax", + in("eax") cmd +); + +// Consider this example which uses the x86 mul instruction: +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 +); + +// # Clobbered registers +// In many cases inline assembly will modify state that is not needed as an output. +// 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. +// In the example below 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 _ +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") _, +); + +// This can also be used with a general register class (e.g. reg) to obtain a scratch register +// for use inside the asm code: +asm!( + "mov {tmp}, {x}", + "shl {tmp}, 1", + "shl {x}, 2", + "add {x}, {tmp}", + x = inout(reg) x, + tmp = out(reg) _, +); + +// # Symbol operands and ABI clobbers +// A special operand type, sym, allows you to use the symbol name of a fn or static in inline +// assembly code. 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 +extern "C" fn foo(arg: i32) -> i32 { + println!("arg = {}", arg); + arg * 2 +} +asm!( + "call {}", + sym foo, + // 1st argument in rdi + in("rdi") arg, + // Return value in rax + out("rax") result, + // Mark all registers which are not preserved by the "C" calling + // convention as clobbered. + clobber_abi("C"), +); + +// # 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: +// 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) +asm!( + "mov {0:h}, {0:l}", + inout(reg_abcd) x +); + +// # Memory address operands +// Sometimes assembly instructions require operands passed via memory addresses/memory locations. +// You have to manually use the memory address syntax specified by the target architecture. +// For example, on x86/x86_64 using intel assembly syntax, you should wrap inputs/outputs in +// [] to indicate they are memory operands: +asm!( + "fldcw [{}]", + in(reg) &control, + options(nostack) +); + +// # Labels +// you should only use GNU assembler numeric local labels inside inline assembly code. +// https://sourceware.org/binutils/docs/as/Symbol-Names.html#Local-Labels +asm!( + "mov {0}, 10", + "2:", + " sub {0}, 1", + " cmp {0}, 3", + " jle 2f", + " jmp 2b", + "2:", + " add {0}, 2", + out(reg) a +); + +// # 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. +// Options can be provided as an optional final argument to the asm! macro. +asm!( + "add {0}, {1}", + inlateout(reg) a, + in(reg) b, + options(pure, nomem, nostack) +); diff --git a/tests/target/asm/single_template.rs b/tests/target/asm/single_template.rs new file mode 100644 index 00000000000..869c2bfa127 --- /dev/null +++ b/tests/target/asm/single_template.rs @@ -0,0 +1,37 @@ +// rustfmt-max_width: 31 + +asm!("nop"); + +asm!( + "can't fit it on one line" +); + +asm!("nop",); + +asm!( + "can't fit it on one line", +); + +asm!["nop"]; + +asm![ + "can't fit it on one line" +]; + +asm!["nop",]; + +asm![ + "can't fit it on one line", +]; + +asm! {"nop"} + +asm! { + "can't fit it on one line" +} + +asm! {"nop",} + +asm! { + "can't fit it on one line", +} diff --git a/tests/target/asm/with_clobber_abis.rs b/tests/target/asm/with_clobber_abis.rs new file mode 100644 index 00000000000..6d75a103450 --- /dev/null +++ b/tests/target/asm/with_clobber_abis.rs @@ -0,0 +1,31 @@ +// The clobber_abi argument to asm! tells the compiler to automatically insert the +// necessary clobber operands according to the given calling convention ABI +asm!( + "some asm template", + clobber_abi("C") +); + +asm!( + "some asm template", + clobber_abi("C"), +); + +asm![ + "some asm template", + clobber_abi("C") +]; + +asm![ + "some asm template", + clobber_abi("C"), +]; + +asm! { + "some asm template", + clobber_abi("C") +} + +asm! { + "some asm template", + clobber_abi("C"), +} diff --git a/tests/target/asm/with_operands.rs b/tests/target/asm/with_operands.rs new file mode 100644 index 00000000000..609f0f30be3 --- /dev/null +++ b/tests/target/asm/with_operands.rs @@ -0,0 +1,379 @@ +// rustfmt-max_width: 50 + +// Refer to Operand Types +// https://doc.rust-lang.org/unstable-book/library-features/asm.html#operand-type +// To come up with valid operand expression examples. + +asm!( + "instruction {}", + // case 1 + inout(reg) very_long_expression + => very_long_out_expression, + inlateout(reg) very_long_expression + => very_long_out_expression, + // case 2 + inout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => very_long_out_expression, + inlateout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => very_long_out_expression, + // case 3 + inout(reg) long_function_name( + long_function_argument_expression + ) + => very_long_out_expression, + inlateout(reg) long_function_name( + long_function_argument_expression + ) + => very_long_out_expression, + // case 4 + inout(reg) very_long_expression + => function_name() + .method_name(method_arg) + .further_chained_method(), + inlateout(reg) very_long_expression + => function_name() + .method_name(method_arg) + .further_chained_method(), + // case 5 + inout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => function_name() + .method_name(method_arg) + .further_chained_method(), + inlateout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => function_name() + .method_name(method_arg) + .further_chained_method(), + // case 6 + inout(reg) long_function_name( + long_function_argument_expression + ) + => function_name() + .method_name(method_arg) + .further_chained_method(), + inlateout(reg) long_function_name( + long_function_argument_expression + ) + => function_name() + .method_name(method_arg) + .further_chained_method(), + // case 7 + inout(reg) very_long_expression + => long_function_name( + long_function_argument_expression + ), + inlateout(reg) very_long_expression + => long_function_name( + long_function_argument_expression + ), + // case 8 + inout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => long_function_name( + long_function_argument_expression + ), + inlateout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => long_function_name( + long_function_argument_expression + ), + // case 9 + inout(reg) long_function_name( + long_function_argument_expression + ) + => long_function_name( + long_function_argument_expression + ), + inlateout(reg) long_function_name( + long_function_argument_expression + ) + => long_function_name( + long_function_argument_expression + ), +); + +asm!( + "instruction {}", + // case 1 + aaaaaaaaaaaaaaaaaaaa = + inout(reg) very_long_expression + => very_long_out_expression, + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) very_long_expression + => very_long_out_expression, + // case 2 + aaaaaaaaaaaaaaaaaaaa = + inout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => very_long_out_expression, + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => very_long_out_expression, + // case 3 + aaaaaaaaaaaaaaaaaaaa = + inout(reg) long_function_name( + long_function_argument_expression + ) + => very_long_out_expression, + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) long_function_name( + long_function_argument_expression + ) + => very_long_out_expression, + // case 4 + aaaaaaaaaaaaaaaaaaaa = + inout(reg) very_long_expression + => function_name() + .method_name(method_arg) + .further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) very_long_expression + => function_name() + .method_name(method_arg) + .further_chained_method(), + // case 5 + aaaaaaaaaaaaaaaaaaaa = + inout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => function_name() + .method_name(method_arg) + .further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => function_name() + .method_name(method_arg) + .further_chained_method(), + // case 6 + aaaaaaaaaaaaaaaaaaaa = + inout(reg) long_function_name( + long_function_argument_expression + ) + => function_name() + .method_name(method_arg) + .further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) long_function_name( + long_function_argument_expression + ) + => function_name() + .method_name(method_arg) + .further_chained_method(), + // case 7 + aaaaaaaaaaaaaaaaaaaa = + inout(reg) very_long_expression + => long_function_name( + long_function_argument_expression + ), + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) very_long_expression + => long_function_name( + long_function_argument_expression + ), + // case 8 + aaaaaaaaaaaaaaaaaaaa = + inout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => long_function_name( + long_function_argument_expression + ), + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) function_name() + .method_name(method_arg) + .further_chained_method() + => long_function_name( + long_function_argument_expression + ), + // case 9 + aaaaaaaaaaaaaaaaaaaa = + inout(reg) long_function_name( + long_function_argument_expression + ) + => long_function_name( + long_function_argument_expression + ), + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) long_function_name( + long_function_argument_expression + ) + => long_function_name( + long_function_argument_expression + ), +); + +asm!( + "instruction {}", + in(reg) + extremely_long_unbreakable_expression, + out(reg) + extremely_long_unbreakable_expression, + lateout(reg) + extremely_long_unbreakable_expression, + inout(reg) + extremely_long_unbreakable_expression, + inlateout(reg) + extremely_long_unbreakable_expression, + const extremely_long_unbreakable_expression, + sym extremely_long_unbreakable_symbol_name, +); + +asm!( + "instruction {}", + in(reg) function_name() + .method_name(method_arg) + .further_chained_method(), + out(reg) function_name() + .method_name(method_arg) + .further_chained_method(), + lateout(reg) function_name() + .method_name(method_arg) + .further_chained_method(), + inout(reg) function_name() + .method_name(method_arg) + .further_chained_method(), + inlateout(reg) function_name() + .method_name(method_arg) + .further_chained_method(), + const function_name() + .method_name(method_arg) + .further_chained_method(), +); + +asm!( + "instruction {}", + in(reg) long_function_name( + long_function_argument_expression + ), + out(reg) long_function_name( + long_function_argument_expression + ), + lateout(reg) long_function_name( + long_function_argument_expression + ), + inout(reg) long_function_name( + long_function_argument_expression + ), + inlateout(reg) long_function_name( + long_function_argument_expression + ), + const long_function_name( + long_function_argument_expression + ), +); + +asm!( + "instruction {}", + aaaaaaaaaaaaaaaaaaaa = + in(reg) + extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + out(reg) + extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + lateout(reg) + extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + inout(reg) + extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) + extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + const extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + sym extremely_long_unbreakable_symbol_name, +); + +asm!( + "instruction {}", + aaaaaaaaaaaaaaaaaaaa = + in(reg) + extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + out(reg) + extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + lateout(reg) + extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + inout(reg) + extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) + extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + const extremely_long_unbreakable_expression, + aaaaaaaaaaaaaaaaaaaa = + sym extremely_long_unbreakable_symbol_name, +); + +asm!( + "instruction {}", + aaaaaaaaaaaaaaaaaaaa = + in(reg) function_name() + .method_name(method_arg) + .further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = + out(reg) function_name() + .method_name(method_arg) + .further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = + lateout(reg) function_name() + .method_name(method_arg) + .further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = + inout(reg) function_name() + .method_name(method_arg) + .further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) function_name() + .method_name(method_arg) + .further_chained_method(), + aaaaaaaaaaaaaaaaaaaa = + const function_name() + .method_name(method_arg) + .further_chained_method(), +); + +asm!( + "instruction {}", + aaaaaaaaaaaaaaaaaaaa = + in(reg) long_function_name( + long_function_argument_expression + ), + aaaaaaaaaaaaaaaaaaaa = + out(reg) long_function_name( + long_function_argument_expression + ), + aaaaaaaaaaaaaaaaaaaa = + lateout(reg) long_function_name( + long_function_argument_expression + ), + aaaaaaaaaaaaaaaaaaaa = + inout(reg) long_function_name( + long_function_argument_expression + ), + aaaaaaaaaaaaaaaaaaaa = + inlateout(reg) long_function_name( + long_function_argument_expression + ), + aaaaaaaaaaaaaaaaaaaa = + const long_function_name( + long_function_argument_expression + ), +); diff --git a/tests/target/asm/with_options.rs b/tests/target/asm/with_options.rs new file mode 100644 index 00000000000..54f04196b53 --- /dev/null +++ b/tests/target/asm/with_options.rs @@ -0,0 +1,141 @@ +// rustfmt-max_width: 30 + +// Options can be provided as an optional final argument to the asm! macro + +// with trailing commas +asm!( + "some asm template", + options(nostack), +); + +asm!( + "some asm template", + options( + pure, nomem, nostack + ), +); + +asm!( + "some asm template", + options( + pure, + nomem, + nostack, + preserves_flags + ), +); + +// without trailing commas +asm!( + "some asm template", + options(nostack) +); + +asm!( + "some asm template", + options( + pure, nomem, nostack + ) +); + +asm!( + "some asm template", + options( + pure, + nomem, + nostack, + preserves_flags + ) +); + +// with trailing commas +asm![ + "some asm template", + options(nostack), +]; + +asm![ + "some asm template", + options( + pure, nomem, nostack + ), +]; + +asm![ + "some asm template", + options( + pure, + nomem, + nostack, + preserves_flags + ), +]; + +// without trailing commas +asm![ + "some asm template", + options(nostack) +]; + +asm![ + "some asm template", + options( + pure, nomem, nostack + ) +]; + +asm![ + "some asm template", + options( + pure, + nomem, + nostack, + preserves_flags + ) +]; + +// with trailing commas +asm! { + "some asm template", + options(nostack), +} + +asm! { + "some asm template", + options( + pure, nomem, nostack + ), +} + +asm! { + "some asm template", + options( + pure, + nomem, + nostack, + preserves_flags + ), +} + +// without trailing commas +asm! { + "some asm template", + options(nostack) +} + +asm! { + "some asm template", + options( + pure, nomem, nostack + ) +} + +asm! { + "some asm template", + options( + pure, + nomem, + nostack, + preserves_flags + ) +} diff --git a/tests/target/asm/x86_labels.rs b/tests/target/asm/x86_labels.rs new file mode 100644 index 00000000000..f704876c0c1 --- /dev/null +++ b/tests/target/asm/x86_labels.rs @@ -0,0 +1,194 @@ +// x86 Assembly Language Reference Manual: +// https://docs.oracle.com/cd/E19120-01/open.solaris/817-5477/esqaq/index.html + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm!( + "label1:", + " instruction1", + " instruction2", + "label2:", + " instruction3", + " instruction4" +); + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm!( + ".lable1", + " instruction1", + " instruction2", + ".label2", + " instruction3", + " instruction4" +); + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm!( + "0:", + " instruction1", + " instruction2", + "9:", + " instruction3", + " instruction4" +); + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm!( + "label1:", + " instruction1", + " instruction2", + "label2:", + " instruction3", + " instruction4", +); + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm!( + ".lable1", + " instruction1", + " instruction2", + ".label2", + " instruction3", + " instruction4", +); + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm!( + "0:", + " instruction1", + " instruction2", + "9:", + " instruction3", + " instruction4", +); + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm![ + "label1:", + " instruction1", + " instruction2", + "label2:", + " instruction3", + " instruction4" +]; + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm![ + ".lable1", + " instruction1", + " instruction2", + ".label2", + " instruction3", + " instruction4" +]; + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm![ + "0:", + " instruction1", + " instruction2", + "9:", + " instruction3", + " instruction4" +]; + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm![ + "label1:", + " instruction1", + " instruction2", + "label2:", + " instruction3", + " instruction4", +]; + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm![ + ".lable1", + " instruction1", + " instruction2", + ".label2", + " instruction3", + " instruction4", +]; + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm![ + "0:", + " instruction1", + " instruction2", + "9:", + " instruction3", + " instruction4", +]; + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm! { + "label1:", + " instruction1", + " instruction2", + "label2:", + " instruction3", + " instruction4" +} + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm! { + ".lable1", + " instruction1", + " instruction2", + ".label2", + " instruction3", + " instruction4" +} + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm! { + "0:", + " instruction1", + " instruction2", + "9:", + " instruction3", + " instruction4" +} + +// A symbolic label consists of an identifier (or symbol) followed by a colon (:) +asm! { + "label1:", + " instruction1", + " instruction2", + "label2:", + " instruction3", + " instruction4", +} + +// Symbolic labels with identifiers beginning with a period (.) (ASCII 0x2E) are +// considered to have local scope and are not included in the object file's symbol table +asm! { + ".lable1", + " instruction1", + " instruction2", + ".label2", + " instruction3", + " instruction4", +} + +// A numeric label consists of a single digit in the range zero (0) through nine (9) +// followed by a colon (:) +asm! { + "0:", + " instruction1", + " instruction2", + "9:", + " instruction3", + " instruction4", +}