2017-09-26 12:37:39 -07:00
|
|
|
//! Runtime support needed for testing the stdsimd crate.
|
2017-09-20 10:28:00 -07:00
|
|
|
//!
|
|
|
|
|
//! This basically just disassembles the current executable and then parses the
|
|
|
|
|
//! output once globally and then provides the `assert` function which makes
|
|
|
|
|
//! assertions about the disassembly of a function.
|
|
|
|
|
|
2018-06-06 00:17:14 +02:00
|
|
|
#![cfg_attr(
|
|
|
|
|
feature = "cargo-clippy",
|
|
|
|
|
allow(missing_docs_in_private_items, print_stdout)
|
|
|
|
|
)]
|
2017-09-19 14:46:00 -07:00
|
|
|
|
|
|
|
|
extern crate assert_instr_macro;
|
|
|
|
|
extern crate backtrace;
|
|
|
|
|
extern crate cc;
|
|
|
|
|
#[macro_use]
|
|
|
|
|
extern crate lazy_static;
|
2017-10-27 17:55:29 +02:00
|
|
|
extern crate rustc_demangle;
|
|
|
|
|
extern crate simd_test_macro;
|
2018-08-15 18:20:33 +02:00
|
|
|
extern crate wasm_bindgen;
|
2017-09-19 14:46:00 -07:00
|
|
|
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use std::env;
|
2018-08-15 18:20:33 +02:00
|
|
|
use std::path::Path;
|
2017-09-19 14:46:00 -07:00
|
|
|
use std::process::Command;
|
|
|
|
|
use std::str;
|
|
|
|
|
|
2018-08-15 18:20:33 +02:00
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
|
|
2017-09-19 14:46:00 -07:00
|
|
|
pub use assert_instr_macro::*;
|
2017-09-26 12:37:39 -07:00
|
|
|
pub use simd_test_macro::*;
|
2017-09-19 14:46:00 -07:00
|
|
|
|
|
|
|
|
lazy_static! {
|
2018-06-06 00:17:14 +02:00
|
|
|
static ref DISASSEMBLY: HashMap<String, Vec<Function>> =
|
|
|
|
|
disassemble_myself();
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Function {
|
2018-08-15 18:20:33 +02:00
|
|
|
addr: Option<usize>,
|
2017-09-19 14:46:00 -07:00
|
|
|
instrs: Vec<Instruction>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Instruction {
|
|
|
|
|
parts: Vec<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn disassemble_myself() -> HashMap<String, Vec<Function>> {
|
2018-08-15 18:20:33 +02:00
|
|
|
if cfg!(target_arch = "wasm32") {
|
|
|
|
|
return parse_wasm2wat();
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 14:46:00 -07:00
|
|
|
let me = env::current_exe().expect("failed to get current exe");
|
|
|
|
|
|
2018-06-06 00:17:14 +02:00
|
|
|
if cfg!(target_arch = "x86_64")
|
|
|
|
|
&& cfg!(target_os = "windows")
|
2017-10-27 17:55:29 +02:00
|
|
|
&& cfg!(target_env = "msvc")
|
|
|
|
|
{
|
|
|
|
|
let mut cmd = cc::windows_registry::find(
|
|
|
|
|
"x86_64-pc-windows-msvc",
|
|
|
|
|
"dumpbin.exe",
|
|
|
|
|
).expect("failed to find `dumpbin` tool");
|
2018-06-06 00:17:14 +02:00
|
|
|
let output = cmd
|
|
|
|
|
.arg("/DISASM")
|
2017-10-27 17:55:29 +02:00
|
|
|
.arg(&me)
|
|
|
|
|
.output()
|
2017-09-19 14:46:00 -07:00
|
|
|
.expect("failed to execute dumpbin");
|
2017-10-27 17:55:29 +02:00
|
|
|
println!(
|
|
|
|
|
"{}\n{}",
|
|
|
|
|
output.status,
|
|
|
|
|
String::from_utf8_lossy(&output.stderr)
|
|
|
|
|
);
|
2017-09-19 14:46:00 -07:00
|
|
|
assert!(output.status.success());
|
|
|
|
|
parse_dumpbin(&String::from_utf8_lossy(&output.stdout))
|
|
|
|
|
} else if cfg!(target_os = "windows") {
|
|
|
|
|
panic!("disassembly unimplemented")
|
|
|
|
|
} else if cfg!(target_os = "macos") {
|
|
|
|
|
let output = Command::new("otool")
|
|
|
|
|
.arg("-vt")
|
|
|
|
|
.arg(&me)
|
|
|
|
|
.output()
|
|
|
|
|
.expect("failed to execute otool");
|
2017-10-27 17:55:29 +02:00
|
|
|
println!(
|
|
|
|
|
"{}\n{}",
|
|
|
|
|
output.status,
|
|
|
|
|
String::from_utf8_lossy(&output.stderr)
|
|
|
|
|
);
|
2017-09-19 14:46:00 -07:00
|
|
|
assert!(output.status.success());
|
|
|
|
|
|
2017-11-22 12:49:15 +01:00
|
|
|
parse_otool(str::from_utf8(&output.stdout).expect("stdout not utf8"))
|
2017-09-19 14:46:00 -07:00
|
|
|
} else {
|
2017-11-22 12:49:15 +01:00
|
|
|
let objdump =
|
|
|
|
|
env::var("OBJDUMP").unwrap_or_else(|_| "objdump".to_string());
|
2018-03-09 17:47:26 +01:00
|
|
|
let output = Command::new(objdump.clone())
|
2017-09-19 14:46:00 -07:00
|
|
|
.arg("--disassemble")
|
|
|
|
|
.arg(&me)
|
|
|
|
|
.output()
|
2018-03-09 17:47:26 +01:00
|
|
|
.expect(&format!(
|
|
|
|
|
"failed to execute objdump. OBJDUMP={}",
|
|
|
|
|
objdump
|
|
|
|
|
));
|
2017-10-27 17:55:29 +02:00
|
|
|
println!(
|
|
|
|
|
"{}\n{}",
|
|
|
|
|
output.status,
|
|
|
|
|
String::from_utf8_lossy(&output.stderr)
|
|
|
|
|
);
|
2017-09-19 14:46:00 -07:00
|
|
|
assert!(output.status.success());
|
|
|
|
|
|
2017-11-22 12:49:15 +01:00
|
|
|
parse_objdump(str::from_utf8(&output.stdout).expect("stdout not utf8"))
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_objdump(output: &str) -> HashMap<String, Vec<Function>> {
|
|
|
|
|
let mut lines = output.lines();
|
2017-11-22 12:49:15 +01:00
|
|
|
let expected_len =
|
|
|
|
|
if cfg!(target_arch = "arm") || cfg!(target_arch = "aarch64") {
|
|
|
|
|
8
|
|
|
|
|
} else {
|
|
|
|
|
2
|
|
|
|
|
};
|
2017-09-19 14:46:00 -07:00
|
|
|
|
|
|
|
|
for line in output.lines().take(100) {
|
|
|
|
|
println!("{}", line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut ret = HashMap::new();
|
|
|
|
|
while let Some(header) = lines.next() {
|
|
|
|
|
// symbols should start with `$hex_addr <$name>:`
|
|
|
|
|
if !header.ends_with(">:") {
|
2017-10-27 17:55:29 +02:00
|
|
|
continue;
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
2017-11-22 12:49:15 +01:00
|
|
|
let start = header.find('<')
|
|
|
|
|
.expect(&format!("\"<\" not found in symbol pattern of the form \"$hex_addr <$name>\": {}", header));
|
2017-09-19 14:46:00 -07:00
|
|
|
let symbol = &header[start + 1..header.len() - 2];
|
|
|
|
|
|
|
|
|
|
let mut instructions = Vec::new();
|
|
|
|
|
while let Some(instruction) = lines.next() {
|
|
|
|
|
if instruction.is_empty() {
|
2017-10-27 17:55:29 +02:00
|
|
|
break;
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
// Each line of instructions should look like:
|
|
|
|
|
//
|
|
|
|
|
// $rel_offset: ab cd ef 00 $instruction...
|
2017-10-27 17:55:29 +02:00
|
|
|
let parts = instruction
|
|
|
|
|
.split_whitespace()
|
2017-09-19 14:46:00 -07:00
|
|
|
.skip(1)
|
|
|
|
|
.skip_while(|s| {
|
2017-10-27 17:55:29 +02:00
|
|
|
s.len() == expected_len
|
|
|
|
|
&& usize::from_str_radix(s, 16).is_ok()
|
2018-08-14 16:22:06 +02:00
|
|
|
}).map(|s| s.to_string())
|
2017-09-19 14:46:00 -07:00
|
|
|
.collect::<Vec<String>>();
|
|
|
|
|
instructions.push(Instruction { parts });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret.entry(normalize(symbol))
|
2017-11-22 12:49:15 +01:00
|
|
|
.or_insert_with(Vec::new)
|
2017-10-27 17:55:29 +02:00
|
|
|
.push(Function {
|
2018-08-15 18:20:33 +02:00
|
|
|
addr: None,
|
2017-10-27 17:55:29 +02:00
|
|
|
instrs: instructions,
|
|
|
|
|
});
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
|
2017-11-22 12:49:15 +01:00
|
|
|
ret
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_otool(output: &str) -> HashMap<String, Vec<Function>> {
|
|
|
|
|
let mut lines = output.lines();
|
|
|
|
|
|
|
|
|
|
for line in output.lines().take(100) {
|
|
|
|
|
println!("{}", line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut ret = HashMap::new();
|
|
|
|
|
let mut cached_header = None;
|
2017-11-22 12:49:15 +01:00
|
|
|
while let Some(header) = cached_header.take().or_else(|| lines.next()) {
|
2017-09-19 14:46:00 -07:00
|
|
|
// symbols should start with `$symbol:`
|
2017-11-22 12:49:15 +01:00
|
|
|
if !header.ends_with(':') {
|
2017-10-27 17:55:29 +02:00
|
|
|
continue;
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
// strip the leading underscore and the trailing colon
|
|
|
|
|
let symbol = &header[1..header.len() - 1];
|
|
|
|
|
|
|
|
|
|
let mut instructions = Vec::new();
|
|
|
|
|
while let Some(instruction) = lines.next() {
|
2017-11-22 12:49:15 +01:00
|
|
|
if instruction.ends_with(':') {
|
2017-09-19 14:46:00 -07:00
|
|
|
cached_header = Some(instruction);
|
2017-10-27 17:55:29 +02:00
|
|
|
break;
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
// Each line of instructions should look like:
|
|
|
|
|
//
|
|
|
|
|
// $addr $instruction...
|
2017-10-27 17:55:29 +02:00
|
|
|
let parts = instruction
|
|
|
|
|
.split_whitespace()
|
2017-09-19 14:46:00 -07:00
|
|
|
.skip(1)
|
|
|
|
|
.map(|s| s.to_string())
|
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
|
instructions.push(Instruction { parts });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret.entry(normalize(symbol))
|
2017-11-22 12:49:15 +01:00
|
|
|
.or_insert_with(Vec::new)
|
2017-10-27 17:55:29 +02:00
|
|
|
.push(Function {
|
2018-08-15 18:20:33 +02:00
|
|
|
addr: None,
|
2017-10-27 17:55:29 +02:00
|
|
|
instrs: instructions,
|
|
|
|
|
});
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
|
2017-11-22 12:49:15 +01:00
|
|
|
ret
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_dumpbin(output: &str) -> HashMap<String, Vec<Function>> {
|
|
|
|
|
let mut lines = output.lines();
|
|
|
|
|
|
|
|
|
|
for line in output.lines().take(100) {
|
|
|
|
|
println!("{}", line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut ret = HashMap::new();
|
|
|
|
|
let mut cached_header = None;
|
2017-11-22 12:49:15 +01:00
|
|
|
while let Some(header) = cached_header.take().or_else(|| lines.next()) {
|
2017-09-19 14:46:00 -07:00
|
|
|
// symbols should start with `$symbol:`
|
2017-11-22 12:49:15 +01:00
|
|
|
if !header.ends_with(':') {
|
2017-10-27 17:55:29 +02:00
|
|
|
continue;
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
// strip the trailing colon
|
|
|
|
|
let symbol = &header[..header.len() - 1];
|
|
|
|
|
|
|
|
|
|
let mut instructions = Vec::new();
|
|
|
|
|
while let Some(instruction) = lines.next() {
|
|
|
|
|
if !instruction.starts_with(" ") {
|
|
|
|
|
cached_header = Some(instruction);
|
2017-10-27 17:55:29 +02:00
|
|
|
break;
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
// Each line looks like:
|
|
|
|
|
//
|
|
|
|
|
// > $addr: ab cd ef $instr..
|
|
|
|
|
// > 00 12 # this line os optional
|
|
|
|
|
if instruction.starts_with(" ") {
|
2017-10-27 17:55:29 +02:00
|
|
|
continue;
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
2017-10-27 17:55:29 +02:00
|
|
|
let parts = instruction
|
|
|
|
|
.split_whitespace()
|
2017-09-19 14:46:00 -07:00
|
|
|
.skip(1)
|
2017-12-14 19:57:53 +01:00
|
|
|
.skip_while(|s| {
|
|
|
|
|
s.len() == 2 && usize::from_str_radix(s, 16).is_ok()
|
2018-08-14 16:22:06 +02:00
|
|
|
}).map(|s| s.to_string())
|
2017-09-19 14:46:00 -07:00
|
|
|
.collect::<Vec<String>>();
|
|
|
|
|
instructions.push(Instruction { parts });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret.entry(normalize(symbol))
|
2017-11-22 12:49:15 +01:00
|
|
|
.or_insert_with(Vec::new)
|
2017-10-27 17:55:29 +02:00
|
|
|
.push(Function {
|
2018-08-15 18:20:33 +02:00
|
|
|
addr: None,
|
2017-10-27 17:55:29 +02:00
|
|
|
instrs: instructions,
|
|
|
|
|
});
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
|
2017-11-22 12:49:15 +01:00
|
|
|
ret
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
|
2018-08-15 18:20:33 +02:00
|
|
|
#[wasm_bindgen(module = "child_process")]
|
|
|
|
|
extern "C" {
|
|
|
|
|
#[wasm_bindgen(js_name = execSync)]
|
|
|
|
|
fn exec_sync(cmd: &str) -> Buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen(module = "buffer")]
|
|
|
|
|
extern "C" {
|
|
|
|
|
type Buffer;
|
|
|
|
|
#[wasm_bindgen(method, js_name = toString)]
|
|
|
|
|
fn to_string(this: &Buffer) -> String;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[wasm_bindgen]
|
|
|
|
|
extern "C" {
|
|
|
|
|
#[wasm_bindgen(js_namespace = require)]
|
|
|
|
|
fn resolve(module: &str) -> String;
|
|
|
|
|
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
|
|
|
|
fn js_console_log(s: &str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// println! doesn't work on wasm32 right now, so shadow the compiler's println!
|
|
|
|
|
// macro with our own shim that redirects to `console.log`.
|
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
|
macro_rules! println {
|
|
|
|
|
($($args:tt)*) => (js_console_log(&format!($($args)*)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_wasm2wat() -> HashMap<String, Vec<Function>> {
|
|
|
|
|
// Our wasm module in the wasm-bindgen test harness is called
|
|
|
|
|
// "wasm-bindgen-test_bg". When running in node this is actually a shim JS
|
|
|
|
|
// file. Ask node where that JS file is, and then we use that with a wasm
|
|
|
|
|
// extension to find the wasm file itself.
|
|
|
|
|
let js_shim = resolve("wasm-bindgen-test_bg");
|
|
|
|
|
let js_shim = Path::new(&js_shim).with_extension("wasm");
|
|
|
|
|
|
|
|
|
|
// Execute `wasm2wat` synchronously, waiting for and capturing all of its
|
|
|
|
|
// output.
|
|
|
|
|
let output =
|
|
|
|
|
exec_sync(&format!("wasm2wat {}", js_shim.display())).to_string();
|
|
|
|
|
|
|
|
|
|
let mut ret: HashMap<String, Vec<Function>> = HashMap::new();
|
|
|
|
|
let mut lines = output.lines().map(|s| s.trim());
|
|
|
|
|
while let Some(line) = lines.next() {
|
|
|
|
|
// If we found the table of function pointers, fill in the known
|
|
|
|
|
// address for all our `Function` instances
|
|
|
|
|
if line.starts_with("(elem") {
|
|
|
|
|
for (i, name) in line.split_whitespace().skip(3).enumerate() {
|
|
|
|
|
let name = name.trim_right_matches(")");
|
|
|
|
|
for f in ret.get_mut(name).expect("ret.get_mut(name) failed") {
|
|
|
|
|
f.addr = Some(i + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this isn't a function, we don't care about it.
|
|
|
|
|
if !line.starts_with("(func ") {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut function = Function {
|
|
|
|
|
instrs: Vec::new(),
|
|
|
|
|
addr: None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Empty functions will end in `))` so there's nothing to do, otherwise
|
|
|
|
|
// we'll have a bunch of following lines which are instructions.
|
|
|
|
|
//
|
|
|
|
|
// Lines that have an imbalanced `)` mark the end of a function.
|
|
|
|
|
if !line.ends_with("))") {
|
|
|
|
|
while let Some(line) = lines.next() {
|
|
|
|
|
function.instrs.push(Instruction {
|
|
|
|
|
parts: line
|
|
|
|
|
.split_whitespace()
|
|
|
|
|
.map(|s| s.to_string())
|
|
|
|
|
.collect(),
|
|
|
|
|
});
|
|
|
|
|
if !line.starts_with("(") && line.ends_with(")") {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The second element here split on whitespace should be the name of
|
|
|
|
|
// the function, skipping the type/params/results
|
|
|
|
|
ret.entry(line.split_whitespace().nth(1).unwrap().to_string())
|
|
|
|
|
.or_insert(Vec::new())
|
|
|
|
|
.push(function);
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-19 14:46:00 -07:00
|
|
|
fn normalize(symbol: &str) -> String {
|
|
|
|
|
let symbol = rustc_demangle::demangle(symbol).to_string();
|
|
|
|
|
match symbol.rfind("::h") {
|
|
|
|
|
Some(i) => symbol[..i].to_string(),
|
|
|
|
|
None => symbol.to_string(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-20 10:28:00 -07:00
|
|
|
/// Main entry point for this crate, called by the `#[assert_instr]` macro.
|
|
|
|
|
///
|
|
|
|
|
/// This asserts that the function at `fnptr` contains the instruction
|
|
|
|
|
/// `expected` provided.
|
2017-09-21 07:15:24 -07:00
|
|
|
pub fn assert(fnptr: usize, fnname: &str, expected: &str) {
|
2018-08-15 18:20:33 +02:00
|
|
|
let mut fnname = fnname.to_string();
|
|
|
|
|
let functions = get_functions(fnptr, &mut fnname);
|
2017-09-19 14:46:00 -07:00
|
|
|
assert_eq!(functions.len(), 1);
|
|
|
|
|
let function = &functions[0];
|
2017-09-20 10:28:00 -07:00
|
|
|
|
2018-01-28 20:35:37 -08:00
|
|
|
let mut instrs = &function.instrs[..];
|
2018-06-06 00:17:14 +02:00
|
|
|
while instrs.last().map_or(false, |s| s.parts == ["nop"]) {
|
2018-01-28 20:35:37 -08:00
|
|
|
instrs = &instrs[..instrs.len() - 1];
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-20 10:28:00 -07:00
|
|
|
// Look for `expected` as the first part of any instruction in this
|
|
|
|
|
// function, returning if we do indeed find it.
|
2017-09-25 13:55:48 -07:00
|
|
|
let mut found = false;
|
2018-01-28 20:35:37 -08:00
|
|
|
for instr in instrs {
|
2017-09-21 09:28:02 +02:00
|
|
|
// Gets the first instruction, e.g. tzcntl in tzcntl %rax,%rax
|
2017-09-19 14:46:00 -07:00
|
|
|
if let Some(part) = instr.parts.get(0) {
|
2017-09-21 09:28:02 +02:00
|
|
|
// Truncates the instruction with the length of the expected
|
|
|
|
|
// instruction: tzcntl => tzcnt and compares that.
|
2017-09-21 16:13:46 +02:00
|
|
|
if part.starts_with(expected) {
|
2017-09-25 13:55:48 -07:00
|
|
|
found = true;
|
2017-10-27 17:55:29 +02:00
|
|
|
break;
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-03 16:37:45 -06:00
|
|
|
// Look for `call` instructions in the disassembly to detect whether
|
|
|
|
|
// inlining failed: all intrinsics are `#[inline(always)]`, so
|
|
|
|
|
// calling one intrinsic from another should not generate `call`
|
|
|
|
|
// instructions.
|
|
|
|
|
let mut inlining_failed = false;
|
2018-01-28 20:35:37 -08:00
|
|
|
for (i, instr) in instrs.iter().enumerate() {
|
2018-01-03 16:37:45 -06:00
|
|
|
let part = match instr.parts.get(0) {
|
|
|
|
|
Some(part) => part,
|
|
|
|
|
None => continue,
|
|
|
|
|
};
|
|
|
|
|
if !part.contains("call") {
|
2018-01-04 17:15:23 +01:00
|
|
|
continue;
|
2018-01-03 16:37:45 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// On 32-bit x86 position independent code will call itself and be
|
|
|
|
|
// immediately followed by a `pop` to learn about the current address.
|
|
|
|
|
// Let's not take that into account when considering whether a function
|
|
|
|
|
// failed inlining something.
|
2018-01-04 17:15:23 +01:00
|
|
|
let followed_by_pop = function
|
|
|
|
|
.instrs
|
|
|
|
|
.get(i + 1)
|
2018-01-03 16:37:45 -06:00
|
|
|
.and_then(|i| i.parts.get(0))
|
2018-01-04 17:15:23 +01:00
|
|
|
.map_or(false, |s| s.contains("pop"));
|
2018-01-03 16:37:45 -06:00
|
|
|
if followed_by_pop && cfg!(target_arch = "x86") {
|
2018-01-04 17:15:23 +01:00
|
|
|
continue;
|
2018-01-03 16:37:45 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inlining_failed = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-12 11:33:20 +02:00
|
|
|
let instruction_limit = std::env::var("STDSIMD_ASSERT_INSTR_LIMIT")
|
|
|
|
|
.map(|v| v.parse().unwrap())
|
|
|
|
|
.unwrap_or_else(|_| match expected {
|
|
|
|
|
// cpuid returns a pretty big aggregate structure so exempt it from
|
|
|
|
|
// the slightly more restrictive 22 instructions below
|
|
|
|
|
"cpuid" => 30,
|
|
|
|
|
|
|
|
|
|
// Apparently on Windows LLVM generates a bunch of saves/restores
|
|
|
|
|
// of xmm registers around these intstructions which
|
|
|
|
|
// blows the 20 limit below. As it seems dictates by
|
|
|
|
|
// Windows's abi (I guess?) we probably can't do much
|
|
|
|
|
// about it...
|
|
|
|
|
"vzeroall" | "vzeroupper" if cfg!(windows) => 30,
|
|
|
|
|
|
|
|
|
|
// Intrinsics using `cvtpi2ps` are typically "composites" and in
|
|
|
|
|
// some cases exceed the limit.
|
|
|
|
|
"cvtpi2ps" => 25,
|
|
|
|
|
|
|
|
|
|
// Original limit was 20 instructions, but ARM DSP Intrinsics are
|
|
|
|
|
// exactly 20 instructions long. So bump the limit to 22 instead of
|
|
|
|
|
// adding here a long list of exceptions.
|
|
|
|
|
_ => 22,
|
|
|
|
|
});
|
2018-02-02 16:08:27 +01:00
|
|
|
let probably_only_one_instruction = instrs.len() < instruction_limit;
|
2017-09-25 13:55:48 -07:00
|
|
|
|
2018-01-03 16:37:45 -06:00
|
|
|
if found && probably_only_one_instruction && !inlining_failed {
|
2017-10-27 17:55:29 +02:00
|
|
|
return;
|
2017-09-25 13:55:48 -07:00
|
|
|
}
|
|
|
|
|
|
2017-09-20 10:28:00 -07:00
|
|
|
// Help debug by printing out the found disassembly, and then panic as we
|
|
|
|
|
// didn't find the instruction.
|
2018-08-15 18:20:33 +02:00
|
|
|
println!("disassembly for {}: ", fnname,);
|
2018-01-28 20:35:37 -08:00
|
|
|
for (i, instr) in instrs.iter().enumerate() {
|
2018-08-15 18:20:33 +02:00
|
|
|
let mut s = format!("\t{:2}: ", i);
|
2017-11-22 12:49:15 +01:00
|
|
|
for part in &instr.parts {
|
2018-08-15 18:20:33 +02:00
|
|
|
s.push_str(part);
|
|
|
|
|
s.push_str(" ");
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
2018-08-15 18:20:33 +02:00
|
|
|
println!("{}", s);
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
2017-09-25 13:55:48 -07:00
|
|
|
|
|
|
|
|
if !found {
|
2017-12-14 19:57:53 +01:00
|
|
|
panic!(
|
|
|
|
|
"failed to find instruction `{}` in the disassembly",
|
|
|
|
|
expected
|
|
|
|
|
);
|
2017-09-25 13:55:48 -07:00
|
|
|
} else if !probably_only_one_instruction {
|
2018-01-04 17:15:23 +01:00
|
|
|
panic!(
|
|
|
|
|
"instruction found, but the disassembly contains too many \
|
|
|
|
|
instructions: #instructions = {} >= {} (limit)",
|
2018-01-28 20:35:37 -08:00
|
|
|
instrs.len(),
|
2018-01-04 17:15:23 +01:00
|
|
|
instruction_limit
|
|
|
|
|
);
|
2018-01-03 16:37:45 -06:00
|
|
|
} else if inlining_failed {
|
2018-01-04 17:15:23 +01:00
|
|
|
panic!(
|
|
|
|
|
"instruction found, but the disassembly contains `call` \
|
|
|
|
|
instructions, which hint that inlining failed"
|
|
|
|
|
);
|
2017-09-25 13:55:48 -07:00
|
|
|
}
|
2017-09-19 14:46:00 -07:00
|
|
|
}
|
2017-10-18 11:35:11 -04:00
|
|
|
|
2018-08-15 18:20:33 +02:00
|
|
|
fn get_functions(fnptr: usize, fnname: &mut String) -> &'static [Function] {
|
|
|
|
|
// Translate this function pointer to a symbolic name that we'd have found
|
|
|
|
|
// in the disassembly.
|
|
|
|
|
let mut sym = None;
|
|
|
|
|
backtrace::resolve(fnptr as *mut _, |name| {
|
|
|
|
|
sym = name.name().and_then(|s| s.as_str()).map(normalize);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if let Some(sym) = &sym {
|
|
|
|
|
if let Some(s) = DISASSEMBLY.get(sym) {
|
|
|
|
|
*fnname = sym.to_string();
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let exact_match = DISASSEMBLY
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|(_, list)| list.iter().any(|f| f.addr == Some(fnptr)));
|
|
|
|
|
if let Some((name, list)) = exact_match {
|
|
|
|
|
*fnname = name.to_string();
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(sym) = sym {
|
|
|
|
|
println!("assumed symbol name: `{}`", sym);
|
|
|
|
|
}
|
|
|
|
|
println!("maybe related functions");
|
|
|
|
|
for f in DISASSEMBLY.keys().filter(|k| k.contains(&**fnname)) {
|
|
|
|
|
println!("\t- {}", f);
|
|
|
|
|
}
|
|
|
|
|
panic!("failed to find disassembly of {:#x} ({})", fnptr, fnname);
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 11:35:11 -04:00
|
|
|
pub fn assert_skip_test_ok(name: &str) {
|
|
|
|
|
if env::var("STDSIMD_TEST_EVERYTHING").is_err() {
|
2017-10-28 22:29:52 +02:00
|
|
|
return;
|
2017-10-18 11:35:11 -04:00
|
|
|
}
|
2018-06-06 00:17:14 +02:00
|
|
|
panic!("skipped test `{}` when it shouldn't be skipped", name);
|
2017-10-18 11:35:11 -04:00
|
|
|
}
|
2018-05-21 20:37:41 +02:00
|
|
|
|
|
|
|
|
// See comment in `assert-instr-macro` crate for why this exists
|
|
|
|
|
pub static mut _DONT_DEDUP: &'static str = "";
|