Skip to content

Commit

Permalink
Stub Out Linking with Object Files
Browse files Browse the repository at this point in the history
Adds CLI flag for specifying the link kind, and the architecture to
build our own `llvmext` module. This will be needed to write a given
module out as a native object file.
  • Loading branch information
iwillspeak committed Oct 3, 2020
1 parent 76a07ee commit 00f88d2
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 33 deletions.
251 changes: 247 additions & 4 deletions Cargo.lock

Large diffs are not rendered by default.

15 changes: 5 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,18 @@ categories = [ "parsing" ]
edition = "2018"

[features]

# bitcode link feature outputs intermediate files as bitcode rather than
# LLMV IR.
# FIXME: This should be a CLI flag
bitcode_link = []

default = [ "llvm-9" ]

[dependencies]
# FIXME: Want to upgrade to LLVM 10, but Clang doesn't seem capable of linking
# the IL or Bitcode output from it. Do we need to wait for LLVM 11? Is
# It time to stop relying on `cc` for linking?
llvm-9 = { package = "llvm-sys", version = "90", optional = true }
llvm-10 = { package = "llvm-sys", version = "100", optional = true, git = "https://gitlab.com/iwillspeak/llvm-sys.rs.git", branch = "macos-tbd-link" }
llvm-10 = { package = "llvm-sys", version = "100", optional = true }
docopt = "1.1"
serde = { version = "1.0", features = ["derive"] }
tempfile = "3.1"
failure = "0.1"
libc = "0.2"
indexmap = "1.5"

[build-dependencies]
cc = "1.0"
bindgen = "0.53"
23 changes: 23 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use std::env;
use std::path::PathBuf;

