Skip to content

Assembly tests #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 38 additions & 26 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
rust:
toolchain: nightly
# Run on nightly to help find regressions
args: --features tests/ui
test-args: --features tests/ui
- name: Build macOS 32bit
os: macos-10.15
target: i686-apple-darwin
Expand All @@ -77,14 +77,15 @@ jobs:
- name: Test GNUStep
os: ubuntu-latest
target: x86_64-unknown-linux-gnu
features: gnustep-1-9
args: --features gnustep-1-9
host-args: --features block-sys/gnustep-1-9,objc-sys/gnustep-1-9
args: --features block-sys/gnustep-1-9,objc-sys/gnustep-1-9
- name: Test GNUStep 32bit
os: ubuntu-latest
target: i686-unknown-linux-gnu
cflags: -m32
configureflags: --target=x86-pc-linux-gnu
args: --features gnustep-1-9
host-args: --features block-sys/gnustep-1-9,objc-sys/gnustep-1-9
args: --features block-sys/gnustep-1-9,objc-sys/gnustep-1-9
- name: Test iOS simulator x86 64bit
os: macos-11
target: x86_64-apple-ios
Expand Down Expand Up @@ -270,28 +271,6 @@ jobs:
if: matrix.dinghy && steps.extern-cache.outputs.cache-hit != 'true'
run: cargo install cargo-dinghy --version=^0.4 --root=$HOME/extern --target=x86_64-apple-darwin

- name: Run Cargo Dinghy
if: matrix.dinghy
run: |
# Launch the simulator
xcrun simctl list runtimes
RUNTIME_ID=$(xcrun simctl list runtimes | grep iOS | cut -d ' ' -f 7 | tail -1)
export SIM_ID=$(xcrun simctl create My-iphone7 com.apple.CoreSimulator.SimDeviceType.iPhone-7 $RUNTIME_ID)
xcrun simctl boot $SIM_ID

# Build
cargo dinghy build

# Run tests
cargo dinghy --device=$SIM_ID test --no-default-features
# Enable a few features. We're doing it this way because cargo dingy
# doesn't support specifying features from a workspace.
sed -i -e '/\[features\]/a\
default = ["exception", "verify_message", "catch_all"]
' objc2/Cargo.toml
cargo dinghy --device=$SIM_ID test
cargo dinghy --device=$SIM_ID test --release

- name: Build
if: ${{ !matrix.dinghy }}
uses: actions-rs/cargo@v1
Expand Down Expand Up @@ -343,3 +322,36 @@ jobs:
command: test
# Not using --all-features because that would enable e.g. gnustep
args: --features ${{ env.FEATURES }},${{ env.UNSTABLE_FEATURES }} ${{ env.TESTARGS }}

- name: Run assembly tests
# Not run on GNUStep yet since a lot of function labels are mangled and
# not inlined (and hence quite hard to match on, at some point we'll
# need to find a solution to that).
if: ${{ !contains(matrix.os, 'ubuntu') }}
shell: bash
run:
export HOST_TARGET=$(rustc -vV | grep host | cut -f2 -d' ')

cargo run ${{ matrix.host-args }} --features assembly --target=$HOST_TARGET test_assembly ${{ matrix.args }}

- name: Run Cargo Dinghy
if: matrix.dinghy
run: |
# Launch the simulator
xcrun simctl list runtimes
RUNTIME_ID=$(xcrun simctl list runtimes | grep iOS | cut -d ' ' -f 7 | tail -1)
export SIM_ID=$(xcrun simctl create My-iphone7 com.apple.CoreSimulator.SimDeviceType.iPhone-7 $RUNTIME_ID)
xcrun simctl boot $SIM_ID

# Build
cargo dinghy build

# Run tests
cargo dinghy --device=$SIM_ID test --no-default-features
# Enable a few features. We're doing it this way because cargo dingy
# doesn't support specifying features from a workspace.
sed -i -e '/\[features\]/a\
default = ["exception", "verify_message", "catch_all"]
' objc2/Cargo.toml
cargo dinghy --device=$SIM_ID test
cargo dinghy --device=$SIM_ID test --release
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ members = [
"block2",
"block-sys",
"tests",
"tests/assembly/*",
]
6 changes: 6 additions & 0 deletions tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ build = "build.rs"
#
# Also, they're slower than most tests.
ui = ["trybuild"]
assembly = ["cargo_metadata"]

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

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

