diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 308b93e..b29fa6d 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -15,6 +15,7 @@ jobs: runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.toolchain == 'nightly' }} strategy: + fail-fast: false matrix: toolchain: [stable, beta, nightly] os: @@ -30,4 +31,11 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo test + - run: cargo test --features enc2-m32-a32 + - run: cargo test --features enc2-m64-a64 + - run: cargo test --features dylib + - run: cargo test --all-features + # Enabling enc2 features and dylib currently results in link errors + # on windows. + if: matrix.os != 'windows-latest' diff --git a/Cargo.toml b/Cargo.toml index 03d6468..b5bc11d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,31 @@ categories = ["encoding", "external-ffi-bindings", "hardware-support", "parsing" rust-version = "1.64" [features] +# Enable the enc2 module of XED. +# +# Each feature enables a version of enc2 for a different module and operand +# size. Note that attempting to enable multiple enc2 feature variants is not +# supported and will result in a compile error (instead of the link error it +# would normally emit). +enc2-m32-a32 = ["bindgen", "_internal-enc2"] +enc2-m64-a64 = ["bindgen", "_internal-enc2"] + +# Enable the checked variants for the enc2 module of XED. +enc2-chk = ["bindgen", "_internal-enc2"] + # Generate bindings with bindgen at build-time instead of using the # pregenerated bindings. # # This will be slower but is required for certain feature combinations. bindgen = ["dep:bindgen"] +dylib = [] + +# Build xed with the enc2. +# +# This is an internal feature and you do not need to enable it manually. +_internal-enc2 = [] + [dependencies] [build-dependencies] diff --git a/build.rs b/build.rs index 3ad97a3..52db1b4 100644 --- a/build.rs +++ b/build.rs @@ -5,7 +5,7 @@ use std::{ str::FromStr, }; -use target_lexicon::Triple; +use target_lexicon::{OperatingSystem, Triple}; fn create_dir>(path: P) -> io::Result<()> { fs::create_dir(path).or_else(|e| match e.kind() { @@ -47,7 +47,7 @@ fn find_python() -> Command { panic!("Unable to find a working python installation. Tried `python3` and `python`."); } -fn build_xed() { +fn build_xed(_cc: &cc::Build) { println!("cargo:rerun-if-changed=xed/VERSION"); println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed-env=PROFILE"); @@ -56,6 +56,12 @@ fn build_xed() { let cwd = env::current_dir().expect("Failed to get CWD"); let target = env::var("TARGET").expect("Failed to read TARGET"); let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".to_owned()); + let debug = match env::var("DEBUG") { + Ok(var) => var != "false", + _ => false, + }; + + let triple = Triple::from_str(&target).expect("TARGET was not a valid target triple"); let install_dir = out_dir.join("install"); let build_dir = out_dir.join("build"); @@ -82,24 +88,31 @@ fn build_xed() { .arg(&mfile_path) .arg("install") .arg(format!("--jobs={}", num_jobs)) - .arg("--silent") - .arg("--static-stripped") .arg("--extra-ccflags=-fPIC") .arg("--no-werror") - .arg(format!( - "--host-cpu={}", - Triple::from_str(&target) - .expect("TARGET was not a valid target triple") - .architecture - )) + .arg(format!("--host-cpu={}", triple.architecture)) .arg(format!("--install-dir={}", install_dir.display())); + if cfg!(feature = "dylib") { + cmd.arg("--shared"); + } else { + cmd.arg("--static-stripped"); + } + + if debug { + cmd.arg("--debug"); + } + if profile == "release" { cmd.arg("--opt=3"); } else { cmd.arg("--opt=0"); } + if cfg!(feature = "_internal-enc2") { + cmd.arg("--enc2"); + } + eprintln!("XED build command: {:?}", cmd); let status = cmd.status().expect("Failed to start xed build"); @@ -109,22 +122,71 @@ fn build_xed() { } let lib_dir = install_dir.join("lib"); + let bin_dir = install_dir.join("bin"); + let linkty = match cfg!(feature = "dylib") { + true => "dylib", + false => "static", + }; println!("cargo:rustc-link-search=native={}", lib_dir.display()); - println!("cargo:rustc-link-lib=static=xed"); -} + println!("cargo:rustc-link-lib={linkty}=xed"); -fn build_inline_shim() { + if triple.operating_system == OperatingSystem::Windows { + println!("cargo:rustc-link-search=native={}", bin_dir.display()); + } + + macro_rules! cfg_link_enc2 { + ($variant:literal) => { + if cfg!(feature = $variant) { + println!("cargo:rustc-link-lib={linkty}=xed-{}", $variant); + + if cfg!(feature = "enc2-chk") { + println!("cargo:rustc-link-lib={linkty}=xed-chk-{}", $variant); + } + } + }; + } + + cfg_link_enc2!("enc2-m32-a32"); + cfg_link_enc2!("enc2-m64-a64"); + + #[cfg(not(feature = "dylib"))] + #[cfg(all(feature = "enc2-m32-a32", feature = "enc2-m64-a64"))] + compile_error!(concat!( + "Cannot enable both `enc2-m32-a32` and `enc2-m64-a64` features when linking statically.", + "You can avoid this by enabling the `dylib` feature to build these as dylibs." + )); +} +fn build_inline_shim(mut cc: cc::Build) { let cwd = std::env::current_dir().unwrap(); let out_dir = PathBuf::from(env::var("OUT_DIR").expect("Failed to read OUT_DIR")); - let mut cc = cc::Build::new(); cc.include(out_dir.join("install/include")) .include(&cwd) // xed-static.c contains an instance of this. It's not an error and we // don't want to be modifying generated files so just silence the warning. .flag_if_supported("-Wno-duplicate-decl-specifier"); + macro_rules! cfg_define_enc2 { + ($variant:literal) => { + if cfg!(feature = $variant) { + let def = $variant.replace('-', "_").to_ascii_uppercase(); + cc.define(&format!("XED_SYS_{def}"), None); + } + }; + } + + cfg_define_enc2!("enc2-m32-a32"); + cfg_define_enc2!("enc2-m64-a64"); + + if cfg!(feature = "enc2-chk") { + cc.define("XED_SYS_ENC2_CHK", None); + } + + if cfg!(feature = "dylib") { + cc.define("XED_DLL", None); + } + if cfg!(feature = "bindgen") { cc.file(out_dir.join("xed-static.c")); } else { @@ -140,7 +202,7 @@ fn build_bindgen() { let dot_rs = out_dir.join("xed.rs"); - let builder = bindgen::builder() + let mut builder = bindgen::builder() .clang_arg("-I") .clang_arg(out_dir.join("install/include").display().to_string()) .allowlist_type("xed3?_.*") @@ -154,6 +216,22 @@ fn build_bindgen() { .wrap_static_fns_path(out_dir.join("xed-static.c")) .wrap_static_fns_suffix("_xed_sys_inline"); + if cfg!(feater = "dylib") { + builder = builder.clang_arg("-DXED_DLL"); + } + + if cfg!(feature = "enc2-m32-a32") { + builder = builder.clang_arg("-DXED_SYS_ENC2_M32_A32"); + } + + if cfg!(feature = "enc2-m64-a64") { + builder = builder.clang_arg("-DXED_SYS_ENC2_M64_A64"); + } + + if cfg!(feature = "enc2-chk") { + builder = builder.clang_arg("-DXED_SYS_ENC2_CHK"); + } + let bindings = builder .header("xed.h") .generate() @@ -168,10 +246,9 @@ fn build_bindgen() { } fn main() { - build_xed(); - + let cc = cc::Build::new(); + build_xed(&cc); #[cfg(feature = "bindgen")] build_bindgen(); - - build_inline_shim(); + build_inline_shim(cc); } diff --git a/src/lib.rs b/src/lib.rs index 9dc66c1..4058f71 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,30 @@ //! Note that [`xed_tables_init()`][0] must be called before using the library. //! //! # Features -//! //! - `bindgen` - Don't use the bundled bindings files and instead regenerate //! rust bindings from scratch at compile time. You should never need to //! enable this manually but it will be enabled by other features. +//! - `dylib` - Link XED (and `enc2` libraries if enabled) dynamically. This +//! allows you to work around the link errors that prevent you from linking +//! both enc2 libraries statically. +//! +//! ## `enc2` +//! XED has the option to enable its [`enc2`][1] encoder. This contains a +//! function to generate an x86 instruction for _every single instruction +//! variant_. As such, it is slow to compile and running bindgen on the +//! resulting headers generates a mountain (20+MB) of rust code. +//! +//! The generated static libraries also unfortunately have some duplicate +//! symbols so attempting to link both will result in linker errors. To make +//! the error less confusing this library will fail to compile if both +//! `enc2-m64-a64` and `enc2-m32-a32` are enabled and the `dylib` feature is +//! not enabled. +//! +//! - `enc2-m64-a64` - Enable enc2 for 64-bit mode with 64-bit addresses. +//! - `enc2-m32-a32` - Enable enc2 for 32-bit mode with 32-bit addresses. +//! - `enc2-chk` - Enable checked variants of the of the enc2 functions. Note +//! that this feature does nothing unless you have enabled one of the other +//! enc2 features. //! //! [0]: crate::xed_tables_init //! [1]: https://github.com/intelxed/xed/wiki/The-fast-encoder---enc2 diff --git a/xed.h b/xed.h index 70fec29..b536ae8 100644 --- a/xed.h +++ b/xed.h @@ -7,9 +7,16 @@ #include "xed/xed-interface.h" #include "xed/xed-isa-set.h" -#ifdef XED_ENC2_ENCODER -#include "xed/xed-enc2-m32-a32.h" -#include "xed/xed-chk-enc2-m32-a32.h" -#include "xed/xed-enc2-m64-a64.h" -#include "xed/xed-chk-enc2-m64-a64.h" +#ifdef XED_SYS_ENC2_M32_A32 +# include "xed/xed-enc2-m32-a32.h" +# ifdef XED_SYS_ENC2_CHK +# include "xed/xed-chk-enc2-m32-a32.h" +# endif +#endif + +#ifdef XED_SYS_ENC2_M64_A64 +# include "xed/xed-enc2-m64-a64.h" +# ifdef XED_SYS_ENC2_CHK +# include "xed/xed-chk-enc2-m64-a64.h" +# endif #endif