blob: 5b1c24adce722bd3b41a858f6bef102ab022ba21 [file] [log] [blame]
// 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;
void _convertDartTypeToC(
StringBuffer buffer,
CoreTypes? coreTypes,
DartType type,
) {
if (coreTypes == null) {
buffer.write('Dart_Handle');
} else if (type is VoidType) {
buffer.write('void');
} else if (type == coreTypes.intNonNullableRawType) {
buffer.write('int64_t');
} else if (type == coreTypes.doubleNonNullableRawType) {
buffer.write('double');
} else {
buffer.write('Dart_Handle');
}
}
String _getCReturnType(CoreTypes? coreTypes, DartType type) {
if (coreTypes == null) {
return 'Dart_Handle';
} else if (type is VoidType) {
return 'void';
} else if (type == coreTypes.intNonNullableRawType) {
return 'int64_t';
} else if (type == coreTypes.doubleNonNullableRawType) {
return 'double';
} else {
return 'Dart_Handle';
}
}
void _convertCValueToDart(
StringBuffer buffer,
CoreTypes coreTypes,
DartType type,
void Function(StringBuffer) bodyWriter,
) {
assert(type is! VoidType);
if (type == coreTypes.intNonNullableRawType) {
buffer.write('Dart_NewInteger(');
bodyWriter(buffer);
buffer.write(')');
} else if (type == coreTypes.doubleNonNullableRawType) {
buffer.write('Dart_NewDouble(');
bodyWriter(buffer);
buffer.write(')');
} else {
bodyWriter(buffer);
}
}
void _convertDartValueToC(
StringBuffer buffer,
CoreTypes coreTypes,
DartType type,
void Function(StringBuffer) bodyWriter,
) {
if (type is VoidType) {
buffer.write('CheckError(');
bodyWriter(buffer);
buffer.write(')');
} else if (type == coreTypes.intNonNullableRawType) {
buffer.write('IntFromHandle(');
bodyWriter(buffer);
buffer.write(')');
} else if (type == coreTypes.doubleNonNullableRawType) {
buffer.write('DoubleFromHandle(');
bodyWriter(buffer);
buffer.write(')');
} else {
bodyWriter(buffer);
}
}
class EntryPointShimParameter {
final String name;
final DartType type;
EntryPointShimParameter._(this.name, {DartType? type})
: type = type ?? DynamicType();
void writeAsParameter(StringBuffer buffer, [CoreTypes? coreTypes]) {
assert(type is! VoidType);
_convertDartTypeToC(buffer, coreTypes, type);
buffer.write(' $name');
}
void writeAsArgument(StringBuffer buffer, [CoreTypes? coreTypes]) {
if (coreTypes != null) {
_convertCValueToDart(buffer, coreTypes, type, (b) => b.write(name));
} else {
buffer.write(name);
}
}
@override
String toString() {
final b = StringBuffer();
writeAsArgument(b);
return b.toString();
}
}
class EntryPointShimTypeParameter extends EntryPointShimParameter {
static int counter = 0;
static String get gensym => 'tp${counter++}';
EntryPointShimTypeParameter._(String name) : super._('t_' + name);
factory EntryPointShimTypeParameter.fromTypeParameter(TypeParameter p) {
return EntryPointShimTypeParameter._(p.name ?? gensym);
}
}
class EntryPointShimValueParameter extends EntryPointShimParameter {
static int counter = 0;
static String get gensym => '${counter++}';
EntryPointShimValueParameter._(String name, {super.type})
: super._('v_' + name);
factory EntryPointShimValueParameter.fromDeclaration(VariableDeclaration v) {
return EntryPointShimValueParameter._(v.name ?? gensym, type: v.type);
}
}
abstract class EntryPointFunctionShim {
final NamedNode node;
final CoreTypes coreTypes;
final List<EntryPointShimParameter> parameters;
// The target may be the instance for instance members or a type if the
// enclosing class is generic and thus needs instantiation.
final EntryPointShimParameter? target;
static EntryPointShimParameter? targetOf(
NamedNode node,
CoreTypes coreTypes,
) {
if (node is! Member) return null;
if (node.isInstanceMember) {
// For instance methods, the instance contains the instantiation of the
// class type parameters.
return EntryPointShimValueParameter._(
"_inst",
type: node.enclosingClass!.getThisType(
coreTypes,
Nullability.nonNullable,
),
);
}
final cls = node.enclosingClass;
if (cls != null && cls.typeParameters.isNotEmpty) {
// Need to handle type parameters on the enclosing class. Defer this
// by requiring the user provide the appropriate instantiated
// non-nullable type.
return EntryPointShimTypeParameter._("_T");
}
return null;
}
static List<EntryPointShimParameter> parametersOf(NamedNode node) {
if (node is Procedure || node is Constructor) {
final function = (node as dynamic).function as FunctionNode;
assert(function.namedParameters.isEmpty);
assert(
function.requiredParameterCount == function.positionalParameters.length,
);
return function.positionalParameters
.map(EntryPointShimValueParameter.fromDeclaration)
.toList();
}
if (node is Class) {
return node.typeParameters
.map(EntryPointShimTypeParameter.fromTypeParameter)
.toList();
}
return const [];
}
EntryPointFunctionShim._({
required this.node,
required this.coreTypes,
List<EntryPointShimParameter>? parameters,
EntryPointShimParameter? target,
}) : parameters = parameters ?? parametersOf(node),
target = target ?? targetOf(node, coreTypes);
static final _cachedBaseNames = <Reference, String>{};
static final _cachedPrefixedNames = <Reference, String>{};
static String _baseName(NamedNode node) {
if (!_cachedBaseNames.containsKey(node.reference)) {
var name = (node as dynamic).name;
if (name is Name) {
assert(name.isPrivate == name.text.startsWith('_'));
name = name.text;
}
_cachedBaseNames[node.reference] = name as String;
}
return _cachedBaseNames[node.reference]!;
}
static String _prefixedName(NamedNode node) {
if (!_cachedPrefixedNames.containsKey(node.reference)) {
String prefixedName = _baseName(node);
if (node is Member) {
final cls = node.enclosingClass;
if (cls != null) {
final prefix = _prefixedName(cls);
if (prefixedName.isEmpty || prefixedName == '_') {
prefixedName = '$prefix$prefixedName';
} else {
prefixedName = '${prefix}_$prefixedName';
}
}
}
_cachedPrefixedNames[node.reference] = prefixedName;
}
return _cachedPrefixedNames[node.reference]!;
}
String get baseName => _baseName(node);
String get prefixedName => _prefixedName(node);
String get functionName;
DartType get returnType;
void _writeTarget(StringBuffer buffer) {
if (target != null) {
target!.writeAsArgument(buffer, coreTypes);
return;
}
final n = node;
Class? cls;
if (n is Member) {
cls = n.enclosingClass;
if (cls != null && cls.typeParameters.isNotEmpty) {
// The shim should have had a target parameter created.
throw "Unexpected attempt to write target";
}
} else if (n is Class) {
cls = n;
}
if (cls == null) {
buffer.write('PackageState::instance()->PackageLibrary()');
} else if (cls.typeParameters.isEmpty) {
buffer.write(
'PackageState::instance()->TypeWithDefaults("${_baseName(cls)}")',
);
} else {
assert(node is Class);
assert(parameters.isNotEmpty);
buffer.write(
'Dart_GetNonNullableType(PackageState::instance()->PackageLibrary(), Dart_NewStringFromCString("$baseName"), ',
);
_writeArgumentsListCountAndPointer(buffer);
buffer.write(')');
}
}
void _writeArguments(StringBuffer buffer) {
bool first = true;
for (final p in parameters) {
if (!first) buffer.write(', ');
p.writeAsArgument(buffer, coreTypes);
first = false;
}
}
void _writeArgumentsListCountAndPointer(StringBuffer buffer) {
buffer.write('${parameters.length}, ');
if (parameters.isEmpty) {
// No arguments list created.
buffer.write('nullptr');
} else {
buffer.write('parameter_list.data()');
}
}
void _writeReturnType(StringBuffer buffer) {
_convertDartTypeToC(buffer, coreTypes, returnType);
}
String get isolateName => 'dart_isolate';
void _writeFunctionHeader(StringBuffer buffer) {
buffer.write('PACKAGE_EXPORT ');
_writeReturnType(buffer);
buffer.write(' $functionName(Dart_Isolate $isolateName');
if (target != null) {
buffer.write(', ');
target!.writeAsParameter(buffer, coreTypes);
}
for (final p in parameters) {
buffer.write(', ');
p.writeAsParameter(buffer, coreTypes);
}
buffer.write(')');
}
void writeDeclaration(StringBuffer buffer) {
_writeFunctionHeader(buffer);
buffer.writeln(';');
}
void _writeReturnBody(StringBuffer buffer);
void writeDefinition(StringBuffer buffer) {
_writeFunctionHeader(buffer);
buffer.writeln(''' {
IsolateScope isolate_scope($isolateName);
DartScope scope;''');
if (parameters.isNotEmpty) {
buffer.write('''
std::vector parameter_list{''');
_writeArguments(buffer);
buffer.writeln('};');
}
buffer.write(' ');
if (returnType is! VoidType) {
buffer.write('return ');
}
_convertDartValueToC(buffer, coreTypes, returnType, _writeReturnBody);
buffer
..writeln(';')
..writeln('}')
..writeln('');
}
void write(StringBuffer declaration, StringBuffer definition) {
writeDeclaration(declaration);
writeDefinition(definition);
}
@override
String toString() {
final b = StringBuffer();
writeDefinition(b);
return b.toString();
}
}
class EntryPointGetterShim extends EntryPointFunctionShim {
@override
final Member node;
EntryPointGetterShim._({required this.node, required super.coreTypes})
: super._(node: node, parameters: const []);
factory EntryPointGetterShim.fromMember(Member node, CoreTypes coreTypes) {
assert(node is Field || (node is Procedure && node.isGetter));
return EntryPointGetterShim._(node: node, coreTypes: coreTypes);
}
@override
String get functionName => 'Get_$prefixedName';
@override
DartType get returnType => node.getterType;
@override
void _writeReturnBody(StringBuffer buffer) {
buffer.write('Dart_GetField(');
_writeTarget(buffer);
buffer.write(', Dart_NewStringFromCString("$baseName"))');
}
}
class EntryPointClosureShim extends EntryPointGetterShim {
@override
final Procedure node;
EntryPointClosureShim._({required this.node, required super.coreTypes})
: super._(node: node);
factory EntryPointClosureShim.fromProcedure(
Procedure node,
CoreTypes coreTypes,
) {
assert(!node.isGetter && !node.isFactory);
return EntryPointClosureShim._(node: node, coreTypes: coreTypes);
}
@override
String get functionName => '${super.functionName}_Closure';
}
class EntryPointSetterShim extends EntryPointFunctionShim {
@override
final Member node;
EntryPointSetterShim._({required this.node, required super.coreTypes})
: super._(
node: node,
parameters: [
EntryPointShimValueParameter._("value", type: node.setterType),
],
);
factory EntryPointSetterShim.fromMember(Member node, CoreTypes coreTypes) {
assert(node is Field || (node is Procedure && node.isSetter));
return EntryPointSetterShim._(node: node, coreTypes: coreTypes);
}
@override
String get functionName => 'Set_$prefixedName';
@override
DartType get returnType => VoidType();
@override
void _writeReturnBody(StringBuffer buffer) {
buffer.write('Dart_SetField(');
_writeTarget(buffer);
buffer.write(', Dart_NewStringFromCString("$baseName"), ');
_writeArguments(buffer);
buffer.write(')');
}
}
class EntryPointClassShim extends EntryPointFunctionShim {
@override
final Class node;
EntryPointClassShim._({required this.node, required super.coreTypes})
: super._(node: node, parameters: const []);
factory EntryPointClassShim.fromClass(Class node, CoreTypes coreTypes) {
return EntryPointClassShim._(node: node, coreTypes: coreTypes);
}
@override
String get functionName => 'Get_${prefixedName}_Class';
@override
DartType get returnType => coreTypes.typeNonNullableRawType;
@override
void _writeReturnBody(StringBuffer buffer) =>
// Don't use _writeTarget, since we want the default type arguments for
// generic classes.
buffer.write('PackageState::instance()->TypeWithDefaults("$baseName")');
}
class EntryPointNullableTypeShim extends EntryPointFunctionShim {
@override
final Class node;
EntryPointNullableTypeShim._({required this.node, required super.coreTypes})
: super._(node: node);
factory EntryPointNullableTypeShim.fromClass(
Class node,
CoreTypes coreTypes,
) {
return EntryPointNullableTypeShim._(node: node, coreTypes: coreTypes);
}
@override
String get functionName => 'Get_${prefixedName}_NullableType';
@override
DartType get returnType => coreTypes.typeNonNullableRawType;
@override
void _writeReturnBody(StringBuffer buffer) {
buffer.write('Dart_TypeToNullableType(');
_writeTarget(buffer);
buffer.write(')');
}
}
class EntryPointNonNullableTypeShim extends EntryPointFunctionShim {
@override
final Class node;
EntryPointNonNullableTypeShim._({
required this.node,
required super.coreTypes,
}) : super._(node: node);
factory EntryPointNonNullableTypeShim.fromClass(
Class node,
CoreTypes coreTypes,
) {
return EntryPointNonNullableTypeShim._(node: node, coreTypes: coreTypes);
}
@override
String get functionName => 'Get_${prefixedName}_NonNullableType';
@override
DartType get returnType => coreTypes.typeNonNullableRawType;
@override
void _writeReturnBody(StringBuffer buffer) => _writeTarget(buffer);
}
class EntryPointAllocationShim extends EntryPointFunctionShim {
@override
final Class node;
EntryPointAllocationShim._({required this.node, required super.coreTypes})
: super._(node: node);
factory EntryPointAllocationShim.fromClass(Class node, CoreTypes coreTypes) {
return EntryPointAllocationShim._(node: node, coreTypes: coreTypes);
}
@override
String get functionName => 'Allocate_${prefixedName}';
@override
DartType get returnType => DynamicType(); // to avoid C conversions
@override
void _writeReturnBody(StringBuffer buffer) {
buffer.write('Dart_Allocate(');
_writeTarget(buffer);
buffer.write(')');
}
}
class EntryPointNewShim extends EntryPointFunctionShim {
@override
final Constructor node;
EntryPointNewShim._({required this.node, required super.coreTypes})
: super._(node: node);
factory EntryPointNewShim.fromConstructor(
Constructor node,
CoreTypes coreTypes,
) {
return EntryPointNewShim._(node: node, coreTypes: coreTypes);
}
static FunctionType functionType(Constructor node) =>
node.function.computeThisFunctionType(Nullability.nonNullable);
@override
String get functionName => 'New_$prefixedName';
@override
DartType get returnType => node.function.returnType;
@override
void _writeReturnBody(StringBuffer buffer) {
buffer.write('Dart_New(');
_writeTarget(buffer);
buffer.write(', Dart_NewStringFromCString("$baseName"), ');
_writeArgumentsListCountAndPointer(buffer);
buffer.write(')');
}
}
class EntryPointInitializationShim extends EntryPointFunctionShim {
@override
final Constructor node;
EntryPointInitializationShim._({required this.node, required super.coreTypes})
: super._(
node: node,
target: EntryPointShimValueParameter._(
"_instance",
type: node.enclosingClass.getThisType(
coreTypes,
Nullability.nonNullable,
),
),
);
factory EntryPointInitializationShim.fromConstructor(
Constructor node,
CoreTypes coreTypes,
) {
return EntryPointInitializationShim._(node: node, coreTypes: coreTypes);
}
static FunctionType functionType(Constructor node) {
final type = node.function.computeThisFunctionType(Nullability.nonNullable);
return FunctionType(
type.positionalParameters,
VoidType(),
type.declaredNullability,
namedParameters: type.namedParameters,
typeParameters: type.typeParameters,
requiredParameterCount: type.requiredParameterCount,
);
}
@override
String get functionName => 'Initialize_$prefixedName';
@override
DartType get returnType => VoidType();
@override
void _writeReturnBody(StringBuffer buffer) {
buffer.write('Dart_InvokeConstructor(');
_writeTarget(buffer);
buffer.write(', Dart_NewStringFromCString("$baseName"), ');
_writeArgumentsListCountAndPointer(buffer);
buffer.write(')');
}
}
class EntryPointCallShim extends EntryPointFunctionShim {
@override
final Member node;
EntryPointCallShim._({
required this.node,
required super.coreTypes,
super.parameters,
}) : super._(node: node);
factory EntryPointCallShim.fromMember(Member node, CoreTypes coreTypes) {
final FunctionType type = functionType(node);
assert(type.typeParameters.isEmpty);
assert(type.namedParameters.isEmpty);
assert(
type.requiredPositionalParameterCount == type.positionalParameters.length,
);
List<EntryPointShimParameter>? parameters;
if (_isGetter(node)) {
parameters = <EntryPointShimParameter>[];
for (int i = 0; i < type.positionalParameters.length; ++i) {
parameters.add(
EntryPointShimValueParameter._(
i.toString(),
type: type.positionalParameters[i],
),
);
}
}
return EntryPointCallShim._(
node: node,
coreTypes: coreTypes,
parameters: parameters,
);
}
static bool _isGetter(Member node) =>
node is Field || (node is Procedure && node.isGetter);
static FunctionType functionType(Member node) =>
_isGetter(node)
? node.getterType as FunctionType
: (node as Procedure).computeSignatureOrFunctionType();
@override
String get functionName => 'Call_$prefixedName';
@override
DartType get returnType => functionType(node).returnType;
@override
void _writeReturnBody(StringBuffer buffer) {
var isDartHandle = _getCReturnType(coreTypes, returnType) == 'Dart_Handle';
if (isDartHandle) {
buffer.write('Dart_NewPersistentHandle(');
}
buffer.write('Dart_Invoke(');
_writeTarget(buffer);
buffer.write(', Dart_NewStringFromCString("$baseName"), ');
_writeArgumentsListCountAndPointer(buffer);
buffer.write(')');
if (isDartHandle) {
buffer.write(')');
}
}
}