[build-dependencies]
cc = "1.0"

[dev-dependencies]
paste = "1.0"

[[bin]]
name = "test_assembly"
required-features = ["assembly"]
13 changes: 13 additions & 0 deletions tests/assembly/test_msg_send_zero_cost/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "test_msg_send_zero_cost"
version = "0.1.0"
edition = "2018"
publish = false

[lib]
path = "lib.rs"

[dependencies]
objc2 = { path = "../../../objc2" }
objc-sys = { path = "../../../objc-sys" }
block-sys = { path = "../../../block-sys" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.section __TEXT,__text,regular,pure_instructions
.globl _handle
.p2align 2
_handle:
b _objc_msgSend

.subsections_via_symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.section __TEXT,__text,regular,pure_instructions
.globl _handle
.p2align 2
_handle:
b _objc_msgSend

; Stripped __LLVM section

; Stripped __LLVM section

.subsections_via_symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.section __TEXT,__text,regular,pure_instructions
.globl _handle
.p2align 2
_handle:
b _objc_msgSend

; Stripped __LLVM section

; Stripped __LLVM section

.subsections_via_symbols
10 changes: 10 additions & 0 deletions tests/assembly/test_msg_send_zero_cost/expected/armv7-apple-ios.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.section __TEXT,__text,regular,pure_instructions
.ios_version_min 7, 0
.syntax unified
.globl _handle
.p2align 2
.code 32
_handle:
b _objc_msgSend

.subsections_via_symbols
12 changes: 12 additions & 0 deletions tests/assembly/test_msg_send_zero_cost/expected/armv7s-apple-ios.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.section __TEXT,__text,regular,pure_instructions
.syntax unified
.globl _handle
.p2align 2
.code 32
_handle:
push {r7, lr}
mov r7, sp
bl _objc_msgSend
pop {r7, pc}

.subsections_via_symbols
11 changes: 11 additions & 0 deletions tests/assembly/test_msg_send_zero_cost/expected/i386-apple-ios.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.section __TEXT,__text,regular,pure_instructions
.intel_syntax noprefix
.globl _handle
.p2align 4, 0x90
_handle:
push ebp
mov ebp, esp
pop ebp
jmp _objc_msgSend

.subsections_via_symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.section __TEXT,__text,regular,pure_instructions
.intel_syntax noprefix
.globl _handle
.p2align 4, 0x90
_handle:
push ebp
mov ebp, esp
pop ebp
jmp _objc_msgSend

.subsections_via_symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.section __TEXT,__text,regular,pure_instructions
.intel_syntax noprefix
.globl _handle
.p2align 4, 0x90
_handle:
push rbp
mov rbp, rsp
pop rbp
jmp _objc_msgSend

.subsections_via_symbols
11 changes: 11 additions & 0 deletions tests/assembly/test_msg_send_zero_cost/expected/x86_64-apple-ios.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.section __TEXT,__text,regular,pure_instructions
.intel_syntax noprefix
.globl _handle
.p2align 4, 0x90
_handle:
push rbp
mov rbp, rsp
pop rbp
jmp _objc_msgSend

.subsections_via_symbols
9 changes: 9 additions & 0 deletions tests/assembly/test_msg_send_zero_cost/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! Test that the inner part of msg_send! is inlined into an objc_msgSend

use objc2::runtime::{Object, Sel};
use objc2::MessageReceiver;

#[no_mangle]
pub fn handle(obj: &Object, sel: Sel) -> *mut Object {
unsafe { MessageReceiver::send_message(&obj, sel, ()).unwrap() }
}
3 changes: 3 additions & 0 deletions tests/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ fn main() {
builder.flag(flag);
}

// For assembly tests
println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());

