acf24331e3
by escape analysis). Added several tests that expose the bug. Summary: LCodegen::AddToTranslation assumes that Lithium environments are generated by depth-first traversal, but LChunkBuilder::CreateEnvironment was generating them in breadth-first fashion. This fixes the CreateEnvironment to traverse the captured objects depth-first. Note: It might be worth considering representing LEnvironment by a list with the same order as the serialized translation representation rather than having two lists with a subtle relationship between them (and then serialize in a slightly different order again). R=titzer@chromium.org, mstarzinger@chromium.org LOG=N BUG= Review URL: https://codereview.chromium.org/93803003 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@18470 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
439 lines
10 KiB
JavaScript
439 lines
10 KiB
JavaScript
// Copyright 2013 the V8 project authors. All rights reserved.
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following
|
|
// disclaimer in the documentation and/or other materials provided
|
|
// with the distribution.
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived
|
|
// from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
// Flags: --allow-natives-syntax --use-escape-analysis --expose-gc
|
|
|
|
|
|
// Test stores on a join path.
|
|
(function testJoin() {
|
|
function constructor() {
|
|
this.a = 0;
|
|
}
|
|
function join(mode, expected) {
|
|
var object = new constructor();
|
|
if (mode) {
|
|
object.a = 1;
|
|
} else {
|
|
object.a = 2;
|
|
}
|
|
assertEquals(expected, object.a);
|
|
}
|
|
join(true, 1); join(true, 1);
|
|
join(false, 2); join(false, 2);
|
|
%OptimizeFunctionOnNextCall(join);
|
|
join(true, 1); join(false, 2);
|
|
})();
|
|
|
|
|
|
// Test loads and stores inside a loop.
|
|
(function testLoop() {
|
|
function constructor() {
|
|
this.a = 0;
|
|
this.b = 23;
|
|
}
|
|
function loop() {
|
|
var object = new constructor();
|
|
for (var i = 1; i < 10; i++) {
|
|
object.a = object.a + i;
|
|
assertEquals(i*(i+1)/2, object.a);
|
|
assertEquals(23, object.b);
|
|
}
|
|
assertEquals(45, object.a);
|
|
assertEquals(23, object.b);
|
|
}
|
|
loop(); loop();
|
|
%OptimizeFunctionOnNextCall(loop);
|
|
loop(); loop();
|
|
})();
|
|
|
|
|
|
// Test loads and stores inside nested loop.
|
|
(function testNested() {
|
|
function constructor() {
|
|
this.a = 0;
|
|
this.b = 0;
|
|
this.c = 23;
|
|
}
|
|
function nested() {
|
|
var object = new constructor();
|
|
for (var i = 1; i < 10; i++) {
|
|
object.a = object.a + i;
|
|
assertEquals(i*(i+1)/2, object.a);
|
|
assertEquals((i-1)*6, object.b);
|
|
assertEquals(23, object.c);
|
|
for (var j = 1; j < 4; j++) {
|
|
object.b = object.b + j;
|
|
assertEquals(i*(i+1)/2, object.a);
|
|
assertEquals((i-1)*6+j*(j+1)/2, object.b);
|
|
assertEquals(23, object.c);
|
|
}
|
|
assertEquals(i*(i+1)/2, object.a);
|
|
assertEquals(i*6, object.b);
|
|
assertEquals(23, object.c);
|
|
}
|
|
assertEquals(45, object.a);
|
|
assertEquals(54, object.b);
|
|
assertEquals(23, object.c);
|
|
}
|
|
nested(); nested();
|
|
%OptimizeFunctionOnNextCall(nested);
|
|
nested(); nested();
|
|
})();
|
|
|
|
|
|
// Test deoptimization with captured objects in local variables.
|
|
(function testDeoptLocal() {
|
|
var deopt = { deopt:false };
|
|
function constructor1() {
|
|
this.a = 1.0;
|
|
this.b = 2.3;
|
|
this.c = 3.0;
|
|
}
|
|
function constructor2(o) {
|
|
this.d = o;
|
|
this.e = 4.5;
|
|
}
|
|
function func() {
|
|
var o1 = new constructor1();
|
|
var o2 = new constructor2(o1);
|
|
deopt.deopt;
|
|
assertEquals(1.0, o1.a);
|
|
assertEquals(2.3, o2.d.b);
|
|
assertEquals(3.0, o2.d.c);
|
|
assertEquals(4.5, o2.e);
|
|
}
|
|
func(); func();
|
|
%OptimizeFunctionOnNextCall(func);
|
|
func(); func();
|
|
delete deopt.deopt;
|
|
func(); func();
|
|
})();
|
|
|
|
|
|
// Test deoptimization with captured objects on operand stack.
|
|
(function testDeoptOperand() {
|
|
var deopt = { deopt:false };
|
|
function constructor1() {
|
|
this.a = 1.0;
|
|
this.b = 2.3;
|
|
deopt.deopt;
|
|
assertEquals(1.0, this.a);
|
|
assertEquals(2.3, this.b);
|
|
this.b = 2.7;
|
|
this.c = 3.0;
|
|
this.d = 4.5;
|
|
}
|
|
function constructor2() {
|
|
this.e = 5.0;
|
|
this.f = new constructor1();
|
|
assertEquals(1.0, this.f.a);
|
|
assertEquals(2.7, this.f.b);
|
|
assertEquals(3.0, this.f.c);
|
|
assertEquals(4.5, this.f.d);
|
|
assertEquals(5.0, this.e);
|
|
this.e = 5.9;
|
|
this.g = 6.7;
|
|
}
|
|
function func() {
|
|
var o = new constructor2();
|
|
assertEquals(1.0, o.f.a);
|
|
assertEquals(2.7, o.f.b);
|
|
assertEquals(3.0, o.f.c);
|
|
assertEquals(4.5, o.f.d);
|
|
assertEquals(5.9, o.e);
|
|
assertEquals(6.7, o.g);
|
|
}
|
|
func(); func();
|
|
%OptimizeFunctionOnNextCall(func);
|
|
func(); func();
|
|
delete deopt.deopt;
|
|
func(); func();
|
|
})();
|
|
|
|
|
|
// Test map checks on captured objects.
|
|
(function testMapCheck() {
|
|
var sum = 0;
|
|
function getter() { return 27; }
|
|
function setter(v) { sum += v; }
|
|
function constructor() {
|
|
this.x = 23;
|
|
this.y = 42;
|
|
}
|
|
function check(x, y) {
|
|
var o = new constructor();
|
|
assertEquals(x, o.x);
|
|
assertEquals(y, o.y);
|
|
}
|
|
var monkey = Object.create(null, {
|
|
x: { get:getter, set:setter },
|
|
y: { get:getter, set:setter }
|
|
});
|
|
check(23, 42); check(23, 42);
|
|
%OptimizeFunctionOnNextCall(check);
|
|
check(23, 42); check(23, 42);
|
|
constructor.prototype = monkey;
|
|
check(27, 27); check(27, 27);
|
|
assertEquals(130, sum);
|
|
})();
|
|
|
|
|
|
// Test OSR into a loop with captured objects.
|
|
(function testOSR() {
|
|
function constructor() {
|
|
this.a = 23;
|
|
}
|
|
function osr1(length) {
|
|
assertEquals(23, (new constructor()).a);
|
|
var result = 0;
|
|
for (var i = 0; i < length; i++) {
|
|
result = (result + i) % 99;
|
|
}
|
|
return result;
|
|
}
|
|
function osr2(length) {
|
|
var result = 0;
|
|
for (var i = 0; i < length; i++) {
|
|
result = (result + i) % 99;
|
|
}
|
|
assertEquals(23, (new constructor()).a);
|
|
return result;
|
|
}
|
|
function osr3(length) {
|
|
var result = 0;
|
|
var o = new constructor();
|
|
for (var i = 0; i < length; i++) {
|
|
result = (result + i) % 99;
|
|
}
|
|
assertEquals(23, o.a);
|
|
return result;
|
|
}
|
|
function test(closure) {
|
|
assertEquals(45, closure(10));
|
|
assertEquals(45, closure(10));
|
|
assertEquals(10, closure(50000));
|
|
}
|
|
test(osr1);
|
|
test(osr2);
|
|
test(osr3);
|
|
})();
|
|
|
|
|
|
// Test out-of-bounds access on captured objects.
|
|
(function testOOB() {
|
|
function cons1() {
|
|
this.x = 1;
|
|
this.y = 2;
|
|
this.z = 3;
|
|
}
|
|
function cons2() {
|
|
this.a = 7;
|
|
}
|
|
function oob(constructor, branch) {
|
|
var o = new constructor();
|
|
if (branch) {
|
|
return o.a;
|
|
} else {
|
|
return o.z;
|
|
}
|
|
}
|
|
assertEquals(3, oob(cons1, false));
|
|
assertEquals(3, oob(cons1, false));
|
|
assertEquals(7, oob(cons2, true));
|
|
assertEquals(7, oob(cons2, true));
|
|
gc(); // Clears type feedback of constructor call.
|
|
assertEquals(7, oob(cons2, true));
|
|
assertEquals(7, oob(cons2, true));
|
|
%OptimizeFunctionOnNextCall(oob);
|
|
assertEquals(7, oob(cons2, true));
|
|
})();
|
|
|
|
|
|
// Test non-shallow nested graph of captured objects.
|
|
(function testDeep() {
|
|
var deopt = { deopt:false };
|
|
function constructor1() {
|
|
this.x = 23;
|
|
}
|
|
function constructor2(nested) {
|
|
this.a = 17;
|
|
this.b = nested;
|
|
this.c = 42;
|
|
}
|
|
function deep() {
|
|
var o1 = new constructor1();
|
|
var o2 = new constructor2(o1);
|
|
assertEquals(17, o2.a);
|
|
assertEquals(23, o2.b.x);
|
|
assertEquals(42, o2.c);
|
|
o1.x = 99;
|
|
deopt.deopt;
|
|
assertEquals(99, o1.x);
|
|
assertEquals(99, o2.b.x);
|
|
}
|
|
deep(); deep();
|
|
%OptimizeFunctionOnNextCall(deep);
|
|
deep(); deep();
|
|
delete deopt.deopt;
|
|
deep(); deep();
|
|
})();
|
|
|
|
|
|
// Test non-shallow nested graph of captured objects with duplicates
|
|
(function testDeepDuplicate() {
|
|
function constructor1() {
|
|
this.x = 23;
|
|
}
|
|
function constructor2(nested) {
|
|
this.a = 17;
|
|
this.b = nested;
|
|
this.c = 42;
|
|
}
|
|
function deep(shouldDeopt) {
|
|
var o1 = new constructor1();
|
|
var o2 = new constructor2(o1);
|
|
var o3 = new constructor2(o1);
|
|
assertEquals(17, o2.a);
|
|
assertEquals(23, o2.b.x);
|
|
assertEquals(42, o2.c);
|
|
o3.c = 54;
|
|
o1.x = 99;
|
|
if (shouldDeopt) %DeoptimizeFunction(deep);
|
|
assertEquals(99, o1.x);
|
|
assertEquals(99, o2.b.x);
|
|
assertEquals(99, o3.b.x);
|
|
assertEquals(54, o3.c);
|
|
assertEquals(17, o3.a);
|
|
assertEquals(42, o2.c);
|
|
assertEquals(17, o2.a);
|
|
o3.b.x = 1;
|
|
assertEquals(1, o1.x);
|
|
}
|
|
deep(false); deep(false);
|
|
%OptimizeFunctionOnNextCall(deep);
|
|
deep(false); deep(false);
|
|
deep(true); deep(true);
|
|
})();
|
|
|
|
|
|
// Test non-shallow nested graph of captured objects with inline
|
|
(function testDeepInline() {
|
|
function h() {
|
|
return { y : 3 };
|
|
}
|
|
|
|
function g(x) {
|
|
var u = { x : h() };
|
|
%DeoptimizeFunction(f);
|
|
return u;
|
|
}
|
|
|
|
function f() {
|
|
var l = { dummy : { } };
|
|
var r = g(l);
|
|
assertEquals(3, r.x.y);
|
|
}
|
|
|
|
f(); f(); f();
|
|
%OptimizeFunctionOnNextCall(f);
|
|
f();
|
|
})();
|
|
|
|
|
|
// Test two nested objects
|
|
(function testTwoNestedObjects() {
|
|
function f() {
|
|
var l = { x : { y : 111 } };
|
|
var l2 = { x : { y : 111 } };
|
|
%DeoptimizeFunction(f);
|
|
assertEquals(111, l.x.y);
|
|
assertEquals(111, l2.x.y);
|
|
}
|
|
|
|
f(); f(); f();
|
|
%OptimizeFunctionOnNextCall(f);
|
|
f();
|
|
})();
|
|
|
|
|
|
// Test a nested object and a duplicate
|
|
(function testTwoObjectsWithDuplicate() {
|
|
function f() {
|
|
var l = { x : { y : 111 } };
|
|
var dummy = { d : 0 };
|
|
var l2 = l.x;
|
|
%DeoptimizeFunction(f);
|
|
assertEquals(111, l.x.y);
|
|
assertEquals(111, l2.y);
|
|
assertEquals(0, dummy.d);
|
|
}
|
|
|
|
f(); f(); f();
|
|
%OptimizeFunctionOnNextCall(f);
|
|
f();
|
|
})();
|
|
|
|
|
|
// Test materialization of a field that requires a Smi value.
|
|
(function testSmiField() {
|
|
var deopt = { deopt:false };
|
|
function constructor() {
|
|
this.x = 1;
|
|
}
|
|
function field(x) {
|
|
var o = new constructor();
|
|
o.x = x;
|
|
deopt.deopt
|
|
assertEquals(x, o.x);
|
|
}
|
|
field(1); field(2);
|
|
%OptimizeFunctionOnNextCall(field);
|
|
field(3); field(4);
|
|
delete deopt.deopt;
|
|
field(5.5); field(6.5);
|
|
})();
|
|
|
|
|
|
// Test materialization of a field that requires a heap object value.
|
|
(function testHeapObjectField() {
|
|
var deopt = { deopt:false };
|
|
function constructor() {
|
|
this.x = {};
|
|
}
|
|
function field(x) {
|
|
var o = new constructor();
|
|
o.x = x;
|
|
deopt.deopt
|
|
assertEquals(x, o.x);
|
|
}
|
|
field({}); field({});
|
|
%OptimizeFunctionOnNextCall(field);
|
|
field({}); field({});
|
|
delete deopt.deopt;
|
|
field(1); field(2);
|
|
})();
|