diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e9218c..74a7e77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed + +- **`litert-sys`** on `x86_64-pc-windows-msvc`: the committed bindings were + seeded from macOS, so `LiteRtLayout` had GCC's layout (68 bytes, + `dimensions` at offset 4) instead of MSVC's (72 bytes, offset 8, as + `litert_layout.h` asserts). Every tensor shape read back garbled (`[0, 10]` + for `[10, 10]`) and running a model failed. The bindings are regenerated on + Windows with `cargo xtask regen-bindings`, and a new test checks the + struct's size and offsets on every target. +- **`litert-sys`** on MSVC: linking failed with `LNK1181: cannot open input + file 'LiteRt.lib'`, since the prebuilts have no import library. build.rs now + makes one from the DLL's exports (see the README's Windows section), and no + longer passes the `-Wl,-rpath` flag `link.exe` ignores; nor does `litert`. +- **`litert`**: `ElementType` builds with the MSVC bindings, whose C enums are + `int` rather than `unsigned int`. + ## [0.3.0] — WASM (browser + server) support for `litert-sys` / `litert` First-class `wasm32-unknown-emscripten` target for the base TFLite inference diff --git a/Cargo.toml b/Cargo.toml index 350fd82..ebe5bf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,9 @@ dirs = "5" bindgen = "0.69" serde = { version = "1", features = ["derive"] } serde_json = "1" +cc = "1" +# 0.39: the newest that keeps the workspace's rust-version (0.40 needs 1.85). +object = { version = "0.39", default-features = false, features = ["read_core", "pe", "std"] } [profile.release] lto = "thin" diff --git a/README.md b/README.md index 6aed912..1dd75f8 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,17 @@ Alternatively, prefix individual invocations with `DYLD_LIBRARY_PATH=$(cargo xtask cache-dir)`, or link with `RUSTFLAGS="-C link-arg=-Wl,-rpath,/path/to/cache"`. -Linux, Windows, and Android are unaffected. +Linux and Android are unaffected. + +## Windows (MSVC) + +Google ships `libLiteRt.dll` without an import library, so `litert-sys`' build +script makes `LiteRt.lib` from the DLL's exports with MSVC's `lib.exe` (found +the way the `cc` crate finds MSVC tools) and links the DLLs beside it in its +`OUT_DIR`, which it publishes as `DEP_LITERT_LIB_DIR`. Cargo puts that +directory on `PATH` for `cargo run` and `cargo test`. A binary started any +other way finds the DLLs the usual Windows way: copy the `*.dll` files of +`DEP_LITERT_LIB_DIR` next to it when you ship it. ## WebAssembly (browser + Node.js + wasmtime) diff --git a/litert-sys/Cargo.toml b/litert-sys/Cargo.toml index 0005c17..3d5defa 100644 --- a/litert-sys/Cargo.toml +++ b/litert-sys/Cargo.toml @@ -47,3 +47,6 @@ serde_json = { workspace = true } zip = { workspace = true } tar = { workspace = true } flate2 = { workspace = true } +# Make the MSVC import library of libLiteRt.dll (see `msvc_link_dir`). +cc = { workspace = true } +object = { workspace = true } diff --git a/litert-sys/build.rs b/litert-sys/build.rs index 7269ed3..0789bce 100644 --- a/litert-sys/build.rs +++ b/litert-sys/build.rs @@ -226,10 +226,83 @@ fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR env var missing")); emit_bindings(&target, &out_dir); - let lib_dir = locate_library(&target); + let lib_dir = match locate_library(&target) { + Some(dir) if target.ends_with("-windows-msvc") => { + Some(msvc_link_dir(&target, &dir, &out_dir)) + } + other => other, + }; emit_link_directives(&target, lib_dir.as_deref()); } +/// The directory MSVC links from. `link.exe` links a DLL through its import +/// library, `LiteRt.lib` for `-lLiteRt`, and the prebuilts ship +/// `libLiteRt.dll` without one: it is made here from the DLL's exports, with +/// `lib.exe`, in `OUT_DIR`, and the DLLs of `lib_dir` are linked beside it. +/// Cargo puts link search directories inside the target directory on `PATH` +/// for `cargo run` and `cargo test`, so those find the DLLs too. A `lib_dir` +/// without `libLiteRt.dll` (say, a local build in `LITERT_LIB_DIR` with an +/// import library of its own) is used as is. +fn msvc_link_dir(target: &str, lib_dir: &Path, out_dir: &Path) -> PathBuf { + let dll = "libLiteRt.dll"; + if !lib_dir.join(dll).exists() { + return lib_dir.to_path_buf(); + } + println!("cargo:rerun-if-changed={}", lib_dir.join(dll).display()); + let dir = out_dir.join("msvc"); + fs::create_dir_all(&dir).expect("create the MSVC link dir"); + for entry in fs::read_dir(lib_dir).expect("read the LiteRT lib dir") { + let path = entry.expect("read the LiteRT lib dir").path(); + if !path + .extension() + .is_some_and(|it| it.eq_ignore_ascii_case("dll")) + { + continue; + } + let linked = dir.join(path.file_name().expect("a DLL file name")); + let _ = fs::remove_file(&linked); + if fs::hard_link(&path, &linked).is_err() { + fs::copy(&path, &linked) + .unwrap_or_else(|e| panic!("copy {} -> {}: {e}", path.display(), linked.display())); + } + } + + let data = fs::read(dir.join(dll)).unwrap_or_else(|e| panic!("read {dll}: {e}")); + let exports = { + use object::Object; + object::read::pe::PeFile64::parse(&*data) + .and_then(|pe| pe.exports()) + .unwrap_or_else(|e| panic!("read the exports of {dll}: {e}")) + }; + let mut def = format!("LIBRARY {dll}\nEXPORTS\n"); + for export in &exports { + let name = std::str::from_utf8(export.name()).expect("an ASCII export name"); + def.push_str(&format!(" {name}\n")); + } + let def_path = dir.join("LiteRt.def"); + fs::write(&def_path, def).expect("write LiteRt.def"); + + let machine = if target.starts_with("aarch64") { + "arm64" + } else { + "x64" + }; + let mut lib = cc::windows_registry::find(target, "lib.exe") + .expect("MSVC's lib.exe, to make LiteRt.lib (install the Visual Studio C++ build tools)"); + let status = lib + .arg("/nologo") + .arg(format!("/machine:{machine}")) + .arg(format!("/def:{}", def_path.display())) + .arg(format!("/out:{}", dir.join("LiteRt.lib").display())) + .status() + .expect("run lib.exe"); + assert!( + status.success(), + "lib.exe failed to make LiteRt.lib: {status}" + ); + dir +} + #[cfg(feature = "generate-bindings")] fn emit_bindings(_target: &str, out_dir: &Path) { generate_bindings(out_dir); @@ -642,8 +715,9 @@ fn emit_link_directives(target: &str, lib_dir: Option<&Path>) { if let Some(dir) = lib_dir { let dir = dir.display(); println!("cargo:rustc-link-search=native={dir}"); - if target != "wasm32-unknown-emscripten" { - // wasm-ld doesn't understand -rpath; emcc rejects it as well. + if target != "wasm32-unknown-emscripten" && !target.ends_with("-windows-msvc") { + // wasm-ld doesn't understand -rpath; emcc rejects it as well. MSVC's + // link.exe has no rpath either: Windows finds DLLs through PATH. println!("cargo:rustc-link-arg=-Wl,-rpath,{dir}"); } println!("cargo:lib_dir={dir}"); diff --git a/litert-sys/src/bindings/x86_64-pc-windows-msvc.rs b/litert-sys/src/bindings/x86_64-pc-windows-msvc.rs index 56b9c61..550a8fd 100644 --- a/litert-sys/src/bindings/x86_64-pc-windows-msvc.rs +++ b/litert-sys/src/bindings/x86_64-pc-windows-msvc.rs @@ -1,4 +1,3 @@ -// AUTOGENERATED: seeded from aarch64-apple-darwin.rs. CI regenerates per-target via `cargo xtask regen-bindings` and fails on drift. /* automatically generated by rust-bindgen 0.69.5 */ #[repr(C)] @@ -81,50 +80,21 @@ where } } } -#[repr(C)] -#[derive(Default)] -pub struct __IncompleteArrayField(::std::marker::PhantomData, [T; 0]); -impl __IncompleteArrayField { - #[inline] - pub const fn new() -> Self { - __IncompleteArrayField(::std::marker::PhantomData, []) - } - #[inline] - pub fn as_ptr(&self) -> *const T { - self as *const _ as *const T - } - #[inline] - pub fn as_mut_ptr(&mut self) -> *mut T { - self as *mut _ as *mut T - } - #[inline] - pub unsafe fn as_slice(&self, len: usize) -> &[T] { - ::std::slice::from_raw_parts(self.as_ptr(), len) - } - #[inline] - pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] { - ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len) - } -} -impl ::std::fmt::Debug for __IncompleteArrayField { - fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - fmt.write_str("__IncompleteArrayField") - } -} +pub const LITERT_WINDOWS_OS: u32 = 1; pub const LITERT_HAS_SYNC_FENCE_SUPPORT: u32 = 0; pub const LITERT_HAS_AHWB_SUPPORT_DEFAULT: u32 = 0; pub const LITERT_HAS_OPENGL_SUPPORT_DEFAULT: u32 = 0; pub const LITERT_HAS_ION_SUPPORT_DEFAULT: u32 = 0; pub const LITERT_HAS_DMABUF_SUPPORT_DEFAULT: u32 = 0; pub const LITERT_HAS_FASTRPC_SUPPORT_DEFAULT: u32 = 0; -pub const LITERT_HAS_METAL_SUPPORT_DEFAULT: u32 = 1; +pub const LITERT_HAS_METAL_SUPPORT_DEFAULT: u32 = 0; pub const LITERT_HAS_OPENCL_SUPPORT_DEFAULT: u32 = 0; -pub const LITERT_HAS_VULKAN_SUPPORT_DEFAULT: u32 = 0; +pub const LITERT_HAS_VULKAN_SUPPORT_DEFAULT: u32 = 1; pub const LITERT_HAS_WEBGPU_SUPPORT_DEFAULT: u32 = 1; -pub const LITERT_HAS_METAL_SUPPORT: u32 = 1; +pub const LITERT_HAS_METAL_SUPPORT: u32 = 0; pub const LITERT_HAS_OPENCL_SUPPORT: u32 = 0; pub const LITERT_HAS_WEBGPU_SUPPORT: u32 = 1; -pub const LITERT_HAS_VULKAN_SUPPORT: u32 = 0; +pub const LITERT_HAS_VULKAN_SUPPORT: u32 = 1; pub const LITERT_HAS_OPENGL_SUPPORT: u32 = 0; pub const LITERT_HAS_AHWB_SUPPORT: u32 = 0; pub const LITERT_HAS_ION_SUPPORT: u32 = 0; @@ -333,7 +303,7 @@ pub const kLiteRtStatusErrorUnsupportedCompilerVersion: LiteRtStatus = 4001; pub const kLiteRtStatusErrorIncompatibleByteCodeVersion: LiteRtStatus = 4002; pub const kLiteRtStatusErrorUnsupportedOpShapeInferer: LiteRtStatus = 5000; pub const kLiteRtStatusErrorShapeInferenceFailed: LiteRtStatus = 5001; -pub type LiteRtStatus = ::std::os::raw::c_uint; +pub type LiteRtStatus = ::std::os::raw::c_int; extern "C" { pub fn LiteRtGetStatusString(status: LiteRtStatus) -> *const ::std::os::raw::c_char; } @@ -345,34 +315,34 @@ pub type LiteRtHwAccelerators = ::std::os::raw::c_int; pub const kLiteRtDelegatePrecisionDefault: LiteRtDelegatePrecision = 0; pub const kLiteRtDelegatePrecisionFp16: LiteRtDelegatePrecision = 1; pub const kLiteRtDelegatePrecisionFp32: LiteRtDelegatePrecision = 2; -pub type LiteRtDelegatePrecision = ::std::os::raw::c_uint; +pub type LiteRtDelegatePrecision = ::std::os::raw::c_int; pub const kLiteRtGpuPriorityDefault: LiteRtGpuPriority = 0; pub const kLiteRtGpuPriorityLow: LiteRtGpuPriority = 1; pub const kLiteRtGpuPriorityNormal: LiteRtGpuPriority = 2; pub const kLiteRtGpuPriorityHigh: LiteRtGpuPriority = 3; -pub type LiteRtGpuPriority = ::std::os::raw::c_uint; +pub type LiteRtGpuPriority = ::std::os::raw::c_int; pub const kLiteRtDelegateBufferStorageTypeDefault: LiteRtDelegateBufferStorageType = 0; pub const kLiteRtDelegateBufferStorageTypeBuffer: LiteRtDelegateBufferStorageType = 1; pub const kLiteRtDelegateBufferStorageTypeTexture2D: LiteRtDelegateBufferStorageType = 2; -pub type LiteRtDelegateBufferStorageType = ::std::os::raw::c_uint; +pub type LiteRtDelegateBufferStorageType = ::std::os::raw::c_int; pub const kLiteRtTensorBufferLockModeRead: LiteRtTensorBufferLockMode = 0; pub const kLiteRtTensorBufferLockModeWrite: LiteRtTensorBufferLockMode = 1; pub const kLiteRtTensorBufferLockModeReadWrite: LiteRtTensorBufferLockMode = 2; -pub type LiteRtTensorBufferLockMode = ::std::os::raw::c_uint; +pub type LiteRtTensorBufferLockMode = ::std::os::raw::c_int; pub const kLiteRtGpuBackendAutomatic: LiteRtGpuBackend = 0; pub const kLiteRtGpuBackendOpenCl: LiteRtGpuBackend = 1; pub const kLiteRtGpuBackendWebGpu: LiteRtGpuBackend = 2; pub const kLiteRtGpuBackendOpenGl: LiteRtGpuBackend = 3; -pub type LiteRtGpuBackend = ::std::os::raw::c_uint; +pub type LiteRtGpuBackend = ::std::os::raw::c_int; pub const kLiteRtGpuWaitTypeDefault: LiteRtGpuWaitType = 0; pub const kLiteRtGpuWaitTypePassive: LiteRtGpuWaitType = 1; pub const kLiteRtGpuWaitTypeActive: LiteRtGpuWaitType = 2; pub const kLiteRtGpuWaitTypeDoNotWait: LiteRtGpuWaitType = 3; -pub type LiteRtGpuWaitType = ::std::os::raw::c_uint; +pub type LiteRtGpuWaitType = ::std::os::raw::c_int; pub const kLiteRtErrorReporterModeNone: LiteRtErrorReporterMode = 0; pub const kLiteRtErrorReporterModeStderr: LiteRtErrorReporterMode = 1; pub const kLiteRtErrorReporterModeBuffer: LiteRtErrorReporterMode = 2; -pub type LiteRtErrorReporterMode = ::std::os::raw::c_uint; +pub type LiteRtErrorReporterMode = ::std::os::raw::c_int; pub type LiteRtHwAcceleratorSet = ::std::os::raw::c_int; pub type LiteRtParamIndex = usize; pub const kLiteRtAnyTypeNone: LiteRtAnyType = 0; @@ -381,7 +351,7 @@ pub const kLiteRtAnyTypeInt: LiteRtAnyType = 2; pub const kLiteRtAnyTypeReal: LiteRtAnyType = 3; pub const kLiteRtAnyTypeString: LiteRtAnyType = 8; pub const kLiteRtAnyTypeVoidPtr: LiteRtAnyType = 9; -pub type LiteRtAnyType = ::std::os::raw::c_uint; +pub type LiteRtAnyType = ::std::os::raw::c_int; #[doc = " An object that holds various value type.\n\n @note This concrete type is part of the public API and is ABI stable."] #[repr(C)] #[derive(Copy, Clone)] @@ -422,6 +392,7 @@ impl Default for LiteRtAny { pub struct LiteRtLayout { pub _bitfield_align_1: [u8; 0], pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>, + pub __bindgen_padding_0: u32, pub dimensions: [i32; 8usize], pub strides: [u32; 8usize], } @@ -508,7 +479,7 @@ pub const kLiteRtElementTypeComplex128: LiteRtElementType = 12; pub const kLiteRtElementTypeTfResource: LiteRtElementType = 14; pub const kLiteRtElementTypeTfString: LiteRtElementType = 5; pub const kLiteRtElementTypeTfVariant: LiteRtElementType = 15; -pub type LiteRtElementType = ::std::os::raw::c_uint; +pub type LiteRtElementType = ::std::os::raw::c_int; #[doc = " Tensor type when its rank is dynamic.\n\n @note This concrete type is part of the public API and is ABI stable."] #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -542,7 +513,7 @@ impl Default for LiteRtRankedTensorType { } pub const kLiteRtRankedTensorType: LiteRtTensorTypeId = 0; pub const kLiteRtUnrankedTensorType: LiteRtTensorTypeId = 1; -pub type LiteRtTensorTypeId = ::std::os::raw::c_uint; +pub type LiteRtTensorTypeId = ::std::os::raw::c_int; #[repr(C)] #[derive(Debug, Default, Copy, Clone)] pub struct LiteRtQuantizationPerTensor { @@ -570,7 +541,7 @@ pub const kLiteRtQuantizationNone: LiteRtQuantizationTypeId = 0; pub const kLiteRtQuantizationPerTensor: LiteRtQuantizationTypeId = 1; pub const kLiteRtQuantizationPerChannel: LiteRtQuantizationTypeId = 2; pub const kLiteRtQuantizationBlockWise: LiteRtQuantizationTypeId = 3; -pub type LiteRtQuantizationTypeId = ::std::os::raw::c_uint; +pub type LiteRtQuantizationTypeId = ::std::os::raw::c_int; extern "C" { pub fn LiteRtGetQuantizationTypeId( tensor: LiteRtTensor, @@ -690,7 +661,7 @@ pub const kLiteRtTensorBufferTypeVulkanBufferPacked: LiteRtTensorBufferType = 46 pub const kLiteRtTensorBufferTypeUserCustomBuffer: LiteRtTensorBufferType = 100; pub const kLiteRtTensorBufferTypeOpenVINOTensorBuffer: LiteRtTensorBufferType = 100; pub const kLiteRtTensorBufferTypeUserCustomBufferEnd: LiteRtTensorBufferType = 199; -pub type LiteRtTensorBufferType = ::std::os::raw::c_uint; +pub type LiteRtTensorBufferType = ::std::os::raw::c_int; pub type LiteRtGpuDeviceId = *mut ::std::os::raw::c_void; pub type LiteRtGpuQueueId = *mut ::std::os::raw::c_void; pub type HwMemoryHandle = *mut ::std::os::raw::c_void; @@ -776,7 +747,7 @@ pub const kLiteRtEnvOptionTagAutoRegisterAccelerators: LiteRtEnvOptionTag = 24; pub const kLiteRtEnvOptionTagMinLoggerSeverity: LiteRtEnvOptionTag = 25; #[doc = " \\internal This is for internal use only, for a custom runtime."] pub const kLiteRtEnvOptionTagNull: LiteRtEnvOptionTag = 255; -pub type LiteRtEnvOptionTag = ::std::os::raw::c_uint; +pub type LiteRtEnvOptionTag = ::std::os::raw::c_int; #[doc = " An object that holds option data for the LiteRtEnvironment.\n\n @note This concrete type is part of the public API and is ABI stable."] #[repr(C)] #[derive(Copy, Clone)] @@ -810,10 +781,10 @@ impl Default for LiteRtMagicNumberConfig { } } #[repr(C)] -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct LiteRtMagicNumberConfigs { pub num_configs: i64, - pub configs: __IncompleteArrayField, + pub configs: [LiteRtMagicNumberConfig; 1usize], } impl Default for LiteRtMagicNumberConfigs { fn default() -> Self { @@ -841,10 +812,10 @@ impl Default for LiteRtMagicNumberVerification { } } #[repr(C)] -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct LiteRtMagicNumberVerifications { pub num_verifications: i64, - pub verifications: __IncompleteArrayField, + pub verifications: [LiteRtMagicNumberVerification; 1usize], } impl Default for LiteRtMagicNumberVerifications { fn default() -> Self { @@ -1276,7 +1247,7 @@ pub const kLiteRtOpCodeTflDilate: LiteRtOpCode = 203; pub const kLiteRtOpCodeShloRngBitGenerator: LiteRtOpCode = 204; pub const kLiteRtOpCodeTflReduceWindow: LiteRtOpCode = 205; pub const kLiteRtOpCodeShloComposite: LiteRtOpCode = 206; -pub type LiteRtOpCode = ::std::os::raw::c_uint; +pub type LiteRtOpCode = ::std::os::raw::c_int; extern "C" { pub fn LiteRtGetTensorTypeId( tensor: LiteRtTensor, @@ -2685,7 +2656,7 @@ pub const LiteRtEventTypeOpenCl: LiteRtEventType = 2; pub const LiteRtEventTypeEglSyncFence: LiteRtEventType = 3; pub const LiteRtEventTypeEglNativeSyncFence: LiteRtEventType = 4; pub const LiteRtEventTypeCustom: LiteRtEventType = 5; -pub type LiteRtEventType = ::std::os::raw::c_uint; +pub type LiteRtEventType = ::std::os::raw::c_int; pub type LiteRtCustomEvent = *mut LiteRtCustomEventT; #[repr(C)] #[derive(Debug, Default, Copy, Clone)] @@ -2790,12 +2761,12 @@ pub const kLiteRtSchedulingInfoFieldOriginalUid: LiteRtSchedulingInfoField = 1; pub const kLiteRtSchedulingInfoFieldDebugFeatureId: LiteRtSchedulingInfoField = 2; pub const kLiteRtSchedulingInfoFieldJobPriority: LiteRtSchedulingInfoField = 4; pub const kLiteRtSchedulingInfoFieldGroupId: LiteRtSchedulingInfoField = 8; -pub type LiteRtSchedulingInfoField = ::std::os::raw::c_uint; +pub type LiteRtSchedulingInfoField = ::std::os::raw::c_int; pub const kLiteRtSchedulingInfoGroupIdSize: _bindgen_ty_1 = 16; -pub type _bindgen_ty_1 = ::std::os::raw::c_uint; +pub type _bindgen_ty_1 = ::std::os::raw::c_int; pub const kLiteRtSchedulingInfoJobPriorityHighest: LiteRtSchedulingJobPriority = 0; pub const kLiteRtSchedulingInfoJobPriorityLowest: LiteRtSchedulingJobPriority = 1000; -pub type LiteRtSchedulingJobPriority = ::std::os::raw::c_uint; +pub type LiteRtSchedulingJobPriority = ::std::os::raw::c_int; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct LiteRtSchedulingInfo { @@ -3031,11 +3002,11 @@ pub const kLiteRtCompilerOptionsPartitionStrategyDefault: LiteRtCompilerOptionsP 0; pub const kLiteRtCompilerOptionsPartitionStrategyWeaklyConnected: LiteRtCompilerOptionsPartitionStrategy = 1; -pub type LiteRtCompilerOptionsPartitionStrategy = ::std::os::raw::c_uint; +pub type LiteRtCompilerOptionsPartitionStrategy = ::std::os::raw::c_int; pub const kLiteRtCpuKernelModeXnnpack: LiteRtCpuKernelMode = 0; pub const kLiteRtCpuKernelModeReference: LiteRtCpuKernelMode = 1; pub const kLiteRtCpuKernelModeBuiltin: LiteRtCpuKernelMode = 2; -pub type LiteRtCpuKernelMode = ::std::os::raw::c_uint; +pub type LiteRtCpuKernelMode = ::std::os::raw::c_int; pub const DEFAULT: LiteRtProfilerEventType = 1; pub const OPERATOR_INVOKE_EVENT: LiteRtProfilerEventType = 2; pub const DELEGATE_OPERATOR_INVOKE_EVENT: LiteRtProfilerEventType = 4; @@ -3045,7 +3016,7 @@ pub const TELEMETRY_EVENT: LiteRtProfilerEventType = 32; pub const TELEMETRY_REPORT_SETTINGS: LiteRtProfilerEventType = 64; pub const TELEMETRY_DELEGATE_EVENT: LiteRtProfilerEventType = 128; pub const TELEMETRY_DELEGATE_REPORT_SETTINGS: LiteRtProfilerEventType = 256; -pub type LiteRtProfilerEventType = ::std::os::raw::c_uint; +pub type LiteRtProfilerEventType = ::std::os::raw::c_int; #[repr(C)] #[derive(Debug, Default, Copy, Clone)] pub struct LiteRtMemoryUsage { diff --git a/litert-sys/tests/layout.rs b/litert-sys/tests/layout.rs new file mode 100644 index 0000000..fd41c90 --- /dev/null +++ b/litert-sys/tests/layout.rs @@ -0,0 +1,27 @@ +//! Layout of the ABI-stable structs whose layout differs between compilers. +//! `litert_layout.h` asserts these sizes and offsets in C++; the bindings of a +//! target generated on another host's ABI break them silently, as the Windows +//! MSVC bindings once seeded from macOS put `dimensions` at offset 4. + +use std::mem::{size_of, MaybeUninit}; +use std::ptr::addr_of; + +#[test] +fn litert_layout_matches_the_header() { + let layout = MaybeUninit::::uninit(); + let base = layout.as_ptr(); + let offset = |field: *const u8| field as usize - base as usize; + let dimensions = offset(unsafe { addr_of!((*base).dimensions) }.cast()); + let strides = offset(unsafe { addr_of!((*base).strides) }.cast()); + + // MSVC keeps `unsigned int rank : 7` and `bool has_strides : 1` in two + // allocation units of their own; GCC and Clang pack both into one. + let (size, expected_dimensions, expected_strides) = if cfg!(target_env = "msvc") { + (72, 8, 40) + } else { + (68, 4, 36) + }; + assert_eq!(size_of::(), size, "size"); + assert_eq!(dimensions, expected_dimensions, "offset of dimensions"); + assert_eq!(strides, expected_strides, "offset of strides"); +} diff --git a/litert/build.rs b/litert/build.rs index 5834519..95c4859 100644 --- a/litert/build.rs +++ b/litert/build.rs @@ -4,10 +4,14 @@ //! //! `litert-sys` declares `links = "LiteRt"` and emits `cargo:lib_dir=...`, //! which Cargo exposes here as `DEP_LITERT_LIB_DIR`. +//! +//! MSVC's link.exe has no rpath: on Windows the DLLs are found through PATH, +//! which Cargo extends with `litert-sys`'s link directory (see its build.rs). fn main() { println!("cargo:rerun-if-env-changed=DEP_LITERT_LIB_DIR"); - if let Ok(dir) = std::env::var("DEP_LITERT_LIB_DIR") { + let msvc = std::env::var("CARGO_CFG_TARGET_ENV").is_ok_and(|env| env == "msvc"); + if let (Ok(dir), false) = (std::env::var("DEP_LITERT_LIB_DIR"), msvc) { println!("cargo:rustc-link-arg=-Wl,-rpath,{dir}"); } } diff --git a/litert/src/element_type.rs b/litert/src/element_type.rs index f91400c..407fe66 100644 --- a/litert/src/element_type.rs +++ b/litert/src/element_type.rs @@ -7,27 +7,29 @@ use litert_sys as sys; /// types or IEEE/INT naming (`Float16`, `Int32`) where no Rust analogue exists. #[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +// The C enum is `unsigned int` under GCC and Clang but `int` under MSVC, so +// each discriminant is cast to the `u32` this enum is. #[repr(u32)] #[non_exhaustive] pub enum ElementType { - None = sys::kLiteRtElementTypeNone, - Bool = sys::kLiteRtElementTypeBool, - Int2 = sys::kLiteRtElementTypeInt2, - Int4 = sys::kLiteRtElementTypeInt4, - Int8 = sys::kLiteRtElementTypeInt8, - Int16 = sys::kLiteRtElementTypeInt16, - Int32 = sys::kLiteRtElementTypeInt32, - Int64 = sys::kLiteRtElementTypeInt64, - UInt8 = sys::kLiteRtElementTypeUInt8, - UInt16 = sys::kLiteRtElementTypeUInt16, - UInt32 = sys::kLiteRtElementTypeUInt32, - UInt64 = sys::kLiteRtElementTypeUInt64, - Float16 = sys::kLiteRtElementTypeFloat16, - BFloat16 = sys::kLiteRtElementTypeBFloat16, - Float32 = sys::kLiteRtElementTypeFloat32, - Float64 = sys::kLiteRtElementTypeFloat64, - Complex64 = sys::kLiteRtElementTypeComplex64, - Complex128 = sys::kLiteRtElementTypeComplex128, + None = sys::kLiteRtElementTypeNone as u32, + Bool = sys::kLiteRtElementTypeBool as u32, + Int2 = sys::kLiteRtElementTypeInt2 as u32, + Int4 = sys::kLiteRtElementTypeInt4 as u32, + Int8 = sys::kLiteRtElementTypeInt8 as u32, + Int16 = sys::kLiteRtElementTypeInt16 as u32, + Int32 = sys::kLiteRtElementTypeInt32 as u32, + Int64 = sys::kLiteRtElementTypeInt64 as u32, + UInt8 = sys::kLiteRtElementTypeUInt8 as u32, + UInt16 = sys::kLiteRtElementTypeUInt16 as u32, + UInt32 = sys::kLiteRtElementTypeUInt32 as u32, + UInt64 = sys::kLiteRtElementTypeUInt64 as u32, + Float16 = sys::kLiteRtElementTypeFloat16 as u32, + BFloat16 = sys::kLiteRtElementTypeBFloat16 as u32, + Float32 = sys::kLiteRtElementTypeFloat32 as u32, + Float64 = sys::kLiteRtElementTypeFloat64 as u32, + Complex64 = sys::kLiteRtElementTypeComplex64 as u32, + Complex128 = sys::kLiteRtElementTypeComplex128 as u32, } impl ElementType { diff --git a/xtask/src/main.rs b/xtask/src/main.rs index ff4a41a..5f52a51 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -313,10 +313,13 @@ fn find_libclang() -> Result { "/Library/Developer/CommandLineTools/usr/lib", "/usr/lib/x86_64-linux-gnu", "/usr/lib64", + // LLVM's Windows installer. + r"C:\Program Files\LLVM\bin", ] { if Path::new(guess).join("libclang.dylib").exists() || Path::new(guess).join("libclang.so").exists() || Path::new(guess).join("libclang.so.1").exists() + || Path::new(guess).join("libclang.dll").exists() { return Ok(guess.to_string()); }