Skip to content

Commit 55677c8

Browse files
authored
Merge pull request rust-lang#28 from alexcrichton/tests
Add assertions correct instructions are generated
2 parents 9fec93a + 4c74150 commit 55677c8

File tree

10 files changed

+388
-0
lines changed

10 files changed

+388
-0
lines changed

.appveyor.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
environment:
2+
matrix:
3+
- TARGET: x86_64-pc-windows-msvc
4+
5+
install:
6+
# Install rust, x86_64-pc-windows-msvc host
7+
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
8+
- rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
9+
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
10+
- if NOT "%TARGET%" == "x86_64-pc-windows-msvc" rustup target add %TARGET%
11+
- rustc -vV
12+
- cargo -vV
13+
14+
build: false
15+
16+
test_script:
17+
- cargo test --target %TARGET%
18+
- cargo test --target %TARGET% --release

.travis.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
language: rust
2+
sudo: false
3+
4+
matrix:
5+
include:
6+
- rust: nightly
7+
- rust: nightly
8+
os: osx
9+
10+
script:
11+
- cargo test
12+
- cargo test --release
13+
14+
notifications:
15+
email:
16+
on_success: never

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,10 @@ license = "MIT"
1313
[profile.release]
1414
debug = true
1515
opt-level = 3
16+
17+
[profile.bench]
18+
debug = 1
19+
opt-level = 3
20+
21+
[dev-dependencies]
22+
assert-instr = { path = "assert-instr" }

