Skip to content
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

Abort linking if "Undefined external references" are found #2

Merged
merged 3 commits into from
Jun 14, 2017
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
2 changes: 2 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
BasedOnStyle: LLVM

3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target/
target/
.vscode/
**/*.rs.bk
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "llvm/headers"]
path = llvm/headers
url = https://github.com/denzp/rust-llvm-headers.git
34 changes: 30 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -15,6 +37,10 @@ after_script: set +e

cache: cargo

branches:
only:
- master

notifications:
email:
on_success: never
on_success: never
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
[package]
name = "ptx-linker"
version = "0.1.0"
version = "0.2.0"
authors = ["Denys Zariaiev <denys.zariaiev@gmail.com>"]
repository = "https://github.com/denzp/rust-ptx-linker"
readme = "README.md"
build = "build.rs"

[[bin]]
name = "ptx-linker"
Expand All @@ -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" }
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 16 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
extern crate gcc;

fn main() {
gcc::Config::new()
.cpp(true)
.opt_level(0)
.include("llvm/headers")
.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");
}

30 changes: 30 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -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"]
```
2 changes: 2 additions & 0 deletions examples/undefined-ref-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target/
Cargo.lock
8 changes: 8 additions & 0 deletions examples/undefined-ref-example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "example"
version = "0.1.0"
authors = ["Denys <denys.zariaiev@gmail.com>"]

[lib]
crate_type = ["dylib"]

18 changes: 18 additions & 0 deletions examples/undefined-ref-example/nvptx64-nvidia-cuda.json
Original file line number Diff line number Diff line change
@@ -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"
}
17 changes: 17 additions & 0 deletions examples/undefined-ref-example/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 {}
}
36 changes: 36 additions & 0 deletions llvm/external-refs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <llvm-c/Core.h>
#include <llvm/IR/Function.h>
#include <llvm/IR/Module.h>
#include <sstream>

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;
}
1 change: 1 addition & 0 deletions llvm/headers
Submodule headers added at 73aa2b
17 changes: 16 additions & 1 deletion src/application/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>()
.join(&separator)
}
}

impl AlignedOutputString for String {}

pub fn setup_logging() {
Dispatch::new()
.format(logging_handler)
Expand All @@ -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)));
}

12 changes: 7 additions & 5 deletions src/application/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand All @@ -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")
}

Linker::new(session)
.link()
.chain_err(|| "Unable to link modules")
}
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@ error_chain!{
NoOutputPathError {
display("No output path is specified")
}

UndefinedReferences(references: Vec<String>) {
display("Undefined references: {:?}", references)
}
}
}
29 changes: 18 additions & 11 deletions src/linker.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<String> = message
.to_string()
.split(";")
.map(|name| String::from(name))
.collect();

return Err(ErrorKind::UndefinedReferences(references).into());
}
}

for output in &self.session.emit {
match *output {
Expand Down Expand Up @@ -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,
Expand All @@ -116,7 +125,6 @@ impl Linker {
}
}

llvm::LLVMPassManagerBuilderPopulateFunctionPassManager(builder, pass_manager);
llvm::LLVMPassManagerBuilderPopulateModulePassManager(builder, pass_manager);
llvm::LLVMPassManagerBuilderDispose(builder);

Expand All @@ -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);
Expand Down Expand Up @@ -227,3 +233,4 @@ impl Drop for Linker {
}
}
}

Loading