Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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
3 changes: 1 addition & 2 deletions pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,9 @@
- __Breaking change__: Dart const values will be generated for global variables
marked const in C (e.g. static const int) instead of symbol lookups. This
supports integers, doubles, and string literals. Including the variable name
in the globals -> symbol-address configuration will still generate symbol
in the globals -> symbol-address configuration will still generate symbol
lookups.


## 20.1.1

- Update tests and examples now that package:objective_c is using native assets.
Expand Down
1 change: 1 addition & 0 deletions pkgs/ffigen/lib/src/code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
library;

export 'code_generator/binding.dart';
export 'code_generator/bindings_index.dart';
export 'code_generator/compound.dart';
export 'code_generator/constant.dart';
export 'code_generator/enum_class.dart';
Expand Down
81 changes: 81 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/bindings_index.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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 '../header_parser/clang_bindings/clang_bindings.dart' as clang_types;
import '../header_parser/utils.dart';
import '../visitor/ast.dart';
import 'binding.dart';

class BindingsIndex {
final _entries = <String, IndexEntry>{};

void addDefinition(clang_types.CXCursor cursor) {
if (cursor.isNull) return;
final definition = cursor.definition;
if (!definition.isNull) cursor = definition;
final usr = cursor.usr();
if (usr.isEmpty) return;
final existingEntry = _entries[usr];
if (existingEntry == null) {
_entries[usr] = IndexEntry(definition: cursor);
} else if (!(existingEntry.definition?.isDefinition ?? false)) {
existingEntry.definition = cursor;
}
}

AstNode? cache(
clang_types.CXCursor cursor,
CachableBinding? Function(clang_types.CXCursor cursor) builder,
) {
final usr = cursor.usr();
if (usr.isEmpty) return null;
final entry = getOrInsert(usr);
if (!entry.filled) {
final cachable = builder(entry.definition ?? cursor);
entry.filled = true;
if (cachable != null) {
entry.node = cachable.node;
// Note: Filler may re-enter this cache method.
cachable.filler();
}
}
return entry.node;
}

void fillBinding(Binding binding) {
final entry = getOrInsert(binding.usr);
assert(!entry.filled);
entry.node = binding;
entry.filled = true;
}

IndexEntry? operator [](String usr) => _entries[usr];
IndexEntry getOrInsert(String usr) {
assert(usr.isNotEmpty);
return _entries[usr] ??= IndexEntry();
}

Set<Binding> get bindings =>
_entries.values.map((e) => e.node).whereType<Binding>().toSet();
}

class IndexEntry {
clang_types.CXCursor? definition;
bool filled = false;
AstNode? node;
IndexEntry({this.definition});

@override
String toString() => '$node';
}

// Some bindings need to split intial creation from filling, to avoid cycles.
// In that case they can provide a filler function that will be called after the
// cache entry is created.
class CachableBinding {
AstNode node;
void Function() filler;
CachableBinding(this.node, [this.filler = _defaultFiller]);
static void _defaultFiller() {}
}
25 changes: 9 additions & 16 deletions pkgs/ffigen/lib/src/code_generator/objc_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,15 @@ class ObjCBlock extends BindingType with HasLocalScope {

final usr = _getBlockUsr(returnType, renamedParams, returnsRetained);

final oldBlock = context.bindingsIndex.getSeenObjCBlock(usr);
if (oldBlock != null) {
return oldBlock;
}

final block = ObjCBlock._(
context,
usr: usr,
name: _getBlockName(returnType, renamedParams.map((a) => a.type)),
returnType: returnType,
params: renamedParams,
returnsRetained: returnsRetained,
);
context.bindingsIndex.addObjCBlockToSeen(usr, block);

return block;
return (context.bindingsIndex.getOrInsert(usr).node ??= ObjCBlock._(
context,
usr: usr,
name: _getBlockName(returnType, renamedParams.map((a) => a.type)),
returnType: returnType,
params: renamedParams,
returnsRetained: returnsRetained,
))
as ObjCBlock;
}