assert-instr/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "assert-instr"
3+
version = "0.1.0"
4+
authors = ["Alex Crichton <[email protected]>"]
5+
6+
[dependencies]
7+
assert-instr-macro = { path = "assert-instr-macro" }
8+
backtrace = "0.3"
9+
cc = "1.0"
10+
lazy_static = "0.2"
11+
rustc-demangle = "0.1"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "assert-instr-macro"
3+
version = "0.1.0"
4+
authors = ["Alex Crichton <[email protected]>"]
5+
6+
[lib]
7+
proc-macro = true
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use std::env;
2+
3+
fn main() {
4+
println!("cargo:rerun-if-changed=build.rs");
5+
let opt_level = env::var("OPT_LEVEL").ok().and_then(|s| s.parse().ok()).unwrap_or(0);
6+
let profile = env::var("PROFILE").unwrap_or(String::new());
7+
if profile == "release" || opt_level >= 2 {
8+
println!("cargo:rustc-cfg=optimized");
9+
}
10+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#![feature(proc_macro)]
2+
3+
extern crate proc_macro;
4+
5+
use proc_macro::{TokenStream, Term, TokenNode, Delimiter};
6+
7+
#[proc_macro_attribute]
8+
pub fn assert_instr(attr: TokenStream, item: TokenStream) -> TokenStream {
9+
let name = find_name(item.clone());
10+
let tokens = attr.into_iter().collect::<Vec<_>>();
11+
if tokens.len() != 1 {
12+
panic!("expected #[assert_instr(foo)]");
13+
}
14+
let tokens = match tokens[0].kind {
15+
TokenNode::Group(Delimiter::Parenthesis, ref rest) => rest.clone(),
16+
_ => panic!("expected #[assert_instr(foo)]"),
17+
};
18+
let tokens = tokens.into_iter().collect::<Vec<_>>();
19+
if tokens.len() != 1 {
20+
panic!("expected #[assert_instr(foo)]");
21+
}
22+
let instr = match tokens[0].kind {
23+
TokenNode::Term(term) => term,
24+
_ => panic!("expected #[assert_instr(foo)]"),
25+
};
26+
27+
let ignore = if cfg!(optimized) {
28+
""
29+
} else {
30+
"#[ignore]"
31+
};
32+
let test = format!("
33+
#[test]
34+
#[allow(non_snake_case)]
35+
{ignore}
36+
fn assert_instr_{name}() {{
37+
::assert_instr::assert({name} as usize, \"{instr}\");
38+
}}
39+
", name = name.as_str(), instr = instr.as_str(), ignore = ignore);
40+
let test: TokenStream = test.parse().unwrap();
41+
42+
item.into_iter().chain(test.into_iter()).collect()
43+
}
44+
45+
fn find_name(item: TokenStream) -> Term {
46+
let mut tokens = item.into_iter();
47+
while let Some(tok) = tokens.next() {
48+
if let TokenNode::Term(word) = tok.kind {
49+
if word.as_str() == "fn" {
50+
break
51+
}
52+
}
53+
}
54+
55+
match tokens.next().map(|t| t.kind) {
56+
Some(TokenNode::Term(word)) => word,
57+
_ => panic!("failed to find function name"),
58+
}
59+
}

assert-instr/src/lib.rs

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
#![feature(proc_macro)]
2+
3+
extern crate assert_instr_macro;
4+
extern crate backtrace;
5+
extern crate cc;
6+
extern crate rustc_demangle;
7+
#[macro_use]
8+
extern crate lazy_static;
9+
10+
use std::collections::HashMap;
11+
use std::env;
12+
use std::process::Command;
13+
use std::str;
14+
15+
pub use assert_instr_macro::*;
16+
17+
lazy_static! {
18+
static ref DISASSEMBLY: HashMap<String, Vec<Function>> = disassemble_myself();
19+
}
20+
21+
struct Function {
22+
instrs: Vec<Instruction>,
23+
}
24+
25+
struct Instruction {
26+
parts: Vec<String>,
27+
}
28+
29+
fn disassemble_myself() -> HashMap<String, Vec<Function>> {
30+
let me = env::current_exe().expect("failed to get current exe");
31+
32+
if cfg!(target_arch = "x86_64") &&
33+
cfg!(target_os = "windows") &&
34+
cfg!(target_env = "msvc") {
35+
let mut cmd = cc::windows_registry::find("x86_64-pc-windows-msvc", "dumpbin.exe")
36+
.expect("failed to find `dumpbin` tool");
37+
let output = cmd.arg("/DISASM").arg(&me).output()
38+
.expect("failed to execute dumpbin");
39+
println!("{}\n{}", output.status, String::from_utf8_lossy(&output.stderr));
40+
assert!(output.status.success());
41+
parse_dumpbin(&String::from_utf8_lossy(&output.stdout))
42+
} else if cfg!(target_os = "windows") {
43+
panic!("disassembly unimplemented")
44+
} else if cfg!(target_os = "macos") {
45+
let output = Command::new("otool")
46+
.arg("-vt")
47+
.arg(&me)
48+
.output()
49+
.expect("failed to execute otool");
50+
println!("{}\n{}", output.status, String::from_utf8_lossy(&output.stderr));
51+
assert!(output.status.success());
52+
53+
parse_otool(&str::from_utf8(&output.stdout).expect("stdout not utf8"))
54+
} else {
55+
let output = Command::new("objdump")
56+
.arg("--disassemble")
57+
.arg(&me)
58+
.output()
59+
.expect("failed to execute objdump");
60+
println!("{}\n{}", output.status, String::from_utf8_lossy(&output.stderr));
61+
assert!(output.status.success());
62+
63+
parse_objdump(&str::from_utf8(&output.stdout).expect("stdout not utf8"))
64+
}
65+
}
66+
67+
fn parse_objdump(output: &str) -> HashMap<String, Vec<Function>> {
68+
let mut lines = output.lines();
69+
70+
for line in output.lines().take(100) {
71+
println!("{}", line);
72+
}
73+
74+
let mut ret = HashMap::new();
75+
while let Some(header) = lines.next() {
76+
// symbols should start with `$hex_addr <$name>:`
77+
if !header.ends_with(">:") {
78+
continue
79+
}
80+
let start = header.find("<").unwrap();
81+
let symbol = &header[start + 1..header.len() - 2];
82+
83+
let mut instructions = Vec::new();
84+
while let Some(instruction) = lines.next() {
85+
if instruction.is_empty() {
86+
break
87+
}
88+
// Each line of instructions should look like:
89+
//
90+
// $rel_offset: ab cd ef 00 $instruction...
91+
let parts = instruction.split_whitespace()
92+
.skip(1)
93+
.skip_while(|s| {
94+
s.len() == 2 && usize::from_str_radix(s, 16).is_ok()
95+
})
96+
.map(|s| s.to_string())
97+
.collect::<Vec<String>>();
98+
instructions.push(Instruction { parts });
99+
}
100+
101+
ret.entry(normalize(symbol))
102+
.or_insert(Vec::new())
103+
.push(Function { instrs: instructions });
104+
}
105+
106+
return ret
107+
}
108+
109+
fn parse_otool(output: &str) -> HashMap<String, Vec<Function>> {
110+
let mut lines = output.lines();
111+
112+
for line in output.lines().take(100) {
113+
println!("{}", line);
114+
}
115+
116+
let mut ret = HashMap::new();
117+
let mut cached_header = None;
118+
loop {
119+
let header = match cached_header.take().or_else(|| lines.next()) {
120+
Some(header) => header,
121+
None => break,
122+
};
123+
// symbols should start with `$symbol:`
124+
if !header.ends_with(":") {
125+
continue
126+
}
127+
// strip the leading underscore and the trailing colon
128+
let symbol = &header[1..header.len() - 1];
129+
130+
let mut instructions = Vec::new();
131+
while let Some(instruction) = lines.next() {
132+
if instruction.ends_with(":") {
133+
cached_header = Some(instruction);
134+
break
135+
}
136+
// Each line of instructions should look like:
137+
//
138+
// $addr $instruction...
139+
let parts = instruction.split_whitespace()
140+
.skip(1)
141+
.map(|s| s.to_string())
142+
.collect::<Vec<String>>();
143+
instructions.push(Instruction { parts });
144+
}
145+
146+
ret.entry(normalize(symbol))
147+
.or_insert(Vec::new())
148+
.push(Function { instrs: instructions });
149+
}
150+
151+
return ret
152+
}
153+
154+
fn parse_dumpbin(output: &str) -> HashMap<String, Vec<Function>> {
155+
let mut lines = output.lines();
156+
157+
for line in output.lines().take(100) {
158+
println!("{}", line);
159+
}
160+
161+
let mut ret = HashMap::new();
162+
let mut cached_header = None;
163+
loop {
164+
let header = match cached_header.take().or_else(|| lines.next()) {
165+
Some(header) => header,
166+
None => break,
167+
};
168+
// symbols should start with `$symbol:`
169+
if !header.ends_with(":") {
170+
continue
171+
}
172+
// strip the trailing colon
173+
let symbol = &header[..header.len() - 1];
174+
175+
let mut instructions = Vec::new();
176+
while let Some(instruction) = lines.next() {
177+
if !instruction.starts_with(" ") {
178+
cached_header = Some(instruction);
179+
break
180+
}
181+
// Each line looks like:
182+
//
183+
// > $addr: ab cd ef $instr..
184+
// > 00 12 # this line os optional
185+
if instruction.starts_with(" ") {
186+
continue
187+
}
188+
let parts = instruction.split_whitespace()
189+
.skip(1)
190+
.skip_while(|s| {
191+
s.len() == 2 && usize::from_str_radix(s, 16).is_ok()
192+
})
193+
.map(|s| s.to_string())
194+
.collect::<Vec<String>>();
195+
instructions.push(Instruction { parts });
196+
}
197+
198+
ret.entry(normalize(symbol))
199+
.or_insert(Vec::new())
200+
.push(Function { instrs: instructions });
201+
}
202+
203+
return ret
204+
}
205+
206+
fn normalize(symbol: &str) -> String {
207+
let symbol = rustc_demangle::demangle(symbol).to_string();
208+
match symbol.rfind("::h") {
209+
Some(i) => symbol[..i].to_string(),
210+
None => symbol.to_string(),
211+
}
212+
}
213+
214+
pub fn assert(fnptr: usize, expected: &str) {
215+
let mut sym = None;
216+
backtrace::resolve(fnptr as *mut _, |name| {
217+
sym = name.name().and_then(|s| s.as_str()).map(normalize);
218+
});
219+
220+
let sym = match sym {
221+
Some(s) => s,
222+
None => panic!("failed to get symbol of function pointer: {}", fnptr),
223+
};
224+
225+
let functions = &DISASSEMBLY.get(&sym)
226+
.expect(&format!("failed to find disassembly of {}", sym));
227+
assert_eq!(functions.len(), 1);
228+
let function = &functions[0];
229+
for instr in function.instrs.iter() {
230+
if let Some(part) = instr.parts.get(0) {
231+
if part == expected {
232+
return
233+
}
234+
}
235+
}
236+
237+
println!("disassembly for {}: ", sym);
238+
for (i, instr) in function.instrs.iter().enumerate() {
239+
print!("\t{:2}: ", i);
240+
for part in instr.parts.iter() {
241+
print!("{} ", part);
242+
}
243+
println!("");
244+
}
245+
panic!("failed to find instruction `{}` in the disassembly", expected);
246+
}
247+

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
const_fn, link_llvm_intrinsics, platform_intrinsics, repr_simd, simd_ffi,
44
target_feature, cfg_target_feature, i128_type
55
)]
6+
#![cfg_attr(test, feature(proc_macro))]
7+
8+
#[cfg(test)]
9+
extern crate assert_instr;
610

711
/// Platform independent SIMD vector types and operations.
812
pub mod simd {

0 commit comments

Comments
 (0)