1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
//! build.rs utils for building LLVM-IR and bytecode
//!
//! Building assembly in a cross platform manner is a very painful endeavour requiring to be aware
//! of various assemblers available and syntaxes they use. Using LLVM-IR as *the* assembly is a
//! better alternative and this crate provides tools converting your `.ll` or `.bc` files into `.a`
//! archives containing machine code. These archives can then be statically linked to your project.
//!
//! This library does not need an installation of LLVM or `ar` to work. The LLVM which comes with
//! rustc is used instead. While this library works perfectly on stable versions of the compiler,
//! the library may be incompatible with a future version of rustc.
//!
//! # Usage
//!
//! First, you’ll want to both add a build script for your crate (`build.rs`) and also add this
//! crate to your `Cargo.toml` via:
//!
//! ```toml
//! [package]
//! # ...
//! build = "build.rs"
//!
//! [build-dependencies]
//! llvm_build_utils = "0.3"
//! ```
//!
//! Then write your `build.rs` like this:
//!
//! ```rust,no_run
//! extern crate llvm_build_utils;
//! use llvm_build_utils::*;
//!
//! fn main() {
//!     build_archive("libyourthing.a", &[
//!         ("input.ll", BuildOptions {
//!             ..BuildOptions::default() // customise how the file is built
//!         })
//!     ]).expect("error happened").print();
//! }
//! ```
//!
//! Running a `cargo build` should produce `libyourthing.a` which then may be linked to your Rust
//! executable/library.
#![allow(non_camel_case_types, non_upper_case_globals)]
extern crate libc;
extern crate mktemp;
extern crate target_build_utils;

use std::path::Path;
use std::ffi::{CString, CStr, OsString, OsStr};
use std::sync::{Once, ONCE_INIT};

type LLVMBool = libc::c_uint;
const LLVMTrue: LLVMBool = 1;
const LLVMFalse: LLVMBool = 0;
#[allow(missing_copy_implementations)]
enum LLVMContext_opaque {}
type LLVMContextRef = *mut LLVMContext_opaque;
#[allow(missing_copy_implementations)]
enum LLVMMemoryBuffer_opaque {}
type LLVMMemoryBufferRef = *mut LLVMMemoryBuffer_opaque;
#[allow(missing_copy_implementations)]
enum LLVMModule_opaque {}
type LLVMModuleRef = *mut LLVMModule_opaque;
#[allow(missing_copy_implementations)]
enum LLVMTarget_opaque {}
type LLVMTargetRef = *mut LLVMTarget_opaque;
enum LLVMTargetMachine_opaque {}
type LLVMTargetMachineRef = *mut LLVMTargetMachine_opaque;
enum LLVMArchiveChild_opaque {}
type LLVMArchiveChildRef = *mut LLVMArchiveChild_opaque;
#[allow(missing_copy_implementations)]
enum LLVMRustArchiveMember_opaque {}
type LLVMRustArchiveMemberRef = *mut LLVMRustArchiveMember_opaque;

