[ddc] Use direct cast methods for primitive casts
The `_as` methods for primitive types are designed to be called from both the `_as` selector and directly from generated code. For the primitive types that are known at compile time and cannot change via linking or hot reload, we can generate direct calls. Adapted from https://dart-review.googlesource.com/c/sdk/+/420380 In addition to the base change: * Remove `int` cast helpers from DDC only code in favor of the versions from dart:_rti. * Moves existing optimizations from `visitAsExpression` to `_emitCast` to apply them consistently. Change-Id: I13d24e3756400f2358556812728db17bc73f544a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/427040 Reviewed-by: Johnni Winther <johnniwinther@google.com> Commit-Queue: Nicholas Shahan <nshahan@google.com> Reviewed-by: Stephen Adams <sra@google.com>
This commit is contained in:
committed by
Commit Queue
parent
2b5b551cfb
commit
1806e4ce65
@@ -449,6 +449,19 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
|
||||
final Procedure _assertInteropMethod;
|
||||
|
||||
// The direct `_as` methods for primitive types.
|
||||
final Member _asBool;
|
||||
final Member _asDouble;
|
||||
final Member _asInt;
|
||||
final Member _asNum;
|
||||
final Member _asObject;
|
||||
final Member _asString;
|
||||
final Member _asBoolQ;
|
||||
final Member _asDoubleQ;
|
||||
final Member _asIntQ;
|
||||
final Member _asNumQ;
|
||||
final Member _asStringQ;
|
||||
|
||||
final DevCompilerConstants _constants;
|
||||
|
||||
final NullableInference _nullableInference;
|
||||
@@ -552,7 +565,18 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
ExtensionIndex(_coreTypes, _staticTypeContext.typeEnvironment),
|
||||
_inlineTester = BasicInlineTester(_constants),
|
||||
_rtiLibrary = sdk.getLibrary('dart:_rti'),
|
||||
_rtiClass = sdk.getClass('dart:_rti', 'Rti');
|
||||
_rtiClass = sdk.getClass('dart:_rti', 'Rti'),
|
||||
_asBool = sdk.getTopLevelMember('dart:_rti', '_asBool'),
|
||||
_asDouble = sdk.getTopLevelMember('dart:_rti', '_asDouble'),
|
||||
_asInt = sdk.getTopLevelMember('dart:_rti', '_asInt'),
|
||||
_asNum = sdk.getTopLevelMember('dart:_rti', '_asNum'),
|
||||
_asObject = sdk.getTopLevelMember('dart:_rti', '_asObject'),
|
||||
_asString = sdk.getTopLevelMember('dart:_rti', '_asString'),
|
||||
_asBoolQ = sdk.getTopLevelMember('dart:_rti', '_asBoolQ'),
|
||||
_asDoubleQ = sdk.getTopLevelMember('dart:_rti', '_asDoubleQ'),
|
||||
_asIntQ = sdk.getTopLevelMember('dart:_rti', '_asIntQ'),
|
||||
_asNumQ = sdk.getTopLevelMember('dart:_rti', '_asNumQ'),
|
||||
_asStringQ = sdk.getTopLevelMember('dart:_rti', '_asStringQ');
|
||||
|
||||
/// The library for dart:core in the SDK.
|
||||
Library get _coreLibrary => _coreTypes.coreLibrary;
|
||||
@@ -6963,82 +6987,98 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
var fromExpr = node.operand;
|
||||
var jsFrom = _visitExpression(fromExpr);
|
||||
if (node.isUnchecked) return jsFrom;
|
||||
var to = node.type.extensionTypeErasure;
|
||||
var from = fromExpr.getStaticType(_staticTypeContext).extensionTypeErasure;
|
||||
|
||||
// If the check was put here by static analysis to ensure soundness, we
|
||||
// can't skip it. For example, one could implement covariant generic caller
|
||||
// side checks like this:
|
||||
//
|
||||
// typedef F<T>(T t);
|
||||
// class C<T> {
|
||||
// F<T> f;
|
||||
// add(T t) {
|
||||
// // required check `t as T`
|
||||
// }
|
||||
// }
|
||||
// main() {
|
||||
// C<Object> c = new C<int>()..f = (int x) => x.isEven;
|
||||
// c.f('hi'); // required check `c.f as F<Object>`
|
||||
// c.add('hi);
|
||||
// }
|
||||
//
|
||||
var isTypeError = node.isTypeError;
|
||||
if (!isTypeError &&
|
||||
_types.isSubtypeOf(from, to, SubtypeCheckMode.withNullabilities)) {
|
||||
return jsFrom;
|
||||
}
|
||||
|
||||
if (!isTypeError &&
|
||||
DartTypeEquivalence(_coreTypes, ignoreTopLevelNullability: true)
|
||||
.areEqual(from, to) &&
|
||||
_mustBeNonNullable(to)) {
|
||||
// If the underlying type is the same, we only need a null check.
|
||||
return _runtimeCall('nullCast(#, #)', [jsFrom, _emitType(to)]);
|
||||
}
|
||||
|
||||
// All Dart number types map to a JS double. We can specialize these
|
||||
// cases.
|
||||
if (_typeRep.isNumber(from) && _typeRep.isNumber(to)) {
|
||||
// If `to` is some form of `num`, it should have been filtered above.
|
||||
|
||||
// * -> double? : no-op
|
||||
if (to == _coreTypes.doubleNullableRawType) {
|
||||
return jsFrom;
|
||||
}
|
||||
|
||||
// * -> double : null check
|
||||
if (to == _coreTypes.doubleNonNullableRawType) {
|
||||
if (from.nullability == Nullability.nonNullable) {
|
||||
return jsFrom;
|
||||
}
|
||||
return _runtimeCall('nullCast(#, #)', [jsFrom, _emitType(to)]);
|
||||
}
|
||||
|
||||
// * -> int : asInt check
|
||||
if (to == _coreTypes.intNonNullableRawType) {
|
||||
return _runtimeCall('asInt(#)', [jsFrom]);
|
||||
}
|
||||
|
||||
// * -> int? : asNullableInt check
|
||||
if (to == _coreTypes.intNullableRawType) {
|
||||
return _runtimeCall('asNullableInt(#)', [jsFrom]);
|
||||
}
|
||||
}
|
||||
|
||||
return _emitCast(jsFrom, to);
|
||||
return _emitCast(jsFrom, node.type,
|
||||
fromStaticType: fromExpr.getStaticType(_staticTypeContext),
|
||||
isTypeError: node.isTypeError);
|
||||
}
|
||||
|
||||
js_ast.Expression _emitCast(js_ast.Expression expr, DartType type) {
|
||||
var normalizedType = type.extensionTypeErasure;
|
||||
if (_types.isTop(normalizedType)) return expr;
|
||||
js_ast.Expression _emitCast(js_ast.Expression value, DartType toType,
|
||||
{DartType? fromStaticType, bool isTypeError = false}) {
|
||||
toType = toType.extensionTypeErasure;
|
||||
if (_types.isTop(toType)) return value;
|
||||
if (fromStaticType != null) {
|
||||
fromStaticType = fromStaticType.extensionTypeErasure;
|
||||
// If the check was put here by static analysis to ensure soundness, we
|
||||
// can't skip it. For example, one could implement covariant generic
|
||||
// caller side checks like this:
|
||||
//
|
||||
// typedef F<T>(T t);
|
||||
// class C<T> {
|
||||
// F<T> f;
|
||||
// add(T t) {
|
||||
// // required check `t as T`
|
||||
// }
|
||||
// }
|
||||
// main() {
|
||||
// C<Object> c = new C<int>()..f = (int x) => x.isEven;
|
||||
// c.f('hi'); // required check `c.f as F<Object>`
|
||||
// c.add('hi);
|
||||
// }
|
||||
//
|
||||
if (!isTypeError &&
|
||||
_types.isSubtypeOf(
|
||||
fromStaticType, toType, SubtypeCheckMode.withNullabilities)) {
|
||||
return value;
|
||||
}
|
||||
if (!isTypeError &&
|
||||
_mustBeNonNullable(toType) &&
|
||||
DartTypeEquivalence(_coreTypes, ignoreTopLevelNullability: true)
|
||||
.areEqual(fromStaticType, toType)) {
|
||||
// If the underlying type is the same, we only need a null check.
|
||||
return _runtimeCall('nullCast(#, #)', [value, _emitType(toType)]);
|
||||
}
|
||||
// All Dart number types map to a JavaScript Number. We can specialize
|
||||
// these cases.
|
||||
if (_typeRep.isNumber(fromStaticType) && _typeRep.isNumber(toType)) {
|
||||
// If `toType` is some form of `num`, it should have been filtered
|
||||
// above.
|
||||
if (toType == _coreTypes.doubleNullableRawType) {
|
||||
// Any number/nullability -> double? : no-op
|
||||
return value;
|
||||
}
|
||||
if (toType == _coreTypes.doubleNonNullableRawType) {
|
||||
if (fromStaticType.nullability == Nullability.nonNullable) {
|
||||
// Any non-nullable number -> double : no-op
|
||||
return value;
|
||||
}
|
||||
// Any number/nullability -> double : null check
|
||||
return _runtimeCall('nullCast(#, #)', [value, _emitType(toType)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
var directMethod = _directCastMethod(toType);
|
||||
if (directMethod != null) {
|
||||
return js.call('#(#)', [_emitTopLevelName(directMethod), value]);
|
||||
}
|
||||
return js.call('#.#(#)', [
|
||||
_emitType(normalizedType),
|
||||
_emitType(toType),
|
||||
_emitMemberName(js_ast.FixedNames.rtiAsField, memberClass: _rtiClass),
|
||||
expr
|
||||
value
|
||||
]);
|
||||
}
|
||||
|
||||
/// Returns the direct `_as` method when [type] is a primitive type otherwise,
|
||||
/// `null`.
|
||||
Member? _directCastMethod(DartType type) {
|
||||
if (type is InterfaceType && type.typeArguments.isEmpty) {
|
||||
if (type.nullability == Nullability.nonNullable) {
|
||||
if (type == _types.coreTypes.boolNonNullableRawType) return _asBool;
|
||||
if (type == _types.coreTypes.doubleNonNullableRawType) return _asDouble;
|
||||
if (type == _types.coreTypes.intNonNullableRawType) return _asInt;
|
||||
if (type == _types.coreTypes.numNonNullableRawType) return _asNum;
|
||||
if (type == _types.coreTypes.objectNonNullableRawType) return _asObject;
|
||||
if (type == _types.coreTypes.stringNonNullableRawType) return _asString;
|
||||
} else if (type.nullability == Nullability.nullable) {
|
||||
if (type == _types.coreTypes.boolNullableRawType) return _asBoolQ;
|
||||
if (type == _types.coreTypes.doubleNullableRawType) return _asDoubleQ;
|
||||
if (type == _types.coreTypes.intNullableRawType) return _asIntQ;
|
||||
if (type == _types.coreTypes.numNullableRawType) return _asNumQ;
|
||||
if (type == _types.coreTypes.stringNullableRawType) return _asStringQ;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
js_ast.Expression visitSymbolLiteral(SymbolLiteral node) =>
|
||||
_emitDartSymbol(node.value);
|
||||
|
||||
@@ -616,6 +616,19 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
|
||||
final Procedure _assertInteropMethod;
|
||||
|
||||
// The direct `_as` methods for primitive types.
|
||||
final Member _asBool;
|
||||
final Member _asDouble;
|
||||
final Member _asInt;
|
||||
final Member _asNum;
|
||||
final Member _asObject;
|
||||
final Member _asString;
|
||||
final Member _asBoolQ;
|
||||
final Member _asDoubleQ;
|
||||
final Member _asIntQ;
|
||||
final Member _asNumQ;
|
||||
final Member _asStringQ;
|
||||
|
||||
final DevCompilerConstants _constants;
|
||||
|
||||
final NullableInference _nullableInference;
|
||||
@@ -741,7 +754,18 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
_inlineTester = BasicInlineTester(_constants),
|
||||
_runtimeLibrary = sdk.getLibrary('dart:_runtime'),
|
||||
_rtiLibrary = sdk.getLibrary('dart:_rti'),
|
||||
_rtiClass = sdk.getClass('dart:_rti', 'Rti');
|
||||
_rtiClass = sdk.getClass('dart:_rti', 'Rti'),
|
||||
_asBool = sdk.getTopLevelMember('dart:_rti', '_asBool'),
|
||||
_asDouble = sdk.getTopLevelMember('dart:_rti', '_asDouble'),
|
||||
_asInt = sdk.getTopLevelMember('dart:_rti', '_asInt'),
|
||||
_asNum = sdk.getTopLevelMember('dart:_rti', '_asNum'),
|
||||
_asObject = sdk.getTopLevelMember('dart:_rti', '_asObject'),
|
||||
_asString = sdk.getTopLevelMember('dart:_rti', '_asString'),
|
||||
_asBoolQ = sdk.getTopLevelMember('dart:_rti', '_asBoolQ'),
|
||||
_asDoubleQ = sdk.getTopLevelMember('dart:_rti', '_asDoubleQ'),
|
||||
_asIntQ = sdk.getTopLevelMember('dart:_rti', '_asIntQ'),
|
||||
_asNumQ = sdk.getTopLevelMember('dart:_rti', '_asNumQ'),
|
||||
_asStringQ = sdk.getTopLevelMember('dart:_rti', '_asStringQ');
|
||||
|
||||
/// The library for dart:core in the SDK.
|
||||
Library get _coreLibrary => _coreTypes.coreLibrary;
|
||||
@@ -7632,82 +7656,99 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
var fromExpr = node.operand;
|
||||
var jsFrom = _visitExpression(fromExpr);
|
||||
if (node.isUnchecked) return jsFrom;
|
||||
var to = node.type.extensionTypeErasure;
|
||||
var from = fromExpr.getStaticType(_staticTypeContext).extensionTypeErasure;
|
||||
|
||||
// If the check was put here by static analysis to ensure soundness, we
|
||||
// can't skip it. For example, one could implement covariant generic caller
|
||||
// side checks like this:
|
||||
//
|
||||
// typedef F<T>(T t);
|
||||
// class C<T> {
|
||||
// F<T> f;
|
||||
// add(T t) {
|
||||
// // required check `t as T`
|
||||
// }
|
||||
// }
|
||||
// main() {
|
||||
// C<Object> c = new C<int>()..f = (int x) => x.isEven;
|
||||
// c.f('hi'); // required check `c.f as F<Object>`
|
||||
// c.add('hi);
|
||||
// }
|
||||
//
|
||||
var isTypeError = node.isTypeError;
|
||||
if (!isTypeError &&
|
||||
_types.isSubtypeOf(from, to, SubtypeCheckMode.withNullabilities)) {
|
||||
return jsFrom;
|
||||
}
|
||||
|
||||
if (!isTypeError &&
|
||||
DartTypeEquivalence(_coreTypes, ignoreTopLevelNullability: true)
|
||||
.areEqual(from, to) &&
|
||||
_mustBeNonNullable(to)) {
|
||||
// If the underlying type is the same, we only need a null check.
|
||||
return _runtimeCall('nullCast(#, #)', [jsFrom, _emitType(to)]);
|
||||
}
|
||||
|
||||
// All Dart number types map to a JS double. We can specialize these
|
||||
// cases.
|
||||
if (_typeRep.isNumber(from) && _typeRep.isNumber(to)) {
|
||||
// If `to` is some form of `num`, it should have been filtered above.
|
||||
|
||||
// * -> double? : no-op
|
||||
if (to == _coreTypes.doubleNullableRawType) {
|
||||
return jsFrom;
|
||||
}
|
||||
|
||||
// * -> double : null check
|
||||
if (to == _coreTypes.doubleNonNullableRawType) {
|
||||
if (from.nullability == Nullability.nonNullable) {
|
||||
return jsFrom;
|
||||
}
|
||||
return _runtimeCall('nullCast(#, #)', [jsFrom, _emitType(to)]);
|
||||
}
|
||||
|
||||
// * -> int : asInt check
|
||||
if (to == _coreTypes.intNonNullableRawType) {
|
||||
return _runtimeCall('asInt(#)', [jsFrom]);
|
||||
}
|
||||
|
||||
// * -> int? : asNullableInt check
|
||||
if (to == _coreTypes.intNullableRawType) {
|
||||
return _runtimeCall('asNullableInt(#)', [jsFrom]);
|
||||
}
|
||||
}
|
||||
|
||||
return _emitCast(jsFrom, to);
|
||||
return _emitCast(jsFrom, node.type,
|
||||
fromStaticType: fromExpr.getStaticType(_staticTypeContext),
|
||||
isTypeError: node.isTypeError);
|
||||
}
|
||||
|
||||
js_ast.Expression _emitCast(js_ast.Expression expr, DartType type) {
|
||||
var normalizedType = type.extensionTypeErasure;
|
||||
if (_types.isTop(normalizedType)) return expr;
|
||||
js_ast.Expression _emitCast(js_ast.Expression value, DartType toType,
|
||||
{DartType? fromStaticType, bool isTypeError = false}) {
|
||||
toType = toType.extensionTypeErasure;
|
||||
if (_types.isTop(toType)) return value;
|
||||
if (fromStaticType != null) {
|
||||
fromStaticType = fromStaticType.extensionTypeErasure;
|
||||
// If the check was put here by static analysis to ensure soundness, we
|
||||
// can't skip it. For example, one could implement covariant generic
|
||||
// caller side checks like this:
|
||||
//
|
||||
// typedef F<T>(T t);
|
||||
// class C<T> {
|
||||
// F<T> f;
|
||||
// add(T t) {
|
||||
// // required check `t as T`
|
||||
// }
|
||||
// }
|
||||
// main() {
|
||||
// C<Object> c = new C<int>()..f = (int x) => x.isEven;
|
||||
// c.f('hi'); // required check `c.f as F<Object>`
|
||||
// c.add('hi);
|
||||
// }
|
||||
//
|
||||
if (!isTypeError &&
|
||||
_types.isSubtypeOf(
|
||||
fromStaticType, toType, SubtypeCheckMode.withNullabilities)) {
|
||||
return value;
|
||||
}
|
||||
if (!isTypeError &&
|
||||
_mustBeNonNullable(toType) &&
|
||||
DartTypeEquivalence(_coreTypes, ignoreTopLevelNullability: true)
|
||||
.areEqual(fromStaticType, toType)) {
|
||||
// If the underlying type is the same, we only need a null check.
|
||||
return _runtimeCall('nullCast(#, #)', [value, _emitType(toType)]);
|
||||
}
|
||||
// All Dart number types map to a JavaScript Number. We can specialize
|
||||
// these cases.
|
||||
if (_typeRep.isNumber(fromStaticType) && _typeRep.isNumber(toType)) {
|
||||
// If `toType` is some form of `num`, it should have been filtered
|
||||
// above.
|
||||
if (toType == _coreTypes.doubleNullableRawType) {
|
||||
// Any number/nullability -> double? : no-op
|
||||
return value;
|
||||
}
|
||||
if (toType == _coreTypes.doubleNonNullableRawType) {
|
||||
if (fromStaticType.nullability == Nullability.nonNullable) {
|
||||
// Any non-nullable number -> double : no-op
|
||||
return value;
|
||||
}
|
||||
// Any number/nullability -> double : null check
|
||||
return _runtimeCall('nullCast(#, #)', [value, _emitType(toType)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
var directMethod = _directCastMethod(toType);
|
||||
if (directMethod != null) {
|
||||
return js.call('#(#)', [_emitTopLevelName(directMethod), value]);
|
||||
}
|
||||
|
||||
return js.call('#.#(#)', [
|
||||
_emitType(normalizedType),
|
||||
_emitType(toType),
|
||||
_emitMemberName(js_ast.FixedNames.rtiAsField, memberClass: _rtiClass),
|
||||
expr
|
||||
value
|
||||
]);
|
||||
}
|
||||
|
||||
/// Returns the direct `_as` method when [type] is a primitive type otherwise,
|
||||
/// `null`.
|
||||
Member? _directCastMethod(DartType type) {
|
||||
if (type is InterfaceType && type.typeArguments.isEmpty) {
|
||||
if (type.nullability == Nullability.nonNullable) {
|
||||
if (type == _types.coreTypes.boolNonNullableRawType) return _asBool;
|
||||
if (type == _types.coreTypes.doubleNonNullableRawType) return _asDouble;
|
||||
if (type == _types.coreTypes.intNonNullableRawType) return _asInt;
|
||||
if (type == _types.coreTypes.numNonNullableRawType) return _asNum;
|
||||
if (type == _types.coreTypes.objectNonNullableRawType) return _asObject;
|
||||
if (type == _types.coreTypes.stringNonNullableRawType) return _asString;
|
||||
} else if (type.nullability == Nullability.nullable) {
|
||||
if (type == _types.coreTypes.boolNullableRawType) return _asBoolQ;
|
||||
if (type == _types.coreTypes.doubleNullableRawType) return _asDoubleQ;
|
||||
if (type == _types.coreTypes.intNullableRawType) return _asIntQ;
|
||||
if (type == _types.coreTypes.numNullableRawType) return _asNumQ;
|
||||
if (type == _types.coreTypes.stringNullableRawType) return _asStringQ;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
js_ast.Expression visitSymbolLiteral(SymbolLiteral node) =>
|
||||
_emitDartSymbol(node.value);
|
||||
|
||||
@@ -69,6 +69,19 @@ final Map<String, List<String>> requiredTopLevels = {
|
||||
'_streamOfController',
|
||||
'_wrapJsFunctionForAsync',
|
||||
],
|
||||
'dart:_rti': [
|
||||
'_asBool',
|
||||
'_asDouble',
|
||||
'_asInt',
|
||||
'_asNum',
|
||||
'_asObject',
|
||||
'_asString',
|
||||
'_asBoolQ',
|
||||
'_asDoubleQ',
|
||||
'_asIntQ',
|
||||
'_asNumQ',
|
||||
'_asStringQ',
|
||||
],
|
||||
};
|
||||
|
||||
void main() {
|
||||
|
||||
@@ -911,16 +911,6 @@ bool dtest(obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
asInt(obj) {
|
||||
// Note: null (and undefined) will fail this test.
|
||||
if (JS('!', 'Math.floor(#) != #', obj, obj)) {
|
||||
castError(obj, TYPE_REF<int>());
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
asNullableInt(obj) => obj == null ? null : asInt(obj);
|
||||
|
||||
/// Checks for null or undefined and returns [x].
|
||||
///
|
||||
/// Throws [NoSuchMethodError] when it is null or undefined.
|
||||
|
||||
Reference in New Issue
Block a user