fn main() {
//build our library as C++ static lib
cc::Build::new()
.cpp(true)
.file("llvmext/lib.cpp")
.compile("libllvmext.a");

// Generate bindings for the library's exported types
let bindings = bindgen::Builder::default()
.header("llvmext/llvmext.hpp")
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate bindings");

// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
5 changes: 5 additions & 0 deletions llvmext/lib.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extern "C"
int LLVMExtWriteModuleToObjectFile()
{
return 0;
}
1 change: 1 addition & 0 deletions llvmext/llvmext.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extern "C" int LLVMExtWriteModuleToObjectFile();
3 changes: 2 additions & 1 deletion specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

Expectations = collections.namedtuple('Expectations', ['expects', 'failure_expects', 'skip_run'])

LINKER_KIND = "Object"
EXPECT_PATTERN = re.compile(r'#\s?=>\s?(.+)')
EXPECT_ERR_PATTERN = re.compile(r'#\s?!>\s?(.+)')
SKIP_PATTERN = re.compile(r'#\s?!!skip')
Expand Down Expand Up @@ -123,7 +124,7 @@ def run_spec(path):

expectations = parse_spec(path)
out = "specbin/{}".format(os.path.basename(path).split('.')[0])
compile_cmd = subprocess.Popen(["target/release/ullage", path, "-o", out], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
compile_cmd = subprocess.Popen(["target/release/ullage", path, "--link-kind", LINKER_KIND, "-o", out], stdout=subprocess.PIPE, stderr=subprocess.PIPE)

# Give the compiler 5 seconds to run, and return an error on timeout
timer = threading.Timer(5, compile_cmd.kill)
Expand Down
12 changes: 6 additions & 6 deletions src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::process::Command;
use tempfile::Builder;

pub use self::error::{CompError, CompResult};
pub use self::options::{CompilationOptions, OptimisationLevel};
pub use self::options::{CompilationOptions, OptimisationLevel, LinkKind};

pub mod error;
pub mod options;
Expand Down Expand Up @@ -106,10 +106,10 @@ impl Compilation {
module.verify_or_panic();

// Create a tempdir to write the LLVM IR or bitcode to
let suffix = if cfg!(feature="bitcode_link") {
".bc"
} else {
".ll"
let (suffix, kind) = match self.options.link_kind {
LinkKind::IL => (".il", OutputFileKind::LLVMIl),
LinkKind::Bitcode => (".bc", OutputFileKind::Bitcode),
LinkKind::Object => (".o", OutputFileKind::NativeObject),
};
let temp_file = Builder::new().prefix("ullage").suffix(suffix).tempfile()?;

Expand All @@ -123,7 +123,7 @@ impl Compilation {
if self.options.dump_ir {
module.dump();
}
module.write_to_file(temp_file.path())?;
module.write_to_file(temp_file.path(), kind)?;

// Shell out to Clang to link the final assembly
let output = Command::new("clang")
Expand Down
26 changes: 26 additions & 0 deletions src/compile/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! This module defines the options structure used to tweak
//! compilation output.
use serde::Deserialize;
use crate::low_loader::pass_manager as pm;

/// Compilation Options
Expand All @@ -14,6 +15,26 @@ pub struct CompilationOptions {
pub dump_ir: bool,
/// Optimisation level to use when emitting code
pub opt_level: OptimisationLevel,
/// How to perform the link
pub link_kind: LinkKind,
}

/// Kinds of linking supported by the compiler. Maybe LTO supported in
/// the future.
#[derive(Debug,Deserialize)]
pub enum LinkKind {
/// LLVM IL emit
IL,
/// LLVM Bitcode
Bitcode,
/// Object files
Object,
}

impl std::default::Default for LinkKind {
fn default() -> Self {
LinkKind::Bitcode
}
}

/// Optimisation levels
Expand Down Expand Up @@ -49,6 +70,11 @@ impl CompilationOptions {
pub fn with_opt_level(self, opt_level: OptimisationLevel) -> Self {
CompilationOptions { opt_level, ..self }
}

/// Set the link kind for the compiler
pub fn with_link_kind(self, link_kind: LinkKind) -> Self {
CompilationOptions { link_kind, ..self }
}
}

impl Default for OptimisationLevel {
Expand Down
3 changes: 2 additions & 1 deletion src/low_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub mod pass_manager;
pub mod targets;
pub mod types;
pub mod value;
pub mod llvmext;

/// Prelude Module
///
Expand All @@ -43,7 +44,7 @@ pub mod prelude {
pub use super::builder::Predicate;
pub use super::context::Context;
pub use super::function::{CallConvention, Function};
pub use super::module::Module;
pub use super::module::{Module, OutputFileKind};
pub use super::targets::{Target, TargetLookupError};
pub use super::types::Type;
pub use super::value::Value;
Expand Down
6 changes: 6 additions & 0 deletions src/low_loader/llvmext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(missing_docs)]

include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
34 changes: 23 additions & 11 deletions src/low_loader/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
use super::function::Function;
use super::llvm_sys::prelude::*;
use super::llvm_sys::{analysis, core, bit_writer};
use super::llvm_sys::{analysis, bit_writer, core};
use super::pass_manager::{OptLevel, OptSize, PassManagerBuilder};
use super::targets::Target;
use super::llvmext;

use std::ffi::{CStr, CString};
use std::path::Path;
Expand All @@ -22,6 +23,19 @@ pub struct Module {
raw: LLVMModuleRef,
}

/// The kind of output file to write
///
/// Used when writing modules to disk.
#[derive(Debug, PartialEq)]
pub enum OutputFileKind {
/// LLVM IL files
LLVMIl,
/// LLVM Bitcode file
Bitcode,
/// Native executable object files
NativeObject
}

impl Module {
/// Module from Raw
///
Expand Down Expand Up @@ -89,20 +103,18 @@ impl Module {
}

/// Write the Module to the Given File as LLVM IR or Bitcode
///
/// If the path's extension is `.ll` then the file is written as
/// LLVM IR, otherwise the file is written as bitcode.
pub fn write_to_file(&self, path: &Path) -> Result<(), String> {
let is_il = path.extension().map(|e| e == "ll").unwrap_or(false);
///
/// The kind of file written depends on `kind`.
pub fn write_to_file(&self, path: &Path, kind: OutputFileKind) -> Result<(), String> {
let path = path.to_str().and_then(|s| CString::new(s).ok()).unwrap();

unsafe {
let mut message = ptr::null_mut();
let r = if is_il {
core::LLVMPrintModuleToFile(self.raw, path.as_ptr(), &mut message)
} else {
bit_writer::LLVMWriteBitcodeToFile(self.raw, path.as_ptr())
};
let r = match kind {
OutputFileKind::LLVMIl => core::LLVMPrintModuleToFile(self.raw, path.as_ptr(), &mut message),
OutputFileKind::Bitcode => bit_writer::LLVMWriteBitcodeToFile(self.raw, path.as_ptr()),
OutputFileKind::NativeObject => llvmext::LLVMExtWriteModuleToObjectFile(),
};
if r == 0 {
Ok(())
} else {
Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Options:
-O, --optimise=<lvl> Set the compilation optimisation level.
0 = off, 1 = low, 2 = medium, 3 = high, s = size.
-o, --output=<out> Write the output to <out>.
--link-kind=<type> Set the link type to perform.
--target=<triple> Set the compilation target triple.
--dumpir Dump the LLVM IR for the module.
--dumpast Dump the syntax tree to stdout and exit.
Expand All @@ -57,6 +58,7 @@ struct Args {
flag_output: Option<String>,
flag_optimise: Option<OptFlag>,
flag_target: Option<String>,
flag_link_kind: Option<LinkKind>,
arg_file: Option<String>,

// TODO: maybe move these dump options into a single flag?
Expand Down Expand Up @@ -202,6 +204,7 @@ fn main() {

let options = CompilationOptions::default()
.with_dump_ir(args.flag_dumpir)
.with_link_kind(args.flag_link_kind.unwrap_or_default())
.with_opt_level(
args.flag_optimise
.map_or(OptimisationLevel::Off, |o| o.into()),
Expand Down

0 comments on commit 00f88d2

Please sign in to comment.