extern {
    fn LLVMContextCreate() -> LLVMContextRef;
    fn LLVMContextDispose(C: LLVMContextRef);
    fn LLVMParseIRInContext(context: LLVMContextRef,
                            buf: LLVMMemoryBufferRef,
                            om: *mut LLVMModuleRef,
                            msg: *mut *mut libc::c_char) -> LLVMBool;
    // fn LLVMDisposeMemoryBuffer(MemBuf: LLVMMemoryBufferRef);
    fn LLVMSetTarget(M: LLVMModuleRef, Triple: *const libc::c_char);
    fn LLVMDisposeModule(M: LLVMModuleRef);
    fn LLVMVerifyModule(_: LLVMModuleRef, _: VerifierFailureAction, _: *mut *mut libc::c_char)
    -> LLVMBool;
    fn LLVMDisposeMessage(_: *mut libc::c_char);
    fn LLVMCreateTargetMachine(tr: LLVMTargetRef,
                               triple: *const libc::c_char,
                               cpu: *const libc::c_char,
                               features: *const libc::c_char,
                               lvl: Optimisation,
                               reloc: Relocations,
                               cm: CodegenModel) -> LLVMTargetMachineRef;
    fn LLVMDisposeTargetMachine(_: LLVMTargetMachineRef);
    fn LLVMTargetMachineEmitToFile (_: LLVMTargetMachineRef,
                                            _: LLVMModuleRef,
                                            filename: *const libc::c_char,
                                            _: CodeGenFileType,
                                            err: *mut *mut libc::c_char) -> LLVMBool;
    fn LLVMGetTargetFromTriple(triple: *const libc::c_char,
                               _: *mut LLVMTargetRef,
                               err: *mut *mut libc::c_char) -> LLVMBool;

    // Unstable Rust’s LLVM bindings
    fn LLVMRustCreateMemoryBufferWithContentsOfFile(Path: *const libc::c_char)
                                                    -> LLVMMemoryBufferRef;
    fn LLVMRustGetLastError() -> *const libc::c_char;
    fn LLVMRustArchiveMemberNew(_: *const libc::c_char,
                                _: *const libc::c_char,
                                _: LLVMArchiveChildRef) -> LLVMRustArchiveMemberRef;
    fn LLVMRustArchiveMemberFree(_: LLVMRustArchiveMemberRef);
    fn LLVMRustWriteArchive(Dst: *const libc::c_char,
                            NumMembers: libc::size_t,
                            Members: *const LLVMRustArchiveMemberRef,
                            WriteSymbtab: bool,
                            Kind: ArchiveKind) -> libc::c_int;
}

#[allow(dead_code)]
#[repr(C)]
enum VerifierFailureAction {
    AbortProcess = 0,
    PrintMessage = 1,
    ReturnStatus = 2,
}

#[allow(dead_code)]
#[repr(C)]
enum CodeGenFileType {
    Assembly = 0,
    Object = 1,
}


/// Relocation mode
///
/// This option decides how relocations are handled.
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(C)]
pub enum Relocations {
    /// Target default relocation model
    Default = 0,
    /// Non-relocatable code
    Static = 1,
    /// Fully relocatable, position independent code
    PIC = 2,
    /// Relocatable external references, non-relocatable code
    DynamicNoPic = 3,
}

/// Codegen model
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub enum CodegenModel {
    /// Target default code model
    Default = 0,
    /// Small code model
    Small = 2,
    /// Kernel code model
    Kernel = 3,
    /// Medium code model
    Medium = 4,
    /// Large code model
    Large = 5,
}

/// Codegen optimisation level
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(C)]
pub enum Optimisation {
    /// No codegen optimisation
    ///
    /// Corresponds to the -O0 option of `llc`
    O0 = 0,
    /// Some codegen optimisations
    ///
    /// Corresponds to the -O1 option of `llc`
    O1 = 1,
    /// Considerable codegen optimisations
    ///
    /// Corresponds to the -O2 option of `llc`
    O2 = 2,
    /// Heavy codegen optimisations
    ///
    /// Corresponds to the -O3 option of `llc`
    O3 = 3,
}

/// The format of generated archive file
#[repr(C)]
#[derive(Copy, Clone)]
pub enum ArchiveKind {
    /// GNU archive format, default in majority of cases
    Gnu,
    /// MIPS64 archive format
    Mips64,
    /// BSD archive format, default on OS X and iOS systems
    Bsd,
    /// COFF archive format
    Coff,
}

impl Default for ArchiveKind {
    /// Gets the default ArchiveKind depending on `TARGET` variable
    ///
    /// If the `TARGET` envronment variable is not set or the target spec cannot be parsed for some
    /// reason, this function will panic.
    fn default() -> ArchiveKind {
        let target_info = target_build_utils::TargetInfo::new()
            .expect("could not parse target info");
        match target_info.target_os() {
            "macos" | "ios" => ArchiveKind::Bsd,
            _ => ArchiveKind::Gnu
        }
    }
}

