Files
sdk/pkg/vm/lib/embedder/visitor.dart
T
Tess Strickland acd16c6aa7 [pkg/vm] Add tool for generating DartEngine shims from entry points.
This is an initial cut of a tool for automatically creating shims for
C/C++ programs using the entry point annotations in Dart code. The tool
has two required arguments:

* the .dill file containing the kernel representation of the Dart code
* the base path of the header and implementation files to create

With a base path of 'dir/name', the header file is created as
'dir/name.h' and the implementation file is created as 'dir/name.cc'.
In addition, 'dir/name.h' is used as a basis for creating a
#ifndef/#define/#endif header guard around the header contents.


The created shims are specific to a single package, either

* a user-specified package, provided via '-p'/'--package', or
* the package of the main method

If the user does not specify a package and there is no main method
in the .dill file, the tool fails.


Each shim takes the following arguments in order when applicable:

* the isolate in which to perform the requested operation,
* the instantiated type of the generic class (for invoking, setting, or
  getting constructors, static methods, and static fields)
* the instance (for invoking, getting, or setting instance methods and
  fields)
* the type arguments (for retrieving nullable or non-nullable
  instantiated types of a generic class)
* the arguments (for invoking, setting, or getting constructors,
  methods, and fields)


The generated shims:

* cache the package library, types for non-generic classes, and
  types for generic classes instantiated with default type arguments.
  The cached persistent handles are cleared if any of the methods
  are called with a different isolate from the one used to populate
  the cache.
* automatically handle conversions between C int64_t <=> Dart int
  and C double <=> Dart double.


By default, shims are not created for allocation or initializing
uninitialized instances. To create such shims, use the '-u' command
line argument.


Currently, shims are not created for methods that take optional
or named arguments. To report an error if a shim cannot be created
for any entry points, use the '-e' command line argument.

TEST=tests/standalone/embedder_samples_test

Change-Id: Ibdf3b52d900ba98038528178485f295c5868ac9d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/410500
Reviewed-by: Ivan Inozemtsev <iinozemtsev@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
2025-03-20 03:53:10 -07:00

171 lines
5.7 KiB
Dart

// Copyright (c) 2025, 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/core_types.dart' show CoreTypes;
import 'package:kernel/target/targets.dart' show TargetFlags;
import 'package:vm/modular/target/vm.dart' show VmTarget;
import 'package:vm/transformations/pragma.dart';
import 'collector.dart';
bool _allowsCall(ParsedEntryPointPragma pragma) =>
pragma.type == PragmaEntryPointType.Default ||
pragma.type == PragmaEntryPointType.CallOnly;
bool _allowsGet(ParsedEntryPointPragma pragma) =>
pragma.type == PragmaEntryPointType.Default ||
pragma.type == PragmaEntryPointType.GetterOnly;
bool _allowsSet(ParsedEntryPointPragma pragma) =>
pragma.type == PragmaEntryPointType.Default ||
pragma.type == PragmaEntryPointType.SetterOnly;
EntryPointShimCollector visitLibrary(
Component component,
Library library, {
CoreTypes? coreTypes,
bool createUninitializedInstanceMethods = false,
bool errorOnUnhandledEntryPoints = false,
}) {
coreTypes ??= CoreTypes(component);
final collector = EntryPointShimCollector(
coreTypes,
errorOnUnhandledEntryPoints,
);
final visitor = EntryPointShimVisitor(coreTypes, library, collector);
component.accept(visitor);
return visitor.collector;
}
class EntryPointShimVisitor extends RecursiveVisitor {
final Library _library;
final EntryPointShimCollector _collector;
final bool _createUninitializedInstances;
final PragmaAnnotationParser _pragmaParser;
EntryPointShimVisitor(
CoreTypes coreTypes,
this._library,
this._collector, {
bool createUninitializedInstanceMethods = false,
}) : _createUninitializedInstances = createUninitializedInstanceMethods,
_pragmaParser = ConstantPragmaAnnotationParser(
coreTypes,
VmTarget(const TargetFlags()),
);
EntryPointShimCollector get collector => _collector;
ParsedEntryPointPragma? _entryPointAnnotation(Annotatable node) {
if ((node as dynamic).enclosingLibrary != _library) {
return null;
}
final pragmas = _pragmaParser
.parsedPragmas<ParsedEntryPointPragma>(node.annotations)
.where(
(p) =>
p.type != PragmaEntryPointType.Extendable &&
p.type != PragmaEntryPointType.CanBeOverridden,
);
if (pragmas.isEmpty) return null;
var pragma = pragmas.first;
if (pragma.type != PragmaEntryPointType.Default) {
for (final p in pragmas.skip(1)) {
if (p.type == PragmaEntryPointType.Default) {
pragma = p;
break;
}
if (p.type != pragma.type) {
throw "Incompatible non-default pragmas: ${pragma.type} and ${p.type}";
}
}
}
return pragma;
}
@override
void visitField(Field field) {
final pragma = _entryPointAnnotation(field);
if (pragma != null) {
assert(pragma.type != PragmaEntryPointType.CallOnly);
assert(!field.isFinal || pragma.type != PragmaEntryPointType.SetterOnly);
if (_allowsGet(pragma)) {
_collector.addAll(field.getterReference, {
EntryPointRole.getter,
// If the field's value has a Function type, then the value can be
// both retrieved and invoked using a single Dart_Invoke call.
if (field.getterType is FunctionType) EntryPointRole.call,
});
}
if (_allowsSet(pragma)) {
_collector.add(field.setterReference!, EntryPointRole.setter);
}
}
super.visitField(field);
}
@override
void visitConstructor(Constructor constructor) {
final pragma = _entryPointAnnotation(constructor);
if (pragma != null) {
assert(_allowsCall(pragma));
_collector.addAll(constructor.reference, {
// Dart_New
EntryPointRole.allocation,
// Dart_InvokeConstructor
if (_createUninitializedInstances) EntryPointRole.initialization,
});
}
super.visitConstructor(constructor);
}
@override
void visitProcedure(Procedure procedure) {
final pragma = _entryPointAnnotation(procedure);
if (pragma != null) {
if (procedure.isGetter) {
assert(_allowsGet(pragma));
_collector.addAll(procedure.reference, {
EntryPointRole.getter,
// If the returned value has a Function type, then the closure value
// can be both retrieved and invoked using a single Dart_Invoke call.
if (procedure.getterType is FunctionType) EntryPointRole.call,
});
} else if (procedure.isSetter) {
assert(_allowsSet(pragma));
// Treat the procedure the same as a field, since both are accessed via
// Dart_SetField.
_collector.add(procedure.reference, EntryPointRole.setter);
} else if (procedure.isFactory) {
assert(_allowsCall(pragma));
_collector.add(procedure.reference, EntryPointRole.call);
} else {
_collector.addAll(procedure.reference, {
if (_allowsCall(pragma)) EntryPointRole.call,
if (_allowsGet(pragma)) EntryPointRole.closure,
});
}
}
super.visitProcedure(procedure);
}
@override
void visitClass(Class cls) {
final pragma = _entryPointAnnotation(cls);
if (pragma != null) {
assert(pragma.type == PragmaEntryPointType.Default);
final canAllocate = !cls.isAbstract && _createUninitializedInstances;
_collector.addAll(cls.reference, {
EntryPointRole.class_,
EntryPointRole.nonNullableType,
EntryPointRole.nullableType,
if (canAllocate) EntryPointRole.allocation,
});
}
super.visitClass(cls);
}
}