builder.compile("libencode_utils.a");
}
137 changes: 137 additions & 0 deletions tests/src/bin/test_assembly.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//! A helper script for testing the assembly output.
//!
//! Similar to `trybuild` and `compiletest`, except specialized to our setup!

use cargo_metadata::Message;
use std::env;
use std::env::args;
use std::fmt::Write;
use std::fs;
use std::io;
use std::path::Path;
use std::process::{Command, Stdio};

fn strip_lines(data: &str, starts_with: &str) -> String {
data.lines()
.filter(|line| !line.trim_start().starts_with(starts_with))
.collect::<Vec<_>>()
.join("\n")
}

fn strip_section(data: &str, section: &str) -> String {
let mut res = String::with_capacity(data.len());
let mut in_removed_section = false;
for line in data.lines() {
// This only works for the __LLVM sections we're interested in
if line == "" {
in_removed_section = false;
}
if line.trim().starts_with(".section") {
if line.contains(section) {
in_removed_section = true;
write!(res, "; Stripped {section} section\n").unwrap();
} else {
in_removed_section = false;
}
}
if !in_removed_section {
res.push_str(line);
res.push('\n');
}
}
res
}

fn read_assembly<P: AsRef<Path>>(path: P) -> io::Result<String> {
let s = fs::read_to_string(path)?;
let workspace_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.as_os_str()
.to_str()
.unwrap();
let s = s.replace(workspace_dir, "$WORKSPACE");
let s = strip_lines(&s, ".cfi_");
let s = strip_lines(&s, ".macosx_version_");
let s = strip_lines(&s, ".ios_version_");
let s = strip_lines(&s, ".build_version");
// We remove the __LLVM,__bitcode and __LLVM,__cmdline sections because
// they're uninteresting for out use-case.
//
// See https://github.com/rust-lang/rust/blob/1.59.0/compiler/rustc_codegen_llvm/src/back/write.rs#L978-L1074
Ok(strip_section(&s, "__LLVM"))
}

fn main() {
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
let should_overwrite = option_env!("TEST_OVERWRITE").is_some();
let host = env!("TARGET");

for entry in manifest_dir.join("assembly").read_dir().unwrap() {
let package_path = entry.unwrap().path();
let package = package_path.file_name().unwrap().to_str().unwrap();

println!("Testing {package}.");

let result = Command::new(std::env::var("CARGO").unwrap_or("cargo".into()))
// .arg("+nightly")
// .arg("-Zbuild-std")
// .arg("-vv")
.arg("rustc")
.arg(format!("--package={package}"))
.args(args().skip(2))
.arg("--release")
.arg("--message-format=json-render-diagnostics")
.arg("--")
.arg("--emit=asm")
.arg("-Cllvm-args=--x86-asm-syntax=intel")
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.output()
.unwrap();

let artifact = Message::parse_stream(&*result.stdout)
.find_map(|message| {
if let Message::CompilerArtifact(artifact) = message.unwrap() {
// Brittle!
if artifact.target.name == package && artifact.filenames.len() == 2 {
let path = artifact.filenames[1].clone();
let stem = path.file_stem().unwrap().strip_prefix("lib").unwrap();
return Some(path.with_file_name(format!("{stem}.s")));
}
}
None
})
.unwrap_or_else(|| {
panic!(
"Could not find package data:\n{}",
String::from_utf8_lossy(&result.stdout)
)
});

// Very brittle!
let target = artifact
.components()
.map(|component| component.as_str())
.skip_while(|&component| component != "target")
.skip(1)
.next()
.unwrap_or(host);

println!("Target {target}.");

let expected_file = package_path.join("expected").join(format!("{target}.s"));

let actual = read_assembly(&artifact).unwrap();
if should_overwrite {
fs::write(expected_file, actual).unwrap();
} else if let Ok(expected) = read_assembly(expected_file) {
if expected != actual {
eprintln!("\n===Expected===\n{}\n===Actual===\n{}", expected, actual);
panic!("Expected and actual did not match.");
}
} else {
panic!("Missing assembly output for target {}:\n{}", target, actual);
}
}
}