Skip to content

Commit 4856ef7

Browse files
authored
Merge pull request #130 from madsmtm/assembly-tests
Assembly tests
2 parents 63badcc + a644265 commit 4856ef7

16 files changed

+302
-26
lines changed

.github/workflows/ci.yml

+38-26
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
rust:
6161
toolchain: nightly
6262
# Run on nightly to help find regressions
63-
args: --features tests/ui
63+
test-args: --features tests/ui
6464
- name: Build macOS 32bit
6565
os: macos-10.15
6666
target: i686-apple-darwin
@@ -77,14 +77,15 @@ jobs:
7777
- name: Test GNUStep
7878
os: ubuntu-latest
7979
target: x86_64-unknown-linux-gnu
80-
features: gnustep-1-9
81-
args: --features gnustep-1-9
80+
host-args: --features block-sys/gnustep-1-9,objc-sys/gnustep-1-9
81+
args: --features block-sys/gnustep-1-9,objc-sys/gnustep-1-9
8282
- name: Test GNUStep 32bit
8383
os: ubuntu-latest
8484
target: i686-unknown-linux-gnu
8585
cflags: -m32
8686
configureflags: --target=x86-pc-linux-gnu
87-
args: --features gnustep-1-9
87+
host-args: --features block-sys/gnustep-1-9,objc-sys/gnustep-1-9
88+
args: --features block-sys/gnustep-1-9,objc-sys/gnustep-1-9
8889
- name: Test iOS simulator x86 64bit
8990
os: macos-11
9091
target: x86_64-apple-ios
@@ -270,28 +271,6 @@ jobs:
270271
if: matrix.dinghy && steps.extern-cache.outputs.cache-hit != 'true'
271272
run: cargo install cargo-dinghy --version=^0.4 --root=$HOME/extern --target=x86_64-apple-darwin
272273

273-
- name: Run Cargo Dinghy
274-
if: matrix.dinghy
275-
run: |
276-
# Launch the simulator
277-
xcrun simctl list runtimes
278-
RUNTIME_ID=$(xcrun simctl list runtimes | grep iOS | cut -d ' ' -f 7 | tail -1)
279-
export SIM_ID=$(xcrun simctl create My-iphone7 com.apple.CoreSimulator.SimDeviceType.iPhone-7 $RUNTIME_ID)
280-
xcrun simctl boot $SIM_ID
281-
282-
# Build
283-
cargo dinghy build
284-
285-
# Run tests
286-
cargo dinghy --device=$SIM_ID test --no-default-features
287-
# Enable a few features. We're doing it this way because cargo dingy
288-
# doesn't support specifying features from a workspace.
289-
sed -i -e '/\[features\]/a\
290-
default = ["exception", "verify_message", "catch_all"]
291-
' objc2/Cargo.toml
292-
cargo dinghy --device=$SIM_ID test
293-
cargo dinghy --device=$SIM_ID test --release
294-
295274
- name: Build
296275
if: ${{ !matrix.dinghy }}
297276
uses: actions-rs/cargo@v1
@@ -343,3 +322,36 @@ jobs:
343322
command: test
344323
# Not using --all-features because that would enable e.g. gnustep
345324
args: --features ${{ env.FEATURES }},${{ env.UNSTABLE_FEATURES }} ${{ env.TESTARGS }}
325+
326+
- name: Run assembly tests
327+
# Not run on GNUStep yet since a lot of function labels are mangled and
328+
# not inlined (and hence quite hard to match on, at some point we'll
329+
# need to find a solution to that).
330+
if: ${{ !contains(matrix.os, 'ubuntu') }}
331+
shell: bash
332+
run:
333+
export HOST_TARGET=$(rustc -vV | grep host | cut -f2 -d' ')
334+
335+
cargo run ${{ matrix.host-args }} --features assembly --target=$HOST_TARGET test_assembly ${{ matrix.args }}
336+
337+
- name: Run Cargo Dinghy
338+
if: matrix.dinghy
339+
run: |
340+
# Launch the simulator
341+
xcrun simctl list runtimes
342+
RUNTIME_ID=$(xcrun simctl list runtimes | grep iOS | cut -d ' ' -f 7 | tail -1)
343+
export SIM_ID=$(xcrun simctl create My-iphone7 com.apple.CoreSimulator.SimDeviceType.iPhone-7 $RUNTIME_ID)
344+
xcrun simctl boot $SIM_ID
345+
346+
# Build
347+
cargo dinghy build
348+
349+
# Run tests
350+
cargo dinghy --device=$SIM_ID test --no-default-features
351+
# Enable a few features. We're doing it this way because cargo dingy
352+
# doesn't support specifying features from a workspace.
353+
sed -i -e '/\[features\]/a\
354+
default = ["exception", "verify_message", "catch_all"]
355+
' objc2/Cargo.toml
356+
cargo dinghy --device=$SIM_ID test
357+
cargo dinghy --device=$SIM_ID test --release

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ members = [
77
"block2",
88
"block-sys",
99
"tests",
10+
"tests/assembly/*",
1011
]

