[dart2wasm] Explicitly model functions that do not return
Similar to [0] which recognizes functions that return always nulls, we recognize functions that never return and * make the wasm function have no outputs * make call sites emit `unreachable()` after the call (to inform binaryen & wasm runtime that this is unreachable) Before we had an artificial construct where we made such functions have a `w.RefType(HeapType.none, nullable: false)` return type (i.e. bottom, i.e. no values) and encoded that way it's unreachable. We also change some exported wasm functions to explicitly use wasm types in the signature (namely `WasmVoid` instead of Dart `void`). [0] https://dart-review.googlesource.com/c/sdk/+/497620 Change-Id: I3724e777cda23c0cf2c8a7dd2e473f3fef0a4f54 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499240 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Srujan Gaddam <srujzs@google.com>
This commit is contained in:
committed by
dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
d0e0290702
commit
147ed24717
@@ -2077,6 +2077,11 @@ abstract class AstCodeGenerator
|
||||
b.ref_null(w.HeapType.none);
|
||||
return w.RefType(w.HeapType.none, nullable: true);
|
||||
}
|
||||
if (selector.synthesizeNoReturn) {
|
||||
assert(selector.signature.outputs.isEmpty);
|
||||
b.unreachable();
|
||||
return voidMarker;
|
||||
}
|
||||
|
||||
return translator.outputOrVoid(signature.outputs);
|
||||
}
|
||||
@@ -6099,6 +6104,9 @@ abstract class CallTarget {
|
||||
/// Whether callers should synthesize a `null` return value.
|
||||
bool get synthesizeNullReturnValue => false;
|
||||
|
||||
/// Whether callee never returns and callers should emit `unreachable`.
|
||||
bool get synthesizeNoReturn => false;
|
||||
|
||||
/// Whether this call target supports inlining.
|
||||
bool get supportsInlining => false;
|
||||
|
||||
@@ -6133,6 +6141,9 @@ class AstCallTarget extends CallTarget {
|
||||
bool get synthesizeNullReturnValue =>
|
||||
_translator.synthesizeNullReturnValue(_reference);
|
||||
|
||||
@override
|
||||
bool get synthesizeNoReturn => _translator.synthesizeNoReturn(_reference);
|
||||
|
||||
@override
|
||||
String get name => _translator.functions.getFunctionName(_reference);
|
||||
|
||||
|
||||
@@ -67,6 +67,12 @@ class SelectorInfo {
|
||||
/// Will be set during `_computeSignature`.
|
||||
late final bool synthesizeNullReturnValue;
|
||||
|
||||
/// Whether the call will never return and callers can emit an
|
||||
/// `unreachable()` after the call.
|
||||
///
|
||||
/// Will be set during `_computeSignature`.
|
||||
late final bool synthesizeNoReturn;
|
||||
|
||||
/// The selector's member's name.
|
||||
final String name;
|
||||
|
||||
@@ -210,14 +216,18 @@ class SelectorInfo {
|
||||
outputSets.length,
|
||||
(i) => _upperBound(outputSets[i], ensureBoxed: false),
|
||||
);
|
||||
if (outputs case [w.RefType(heapType: w.HeapType.none, nullable: true)]) {
|
||||
// All functions are guaranteed to return null.
|
||||
// Will prune the signature and make call sites synthesize `null` if
|
||||
// needed.
|
||||
if (outputs case [
|
||||
w.RefType(heapType: w.HeapType.none, nullable: final nullable),
|
||||
]) {
|
||||
// All functions are guaranteed to return null or are unreachable.
|
||||
// => Prune signature to not return anything
|
||||
// => Tell callers to synthesize `null` or emit `unreachable`.
|
||||
outputs.clear();
|
||||
synthesizeNullReturnValue = true;
|
||||
synthesizeNullReturnValue = nullable;
|
||||
synthesizeNoReturn = !nullable;
|
||||
} else {
|
||||
synthesizeNullReturnValue = isSetterOrIndexSetter;
|
||||
synthesizeNoReturn = false;
|
||||
}
|
||||
return translator.typesBuilder.defineFunction([
|
||||
inputs[0],
|
||||
|
||||
@@ -107,6 +107,7 @@ class FunctionCollector {
|
||||
null,
|
||||
isImportOrExport: true,
|
||||
synthesizeNullReturnValue: false,
|
||||
synthesizeNoReturn: false,
|
||||
);
|
||||
return _functions[member.reference] =
|
||||
translator
|
||||
@@ -141,6 +142,7 @@ class FunctionCollector {
|
||||
null,
|
||||
isImportOrExport: true,
|
||||
synthesizeNullReturnValue: false,
|
||||
synthesizeNoReturn: false,
|
||||
)
|
||||
: translator.signatureForDirectCall(target);
|
||||
|
||||
@@ -253,6 +255,7 @@ class FunctionCollector {
|
||||
w.FunctionType _getFunctionType(Reference target) {
|
||||
final Member member = target.asMember;
|
||||
final synthesizeNullReturnValue = this.synthesizeNullReturnValue(target);
|
||||
final synthesizeNoReturn = this.synthesizeNoReturn(target);
|
||||
|
||||
if (target.isBodyReference) {
|
||||
// This is the function body that is always called directly (never via
|
||||
@@ -262,11 +265,16 @@ class FunctionCollector {
|
||||
translator,
|
||||
member,
|
||||
synthesizeNullReturnValue,
|
||||
synthesizeNoReturn,
|
||||
);
|
||||
}
|
||||
|
||||
return member.accept1(
|
||||
_FunctionTypeGenerator(translator, synthesizeNullReturnValue),
|
||||
_FunctionTypeGenerator(
|
||||
translator,
|
||||
synthesizeNullReturnValue,
|
||||
synthesizeNoReturn,
|
||||
),
|
||||
target,
|
||||
);
|
||||
}
|
||||
@@ -277,13 +285,25 @@ class FunctionCollector {
|
||||
if (member.name == indexSetName) return true;
|
||||
|
||||
final returnType = translator.typeOfReturnValue(member);
|
||||
final wasmType = translator.translateType(returnType);
|
||||
final wasmType = translator.translateReturnType(returnType);
|
||||
if (wasmType case w.RefType(heapType: w.HeapType.none, nullable: true)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool synthesizeNoReturn(Reference target) {
|
||||
final member = target.asMember;
|
||||
if (member is! Procedure) return false;
|
||||
|
||||
final returnType = translator.typeOfReturnValue(member);
|
||||
final wasmType = translator.translateReturnType(returnType);
|
||||
if (wasmType case w.RefType(heapType: w.HeapType.none, nullable: false)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String getFunctionName(Reference target) {
|
||||
final Member member = target.asMember;
|
||||
String memberName = member.toString();
|
||||
@@ -433,8 +453,13 @@ class FunctionCollector {
|
||||
class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
|
||||
final Translator translator;
|
||||
final bool synthesizeNullReturnValue;
|
||||
final bool synthesizeNoReturn;
|
||||
|
||||
_FunctionTypeGenerator(this.translator, this.synthesizeNullReturnValue);
|
||||
_FunctionTypeGenerator(
|
||||
this.translator,
|
||||
this.synthesizeNullReturnValue,
|
||||
this.synthesizeNoReturn,
|
||||
);
|
||||
|
||||
@override
|
||||
w.FunctionType visitField(Field node, Reference target) {
|
||||
@@ -445,6 +470,7 @@ class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
|
||||
target,
|
||||
null,
|
||||
synthesizeNullReturnValue: synthesizeNullReturnValue,
|
||||
synthesizeNoReturn: synthesizeNoReturn,
|
||||
);
|
||||
}
|
||||
assert(
|
||||
@@ -465,6 +491,7 @@ class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
|
||||
target,
|
||||
translator.translateType(receiverType),
|
||||
synthesizeNullReturnValue: synthesizeNullReturnValue,
|
||||
synthesizeNoReturn: synthesizeNoReturn,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -477,6 +504,7 @@ class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
|
||||
target,
|
||||
null,
|
||||
synthesizeNullReturnValue: synthesizeNullReturnValue,
|
||||
synthesizeNoReturn: synthesizeNoReturn,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -502,6 +530,7 @@ class _FunctionTypeGenerator extends MemberVisitor1<w.FunctionType, Reference> {
|
||||
target,
|
||||
receiverType,
|
||||
synthesizeNullReturnValue: synthesizeNullReturnValue,
|
||||
synthesizeNoReturn: synthesizeNoReturn,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -738,6 +767,7 @@ w.FunctionType makeFunctionTypeForBody(
|
||||
Translator translator,
|
||||
Member member,
|
||||
bool synthesizeNullReturnValue,
|
||||
bool synthesizeNoReturn,
|
||||
) {
|
||||
assert(member.isInstanceMember);
|
||||
assert(member is Procedure);
|
||||
@@ -758,7 +788,7 @@ w.FunctionType makeFunctionTypeForBody(
|
||||
translator.translateType(translator.typeOfCheckedParameterVariable(p)),
|
||||
];
|
||||
|
||||
final hasNoReturnValue = synthesizeNullReturnValue;
|
||||
final hasNoReturnValue = synthesizeNullReturnValue || synthesizeNoReturn;
|
||||
final outputs = [
|
||||
if (!hasNoReturnValue)
|
||||
translator.translateReturnType(translator.typeOfReturnValue(member)),
|
||||
@@ -841,6 +871,7 @@ w.FunctionType _makeFunctionType(
|
||||
Reference target,
|
||||
w.ValueType? receiverType, {
|
||||
required bool synthesizeNullReturnValue,
|
||||
required bool synthesizeNoReturn,
|
||||
bool isImportOrExport = false,
|
||||
}) {
|
||||
Member member = target.asMember;
|
||||
@@ -881,7 +912,7 @@ w.FunctionType _makeFunctionType(
|
||||
(t is InterfaceType && t.classNode == translator.wasmVoidClass);
|
||||
|
||||
final List<w.ValueType> outputs;
|
||||
final hasNoReturnValue = target.isSetter || synthesizeNullReturnValue;
|
||||
final hasNoReturnValue = synthesizeNullReturnValue || synthesizeNoReturn;
|
||||
if (hasNoReturnValue) {
|
||||
outputs = const [];
|
||||
} else {
|
||||
|
||||
@@ -783,6 +783,11 @@ class Translator with KernelNodes {
|
||||
b.ref_null(w.HeapType.none);
|
||||
return [w.RefType(w.HeapType.none, nullable: true)];
|
||||
}
|
||||
if (callTarget.synthesizeNoReturn) {
|
||||
assert(outputs.isEmpty);
|
||||
b.unreachable();
|
||||
return const [];
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
@@ -1782,16 +1787,24 @@ class Translator with KernelNodes {
|
||||
final table = dispatchTable;
|
||||
final selector = table.selectorForTarget(target);
|
||||
if (selector.containsTarget(target)) {
|
||||
assert(
|
||||
!selector.synthesizeNullReturnValue ||
|
||||
selector.signature.outputs.isEmpty,
|
||||
);
|
||||
return selector.synthesizeNullReturnValue;
|
||||
}
|
||||
}
|
||||
return functions.synthesizeNullReturnValue(target);
|
||||
}
|
||||
|
||||
bool synthesizeNoReturn(Reference target) {
|
||||
final member = target.asMember;
|
||||
if (member.isInstanceMember) {
|
||||
final table = dispatchTable;
|
||||
final selector = table.selectorForTarget(target);
|
||||
if (selector.containsTarget(target)) {
|
||||
return selector.synthesizeNoReturn;
|
||||
}
|
||||
}
|
||||
return functions.synthesizeNoReturn(target);
|
||||
}
|
||||
|
||||
ParameterInfo paramInfoForDirectCall(Reference target) {
|
||||
if (target.asMember.isInstanceMember) {
|
||||
final selector = dispatchTable.selectorForTarget(target);
|
||||
@@ -2920,9 +2933,6 @@ class _ClosureDynamicEntryGenerator implements CodeGenerator {
|
||||
outputs.single,
|
||||
translator.outputOrVoid(function.type.outputs),
|
||||
);
|
||||
} else if (function.type.outputs.isNotEmpty) {
|
||||
assert(target.synthesizeNullReturnValue);
|
||||
b.ref_null(w.HeapType.none);
|
||||
}
|
||||
|
||||
b.end(); // end function
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2026, 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.
|
||||
|
||||
// functionFilter=foo
|
||||
// typeFilter=NoMatch
|
||||
// globalFilter=NoMatch
|
||||
// compilerOption=-O0
|
||||
|
||||
void main() {
|
||||
foo();
|
||||
}
|
||||
|
||||
void foo() {
|
||||
print('foo');
|
||||
print(fooAlwaysThrows());
|
||||
}
|
||||
|
||||
Never fooAlwaysThrows() {
|
||||
print('fooAlwaysThrows');
|
||||
throw Object();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
(module $module0
|
||||
(type $#Top <...>)
|
||||
(type $JSExternWrapper <...>)
|
||||
(type $Object <...>)
|
||||
(global $"\"fooAlwaysThrows\"" (ref $JSExternWrapper) <...>)
|
||||
(global $"\"foo\"" (ref $JSExternWrapper) <...>)
|
||||
(func $Error._throwWithCurrentStackTrace <noInline> (param $object (ref $#Top)) <...>)
|
||||
(func $Object (result (ref $Object)) <...>)
|
||||
(func $foo (result (ref null $#Top))
|
||||
global.get $"\"foo\""
|
||||
call $print
|
||||
ref.null none
|
||||
drop
|
||||
call $fooAlwaysThrows
|
||||
unreachable
|
||||
)
|
||||
(func $fooAlwaysThrows
|
||||
global.get $"\"fooAlwaysThrows\""
|
||||
call $print
|
||||
ref.null none
|
||||
drop
|
||||
call $Object
|
||||
call $"Error._throwWithCurrentStackTrace <noInline>"
|
||||
unreachable
|
||||
)
|
||||
(func $print (param $object (ref null $#Top)) <...>)
|
||||
)
|
||||
@@ -211,6 +211,7 @@
|
||||
global.get $"\"[]\""
|
||||
i32.const 21
|
||||
call_indirect $module0.cross-module-funcs-0 (param i64 i64 (ref null $JSExternWrapper))
|
||||
unreachable
|
||||
end
|
||||
local.get $var1
|
||||
struct.get $WasmListBase $_data
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
(type $_InterfaceType <...>)
|
||||
(type $_Type <...>)
|
||||
(global $"\")\"_11" (import "$" "2") (ref $JSExternWrapper))
|
||||
(global $"\"Attempt to execute code remove<...>\"" (import "$" "(") (ref $JSExternWrapper))
|
||||
(global $_InterfaceType (import "$" "0") (ref $_InterfaceType))
|
||||
(table $$.% (import "$" "%") 742 funcref)
|
||||
(table $$.' (import "$" "'") 20 funcref)
|
||||
@@ -158,9 +157,6 @@
|
||||
if
|
||||
i32.const 2
|
||||
call_indirect $$.'
|
||||
global.get $"\"Attempt to execute code remove<...>\""
|
||||
i32.const 3
|
||||
call_indirect $$.' (param (ref $#Top))
|
||||
unreachable
|
||||
end
|
||||
local.get $var0
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
local.get $var3
|
||||
i32.const 5
|
||||
call_indirect $module0.cross-module-funcs-0 (param (ref null $#Top) (ref $_Type))
|
||||
unreachable
|
||||
end
|
||||
local.get $var1
|
||||
ref.cast $Object
|
||||
|
||||
Reference in New Issue
Block a user