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

Add wasmer create-exe #1693

Merged
merged 17 commits into from
Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions lib/c-api/wasmer_wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
# define DEPRECATED(message) __declspec(deprecated(message))
#endif

// The `jit` feature has been enabled for this build.
#define WASMER_JIT_ENABLED

// The `compiler` feature has been enabled for this build.
#define WASMER_COMPILER_ENABLED

Expand Down
1 change: 1 addition & 0 deletions lib/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ cfg-if = "0.1"
# For debug feature
fern = { version = "0.6", features = ["colored"], optional = true }
log = { version = "0.4", optional = true }
tempfile = "3"

[features]
# Don't add the compiler features in default, please add them on the Makefile
Expand Down
9 changes: 9 additions & 0 deletions lib/cli/src/bin/wasmer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use anyhow::Result;
#[cfg(feature = "object-file")]
use wasmer_cli::commands::CreateExe;
#[cfg(feature = "wast")]
use wasmer_cli::commands::Wast;
use wasmer_cli::commands::{Cache, Compile, Config, Inspect, Run, SelfUpdate, Validate};
Expand Down Expand Up @@ -26,6 +28,11 @@ enum WasmerCLIOptions {
#[structopt(name = "compile")]
Compile(Compile),

/// Compile a WebAssembly binary into a native executable
#[cfg(feature = "object-file")]
#[structopt(name = "create-exe")]
CreateExe(CreateExe),

/// Get various configuration information needed
/// to compile programs which use Wasmer
#[structopt(name = "config")]
Expand Down Expand Up @@ -53,6 +60,8 @@ impl WasmerCLIOptions {
Self::Cache(cache) => cache.execute(),
Self::Validate(validate) => validate.execute(),
Self::Compile(compile) => compile.execute(),
#[cfg(feature = "object-file")]
Self::CreateExe(create_exe) => create_exe.execute(),
Self::Config(config) => config.execute(),
Self::Inspect(inspect) => inspect.execute(),
#[cfg(feature = "wast")]
Expand Down
4 changes: 4 additions & 0 deletions lib/cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
mod cache;
mod compile;
mod config;
#[cfg(feature = "object-file")]
mod create_exe;
mod inspect;
mod run;
mod self_update;
mod validate;
#[cfg(feature = "wast")]
mod wast;

#[cfg(feature = "object-file")]
pub use create_exe::*;
#[cfg(feature = "wast")]
pub use wast::*;
pub use {cache::*, compile::*, config::*, inspect::*, run::*, self_update::*, validate::*};
5 changes: 2 additions & 3 deletions lib/cli/src/commands/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ impl Compile {
.context(format!("failed to compile `{}`", self.path.display()))
}

fn get_recommend_extension(
&self,
pub(crate) fn get_recommend_extension(
engine_type: &EngineType,
target_triple: &Triple,
) -> &'static str {
Expand Down Expand Up @@ -82,7 +81,7 @@ impl Compile {
.file_stem()
.map(|osstr| osstr.to_string_lossy().to_string())
.unwrap_or_default();
let recommended_extension = self.get_recommend_extension(&engine_type, target.triple());
let recommended_extension = Self::get_recommend_extension(&engine_type, target.triple());
match self.output.extension() {
Some(ext) => {
if ext != recommended_extension {
Expand Down
273 changes: 273 additions & 0 deletions lib/cli/src/commands/create_exe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
//! Create a standalone native executable for a given Wasm file.

use crate::store::{CompilerOptions, EngineType};
use anyhow::{Context, Result};
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use structopt::StructOpt;
use wasmer::*;

const WASMER_MAIN_C_SOURCE: &[u8] = include_bytes!("wasmer_create_exe_main.c");

// TODO: to get this working locally I had to add wasmer_wasm.h and wasm.h to `.wasmer/include` manually.
// this needs to be fixed before we can ship this.
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved

#[derive(Debug, StructOpt)]
/// The options for the `wasmer create-exe` subcommand
pub struct CreateExe {
/// Input file
#[structopt(name = "FILE", parse(from_os_str))]
path: PathBuf,

/// Output file
#[structopt(name = "OUTPUT PATH", short = "o", parse(from_os_str))]
output: PathBuf,

/// Compilation Target triple
#[structopt(long = "target")]
target_triple: Option<Triple>,

#[structopt(flatten)]
compiler: CompilerOptions,

#[structopt(short = "m", multiple = true)]
cpu_features: Vec<CpuFeature>,

/// Additional libraries to link against.
/// This is useful for fixing linker errors that may occur on some systems.
#[structopt(short = "l", multiple = true)]
libraries: Vec<String>,
}

impl CreateExe {
/// Runs logic for the `compile` subcommand
pub fn execute(&self) -> Result<()> {
let target = self
.target_triple
.as_ref()
.map(|target_triple| {
let mut features = self
.cpu_features
.clone()
.into_iter()
.fold(CpuFeature::set(), |a, b| a | b);
// Cranelift requires SSE2, so we have this "hack" for now to facilitate
// usage
features |= CpuFeature::SSE2;
Target::new(target_triple.clone(), features)
})
.unwrap_or_default();
let engine_type = EngineType::ObjectFile;
let (store, compiler_type) = self
.compiler
.get_store_for_target_and_engine(target.clone(), engine_type)?;

println!("Engine: {}", engine_type.to_string());
println!("Compiler: {}", compiler_type.to_string());
println!("Target: {}", target.triple());

let working_dir = tempfile::tempdir()?;
let starting_cd = env::current_dir()?;
let output_path = starting_cd.join(&self.output);
env::set_current_dir(&working_dir)?;

// TODO: encapsulate compile code
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(not(windows))]
let wasm_object_path = PathBuf::from("wasm.o");
#[cfg(windows)]
let wasm_object_path = PathBuf::from("wasm.obj");

let wasm_module_path = starting_cd.join(&self.path);

let header_file_path = Path::new("my_wasm.h");
let module =
Module::from_file(&store, &wasm_module_path).context("failed to compile Wasm")?;
let _ = module.serialize_to_file(&wasm_object_path)?;

let artifact: &wasmer_engine_object_file::ObjectFileArtifact =
module.artifact().as_ref().downcast_ref().context(
"Engine type is ObjectFile but could not downcast artifact into ObjectFileArtifact",
)?;
let symbol_registry = artifact.symbol_registry();
let metadata_length = artifact.metadata_length();
let module_info = module.info();
let header_file_src = crate::c_gen::object_file_header::generate_header_file(
module_info,
symbol_registry,
metadata_length,
);

let header_path = header_file_path.clone();
// for C code
let mut header = std::fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(&header_path)?;

use std::io::Write;
header.write(header_file_src.as_bytes())?;

// auto compilation
//

// write C src to disk
let c_src_path = Path::new("wasmer_main.c");
#[cfg(not(windows))]
let c_src_obj = PathBuf::from("wasmer_main.o");
#[cfg(windows)]
let c_src_obj = PathBuf::from("wasmer_main.obj");

{
let mut c_src_file = fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&c_src_path)
.context("Failed to open C source code file")?;
// TODO:
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
c_src_file.write_all(WASMER_MAIN_C_SOURCE)?;
}
run_c_compile(&c_src_path, &c_src_obj).context("Failed to compile C source code")?;
LinkCode {
object_paths: vec![c_src_obj, wasm_object_path],
output_path,
additional_libraries: self.libraries.clone(),
..Default::default()
}
.run()
.context("Failed to link objects together")?;

eprintln!(
"✔ Native executable compiled successfully to `{}`.",
self.output.display(),
);

Ok(())
}
}

fn get_wasmer_include_directory() -> anyhow::Result<PathBuf> {
let mut path = PathBuf::from(env::var("WASMER_DIR")?);
path.push("include");
Ok(path)
}

fn get_libwasmer_path() -> anyhow::Result<PathBuf> {
let mut path = PathBuf::from(env::var("WASMER_DIR")?);
path.push("lib");

MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(not(windows))]
path.push("libwasmer.a");
#[cfg(windows)]
path.push("libwasmer.lib");

Ok(path)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be a good idea to abstract this out, and also reuse them on the lib/cli/src/commands/config.rs file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it looks like the config logic isn't tested right now... changes Ivan and I have been making to the C API are going to break it I think...

The config code implies we can just do -lwasmer which is interesting that would make the code have fewer cfgs in it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in wasmer config for linking against wasmer seems to default to the dynamic libwasmer 🤔


/// Compile the C code.
fn run_c_compile(path_to_c_src: &Path, output_name: &Path) -> anyhow::Result<()> {
#[cfg(not(windows))]
let c_compiler = "cc";
#[cfg(windows)]
let c_compiler = "clang++";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just caught this. Why clang++ in Windows? Can we add a comment?


let output = Command::new(c_compiler)
.arg("-O2")
.arg("-c")
.arg(path_to_c_src)
.arg("-I")
.arg(get_wasmer_include_directory()?)
.arg("-o")
.arg(output_name)
.output()?;

if !output.status.success() {
bail!(
"C code compile failed with: stdout: {}\n\nstderr: {}",
std::str::from_utf8(&output.stdout)
.expect("stdout is not utf8! need to handle arbitrary bytes"),
std::str::from_utf8(&output.stderr)
.expect("stderr is not utf8! need to handle arbitrary bytes")
);
}
Ok(())
}

/// Data used to run a linking command for generated artifacts.
#[derive(Debug)]
struct LinkCode {
/// Path to the linker used to run the linking command.
linker_path: PathBuf,
/// String used as an optimization flag.
optimization_flag: String,
/// Paths of objects to link.
object_paths: Vec<PathBuf>,
/// Additional libraries to link against.
additional_libraries: Vec<String>,
/// Path to the output target.
output_path: PathBuf,
/// Path to the static libwasmer library.
libwasmer_path: PathBuf,
}

impl Default for LinkCode {
fn default() -> Self {
#[cfg(not(windows))]
let linker = "cc";
#[cfg(windows)]
let linker = "clang";
Self {
linker_path: PathBuf::from(linker),
optimization_flag: String::from("-O2"),
object_paths: vec![],
additional_libraries: vec![],
output_path: PathBuf::from("a.out"),
libwasmer_path: get_libwasmer_path().unwrap(),
}
}
}

impl LinkCode {
// TODO: `wasmer create-exe` needs a command line flag for extra libraries to link aganist
// or perhaps we just want to add a flag for passing things to the linker
fn run(&self) -> anyhow::Result<()> {
let mut command = Command::new(&self.linker_path);
let command = command
.arg(&self.optimization_flag)
.args(
self.object_paths
.iter()
.map(|path| path.canonicalize().unwrap()),
)
.arg(
&self
.libwasmer_path
.canonicalize()
.context("Failed to find libwasmer")?,
);
#[cfg(windows)]
let command = command.arg("-luserenv").arg("-lWs2_32").arg("-ladvapi32");
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(not(windows))]
let command = command.arg("-ldl").arg("-lm").arg("-pthread");
let link_aganist_extra_libs = self
.additional_libraries
.iter()
.map(|lib| format!("-l{}", lib));
let command = command.args(link_aganist_extra_libs);
let output = command.arg("-o").arg(&self.output_path).output()?;

if !output.status.success() {
bail!(
"linking failed with: stdout: {}\n\nstderr: {}",
std::str::from_utf8(&output.stdout)
.expect("stdout is not utf8! need to handle arbitrary bytes"),
std::str::from_utf8(&output.stderr)
.expect("stderr is not utf8! need to handle arbitrary bytes")
);
}
Ok(())
}
}
Loading