From 1f5bd5d1615fda2f7fbe1c924167b7c39d8dd617 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Sun, 25 Oct 2026 10:51:48 +0300 Subject: [PATCH 1/2] litert-sys: link on Windows MSVC without an import library Google ships libLiteRt.dll with no LiteRt.lib, so every MSVC link of -lLiteRt failed with LNK1181. build.rs now reads the DLL's exports (the object crate), writes a .def and makes LiteRt.lib with MSVC's lib.exe (found through cc::windows_registry), in OUT_DIR, with the prebuilt DLLs linked beside it. Cargo puts that link directory on PATH for cargo run and cargo test, so tests find the DLLs with no setup. link.exe has no rpath and warned about -Wl,-rpath (LNK4044): neither litert-sys nor litert passes it on MSVC any more. xtask also looks for libclang where LLVM's Windows installer puts it. Co-Authored-By: Claude Opus 5.5 --- Cargo.toml | 3 ++ README.md | 12 ++++++- litert-sys/Cargo.toml | 3 ++ litert-sys/build.rs | 80 +++++++++++++++++++++++++++++++++++++++++-- litert/build.rs | 6 +++- xtask/src/main.rs | 3 ++ 6 files changed, 102 insertions(+), 5 deletions(-) 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/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/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()); } From e1297a16e513f05e4aae8076b43d857e773f9f70 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Sun, 25 Oct 2026 10:51:48 +0300 Subject: [PATCH 2/2] litert-sys: regenerate the Windows MSVC bindings on Windows They were seeded from aarch64-apple-darwin.rs, so LiteRtLayout had the GCC layout: 68 bytes, dimensions at offset 4. MSVC keeps the rank and has_strides bit-fields in two allocation units, which makes it 72 bytes with dimensions at offset 8, as litert_layout.h asserts. Tensor shapes read back garbled ([0, 10] for [10, 10] in add_10x10) and running any model failed. Regenerated with `cargo xtask regen-bindings --target x86_64-pc-windows-msvc` (libclang 18.1.1). The C enums are int under MSVC, so ElementType casts its discriminants to u32. A new test checks LiteRtLayout's size and offsets against the header on every target. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 16 ++++ .../src/bindings/x86_64-pc-windows-msvc.rs | 93 +++++++------------ litert-sys/tests/layout.rs | 27 ++++++ litert/src/element_type.rs | 38 ++++---- 4 files changed, 95 insertions(+), 79 deletions(-) create mode 100644 litert-sys/tests/layout.rs 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/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/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 {