From 5a9b91f041a09a0a402bcea9a5f4bfb95644225e Mon Sep 17 00:00:00 2001 From: Denys Date: Tue, 13 Jun 2017 00:26:13 +0200 Subject: [PATCH 1/3] Add Undefined References assertions --- .clang-format | 2 + .gitignore | 3 +- .gitmodules | 3 + Cargo.lock | 3 +- Cargo.toml | 6 +- README.md | 2 +- build.rs | 15 +++++ examples/undefined-ref-example/.gitignore | 2 + examples/undefined-ref-example/Cargo.toml | 8 +++ .../nvptx64-nvidia-cuda.json | 18 ++++++ examples/undefined-ref-example/src/lib.rs | 17 ++++++ llvm/external-refs.cpp | 36 ++++++++++++ llvm/headers | 1 + src/application/logging.rs | 17 +++++- src/application/main.rs | 12 ++-- src/error.rs | 4 ++ src/linker.rs | 29 ++++++---- src/llvm.rs | 53 +++++++++++++++++- .../codegen/inputs-undefined-ref/example.0.o | Bin 0 -> 1732 bytes tests/linker.rs | 22 ++++++++ 20 files changed, 230 insertions(+), 23 deletions(-) create mode 100644 .clang-format create mode 100644 .gitmodules create mode 100644 build.rs create mode 100644 examples/undefined-ref-example/.gitignore create mode 100644 examples/undefined-ref-example/Cargo.toml create mode 100644 examples/undefined-ref-example/nvptx64-nvidia-cuda.json create mode 100644 examples/undefined-ref-example/src/lib.rs create mode 100644 llvm/external-refs.cpp create mode 160000 llvm/headers create mode 100644 tests/codegen/inputs-undefined-ref/example.0.o diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5bead5f --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: LLVM + diff --git a/.gitignore b/.gitignore index eccd7b4..008e95c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -/target/ +target/ +.vscode/ **/*.rs.bk diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..925e2ac --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "llvm/headers"] + path = llvm/headers + url = https://github.com/denzp/rust-llvm-headers.git diff --git a/Cargo.lock b/Cargo.lock index b72799f..2fac5b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,12 +1,13 @@ [root] name = "ptx-linker" -version = "0.1.0" +version = "0.2.0" dependencies = [ "colored 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "cty 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "difference 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "fern 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index cafcf1b..f394999 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "ptx-linker" -version = "0.1.0" +version = "0.2.0" authors = ["Denys Zariaiev "] repository = "https://github.com/denzp/rust-ptx-linker" readme = "README.md" +build = "build.rs" [[bin]] name = "ptx-linker" @@ -24,6 +25,9 @@ error-chain = "0.10" difference = "1.0" tempdir = "0.3" +[build-dependencies] +gcc = "0.3" + [badges] travis-ci = { repository = "denzp/rust-ptx-linker", branch = "master" } appveyor = { repository = "denzp/rust-ptx-linker", branch = "master", service = "github" } \ No newline at end of file diff --git a/README.md b/README.md index e71bf75..3321b2a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Unfortunately, `--emit asm` can't link couple modules into a single PTX. From [d According to Rust [NVPTX metabug](https://github.com/rust-lang/rust/issues/38789) it's quite realistic to solve part of bugs within this repo: - [x] Non-inlined functions can't be used cross crate - [rust#38787](https://github.com/rust-lang/rust/issues/38787) -- [ ] No "undefined reference" error is raised when it should be - [rust#38786](https://github.com/rust-lang/rust/issues/38786) +- [x] No "undefined reference" error is raised when it should be - [rust#38786](https://github.com/rust-lang/rust/issues/38786) ## Approach diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..3d44c63 --- /dev/null +++ b/build.rs @@ -0,0 +1,15 @@ +extern crate gcc; + +fn main() { + gcc::Config::new() + .cpp(true) + .opt_level(0) + .include("llvm/headers") + .flag("-pedantic") + .flag("-Wall") + .flag("-Werror") + .flag("-D_GLIBCXX_USE_CXX11_ABI=0") + .file("llvm/external-refs.cpp") + .compile("librust-ptx-llvm-stuff.a"); +} + diff --git a/examples/undefined-ref-example/.gitignore b/examples/undefined-ref-example/.gitignore new file mode 100644 index 0000000..2c96eb1 --- /dev/null +++ b/examples/undefined-ref-example/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/examples/undefined-ref-example/Cargo.toml b/examples/undefined-ref-example/Cargo.toml new file mode 100644 index 0000000..c328455 --- /dev/null +++ b/examples/undefined-ref-example/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "example" +version = "0.1.0" +authors = ["Denys "] + +[lib] +crate_type = ["dylib"] + diff --git a/examples/undefined-ref-example/nvptx64-nvidia-cuda.json b/examples/undefined-ref-example/nvptx64-nvidia-cuda.json new file mode 100644 index 0000000..9c88391 --- /dev/null +++ b/examples/undefined-ref-example/nvptx64-nvidia-cuda.json @@ -0,0 +1,18 @@ +{ + "arch": "nvptx64", + "cpu": "sm_20", + "data-layout": "e-i64:64-v16:16-v32:32-n16:32:64", + "linker": "ptx-linker", + "linker-flavor": "ld", + "linker-is-gnu": true, + "dll-prefix": "", + "dll-suffix": ".ptx", + "dynamic-linking": true, + "llvm-target": "nvptx64-nvidia-cuda", + "max-atomic-width": 0, + "os": "cuda", + "obj-is-bitcode": true, + "panic-strategy": "abort", + "target-endian": "little", + "target-pointer-width": "64" +} \ No newline at end of file diff --git a/examples/undefined-ref-example/src/lib.rs b/examples/undefined-ref-example/src/lib.rs new file mode 100644 index 0000000..6f45bfc --- /dev/null +++ b/examples/undefined-ref-example/src/lib.rs @@ -0,0 +1,17 @@ +#![feature(abi_ptx, lang_items)] +#![no_std] + +extern "C" { + fn bar(); +} + +#[no_mangle] +pub unsafe extern "ptx-kernel" fn kernel() { + bar() +} + +// Needed because we compile `dylib`... +#[lang = "panic_fmt"] +fn panic_fmt() -> ! { + loop {} +} diff --git a/llvm/external-refs.cpp b/llvm/external-refs.cpp new file mode 100644 index 0000000..43cc0bb --- /dev/null +++ b/llvm/external-refs.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +std::string FindExternalReferences(const llvm::Module &module) { + std::ostringstream external_refs_stream; + + for (auto &function : module) { + if (function.isDeclaration()) { + external_refs_stream << function.getName().data() << ";"; + } + } + + return external_refs_stream.str(); +} + +// Returns `true` (in terms of LLVM C Api `true` == `1` to be correct) if some +// external references are found. Also writes semicolon (";") separated +// list to the `out_messages`. +extern "C" LLVMBool IsExternalReferencesExists(LLVMModuleRef module, + char **out_messages) { + auto module_ptr = llvm::unwrap(module); + auto external_refs = FindExternalReferences(*module_ptr); + + if (external_refs.size() == 0) { + return 0; + } + + // remove trailing ";" + external_refs.pop_back(); + + *out_messages = strdup(external_refs.c_str()); + + return 1; +} diff --git a/llvm/headers b/llvm/headers new file mode 160000 index 0000000..73aa2b6 --- /dev/null +++ b/llvm/headers @@ -0,0 +1 @@ +Subproject commit 73aa2b6f701a8738843e99bed452c8de594a1f18 diff --git a/src/application/logging.rs b/src/application/logging.rs index 3ce6699..cce758b 100644 --- a/src/application/logging.rs +++ b/src/application/logging.rs @@ -5,6 +5,19 @@ use fern::{Dispatch, FormatCallback}; use log::{LogLevel, LogLevelFilter, LogRecord}; use colored::*; +pub trait AlignedOutputString: ToString { + fn prefix_with_spaces(&self, spaces_count: usize) -> String { + let separator = String::from("\n") + &" ".repeat(spaces_count); + + self.to_string() + .split('\n') + .collect::>() + .join(&separator) + } +} + +impl AlignedOutputString for String {} + pub fn setup_logging() { Dispatch::new() .format(logging_handler) @@ -23,6 +36,8 @@ fn logging_handler(out: FormatCallback, message: &Arguments, record: &LogRecord) LogLevel::Error => format!("{}{}{}", "[".bold(), "ERROR".red().bold(), "]".bold()), }; - out.finish(format_args!("{} {}", level, message)); + let message = format!("{}", message); + + out.finish(format_args!("{} {}", level, message.prefix_with_spaces(8))); } diff --git a/src/application/main.rs b/src/application/main.rs index 35a8252..bbafa2d 100644 --- a/src/application/main.rs +++ b/src/application/main.rs @@ -7,7 +7,7 @@ extern crate colored; extern crate ptx_linker; mod logging; -use logging::setup_logging; +use logging::{AlignedOutputString, setup_logging}; use std::env; use ptx_linker::session::ArgsParser; @@ -21,7 +21,7 @@ fn main() { error!("{}", e); for e in e.iter().skip(1) { - error!(" caused by: {}", e); + error!(" caused by: {}", e.to_string().prefix_with_spaces(13)); } if let Some(backtrace) = e.backtrace() { @@ -36,6 +36,8 @@ fn run() -> Result<()> { let session = ArgsParser::new(env::args().skip(1)) .create_session() .chain_err(|| "Unable to create a session")?; - - Linker::new(session).link().chain_err(|| "Unable to link modules") -} \ No newline at end of file + + Linker::new(session) + .link() + .chain_err(|| "Unable to link modules") +} diff --git a/src/error.rs b/src/error.rs index 6113d3d..f9787aa 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,5 +8,9 @@ error_chain!{ NoOutputPathError { display("No output path is specified") } + + UndefinedReferences(references: Vec) { + display("Undefined references: {:?}", references) + } } } diff --git a/src/linker.rs b/src/linker.rs index b75a4f3..f6d1798 100644 --- a/src/linker.rs +++ b/src/linker.rs @@ -1,8 +1,7 @@ -use std::ptr; use std::fs::File; use std::io::{Read, BufReader}; use std::path::{Path, PathBuf}; -use std::ffi::{CStr, CString}; +use std::ffi::CString; use rustc_llvm::archive_ro::ArchiveRO; use cty::c_char; @@ -35,8 +34,18 @@ impl Linker { self.link_rlibs(); self.run_passes(); - // TODO: LLVMVerifyModule(mod, LLVMAbortProcessAction, &error); - // TODO: LLVMDisposeMessage(error); + unsafe { + let mut message = llvm::Message::new(); + if llvm::IsExternalReferencesExists(self.module, &mut message) == llvm::True { + let references: Vec = message + .to_string() + .split(";") + .map(|name| String::from(name)) + .collect(); + + return Err(ErrorKind::UndefinedReferences(references).into()); + } + } for output in &self.session.emit { match *output { @@ -107,7 +116,7 @@ impl Linker { } Configuration::Release => { - info!("Linking with Link Time Optimisations"); + info!("Linking with Link Time Optimisation"); llvm::LLVMPassManagerBuilderSetOptLevel(builder, 3); llvm::LLVMPassManagerBuilderPopulateLTOPassManager(builder, pass_manager, @@ -116,7 +125,6 @@ impl Linker { } } - llvm::LLVMPassManagerBuilderPopulateFunctionPassManager(builder, pass_manager); llvm::LLVMPassManagerBuilderPopulateModulePassManager(builder, pass_manager); llvm::LLVMPassManagerBuilderDispose(builder); @@ -133,15 +141,13 @@ impl Linker { unsafe { // TODO: check result - let mut message: *const c_char = ptr::null(); + let mut message = llvm::Message::new(); llvm::LLVMPrintModuleToFile(self.module, path.as_ptr(), &mut message); - if message != ptr::null() { + if !message.is_empty() { // TODO: stderr? - println!("{}", CStr::from_ptr(message).to_str().unwrap()); + println!("{}", message); } - - llvm::LLVMDisposeMessage(message); } info!("LLVM IR has been written to {:?}", path); @@ -227,3 +233,4 @@ impl Drop for Linker { } } } + diff --git a/src/llvm.rs b/src/llvm.rs index b4c837b..419f368 100644 --- a/src/llvm.rs +++ b/src/llvm.rs @@ -1,3 +1,6 @@ +use std::ptr; +use std::fmt; +use std::ffi::CStr; use cty::{c_char, c_uint}; pub use rustc_llvm::*; @@ -7,10 +10,11 @@ extern "C" { pub fn LLVMGetTarget(module: ModuleRef) -> *const c_char; pub fn LLVMDisposeMessage(message: *const c_char); + pub fn LLVMPrintModuleToFile(module: ModuleRef, file_path: *const c_char, - message_ptr: &mut *const c_char) - -> bool; + out_message: &mut Message) + -> Bool; pub fn LLVMPassManagerBuilderSetOptLevel(builder: PassManagerBuilderRef, opt_level: c_uint); pub fn LLVMAddStripDeadPrototypesPass(manager: PassManagerRef); @@ -20,4 +24,49 @@ extern "C" { pub fn LLVMInitializeNVPTXTarget(); pub fn LLVMInitializeNVPTXTargetMC(); pub fn LLVMInitializeNVPTXAsmPrinter(); + + /// Returns `llvm::True` if some external references are found. + /// Also writes semicolon (";") separated list to the `out_messages`. + /// + /// Defined in `llvm/external-refs.cpp` + pub fn IsExternalReferencesExists(module: ModuleRef, out_message: &mut Message) -> Bool; +} + +/// Convinient LLVM Message pointer wrapper. +/// Does not own the ptr, so we have to call `LLVMDisposeMessage` to free message memory. +#[repr(C)] +pub struct Message { + ptr: *const c_char, +} + +impl Message { + pub fn new() -> Self { + Message { ptr: ptr::null() } + } + + pub fn is_empty(&self) -> bool { + self.ptr == ptr::null() + } +} + +impl Drop for Message { + fn drop(&mut self) { + if !self.is_empty() { + unsafe { + LLVMDisposeMessage(self.ptr); + } + } + } } + +impl fmt::Display for Message { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if !self.is_empty() { + let contents = unsafe { CStr::from_ptr(self.ptr).to_str().unwrap() }; + write!(f, "{}", contents) + } else { + write!(f, "(empty)") + } + } +} + diff --git a/tests/codegen/inputs-undefined-ref/example.0.o b/tests/codegen/inputs-undefined-ref/example.0.o new file mode 100644 index 0000000000000000000000000000000000000000..791236de0f511582ba662ccc3f0fae37d529e38e GIT binary patch literal 1732 zcmZ`(drVVT7(bU@daqD#FWP18XuI1|4Q3#h>UdblN^en!)xb2)z2vq~Y$w!GYANVk zp~X{Nbyr8Wnz;=U++5}iJ`$J3Y&;YjQy7 z&iTIc`(DSwjof%y0YD}Iz#(G|wXc7_`2N+Mg;vZcihxcAz?T>R**y%%K>R{9i0-_7 zRSCRSN*l}fV-eY+Oj4!A-*<%Q>I}>NY)gbTD#}RZ>MWxrS|cbJZe{ckP#G;#HI$H} zKT}s#-yKVZZ|%l)E2Z^H0A4^gb4QrRPtb=6WJeH|utX;b1NpEd1UqVojsfTsU`aj^ zsDS}+r%HQwP#v1jGqa)wutrR$x!icTBEp&ax!jR)(@@xSnJZ6lx&0E;5N84p8Tq3+ z3f%%gExIr5M-Ojd5Z{N$d^8*N#O4!9f?=>L3by@lwPxuFjfSG9J z0}LuZ{!^0DOWwm;rDE1!V}ZQ#t@>v=IG6TL2RoaW}M*{BYgjnEX^~`uLU| zM^Euf|9s04Hm8zktAL*KUmKUHa%@6u-r`jgIQN}CUy?OHJaVAm@8iGPAH25d^Q%92 zI}fbA^j_-0$xk+Ybm{Gx3UCa?(uK?~BSY#lDRr{~tBi7Gk@1)%M05s;_IR7@Hjv+= zsfQtInarXc8`WGKR5!Dk>0{yCq@*&zRmLQh$zaUyCAO19XASgvi7u}s5QJU^s**X> zv_Pd;xrdeCoS|mwsAcpS%?+LUnocuj*EX|#rqk(MpOjRbMg<@-C1(@?p4jdpN?p*0 zXb5@*=;et3DxSxg$ZSUlHjuDQ34L+c8SgH*KSSN0q#lh>)2zgi7*tOUs*`DY-L5{b zQ=jS9oVQDV7Q}KYkEiLk<7NF^SyW=`A1^|e&i0sol9-0aW#`<`yAk@lVZVzA^3bn@ z{sE}ExK4f_$RE^EQv!9a8a46TxAgUnI{qnm%ffv7(Y|0bgL3VeSvTcfGlmO z$B)jo`x=W{+>WKb_9EBTR%cO@(^KTz*6uI#`h$fnZqF8{ugDp+w|ZNg?M2%>P0nVw z$Jtcqb2b+~6*sonJ)0SuZNnNSU({@50#0AMd#i^jURqqflr8kQH@o~TolJgdiE+8n z?sPa9BU@Zr$d;gg$r7v6v28O`>uKJ~Y?xDgY8d2^L~)N-!Bt})qt^*^i}emh&6zX_ zSP47M)93h1V5nyDy62tm-E-mSg z=*1{zS&0eMb0|I@XG_ByYfGP^E}4^5qjO&$?sgwE^o1DIwN_xo6g&r7^mw{&^Q1WF z;m!0iX(Xd!q@aqmgUJ|n49IB;mmuBeS-k39(7PP_5eHcDT;soqkBI%B#$-VAznGpw z>og_`SBd+5NZ_@5+>H&*t^WT&D}N@e)}^sc9SBW6!}b9tj7q?{UCb|5G9Jz z!!V3ySJO5nNSXg1muLlmHUw>g+3`bpZYW DYAP|O literal 0 HcmV?d00001 diff --git a/tests/linker.rs b/tests/linker.rs index fbd8a39..f8ffe3b 100644 --- a/tests/linker.rs +++ b/tests/linker.rs @@ -129,6 +129,28 @@ fn it_should_emit_correct_release_asm() { assert_files_equal(expected_output, reference_output); } +#[test] +fn it_should_reject_to_emit_when_undefined_ref_exists() { + let mut session = Session::default(); + let directory = TempDir::new("ptx-linker").unwrap(); + + let expected_output = directory.path().join("module-name.ptx"); + + session.emit = vec![Output::PTXAssembly]; + session.output = Some(expected_output.clone()); + session.configuration = Configuration::Debug; + session.link_bitcode(&PathBuf::from("tests/codegen/inputs-undefined-ref/example.0.o")); + + assert_eq!(expected_output.exists(), false); + let result = Linker::new(session).link(); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), + "Undefined references: [\"bar\"]"); + + assert_eq!(expected_output.exists(), false); +} + fn assert_files_equal(current_file_path: PathBuf, reference_file_path: PathBuf) { let mut current_file = BufReader::new(File::open(current_file_path).unwrap()); let mut ref_file = BufReader::new(File::open(reference_file_path).unwrap()); From 17cf6b491db4430903f89f2f34ec0b15c321cbab Mon Sep 17 00:00:00 2001 From: Denys Date: Wed, 14 Jun 2017 08:39:34 +0200 Subject: [PATCH 2/3] Add Examples readme --- examples/README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 examples/README.md diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..1af2055 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,30 @@ +# Examples + +To run the examples: +``` +xargo rustc --target nvptx64-nvidia-cuda --release +``` + +## Deep Dependencies Example: `deep-deps-example` +This example shows that the linker is actually "linking" :) + +The dependencies hierarchy is: +``` +example (is the CUDA kernel dylib crate) +└─ dummy_math (rlib crate) + └─ dummy_utils (rlib crate) +``` + +Both `dummy_math` and `dummy_utils` exports a function and also a kernel. + +A PTX output at `target/nvptx64-nvidia-cuda/release/example.ptx` should contain no `.extern` function declaration and have 3 kernels. + + +## Undefined Reference Example: `undefined-ref-example` +This example shows that the linker can find unresoved external references and reject linking because the output won't be a valid PTX. + +When you try to run the example the linker should fail with error message: +``` +[ERROR] Unable to link modules +[ERROR] caused by: Undefined references: ["bar"] +``` From e7e8a67a957cd713a02e5b0c1337185a37eebbbb Mon Sep 17 00:00:00 2001 From: Denys Date: Wed, 14 Jun 2017 09:00:01 +0200 Subject: [PATCH 3/3] Travis: build with both GCC and Clang --- .travis.yml | 34 ++++++++++++++++++++++++++++++---- appveyor.yml | 4 ++++ build.rs | 1 + 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index fbb81c2..1b8b37e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,32 @@ language: rust matrix: include: - - env: TARGET=x86_64-unknown-linux-gnu - rust: nightly + - rust: nightly + env: + - MATRIX_EVAL="export CXX=g++-6" + - TARGET=x86_64-unknown-linux-gnu + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-6 -before_install: set -e + - rust: nightly + env: + - MATRIX_EVAL="export CXX=clang++-5.0" + - TARGET=x86_64-unknown-linux-gnu + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-trusty + packages: + - clang-5.0 + +before_install: +- set -e +- eval "${MATRIX_EVAL}" script: - cargo check --target $TARGET @@ -15,6 +37,10 @@ after_script: set +e cache: cargo +branches: + only: + - master + notifications: email: - on_success: never \ No newline at end of file + on_success: never diff --git a/appveyor.yml b/appveyor.yml index baa8430..520740f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -39,5 +39,9 @@ notifications: - provider: Email on_build_success: false +branches: + only: + - master + # Building is done in the test phase, so we disable Appveyor's build phase. build: false diff --git a/build.rs b/build.rs index 3d44c63..3e55e88 100644 --- a/build.rs +++ b/build.rs @@ -8,6 +8,7 @@ fn main() { .flag("-pedantic") .flag("-Wall") .flag("-Werror") + .flag("-std=c++11") .flag("-D_GLIBCXX_USE_CXX11_ABI=0") .file("llvm/external-refs.cpp") .compile("librust-ptx-llvm-stuff.a");