Navigation Menu

Skip to content

Commit

Permalink
Fix and improve inline assembly.
Browse files Browse the repository at this point in the history
Read+write modifier
Some documentation in asm.rs
rpass and cfail tests
  • Loading branch information
pczarn committed Mar 13, 2014
1 parent 3316a0e commit 2a1bd2f
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 54 deletions.
9 changes: 5 additions & 4 deletions src/librustc/middle/liveness.rs
Expand Up @@ -1259,14 +1259,15 @@ impl Liveness {
}

ExprInlineAsm(ref ia) => {
let succ = ia.inputs.rev_iter().fold(succ, |succ, &(_, expr)| {
self.propagate_through_expr(expr, succ)
});
ia.outputs.rev_iter().fold(succ, |succ, &(_, expr)| {
let succ = ia.outputs.rev_iter().fold(succ, |succ, &(_, expr)| {
// see comment on lvalues in
// propagate_through_lvalue_components()
let succ = self.write_lvalue(expr, succ, ACC_WRITE);
self.propagate_through_lvalue_components(expr, succ)
});
// Inputs are executed first. Propagate last because of rev order
ia.inputs.rev_iter().fold(succ, |succ, &(_, expr)| {
self.propagate_through_expr(expr, succ)
})
}

Expand Down
119 changes: 69 additions & 50 deletions src/libsyntax/ext/asm.rs
Expand Up @@ -27,19 +27,25 @@ enum State {
Outputs,
Inputs,
Clobbers,
Options
Options,
StateNone
}

fn next_state(s: State) -> Option<State> {
match s {
Asm => Some(Outputs),
Outputs => Some(Inputs),
Inputs => Some(Clobbers),
Clobbers => Some(Options),
Options => None
impl State {
fn next(&self) -> State {
match *self {
Asm => Outputs,
Outputs => Inputs,
Inputs => Clobbers,
Clobbers => Options,
Options => StateNone,
StateNone => StateNone
}
}
}

static OPTIONS: &'static [&'static str] = &["volatile", "alignstack", "intel"];

pub fn expand_asm(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
-> base::MacResult {
let mut p = parse::new_parser_from_tts(cx.parse_sess(),
Expand All @@ -59,9 +65,9 @@ pub fn expand_asm(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])

let mut state = Asm;

// Not using labeled break to get us through one round of bootstrapping.
let mut continue_ = true;
while continue_ {
let mut read_write_operands = Vec::new();

'statement: loop {
match state {
Asm => {
let (s, style) = match expr_to_str(cx, p.parse_expr(),
Expand All @@ -84,18 +90,33 @@ pub fn expand_asm(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])

let (constraint, _str_style) = p.parse_str();

if constraint.get().starts_with("+") {
cx.span_unimpl(p.last_span,
"'+' (read+write) output operand constraint modifier");
} else if !constraint.get().starts_with("=") {
cx.span_err(p.last_span, "output operand constraint lacks '='");
}
let span = p.last_span;

p.expect(&token::LPAREN);
let out = p.parse_expr();
p.expect(&token::RPAREN);

outputs.push((constraint, out));
// Expands a read+write operand into two operands.
//
// Use '+' modifier when you want the same expression
// to be both an input and an output at the same time.
// It's the opposite of '=&' which means that the memory
// cannot be shared with any other operand (usually when
// a register is clobbered early.)
let output = match constraint.get().slice_shift_char() {
(Some('='), _) => None,
(Some('+'), operand) => {
// Save a reference to the output
read_write_operands.push((outputs.len(), out));
Some(token::intern_and_get_ident("=" + operand))
}
_ => {
cx.span_err(span, "output operand constraint lacks '=' or '+'");
None
}
};

outputs.push((output.unwrap_or(constraint), out));
}
}
Inputs => {
Expand Down Expand Up @@ -135,6 +156,10 @@ pub fn expand_asm(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
let (s, _str_style) = p.parse_str();
let clob = format!("~\\{{}\\}", s);
clobs.push(clob);

if OPTIONS.iter().any(|opt| s.equiv(opt)) {
cx.span_warn(p.last_span, "expected a clobber, but found an option");
}
}

cons = clobs.connect(",");
Expand All @@ -143,56 +168,50 @@ pub fn expand_asm(cx: &mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
let (option, _str_style) = p.parse_str();

if option.equiv(&("volatile")) {
// Indicates that the inline assembly has side effects
// and must not be optimized out along with its outputs.
volatile = true;
} else if option.equiv(&("alignstack")) {
alignstack = true;
} else if option.equiv(&("intel")) {
dialect = ast::AsmIntel;
} else {
cx.span_warn(p.last_span, "unrecognized option");
}

if p.token == token::COMMA {
p.eat(&token::COMMA);
}
}
StateNone => ()
}

while p.token == token::COLON ||
p.token == token::MOD_SEP ||
p.token == token::EOF {
state = if p.token == token::COLON {
p.bump();
match next_state(state) {
Some(x) => x,
None => {
continue_ = false;
break
}
loop {
// MOD_SEP is a double colon '::' without space in between.
// When encountered, the state must be advanced twice.
match (&p.token, state.next(), state.next().next()) {
(&token::COLON, StateNone, _) |
(&token::MOD_SEP, _, StateNone) => {
p.bump();
break 'statement;
}
} else if p.token == token::MOD_SEP {
p.bump();
let s = match next_state(state) {
Some(x) => x,
None => {
continue_ = false;
break
}
};
match next_state(s) {
Some(x) => x,
None => {
continue_ = false;
break
}
(&token::COLON, st, _) |
(&token::MOD_SEP, _, st) => {
p.bump();
state = st;
}
} else if p.token == token::EOF {
continue_ = false;
break;
} else {
state
};
(&token::EOF, _, _) => break 'statement,
_ => break
}
}
}

// Append an input operand, with the form of ("0", expr)
// that links to an output operand.
for &(i, out) in read_write_operands.iter() {
inputs.push((token::intern_and_get_ident(i.to_str()), out));
}

MRExpr(@ast::Expr {
id: ast::DUMMY_NODE_ID,
node: ast::ExprInlineAsm(ast::InlineAsm {
Expand Down
41 changes: 41 additions & 0 deletions src/test/compile-fail/asm-misplaced-option.rs
@@ -0,0 +1,41 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// ignore-fast #[feature] doesn't work with check-fast
#[feature(asm)];

#[allow(dead_code)];

#[cfg(target_arch = "x86")]
#[cfg(target_arch = "x86_64")]
pub fn main() {
// assignment not dead
let mut x: int = 0;
unsafe {
// extra colon
asm!("mov $1, $0" : "=r"(x) : "r"(5u), "0"(x) : : "cc");
//~^ WARNING unrecognized option
}
assert_eq!(x, 5);

unsafe {
// comma in place of a colon
asm!("add $2, $1; mov $1, $0" : "=r"(x) : "r"(x), "r"(8u) : "cc", "volatile");
//~^ WARNING expected a clobber, but found an option
}
assert_eq!(x, 13);
}

// #[cfg(not(target_arch = "x86"), not(target_arch = "x86_64"))]
// pub fn main() {}

// At least one error is needed so that compilation fails
#[static_assert]
static b: bool = false; //~ ERROR static assertion failed
67 changes: 67 additions & 0 deletions src/test/run-pass/asm-in-out-operand.rs
@@ -0,0 +1,67 @@
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// ignore-fast #[feature] doesn't work with check-fast
#[feature(asm)];

#[cfg(target_arch = "x86")]
#[cfg(target_arch = "x86_64")]
unsafe fn next_power_of_2(n: u32) -> u32 {
let mut tmp = n;
asm!("dec $0" : "+rm"(tmp) :: "cc");
let mut shift = 1u;
while shift <= 16 {
asm!(
"shr %cl, $2
or $2, $0
shl $$1, $1"
: "+&rm"(tmp), "+{ecx}"(shift) : "r"(tmp) : "cc"
);
}
asm!("inc $0" : "+rm"(tmp) :: "cc");
return tmp;
}

#[cfg(target_arch = "x86")]
#[cfg(target_arch = "x86_64")]
pub fn main() {
unsafe {
assert_eq!(64, next_power_of_2(37));
assert_eq!(2147483648, next_power_of_2(2147483647));
}

let mut y: int = 5;
let x: int;
unsafe {
// Treat the output as initialization.
asm!(
"shl $2, $1
add $3, $1
mov $1, $0"
: "=r"(x), "+r"(y) : "i"(3u), "ir"(7u) : "cc"
);
}
assert_eq!(x, 47);
assert_eq!(y, 47);

let mut x = x + 1;
assert_eq!(x, 48);

unsafe {
// Assignment to mutable.
// Early clobber "&":
// Forbids the use of a single register by both operands.
asm!("shr $$2, $1; add $1, $0" : "+&r"(x) : "r"(x) : "cc");
}
assert_eq!(x, 60);
}

#[cfg(not(target_arch = "x86"), not(target_arch = "x86_64"))]
pub fn main() {}

5 comments on commit 2a1bd2f

@bors
Copy link
Contributor

@bors bors commented on 2a1bd2f Mar 14, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

saw approval from alexcrichton
at pczarn@2a1bd2f

@bors
Copy link
Contributor

@bors bors commented on 2a1bd2f Mar 14, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merging pczarn/rust/inline-asm = 2a1bd2f into auto

@bors
Copy link
Contributor

@bors bors commented on 2a1bd2f Mar 14, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pczarn/rust/inline-asm = 2a1bd2f merged ok, testing candidate = 98fa0f8

@bors
Copy link
Contributor

@bors bors commented on 2a1bd2f Mar 14, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fast-forwarding master to auto = 98fa0f8

Please sign in to comment.