Skip to content
Closed
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
8 changes: 8 additions & 0 deletions test/fixtures/litert_lm/c_api_version.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,11 @@ int litert_lm_native_in_process_litert_abi_version(void) {
// A missing gate must fail the test process before an incompatible call can
// accidentally look like a supported engine initialization.
void *litert_lm_engine_settings_create(void) { abort(); }

#ifdef SMOKE_SYMBOLS
void litert_lm_engine_create(void) { abort(); }
void litert_lm_engine_delete(void) { abort(); }
void stream_proxy_create(void) { abort(); }
void stream_proxy_free_string(void) { abort(); }
void stream_proxy_delete(void) { abort(); }
#endif
79 changes: 79 additions & 0 deletions test/unit/tooling/litert_lm_library_smoke_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,83 @@ void main() {
throwsA(same(failure)),
);
});

group('in-process LiteRT marker', () {
final skip = Platform.isWindows ? 'compiles fixtures with cc' : null;
final libraries = <String, DynamicLibrary>{};
late Directory fixtures;

setUpAll(() {
if (skip != null) return;
fixtures = Directory.systemTemp.createTempSync('litert_marker_');
for (final (name, defines) in [
('absent', ['-DSMOKE_SYMBOLS']),
('zero', ['-DSMOKE_SYMBOLS', '-DIN_PROCESS_LITERT_ABI=0']),
('one', ['-DSMOKE_SYMBOLS', '-DIN_PROCESS_LITERT_ABI=1']),
]) {
final output = path.join(
fixtures.path,
'$name.${Platform.isMacOS ? 'dylib' : 'so'}',
);
final result = Process.runSync('cc', [
'-shared',
'-fPIC',
...defines,
'test/fixtures/litert_lm/c_api_version.c',
'-o',
output,
]);
expect(result.exitCode, 0, reason: '${result.stderr}');
libraries[name] = DynamicLibrary.open(output);
}
});

tearDownAll(() {
if (skip == null) fixtures.deleteSync(recursive: true);
});

test('desktop runtimes must export the marker with value 1', () {
for (final abi in [Abi.linuxX64, Abi.linuxArm64, Abi.windowsX64]) {
requireInProcessLiteRtMarker(libraries['one']!, abi);
for (final name in ['absent', 'zero']) {
expect(
() => requireInProcessLiteRtMarker(libraries[name]!, abi),
throwsA(
isA<StateError>().having(
(error) => error.message,
'message',
contains('litert_lm_native_in_process_litert_abi_version'),
),
),
);
}
}
}, skip: skip);

Map<String, Object?> runSmoke(String fixture, Abi abi) =>
runLiteRtLmLibrarySmoke(
abi: abi,
directory: null,
releaseTag: 'v0.19.1-2',
openLibrary: (_) => libraries[fixture]!,
);

test('the smoke run enforces the marker and reports its value', () {
for (final abi in [Abi.linuxX64, Abi.windowsX64]) {
expect(runSmoke('one', abi)['inProcessLiteRtAbi'], 1);
for (final name in ['absent', 'zero']) {
expect(() => runSmoke(name, abi), throwsStateError);
}
}
}, skip: skip);

test('the smoke run reports no marker where it is not required', () {
expect(runSmoke('absent', Abi.macosArm64)['inProcessLiteRtAbi'], isNull);
}, skip: skip);

test('Apple and Android runtimes are not required to export it', () {
requireInProcessLiteRtMarker(libraries['absent']!, Abi.macosArm64);
requireInProcessLiteRtMarker(libraries['absent']!, Abi.androidArm64);
}, skip: skip);
});
}
52 changes: 46 additions & 6 deletions tool/litert_lm_library_smoke.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dart:convert';
import 'dart:ffi';
import 'dart:io';

