From 2a1bd2ff9f9039df4b3c0158eac3589a222a2833 Mon Sep 17 00:00:00 2001
From: Piotr Czarnecki <pioczarn@gmail.com>
Date: Sun, 9 Mar 2014 23:41:18 +0100
Subject: [PATCH] Fix and improve inline assembly.

Read+write modifier
Some documentation in asm.rs
rpass and cfail tests
---
 src/librustc/middle/liveness.rs               |   9 +-
 src/libsyntax/ext/asm.rs                      | 119 ++++++++++--------
 src/test/compile-fail/asm-misplaced-option.rs |  41 ++++++
 src/test/run-pass/asm-in-out-operand.rs       |  67 ++++++++++
 4 files changed, 182 insertions(+), 54 deletions(-)
 create mode 100644 src/test/compile-fail/asm-misplaced-option.rs
 create mode 100644 src/test/run-pass/asm-in-out-operand.rs

diff --git a/src/librustc/middle/liveness.rs b/src/librustc/middle/liveness.rs
index 02a947a0ddc..3be28bad5ab 100644
--- a/src/librustc/middle/liveness.rs
+++ b/src/librustc/middle/liveness.rs
@@ -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)
             })
           }
 
diff --git a/src/libsyntax/ext/asm.rs b/src/libsyntax/ext/asm.rs
index 6080613460d..8c1e27d8b01 100644
--- a/src/libsyntax/ext/asm.rs
+++ b/src/libsyntax/ext/asm.rs
@@ -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(),
@@ -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(),
@@ -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 => {
@@ -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(",");
@@ -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 {
diff --git a/src/test/compile-fail/asm-misplaced-option.rs b/src/test/compile-fail/asm-misplaced-option.rs
new file mode 100644
index 00000000000..595247143ed
--- /dev/null
+++ b/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
diff --git a/src/test/run-pass/asm-in-out-operand.rs b/src/test/run-pass/asm-in-out-operand.rs
new file mode 100644
index 00000000000..11d4321b3e9
--- /dev/null
+++ b/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() {}