diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80a54dc5c..d933a705c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 0dfd40efa..47ad4dc92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ members = [ "block2", "block-sys", "tests", + "tests/assembly/*", ] diff --git a/tests/Cargo.toml b/tests/Cargo.toml index f51bd605d..f65187d8e 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -18,6 +18,7 @@ build = "build.rs" # # Also, they're slower than most tests. ui = ["trybuild"] +assembly = ["cargo_metadata"] [dependencies] block2 = { path = "../block2" } @@ -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"] diff --git a/tests/assembly/test_msg_send_zero_cost/Cargo.toml b/tests/assembly/test_msg_send_zero_cost/Cargo.toml new file mode 100644 index 000000000..a25ae18f9 --- /dev/null +++ b/tests/assembly/test_msg_send_zero_cost/Cargo.toml @@ -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" } diff --git a/tests/assembly/test_msg_send_zero_cost/expected/aarch64-apple-darwin.s b/tests/assembly/test_msg_send_zero_cost/expected/aarch64-apple-darwin.s new file mode 100644 index 000000000..b195a041a --- /dev/null +++ b/tests/assembly/test_msg_send_zero_cost/expected/aarch64-apple-darwin.s @@ -0,0 +1,7 @@ + .section __TEXT,__text,regular,pure_instructions + .globl _handle + .p2align 2 +_handle: + b _objc_msgSend + +.subsections_via_symbols diff --git a/tests/assembly/test_msg_send_zero_cost/expected/aarch64-apple-ios-sim.s b/tests/assembly/test_msg_send_zero_cost/expected/aarch64-apple-ios-sim.s new file mode 100644 index 000000000..d73fd4f2a --- /dev/null +++ b/tests/assembly/test_msg_send_zero_cost/expected/aarch64-apple-ios-sim.s @@ -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 diff --git a/tests/assembly/test_msg_send_zero_cost/expected/aarch64-apple-ios.s b/tests/assembly/test_msg_send_zero_cost/expected/aarch64-apple-ios.s new file mode 100644 index 000000000..d73fd4f2a --- /dev/null +++ b/tests/assembly/test_msg_send_zero_cost/expected/aarch64-apple-ios.s @@ -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 diff --git a/tests/assembly/test_msg_send_zero_cost/expected/armv7-apple-ios.s b/tests/assembly/test_msg_send_zero_cost/expected/armv7-apple-ios.s new file mode 100644 index 000000000..f9b43ae49 --- /dev/null +++ b/tests/assembly/test_msg_send_zero_cost/expected/armv7-apple-ios.s @@ -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 diff --git a/tests/assembly/test_msg_send_zero_cost/expected/armv7s-apple-ios.s b/tests/assembly/test_msg_send_zero_cost/expected/armv7s-apple-ios.s new file mode 100644 index 000000000..abb037871 --- /dev/null +++ b/tests/assembly/test_msg_send_zero_cost/expected/armv7s-apple-ios.s @@ -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 diff --git a/tests/assembly/test_msg_send_zero_cost/expected/i386-apple-ios.s b/tests/assembly/test_msg_send_zero_cost/expected/i386-apple-ios.s new file mode 100644 index 000000000..f20f298e7 --- /dev/null +++ b/tests/assembly/test_msg_send_zero_cost/expected/i386-apple-ios.s @@ -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 diff --git a/tests/assembly/test_msg_send_zero_cost/expected/i686-apple-darwin.s b/tests/assembly/test_msg_send_zero_cost/expected/i686-apple-darwin.s new file mode 100644 index 000000000..f20f298e7 --- /dev/null +++ b/tests/assembly/test_msg_send_zero_cost/expected/i686-apple-darwin.s @@ -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 diff --git a/tests/assembly/test_msg_send_zero_cost/expected/x86_64-apple-darwin.s b/tests/assembly/test_msg_send_zero_cost/expected/x86_64-apple-darwin.s new file mode 100644 index 000000000..cd046e70f --- /dev/null +++ b/tests/assembly/test_msg_send_zero_cost/expected/x86_64-apple-darwin.s @@ -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 diff --git a/tests/assembly/test_msg_send_zero_cost/expected/x86_64-apple-ios.s b/tests/assembly/test_msg_send_zero_cost/expected/x86_64-apple-ios.s new file mode 100644 index 000000000..cd046e70f --- /dev/null +++ b/tests/assembly/test_msg_send_zero_cost/expected/x86_64-apple-ios.s @@ -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 diff --git a/tests/assembly/test_msg_send_zero_cost/lib.rs b/tests/assembly/test_msg_send_zero_cost/lib.rs new file mode 100644 index 000000000..9a29a4af4 --- /dev/null +++ b/tests/assembly/test_msg_send_zero_cost/lib.rs @@ -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() } +} diff --git a/tests/build.rs b/tests/build.rs index 2271e764a..ed907b019 100644 --- a/tests/build.rs +++ b/tests/build.rs @@ -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"); } diff --git a/tests/src/bin/test_assembly.rs b/tests/src/bin/test_assembly.rs new file mode 100644 index 000000000..73653571f --- /dev/null +++ b/tests/src/bin/test_assembly.rs @@ -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::>() + .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>(path: P) -> io::Result { + 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); + } + } +}