#[derive(Debug)]
pub struct BuildOptions {
    /// Target triple to generate machine code for
    ///
    /// The target triple has the general format `<arch><sub>-<vendor>-<sys>-<abi>`, where:
    ///
    /// * `<arch>`   x86, arm, thumb, mips, etc.
    /// * `<sub>`    for example on ARM: v5, v6m, v7a, v7m, etc.
    /// * `<vendor>` pc, apple, nvidia, ibm, etc.
    /// * `<sys>`    none, linux, win32, darwin, cuda, etc.
    /// * `<abi>`    eabi, gnu, android, macho, elf, etc.
    ///
    /// *Defaults* to `$TARGET` environment variable, if set (always is in cargo build scripts).
    ///
    /// Corresponds to the `-mtriple` option of `llc`.
    pub triple: String,
    /// Target CPU to generate machine code for
    ///
    /// *Default* is chosen depending on the target `triple`.
    ///
    /// Corresponds to the `-mcpu` option of `llc`.
    pub cpu: String,
    /// Capabilities of the target code is generated for
    ///
    /// Format of this field is the same as the format for `-mattr` option: +feature enables a
    /// feature, -feature disables it. Each feature is delimited by a comma.
    ///
    /// Sample string: `+sse,+sse2,+sse3,-avx`.
    ///
    /// *Default* is chosen depending on the target `triple`.
    ///
    /// Corresponds to the `-mattr` option of `llc`.
    pub attr: String,
    /// Code generation
    ///
    /// *Defaults* to `CodegenModel::Default`.
    ///
    /// Corresponds to the `-code-model` option of `llc`.
    pub model: CodegenModel,
    /// Relocation model
    ///
    /// *Defaults* to `Relocations::Default`.
    ///
    /// Corresponds to the `-relocation-model` option of `llc`.
    pub reloc: Relocations,
    /// Code optimisation level
    ///
    /// *Defaults* to the same level as specified in the `$OPT_LEVEL` environment variable (set by
    /// cargo) and `Optimisation::O0` if not set.
    ///
    /// Corresponds to the `-O` option of `llc`.
    pub opt: Optimisation,
    /// Name of the archive section to insert generated object into
    pub ar_section_name: String,
}

impl Default for BuildOptions {
    fn default() -> BuildOptions {
        use std::env::var;
        BuildOptions {
            triple: var("TARGET").unwrap_or(String::new()),
            cpu: String::new(),
            attr: String::new(),
            model: CodegenModel::Default,
            reloc: Relocations::Default,
            opt: match var("OPT_LEVEL").ok().and_then(|v| v.parse().ok()).unwrap_or(0u64) {
                0 => Optimisation::O0,
                1 => Optimisation::O1,
                2 => Optimisation::O2,
                3 | _ => Optimisation::O3,
            },
            ar_section_name: String::new(),
        }
    }
}

/// Output for cargo
pub struct Printout {
    libname: String,
    outdir: OsString,
    deps: Vec<String>
}

impl Printout {
    /// Inform cargo about the outcome of compilation
    ///
    /// Information cargo receives:
    ///
    /// * What library to link to (`print_link`);
    /// * Where to look for the library in question (`print_path`);
    /// * List of dependencies which trigger the rebuild (`print_deps`).
    ///
    /// All of these may also be printed separately via other methods on this struct.
    pub fn print(mut self) {
        self.print_link();
        self.print_path();
        self.print_deps();
    }

    /// Inform cargo of the library to link to
    pub fn print_link(&mut self) {
        let name = ::std::mem::replace(&mut self.libname, String::new());
        if !name.is_empty() {
            println!("cargo:rustc-link-lib={}", name);
        }
    }

    /// Inform cargo of the location where built library resides
    ///
    /// May panic if the path to output directory is not valid unicode.
    pub fn print_path(&mut self) {
        let outdir = ::std::mem::replace(&mut self.outdir, OsString::new());
        if !outdir.is_empty() {
            let od = outdir.into_string().expect("outdir contains invalid unicode");
            println!("cargo:rustc-link-search=native={}", od);
        }
    }