tests/Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ build = "build.rs"
1818
#
1919
# Also, they're slower than most tests.
2020
ui = ["trybuild"]
21+
assembly = ["cargo_metadata"]
2122

2223
[dependencies]
2324
block2 = { path = "../block2" }
@@ -29,9 +30,14 @@ objc2-foundation = { path = "../objc2-foundation" }
2930

3031
# Put here instead of dev-dependencies because we want to make it optional
3132
trybuild = { version = "1.0", optional = true }
33+
cargo_metadata = { version = "0.14", optional = true }
3234

3335
[build-dependencies]
3436
cc = "1.0"
3537

3638
[dev-dependencies]
3739
paste = "1.0"
40+
41+
[[bin]]
42+
name = "test_assembly"
43+
required-features = ["assembly"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "test_msg_send_zero_cost"
3+
version = "0.1.0"
4+
edition = "2018"
5+
publish = false
6+
7+
[lib]
8+
path = "lib.rs"
9+
10+
[dependencies]
11+
objc2 = { path = "../../../objc2" }
12+
objc-sys = { path = "../../../objc-sys" }
13+
block-sys = { path = "../../../block-sys" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.globl _handle
3+
.p2align 2
4+
_handle:
5+
b _objc_msgSend
6+
7+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.globl _handle
3+
.p2align 2
4+
_handle:
5+
b _objc_msgSend
6+
7+
; Stripped __LLVM section
8+
9+
; Stripped __LLVM section
10+
11+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.globl _handle
3+
.p2align 2
4+
_handle:
5+
b _objc_msgSend
6+
7+
; Stripped __LLVM section
8+
9+
; Stripped __LLVM section
10+
11+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.ios_version_min 7, 0
3+
.syntax unified
4+
.globl _handle
5+
.p2align 2
6+
.code 32
7+
_handle:
8+
b _objc_msgSend
9+
10+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.syntax unified
3+
.globl _handle
4+
.p2align 2
5+
.code 32
6+
_handle:
7+
push {r7, lr}
8+
mov r7, sp
9+
bl _objc_msgSend
10+
pop {r7, pc}
11+
12+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.intel_syntax noprefix
3+
.globl _handle
4+
.p2align 4, 0x90
5+
_handle:
6+
push ebp
7+
mov ebp, esp
8+
pop ebp
9+
jmp _objc_msgSend
10+
11+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.intel_syntax noprefix
3+
.globl _handle
4+
.p2align 4, 0x90
5+
_handle:
6+
push ebp
7+
mov ebp, esp
8+
pop ebp
9+
jmp _objc_msgSend
10+
11+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.intel_syntax noprefix
3+
.globl _handle
4+
.p2align 4, 0x90
5+
_handle:
6+
push rbp
7+
mov rbp, rsp
8+
pop rbp
9+
jmp _objc_msgSend
10+
11+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.section __TEXT,__text,regular,pure_instructions
2+
.intel_syntax noprefix
3+
.globl _handle
4+
.p2align 4, 0x90
5+
_handle:
6+
push rbp
7+
mov rbp, rsp
8+
pop rbp
9+
jmp _objc_msgSend
10+
11+
.subsections_via_symbols
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//! Test that the inner part of msg_send! is inlined into an objc_msgSend
2+
3+
use objc2::runtime::{Object, Sel};
4+
use objc2::MessageReceiver;
5+
6+
#[no_mangle]
7+
pub fn handle(obj: &Object, sel: Sel) -> *mut Object {
8+
unsafe { MessageReceiver::send_message(&obj, sel, ()).unwrap() }
9+
}

tests/build.rs

+3
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,8 @@ fn main() {
2222
builder.flag(flag);
2323
}
2424

25+
// For assembly tests
26+
println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());
27+
2528
builder.compile("libencode_utils.a");
2629
}

