Commit c9b13533 authored by qinsoon's avatar qinsoon

linkutils (previously as testutils)

parent ca0d77bf
......@@ -37,4 +37,4 @@ pub extern crate gc;
pub mod vm;
pub mod compiler;
pub mod runtime;
pub mod testutil;
\ No newline at end of file
pub mod linkutils;
\ No newline at end of file
......@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use testutil::*;
use linkutils::*;
use ast::ir::MuName;
use runtime;
use vm::VM;
......@@ -20,10 +20,67 @@ use compiler::backend;
use std::path::PathBuf;
use std::process::Command;
use std::process::Output;
/// links generated code for the given functions, static library of Zebu,
/// and a main function to produce an executable of the given name
pub fn link_primordial (funcs: Vec<MuName>, out: &str, vm: &VM) -> PathBuf {
let emit_dir = PathBuf::from(&vm.vm_options.flag_aot_emit_dir);
// prepare a list of files that need to be compiled and linked together
let files : Vec<PathBuf> = {
use std::fs;
let mut ret = vec![];
// all interested mu funcs
for func in funcs {
ret.push(get_path_for_mu_func(func, vm));
}
// mu context
ret.push(get_path_for_mu_context(vm));
// copy primoridal entry
let source = get_path_under_zebu(runtime::PRIMORDIAL_ENTRY);
let dest = {
let mut ret = PathBuf::from(&vm.vm_options.flag_aot_emit_dir);
ret.push("main.c");
ret
};
trace!("copying from {:?} to {:?}", source, dest);
match fs::copy(source.as_path(), dest.as_path()) {
Ok(_) => {},
Err(e) => panic!("failed to copy: {}", e)
}
// include the primordial C main
ret.push(dest);
// include mu static lib
ret.push(get_path_under_zebu(if cfg!(debug_assertions) {
"target/debug/libmu.a"
} else {
"target/release/libmu.a"
}));
ret
};
let mut out_path = emit_dir.clone();
out_path.push(out);
link_executable_internal(files,
&vm.vm_options.flag_bootimage_external_lib,
&vm.vm_options.flag_bootimage_external_libpath,
out_path)
}
/// invokes the C compiler to link code into an executable
fn link_executable_internal (files: Vec<PathBuf>, lib: &Vec<String>, libpath: &Vec<String>, out: PathBuf) -> PathBuf {
let mut cc = Command::new(get_test_clang_path());
info!("output as {:?}", out.as_path());
let mut cc = Command::new(get_c_compiler());
// external libs
for path in libpath.iter() {
......@@ -33,7 +90,7 @@ fn link_executable_internal (files: Vec<PathBuf>, lib: &Vec<String>, libpath: &V
cc.arg(format!("-l{}", l));
}
info!("output as {:?}", out.as_path());
// dylibs used for linux
if cfg!(target_os = "linux") {
cc.arg("-ldl");
cc.arg("-lrt");
......@@ -41,29 +98,69 @@ fn link_executable_internal (files: Vec<PathBuf>, lib: &Vec<String>, libpath: &V
cc.arg("-lpthread");
}
// This needs to be at the bottom due to the linkage of librodall_alloc.a
// all the source code
for file in files {
info!("link with {:?}", file.as_path());
cc.arg(file.as_path());
}
// so we can find symbols in itself
// flag to allow find symbols in the executable
cc.arg("-rdynamic");
// specified output
cc.arg("-o");
cc.arg(out.as_os_str());
assert!(exec(cc).status.success());
// execute and check results
assert!(exec_cmd(cc).status.success(), "failed to link code");
out
}
/// links generated code for the given functions to produce a dynamic
/// library of the given name
pub fn link_dylib (funcs: Vec<MuName>, out: &str, vm: &VM) -> PathBuf {
link_dylib_with_extra_srcs(funcs, vec![], out, vm)
}
/// links generated code for the given functions with a few external sources
/// to produce a dynamic library of the given name
pub fn link_dylib_with_extra_srcs(funcs: Vec<MuName>, srcs: Vec<String>, out: &str, vm: &VM) -> PathBuf{
let files = {
let mut ret = vec![];
for func in funcs {
ret.push(get_path_for_mu_func(func, vm));
}
for src in srcs {
ret.push(PathBuf::from(src));
}
ret.push(get_path_for_mu_context(vm));
ret
};
let mut out_path = PathBuf::from(&vm.vm_options.flag_aot_emit_dir);
out_path.push(out);
link_dylib_internal(files,
&vm.vm_options.flag_bootimage_external_lib,
&vm.vm_options.flag_bootimage_external_libpath,
out_path)
}
/// invokes the C compiler to link code into a dynamic library
fn link_dylib_internal (files: Vec<PathBuf>, lib: &Vec<String>, libpath: &Vec<String>, out: PathBuf) -> PathBuf {
let mut object_files : Vec<PathBuf> = vec![];
// compile each single source file
for file in files {
let mut cc = Command::new(get_test_clang_path());
let mut cc = Command::new(get_c_compiler());
// output object file
cc.arg("-c");
// position independent code
cc.arg("-fPIC");
let mut out = file.clone();
......@@ -74,10 +171,11 @@ fn link_dylib_internal (files: Vec<PathBuf>, lib: &Vec<String>, libpath: &Vec<St
cc.arg(out.as_os_str());
object_files.push(out);
exec(cc);
exec_cmd(cc);
}
let mut cc = Command::new(get_test_clang_path());
// link object files into a dynamic library
let mut cc = Command::new(get_c_compiler());
// external libs
for path in libpath.iter() {
......@@ -87,11 +185,15 @@ fn link_dylib_internal (files: Vec<PathBuf>, lib: &Vec<String>, libpath: &Vec<St
cc.arg(format!("-l{}", l));
}
// options
// options:
// shared library
cc.arg("-shared");
// position independent code
cc.arg("-fPIC");
// allow undefined symbol
cc.arg("-Wl");
cc.arg("-undefined");
// allow dynamic lookup symbols
cc.arg("dynamic_lookup");
// all object files
......@@ -103,11 +205,73 @@ fn link_dylib_internal (files: Vec<PathBuf>, lib: &Vec<String>, libpath: &Vec<St
cc.arg("-o");
cc.arg(out.as_os_str());
exec(cc);
exec_cmd(cc);
out
}
/// builds a bundle (that contains a function), compiles it,
/// links and loads it as a dynamic library
/// This function is used to test compiler.
// TODO: should think about using make_boot_image() instead of this adhoc code (Issue #52)
pub fn compile_fnc<'a>(fnc_name: &'static str, build_fnc: &'a Fn() -> VM) -> ll::Library {
VM::start_logging_trace();
let vm = Arc::new(build_fnc());
let compiler = Compiler::new(CompilerPolicy::default(), &vm);
let func_id = vm.id_of(fnc_name);
{
let funcs = vm.funcs().read().unwrap();
let func = match funcs.get(&func_id) {
Some(func) => func.read().unwrap(),
None => panic!("cannot find function {}", fnc_name)
};
let cur_ver = match func.cur_ver {
Some(v) => v,
None => panic!("function {} does not have a defined current version", fnc_name)
};
let func_vers = vm.func_vers().read().unwrap();
let mut func_ver = match func_vers.get(&cur_ver) {
Some(fv) => fv.write().unwrap(),
None => panic!("cannot find function version {}", cur_ver)
};
compiler.compile(&mut func_ver);
}
backend::emit_context(&vm);
let libname = &get_dylib_name(fnc_name);
let dylib = aot::link_dylib(vec![Mu(fnc_name)], libname, &vm);
ll::Library::new(dylib.as_os_str()).unwrap()
}
/// builds a bundle (that contains several functions), compiles them,
/// links and loads it as a dynamic library
/// This function is used to test compiler.
// TODO: should think about using make_boot_image() instead of this adhoc code (Issue #52)
pub fn compile_fncs<'a>(entry: &'static str, fnc_names: Vec<&'static str>, build_fnc: &'a Fn() -> VM) -> ll::Library {
VM::start_logging_trace();
let vm = Arc::new(build_fnc());
let compiler = Compiler::new(CompilerPolicy::default(), &vm);
for func in fnc_names.iter() {
let func_id = vm.id_of(func);
let funcs = vm.funcs().read().unwrap();
let func = funcs.get(&func_id).unwrap().read().unwrap();
let func_vers = vm.func_vers().read().unwrap();
let mut func_ver = func_vers.get(&func.cur_ver.unwrap()).unwrap().write().unwrap();
compiler.compile(&mut func_ver);
}
backend::emit_context(&vm);
let libname = &get_dylib_name(entry);
let dylib = aot::link_dylib(fnc_names.iter().map(|x| Mu(x)).collect(), libname, &vm);
ll::Library::new(dylib.as_os_str()).unwrap()
}
/// gets the path for the generated code of a Mu function
fn get_path_for_mu_func (f: MuName, vm: &VM) -> PathBuf {
let mut ret = PathBuf::from(&vm.vm_options.flag_aot_emit_dir);
ret.push(f);
......@@ -116,126 +280,9 @@ fn get_path_for_mu_func (f: MuName, vm: &VM) -> PathBuf {
ret
}
/// gets the path for generated Mu context (persisted VM/heap)
fn get_path_for_mu_context (vm: &VM) -> PathBuf {
let mut ret = PathBuf::from(&vm.vm_options.flag_aot_emit_dir);
ret.push(backend::AOT_EMIT_CONTEXT_FILE);
ret
}
pub fn link_primordial (funcs: Vec<MuName>, out: &str, vm: &VM) -> PathBuf {
let emit_dir = PathBuf::from(&vm.vm_options.flag_aot_emit_dir);
let files : Vec<PathBuf> = {
use std::fs;
let mut ret = vec![];
// all interested mu funcs
for func in funcs {
ret.push(get_path_for_mu_func(func, vm));
}
// mu context
ret.push(get_path_for_mu_context(vm));
// copy primoridal entry
let source = get_path_under_mu(runtime::PRIMORDIAL_ENTRY);
let dest = {
let mut ret = PathBuf::from(&vm.vm_options.flag_aot_emit_dir);
ret.push("main.c");
ret
};
trace!("copying from {:?} to {:?}", source, dest);
match fs::copy(source.as_path(), dest.as_path()) {
Ok(_) => {},
Err(e) => panic!("failed to copy: {}", e)
}
// include the primordial C main
ret.push(dest);
// include mu static lib
ret.push(get_path_under_mu(if cfg!(debug_assertions) {
"target/debug/libmu.a"
} else {
"target/release/libmu.a"
}));
/*// include rodal alloc static lib (it overrides free and realloc so it should be the last thing linked)
ret.push(get_path_under_mu(if cfg!(debug_assertions) {
"target/debug/deps/librodal_alloc.a"
} else {
"target/release/deps/librodal_alloc.a"
}));*/
ret
};
let mut out_path = emit_dir.clone();
out_path.push(out);
link_executable_internal(files,
&vm.vm_options.flag_bootimage_external_lib,
&vm.vm_options.flag_bootimage_external_libpath,
out_path)
}
pub fn execute(executable: PathBuf) -> Output {
let run = Command::new(executable.as_os_str());
exec(run)
}
pub fn execute_nocheck(executable: PathBuf) -> Output {
let run = Command::new(executable.as_os_str());
exec_nocheck(run)
}
pub fn link_dylib (funcs: Vec<MuName>, out: &str, vm: &VM) -> PathBuf {
let files = {
let mut ret = vec![];
for func in funcs {
ret.push(get_path_for_mu_func(func, vm));
}
ret.push(get_path_for_mu_context(vm));
ret
};
let mut out_path = PathBuf::from(&vm.vm_options.flag_aot_emit_dir);
out_path.push(out);
link_dylib_internal(files,
&vm.vm_options.flag_bootimage_external_lib,
&vm.vm_options.flag_bootimage_external_libpath,
out_path)
}
pub fn link_dylib_with_extra_srcs(funcs: Vec<MuName>, srcs: Vec<String>, out: &str, vm: &VM) -> PathBuf{
let files = {
let mut ret = vec![];
for func in funcs {
ret.push(get_path_for_mu_func(func, vm));
}
for src in srcs {
ret.push(PathBuf::from(src));
}
ret.push(get_path_for_mu_context(vm));
ret
};
let mut out_path = PathBuf::from(&vm.vm_options.flag_aot_emit_dir);
out_path.push(out);
link_dylib_internal(files,
&vm.vm_options.flag_bootimage_external_lib,
&vm.vm_options.flag_bootimage_external_libpath,
out_path)
}
}
\ No newline at end of file
......@@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! This module contains utility functions for linking generated code and
//! running tests for Zebu.
extern crate libloading as ll;
use compiler::*;
use ast::ir::*;
use vm::*;
use std::sync::Arc;
use std::path::PathBuf;
......@@ -24,9 +26,13 @@ use std::process::Command;
use std::process::Output;
use std::os::unix::process::ExitStatusExt;
/// linking utilities for ahead-of-time compilation
pub mod aot;
pub fn get_test_clang_path() -> String {
/// gets a C compiler (for assembling and linking generated assembly code)
/// This function will check CC environment variable and return it.
/// Otherwise, it returns the default C compiler (clang).
fn get_c_compiler() -> String {
use std::env;
match env::var("CC") {
......@@ -35,15 +41,45 @@ pub fn get_test_clang_path() -> String {
}
}
pub fn exec (cmd: Command) -> Output {
let output = exec_nocheck(cmd);
/// concatenates the given path related to Zebu root path
/// This function will check MU_ZEBU environment variable and use it as Zebu root path.
/// Otherwise, it uses the current directory as Zebu root path.
fn get_path_under_zebu(str: &'static str) -> PathBuf {
use std::env;
assert!(output.status.success());
match env::var("MU_ZEBU") {
Ok(v) => {
let mut ret = PathBuf::from(v);
ret.push(str);
ret
}
Err(_) => PathBuf::from(str)
}
}
/// executes the executable of the given path, checks its exit status
/// panics if this executable does not finish normally
pub fn exec_path(executable: PathBuf) -> Output {
let run = Command::new(executable.as_os_str());
exec_cmd(run)
}
/// executes the executable of the given path, does not check exit status
pub fn exec_path_nocheck(executable: PathBuf) -> Output {
let run = Command::new(executable.as_os_str());
exec_cmd_nocheck(run)
}
/// executes the given command, checks its exit status,
/// panics if this command does not finish normally
fn exec_cmd(cmd: Command) -> Output {
let output = exec_cmd_nocheck(cmd);
assert!(output.status.success());
output
}
pub fn exec_nocheck (mut cmd: Command) -> Output {
/// executes the given command, does not check exit status
fn exec_cmd_nocheck(mut cmd: Command) -> Output {
info!("executing: {:?}", cmd);
let output = match cmd.output() {
Ok(res) => res,
......@@ -62,79 +98,14 @@ pub fn exec_nocheck (mut cmd: Command) -> Output {
output
}
pub fn get_path_under_mu(str: &'static str) -> PathBuf {
use std::env;
match env::var("MU_ZEBU") {
Ok(v) => {
let mut ret = PathBuf::from(v);
ret.push(str);
ret
}
Err(_) => PathBuf::from(str)
}
}
/// returns a name for dynamic library
#[cfg(target_os = "macos")]
pub fn get_dylib_name(name: &'static str) -> String {
format!("lib{}.dylib", name)
}
/// returns a name for dynamic library
#[cfg(target_os = "linux")]
pub fn get_dylib_name(name: &'static str) -> String {
format!("lib{}.so", name)
}
pub fn compile_fnc<'a>(fnc_name: &'static str, build_fnc: &'a Fn() -> VM) -> ll::Library {
VM::start_logging_trace();
let vm = Arc::new(build_fnc());
let compiler = Compiler::new(CompilerPolicy::default(), &vm);
let func_id = vm.id_of(fnc_name);
{
let funcs = vm.funcs().read().unwrap();
let func = match funcs.get(&func_id) {
Some(func) => func.read().unwrap(),
None => panic!("cannot find function {}", fnc_name)
};
let cur_ver = match func.cur_ver {
Some(v) => v,
None => panic!("function {} does not have a defined current version", fnc_name)
};
let func_vers = vm.func_vers().read().unwrap();
let mut func_ver = match func_vers.get(&cur_ver) {
Some(fv) => fv.write().unwrap(),
None => panic!("cannot find function version {}", cur_ver)
};
compiler.compile(&mut func_ver);
}
backend::emit_context(&vm);
let libname = &get_dylib_name(fnc_name);
let dylib = aot::link_dylib(vec![Mu(fnc_name)], libname, &vm);
ll::Library::new(dylib.as_os_str()).unwrap()
}
pub fn compile_fncs<'a>(entry: &'static str, fnc_names: Vec<&'static str>, build_fnc: &'a Fn() -> VM) -> ll::Library {
VM::start_logging_trace();
let vm = Arc::new(build_fnc());
let compiler = Compiler::new(CompilerPolicy::default(), &vm);
for func in fnc_names.iter() {
let func_id = vm.id_of(func);
let funcs = vm.funcs().read().unwrap();
let func = funcs.get(&func_id).unwrap().read().unwrap();
let func_vers = vm.func_vers().read().unwrap();
let mut func_ver = func_vers.get(&func.cur_ver.unwrap()).unwrap().write().unwrap();
compiler.compile(&mut func_ver);
}
backend::emit_context(&vm);
let libname = &get_dylib_name(entry);
let dylib = aot::link_dylib(fnc_names.iter().map(|x| Mu(x)).collect(), libname, &vm);
ll::Library::new(dylib.as_os_str()).unwrap()
}
}
\ No newline at end of file
......@@ -92,7 +92,7 @@ impl MuVM {
extern crate libloading as ll;
use compiler::*;
use testutil::aot;
use linkutils::aot;
let funcs : Vec<MuID> = {
let funcs = self.vm.funcs().read().unwrap();
......
......@@ -978,7 +978,7 @@ impl <'a> VM {
/// has dylib extension, otherwise generates an executable)
#[cfg(feature = "aot")]
fn link_boot_image(&self, funcs: Vec<MuID>, extra_srcs: Vec<String>, output_file: String) {
use testutil;
use linkutils;
info!("Linking boot image...");
......@@ -993,13 +993,13 @@ impl <'a> VM {
if output_file.ends_with("dylib") || output_file.ends_with("so") {
// compile as dynamic library
testutil::aot::link_dylib_with_extra_srcs(func_names, extra_srcs, &output_file, self);
linkutils::aot::link_dylib_with_extra_srcs(func_names, extra_srcs, &output_file, self);
} else {
if extra_srcs.len() != 0 {
panic!("trying to create an executable with linking extern sources, unimplemented");
}
// compile as executable
testutil::aot::link_primordial(func_names, &output_file, self);
linkutils::aot::link_primordial(func_names, &output_file, self);
}
trace!("Done!");
......
......@@ -26,8 +26,8 @@ use self::mu::utils::Address;
use self::mu::utils::LinkedHashMap;
use std::sync::Arc;
use self::mu::testutil;
use self::mu::testutil::aot;
use self::mu::linkutils;
use self::mu::linkutils::aot;
#[test]
fn test_allocation_fastpath() {
......@@ -51,7 +51,7 @@ fn test_allocation_fastpath() {
backend::emit_context(&vm);
let executable = aot::link_primordial(vec!["allocation_fastpath".to_string()], "allocation_fastpath_test", &vm);
aot::execute(executable);
linkutils::exec_path(executable);
}
fn allocation_fastpath() -> VM {
......@@ -125,7 +125,7 @@ fn test_instruction_new() {
backend::emit_context(&vm);
let executable = aot::link_primordial(vec!["alloc_new".to_string()], "alloc_new_test", &vm);
aot::execute(executable);
linkutils::exec_path(executable);
}
#[allow(dead_code)]
......@@ -153,7 +153,7 @@ fn test_instruction_new_on_cur_thread() {
backend::emit_context(&vm);
// link
let libname = &testutil::get_dylib_name("alloc_new_on_cur_thread");
let libname = &linkutils::get_dylib_name("alloc_new_on_cur_thread");
let dylib = aot::link_dylib(vec![Mu("alloc_new")], libname, &vm);
let lib = libloading::Library::new(dylib.as_os_str()).unwrap();
......