Skip to content

[macros] Add benchmark case generator, ToString() macro #3855

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
45 changes: 45 additions & 0 deletions working/macros/dart_model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,48 @@ as a library;\

The "macros" referred to in this exploration are independent of the in-progress
macro implementation, hence the "scare quotes".

## Benchmarks

`testing/benchmark` is a tool to assist in benchmarking, it creates codebases
of the specified size and codegen strategy.

### Scenarios

`trivial_macros` is a scenario with three trivial macros: `Equals()`,
`HashCode()` and `ToString()`. These inspect the fields of a class and generate
corresponding (shallow) `operator==`, `hashCode` and `toString()`.

### Strategies

Four strategies are supported:

`macro` uses the experimental macro implementation from the SDK;\
`dartModel` uses this exploratory macro implementation;\
`manual` writes equivalent code directly in the source;\
`none` means the functionality is missing altogether.

### Example

To compare the SDK macros with this exploratory implementation:

```
$ cd testing/benchmark

# Create a large example using SDK macros: 64 large libraries, about 67k LOC.
$ dart bin/main.dart macros macro 64
# Now open /tmp/dart_model_benchmark/macros in your IDE and modify some files
# to see how the analyzer responds.

# Create an equivalent example using `dart_model`.
$ dart bin/main.dart dartModel dartModel 64
# In a new terminal, launch the "macro" host.
$ cd macro_host
$ dart bin/main.dart /tmp/dart_model_benchmark/dartModel/package_under_test
# In a second new terminal, launch the "macro" process.
$ cd testing/test_macros
$ dart bin/main.dart
# Now open /tmp/dart_model_benchmark/dartModel in your IDE and modify some
# files to see how the analyzer responds; you can watch the macro host terminal
# to see when it is rewriting augmentation files.
```
7 changes: 7 additions & 0 deletions working/macros/dart_model/dart_model/lib/delta.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ extension type Delta.fromJson(Map<String, Object?> node) {

List<Removal> get removals => (node['removals'] as List).cast();

List<String> get uris => updates
.map((u) => u.path.uri)
.nonNulls
.followedBy(removals.map((r) => r.path.uri).nonNulls)
.toSet()
.toList();

String prettyPrint() => const JsonEncoder.withIndent(' ').convert(node);

void update(Model previous) {
Expand Down
2 changes: 2 additions & 0 deletions working/macros/dart_model/dart_model/lib/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ extension type Path.fromJson(List<Object?> node) {

List<String> get path => (node as List).cast();

String? get uri => path.isEmpty ? null : path.first;

Path followedByOne(String element) =>
Path(path.followedBy([element]).toList());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,8 @@ class DartModelAnalyzerService implements Service {
return Stream.fromIterable(<FileSystemEvent?>[null])
.followedBy(changes)
.asyncMap((change) async {
print('File change: $change');
if (change != null) context!.changeFile(change.path);
final changed = await context!.applyPendingFileChanges();
print('Changed: $changed');
await context!.applyPendingFileChanges();
final model = await this.query(query);
var delta = Delta.compute(previousModel, model);
// Round trip to check serialization works.
Expand Down
19 changes: 16 additions & 3 deletions working/macros/dart_model/macro_client/lib/macro_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,23 @@ class MacroClient {

MacroClient(this.arguments);

Future<void> host(List<Macro> macros) async {
final socket = await Socket.connect('localhost', 26199);
Future<void> run(List<Macro> macros) async {
print('~~~ setup');
print('Connect to localhost:26199.');
Socket socket;
try {
socket = await Socket.connect('localhost', 26199);
socket.setOption(SocketOption.tcpNoDelay, true);
} catch (_) {
print('Connection failed! Is `package:macro_host` running?');
exit(1);
}
final host = SocketHost(socket);
for (final macro in macros) {

print('~~~ running macros');
for (var i = 0; i != macros.length; ++i) {
final macro = macros[i];
print('${i + 1}. ${macro.name}');
macro.start(host);
}
}
Expand Down
19 changes: 19 additions & 0 deletions working/macros/dart_model/macro_host/bin/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ import 'package:dart_model_analyzer_service/dart_model_analyzer_service.dart';
import 'package:macro_host/macro_host.dart';

Future<void> main(List<String> arguments) async {
if (arguments.length != 1) {
_usage();
}
final workspace = arguments[0];
if (!Directory(workspace).existsSync()) {
_usage();
}

print('~~~ setup');
print('Launching analyzer on: $workspace');
final contextBuilder = ContextBuilder();
final analysisContext = contextBuilder.createContext(
contextRoot:
Expand All @@ -22,3 +31,13 @@ Future<void> main(List<String> arguments) async {
return File(path);
}).run();
}

void _usage() {
print('''
Usage: dart bin/main.dart <absolute path to workspace>

Hosts macros in a workspace, so they can react to changes in the workspace
and write updates to augmentations.
''');
exit(1);
}
63 changes: 43 additions & 20 deletions working/macros/dart_model/macro_host/lib/macro_host.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import 'dart:async';
import 'dart:io';

import 'package:dart_model/model.dart';
import 'package:dart_model/query.dart';
import 'package:macro_protocol/host.dart';

Expand All @@ -16,47 +17,69 @@ class MacroHost implements Host {
final File? Function(Uri) uriConverter;
ServerSocket? serverSocket;
final Map<String, Map<Object, String>> _augmentationsByUri = {};
final Set<String> _augmentationsToWrite = {};

int _augmentationCounter = 0;
bool _flushing = false;

MacroHost(this.service, this.uriConverter);

Future<void> run() async {
serverSocket = await ServerSocket.bind('localhost', 26199);
print('macro_host listening on localhost:26199');

print('Listening on localhost:26199.');
print('~~~ hosting');
await for (final socket in serverSocket!) {
print('Incoming connection.');
socket.setOption(SocketOption.tcpNoDelay, true);
SocketClient(this, socket);
}
}

void handle(Socket socket) {
print('Got $socket');
}

@override
Future<void> augment(
{required Object macro,
{required QualifiedName macro,
required String uri,
required String augmentation}) async {
print('Augment: $uri $augmentation');
final baseFile = uriConverter(Uri.parse(uri))!;
final baseName =
baseFile.path.substring(baseFile.path.lastIndexOf('/') + 1);
final augmentationFile = File(baseFile.path.replaceAll('.dart', '.a.dart'));

print(_augmentationsByUri);
print(' ${macro.name} --${augmentation.length}--> $uri');
if (_augmentationsByUri[uri] == null) {
print('create map');
_augmentationsByUri[uri] = Map.identity();
_augmentationsByUri[uri] = {};
}
final augmentations = _augmentationsByUri[uri]!;
augmentations[macro] = augmentation;
print(_augmentationsByUri);
_augmentationsToWrite.add(uri);

// Give other augmentations a chance to arrive before flushing.
if (_flushing) return;
++_augmentationCounter;
final augmentationCounter = _augmentationCounter;
unawaited(Future.delayed(Duration(milliseconds: 20)).then<void>((_) async {
if (_augmentationCounter != augmentationCounter) return;
_flushing = true;
while (_augmentationsToWrite.isNotEmpty) {
await flushAugmentations();
}
_flushing = false;
}));
}

// TODO(davidmorgan): write async? Needs locking.
augmentationFile.writeAsStringSync('''
Future<void> flushAugmentations() async {
final augmentationsToWrite = _augmentationsToWrite.toList();
_augmentationsToWrite.clear();
final futures = <Future>[];
for (final uri in augmentationsToWrite) {
final augmentations = _augmentationsByUri[uri]!;
final baseFile = uriConverter(Uri.parse(uri))!;
final baseName =
baseFile.path.substring(baseFile.path.lastIndexOf('/') + 1);
final augmentationFile =
File(baseFile.path.replaceAll('.dart', '.a.dart'));
print('Write: ${augmentationFile.path}');
futures.add(augmentationFile.writeAsString('''
augment library '$baseName';

${augmentations.values.join('\n\n')}
''');
'''));
}
await Future.wait(futures);
}
}
33 changes: 33 additions & 0 deletions working/macros/dart_model/testing/benchmark/bin/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2024, 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 'dart:io';

import 'package:benchmark/trivial_macros/input_generator.dart';
import 'package:benchmark/workspace.dart';

Future<void> main(List<String> arguments) async {
if (arguments.length != 3) {
print('''
Creates packages to benchmark macro performance. Usage:

dart bin/main.dart <workspace name> <macro|dartModel|manual|none> <# libraries>
''');
exit(1);
}

final workspaceName = arguments[0];
final strategy = Strategy.values.where((e) => e.name == arguments[1]).single;
final libraryCount = int.parse(arguments[2]);

print('Creating under: /tmp/dart_model_benchmark/$workspaceName');
final workspace = Workspace(workspaceName);
final inputGenerator = TrivialMacrosInputGenerator(
fieldsPerClass: 100,
classesPerLibrary: 10,
librariesPerCycle: libraryCount,
strategy: strategy);
inputGenerator.generate(workspace);
await workspace.pubGet();
}
9 changes: 9 additions & 0 deletions working/macros/dart_model/testing/benchmark/lib/random.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2024, 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 'dart:math';

final _random = Random.secure();
int get largeRandom =>
_random.nextInt(0xFFFFFFFF) + (_random.nextInt(0x7FFFFFFF) << 32);
Loading
Loading