tests/src/bin/test_assembly.rs

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//! A helper script for testing the assembly output.
2+
//!
3+
//! Similar to `trybuild` and `compiletest`, except specialized to our setup!
4+
5+
use cargo_metadata::Message;
6+
use std::env;
7+
use std::env::args;
8+
use std::fmt::Write;
9+
use std::fs;
10+
use std::io;
11+
use std::path::Path;
12+
use std::process::{Command, Stdio};
13+
14+
fn strip_lines(data: &str, starts_with: &str) -> String {
15+
data.lines()
16+
.filter(|line| !line.trim_start().starts_with(starts_with))
17+
.collect::<Vec<_>>()
18+
.join("\n")
19+
}
20+
21+
fn strip_section(data: &str, section: &str) -> String {
22+
let mut res = String::with_capacity(data.len());
23+
let mut in_removed_section = false;
24+
for line in data.lines() {
25+
// This only works for the __LLVM sections we're interested in
26+
if line == "" {
27+
in_removed_section = false;
28+
}
29+
if line.trim().starts_with(".section") {
30+
if line.contains(section) {
31+
in_removed_section = true;
32+
write!(res, "; Stripped {section} section\n").unwrap();
33+
} else {
34+
in_removed_section = false;
35+
}
36+
}
37+
if !in_removed_section {
38+
res.push_str(line);
39+
res.push('\n');
40+
}
41+
}
42+
res
43+
}
44+
45+
fn read_assembly<P: AsRef<Path>>(path: P) -> io::Result<String> {
46+
let s = fs::read_to_string(path)?;
47+
let workspace_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
48+
.parent()
49+
.unwrap()
50+
.as_os_str()
51+
.to_str()
52+
.unwrap();
53+
let s = s.replace(workspace_dir, "$WORKSPACE");
54+
let s = strip_lines(&s, ".cfi_");
55+
let s = strip_lines(&s, ".macosx_version_");
56+
let s = strip_lines(&s, ".ios_version_");
57+
let s = strip_lines(&s, ".build_version");
58+
// We remove the __LLVM,__bitcode and __LLVM,__cmdline sections because
59+
// they're uninteresting for out use-case.
60+
//
61+
// See https://github.com/rust-lang/rust/blob/1.59.0/compiler/rustc_codegen_llvm/src/back/write.rs#L978-L1074
62+
Ok(strip_section(&s, "__LLVM"))
63+
}
64+
65+
fn main() {
66+
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
67+
let should_overwrite = option_env!("TEST_OVERWRITE").is_some();
68+
let host = env!("TARGET");
69+
70+
for entry in manifest_dir.join("assembly").read_dir().unwrap() {
71+
let package_path = entry.unwrap().path();
72+
let package = package_path.file_name().unwrap().to_str().unwrap();
73+
74+
println!("Testing {package}.");
75+
76+
let result = Command::new(std::env::var("CARGO").unwrap_or("cargo".into()))
77+
// .arg("+nightly")
78+
// .arg("-Zbuild-std")
79+
// .arg("-vv")
80+
.arg("rustc")
81+
.arg(format!("--package={package}"))
82+
.args(args().skip(2))
83+
.arg("--release")
84+
.arg("--message-format=json-render-diagnostics")
85+
.arg("--")
86+
.arg("--emit=asm")
87+
.arg("-Cllvm-args=--x86-asm-syntax=intel")
88+
.stdout(Stdio::piped())
89+
.stderr(Stdio::inherit())
90+
.output()
91+
.unwrap();
92+
93+
let artifact = Message::parse_stream(&*result.stdout)
94+
.find_map(|message| {
95+
if let Message::CompilerArtifact(artifact) = message.unwrap() {
96+
// Brittle!
97+
if artifact.target.name == package && artifact.filenames.len() == 2 {
98+
let path = artifact.filenames[1].clone();
99+
let stem = path.file_stem().unwrap().strip_prefix("lib").unwrap();
100+
return Some(path.with_file_name(format!("{stem}.s")));
101+
}
102+
}
103+
None
104+
})
105+
.unwrap_or_else(|| {
106+
panic!(
107+
"Could not find package data:\n{}",
108+
String::from_utf8_lossy(&result.stdout)
109+
)
110+
});
111+
112+
// Very brittle!
113+
let target = artifact
114+
.components()
115+
.map(|component| component.as_str())
116+
.skip_while(|&component| component != "target")
117+
.skip(1)
118+
.next()
119+
.unwrap_or(host);
120+
121+
println!("Target {target}.");
122+
123+
let expected_file = package_path.join("expected").join(format!("{target}.s"));
124+
125+
let actual = read_assembly(&artifact).unwrap();
126+
if should_overwrite {
127+
fs::write(expected_file, actual).unwrap();
128+
} else if let Ok(expected) = read_assembly(expected_file) {
129+
if expected != actual {
130+
eprintln!("\n===Expected===\n{}\n===Actual===\n{}", expected, actual);
131+
panic!("Expected and actual did not match.");
132+
}
133+
} else {
134+
panic!("Missing assembly output for target {}:\n{}", target, actual);
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)