Skip to content

Commit e303f54

Browse files
natebiggsCommit Queue
authored andcommitted
Add dart2wasm support to pkg/dynamic_modules.
Also introduce some tests that include some potential pitfalls in the wasm implementation. Change-Id: I904930db992d45414b6e214b68ca542ca29419e4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/400901 Reviewed-by: Sigmund Cherem <[email protected]> Commit-Queue: Nate Biggs <[email protected]>
1 parent 0512bab commit e303f54

File tree

10 files changed

+212
-3
lines changed

10 files changed

+212
-3
lines changed

pkg/dynamic_modules/test/common/testing.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ Future<Object?> load(String moduleName,
3232
}
3333
return loadModuleFromBytes(bytes);
3434
}
35-
throw "load is not implemented for dart2wasm";
35+
// Dart2wasm implementation
36+
return loadModuleFromUri(
37+
Uri(scheme: '', path: 'modules/${moduleName}_module1.wasm'));
3638
}
3739

3840
/// Notify the test harness that the test has run to completion.

pkg/dynamic_modules/test/data/core_api/dynamic_interface.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ callable:
3333
member: 'get:current'
3434
- library: 'dart:core'
3535
class: 'Iterator'
36-
- library: 'dart:core'
3736

3837
# Needed to support creating list literals
3938
- library: 'dart:core'
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
# for details. All rights reserved. Use of this source code is governed by a
3+
# BSD-style license that can be found in the LICENSE file.
4+
5+
extendable:
6+
# TODO(sigmund): This should be included by default
7+
- library: 'dart:core'
8+
class: 'Object'
9+
- library: 'modules/common.dart'
10+
class: 'A'
11+
12+
callable:
13+
# TODO(sigmund): This should be included by default
14+
- library: 'dart:core'
15+
class: 'Object'
16+
- library: 'dart:core'
17+
class: 'pragma'
18+
member: '_'
19+
- library: 'modules/common.dart'
20+
class: 'A'
21+
member: 'getString'
22+
23+
can-be-overridden:
24+
- library: 'modules/common.dart'
25+
class: 'A'
26+
member: 'getString'
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:expect/expect.dart';
6+
7+
import '../../common/testing.dart' as helper;
8+
import 'modules/common.dart';
9+
10+
// It is an error to load a module that provides a second definition for
11+
// a library that already exists in the application.
12+
main() async {
13+
final a1 = await helper.load('entry1.dart') as A;
14+
final a2 = await helper.load('entry2.dart') as A;
15+
Expect.equals(a1.getString(), 'B');
16+
Expect.equals(a2.getString(), 'C');
17+
helper.done();
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
abstract class A {
6+
String getString();
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'common.dart';
6+
7+
class B implements A {
8+
@override
9+
String getString() => 'B';
10+
}
11+
12+
@pragma('dyn-module:entry-point')
13+
Object? entrypoint() => B();
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'common.dart';
6+
7+
class C implements A {
8+
@override
9+
String getString() => 'C';
10+
}
11+
12+
@pragma('dyn-module:entry-point')
13+
Object? entrypoint() => C();
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// Implementation of the [TargetExecutor] for dart2wasm.
6+
library;
7+
8+
import 'dart:io';
9+
import '../common/testing.dart' as helper;
10+
11+
import 'model.dart';
12+
import 'util.dart';
13+
import 'target.dart';
14+
15+
/// Logic to build and execute dynamic modules in dart2wasm.
16+
///
17+
/// In particular:
18+
/// * The initial app is built as a regular dart2wasm target, except that
19+
/// a dynamic interface is used to define hooks that dynamic modules update
20+
/// when loaded. The dynamic interface also prevents treeshaking of some
21+
/// entities.
22+
/// * For dynamic modules, dart2wasm validates that the module only accesses
23+
/// what's allowed by the dynamic interface.
24+
/// * For dynamic modules, dart2wasm exports a specific entry point function.
25+
class Dart2wasmExecutor implements TargetExecutor {
26+
static const rootScheme = 'dev-dart-app';
27+
late final bool _shouldCleanup;
28+
late final Directory _tmp;
29+
final Logger _logger;
30+
31+
Dart2wasmExecutor(this._logger) {
32+
/// Allow using an environment variable to run tests on a fixed directory.
33+
/// This prevents the directory from getting deleted too.
34+
var path = Platform.environment['TMP_DIR'] ?? '';
35+
if (path.isEmpty) {
36+
_tmp = Directory.systemTemp.createTempSync('_dynamic_module-');
37+
_shouldCleanup = false;
38+
} else {
39+
_tmp = Directory(path);
40+
if (!_tmp.existsSync()) _tmp.createSync();
41+
_shouldCleanup = false;
42+
}
43+
}
44+
45+
@override
46+
Future<void> suiteComplete() async {
47+
if (!_shouldCleanup) return;
48+
try {
49+
_tmp.delete(recursive: true);
50+
} on FileSystemException {
51+
// Windows bots sometimes fail to delete folders, and can make tests
52+
// flaky. It is OK in those cases to leak some files in the tmp folder,
53+
// these will eventually be cleared when a new bot instance is created.
54+
_logger.warning('Error trying to delete $_tmp');
55+
}
56+
}
57+
58+
Future _compile(
59+
String testName, String source, Uri sourceDir, bool isMain) async {
60+
var testDir = _tmp.uri.resolve(testName).toFilePath();
61+
var args = [
62+
'--packages=${repoRoot.toFilePath()}/.dart_tool/package_config.json',
63+
'--multi-root=${sourceDir.resolve('../../')}',
64+
'--multi-root-scheme=$rootScheme',
65+
// This is required while binaryen lacks support for partially closed
66+
// world optimizations.
67+
'-O0',
68+
'--extra-compiler-option=--dynamic-module-main=main.dart.dill',
69+
if (isMain)
70+
'--extra-compiler-option=--dynamic-module-interface='
71+
'$rootScheme:/data/$testName/dynamic_interface.yaml',
72+
'$rootScheme:/data/$testName/$source',
73+
'$source.wasm',
74+
];
75+
await runProcess(compileBenchmark.toFilePath(), args, testDir, _logger,
76+
'compile $testName/$source');
77+
}
78+
79+
@override
80+
Future compileApplication(DynamicModuleTest test) async {
81+
_ensureDirectory(test.name);
82+
_logger.info('Compile ${test.name} app');
83+
await _compile(test.name, test.main, test.folder, true);
84+
}
85+
86+
@override
87+
Future compileDynamicModule(DynamicModuleTest test, String name) async {
88+
_logger.info('Compile module ${test.name}.$name');
89+
_ensureDirectory(test.name);
90+
await _compile(test.name, test.dynamicModules[name]!, test.folder, false);
91+
}
92+
93+
@override
94+
Future executeApplication(DynamicModuleTest test) async {
95+
_logger.info('Execute ${test.name}');
96+
_ensureDirectory(test.name);
97+
98+
// We generate a self contained script that loads necessary preambles,
99+
// dart2wasm module loader, the necessary modules (the SDK and the main
100+
// module), and finally launches the app.
101+
var testDir = _tmp.uri.resolve('${test.name}/');
102+
var result = await runProcess(
103+
runBenchmark.toFilePath(),
104+
['${test.main}.wasm'],
105+
testDir.toFilePath(),
106+
_logger,
107+
'run_benchmark ${test.main}.wasm');
108+
var stdout = result.stdout as String;
109+
if (!stdout.contains(helper.successToken)) {
110+
_logger.error('Error: test didn\'t complete as expected.\n'
111+
'Make sure the test finishes and calls `helper.done()`.\n'
112+
'Test output:\n$stdout');
113+
throw Exception('missing helper.done');
114+
}
115+
}
116+
117+
void _ensureDirectory(String name) {
118+
var dir = Directory.fromUri(_tmp.uri.resolve(name));
119+
if (!dir.existsSync()) {
120+
dir.createSync();
121+
}
122+
}
123+
}

pkg/dynamic_modules/test/runner/main.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'dart:io';
1010
import 'package:args/args.dart';
1111

1212
import 'aot.dart';
13+
import 'dart2wasm.dart';
1314
import 'ddc.dart';
1415
import 'load.dart';
1516
import 'model.dart';
@@ -55,7 +56,7 @@ void main(List<String> args) async {
5556
executor = switch (target) {
5657
Target.ddc => DdcExecutor(logger),
5758
Target.aot => AotExecutor(logger),
58-
Target.dart2wasm => UnimplementedExecutor(logger),
59+
Target.dart2wasm => Dart2wasmExecutor(logger),
5960
};
6061

6162
final results = <DynamicModuleTestResult>[];

pkg/dynamic_modules/test/runner/util.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ Uri aotRuntimeBin = buildRootUri
9090
.resolve(useProduct ? 'dartaotruntime_product' : 'dartaotruntime');
9191
Uri vmPlatformDill = buildRootUri.resolve('vm_platform_strong.dill');
9292

93+
Uri dart2wasmSnapshot =
94+
_dartBin.resolve('snapshots/dart2wasm_product.snapshot');
95+
Uri dart2wasmPlatformDill = buildRootUri.resolve('dart2wasm_platform.dill');
96+
Uri dart2wasmLibrariesSpec = repoRoot.resolve('sdk/lib/libraries.json');
97+
Uri compileBenchmark = repoRoot.resolve('pkg/dart2wasm/tool/compile_benchmark');
98+
Uri runBenchmark = repoRoot.resolve('pkg/dart2wasm/tool/run_benchmark');
99+
93100
// Encodes test results in the format expected by Dart's CI infrastructure.
94101
class TestResultOutcome {
95102
// This encoder must generate each output element on its own line.

0 commit comments

Comments
 (0)