ObjCBlock._(
Expand Down
2 changes: 2 additions & 0 deletions pkgs/ffigen/lib/src/code_generator/writer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ class Writer {
}

// Warn for macros.
// TODO: Use runtime type, not USR.
final hasMacroBindings = bindings.any(
(element) => element is Constant && element.usr.contains('@macro@'),
);
Expand All @@ -223,6 +224,7 @@ class Writer {
}

// Remove internal bindings and macros.
// TODO: Use runtime type, not USR.
bindings.removeWhere((element) {
return element.isInternal ||
(element is Constant && element.usr.contains('@macro@'));
Expand Down
2 changes: 1 addition & 1 deletion pkgs/ffigen/lib/src/config_provider/spec_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ Type makeTypeFromRawVarArgType(
} else if (supportedTypedefToImportedType.containsKey(rawBaseType)) {
baseType = supportedTypedefToImportedType[rawBaseType]!;
} else if (suportedTypedefToSuportedNativeType.containsKey(rawBaseType)) {
baseType = NativeType(suportedTypedefToSuportedNativeType[rawBaseType]!);
baseType = suportedTypedefToSuportedNativeType[rawBaseType]!;
} else {
// Use library import if specified (E.g - ffi.UintPtr or custom.MyStruct)
final rawVarArgTypeSplit = rawBaseType.split('.');
Expand Down
4 changes: 1 addition & 3 deletions pkgs/ffigen/lib/src/context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import 'header_parser/utils.dart';
class Context {
final Logger logger;
final Config config;
final CursorIndex cursorIndex;
final bindingsIndex = BindingsIndex();
final savedMacros = <String, Macro>{};
final unnamedEnumConstants = <Constant>[];
Expand All @@ -33,8 +32,7 @@ class Context {
late final ExtraSymbols extraSymbols;

Context(this.logger, FfiGenerator generator, {Uri? libclangDylib})
: config = Config(generator),
cursorIndex = CursorIndex(logger) {
: config = Config(generator) {
objCBuiltInFunctions = ObjCBuiltInFunctions(
this,
// ignore: deprecated_member_use_from_same_package
Expand Down
10 changes: 4 additions & 6 deletions pkgs/ffigen/lib/src/header_parser/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ List<Binding> parseToBindings(Context context) {
clangCmdArgs = createDynamicStringArray(compilerOpts);
final cmdLen = compilerOpts.length;

// Contains all bindings. A set ensures we never have duplicates.
final bindings = <Binding>{};

// Log all headers for user.
context.logger.info('Input Headers: ${config.headers.entryPoints}');

Expand Down Expand Up @@ -138,19 +135,20 @@ List<Binding> parseToBindings(Context context) {
}

// Parse definitions from translation units.
for (final rootCursor in tuCursors) {
bindings.addAll(parseTranslationUnit(context, rootCursor));
}
parseTranslationUnits(context, tuCursors);
final bindings = context.bindingsIndex.bindings;

// Dispose translation units.
for (final tu in tuList) {
clang.clang_disposeTranslationUnit(tu);
}

// Add all saved unnamed enums.
// TODO: Store these directly in the bindingsIndex.
bindings.addAll(context.unnamedEnumConstants);

// Parse all saved macros.
// TODO: Store these directly in the bindingsIndex.
bindings.addAll(parseSavedMacros(context));

clangCmdArgs.dispose(cmdLen);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,29 @@ import '../clang_bindings/clang_bindings.dart' as clang_types;
import '../utils.dart';
import 'api_availability.dart';

Compound? parseStructDeclaration(
CachableBinding? parseStructDeclaration(
clang_types.CXCursor cursor,
Context context,
) => _parseCompoundDeclaration(
cursor,
context,
'Struct',
context.config.structs,
context.config.structTypeMappings,
Struct.new,
);

Compound? parseUnionDeclaration(clang_types.CXCursor cursor, Context context) =>
_parseCompoundDeclaration(
cursor,
context,
'Union',
context.config.unions,
Union.new,
);
CachableBinding? parseUnionDeclaration(
clang_types.CXCursor cursor,
Context context,
) => _parseCompoundDeclaration(
cursor,
context,
'Union',
context.config.unions,
context.config.unionTypeMappings,
Union.new,
);

/// Holds temporary information regarding [compound] while parsing.
class _ParsedCompound {
Expand Down Expand Up @@ -94,11 +98,12 @@ class _ParsedCompound {
}

/// Parses a compound declaration.
Compound? _parseCompoundDeclaration(
CachableBinding? _parseCompoundDeclaration(
clang_types.CXCursor cursor,
Context context,
String className,
Declarations configDecl,
Map<String, ImportedType> configTypeMappings,
Compound Function({
String? usr,
String? originalName,
Expand All @@ -109,12 +114,13 @@ Compound? _parseCompoundDeclaration(
})
constructor,
) {
// Parse the cursor definition instead, if this is a forward declaration.
final usr = cursor.usr();

final cachedCompound = context.bindingsIndex.getSeenCompound(usr);
if (cachedCompound != null) return cachedCompound;
final mappedType = configTypeMappings[cursor.spelling()];
if (mappedType != null) {
context.logger.fine(' Type Mapped from type-map: ${cursor.spelling()}');
return CachableBinding(mappedType);
}

final usr = cursor.usr();
final String declName;

// Only set name using USR if the type is not Anonymous (A struct is anonymous
Expand All @@ -136,9 +142,8 @@ Compound? _parseCompoundDeclaration(
}

final decl = Declaration(usr: usr, originalName: declName);
final Compound compound;
Compound compound;
if (declName.isEmpty) {
cursor = context.cursorIndex.getDefinition(cursor);
compound = constructor(
name: 'Unnamed$className',
usr: usr,
Expand All @@ -151,7 +156,6 @@ Compound? _parseCompoundDeclaration(
nativeType: cursor.type().spelling(),
);
} else {
cursor = context.cursorIndex.getDefinition(cursor);
context.logger.fine(
'++++ Adding $className: Name: $declName, ${cursor.completeStringRepr()}',
);
Expand All @@ -168,8 +172,10 @@ Compound? _parseCompoundDeclaration(
nativeType: cursor.type().spelling(),
);
}
context.bindingsIndex.addCompoundToSeen(usr, compound);
return compound;
return CachableBinding(
compound,
() => fillCompoundMembersIfNeeded(compound, cursor, context),
);
}

void fillCompoundMembersIfNeeded(
Expand All @@ -180,8 +186,6 @@ void fillCompoundMembersIfNeeded(
if (compound.parsedDependencies) return;
final logger = context.logger;

cursor = context.cursorIndex.getDefinition(cursor);

final parsed = _ParsedCompound(context, compound);
final className = compound is Struct ? 'Struct' : 'Union';
parsed.hasAttr = clang.clang_Cursor_hasAttrs(cursor) != 0;
Expand Down
29 changes: 14 additions & 15 deletions pkgs/ffigen/lib/src/header_parser/sub_parsers/enumdecl_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,16 @@ import 'api_availability.dart';
import 'unnamed_enumdecl_parser.dart';

/// Parses an enum declaration.
EnumClass parseEnumDeclaration(clang_types.CXCursor cursor, Context context) {
CachableBinding? parseEnumDeclaration(
clang_types.CXCursor cursor,
Context context,
) {
final config = context.config;
final logger = context.logger;
EnumClass? enumClass;
// Parse the cursor definition instead, if this is a forward declaration.
cursor = context.cursorIndex.getDefinition(cursor);

final usr = cursor.usr();

final cachedEnum = context.bindingsIndex.getSeenEnum(usr);
if (cachedEnum != null) return cachedEnum;

final String enumName;
// Only set name using USR if the type is not Anonymous (i.e not inside
// any typedef and declared inplace inside another type).
Expand Down Expand Up @@ -108,7 +106,6 @@ EnumClass parseEnumDeclaration(clang_types.CXCursor cursor, Context context) {
});
final suggestedStyle = isNSOptions ? EnumStyle.intConstants : null;
enumClass.style = config.enums.style(decl, suggestedStyle);
context.bindingsIndex.addEnumToSeen(usr, enumClass);
}

if (hasNegativeEnumConstants) {
Expand All @@ -121,12 +118,14 @@ EnumClass parseEnumDeclaration(clang_types.CXCursor cursor, Context context) {
enumClass?.nativeType = nativeType;
}

return enumClass ??
EnumClass(
usr: usr,
name: enumName,
nativeType: nativeType,
context: context,
isAnonymous: true,
);
return CachableBinding(
enumClass ??
EnumClass(
usr: usr,
name: enumName,
nativeType: nativeType,
context: context,
isAnonymous: true,
),
);
}
Loading
Loading