import 'package:llamadart/src/backends/litert_lm/litert_lm_c_api_version.dart';
import 'package:llamadart/src/backends/litert_lm/litert_lm_platform.dart';
import 'package:llamadart/src/backends/litert_lm/litert_lm_runtime.dart'
as runtime;
import 'package:llamadart/src/hook/native_release_pins.dart';
Expand All @@ -26,19 +28,57 @@ void main() {
return;
}

print(
'RESULT litert_lm_library ${jsonEncode(runLiteRtLmLibrarySmoke(abi: Abi.current(), directory: Platform.environment[_litertLmLibDirEnv], releaseTag: liteRtLmReleaseTag))}',
);
}

/// Opens the runtime, checks its symbols and in-process LiteRT marker, and
/// returns the RESULT payload.
Map<String, Object?> runLiteRtLmLibrarySmoke({
required Abi abi,
required String? directory,
required String releaseTag,
DynamicLibrary Function(String)? openLibrary,
}) {
final opened = openLiteRtLmSmokeLibrary(
directory: Platform.environment[_litertLmLibDirEnv],
abi: Abi.current(),
cApiV1: liteRtLmSmokeUsesCApiV1(liteRtLmReleaseTag),
directory: directory,
abi: abi,
cApiV1: liteRtLmSmokeUsesCApiV1(releaseTag),
openLibrary: openLibrary,
);
final library = opened.library;
for (final symbol in _requiredSymbols) {
library.lookup<NativeFunction<Void Function()>>(symbol);
}

print(
'RESULT litert_lm_library ${jsonEncode({'abi': Abi.current().toString(), 'library': opened.path, 'symbols': _requiredSymbols})}',
);
return {
'abi': abi.toString(),
'library': opened.path,
'symbols': _requiredSymbols,
'inProcessLiteRtAbi': requireInProcessLiteRtMarker(library, abi),
};
}

/// Fails when a bundled Linux or Windows runtime does not report in-process
/// LiteRT, which gates native embeddings there; a repin that drops the
/// `litert_lm_native_in_process_litert_abi_version` export would otherwise
/// surface only on hardware. Returns the reported value, or null where the
/// marker is not required.
int? requireInProcessLiteRtMarker(DynamicLibrary library, Abi abi) {
if (abi != Abi.linuxX64 && abi != Abi.linuxArm64 && abi != Abi.windowsX64) {
return null;
}
final reported = readLiteRtLmInProcessLiteRtAbiVersion(library);
if (reported != liteRtLmInProcessLiteRtAbiVersion) {
throw StateError(
'The $abi LiteRT-LM runtime reports '
'litert_lm_native_in_process_litert_abi_version '
'${reported ?? '(not exported)'}, expected '
'$liteRtLmInProcessLiteRtAbiVersion.',
);
}
return reported;
}

bool _isSupportedHost() {
Expand Down
7 changes: 6 additions & 1 deletion website/docs/maintainers/native-and-web-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,12 @@ hook and Apple companion source now pin published
[v0.19.1-2](https://github.com/leehack/litert-lm-native/releases/tag/v0.19.1-2),
owner `0b15fb34` / upstream `7a03724c`, which keeps C API 1.0.0 and the
`v0.19.0` exports and adds the in-process LiteRT marker
`litert_lm_native_in_process_litert_abi_version` (1 on every platform). The
`litert_lm_native_in_process_litert_abi_version` (1 on every platform); the
`LiteRT-LM Smoke` library step fails when the bundled Linux x64 or Windows
x64 runtime lacks the export or does not report 1, and prints the value as
`inProcessLiteRtAbi`; Linux arm64 has no CI runner for it. Rolling the pin back
to a pre-marker tag (`v0.18.1-4`, `v0.19.0`, `v0.19.1`, `v0.19.1-1`) therefore
fails the smoke on Linux and Windows by design. The
earlier `-1` rebuild links LiteRT into the Linux and Windows
`libLiteRtLm` ([litert-lm-native#112](https://github.com/leehack/litert-lm-native/pull/112));
its Apple and Android binaries are identical to `v0.19.1`. Upstream
Expand Down
Loading