Skip to content

fix(litertlm): detect stream-callback ABI at runtime (LiteRT-LM v0.15.0) - #410

Open
DenisovAV wants to merge 3 commits into
mainfrom
fix/litertlm-v0150-stream-abi
Open

fix(litertlm): detect stream-callback ABI at runtime (LiteRT-LM v0.15.0)#410
DenisovAV wants to merge 3 commits into
mainfrom
fix/litertlm-v0150-stream-abi

Conversation

@DenisovAV

Copy link
Copy Markdown
Owner

LiteRT-LM v0.15.0 changed LiteRtLmStreamCallback from

void (*)(void* data, const char* chunk, bool is_final, const char* error_msg)

to

void (*)(void* data, const LiteRtLmStreamChunk* chunk)

with no compatibility overload, and the C API exposes no version symbol to detect it. A shim built for the old shape keeps linking and starts reading arguments 3 and 4 out of whatever the registers happen to hold — strdup() then runs on a garbage pointer. That surfaced as an ACCESS_VIOLATION in StreamProxy.dll on Windows and a FormatException: Missing extension byte on macOS, neither of which points anywhere near the real cause.

What this does

stream_proxy.c now probes the loaded libLiteRtLm for litert_lm_stream_chunk_get_text — a symbol that simply does not exist before v0.15.0 — and registers the matching callback shape. One binary works against both ABIs, which keeps the upcoming native bump revertible instead of one-way.

Two details worth flagging for review:

  • v0.15.0 reports "no error" as an empty string, not NULL. Dart treats any non-NULL error pointer as a failed stream, so the adapter forwards NULL unless error_msg[0] is set — without that, every ordinary token would abort the stream.
  • The accessors are resolved lazily via dlsym/GetProcAddress rather than linked, so the shim still builds and runs against v0.14.0.

litert_lm_client.dart also decodes native strings leniently now. Utf8Pointer.toDartString throws on malformed bytes, so on the error path a native failure was being replaced by a complaint about character encoding, losing the only message that explained it.

Verification

Same test, same machine, both native versions:

natives result
native-v0.14.0 (shipped) All tests passed
v0.15.0 (2117fc43) All tests passed

The v0.14.0 run is the one that matters: it is the currently published bundle, so this proves no regression for existing users. flutter analyze clean, 32 package unit tests pass.

Not included

This is the adapter only — no native bump, no version changes. The native-v0.15.0 migration follows separately so that a bisect can distinguish "the adapter broke it" from "the bump broke it".

LiteRT-LM v0.15.0 changed LiteRtLmStreamCallback from

  void (*)(void*, const char* chunk, bool is_final, const char* error_msg)

to

  void (*)(void*, const LiteRtLmStreamChunk* chunk)

with no compatibility overload and no version symbol in the C API. A shim
built for the old shape keeps linking and reads arguments 3 and 4 out of
whatever the registers hold, so strdup() runs on a garbage pointer: an
access violation on Windows, malformed UTF-8 on macOS.

Probe for litert_lm_stream_chunk_get_text in the loaded libLiteRtLm and
register the matching callback. One binary now works against both ABIs,
so the native bump stays revertible.

Also decode native strings leniently. Utf8Pointer.toDartString throws on
malformed bytes, which on the error path replaces the real native message
with a complaint about character encoding — the exact misdirection that
made this break hard to trace.

Verified on macOS against both native-v0.14.0 (shipped) and v0.15.0.
The ABI probe uses dlsym(RTLD_DEFAULT, ...). Apple's libc declares
RTLD_DEFAULT unconditionally, but on glibc it lives behind __USE_GNU, so
<dlfcn.h> hides it unless _GNU_SOURCE is defined first. The macOS build
compiled clean and Linux failed with "RTLD_DEFAULT undeclared".

Define it ahead of every include, Linux-only, so the Apple and Windows
paths are untouched.
The stream-callback ABI break, the NPU stacks the build does not produce, the
LiteRT pin as a second ABI surface, and the deployment target that silently
tracks the build host — each cost real time this migration, and none of them
were written down anywhere the next build would look.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant