f99f5b1a2b
If a function doesn't have an effect we can now mark it via
`@pragma('wasm:pure-function')`. We'll then emit this as metadata
in the `binaryen.remove.if.unused` custom section.
This allows `wasm-opt` to remove calls to such functions if the result
of the call isn't used.
For now we mark a few string functions as pure.
Closes https://github.com/dart-lang/sdk/issues/62665
Change-Id: I8d38fb5894fd98248dc4d648d99c8cdcddc271a5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/481802
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
198 lines
5.1 KiB
Dart
198 lines
5.1 KiB
Dart
// Copyright (c) 2023, 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 '../serialize/printer.dart';
|
|
import '../serialize/serialize.dart';
|
|
import 'ir.dart';
|
|
|
|
/// A local variable defined in a function.
|
|
class Local {
|
|
final int index;
|
|
final ValueType type;
|
|
|
|
Local(this.index, this.type);
|
|
|
|
void printTo(IrPrinter p, Map<int, String> localNames,
|
|
{bool isParam = false}) {
|
|
p.write(isParam ? 'param' : 'local');
|
|
p.write(' ');
|
|
p.writeLocalIndexReference(index);
|
|
p.write(' ');
|
|
p.writeValueType(type);
|
|
}
|
|
|
|
@override
|
|
String toString() => "$index";
|
|
}
|
|
|
|
/// An (imported or defined) function.
|
|
abstract class BaseFunction with Indexable, Exportable {
|
|
@override
|
|
final FinalizableIndex finalizableIndex;
|
|
final FunctionType type;
|
|
String? functionName;
|
|
@override
|
|
final Module enclosingModule;
|
|
|
|
/// Whether this function is pure and has no effect.
|
|
///
|
|
/// If marked as spure, we'll emit metadata in the
|
|
/// `binaryen.removable.if.unused` custom section.
|
|
bool isPure = false;
|
|
|
|
BaseFunction(this.enclosingModule, this.finalizableIndex, this.type,
|
|
[this.functionName]);
|
|
|
|
@override
|
|
String get name => functionName ?? super.name;
|
|
|
|
/// Creates an export of this function in this module.
|
|
@override
|
|
Export buildExport(String name) {
|
|
return FunctionExport(name, this);
|
|
}
|
|
}
|
|
|
|
/// A function defined in a module.
|
|
class DefinedFunction extends BaseFunction implements Serializable {
|
|
late final Instructions body;
|
|
|
|
/// All local variables defined in the function, including its inputs.
|
|
List<Local> get locals => body.locals;
|
|
|
|
Map<int, String> get localNames => body.localNames;
|
|
|
|
DefinedFunction(
|
|
super.enclosingModule, this.body, super.finalizableIndex, super.type,
|
|
[super.functionName]);
|
|
|
|
DefinedFunction.withoutBody(
|
|
super.enclosingModule, super.finalizableIndex, super.type,
|
|
[super.functionName]);
|
|
|
|
@override
|
|
void serialize(Serializer s) {
|
|
// Serialize locals internally first in order to compute the total size of
|
|
// the serialized data.
|
|
final localS = Serializer();
|
|
int paramCount = type.inputs.length;
|
|
int entries = 0;
|
|
for (int i = paramCount + 1; i <= locals.length; i++) {
|
|
if (i == locals.length || locals[i - 1].type != locals[i].type) entries++;
|
|
}
|
|
localS.writeUnsigned(entries);
|
|
int start = paramCount;
|
|
for (int i = paramCount + 1; i <= locals.length; i++) {
|
|
if (i == locals.length || locals[i - 1].type != locals[i].type) {
|
|
localS.writeUnsigned(i - start);
|
|
localS.write(locals[i - 1].type);
|
|
start = i;
|
|
}
|
|
}
|
|
|
|
// Bundle locals and body
|
|
localS.write(body);
|
|
s.writeUnsigned(localS.data.length);
|
|
s.sourceMapSerializer.copyMappings(localS.sourceMapSerializer, s.offset);
|
|
s.writeData(localS);
|
|
}
|
|
|
|
void printTo(IrPrinter p) {
|
|
if (isPure) {
|
|
p.writeln('(@binaryen.removable.if.unused)');
|
|
}
|
|
p.write('(func ');
|
|
p.writeFunctionReference(this);
|
|
String? exportName;
|
|
for (final f in enclosingModule.exports.exported) {
|
|
if (f is FunctionExport && f.function == this) {
|
|
exportName = f.name;
|
|
break;
|
|
}
|
|
}
|
|
if (exportName != null) {
|
|
p.write(' ');
|
|
p.writeExport(exportName);
|
|
}
|
|
|
|
p.withLocalNames(localNames, () {
|
|
if (type.inputs.isNotEmpty || type.outputs.isNotEmpty) {
|
|
p.write(' ');
|
|
type.printSignatureWithNamesTo(p, oneLine: true);
|
|
}
|
|
p.writeln('');
|
|
p.withIndent(() {
|
|
for (int i = type.inputs.length; i < locals.length; ++i) {
|
|
p.write('(');
|
|
locals[i].printTo(p, localNames);
|
|
p.writeln(')');
|
|
}
|
|
body.printTo(p);
|
|
});
|
|
});
|
|
p.write(')');
|
|
}
|
|
|
|
void printDeclarationTo(IrPrinter p) {
|
|
p.write('(func \$$functionName ');
|
|
p.withLocalNames(localNames, () {
|
|
type.printSignatureWithNamesTo(p, oneLine: true);
|
|
});
|
|
p.write(' <...>');
|
|
p.writeln(')');
|
|
}
|
|
|
|
@override
|
|
String toString() => functionName ?? "#$finalizableIndex";
|
|
}
|
|
|
|
/// An imported function.
|
|
class ImportedFunction extends BaseFunction implements Import {
|
|
@override
|
|
final String module;
|
|
@override
|
|
final String name;
|
|
|
|
ImportedFunction(super.enclosingModule, this.module, this.name,
|
|
super.finalizableIndex, super.type,
|
|
[super.functionName]);
|
|
|
|
@override
|
|
void serialize(Serializer s) {
|
|
s.writeName(module);
|
|
s.writeName(name);
|
|
s.writeByte(0x00);
|
|
s.writeUnsigned(type.index);
|
|
}
|
|
|
|
void printTo(IrPrinter p) {
|
|
if (isPure) {
|
|
p.writeln('(@binaryen.removable.if.unused)');
|
|
}
|
|
p.write('(func ');
|
|
p.writeFunctionReference(this);
|
|
p.write(' ');
|
|
p.writeImport(module, name);
|
|
p.write(' ');
|
|
type.printOneLineSignatureTo(p);
|
|
p.write(')');
|
|
}
|
|
|
|
@override
|
|
String toString() => "$module.$name";
|
|
}
|
|
|
|
class FunctionExport extends Export {
|
|
final BaseFunction function;
|
|
|
|
FunctionExport(super.name, this.function);
|
|
|
|
@override
|
|
void serialize(Serializer s) {
|
|
s.writeName(name);
|
|
s.writeByte(0x00);
|
|
s.writeUnsigned(function.index);
|
|
}
|
|
}
|