[dart2wasm] Use TFA-inferred types also for static fields Currently the type of a static field is only based on the dart type, not inferred types. This can mean that the type of such field is e.g. `dynamic`. Though the return type of the initializer function used the unboxing information. => There was an invariant that if we use `null` to signal uninitialized in the global, then calling the initializer will also result in a non-nullable reference type. => This was invariant was broken by recent changes to dart2wasm that take advantage of TFA-inferred unboxing information. => We fix this inconsistency by ensuring the wasm global's type is the same as the initializer function's return type. Change-Id: I9c27c0b28d2f81f888b4c9afc59dad42af2229ab Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365825 Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dart2wasm/lib/globals.dart b/pkg/dart2wasm/lib/globals.dart index eae306b..4ce65fb 100644 --- a/pkg/dart2wasm/lib/globals.dart +++ b/pkg/dart2wasm/lib/globals.dart
@@ -138,16 +138,16 @@ /// Return (and if needed create) the Wasm global corresponding to a static /// field. - w.Global getGlobal(Field variable) { - assert(!variable.isLate); - return _globals.putIfAbsent(variable, () { - w.ValueType type = translator.translateType(variable.type); - Constant? init = _getConstantInitializer(variable); + w.Global getGlobal(Field field) { + assert(!field.isLate); + return _globals.putIfAbsent(field, () { + final Constant? init = _getConstantInitializer(field); + w.ValueType type = translator.translateTypeOfField(field); if (init != null && !(translator.constants.ensureConstant(init)?.isLazy ?? false)) { // Initialized to a constant final global = - m.globals.define(w.GlobalType(type, mutable: !variable.isFinal)); + m.globals.define(w.GlobalType(type, mutable: !field.isFinal)); translator.constants .instantiateConstant(null, global.initializer, init, type); global.initializer.end(); @@ -161,15 +161,15 @@ final flag = m.globals.define(w.GlobalType(w.NumType.i32)); flag.initializer.i32_const(0); flag.initializer.end(); - _globalInitializedFlag[variable] = flag; + _globalInitializedFlag[field] = flag; } final global = m.globals.define(w.GlobalType(type)); instantiateDummyValue(global.initializer, type); global.initializer.end(); - _globalInitializers[variable] = - translator.functions.getFunction(variable.fieldReference); + _globalInitializers[field] = + translator.functions.getFunction(field.fieldReference); return global; } });
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart index 7d4f330..7c56c5a 100644 --- a/pkg/dart2wasm/lib/translator.dart +++ b/pkg/dart2wasm/lib/translator.dart
@@ -995,18 +995,18 @@ } DartType typeOfReturnValue(Member member) { - final unboxingInfo = unboxingInfoMetadata[member]; - if (unboxingInfo != null) { - final returnInfo = unboxingInfo.returnInfo; - if (returnInfo.kind == UnboxingKind.int) { - return coreTypes.intRawType(Nullability.nonNullable); - } - if (returnInfo.kind == UnboxingKind.double) { - return coreTypes.doubleRawType(Nullability.nonNullable); - } - } - if (member is Field) return member.type; - return member.function!.returnType; + if (member is Field) return typeOfField(member); + + // TODO(http://dartbug.com/55668): Once TFA annotates members with inferred + // return types we should make use of them here. + return _inferredUnboxedReturnType(member) ?? member.function!.returnType; + } + + DartType typeOfField(Field node) { + assert(!node.isLate); + return _inferredUnboxedReturnType(node) ?? + _inferredTypeOfField(node) ?? + node.type; } w.ValueType translateTypeOfParameter( @@ -1019,7 +1019,7 @@ } w.ValueType translateTypeOfField(Field node) { - return translateType(_inferredTypeOfField(node) ?? node.type); + return translateType(typeOfField(node)); } w.ValueType translateTypeOfLocalVariable(VariableDeclaration node) { @@ -1080,6 +1080,20 @@ return InterfaceType(concreteClass, nullability, typeArguments); } + DartType? _inferredUnboxedReturnType(Member node) { + final unboxingInfo = unboxingInfoMetadata[node]; + if (unboxingInfo == null) return null; + + final returnInfo = unboxingInfo.returnInfo; + if (returnInfo.kind == UnboxingKind.int) { + return coreTypes.intRawType(Nullability.nonNullable); + } + if (returnInfo.kind == UnboxingKind.double) { + return coreTypes.doubleRawType(Nullability.nonNullable); + } + return null; + } + bool shouldInline(Reference target) { if (!options.inlining) return false; Member member = target.asMember;
diff --git a/tests/web/wasm/lazy_static_number_initializer_regression_test.dart b/tests/web/wasm/lazy_static_number_initializer_regression_test.dart new file mode 100644 index 0000000..313d440 --- /dev/null +++ b/tests/web/wasm/lazy_static_number_initializer_regression_test.dart
@@ -0,0 +1,24 @@ +// 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'; + +import 'package:expect/expect.dart'; + +final num maxInt = 1 is double ? pow(2, 52) : 1.0e300.floor(); + +dynamic round(dynamic number) { + if (number is num) { + if (number.isInfinite) { + return maxInt; + } + return number.round(); + } + return number; +} + +main() { + Expect.equals(round(1.0), 1); + Expect.equals(round(1), 1); +}