9a8508ebff
Adds support for `asset: 'asset-id'` in `FfiNative`s.
Symbols in assets are resolved by
1. the provided [asset] (the bulk of this CL),
2. the native resolver set with `Dart_SetFfiNativeResolver` in
`dart_api.h` (old behavior), and
3. the current process.
Symbols in assets are resolved by the Dart VM through the mapping
provided in the `vm:ffi:native_assets` library:
```
@pragma('vm:ffi:native-assets', {
"linux_x64": {
"<asset1>": [
"absolute",
"<absolute-path>"
],
"<asset2>": [
"relative",
"<relative-path>"
],
"<asset3>": [
"system",
"<system-path>"
],
"<asset4>": [
"process",
],
"<asset5>": [
"executable",
],
}
})
library;
```
There are 5 asset path types. Symbols resolved in these asset types are
resolved as follows.
A. Absolute paths are dlopened, after which the symbol is looked up.
B. Relative paths are resolved relative to `Platform.script.uri`, and
dlopened, after which the symbol is looked up.
C. System paths are treated as absolute paths. (But we might explicitly
try to use `PATH` in the future.)
D. Symbols looked up in Process assets are looked up in the current
process.
E. Symbols looked up in Executable assets are looked up in the current
executable.
The native assets mapping can be provided in three ways.
X. In the invocation to `gen_kernel` with the `--native-assets`
argument. This uses the gen_kernel entry point for compiling
kernel.
Y. By placing a `native_assets.yaml` next to the
`package_config.json` used by the isolate group. This works for
`dart <source file>` and `Isolate.spawnUri(<dart source file>)`.
This uses the kernel_service entry point for compiling kernel.
Z. By sending a String containing the native assets mapping when
invoking the kernel_service directly from within the VM. Currently,
only used for unit tests.
TEST=tests/ffi/native_assets/asset_*_test.dart (X)
TEST=tests/ffi/native_assets/infer_native_assets_yaml_*.dart (Y)
TEST=runtime/vm/ffi/native_assets_test.cc (Z)
The library is synthesized from yaml in pkg:vm as AST.
Design doc: go/dart-native-assets
Bug: https://github.com/dart-lang/sdk/issues/49803
Change-Id: I8bf7000bfcc03b362948efd3baf168838948e6b9
Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-precomp-ffi-qemu-linux-release-arm-try,vm-kernel-nnbd-mac-debug-arm64-try,vm-kernel-nnbd-mac-debug-x64-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-kernel-precomp-win-debug-x64c-try,vm-kernel-reload-linux-debug-x64-try,vm-kernel-reload-rollback-linux-debug-x64-try,vm-kernel-precomp-nnbd-linux-debug-x64-try,vm-kernel-precomp-win-debug-x64c-try,vm-kernel-precomp-nnbd-mac-release-arm64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/264842
Reviewed-by: Martin Kustermann <kustermann@google.com>
254 lines
7.3 KiB
Dart
254 lines
7.3 KiB
Dart
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'package:front_end/src/api_unstable/vm.dart';
|
|
import 'package:vm/kernel_front_end.dart';
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
import 'diagnostic_message.dart';
|
|
|
|
class NativeAssetsValidator {
|
|
final ErrorDetector errorDetector;
|
|
List<Uri> involvedFiles = [];
|
|
|
|
NativeAssetsValidator(this.errorDetector);
|
|
|
|
void _reportError(String message, {Severity severity = Severity.error}) {
|
|
errorDetector(NativeAssetsDiagnosticMessage(
|
|
message: message,
|
|
involvedFiles: involvedFiles,
|
|
severity: severity,
|
|
));
|
|
}
|
|
|
|
/// Parses and validates [nativeAssetsYamlString].
|
|
///
|
|
/// Returns the parsed result if valid, otherwise `null`.
|
|
///
|
|
/// Reports errors to [errorDetector].
|
|
Map? parseAndValidate(String nativeAssetsYamlString) {
|
|
late final Object? nativeAssetsYaml;
|
|
try {
|
|
nativeAssetsYaml = loadYaml(nativeAssetsYamlString) as Object?;
|
|
} on YamlException catch (e) {
|
|
_reportError("File not formatted as yaml: $e $nativeAssetsYamlString.");
|
|
return null;
|
|
}
|
|
if (nativeAssetsYaml == null) {
|
|
_reportError("File not formatted as yaml: $nativeAssetsYamlString.");
|
|
return null;
|
|
}
|
|
|
|
final nativeAssetsYamlPruned = _validateAndPrune(nativeAssetsYaml);
|
|
|
|
return nativeAssetsYamlPruned;
|
|
}
|
|
|
|
static const _formatVersionKey = 'format-version';
|
|
static const _nativeAssetsKey = 'native-assets';
|
|
|
|
/// Modifies [object] to prune invalid data that only causes warnings.
|
|
///
|
|
/// Returns `null` if invalid.
|
|
Map? _validateAndPrune(Object object) {
|
|
if (object is! Map) {
|
|
_reportError('Unexpected root object: $object. Expected a Map.');
|
|
return null;
|
|
}
|
|
var isValid = true;
|
|
if (!object.containsKey(_formatVersionKey)) {
|
|
_reportError('Expected $_formatVersionKey in $object.');
|
|
isValid = false;
|
|
} else {
|
|
isValid &= _validateFormatVersion(object[_formatVersionKey]);
|
|
}
|
|
if (!object.containsKey(_nativeAssetsKey)) {
|
|
_reportError('Expected $_nativeAssetsKey in $object.');
|
|
isValid = false;
|
|
} else {
|
|
var nativeAssets = object[_nativeAssetsKey];
|
|
if (nativeAssets is Map) {
|
|
// Make native-assets modifiable.
|
|
nativeAssets = Map.from(nativeAssets);
|
|
object = Map.from(object);
|
|
object[_nativeAssetsKey] = nativeAssets;
|
|
}
|
|
isValid &= _validateNativeAssets(nativeAssets);
|
|
}
|
|
if (!isValid) return null;
|
|
return object;
|
|
}
|
|
|
|
/// The VM cannot consume newer formats with breaking changes.
|
|
static const _maximumMajor = 1;
|
|
|
|
/// This VM cannot consume too old versions.
|
|
static const _minimumMajor = 1;
|
|
static const _minimumMinor = 0;
|
|
static const _minimumPatch = 0;
|
|
static const _minimumVersion = [_minimumMajor, _minimumMinor, _minimumPatch];
|
|
|
|
/// Validate [_formatVersionKey] contents.
|
|
///
|
|
/// Checks the major version upper bound and all version lower bounds.
|
|
bool _validateFormatVersion(Object object) {
|
|
if (object is! List || object.length != 3) {
|
|
_reportError(
|
|
'Unexpected format version: $object. Expected a List with length 3.');
|
|
return false;
|
|
}
|
|
final major = object[0];
|
|
final minor = object[1];
|
|
final patch = object[2];
|
|
if (major > _maximumMajor) {
|
|
_reportError(
|
|
'Unexpected format version: $object. Major version above $_maximumMajor not supported.');
|
|
return false;
|
|
}
|
|
if (major > _minimumMajor) return true;
|
|
if (major < _minimumMajor) {
|
|
_reportError(
|
|
'Unexpected format version: $object. Versions below $_minimumVersion not supported.');
|
|
return false;
|
|
}
|
|
if (minor > _minimumMinor) return true;
|
|
if (minor < _minimumMinor) {
|
|
_reportError(
|
|
'Unexpected format version: $object. Versions below $_minimumVersion not supported.');
|
|
return false;
|
|
}
|
|
if (patch < _minimumPatch) {
|
|
_reportError(
|
|
'Unexpected format version: $object. Versions below $_minimumVersion not supported.');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Validate [_nativeAssetsKey] contents.
|
|
bool _validateNativeAssets(Object object) {
|
|
if (object is! Map) {
|
|
_reportError('Unexpected native-assets: $object. Expected a Map.');
|
|
return false;
|
|
}
|
|
var isValid = true;
|
|
final invalidTargets = [];
|
|
for (final entry in object.entries) {
|
|
final target = entry.key;
|
|
final validTarget = _validateTarget(target);
|
|
if (!validTarget) {
|
|
invalidTargets.add(target);
|
|
} else {
|
|
isValid &= _validateAssets(entry.value);
|
|
}
|
|
}
|
|
for (final target in invalidTargets) {
|
|
object.remove(target);
|
|
}
|
|
return isValid;
|
|
}
|
|
|
|
// TODO(http://dartbug.com/49803): Get this from `package:native` when that
|
|
// is merged.
|
|
static const _validTargets = [
|
|
'android_arm',
|
|
'android_arm64',
|
|
'android_ia32',
|
|
'android_x64',
|
|
'fuchsia_arm64',
|
|
'fuchsia_x64',
|
|
'ios_arm',
|
|
'ios_arm64',
|
|
'ios_x64',
|
|
'iossimulator_arm64',
|
|
'iossimulator_x64',
|
|
'linux_arm',
|
|
'linux_arm64',
|
|
'linux_ia32',
|
|
'linux_riscv32',
|
|
'linux_riscv64',
|
|
'linux_x64',
|
|
'macos_arm64',
|
|
'macos_x64',
|
|
'windows_arm64',
|
|
'windows_ia32',
|
|
'windows_x64',
|
|
];
|
|
|
|
/// An invalid target is considered only a warning.
|
|
///
|
|
/// For example, we may remove ia32 at some point, but we might have existing
|
|
/// yamls lingering around.
|
|
bool _validateTarget(Object object) {
|
|
if (!_validTargets.contains(object)) {
|
|
_reportError('Unexpected target: $object. Valid targets: $_validTargets.',
|
|
severity: Severity.warning);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool _validateAssets(Object object) {
|
|
if (object is! Map) {
|
|
_reportError('Unexpected assets mapping: $object. Expected a Map.');
|
|
return false;
|
|
}
|
|
var isValid = true;
|
|
for (final entry in object.entries) {
|
|
isValid &= _validateAsset(entry.key);
|
|
isValid &= _validateAssetPath(entry.value);
|
|
}
|
|
return isValid;
|
|
}
|
|
|
|
bool _validateAsset(Object object) {
|
|
if (object is! String) {
|
|
_reportError('Unexpected assets name: $object. Expected a String.');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
final _pathTypesWithPath = [
|
|
'absolute',
|
|
'relative',
|
|
'system',
|
|
];
|
|
|
|
late final _validPathTypes = [
|
|
..._pathTypesWithPath,
|
|
'executable',
|
|
'process',
|
|
]..sort();
|
|
|
|
bool _validateAssetPath(Object object) {
|
|
if (object is! List || object.isEmpty) {
|
|
_reportError(
|
|
'Unexpected asset path: $object. Expected a non-empty List.');
|
|
return false;
|
|
}
|
|
final pathType = object[0];
|
|
if (pathType is! String || !_validPathTypes.contains(pathType)) {
|
|
_reportError(
|
|
'Unexpected path type: $pathType. Valid path types: $_validPathTypes.');
|
|
return false;
|
|
}
|
|
final needsPath = _pathTypesWithPath.contains(pathType);
|
|
final listLength = 1 + (needsPath ? 1 : 0);
|
|
if (object.length != listLength) {
|
|
_reportError(
|
|
'Unexpected asset path: $object. Expected list with $listLength elements.');
|
|
return false;
|
|
}
|
|
if (needsPath) {
|
|
final path = object[1];
|
|
if (path is! String) {
|
|
_reportError('Unexpected path: $path. Expected a String.');
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|