    /// Inform cargo of the dependencies which should trigger a rebuild
    pub fn print_deps(&mut self) {
        let deps = ::std::mem::replace(&mut self.deps, Vec::new());
        for dep in deps {
            println!("cargo:rerun-if-changed={}", dep);
        }
    }
}



fn initialize_llvm() {
    static ONCE: Once = ONCE_INIT;

    macro_rules! init_target(
        ($($method:ident),*) => { {
            extern { $(fn $method();)* }
            $($method();)*
        } }
    );

    ONCE.call_once(|| unsafe {
        init_target!(LLVMInitializeX86TargetInfo,
                     LLVMInitializeX86Target,
                     LLVMInitializeX86TargetMC,
                     LLVMInitializeX86AsmPrinter,
                     LLVMInitializeX86AsmParser,
                     LLVMInitializeARMTargetInfo,
                     LLVMInitializeARMTarget,
                     LLVMInitializeARMTargetMC,
                     LLVMInitializeARMAsmPrinter,
                     LLVMInitializeARMAsmParser,
                     LLVMInitializeAArch64TargetInfo,
                     LLVMInitializeAArch64Target,
                     LLVMInitializeAArch64TargetMC,
                     LLVMInitializeAArch64AsmPrinter,
                     LLVMInitializeAArch64AsmParser,
                     LLVMInitializeMipsTargetInfo,
                     LLVMInitializeMipsTarget,
                     LLVMInitializeMipsTargetMC,
                     LLVMInitializeMipsAsmPrinter,
                     LLVMInitializeMipsAsmParser,
                     LLVMInitializePowerPCTargetInfo,
                     LLVMInitializePowerPCTarget,
                     LLVMInitializePowerPCTargetMC,
                     LLVMInitializePowerPCAsmPrinter,
                     LLVMInitializePowerPCAsmParser);
    });
}

macro_rules! fail_if {
    ($ex: expr, $($args: tt)*) => {
        if $ex { return Err(format!($($args)*)) }
    }
}

/// Produce a static library (archive) containing machine code
///
/// The input files must be well formed LLVM-IR files or LLVM bytecode. Format of the input file
/// is autodetected.
pub fn build_archive<'a, P: 'a, I>(archive: P, iter: I)
-> Result<Printout, String>
where P: AsRef<Path>, I: IntoIterator<Item=&'a (P, BuildOptions)> {
    build_archive_kind(ArchiveKind::default(), archive, iter)
}

