Unverified Commit 2269dc10 authored by Jackson Gardner's avatar Jackson Gardner Committed by GitHub

Add option to run wasm-opt on module output. (#124831)

This fixes https://github.com/flutter/flutter/issues/124159

Adds the command line argument `--wasm-opt` to optimize the web assembly output.
parent aa8cc44e
......@@ -43,6 +43,8 @@ enum Artifact {
dart2jsSnapshot,
/// The dart snapshot of the dart2wasm compiler.
dart2wasmSnapshot,
/// The wasm-opt binary that ships with the dart-sdk
wasmOptBinary,
/// The root of the Linux desktop sources.
linuxDesktopPath,
......@@ -184,6 +186,8 @@ String? _artifactToFileName(Artifact artifact, Platform hostPlatform, [ BuildMod
return 'dart2js.dart.snapshot';
case Artifact.dart2wasmSnapshot:
return 'dart2wasm_product.snapshot';
case Artifact.wasmOptBinary:
return 'wasm-opt$exe';
case Artifact.frontendServerSnapshotForEngineDartSdk:
return 'frontend_server.dart.snapshot';
case Artifact.linuxDesktopPath:
......@@ -510,6 +514,7 @@ class CachedArtifacts implements Artifacts {
case Artifact.engineDartAotRuntime:
case Artifact.dart2jsSnapshot:
case Artifact.dart2wasmSnapshot:
case Artifact.wasmOptBinary:
case Artifact.frontendServerSnapshotForEngineDartSdk:
case Artifact.constFinder:
case Artifact.flutterFramework:
......@@ -550,6 +555,7 @@ class CachedArtifacts implements Artifacts {
case Artifact.engineDartAotRuntime:
case Artifact.dart2jsSnapshot:
case Artifact.dart2wasmSnapshot:
case Artifact.wasmOptBinary:
case Artifact.frontendServerSnapshotForEngineDartSdk:
case Artifact.constFinder:
case Artifact.flutterMacOSFramework:
......@@ -608,6 +614,7 @@ class CachedArtifacts implements Artifacts {
case Artifact.engineDartAotRuntime:
case Artifact.dart2jsSnapshot:
case Artifact.dart2wasmSnapshot:
case Artifact.wasmOptBinary:
case Artifact.frontendServerSnapshotForEngineDartSdk:
case Artifact.icuData:
case Artifact.isolateSnapshotData:
......@@ -646,6 +653,11 @@ class CachedArtifacts implements Artifacts {
_dartSdkPath(_cache), 'bin', 'snapshots',
_artifactToFileName(artifact, _platform),
);
case Artifact.wasmOptBinary:
return _fileSystem.path.join(
_dartSdkPath(_cache), 'bin', 'utils',
_artifactToFileName(artifact, _platform),
);
case Artifact.flutterTester:
case Artifact.vmSnapshotData:
case Artifact.isolateSnapshotData:
......@@ -963,6 +975,8 @@ class CachedLocalEngineArtifacts implements Artifacts {
case Artifact.dart2wasmSnapshot:
case Artifact.frontendServerSnapshotForEngineDartSdk:
return _fileSystem.path.join(_getDartSdkPath(), 'bin', 'snapshots', artifactFileName);
case Artifact.wasmOptBinary:
return _fileSystem.path.join(_getDartSdkPath(), 'bin', 'utils', artifactFileName);
case Artifact.flutterToolsFileGenerators:
return _getFileGeneratorsPath();
}
......@@ -1092,6 +1106,11 @@ class CachedLocalWebSdkArtifacts implements Artifacts {
_getDartSdkPath(), 'bin', 'snapshots',
_artifactToFileName(artifact, _platform, mode),
);
case Artifact.wasmOptBinary:
return _fileSystem.path.join(
_getDartSdkPath(), 'bin', 'snapshots',
_artifactToFileName(artifact, _platform, mode),
);
case Artifact.genSnapshot:
case Artifact.flutterTester:
case Artifact.flutterFramework:
......
......@@ -242,9 +242,12 @@ class Dart2WasmTarget extends Dart2WebTarget {
if (buildModeEnvironment == null) {
throw MissingDefineException(kBuildMode, name);
}
final WasmCompilerConfig compilerConfig = WasmCompilerConfig.fromBuildSystemEnvironment(environment.defines);
final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment);
final Artifacts artifacts = globals.artifacts!;
final File outputWasmFile = environment.buildDir.childFile('main.dart.wasm');
final File outputWasmFile = environment.buildDir.childFile(
compilerConfig.runWasmOpt ? 'main.dart.unopt.wasm' : 'main.dart.wasm'
);
final File depFile = environment.buildDir.childFile('dart2wasm.d');
final String dartSdkPath = artifacts.getArtifactPath(Artifact.engineDartSdkPath, platform: TargetPlatform.web_javascript);
final String dartSdkRoot = environment.fileSystem.directory(dartSdkPath).parent.path;
......@@ -260,7 +263,7 @@ class Dart2WasmTarget extends Dart2WebTarget {
...decodeCommaSeparated(environment.defines, kExtraFrontEndOptions),
for (final String dartDefine in decodeDartDefines(environment.defines, kDartDefines))
'-D$dartDefine',
...WasmCompilerConfig.fromBuildSystemEnvironment(environment.defines).toCommandOptions(),
...compilerConfig.toCommandOptions(),
'--packages=.dart_tool/package_config.json',
'--dart-sdk=$dartSdkPath',
'--multi-root-scheme',
......@@ -286,6 +289,35 @@ class Dart2WasmTarget extends Dart2WebTarget {
if (compileResult.exitCode != 0) {
throw Exception(_collectOutput(compileResult));
}
if (compilerConfig.runWasmOpt) {
final String wasmOptBinary = artifacts.getArtifactPath(
Artifact.wasmOptBinary,
platform: TargetPlatform.web_javascript
);
final File optimizedOutput = environment.buildDir.childFile('main.dart.wasm');
final List<String> optimizeArgs = <String>[
wasmOptBinary,
'-all',
'--closed-world',
'-tnh',
'-O3',
'--type-ssa',
'--gufa',
'-O3',
'--type-merging',
outputWasmFile.path,
'-o',
optimizedOutput.path,
];
final ProcessResult optimizeResult = await globals.processManager.run(optimizeArgs);
if (optimizeResult.exitCode != 0) {
throw Exception(_collectOutput(optimizeResult));
}
// Rename the .mjs file not to have the `.unopt` bit
final File jsRuntimeFile = environment.buildDir.childFile('main.dart.unopt.mjs');
await jsRuntimeFile.rename(environment.buildDir.childFile('main.dart.mjs').path);
}
}
@override
......
......@@ -101,6 +101,12 @@ class BuildWebCommand extends BuildSubCommand {
negatable: false,
hide: !featureFlags.isFlutterWebWasmEnabled,
);
argParser.addFlag(
'wasm-opt',
help: 'Run wasm-opt on the output wasm module.',
negatable: false,
hide: !featureFlags.isFlutterWebWasmEnabled,
);
}
final FileSystem _fileSystem;
......@@ -133,6 +139,7 @@ class BuildWebCommand extends BuildSubCommand {
}
compilerConfig = WasmCompilerConfig(
omitTypeChecks: boolArg('omit-type-checks'),
runWasmOpt: boolArg('wasm-opt'),
);
} else {
compilerConfig = JsCompilerConfig(
......
......@@ -130,6 +130,7 @@ class JsCompilerConfig extends WebCompilerConfig {
class WasmCompilerConfig extends WebCompilerConfig {
const WasmCompilerConfig({
required this.omitTypeChecks,
required this.runWasmOpt,
});
/// Creates a new [WasmCompilerConfig] from build system environment values.
......@@ -139,20 +140,26 @@ class WasmCompilerConfig extends WebCompilerConfig {
Map<String, String> defines) =>
WasmCompilerConfig(
omitTypeChecks: defines[kOmitTypeChecks] == 'true',
runWasmOpt: defines[kRunWasmOpt] == 'true',
);
/// Build environment for [omitTypeChecks];
static const String kOmitTypeChecks = 'WasmOmitTypeChecks';
static const String kRunWasmOpt = 'RunWasmOpt';
/// If `omit-type-checks` should be passed to `dart2wasm`.
final bool omitTypeChecks;
// Run wasm-opt on the resulting module.
final bool runWasmOpt;
@override
bool get isWasm => true;
@override
Map<String, String> toBuildSystemEnvironment() => <String, String>{
kOmitTypeChecks: omitTypeChecks.toString(),
kRunWasmOpt: runWasmOpt.toString(),
};
List<String> toCommandOptions() => <String>[
......
......@@ -845,6 +845,62 @@ void main() {
ProcessManager: () => processManager,
}));
test('Dart2WasmTarget invokes dart2wasm and wasm-opt when RunWasmOpt is enabled', () => testbed.run(() async {
environment.defines[kBuildMode] = 'release';
environment.defines[WasmCompilerConfig.kRunWasmOpt] = 'true';
final File depFile = environment.buildDir.childFile('dart2wasm.d');
final File outputJsFile = environment.buildDir.childFile('main.dart.unopt.mjs');
processManager.addCommand(FakeCommand(
command: <String>[
'bin/cache/dart-sdk/bin/dartaotruntime',
'--disable-dart-dev',
'bin/cache/dart-sdk/bin/snapshots/dart2wasm_product.snapshot',
'-Ddart.vm.product=true',
'--packages=.dart_tool/package_config.json',
'--dart-sdk=bin/cache/dart-sdk',
'--multi-root-scheme',
'org-dartlang-sdk',
'--multi-root',
'bin/cache/flutter_web_sdk',
'--multi-root',
'bin/cache',
'--libraries-spec',
'bin/cache/flutter_web_sdk/libraries.json',
'--depfile=${depFile.absolute.path}',
environment.buildDir.childFile('main.dart').absolute.path,
environment.buildDir.childFile('main.dart.unopt.wasm').absolute.path,
], onRun: () => outputJsFile..createSync()..writeAsStringSync('foo')));
processManager.addCommand(FakeCommand(
command: <String>[
'bin/cache/dart-sdk/bin/utils/wasm-opt',
'-all',
'--closed-world',
'-tnh',
'-O3',
'--type-ssa',
'--gufa',
'-O3',
'--type-merging',
environment.buildDir.childFile('main.dart.unopt.wasm').absolute.path,
'-o',
environment.buildDir.childFile('main.dart.wasm').absolute.path,
]));
await Dart2WasmTarget(WebRendererMode.canvaskit).build(environment);
expect(outputJsFile.existsSync(), isFalse);
final File movedJsFile = environment.buildDir.childFile('main.dart.mjs');
expect(movedJsFile.existsSync(), isTrue);
expect(movedJsFile.readAsStringSync(), 'foo');
}, overrides: <Type, Generator>{
ProcessManager: () => processManager,
}));
test('Dart2WasmTarget with skwasm renderer adds extra flags', () => testbed.run(() async {
environment.defines[kBuildMode] = 'release';
final File depFile = environment.buildDir.childFile('dart2wasm.d');
......
......@@ -47,6 +47,7 @@ void main() {
'HasWebPlugins': 'false',
'ServiceWorkerStrategy': ServiceWorkerStrategy.offlineFirst.cliName,
'WasmOmitTypeChecks': 'false',
'RunWasmOpt': 'false',
'BuildMode': 'debug',
'DartObfuscation': 'false',
'TrackWidgetCreation': 'true',
......@@ -70,7 +71,10 @@ void main() {
'target',
BuildInfo.debug,
ServiceWorkerStrategy.offlineFirst,
compilerConfig: const WasmCompilerConfig(omitTypeChecks: false),
compilerConfig: const WasmCompilerConfig(
omitTypeChecks: false,
runWasmOpt: false
),
);
expect(logger.statusText, contains('Compiling target for the Web...'));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment