Optimize DateTime properties

Several small pieces fit together to improve calendar arithmetic code in a customer's app.

1. Mark DateTime primitives that return small integers as returning uint31.

2. uint31 + uint31 -> uint32 in type inference.

3. uint32 / N can be generated as (uint32 / N) | 0 when N >= 2.

R=floitsch@google.com

Review URL: https://codereview.chromium.org//1106443003

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@45621 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
sra@google.com 2015-05-07 21:39:17 +00:00
parent 194d48f9da
commit 532a7948d8
5 changed files with 181 additions and 32 deletions

View file

@ -804,11 +804,17 @@ class DynamicCallSiteTypeInformation extends CallSiteTypeInformation {
// Dart code, for example:
// int + int -> int
// uint31 | uint31 -> uint31
if (name == '*' || name == '+' || name == '%' || name == 'remainder' ||
if (name == '*' || name == '+' ||name == '%' || name == 'remainder' ||
name == '~/') {
if (isPositiveInt(receiver) &&
arguments.hasOnePositionalArgumentThatMatches(isPositiveInt)) {
return inferrer.types.positiveIntType;
// uint31 + uint31 -> uint32
if (name == '+' && isUInt31(receiver) &&
arguments.hasOnePositionalArgumentThatMatches(isUInt31)) {
return inferrer.types.uint32Type;
} else {
return inferrer.types.positiveIntType;
}
} else if (arguments.hasOnePositionalArgumentThatMatches(isInt)) {
return inferrer.types.intType;
} else if (arguments.hasOnePositionalArgumentThatMatches(isEmpty)) {

View file

@ -223,11 +223,16 @@ abstract class BinaryArithmeticSpecializer extends InvokeDynamicSpecializer {
bool inputsArePositiveIntegers(HInstruction instruction, Compiler compiler) {
HInstruction left = instruction.inputs[1];
HInstruction right = instruction.inputs[2];
JavaScriptBackend backend = compiler.backend;
return left.isPositiveIntegerOrNull(compiler)
&& right.isPositiveIntegerOrNull(compiler);
}
bool inputsAreUInt31(HInstruction instruction, Compiler compiler) {
HInstruction left = instruction.inputs[1];
HInstruction right = instruction.inputs[2];
return left.isUInt31(compiler) && right.isUInt31(compiler);
}
HInstruction newBuiltinVariant(HInvokeDynamic instruction, Compiler compiler);
Selector renameToOptimizedSelector(String name,
@ -249,6 +254,10 @@ class AddSpecializer extends BinaryArithmeticSpecializer {
TypeMask computeTypeFromInputTypes(HInvokeDynamic instruction,
Compiler compiler) {
if (inputsAreUInt31(instruction, compiler)) {
JavaScriptBackend backend = compiler.backend;
return backend.uint32Type;
}
if (inputsArePositiveIntegers(instruction, compiler)) {
JavaScriptBackend backend = compiler.backend;
return backend.positiveIntType;
@ -313,6 +322,7 @@ class ModuloSpecializer extends BinaryArithmeticSpecializer {
HInstruction newBuiltinVariant(HInvokeDynamic instruction,
Compiler compiler) {
// Modulo cannot be mapped to the native operator (different semantics).
// TODO(sra): For non-negative values we can use JavaScript's %.
return null;
}
}
@ -380,6 +390,14 @@ class TruncatingDivideSpecializer extends BinaryArithmeticSpecializer {
return count != 0;
}
bool isTwoOrGreater(HInstruction instruction, Compiler compiler) {
if (!instruction.isConstantInteger()) return false;
HConstant rightConstant = instruction;
IntConstantValue intConstant = rightConstant.constant;
int count = intConstant.primitiveValue;
return count >= 2;
}
HInstruction tryConvertToBuiltin(HInvokeDynamic instruction,
Compiler compiler) {
HInstruction left = instruction.inputs[1];
@ -389,6 +407,9 @@ class TruncatingDivideSpecializer extends BinaryArithmeticSpecializer {
if (left.isUInt31(compiler)) {
return newBuiltinVariant(instruction, compiler);
}
if (left.isUInt32(compiler) && isTwoOrGreater(right, compiler)) {
return newBuiltinVariant(instruction, compiler);
}
// We can call _tdivFast because the rhs is a 32bit integer
// and not 0, nor -1.
instruction.selector = renameToOptimizedSelector(

View file

@ -1034,38 +1034,38 @@ class Primitives {
static getMonth(receiver) {
return (receiver.isUtc)
? JS('int', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver))
: JS('int', r'#.getMonth() + 1', lazyAsJsDate(receiver));
? JS('JSUInt31', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver))
: JS('JSUInt31', r'#.getMonth() + 1', lazyAsJsDate(receiver));
}
static getDay(receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getDate() + 0)', lazyAsJsDate(receiver));
? JS('JSUInt31', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver))
: JS('JSUInt31', r'(#.getDate() + 0)', lazyAsJsDate(receiver));
}
static getHours(receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getHours() + 0)', lazyAsJsDate(receiver));
? JS('JSUInt31', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver))
: JS('JSUInt31', r'(#.getHours() + 0)', lazyAsJsDate(receiver));
}
static getMinutes(receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver));
? JS('JSUInt31', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver))
: JS('JSUInt31', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver));
}
static getSeconds(receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver));
? JS('JSUInt31', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver))
: JS('JSUInt31', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver));
}
static getMilliseconds(receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver));
? JS('JSUInt31', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver))
: JS('JSUInt31', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver));
}
static getWeekday(receiver) {

View file

@ -582,6 +582,20 @@ class B extends A {
returnInt9() => super.myField;
}
class C {
var myField = 42;
C();
returnInt1() => ++myField;
returnInt2() => ++this.myField;
returnInt3() => this.myField += 42;
returnInt4() => myField += 42;
operator[](index) => myField;
operator[]= (index, value) {}
returnInt5() => ++this[0];
returnInt6() => this[0] += 1;
}
testCascade1() {
return [1, 2, 3]..add(4)..add(5);
}
@ -684,6 +698,13 @@ main() {
..returnInt7()
..returnInt8()
..returnInt9();
new C()..returnInt1()
..returnInt2()
..returnInt3()
..returnInt4()
..returnInt5()
..returnInt6();
testReturnElementOfConstList1();
testReturnElementOfConstList2();
testReturnItselfOrInt(topLevelGetter());
@ -727,8 +748,8 @@ void main() {
checkReturn('returnInt2', typesTask.uint31Type);
checkReturn('returnDouble', typesTask.doubleType);
checkReturn('returnGiveUp', interceptorType);
checkReturn('returnInt5', typesTask.positiveIntType);
checkReturn('returnInt6', typesTask.positiveIntType);
checkReturn('returnInt5', typesTask.uint32Type); // uint31+uint31->uint32
checkReturn('returnInt6', typesTask.uint32Type); // uint31+uint31->uint32
checkReturn('returnIntOrNull', typesTask.uint31Type.nullable());
checkReturn('returnInt3', typesTask.uint31Type);
checkReturn('returnDynamic', typesTask.dynamicType);
@ -789,7 +810,7 @@ void main() {
checkReturn('testContinue1', interceptorType.nullable());
checkReturn('testBreak1', interceptorType.nullable());
checkReturn('testContinue2', interceptorType.nullable());
checkReturn('testBreak2', typesTask.positiveIntType.nullable());
checkReturn('testBreak2', typesTask.uint32Type.nullable());
checkReturn('testReturnElementOfConstList1', typesTask.uint31Type);
checkReturn('testReturnElementOfConstList2', typesTask.uint31Type);
checkReturn('testReturnItselfOrInt', typesTask.uint31Type);
@ -808,24 +829,31 @@ void main() {
'$className:$methodName');
}
checkReturnInClass('A', 'returnInt1', typesTask.positiveIntType);
checkReturnInClass('A', 'returnInt2', typesTask.positiveIntType);
checkReturnInClass('A', 'returnInt3', typesTask.positiveIntType);
checkReturnInClass('A', 'returnInt4', typesTask.positiveIntType);
checkReturnInClass('A', 'returnInt5', typesTask.positiveIntType);
checkReturnInClass('A', 'returnInt6', typesTask.positiveIntType);
checkReturnInClass('A', 'returnInt1', typesTask.uint32Type);
checkReturnInClass('A', 'returnInt2', typesTask.uint32Type);
checkReturnInClass('A', 'returnInt3', typesTask.uint32Type);
checkReturnInClass('A', 'returnInt4', typesTask.uint32Type);
checkReturnInClass('A', 'returnInt5', typesTask.uint32Type);
checkReturnInClass('A', 'returnInt6', typesTask.uint32Type);
checkReturnInClass('A', '==', interceptorType);
checkReturnInClass('B', 'returnInt1', typesTask.positiveIntType);
checkReturnInClass('B', 'returnInt2', typesTask.positiveIntType);
checkReturnInClass('B', 'returnInt3', typesTask.positiveIntType);
checkReturnInClass('B', 'returnInt4', typesTask.positiveIntType);
checkReturnInClass('B', 'returnInt5', typesTask.positiveIntType);
checkReturnInClass('B', 'returnInt6', typesTask.positiveIntType);
checkReturnInClass('B', 'returnInt7', typesTask.positiveIntType);
checkReturnInClass('B', 'returnInt8', typesTask.positiveIntType);
checkReturnInClass('B', 'returnInt1', typesTask.uint32Type);
checkReturnInClass('B', 'returnInt2', typesTask.uint32Type);
checkReturnInClass('B', 'returnInt3', typesTask.uint32Type);
checkReturnInClass('B', 'returnInt4', typesTask.uint32Type);
checkReturnInClass('B', 'returnInt5', typesTask.uint32Type);
checkReturnInClass('B', 'returnInt6', typesTask.uint32Type);
checkReturnInClass('B', 'returnInt7', typesTask.uint32Type);
checkReturnInClass('B', 'returnInt8', typesTask.uint32Type);
checkReturnInClass('B', 'returnInt9', typesTask.uint31Type);
checkReturnInClass('C', 'returnInt1', typesTask.positiveIntType);
checkReturnInClass('C', 'returnInt2', typesTask.positiveIntType);
checkReturnInClass('C', 'returnInt3', typesTask.positiveIntType);
checkReturnInClass('C', 'returnInt4', typesTask.positiveIntType);
checkReturnInClass('C', 'returnInt5', typesTask.positiveIntType);
checkReturnInClass('C', 'returnInt6', typesTask.positiveIntType);
checkFactoryConstructor(String className, String factoryName) {
var cls = findElement(compiler, className);
var element = cls.localLookup(factoryName);

View file

@ -0,0 +1,94 @@
// Copyright (c) 2015, 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.
library tdiv_test;
import 'dart:async';
import 'package:expect/expect.dart';
import 'package:async_helper/async_helper.dart';
import 'compiler_helper.dart';
const String TEST1 = r"""
foo(param) {
var a = param ? 0xFFFFFFFF : 1;
return a ~/ 2;
// Above can be compiled to division followed by truncate.
// present: ' / 2 | 0'
// absent: 'tdiv'
}
""";
const String TEST2 = r"""
foo(param) {
var a = param ? 0xFFFFFFFF : 1;
return a ~/ 3;
// Above can be compiled to division followed by truncate.
// present: ' / 3 | 0'
// absent: 'tdiv'
}
""";
const String TEST3 = r"""
foo(param) {
var a = param ? 0xFFFFFFFF : -1;
return a ~/ 2;
// Potentially negative inputs go via fast helper.
// present: '_tdivFast'
// absent: '/'
}
""";
const String TEST4 = r"""
foo(param1, param2) {
var a = param1 ? 0xFFFFFFFF : 0;
return a ~/ param2;
// Unknown divisor goes via full implementation.
// present: '$tdiv'
// absent: '/'
}
""";
const String TEST5 = r"""
foo(param1, param2) {
var a = param1 ? 0xFFFFFFFF : 0;
var b = param2 ? 3 : 4;
return a ~/ param2;
// We could optimize this with range analysis, but type inference summarizes
// '3 or 4' to uint31, which is not >= 2.
// present: '$tdiv'
// absent: '/'
}
""";
main() {
RegExp directivePattern = new RegExp(
// \1 \2 \3
r'''// *(present|absent): (?:"([^"]*)"|'([^'']*)')''',
multiLine: true);
Future check(String test) {
return compile(test, entry: 'foo', check: (String generated) {
for (Match match in directivePattern.allMatches(test)) {
String directive = match.group(1);
String pattern = match.groups([2, 3]).where((s) => s != null).single;
if (directive == 'present') {
Expect.isTrue(generated.contains(pattern),
"Cannot find '$pattern' in:\n$generated");
} else {
assert(directive == 'absent');
Expect.isFalse(generated.contains(pattern),
"Must not find '$pattern' in:\n$generated");
}
}
});
}
asyncTest(() => Future.wait([
check(TEST1),
check(TEST2),
check(TEST3),
check(TEST4),
check(TEST5),
]));
}