/// Produce a static library (archive) in specific format
///
/// The input files must be well formed LLVM-IR files or LLVM bytecode. Format of the input file
/// is autodetected.
pub fn build_archive_kind<'a, P: 'a, I>(format: ArchiveKind, archive: P, iter: I)
-> Result<Printout, String>
where P: AsRef<Path>, I: IntoIterator<Item=&'a (P, BuildOptions)>
{
    initialize_llvm();
    let mut strings = vec![];
    let mut members = vec![];
    let mut temps = vec![];
    let mut deps = vec![];
    let outpath = ::std::env::var_os("OUT_DIR").unwrap_or_default();
    let libstem = {
        fail_if!(archive.as_ref().extension() != Some(OsStr::new("a")), "extension must be .a");
        let libstem = try!(archive.as_ref().file_stem().and_then(|s| s.to_str()).ok_or_else(||
                           String::from("output filename has invalid stem")));
        fail_if!(!libstem.starts_with("lib"), "output filename must start with lib");
        String::from(&libstem[3..])
    };

    unsafe {
        let ctx = LLVMContextCreate();
        fail_if!(ctx.is_null(), "could not create the context");
        for &(ref p, ref opt) in iter {
            let mut module = ::std::ptr::null_mut();
            let mut msg = ::std::ptr::null_mut();

            let input_str = try!(p.as_ref().to_str().ok_or_else(||
                                 String::from("input filename is not utf-8")));
            deps.push(String::from(input_str));
            // Read the LLVM-IR/BC into memory
            let path = try!(CString::new(input_str).map_err(|_|
                            String::from("input filename contains nulls")));
            let buf = LLVMRustCreateMemoryBufferWithContentsOfFile(path.as_ptr());
            fail_if!(buf.is_null(), "could not open input file {:?}: {:?}",
                    p.as_ref(), CStr::from_ptr(LLVMRustGetLastError()));

            // Parse the IR/BC
            LLVMParseIRInContext(ctx, buf, &mut module, &mut msg);
            fail_if!(module.is_null(), "module could not be parsed successfully: {:?}",
                     CStr::from_ptr(msg));
            if LLVMVerifyModule(module, VerifierFailureAction::ReturnStatus, &mut msg) == LLVMTrue {
                let ret = Err(format!("Module is not valid: {:?}", CStr::from_ptr(msg)));
                LLVMDisposeMessage(msg);
                return ret;
            }

            // Build the IR/BC to object file
            let triple = CString::new(opt.triple.clone()).expect("triple contains null bytes");
            let cpu = CString::new(opt.cpu.clone()).expect("cpu contains null bytes");
            let attr = CString::new(opt.attr.clone()).expect("attr contains null bytes");
            if !opt.triple.is_empty() {
                LLVMSetTarget(module, triple.as_ptr());
            }
            let mut target = ::std::ptr::null_mut();
            let status = LLVMGetTargetFromTriple(triple.as_ptr(), &mut target, &mut msg);
            fail_if!(status != LLVMFalse, "could not generate target from triple {}: {:?}",
                     opt.triple, CStr::from_ptr(msg));
            let machine = LLVMCreateTargetMachine(target,
                                                  triple.as_ptr(),
                                                  cpu.as_ptr(),
                                                  attr.as_ptr(),
                                                  opt.opt,
                                                  opt.reloc,
                                                  opt.model);
            fail_if!(machine.is_null(), "could not create the target machine \
                                         (likely invalid BuildOptions {:?})", opt);


            let tmp = try!(mktemp::Temp::new_file_in(
                p.as_ref().parent().expect("cannot get basename of input filename")
            ).map_err(|e| format!("could not create temp file: {}", e)));
            let object_file = try!(CString::new(
                try!(tmp.as_ref().to_str().ok_or_else(|| String::from("object path is not utf-8")))
            ).map_err(|_| String::from("object filename contains nulls")));
            temps.push(tmp);

            let status = LLVMTargetMachineEmitToFile(machine,
                                                     module,
                                                     object_file.as_ptr(),
                                                     CodeGenFileType::Object,
                                                     &mut msg);
            fail_if!(status == LLVMTrue, "could not generate object file: {:?}",
                    CStr::from_ptr(msg));

            // Put the built objects into an archive
            let name = try!(CString::new(opt.ar_section_name.clone())
                            .map_err(|_| String::from("archive member name contains nulls")));
            members.push(LLVMRustArchiveMemberNew(object_file.as_ptr(),
                                                  name.as_ptr(),
                                                  std::ptr::null_mut()));
            strings.push(name);
            strings.push(object_file);

            LLVMDisposeTargetMachine(machine);
            LLVMDisposeModule(module);
            // FIXME: SIGSEGVS for some reason
            // LLVMDisposeMemoryBuffer(buf);
        }
        let out_target = Path::new(&outpath).join(archive);
        let libname_str = try!(out_target.to_str().ok_or_else(||
                               String::from("archive filename is not utf-8")));
        let dest = try!(CString::new(libname_str).map_err(|_|
                        String::from("output file has interior nulls")));
        let r = LLVMRustWriteArchive(dest.as_ptr(),
                                     members.len() as libc::size_t,
                                     members.as_ptr(),
                                     true,
                                     format);
        fail_if!(r != 0, "{:?}", {
            let err = LLVMRustGetLastError();
            if err.is_null() {
                "failed to write archive".to_string()
            } else {
                String::from_utf8_lossy(CStr::from_ptr(err).to_bytes())
                        .into_owned()
            }
        });
        for member in members {
            LLVMRustArchiveMemberFree(member);
        }
        LLVMContextDispose(ctx);


        Ok(Printout {
            libname: libstem,
            outdir: outpath,
            deps: deps
        })
    }
}