Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 3 additions & 0 deletions litert-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
80 changes: 77 additions & 3 deletions litert-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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}");
Expand Down
Loading