a44a524bb0
This adds the `Native.addressOf` function, which takes a constant tear- off of a method annotated with `@Native` as a parameter and returns a pointer to the underlying C function. The CFE will resolve these calls in two steps: First, the existing transformer for `@Native` methods adds a pragma describing the fully- resolved native annotation (with the asset id inferred from the library if not given explicitly). Then, the FFI use sites transformer rewrites calls to `Native.addressOf` by searching for this pragma on the passed function and passing the `Native` constants to `Native._addressOf`. The latter method is implemented in the VM, which can re-use existing parts already used for `@Native` calls. An alternative implementation strategy would have been to forward `addressOf` calls to `Native.ffi_resolver_function` directly without any special consideration in the VM. However, the chosen approach makes it easier to support static linking in the future, as this requires unresolved symbols in the generated assembly. Closes https://github.com/dart-lang/sdk/issues/50552 TEST=pkg/vm/testcases/transformations/ffi/ffinative.dart TEST=tests/ffi/native_assets/asset_*_test.dart TEST=tests/ffi/vmspecific_static_checks_ffinative_test.dart TEST=pkg/analyzer/test/src/diagnostics/ffi_native_test.dart Cq-Include-Trybots: luci.dart.try:vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-optimization-level-linux-release-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-debug-x64c-try,vm-aot-win-release-x64-try,vm-kernel-precomp-linux-release-x64-try CoreLibraryReviewExempt: VM & dart2wasm only feature Change-Id: Ic8e3a390146dffd44c95578f975a4472db79a0ee Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/333920 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
533 lines
18 KiB
Dart
533 lines
18 KiB
Dart
// Copyright (c) 2016, 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:kernel/ast.dart';
|
|
import 'package:kernel/clone.dart';
|
|
import 'package:kernel/class_hierarchy.dart';
|
|
import 'package:kernel/core_types.dart';
|
|
import 'package:kernel/reference_from_index.dart';
|
|
import 'package:kernel/target/changed_structure_notifier.dart';
|
|
import 'package:kernel/target/targets.dart';
|
|
|
|
import '../transformations/call_site_annotator.dart' as callSiteAnnotator;
|
|
import '../transformations/lowering.dart' as lowering
|
|
show transformLibraries, transformProcedure;
|
|
import '../transformations/mixin_full_resolution.dart' as transformMixins
|
|
show transformLibraries;
|
|
import '../transformations/ffi/common.dart' as ffiHelper
|
|
show calculateTransitiveImportsOfDartFfiIfUsed;
|
|
import '../transformations/ffi/definitions.dart' as transformFfiDefinitions
|
|
show transformLibraries;
|
|
import '../transformations/ffi/native.dart' as transformFfiNative
|
|
show transformLibraries;
|
|
import '../transformations/ffi/use_sites.dart' as transformFfiUseSites
|
|
show transformLibraries;
|
|
|
|
/// Specializes the kernel IR to the Dart VM.
|
|
class VmTarget extends Target {
|
|
final TargetFlags flags;
|
|
|
|
Class? _growableList;
|
|
Class? _immutableList;
|
|
Class? _constMap;
|
|
Class? _constSet;
|
|
Class? _map;
|
|
Class? _set;
|
|
Class? _record;
|
|
Class? _oneByteString;
|
|
Class? _twoByteString;
|
|
Class? _smi;
|
|
Class? _double; // _Double, not double.
|
|
Class? _closure;
|
|
Class? _syncStarIterable;
|
|
|
|
VmTarget(this.flags);
|
|
|
|
@override
|
|
bool get enableNoSuchMethodForwarders => true;
|
|
|
|
@override
|
|
bool get supportsSetLiterals => false;
|
|
|
|
@override
|
|
int get enabledLateLowerings => LateLowering.none;
|
|
|
|
@override
|
|
bool get supportsLateLoweringSentinel => false;
|
|
|
|
@override
|
|
bool get useStaticFieldLowering => false;
|
|
|
|
@override
|
|
bool get supportsExplicitGetterCalls => true;
|
|
|
|
@override
|
|
int get enabledConstructorTearOffLowerings =>
|
|
ConstructorTearOffLowering.typedefs;
|
|
|
|
@override
|
|
String get name => 'vm';
|
|
|
|
// This is the order that bootstrap libraries are loaded according to
|
|
// `runtime/vm/object_store.h`.
|
|
@override
|
|
List<String> get extraRequiredLibraries => const <String>[
|
|
'dart:async',
|
|
'dart:collection',
|
|
'dart:convert',
|
|
'dart:developer',
|
|
'dart:ffi',
|
|
'dart:_internal',
|
|
'dart:isolate',
|
|
'dart:math',
|
|
|
|
// The library dart:mirrors may be ignored by the VM, e.g. when built in
|
|
// PRODUCT mode.
|
|
'dart:mirrors',
|
|
|
|
'dart:typed_data',
|
|
'dart:vmservice_io',
|
|
'dart:_vmservice',
|
|
'dart:_builtin',
|
|
'dart:nativewrappers',
|
|
'dart:io',
|
|
'dart:cli',
|
|
];
|
|
|
|
@override
|
|
List<String> get extraRequiredLibrariesPlatform => const <String>[];
|
|
|
|
void _patchVmConstants(CoreTypes coreTypes) {
|
|
// Fix Endian.host to be a const field equal to Endian.little instead of
|
|
// a final field. VM does not support big-endian architectures at the
|
|
// moment.
|
|
// Can't use normal patching process for this because CFE does not
|
|
// support patching fields.
|
|
// See http://dartbug.com/32836 for the background.
|
|
final Field host =
|
|
coreTypes.index.getField('dart:typed_data', 'Endian', 'host');
|
|
final Field little =
|
|
coreTypes.index.getField('dart:typed_data', 'Endian', 'little');
|
|
host.isConst = true;
|
|
host.initializer = new CloneVisitorNotMembers().clone(little.initializer!)
|
|
..parent = host;
|
|
}
|
|
|
|
@override
|
|
void performPreConstantEvaluationTransformations(
|
|
Component component,
|
|
CoreTypes coreTypes,
|
|
List<Library> libraries,
|
|
DiagnosticReporter diagnosticReporter,
|
|
{void Function(String msg)? logger,
|
|
ChangedStructureNotifier? changedStructureNotifier}) {
|
|
super.performPreConstantEvaluationTransformations(
|
|
component, coreTypes, libraries, diagnosticReporter,
|
|
logger: logger, changedStructureNotifier: changedStructureNotifier);
|
|
_patchVmConstants(coreTypes);
|
|
}
|
|
|
|
@override
|
|
List<String> get extraIndexedLibraries => const <String>[
|
|
// TODO(askesc): When the VM supports set literals, we no longer
|
|
// need to index dart:collection, as it is only needed for desugaring of
|
|
// const sets. We can remove it from this list at that time.
|
|
"dart:collection",
|
|
// TODO(askesc): This is for the VM host endian optimization, which
|
|
// could possibly be done more cleanly after the VM no longer supports
|
|
// doing constant evaluation on its own. See http://dartbug.com/32836
|
|
"dart:typed_data",
|
|
];
|
|
|
|
@override
|
|
void performModularTransformationsOnLibraries(
|
|
Component component,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
List<Library> libraries,
|
|
Map<String, String>? environmentDefines,
|
|
DiagnosticReporter diagnosticReporter,
|
|
ReferenceFromIndex? referenceFromIndex,
|
|
{void Function(String msg)? logger,
|
|
ChangedStructureNotifier? changedStructureNotifier}) {
|
|
transformMixins.transformLibraries(
|
|
this, coreTypes, hierarchy, libraries, referenceFromIndex);
|
|
logger?.call("Transformed mixin applications");
|
|
|
|
List<Library>? transitiveImportingDartFfi = ffiHelper
|
|
.calculateTransitiveImportsOfDartFfiIfUsed(component, libraries);
|
|
if (transitiveImportingDartFfi == null) {
|
|
logger?.call("Skipped ffi transformation");
|
|
} else {
|
|
transformFfiDefinitions.transformLibraries(
|
|
component,
|
|
coreTypes,
|
|
hierarchy,
|
|
transitiveImportingDartFfi,
|
|
diagnosticReporter,
|
|
referenceFromIndex,
|
|
changedStructureNotifier);
|
|
logger?.call("Transformed ffi definitions");
|
|
|
|
// Transform @Native(..) functions into FFI native call functions.
|
|
// Pass instance method receivers as implicit first argument to the static
|
|
// native function.
|
|
// Transform arguments that extend NativeFieldWrapperClass1 to Pointer if
|
|
// the native function expects Pointer (to avoid Handle overhead).
|
|
transformFfiNative.transformLibraries(component, coreTypes, hierarchy,
|
|
transitiveImportingDartFfi, diagnosticReporter, referenceFromIndex);
|
|
logger?.call("Transformed ffi natives");
|
|
|
|
// The use sites transformer implements `Native.addressOf` by reading a
|
|
// VM pragma attached to valid targets in the native transformer. Hence,
|
|
// it can only run after `@Native` targets have been transformed.
|
|
transformFfiUseSites.transformLibraries(component, coreTypes, hierarchy,
|
|
transitiveImportingDartFfi, diagnosticReporter, referenceFromIndex);
|
|
logger?.call("Transformed ffi use sites");
|
|
}
|
|
|
|
bool productMode = environmentDefines!["dart.vm.product"] == "true";
|
|
lowering.transformLibraries(libraries, coreTypes, hierarchy,
|
|
soundNullSafety: flags.soundNullSafety, productMode: productMode);
|
|
logger?.call("Lowering transformations performed");
|
|
|
|
callSiteAnnotator.transformLibraries(
|
|
component, libraries, coreTypes, hierarchy);
|
|
logger?.call("Annotated call sites");
|
|
}
|
|
|
|
@override
|
|
void performTransformationsOnProcedure(
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
Procedure procedure,
|
|
Map<String, String>? environmentDefines,
|
|
{void Function(String msg)? logger}) {
|
|
bool productMode = environmentDefines!["dart.vm.product"] == "true";
|
|
lowering.transformProcedure(procedure, coreTypes, hierarchy,
|
|
soundNullSafety: flags.soundNullSafety, productMode: productMode);
|
|
logger?.call("Lowering transformations performed");
|
|
}
|
|
|
|
Expression _instantiateInvocationMirrorWithType(
|
|
CoreTypes coreTypes,
|
|
Expression receiver,
|
|
String name,
|
|
Arguments arguments,
|
|
int offset,
|
|
int type) {
|
|
return new ConstructorInvocation(
|
|
coreTypes.invocationMirrorWithTypeConstructor,
|
|
new Arguments(<Expression>[
|
|
new SymbolLiteral(name)..fileOffset = offset,
|
|
new IntLiteral(type)..fileOffset = offset,
|
|
_fixedLengthList(
|
|
coreTypes,
|
|
coreTypes.typeLegacyRawType,
|
|
arguments.types
|
|
.map<Expression>((t) => new TypeLiteral(t))
|
|
.toList(),
|
|
arguments.fileOffset),
|
|
_fixedLengthList(coreTypes, const DynamicType(), arguments.positional,
|
|
arguments.fileOffset),
|
|
new StaticInvocation(
|
|
coreTypes.mapUnmodifiable,
|
|
new Arguments([
|
|
new MapLiteral(new List<MapLiteralEntry>.from(
|
|
arguments.named.map((NamedExpression arg) {
|
|
return new MapLiteralEntry(
|
|
new SymbolLiteral(arg.name)..fileOffset = arg.fileOffset,
|
|
arg.value)
|
|
..fileOffset = arg.fileOffset;
|
|
})), keyType: coreTypes.symbolLegacyRawType)
|
|
..isConst = (arguments.named.isEmpty)
|
|
..fileOffset = arguments.fileOffset
|
|
], types: [
|
|
coreTypes.symbolLegacyRawType,
|
|
new DynamicType()
|
|
]))
|
|
..fileOffset = offset
|
|
]));
|
|
}
|
|
|
|
@override
|
|
Expression instantiateInvocation(CoreTypes coreTypes, Expression receiver,
|
|
String name, Arguments arguments, int offset, bool isSuper) {
|
|
bool isGetter = false, isSetter = false, isMethod = false;
|
|
if (name.startsWith("set:")) {
|
|
isSetter = true;
|
|
name = name.substring(4);
|
|
} else if (name.startsWith("get:")) {
|
|
isGetter = true;
|
|
name = name.substring(4);
|
|
} else {
|
|
isMethod = true;
|
|
}
|
|
|
|
int type = _invocationType(
|
|
isGetter: isGetter,
|
|
isSetter: isSetter,
|
|
isMethod: isMethod,
|
|
isSuper: isSuper);
|
|
|
|
return _instantiateInvocationMirrorWithType(
|
|
coreTypes, receiver, name, arguments, offset, type);
|
|
}
|
|
|
|
@override
|
|
Expression instantiateNoSuchMethodError(CoreTypes coreTypes,
|
|
Expression receiver, String name, Arguments arguments, int offset,
|
|
{bool isMethod = false,
|
|
bool isGetter = false,
|
|
bool isSetter = false,
|
|
bool isField = false,
|
|
bool isLocalVariable = false,
|
|
bool isDynamic = false,
|
|
bool isSuper = false,
|
|
bool isStatic = false,
|
|
bool isConstructor = false,
|
|
bool isTopLevel = false}) {
|
|
int type = _invocationType(
|
|
isMethod: isMethod,
|
|
isGetter: isGetter,
|
|
isSetter: isSetter,
|
|
isField: isField,
|
|
isLocalVariable: isLocalVariable,
|
|
isDynamic: isDynamic,
|
|
isSuper: isSuper,
|
|
isStatic: isStatic,
|
|
isConstructor: isConstructor,
|
|
isTopLevel: isTopLevel);
|
|
return new StaticInvocation(
|
|
coreTypes.noSuchMethodErrorDefaultConstructor,
|
|
new Arguments(<Expression>[
|
|
receiver,
|
|
_instantiateInvocationMirrorWithType(
|
|
coreTypes, receiver, name, arguments, offset, type)
|
|
]));
|
|
}
|
|
|
|
int _invocationType(
|
|
{bool isMethod = false,
|
|
bool isGetter = false,
|
|
bool isSetter = false,
|
|
bool isField = false,
|
|
bool isLocalVariable = false,
|
|
bool isDynamic = false,
|
|
bool isSuper = false,
|
|
bool isStatic = false,
|
|
bool isConstructor = false,
|
|
bool isTopLevel = false}) {
|
|
// This is copied from [_InvocationMirror](
|
|
// ../../../../../../runtime/lib/invocation_mirror_patch.dart).
|
|
|
|
// Constants describing the invocation type.
|
|
// _FIELD cannot be generated by regular invocation mirrors.
|
|
const int _METHOD = 0;
|
|
const int _GETTER = 1;
|
|
const int _SETTER = 2;
|
|
const int _FIELD = 3;
|
|
const int _LOCAL_VAR = 4;
|
|
// ignore: UNUSED_LOCAL_VARIABLE
|
|
const int _KIND_SHIFT = 0;
|
|
const int _KIND_BITS = 3;
|
|
// ignore: UNUSED_LOCAL_VARIABLE
|
|
const int _KIND_MASK = (1 << _KIND_BITS) - 1;
|
|
|
|
// These values, except _DYNAMIC and _SUPER, are only used when throwing
|
|
// NoSuchMethodError for compile-time resolution failures.
|
|
const int _DYNAMIC = 0;
|
|
const int _SUPER = 1;
|
|
const int _STATIC = 2;
|
|
const int _CONSTRUCTOR = 3;
|
|
const int _TOP_LEVEL = 4;
|
|
const int _LEVEL_SHIFT = _KIND_BITS;
|
|
const int _LEVEL_BITS = 3;
|
|
// ignore: UNUSED_LOCAL_VARIABLE
|
|
const int _LEVEL_MASK = (1 << _LEVEL_BITS) - 1;
|
|
|
|
int type = -1;
|
|
// For convenience, [isGetter] and [isSetter] takes precedence over
|
|
// [isMethod].
|
|
if (isGetter) {
|
|
type = _GETTER;
|
|
} else if (isSetter) {
|
|
type = _SETTER;
|
|
} else if (isMethod) {
|
|
type = _METHOD;
|
|
} else if (isField) {
|
|
type = _FIELD;
|
|
} else if (isLocalVariable) {
|
|
type = _LOCAL_VAR;
|
|
}
|
|
|
|
if (isDynamic) {
|
|
type |= (_DYNAMIC << _LEVEL_SHIFT);
|
|
} else if (isSuper) {
|
|
type |= (_SUPER << _LEVEL_SHIFT);
|
|
} else if (isStatic) {
|
|
type |= (_STATIC << _LEVEL_SHIFT);
|
|
} else if (isConstructor) {
|
|
type |= (_CONSTRUCTOR << _LEVEL_SHIFT);
|
|
} else if (isTopLevel) {
|
|
type |= (_TOP_LEVEL << _LEVEL_SHIFT);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
Expression _fixedLengthList(CoreTypes coreTypes, DartType typeArgument,
|
|
List<Expression> elements, int offset) {
|
|
// TODO(ahe): It's possible that it would be better to create a fixed-length
|
|
// list first, and then populate it. That would create fewer objects. But as
|
|
// this is currently only used in (statically resolved) no-such-method
|
|
// handling, the current approach seems sufficient.
|
|
|
|
// The 0-element list must be exactly 'const[]'.
|
|
if (elements.isEmpty) {
|
|
return new ListLiteral([], typeArgument: typeArgument)..isConst = true;
|
|
}
|
|
|
|
return new StaticInvocation(
|
|
coreTypes.listUnmodifiableConstructor,
|
|
new Arguments([
|
|
new ListLiteral(elements, typeArgument: typeArgument)
|
|
..fileOffset = offset
|
|
], types: [
|
|
typeArgument,
|
|
]));
|
|
}
|
|
|
|
// In addition to the default implementation, we allow VM tests to import
|
|
// private platform libraries - such as `dart:_internal` - for testing
|
|
// purposes.
|
|
bool allowPlatformPrivateLibraryAccess(Uri importer, Uri imported) =>
|
|
super.allowPlatformPrivateLibraryAccess(importer, imported) ||
|
|
importer.path.contains('runtime/observatory/tests') ||
|
|
importer.path.contains('runtime/tests/vm/dart') ||
|
|
importer.path.contains('tests/standalone/io') ||
|
|
importer.path.contains('test-lib') ||
|
|
importer.path.contains('tests/ffi');
|
|
|
|
// TODO(sigmund,ahe): limit this to `dart-ext` libraries only (see
|
|
// https://github.com/dart-lang/sdk/issues/29763).
|
|
@override
|
|
bool enableNative(Uri uri) => true;
|
|
|
|
@override
|
|
bool get nativeExtensionExpectsString => true;
|
|
|
|
@override
|
|
Component configureComponent(Component component) {
|
|
callSiteAnnotator.addRepositoryTo(component);
|
|
return super.configureComponent(component);
|
|
}
|
|
|
|
@override
|
|
Class concreteListLiteralClass(CoreTypes coreTypes) {
|
|
return _growableList ??=
|
|
coreTypes.index.getClass('dart:core', '_GrowableList');
|
|
}
|
|
|
|
@override
|
|
Class concreteConstListLiteralClass(CoreTypes coreTypes) {
|
|
return _immutableList ??=
|
|
coreTypes.index.getClass('dart:core', '_ImmutableList');
|
|
}
|
|
|
|
@override
|
|
Class concreteMapLiteralClass(CoreTypes coreTypes) {
|
|
return _map ??= coreTypes.index.getClass('dart:collection', '_Map');
|
|
}
|
|
|
|
@override
|
|
Class concreteConstMapLiteralClass(CoreTypes coreTypes) {
|
|
return _constMap ??=
|
|
coreTypes.index.getClass('dart:collection', '_ConstMap');
|
|
}
|
|
|
|
@override
|
|
Class concreteSetLiteralClass(CoreTypes coreTypes) {
|
|
return _set ??= coreTypes.index.getClass('dart:collection', '_Set');
|
|
}
|
|
|
|
@override
|
|
Class concreteConstSetLiteralClass(CoreTypes coreTypes) {
|
|
return _constSet ??=
|
|
coreTypes.index.getClass('dart:collection', '_ConstSet');
|
|
}
|
|
|
|
@override
|
|
Class getRecordImplementationClass(
|
|
CoreTypes coreTypes, int numPositionalFields, List<String> namedFields) {
|
|
return _record ??= coreTypes.index.getClass('dart:core', '_Record');
|
|
}
|
|
|
|
@override
|
|
Class? concreteIntLiteralClass(CoreTypes coreTypes, int value) {
|
|
const int bitsPerInt32 = 32;
|
|
const int smiBits32 = bitsPerInt32 - 2;
|
|
const int smiMin32 = -(1 << smiBits32);
|
|
const int smiMax32 = (1 << smiBits32) - 1;
|
|
if ((smiMin32 <= value) && (value <= smiMax32)) {
|
|
// Value fits into Smi on all platforms.
|
|
return _smi ??= coreTypes.index.getClass('dart:core', '_Smi');
|
|
}
|
|
// Otherwise, class could be either _Smi or _Mint depending on a platform.
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
Class concreteDoubleLiteralClass(CoreTypes coreTypes, double value) {
|
|
return _double ??= coreTypes.index.getClass('dart:core', '_Double');
|
|
}
|
|
|
|
@override
|
|
Class concreteStringLiteralClass(CoreTypes coreTypes, String value) {
|
|
const int maxLatin1 = 0xff;
|
|
for (int i = 0; i < value.length; ++i) {
|
|
if (value.codeUnitAt(i) > maxLatin1) {
|
|
return _twoByteString ??=
|
|
coreTypes.index.getClass('dart:core', '_TwoByteString');
|
|
}
|
|
}
|
|
return _oneByteString ??=
|
|
coreTypes.index.getClass('dart:core', '_OneByteString');
|
|
}
|
|
|
|
@override
|
|
Class concreteClosureClass(CoreTypes coreTypes) {
|
|
return _closure ??= coreTypes.index.getClass('dart:core', '_Closure');
|
|
}
|
|
|
|
@override
|
|
Class? concreteAsyncResultClass(CoreTypes coreTypes) =>
|
|
coreTypes.futureImplClass;
|
|
|
|
@override
|
|
Class? concreteSyncStarResultClass(CoreTypes coreTypes) {
|
|
return _syncStarIterable ??=
|
|
coreTypes.index.getClass('dart:async', '_SyncStarIterable');
|
|
}
|
|
|
|
@override
|
|
ConstantsBackend get constantsBackend => const ConstantsBackend();
|
|
|
|
@override
|
|
Map<String, String> updateEnvironmentDefines(Map<String, String> map) {
|
|
// TODO(alexmarkov): Call this from the front-end in order to have
|
|
// the same defines when compiling platform.
|
|
map['dart.isVM'] = 'true';
|
|
return map;
|
|
}
|
|
|
|
@override
|
|
DartLibrarySupport get dartLibrarySupport => flags.supportMirrors
|
|
? const DefaultDartLibrarySupport()
|
|
: const CustomizedDartLibrarySupport(unsupported: {'mirrors'});
|
|
|
|
@override
|
|
bool isSupportedPragma(String pragmaName) => pragmaName.startsWith("vm:");
|
|
}
|