Diff
Modified: trunk/JSTests/ChangeLog (209951 => 209952)
--- trunk/JSTests/ChangeLog 2016-12-17 00:48:31 UTC (rev 209951)
+++ trunk/JSTests/ChangeLog 2016-12-17 01:06:49 UTC (rev 209952)
@@ -1,3 +1,18 @@
+2016-12-16 Mark Lam <mark....@apple.com>
+
+ De-duplicate finally blocks.
+ https://bugs.webkit.org/show_bug.cgi?id=160168
+
+ Reviewed by Keith Miller.
+
+ * stress/deeply-nested-finallys.js: Added.
+ - Tests many levels of finally nesting. This causes the old code to hang (and
+ crashes eventually) while trying to generate bytecode for the exponentially
+ duplicated finally blocks. The new code completes this test almost instantly.
+
+ * stress/test-finally.js: Added.
+ - Tests control flow through various permutations of finally blocks.
+
2016-12-16 Saam Barati <sbar...@apple.com>
WebAssembly: WasmB3IRGenerator should throw exceptions instead of crash
Added: trunk/JSTests/stress/deeply-nested-finallys.js (0 => 209952)
--- trunk/JSTests/stress/deeply-nested-finallys.js (rev 0)
+++ trunk/JSTests/stress/deeply-nested-finallys.js 2016-12-17 01:06:49 UTC (rev 209952)
@@ -0,0 +1,78 @@
+// This test should finish almost instantly.
+
+function exp() {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ try {
+ } finally {return 1;}
+ } finally { return 1; }
+ } finally {return 1;}
+ } finally { return 1; }
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally { return 1; }
+ } finally {return 1;}
+ } finally { return 1; }
+ } finally {return 1;}
+ } finally { return 1; }
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally { return 1; }
+ } finally {return 1;}
+ } finally { return 1; }
+ } finally {return 1;}
+ } finally { return 1; }
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally { return 1; }
+ } finally {return 1;}
+ } finally { return 1; }
+ } finally {return 1;}
+ } finally { return 1; }
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally {return 1;}
+ } finally { return 1; }
+}
+
+exp();
Added: trunk/JSTests/stress/test-finally.js (0 => 209952)
--- trunk/JSTests/stress/test-finally.js (rev 0)
+++ trunk/JSTests/stress/test-finally.js 2016-12-17 01:06:49 UTC (rev 209952)
@@ -0,0 +1,1018 @@
+// This test just creates functions which exercise various permutations of control flow
+// thru finally blocks. The test passes if it does not throw any exceptions or crash on
+// bytecode validation.
+
+if (this.window)
+ print = console.log;
+
+var steps;
+var returned;
+var thrown;
+var testLineNumber;
+
+let NothingReturned = "NothingReturned";
+let NothingThrown = "NothingThrown";
+
+function assertResults(expectedSteps, expectedReturned, expectedThrown) {
+ let stepsMismatch = (steps != expectedSteps);
+ let returnedMismatch = (returned != expectedReturned);
+ let thrownMismatch = (thrown != expectedThrown && !("" + thrown).startsWith("" + expectedThrown));
+
+ if (stepsMismatch || returnedMismatch || thrownMismatch) {
+ print("FAILED Test @ line " + testLineNumber + ":");
+ print(" Actual: steps: " + steps + ", returned: " + returned + ", thrown: '" + thrown + "'");
+ print(" Expected: steps: " + expectedSteps + ", returned: " + expectedReturned + ", thrown: '" + expectedThrown + "'");
+ }
+ if (stepsMismatch)
+ throw "FAILED Test @ line " + testLineNumber + ": steps do not match: expected ='" + expectedSteps + "' actual='" + steps + "'";
+ if (returnedMismatch)
+ throw "FAILED Test @ line " + testLineNumber + ": returned value does not match: expected ='" + expectedReturned + "' actual='" + returned + "'";
+ if (thrownMismatch)
+ throw "FAILED Test @ line " + testLineNumber + ": thrown value does does not match: expected ='" + expectedThrown + "' actual='" + thrown + "'";
+}
+
+function resetResults() {
+ steps = [];
+ returned = NothingReturned;
+ thrown = NothingThrown;
+}
+
+function append(step) {
+ let next = steps.length;
+ steps[next] = step;
+}
+
+function test(func, expectedSteps, expectedReturned, expectedThrown) {
+ testLineNumber = new Error().stack.match(/global code@.+\.js:([0-9]+)/)[1];
+ resetResults();
+
+ try {
+ returned = func();
+ } catch (e) {
+ thrown = e;
+ }
+
+ assertResults(expectedSteps, expectedReturned, expectedThrown);
+}
+
+// Test CompletionType::Normal on an empty try blocks.
+test(() => {
+ try {
+ append("t2");
+ for (var i = 0; i < 2; i++) {
+ try {
+ } catch(a) {
+ append("c1");
+ } finally {
+ append("f1");
+ }
+ }
+ } catch(b) {
+ append("c2");
+ } finally {
+ append("f2");
+ }
+
+}, "t2,f1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Normal.
+test(() => {
+ try {
+ append("t2");
+ for (var i = 0; i < 2; i++) {
+ try {
+ append("t1");
+ } catch(a) {
+ append("c1");
+ } finally {
+ append("f1");
+ }
+ }
+ } catch(b) {
+ append("c2");
+ } finally {
+ append("f2");
+ }
+
+}, "t2,t1,f1,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Break.
+test(() => {
+ try {
+ append("t2");
+ for (var i = 0; i < 2; i++) {
+ try {
+ append("t1");
+ break;
+ } catch(a) {
+ append("c1");
+ } finally {
+ append("f1");
+ }
+ }
+ } catch(b) {
+ append("c2");
+ } finally {
+ append("f2");
+ }
+
+}, "t2,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Continue.
+test(() => {
+ try {
+ append("t2");
+ for (var i = 0; i < 2; i++) {
+ try {
+ append("t1");
+ continue;
+ } catch(a) {
+ append("c1");
+ } finally {
+ append("f1");
+ }
+ }
+ } catch(b) {
+ append("c2");
+ } finally {
+ append("f2");
+ }
+
+}, "t2,t1,f1,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Return.
+test(() => {
+ try {
+ append("t2");
+ for (var i = 0; i < 2; i++) {
+ try {
+ append("t1");
+ return;
+ } catch(a) {
+ append("c1");
+ } finally {
+ append("f1");
+ }
+ }
+ } catch(b) {
+ append("c2");
+ } finally {
+ append("f2");
+ }
+
+}, "t2,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Throw.
+test(() => {
+ try {
+ append("t2");
+ for (var i = 0; i < 2; i++) {
+ try {
+ append("t1");
+ throw { };
+ } catch(a) {
+ append("c1");
+ } finally {
+ append("f1");
+ }
+ }
+ } catch(b) {
+ append("c2");
+ } finally {
+ append("f2");
+ }
+
+}, "t2,t1,c1,f1,t1,c1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Normal in a for-of loop.
+test(() => {
+ let arr = [1, 2];
+ try {
+ append("t2");
+ for (let x of arr) {
+ try {
+ append("t1");
+ } catch(a) {
+ append("c1");
+ } finally {
+ append("f1");
+ }
+ }
+ } catch(b) {
+ append("c2");
+ } finally {
+ append("f2");
+ }
+
+}, "t2,t1,f1,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Break in a for-of loop.
+test(() => {
+ let arr = [1, 2];
+ try {
+ append("t2");
+ for (let x of arr) {
+ try {
+ append("t1");
+ break;
+ } catch(a) {
+ append("c1");
+ } finally {
+ append("f1");
+ }
+ }
+ } catch(b) {
+ append("c2");
+ } finally {
+ append("f2");
+ }
+
+}, "t2,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Continue in a for-of loop.
+test(() => {
+ let arr = [1, 2];
+ try {
+ append("t2");
+ for (let x of arr) {
+ try {
+ append("t1");
+ continue;
+ } catch(a) {
+ append("c1");
+ } finally {
+ append("f1");
+ }
+ }
+ } catch(b) {
+ append("c2");
+ } finally {
+ append("f2");
+ }
+
+}, "t2,t1,f1,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Return in a for-of loop.
+test(() => {
+ let arr = [1, 2];
+ try {
+ append("t2");
+ for (let x of arr) {
+ try {
+ append("t1");
+ return;
+ } catch(a) {
+ append("c1");
+ } finally {
+ append("f1");
+ }
+ }
+ } catch(b) {
+ append("c2");
+ } finally {
+ append("f2");
+ }
+
+}, "t2,t1,f1,f2", undefined, NothingThrown);
+
+// Test CompletionType::Throw in a for-of loop.
+test(() => {
+ let arr = [1, 2];
+ try {
+ append("t2");
+ for (let x of arr) {
+ try {
+ append("t1");
+ throw { };
+ } catch(a) {
+ append("c1");
+ } finally {
+ append("f1");
+ }
+ }
+ } catch(b) {
+ append("c2");
+ } finally {
+ append("f2");
+ }
+
+}, "t2,t1,c1,f1,t1,c1,f1,f2", undefined, NothingThrown);
+
+
+// No abrupt completions.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ } finally {
+ append("c");
+ }
+ append("d");
+ return 400;
+
+}, "a,b,c,d", 400, NothingThrown);
+
+
+// Return after a. Should not execute any finally blocks, and return a's result.
+test(() => {
+ append("a");
+ return 100;
+ try {
+ append("b");
+ return 200;
+ try {
+ append("c");
+ return 300;
+ } finally {
+ append("d");
+ }
+ append("e");
+ return 500;
+ } finally {
+ append("f");
+ }
+ append("g");
+ return 700;
+
+}, "a", 100, NothingThrown);
+
+// Return after b. Should execute finally block f only, and return b's result.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ return 200;
+ try {
+ append("c");
+ return 300;
+ } finally {
+ append("d");
+ }
+ append("e");
+ return 500;
+ } finally {
+ append("f");
+ }
+ append("g");
+ return 700;
+
+}, "a,b,f", 200, NothingThrown);
+
+// Return after c. Should execute finally blocks d and f, and return c's result.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ return 300;
+ } finally {
+ append("d");
+ }
+ append("e");
+ return 500;
+ } finally {
+ append("f");
+ }
+ append("g");
+ return 700;
+
+}, "a,b,c,d,f", 300, NothingThrown);
+
+// Return after c and d. Should execute finally blocks d and f, and return last return value from d.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ return 300;
+ } finally {
+ append("d");
+ return 400;
+ }
+ append("e");
+ return 500;
+ } finally {
+ append("f");
+ }
+ append("g");
+ return 700;
+
+}, "a,b,c,d,f", 400, NothingThrown);
+
+// Return after c, d, and f. Should execute finally blocks d and f, and return last return value from f.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ return 300;
+ } finally {
+ append("d");
+ return 400;
+ }
+ append("e");
+ return 500;
+ } finally {
+ append("f");
+ return 600;
+ }
+ append("g");
+ return 700;
+
+}, "a,b,c,d,f", 600, NothingThrown);
+
+// Return after g.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ } finally {
+ append("d");
+ }
+ append("e");
+ } finally {
+ append("f");
+ }
+ append("g");
+ return 700;
+
+}, "a,b,c,d,e,f,g", 700, NothingThrown);
+
+// Throw after a.
+test(() => {
+ append("a");
+ throw 100;
+ try {
+ append("b");
+ throw 200;
+ try {
+ append("c");
+ throw 300;
+ } finally {
+ append("d");
+ }
+ append("e");
+ throw 500;
+ } finally {
+ append("f");
+ }
+ append("g");
+ throw 700;
+
+}, "a", NothingReturned, 100);
+
+// Throw after b.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ throw 200;
+ try {
+ append("c");
+ throw 300;
+ } finally {
+ append("d");
+ }
+ append("e");
+ throw 500;
+ } finally {
+ append("f");
+ }
+ append("g");
+ throw 700;
+
+}, "a,b,f", NothingReturned, 200);
+
+// Throw after c.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ throw 300;
+ } finally {
+ append("d");
+ }
+ append("e");
+ throw 500;
+ } finally {
+ append("f");
+ }
+ append("g");
+ throw 700;
+
+}, "a,b,c,d,f", NothingReturned, 300);
+
+// Throw after c and d.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ throw 300;
+ } finally {
+ append("d");
+ throw 400;
+ }
+ append("e");
+ throw 500;
+ } finally {
+ append("f");
+ }
+ append("g");
+ throw 700;
+
+}, "a,b,c,d,f", NothingReturned, 400);
+
+// Throw after c, d, and f.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ throw 300;
+ } finally {
+ append("d");
+ throw 400;
+ }
+ append("e");
+ throw 500;
+ } finally {
+ append("f");
+ throw 600;
+ }
+ append("g");
+ return 700;
+
+}, "a,b,c,d,f", NothingReturned, 600);
+
+// Throw after g.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ } finally {
+ append("d");
+ }
+ append("e");
+ } finally {
+ append("f");
+ }
+ append("g");
+ throw 700;
+
+}, "a,b,c,d,e,f,g", NothingReturned, 700);
+
+// Throw after b with catch at z.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ throw 200;
+ try {
+ append("c");
+ throw 300;
+ } finally {
+ append("d");
+ }
+ append("e");
+ throw 500;
+ } catch (e) {
+ append("z");
+ } finally {
+ append("f");
+ }
+ append("g");
+ return 700;
+
+}, "a,b,z,f,g", 700, NothingThrown);
+
+// Throw after b with catch at z and rethrow at z.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ throw 200;
+ try {
+ append("c");
+ throw 300;
+ } finally {
+ append("d");
+ }
+ append("e");
+ throw 500;
+ } catch (e) {
+ append("z");
+ throw 5000;
+ } finally {
+ append("f");
+ }
+ append("g");
+ return 700;
+
+}, "a,b,z,f", NothingReturned, 5000);
+
+// Throw after c with catch at y and z.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ throw 300;
+ } catch (e) {
+ append("y");
+ } finally {
+ append("d");
+ }
+ append("e");
+ } catch (e) {
+ append("z");
+ } finally {
+ append("f");
+ }
+ append("g");
+ return 700;
+
+}, "a,b,c,y,d,e,f,g", 700, NothingThrown);
+
+// Throw after c with catch at y and z, and rethrow at y.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ throw 300;
+ } catch (e) {
+ append("y");
+ throw 3000;
+ } finally {
+ append("d");
+ }
+ append("e");
+ } catch (e) {
+ append("z");
+ } finally {
+ append("f");
+ }
+ append("g");
+ return 700;
+
+}, "a,b,c,y,d,z,f,g", 700, NothingThrown);
+
+// Throw after c with catch at y and z, and rethrow at y and z.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ throw 300;
+ } catch (e) {
+ append("y");
+ throw 3000;
+ } finally {
+ append("d");
+ }
+ append("e");
+ } catch (e) {
+ append("z");
+ throw 5000;
+ } finally {
+ append("f");
+ }
+ append("g");
+ return 700;
+
+}, "a,b,c,y,d,z,f", NothingReturned, 5000);
+
+// Throw after c with catch at y and z, and rethrow at y, z, and f.
+test(() => {
+ append("a");
+ try {
+ append("b");
+ try {
+ append("c");
+ throw 300;
+ } catch (e) {
+ append("y");
+ throw 3000;
+ } finally {
+ append("d");
+ }
+ append("e");
+ } catch (e) {
+ append("z");
+ throw 5000;
+ } finally {
+ append("f");
+ throw 600;
+ }
+ append("g");
+ return 700;
+
+}, "a,b,c,y,d,z,f", NothingReturned, 600);
+
+// No throw or return in for-of loop.
+test(() => {
+ class TestIterator {
+ constructor() {
+ append("c");
+ this.i = 0;
+ }
+ next() {
+ append("n");
+ let done = (this.i == 3);
+ return { done, value: this.i++ };
+ }
+ return() {
+ append("r");
+ return { }
+ }
+ }
+
+ var arr = [];
+ arr[Symbol.iterator] = function() {
+ return new TestIterator();
+ }
+
+ for (var element of arr) {
+ append(element);
+ }
+ append("x");
+ return 200;
+
+}, "c,n,0,n,1,n,2,n,x", 200, NothingThrown);
+
+// Break in for-of loop.
+test(() => {
+ class TestIterator {
+ constructor() {
+ append("c");
+ this.i = 0;
+ }
+ next() {
+ append("n");
+ let done = (this.i == 3);
+ return { done, value: this.i++ };
+ }
+ return() {
+ append("r");
+ return { }
+ }
+ }
+
+ var arr = [];
+ arr[Symbol.iterator] = function() {
+ return new TestIterator();
+ }
+
+ for (var element of arr) {
+ append(element);
+ if (element == 1)
+ break;
+ }
+ append("x");
+ return 200;
+
+}, "c,n,0,n,1,r,x", 200, NothingThrown);
+
+// Break in for-of loop with throw in Iterator.return().
+test(() => {
+ class Iterator {
+ constructor() {
+ append("c");
+ this.i = 0;
+ }
+ next() {
+ append("n");
+ let done = (this.i == 3);
+ return { done, value: this.i++ };
+ }
+ return() {
+ append("r");
+ throw 300;
+ }
+ }
+
+ var arr = [];
+ arr[Symbol.iterator] = function() {
+ return new Iterator();
+ }
+
+ for (var element of arr) {
+ append(element);
+ if (element == 1)
+ break;
+ }
+ append("x");
+ return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 300);
+
+// Break in for-of loop with Iterator.return() returning a non object.
+test(() => {
+ class Iterator {
+ constructor() {
+ append("c");
+ this.i = 0;
+ }
+ next() {
+ append("n");
+ let done = (this.i == 3);
+ return { done, value: this.i++ };
+ }
+ return() {
+ append("r");
+ }
+ }
+
+ var arr = [];
+ arr[Symbol.iterator] = function() {
+ return new Iterator();
+ }
+
+ for (var element of arr) {
+ append(element);
+ if (element == 1)
+ break;
+ }
+ append("x");
+ return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 'TypeError');
+
+// Return in for-of loop.
+test(() => {
+ class TestIterator {
+ constructor() {
+ append("c");
+ this.i = 0;
+ }
+ next() {
+ append("n");
+ let done = (this.i == 3);
+ return { done, value: this.i++ };
+ }
+ return() {
+ append("r");
+ return { }
+ }
+ }
+
+ var arr = [];
+ arr[Symbol.iterator] = function() {
+ return new TestIterator();
+ }
+
+ for (var element of arr) {
+ append(element);
+ if (element == 1)
+ return 100;
+ }
+ append("x");
+ return 200;
+
+}, "c,n,0,n,1,r", 100, NothingThrown);
+
+// Return in for-of loop with throw in Iterator.return().
+test(() => {
+ class Iterator {
+ constructor() {
+ append("c");
+ this.i = 0;
+ }
+ next() {
+ append("n");
+ let done = (this.i == 3);
+ return { done, value: this.i++ };
+ }
+ return() {
+ append("r");
+ throw 300;
+ }
+ }
+
+ var arr = [];
+ arr[Symbol.iterator] = function() {
+ return new Iterator();
+ }
+
+ for (var element of arr) {
+ append(element);
+ if (element == 1)
+ return 100;
+ }
+ append("x");
+ return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 300);
+
+// Return in for-of loop with Iterator.return() returning a non object.
+test(() => {
+ class Iterator {
+ constructor() {
+ append("c");
+ this.i = 0;
+ }
+ next() {
+ append("n");
+ let done = (this.i == 3);
+ return { done, value: this.i++ };
+ }
+ return() {
+ append("r");
+ }
+ }
+
+ var arr = [];
+ arr[Symbol.iterator] = function() {
+ return new Iterator();
+ }
+
+ for (var element of arr) {
+ append(element);
+ if (element == 1)
+ return 100;
+ }
+ append("x");
+ return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 'TypeError');
+
+
+// Throw in for-of loop.
+test(() => {
+ class Iterator {
+ constructor() {
+ append("c");
+ this.i = 0;
+ }
+ next() {
+ append("n");
+ let done = (this.i == 3);
+ return { done, value: this.i++ };
+ }
+ return() {
+ append("r");
+ }
+ }
+
+ var arr = [];
+ arr[Symbol.iterator] = function() {
+ return new Iterator();
+ }
+
+ for (var element of arr) {
+ append(element);
+ if (element == 1)
+ throw 100;
+ }
+ append("x");
+ return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 100);
+
+// Throw in for-of loop with throw in Iterator.return().
+test(() => {
+ class Iterator {
+ constructor() {
+ append("c");
+ this.i = 0;
+ }
+ next() {
+ append("n");
+ let done = (this.i == 3);
+ return { done, value: this.i++ };
+ }
+ return() {
+ append("r");
+ throw 300;
+ }
+ }
+
+ var arr = [];
+ arr[Symbol.iterator] = function() {
+ return new Iterator();
+ }
+
+ for (var element of arr) {
+ append(element);
+ if (element == 1)
+ throw 100;
+ }
+ append("x");
+ return 200;
+
+}, "c,n,0,n,1,r", NothingReturned, 100);
Modified: trunk/Source/_javascript_Core/ChangeLog (209951 => 209952)
--- trunk/Source/_javascript_Core/ChangeLog 2016-12-17 00:48:31 UTC (rev 209951)
+++ trunk/Source/_javascript_Core/ChangeLog 2016-12-17 01:06:49 UTC (rev 209952)
@@ -1,3 +1,242 @@
+2016-12-16 Mark Lam <mark....@apple.com>
+
+ De-duplicate finally blocks.
+ https://bugs.webkit.org/show_bug.cgi?id=160168
+
+ Reviewed by Keith Miller.
+
+ JS execution can arrive at a finally block when there are abrupt completions from
+ its try or catch block. The abrupt completion types include Break,
+ Continue, Return, and Throw. The non-abrupt completion type is called Normal
+ (i.e. the case of a try block falling through to the finally block).
+
+ Previously, we enable each of these paths for abrupt completion (except for Throw)
+ to run the finally block code by duplicating the finally block code at each of
+ the sites that trigger those completions. This patch fixes the implementation so
+ that each of these abrupt completions will set a finallyActionRegister (plus a
+ finallyReturnValueRegister for CompletionType::Return) and then jump to the
+ relevant finally blocks, and continue to thread through subsequent outer finally
+ blocks until execution reaches the outermost finally block that the completion
+ type dictates. We no longer duplicate the finally block code.
+
+ The implementation details:
+ 1. We allocate a pair of finallyActionRegister and finallyReturnValueRegister
+ just before entering the outermost try-catch-finally scope.
+
+ On allocating the registers, we set them to the empty JSValue. This serves
+ to set the completion type to CompletionType::Normal (see (2) below).
+
+ 2. The finallyActionRegister serves 2 purpose:
+ a. indicates the CompletionType that triggered entry into the finally block.
+
+ This is how we encode the completion type in the finallyActionRegister:
+ 1. CompletionType::Normal
+ - finallyActionRegister is set to the empty JSValue.
+ 2. CompletionType::Break
+ - finallyActionRegister is set to the int jumpID for the site of the break statement.
+ 3. CompletionType::Continue
+ - finallyActionRegister is set to the int jumpID for the site of the continue statement.
+ 4. CompletionType::Return
+ - finallyActionRegister is set to CompletionType::Return as an int JSValue.
+ - finallyReturnValueRegister is set to the value to be returned.
+ 5. CompletionType::Throw
+ - finallyActionRegister is set to the exception object that was caught by the finally block.
+
+ Hence, if the finallyActionRegister can either be:
+ 1. empty i.e. we're handling CompletionType::Normal.
+ 2. an int JSValue i.e. we're handling CompletionType::Break, Continue, or Return.
+ 3. an object i.e. we're handling CompletionType::Throw.
+
+ b. stores the exception caught in the finally block if we're handing
+ CompletionType::Throw.
+
+ 3. Each finally block will have 2 entries:
+ a. the entry via throw.
+ b. the normal entry.
+
+ The entry via throw is recorded in the codeBlock's exception table, and can
+ only be jumped to by the VM's exception handling mechanism.
+
+ The normal entry is recorded in a FinallyContext (at bytecode generation time
+ only) and is jumped to when we want enter the finally block due any of the
+ other CompletionTypes.
+
+ 4. CompletionType::Normal
+ ======================
+ We encounter this when falling through from a try or catch block to the finally block.
+
+ For the try block case, since finallyActionRegister is set to Normal by default,
+ there's nothing more that needs to be done.
+
+ For the catch block case, since we entered the catch block with an exception,
+ finallyActionRegister may be set to Throw. We'll need to set it to Normal
+ before jumping to the finally block's normal entry.
+
+ CompletionType::Break
+ =====================
+ When we emit bytecode for the BreakNode, we check if we have any FinallyContexts
+ that we need to service before jumping to the breakTarget. If we do, then:
+ a. we'll register a jumpID along with the breakTarget with the outermost FinallyContext.
+ b. we'll also increment the numberOfBreaksOrContinues count in each FinallyContext
+ from the innermost to the outermost.
+ c. instead of emitting bytecode to jump to the breakTarget, we:
+ 1. emit bytecode to set finallyActionRegister to the jumpID.
+ b. emit bytecode to jump to the normal entry of the innermost finally block.
+
+ Each finally block will take care of cascading to the next outer finally block
+ as needed (see (5) below).
+
+ CompletionType::Continue
+ ========================
+ Since continues and breaks work the same way (i.e. with a jump), we handle this
+ exactly the same way as CompletionType::Break, except that we use the
+ continueTarget instead of the breakTarget.
+
+ CompletionType::Return
+ ======================
+ When we emit bytecode for the ReturnNode, we check if we have any FinallyContexts
+ at all on the m_controlFlowScopeStack.
+
+ If so, then instead of emitting op_ret, we:
+ 1. emit bytecode to set finallyActionRegister to the CompletionType::Return.
+ 1. emit bytecode to move the return value into finallyReturnValueRegister.
+ 2. emit bytecode to jump to the normal entry of the innermost finally block.
+
+ Each finally block will take care of cascading to the next outer finally block
+ as needed (see (5) below).
+
+ CompletionType::Throw
+ ======================
+ The op_catch of a finally block will always store the caught exception object
+ in the finallyActionRegister. This means we're handling CompletionType::Throw
+ (see (2) above).
+
+ 5. What happens in each finally block?
+ ==================================
+ Only the finally block's entry via throw will have an op_catch that catches the
+ pending exception (and stores it in the finallyActionRegister). This throw
+ entry then falls through to the normal entry.
+
+ The finally block's normal entry will restore the scope of the finally block
+ and proceed to execute its code.
+
+ At the end of the finally block (see emitFinallyCompletion()), the finally
+ block will check the finallyActionRegister for each completion type in the
+ following order:
+
+ a. CompletionType::Normal: jump to the code after the finally block as
+ designated by a normalCompletion label.
+
+ b. CompletionType::Break and Continue:
+ If the FinallyContext for this block has registered FinallyJumps, we'll
+ check for the jumpIDs against the finallyActionRegister. If the jumpID
+ matches, jump to the corresponding jumpTarget.
+
+ If no jumpIDs match but the FinallyContext's numberOfBreaksOrContinues is
+ greater than the number of registered FinallyJumps, then this means that
+ we have a Break or Continue that needs to be handled by an outer finally
+ block. In that case, jump to the outer finally block's normal entry.
+
+ c. CompletionType::Return:
+ If this finally block is not the outermost and finallyActionRegister contains
+ CompletionType::Return, then jump to the outer finally block's normal entry.
+
+ Otherwise, if this finally block is the outermost and finallyActionRegister
+ contains CompletionType::Return, then execute op_ret and return the value
+ in finallyReturnValueRegister.
+
+ d. CompletionType::Throw:
+ If we're not handling any of the above cases, then just throw the
+ finallyActionRegister which contains the exception to re-throw.
+
+ 6. restoreScopeRegister()
+
+ Since the needed scope objects are always stored in a local, we can restore
+ the scope register by simply moving from that local instead of going through
+ op_get_parent_scope.
+
+ 7. m_controlFlowScopeStack needs to be a SegmentedVector instead of a Vector.
+ This makes it easier to keep a pointer to the FinallyContext on that stack,
+ and not have to worry about the vector being realloc'ed due to resizing.
+
+ Performance appears to be neutral both on ES6SampleBench (run via cli) and the
+ JSC benchmarks.
+
+ Relevant spec references:
+ https://tc39.github.io/ecma262/#sec-completion-record-specification-type
+ https://tc39.github.io/ecma262/#sec-try-statement-runtime-semantics-evaluation
+
+ * bytecode/HandlerInfo.h:
+ (JSC::HandlerInfoBase::typeName):
+ * bytecompiler/BytecodeGenerator.cpp:
+ (JSC::BytecodeGenerator::generate):
+ (JSC::BytecodeGenerator::BytecodeGenerator):
+ (JSC::BytecodeGenerator::emitReturn):
+ (JSC::BytecodeGenerator::pushFinallyControlFlowScope):
+ (JSC::BytecodeGenerator::popFinallyControlFlowScope):
+ (JSC::BytecodeGenerator::allocateAndEmitScope):
+ (JSC::BytecodeGenerator::pushTry):
+ (JSC::BytecodeGenerator::popTry):
+ (JSC::BytecodeGenerator::emitCatch):
+ (JSC::BytecodeGenerator::restoreScopeRegister):
+ (JSC::BytecodeGenerator::labelScopeDepthToLexicalScopeIndex):
+ (JSC::BytecodeGenerator::labelScopeDepth):
+ (JSC::BytecodeGenerator::pushLocalControlFlowScope):
+ (JSC::BytecodeGenerator::popLocalControlFlowScope):
+ (JSC::BytecodeGenerator::emitEnumeration):
+ (JSC::BytecodeGenerator::emitIsNumber):
+ (JSC::BytecodeGenerator::emitYield):
+ (JSC::BytecodeGenerator::emitDelegateYield):
+ (JSC::BytecodeGenerator::emitJumpViaFinallyIfNeeded):
+ (JSC::BytecodeGenerator::emitReturnViaFinallyIfNeeded):
+ (JSC::BytecodeGenerator::emitFinallyCompletion):
+ (JSC::BytecodeGenerator::allocateFinallyRegisters):
+ (JSC::BytecodeGenerator::releaseFinallyRegisters):
+ (JSC::BytecodeGenerator::emitCompareFinallyActionAndJumpIf):
+ (JSC::BytecodeGenerator::pushIteratorCloseControlFlowScope): Deleted.
+ (JSC::BytecodeGenerator::popIteratorCloseControlFlowScope): Deleted.
+ (JSC::BytecodeGenerator::emitComplexPopScopes): Deleted.
+ (JSC::BytecodeGenerator::emitPopScopes): Deleted.
+ (JSC::BytecodeGenerator::popTryAndEmitCatch): Deleted.
+ * bytecompiler/BytecodeGenerator.h:
+ (JSC::FinallyJump::FinallyJump):
+ (JSC::FinallyContext::FinallyContext):
+ (JSC::FinallyContext::outerContext):
+ (JSC::FinallyContext::finallyLabel):
+ (JSC::FinallyContext::depth):
+ (JSC::FinallyContext::numberOfBreaksOrContinues):
+ (JSC::FinallyContext::incNumberOfBreaksOrContinues):
+ (JSC::FinallyContext::handlesReturns):
+ (JSC::FinallyContext::setHandlesReturns):
+ (JSC::FinallyContext::registerJump):
+ (JSC::FinallyContext::numberOfJumps):
+ (JSC::FinallyContext::jumps):
+ (JSC::ControlFlowScope::ControlFlowScope):
+ (JSC::ControlFlowScope::isLabelScope):
+ (JSC::ControlFlowScope::isFinallyScope):
+ (JSC::BytecodeGenerator::currentLexicalScopeIndex):
+ (JSC::BytecodeGenerator::FinallyRegistersScope::FinallyRegistersScope):
+ (JSC::BytecodeGenerator::FinallyRegistersScope::~FinallyRegistersScope):
+ (JSC::BytecodeGenerator::finallyActionRegister):
+ (JSC::BytecodeGenerator::finallyReturnValueRegister):
+ (JSC::BytecodeGenerator::emitSetFinallyActionToNormalCompletion):
+ (JSC::BytecodeGenerator::emitSetFinallyActionToReturnCompletion):
+ (JSC::BytecodeGenerator::emitSetFinallyActionToJumpID):
+ (JSC::BytecodeGenerator::emitSetFinallyReturnValueRegister):
+ (JSC::BytecodeGenerator::emitJumpIfFinallyActionIsNormalCompletion):
+ (JSC::BytecodeGenerator::emitJumpIfFinallyActionIsNotJump):
+ (JSC::BytecodeGenerator::emitJumpIfFinallyActionIsReturnCompletion):
+ (JSC::BytecodeGenerator::emitJumpIfFinallyActionIsNotReturnCompletion):
+ (JSC::BytecodeGenerator::emitJumpIfFinallyActionIsNotThrowCompletion):
+ (JSC::BytecodeGenerator::emitJumpIfCompletionTypeIsThrow):
+ (JSC::BytecodeGenerator::bytecodeOffsetToJumpID):
+ (JSC::BytecodeGenerator::isInFinallyBlock): Deleted.
+ * bytecompiler/NodesCodegen.cpp:
+ (JSC::ContinueNode::emitBytecode):
+ (JSC::BreakNode::emitBytecode):
+ (JSC::ReturnNode::emitBytecode):
+ (JSC::TryNode::emitBytecode):
+
2016-12-16 Keith Miller <keith_mil...@apple.com>
Add missing cases to parseUnreachableExpression and cleanup FunctionParser
Modified: trunk/Source/_javascript_Core/bytecode/HandlerInfo.h (209951 => 209952)
--- trunk/Source/_javascript_Core/bytecode/HandlerInfo.h 2016-12-17 00:48:31 UTC (rev 209951)
+++ trunk/Source/_javascript_Core/bytecode/HandlerInfo.h 2016-12-17 01:06:49 UTC (rev 209952)
@@ -31,9 +31,9 @@
namespace JSC {
enum class HandlerType {
- Illegal = 0,
- Catch = 1,
- Finally = 2,
+ Catch = 0,
+ Finally = 1,
+ SynthesizedCatch = 2,
SynthesizedFinally = 3
};
@@ -53,6 +53,8 @@
return "catch";
case HandlerType::Finally:
return "finally";
+ case HandlerType::SynthesizedCatch:
+ return "synthesized catch";
case HandlerType::SynthesizedFinally:
return "synthesized finally";
default:
Modified: trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp (209951 => 209952)
--- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp 2016-12-17 00:48:31 UTC (rev 209951)
+++ trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.cpp 2016-12-17 01:06:49 UTC (rev 209952)
@@ -156,7 +156,6 @@
if (end <= start)
continue;
- ASSERT(range.tryData->handlerType != HandlerType::Illegal);
UnlinkedHandlerInfo info(static_cast<uint32_t>(start), static_cast<uint32_t>(end),
static_cast<uint32_t>(range.tryData->target->bind()), range.tryData->handlerType);
m_codeBlock->addExceptionHandler(info);
@@ -680,21 +679,25 @@
// because a function's default parameter ExpressionNodes will use temporary registers.
pushTDZVariables(*parentScopeTDZVariables, TDZCheckOptimization::DoNotOptimize, TDZRequirement::UnderTDZ);
+ RefPtr<Label> catchLabel = newLabel();
TryData* tryFormalParametersData = nullptr;
- if (isAsyncFunctionWrapperParseMode(parseMode) && !isSimpleParameterList) {
+ bool needTryCatch = isAsyncFunctionWrapperParseMode(parseMode) && !isSimpleParameterList;
+ if (needTryCatch) {
RefPtr<Label> tryFormalParametersStart = emitLabel(newLabel().get());
- tryFormalParametersData = pushTry(tryFormalParametersStart.get());
+ tryFormalParametersData = pushTry(tryFormalParametersStart.get(), catchLabel.get(), HandlerType::SynthesizedCatch);
}
initializeDefaultParameterValuesAndSetupFunctionScopeStack(parameters, isSimpleParameterList, functionNode, functionSymbolTable, symbolTableConstantIndex, captures, shouldCreateArgumentsVariableInParameterScope);
- if (isAsyncFunctionWrapperParseMode(parseMode) && !isSimpleParameterList) {
+ if (needTryCatch) {
RefPtr<Label> didNotThrow = newLabel();
emitJump(didNotThrow.get());
- RefPtr<RegisterID> exception = newTemporary();
+ emitLabel(catchLabel.get());
+ popTry(tryFormalParametersData, catchLabel.get());
+
RefPtr<RegisterID> thrownValue = newTemporary();
- RefPtr<Label> catchHere = emitLabel(newLabel().get());
- popTryAndEmitCatch(tryFormalParametersData, exception.get(), thrownValue.get(), catchHere.get(), HandlerType::Catch);
+ RegisterID* unused = newTemporary();
+ emitCatch(unused, thrownValue.get());
// return promiseCapability.@reject(thrownValue)
RefPtr<RegisterID> reject = emitGetById(newTemporary(), promiseCapabilityRegister(), m_vm->propertyNames->builtinNames().rejectPrivateName());
@@ -3493,16 +3496,16 @@
}
}
-RegisterID* BytecodeGenerator::emitReturn(RegisterID* src)
+RegisterID* BytecodeGenerator::emitReturn(RegisterID* src, ReturnFrom from)
{
if (isConstructor()) {
bool mightBeDerived = constructorKind() == ConstructorKind::Extends;
bool srcIsThis = src->index() == m_thisRegister.index();
- if (mightBeDerived && srcIsThis)
+ if (mightBeDerived && (srcIsThis || from == ReturnFrom::Finally))
emitTDZCheck(src);
- if (!srcIsThis) {
+ if (!srcIsThis || from == ReturnFrom::Finally) {
RefPtr<Label> isObjectLabel = newLabel();
emitJumpIfTrue(emitIsObject(newTemporary(), src), isObjectLabel.get());
@@ -3680,82 +3683,31 @@
emitDebugHook(WillLeaveCallFrame, m_scopeNode->lastLine(), m_scopeNode->startOffset(), m_scopeNode->lineStartOffset());
}
-void BytecodeGenerator::pushFinallyControlFlowScope(StatementNode* finallyBlock)
+FinallyContext* BytecodeGenerator::pushFinallyControlFlowScope(Label* finallyLabel)
{
// Reclaim free label scopes.
while (m_labelScopes.size() && !m_labelScopes.last().refCount())
m_labelScopes.removeLast();
- ControlFlowScope scope;
- scope.isFinallyBlock = true;
- FinallyContext context = {
- finallyBlock,
- nullptr,
- nullptr,
- static_cast<unsigned>(m_controlFlowScopeStack.size()),
- static_cast<unsigned>(m_switchContextStack.size()),
- static_cast<unsigned>(m_forInContextStack.size()),
- static_cast<unsigned>(m_tryContextStack.size()),
- static_cast<unsigned>(m_labelScopes.size()),
- static_cast<unsigned>(m_lexicalScopeStack.size()),
- m_finallyDepth,
- m_localScopeDepth
- };
- scope.finallyContext = context;
- m_controlFlowScopeStack.append(scope);
- m_finallyDepth++;
-}
+ ControlFlowScope scope(ControlFlowScope::Finally, currentLexicalScopeIndex(), FinallyContext(m_currentFinallyContext, finallyLabel, m_finallyDepth));
+ m_controlFlowScopeStack.append(WTFMove(scope));
-void BytecodeGenerator::pushIteratorCloseControlFlowScope(RegisterID* iterator, ThrowableExpressionData* node)
-{
- // Reclaim free label scopes.
- while (m_labelScopes.size() && !m_labelScopes.last().refCount())
- m_labelScopes.removeLast();
-
- ControlFlowScope scope;
- scope.isFinallyBlock = true;
- FinallyContext context = {
- nullptr,
- iterator,
- node,
- static_cast<unsigned>(m_controlFlowScopeStack.size()),
- static_cast<unsigned>(m_switchContextStack.size()),
- static_cast<unsigned>(m_forInContextStack.size()),
- static_cast<unsigned>(m_tryContextStack.size()),
- static_cast<unsigned>(m_labelScopes.size()),
- static_cast<unsigned>(m_lexicalScopeStack.size()),
- m_finallyDepth,
- m_localScopeDepth
- };
- scope.finallyContext = context;
- m_controlFlowScopeStack.append(scope);
m_finallyDepth++;
+ m_currentFinallyContext = &m_controlFlowScopeStack.last().finallyContext;
+ return m_currentFinallyContext;
}
-void BytecodeGenerator::popFinallyControlFlowScope()
+FinallyContext BytecodeGenerator::popFinallyControlFlowScope()
{
ASSERT(m_controlFlowScopeStack.size());
- ASSERT(m_controlFlowScopeStack.last().isFinallyBlock);
- ASSERT(m_controlFlowScopeStack.last().finallyContext.finallyBlock);
- ASSERT(!m_controlFlowScopeStack.last().finallyContext.iterator);
- ASSERT(!m_controlFlowScopeStack.last().finallyContext.enumerationNode);
+ ASSERT(m_controlFlowScopeStack.last().isFinallyScope());
ASSERT(m_finallyDepth > 0);
- m_controlFlowScopeStack.removeLast();
+ ASSERT(m_currentFinallyContext);
+ m_currentFinallyContext = m_currentFinallyContext->outerContext();
m_finallyDepth--;
+ return m_controlFlowScopeStack.takeLast().finallyContext;
}
-void BytecodeGenerator::popIteratorCloseControlFlowScope()
-{
- ASSERT(m_controlFlowScopeStack.size());
- ASSERT(m_controlFlowScopeStack.last().isFinallyBlock);
- ASSERT(!m_controlFlowScopeStack.last().finallyContext.finallyBlock);
- ASSERT(m_controlFlowScopeStack.last().finallyContext.iterator);
- ASSERT(m_controlFlowScopeStack.last().finallyContext.enumerationNode);
- ASSERT(m_finallyDepth > 0);
- m_controlFlowScopeStack.removeLast();
- m_finallyDepth--;
-}
-
LabelScopePtr BytecodeGenerator::breakTarget(const Identifier& name)
{
// Reclaim free label scopes.
@@ -3853,162 +3805,12 @@
m_topMostScope = addVar();
emitMove(m_topMostScope, scopeRegister());
}
-
-void BytecodeGenerator::emitComplexPopScopes(RegisterID* scope, ControlFlowScope* topScope, ControlFlowScope* bottomScope)
-{
- while (topScope > bottomScope) {
- // First we count the number of dynamic scopes we need to remove to get
- // to a finally block.
- int numberOfNormalScopes = 0;
- while (topScope > bottomScope) {
- if (topScope->isFinallyBlock)
- break;
- ++numberOfNormalScopes;
- --topScope;
- }
- if (numberOfNormalScopes) {
- // We need to remove a number of dynamic scopes to get to the next
- // finally block
- RefPtr<RegisterID> parentScope = newTemporary();
- while (numberOfNormalScopes--) {
- parentScope = emitGetParentScope(parentScope.get(), scope);
- emitMove(scope, parentScope.get());
- }
-
- // If topScope == bottomScope then there isn't a finally block left to emit.
- if (topScope == bottomScope)
- return;
- }
-
- Vector<ControlFlowScope> savedControlFlowScopeStack;
- Vector<SwitchInfo> savedSwitchContextStack;
- Vector<RefPtr<ForInContext>> savedForInContextStack;
- Vector<TryContext> poppedTryContexts;
- Vector<LexicalScopeStackEntry> savedLexicalScopeStack;
- LabelScopeStore savedLabelScopes;
- while (topScope > bottomScope && topScope->isFinallyBlock) {
- RefPtr<Label> beforeFinally = emitLabel(newLabel().get());
-
- // Save the current state of the world while instating the state of the world
- // for the finally block.
- FinallyContext finallyContext = topScope->finallyContext;
- bool flipScopes = finallyContext.controlFlowScopeStackSize != m_controlFlowScopeStack.size();
- bool flipSwitches = finallyContext.switchContextStackSize != m_switchContextStack.size();
- bool flipForIns = finallyContext.forInContextStackSize != m_forInContextStack.size();
- bool flipTries = finallyContext.tryContextStackSize != m_tryContextStack.size();
- bool flipLabelScopes = finallyContext.labelScopesSize != m_labelScopes.size();
- bool flipLexicalScopeStack = finallyContext.lexicalScopeStackSize != m_lexicalScopeStack.size();
- int topScopeIndex = -1;
- int bottomScopeIndex = -1;
- if (flipScopes) {
- topScopeIndex = topScope - m_controlFlowScopeStack.begin();
- bottomScopeIndex = bottomScope - m_controlFlowScopeStack.begin();
- savedControlFlowScopeStack = m_controlFlowScopeStack;
- m_controlFlowScopeStack.shrink(finallyContext.controlFlowScopeStackSize);
- }
- if (flipSwitches) {
- savedSwitchContextStack = m_switchContextStack;
- m_switchContextStack.shrink(finallyContext.switchContextStackSize);
- }
- if (flipForIns) {
- savedForInContextStack = m_forInContextStack;
- m_forInContextStack.shrink(finallyContext.forInContextStackSize);
- }
- if (flipTries) {
- while (m_tryContextStack.size() != finallyContext.tryContextStackSize) {
- ASSERT(m_tryContextStack.size() > finallyContext.tryContextStackSize);
- TryContext context = m_tryContextStack.takeLast();
- TryRange range;
- range.start = context.start;
- range.end = beforeFinally;
- range.tryData = context.tryData;
- m_tryRanges.append(range);
- poppedTryContexts.append(context);
- }
- }
- if (flipLabelScopes) {
- savedLabelScopes = m_labelScopes;
- while (m_labelScopes.size() > finallyContext.labelScopesSize)
- m_labelScopes.removeLast();
- }
- if (flipLexicalScopeStack) {
- savedLexicalScopeStack = m_lexicalScopeStack;
- m_lexicalScopeStack.shrink(finallyContext.lexicalScopeStackSize);
- }
- int savedFinallyDepth = m_finallyDepth;
- m_finallyDepth = finallyContext.finallyDepth;
- int savedDynamicScopeDepth = m_localScopeDepth;
- m_localScopeDepth = finallyContext.dynamicScopeDepth;
-
- if (finallyContext.finallyBlock) {
- // Emit the finally block.
- emitNode(finallyContext.finallyBlock);
- } else {
- // Emit the IteratorClose block.
- ASSERT(finallyContext.iterator);
- emitIteratorClose(finallyContext.iterator, finallyContext.enumerationNode);
- }
-
- RefPtr<Label> afterFinally = emitLabel(newLabel().get());
-
- // Restore the state of the world.
- if (flipScopes) {
- m_controlFlowScopeStack = savedControlFlowScopeStack;
- topScope = &m_controlFlowScopeStack[topScopeIndex]; // assert it's within bounds
- bottomScope = m_controlFlowScopeStack.begin() + bottomScopeIndex; // don't assert, since it the index might be -1.
- }
- if (flipSwitches)
- m_switchContextStack = savedSwitchContextStack;
- if (flipForIns)
- m_forInContextStack = savedForInContextStack;
- if (flipTries) {
- ASSERT(m_tryContextStack.size() == finallyContext.tryContextStackSize);
- for (unsigned i = poppedTryContexts.size(); i--;) {
- TryContext context = poppedTryContexts[i];
- context.start = afterFinally;
- m_tryContextStack.append(context);
- }
- poppedTryContexts.clear();
- }
- if (flipLabelScopes)
- m_labelScopes = savedLabelScopes;
- if (flipLexicalScopeStack)
- m_lexicalScopeStack = savedLexicalScopeStack;
- m_finallyDepth = savedFinallyDepth;
- m_localScopeDepth = savedDynamicScopeDepth;
-
- --topScope;
- }
- }
-}
-
-void BytecodeGenerator::emitPopScopes(RegisterID* scope, int targetScopeDepth)
+TryData* BytecodeGenerator::pushTry(Label* start, Label* handlerLabel, HandlerType handlerType)
{
- ASSERT(labelScopeDepth() - targetScopeDepth >= 0);
-
- size_t scopeDelta = labelScopeDepth() - targetScopeDepth;
- ASSERT(scopeDelta <= m_controlFlowScopeStack.size());
- if (!scopeDelta)
- return;
-
- if (!m_finallyDepth) {
- RefPtr<RegisterID> parentScope = newTemporary();
- while (scopeDelta--) {
- parentScope = emitGetParentScope(parentScope.get(), scope);
- emitMove(scope, parentScope.get());
- }
- return;
- }
-
- emitComplexPopScopes(scope, &m_controlFlowScopeStack.last(), &m_controlFlowScopeStack.last() - scopeDelta);
-}
-
-TryData* BytecodeGenerator::pushTry(Label* start)
-{
TryData tryData;
- tryData.target = newLabel();
- tryData.handlerType = HandlerType::Illegal;
+ tryData.target = handlerLabel;
+ tryData.handlerType = handlerType;
m_tryData.append(tryData);
TryData* result = &m_tryData.last();
@@ -4021,7 +3823,7 @@
return result;
}
-void BytecodeGenerator::popTryAndEmitCatch(TryData* tryData, RegisterID* exceptionRegister, RegisterID* thrownValueRegister, Label* end, HandlerType handlerType)
+void BytecodeGenerator::popTry(TryData* tryData, Label* end)
{
m_usesExceptions = true;
@@ -4033,28 +3835,52 @@
tryRange.tryData = m_tryContextStack.last().tryData;
m_tryRanges.append(tryRange);
m_tryContextStack.removeLast();
-
- emitLabel(tryRange.tryData->target.get());
- tryRange.tryData->handlerType = handlerType;
+}
+void BytecodeGenerator::emitCatch(RegisterID* exceptionRegister, RegisterID* thrownValueRegister)
+{
emitOpcode(op_catch);
instructions().append(exceptionRegister->index());
instructions().append(thrownValueRegister->index());
+}
- bool foundLocalScope = false;
- for (unsigned i = m_lexicalScopeStack.size(); i--; ) {
- // Note that if we don't find a local scope in the current function/program,
- // we must grab the outer-most scope of this bytecode generation.
- if (m_lexicalScopeStack[i].m_scope) {
- foundLocalScope = true;
- emitMove(scopeRegister(), m_lexicalScopeStack[i].m_scope);
- break;
+void BytecodeGenerator::restoreScopeRegister(int lexicalScopeIndex)
+{
+ if (lexicalScopeIndex == CurrentLexicalScopeIndex)
+ return; // No change needed.
+
+ if (lexicalScopeIndex != OutermostLexicalScopeIndex) {
+ ASSERT(lexicalScopeIndex < static_cast<int>(m_lexicalScopeStack.size()));
+ int endIndex = lexicalScopeIndex + 1;
+ for (size_t i = endIndex; i--; ) {
+ if (m_lexicalScopeStack[i].m_scope) {
+ emitMove(scopeRegister(), m_lexicalScopeStack[i].m_scope);
+ return;
+ }
}
}
- if (!foundLocalScope)
- emitMove(scopeRegister(), m_topMostScope);
+ // Note that if we don't find a local scope in the current function/program,
+ // we must grab the outer-most scope of this bytecode generation.
+ emitMove(scopeRegister(), m_topMostScope);
}
+void BytecodeGenerator::restoreScopeRegister()
+{
+ restoreScopeRegister(currentLexicalScopeIndex());
+}
+
+int BytecodeGenerator::labelScopeDepthToLexicalScopeIndex(int targetLabelScopeDepth)
+{
+ ASSERT(labelScopeDepth() - targetLabelScopeDepth >= 0);
+ size_t scopeDelta = labelScopeDepth() - targetLabelScopeDepth;
+ ASSERT(scopeDelta <= m_controlFlowScopeStack.size());
+ if (!scopeDelta)
+ return CurrentLexicalScopeIndex;
+
+ ControlFlowScope& targetScope = m_controlFlowScopeStack[targetLabelScopeDepth];
+ return targetScope.lexicalScopeIndex;
+}
+
int BytecodeGenerator::localScopeDepth() const
{
return m_localScopeDepth;
@@ -4061,8 +3887,10 @@
}
int BytecodeGenerator::labelScopeDepth() const
-{
- return localScopeDepth() + m_finallyDepth;
+{
+ int depth = localScopeDepth() + m_finallyDepth;
+ ASSERT(depth == static_cast<int>(m_controlFlowScopeStack.size()));
+ return depth;
}
void BytecodeGenerator::emitThrowStaticError(ErrorType errorType, RegisterID* raw)
@@ -4132,9 +3960,8 @@
void BytecodeGenerator::pushLocalControlFlowScope()
{
- ControlFlowScope scope;
- scope.isFinallyBlock = false;
- m_controlFlowScopeStack.append(scope);
+ ControlFlowScope scope(ControlFlowScope::Label, currentLexicalScopeIndex());
+ m_controlFlowScopeStack.append(WTFMove(scope));
m_localScopeDepth++;
}
@@ -4141,7 +3968,7 @@
void BytecodeGenerator::popLocalControlFlowScope()
{
ASSERT(m_controlFlowScopeStack.size());
- ASSERT(!m_controlFlowScopeStack.last().isFinallyBlock);
+ ASSERT(!m_controlFlowScopeStack.last().isFinallyScope());
m_controlFlowScopeStack.removeLast();
m_localScopeDepth--;
}
@@ -4296,9 +4123,11 @@
}
return false;
}
-
+
void BytecodeGenerator::emitEnumeration(ThrowableExpressionData* node, ExpressionNode* subjectNode, const std::function<void(BytecodeGenerator&, RegisterID*)>& callBack, ForOfNode* forLoopNode, RegisterID* forLoopSymbolTable)
{
+ FinallyRegistersScope finallyRegistersScope(*this);
+
RefPtr<RegisterID> subject = newTemporary();
emitNode(subject.get(), subjectNode);
RefPtr<RegisterID> iterator = emitGetById(newTemporary(), subject.get(), propertyNames().iteratorSymbol);
@@ -4309,8 +4138,15 @@
}
RefPtr<Label> loopDone = newLabel();
+ RefPtr<Label> tryStartLabel = newLabel();
+ RefPtr<Label> finallyViaThrowLabel = newLabel();
+ RefPtr<Label> finallyLabel = newLabel();
+ RefPtr<Label> catchLabel = newLabel();
+ RefPtr<Label> endCatchLabel = newLabel();
+
// RefPtr<Register> iterator's lifetime must be longer than IteratorCloseContext.
- pushIteratorCloseControlFlowScope(iterator.get(), node);
+ FinallyContext* finallyContext = pushFinallyControlFlowScope(finallyLabel.get());
+
{
LabelScopePtr scope = newLabelScope(LabelScope::Loop);
RefPtr<RegisterID> value = newTemporary();
@@ -4322,40 +4158,68 @@
emitLabel(loopStart.get());
emitLoopHint();
- RefPtr<Label> tryStartLabel = newLabel();
emitLabel(tryStartLabel.get());
- TryData* tryData = pushTry(tryStartLabel.get());
+ TryData* tryData = pushTry(tryStartLabel.get(), finallyViaThrowLabel.get(), HandlerType::SynthesizedFinally);
callBack(*this, value.get());
emitJump(scope->continueTarget());
- // IteratorClose sequence for throw-ed control flow.
+ // IteratorClose sequence for abrupt completions.
{
- RefPtr<Label> catchHere = emitLabel(newLabel().get());
- RefPtr<RegisterID> exceptionRegister = newTemporary();
- RefPtr<RegisterID> thrownValueRegister = newTemporary();
- popTryAndEmitCatch(tryData, exceptionRegister.get(),
- thrownValueRegister.get(), catchHere.get(), HandlerType::SynthesizedFinally);
+ // Finally block for the enumeration.
+ emitLabel(finallyViaThrowLabel.get());
+ popTry(tryData, finallyViaThrowLabel.get());
- RefPtr<Label> catchDone = newLabel();
+ RegisterID* unused = newTemporary();
+ emitCatch(finallyActionRegister(), unused);
+ // Setting the finallyActionRegister to the caught exception here implies CompletionType::Throw.
+ emitLabel(finallyLabel.get());
+ restoreScopeRegister();
+
+ RefPtr<Label> finallyDone = newLabel();
+
RefPtr<RegisterID> returnMethod = emitGetById(newTemporary(), iterator.get(), propertyNames().returnKeyword);
- emitJumpIfTrue(emitIsUndefined(newTemporary(), returnMethod.get()), catchDone.get());
+ emitJumpIfTrue(emitIsUndefined(newTemporary(), returnMethod.get()), finallyDone.get());
+ RefPtr<RegisterID> originalFinallyActionRegister = newTemporary();
+ emitMove(originalFinallyActionRegister.get(), finallyActionRegister());
+
RefPtr<Label> returnCallTryStart = newLabel();
emitLabel(returnCallTryStart.get());
- TryData* returnCallTryData = pushTry(returnCallTryStart.get());
+ TryData* returnCallTryData = pushTry(returnCallTryStart.get(), catchLabel.get(), HandlerType::SynthesizedCatch);
CallArguments returnArguments(*this, nullptr);
emitMove(returnArguments.thisRegister(), iterator.get());
emitCall(value.get(), returnMethod.get(), NoExpectedFunction, returnArguments, node->divot(), node->divotStart(), node->divotEnd(), DebuggableCall::No);
+ emitJumpIfTrue(emitIsObject(newTemporary(), value.get()), finallyDone.get());
+ emitThrowTypeError(ASCIILiteral("Iterator result interface is not an object."));
- emitLabel(catchDone.get());
- emitThrow(exceptionRegister.get());
+ emitLabel(finallyDone.get());
+ emitFinallyCompletion(*finallyContext, endCatchLabel.get());
- // Absorb exception.
- popTryAndEmitCatch(returnCallTryData, newTemporary(),
- newTemporary(), catchDone.get(), HandlerType::SynthesizedFinally);
- emitThrow(exceptionRegister.get());
+ popTry(returnCallTryData, finallyDone.get());
+
+ // Catch block for exceptions that may be thrown while calling the return
+ // handler in the enumeration finally block. The only reason we need this
+ // catch block is because if entered the above finally block due to a thrown
+ // exception, then we want to re-throw the original exception on exiting
+ // the finally block. Otherwise, we'll let any new exception pass through.
+ {
+ emitLabel(catchLabel.get());
+ RefPtr<RegisterID> exceptionRegister = newTemporary();
+ RegisterID* unused = newTemporary();
+ emitCatch(exceptionRegister.get(), unused);
+ restoreScopeRegister();
+
+ RefPtr<Label> throwLabel = newLabel();
+ emitJumpIfCompletionTypeIsThrow(originalFinallyActionRegister.get(), throwLabel.get());
+ emitMove(originalFinallyActionRegister.get(), exceptionRegister.get());
+
+ emitLabel(throwLabel.get());
+ emitThrow(originalFinallyActionRegister.get());
+
+ emitLabel(endCatchLabel.get());
+ }
}
emitLabel(scope->continueTarget());
@@ -4376,7 +4240,7 @@
}
// IteratorClose sequence for break-ed control flow.
- popIteratorCloseControlFlowScope();
+ popFinallyControlFlowScope();
emitIteratorClose(iterator.get(), node);
emitLabel(loopDone.get());
}
@@ -4497,6 +4361,14 @@
return dst;
}
+RegisterID* BytecodeGenerator::emitIsNumber(RegisterID* dst, RegisterID* src)
+{
+ emitOpcode(op_is_number);
+ instructions().append(dst->index());
+ instructions().append(src->index());
+ return dst;
+}
+
RegisterID* BytecodeGenerator::emitIsUndefined(RegisterID* dst, RegisterID* src)
{
emitOpcode(op_is_undefined);
@@ -4784,11 +4656,9 @@
// Return.
{
RefPtr<RegisterID> returnRegister = generatorValueRegister();
- if (isInFinallyBlock()) {
- returnRegister = emitMove(newTemporary(), returnRegister.get());
- emitPopScopes(scopeRegister(), 0);
- }
- emitReturn(returnRegister.get());
+ bool hasFinally = emitReturnViaFinallyIfNeeded(returnRegister.get());
+ if (!hasFinally)
+ emitReturn(returnRegister.get());
}
// Throw.
@@ -4891,9 +4761,9 @@
emitGetById(value.get(), value.get(), propertyNames().value);
emitLabel(returnSequence.get());
- if (isInFinallyBlock())
- emitPopScopes(scopeRegister(), 0);
- emitReturn(value.get());
+ bool hasFinally = emitReturnViaFinallyIfNeeded(value.get());
+ if (!hasFinally)
+ emitReturn(value.get());
}
// Normal.
@@ -4923,6 +4793,157 @@
emitPutById(generatorRegister(), propertyNames().builtinNames().generatorStatePrivateName(), completedState);
}
+bool BytecodeGenerator::emitJumpViaFinallyIfNeeded(int targetLabelScopeDepth, Label* jumpTarget)
+{
+ ASSERT(labelScopeDepth() - targetLabelScopeDepth >= 0);
+ size_t scopeDelta = labelScopeDepth() - targetLabelScopeDepth;
+ ASSERT(scopeDelta <= m_controlFlowScopeStack.size());
+ if (!scopeDelta)
+ return nullptr; // No finallys to thread through.
+
+ ControlFlowScope* topScope = &m_controlFlowScopeStack.last();
+ ControlFlowScope* bottomScope = &m_controlFlowScopeStack.last() - scopeDelta;
+
+ FinallyContext* innermostFinallyContext = nullptr;
+ FinallyContext* outermostFinallyContext = nullptr;
+ while (topScope > bottomScope) {
+ if (topScope->isFinallyScope()) {
+ FinallyContext* finallyContext = &topScope->finallyContext;
+ if (!innermostFinallyContext)
+ innermostFinallyContext = finallyContext;
+ outermostFinallyContext = finallyContext;
+ finallyContext->incNumberOfBreaksOrContinues();
+ }
+ --topScope;
+ }
+ if (!outermostFinallyContext)
+ return false; // No finallys to thread through.
+
+ int jumpID = bytecodeOffsetToJumpID(instructions().size());
+ int lexicalScopeIndex = labelScopeDepthToLexicalScopeIndex(targetLabelScopeDepth);
+ outermostFinallyContext->registerJump(jumpID, lexicalScopeIndex, jumpTarget);
+
+ emitSetFinallyActionToJumpID(jumpID);
+ emitJump(innermostFinallyContext->finallyLabel());
+ return true; // We'll be jumping to a finally block.
+}
+
+bool BytecodeGenerator::emitReturnViaFinallyIfNeeded(RegisterID* returnRegister)
+{
+ if (!m_controlFlowScopeStack.size())
+ return false; // No finallys to thread through.
+
+ ControlFlowScope* topScope = &m_controlFlowScopeStack.last();
+ ControlFlowScope* bottomScope = &m_controlFlowScopeStack.first();
+
+ FinallyContext* innermostFinallyContext = nullptr;
+ while (topScope >= bottomScope) {
+ if (topScope->isFinallyScope()) {
+ FinallyContext* finallyContext = &topScope->finallyContext;
+ if (!innermostFinallyContext)
+ innermostFinallyContext = finallyContext;
+ finallyContext->setHandlesReturns();
+ }
+ --topScope;
+ }
+ if (!innermostFinallyContext)
+ return false; // No finallys to thread through.
+
+ emitSetFinallyActionToReturnCompletion();
+ emitSetFinallyReturnValueRegister(returnRegister);
+ emitJump(innermostFinallyContext->finallyLabel());
+ return true; // We'll be jumping to a finally block.
+}
+
+void BytecodeGenerator::emitFinallyCompletion(FinallyContext& context, Label* normalCompletionLabel)
+{
+ // FIXME: switch the finallyActionRegister to only store int values for all CompletionTypes. This is more optimal for JIT type speculation.
+ // https://bugs.webkit.org/show_bug.cgi?id=165979
+ emitJumpIfFinallyActionIsNormalCompletion(normalCompletionLabel);
+
+ if (context.numberOfBreaksOrContinues() || context.handlesReturns()) {
+ FinallyContext* outerContext = context.outerContext();
+ if (outerContext) {
+ // We are not the outermost finally.
+ size_t numberOfJumps = context.numberOfJumps();
+ for (size_t i = 0; i < numberOfJumps; i++) {
+ RefPtr<Label> nextLabel = newLabel();
+ auto& jump = context.jumps(i);
+ emitJumpIfFinallyActionIsNotJump(jump.jumpID, nextLabel.get());
+
+ restoreScopeRegister(jump.targetLexicalScopeIndex);
+ emitSetFinallyActionToNormalCompletion();
+ emitJump(jump.targetLabel.get());
+
+ emitLabel(nextLabel.get());
+ }
+
+ bool hasBreaksOrContinuesNotCoveredByJumps = context.numberOfBreaksOrContinues() > numberOfJumps;
+ if (hasBreaksOrContinuesNotCoveredByJumps || context.handlesReturns())
+ emitJumpIfFinallyActionIsNotThrowCompletion(outerContext->finallyLabel());
+
+ } else {
+ // We are the outermost finally.
+ size_t numberOfJumps = context.numberOfJumps();
+ ASSERT(numberOfJumps == context.numberOfBreaksOrContinues());
+
+ for (size_t i = 0; i < numberOfJumps; i++) {
+ RefPtr<Label> nextLabel = newLabel();
+ auto& jump = context.jumps(i);
+ emitJumpIfFinallyActionIsNotJump(jump.jumpID, nextLabel.get());
+
+ restoreScopeRegister(jump.targetLexicalScopeIndex);
+ emitSetFinallyActionToNormalCompletion();
+ emitJump(jump.targetLabel.get());
+
+ emitLabel(nextLabel.get());
+ }
+
+ if (context.handlesReturns()) {
+ RefPtr<Label> notReturnLabel = newLabel();
+ emitJumpIfFinallyActionIsNotReturnCompletion(notReturnLabel.get());
+
+ emitWillLeaveCallFrameDebugHook();
+ emitReturn(finallyReturnValueRegister(), ReturnFrom::Finally);
+
+ emitLabel(notReturnLabel.get());
+ }
+ }
+ }
+ emitThrow(finallyActionRegister());
+}
+
+bool BytecodeGenerator::allocateFinallyRegisters()
+{
+ if (m_finallyActionRegister)
+ return false;
+
+ ASSERT(!m_finallyReturnValueRegister);
+ m_finallyActionRegister = newTemporary();
+ m_finallyReturnValueRegister = newTemporary();
+
+ emitSetFinallyActionToNormalCompletion();
+ emitMoveEmptyValue(m_finallyReturnValueRegister.get());
+ return true;
+}
+
+void BytecodeGenerator::releaseFinallyRegisters()
+{
+ ASSERT(m_finallyActionRegister && m_finallyReturnValueRegister);
+ m_finallyActionRegister = nullptr;
+ m_finallyReturnValueRegister = nullptr;
+}
+
+void BytecodeGenerator::emitCompareFinallyActionAndJumpIf(OpcodeID compareOpcode, int value, Label* jumpTarget)
+{
+ RefPtr<RegisterID> tempRegister = newTemporary();
+ RegisterID* valueConstant = addConstantValue(JSValue(value));
+ OperandTypes operandTypes = OperandTypes(ResultType::numberTypeIsInt32(), ResultType::unknownType());
+
+ auto equivalenceResult = emitBinaryOp(compareOpcode, tempRegister.get(), valueConstant, finallyActionRegister(), operandTypes);
+ emitJumpIfTrue(equivalenceResult, jumpTarget);
+}
+
} // namespace JSC
namespace WTF {
Modified: trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h (209951 => 209952)
--- trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h 2016-12-17 00:48:31 UTC (rev 209951)
+++ trunk/Source/_javascript_Core/bytecompiler/BytecodeGenerator.h 2016-12-17 01:06:49 UTC (rev 209952)
@@ -80,22 +80,78 @@
unsigned m_padding;
};
+ struct FinallyJump {
+ FinallyJump(int jumpID, int targetLexicalScopeIndex, Label* targetLabel)
+ : jumpID(jumpID)
+ , targetLexicalScopeIndex(targetLexicalScopeIndex)
+ , targetLabel(targetLabel)
+ { }
+
+ int jumpID { 0 };
+ int targetLexicalScopeIndex { 0 };
+ RefPtr<Label> targetLabel;
+ };
+
struct FinallyContext {
- StatementNode* finallyBlock;
- RegisterID* iterator;
- ThrowableExpressionData* enumerationNode;
- unsigned controlFlowScopeStackSize;
- unsigned switchContextStackSize;
- unsigned forInContextStackSize;
- unsigned tryContextStackSize;
- unsigned labelScopesSize;
- unsigned lexicalScopeStackSize;
- int finallyDepth;
- int dynamicScopeDepth;
+ FinallyContext() { }
+ FinallyContext(FinallyContext* outerContext, Label* finallyLabel, int finallyDepth)
+ : m_outerContext(outerContext)
+ , m_finallyLabel(finallyLabel)
+ , m_finallyDepth(finallyDepth)
+ {
+ ASSERT(!m_jumps || m_jumps->isEmpty());
+ }
+
+ FinallyContext* outerContext() const { return m_outerContext; }
+ Label* finallyLabel() const { return m_finallyLabel; }
+ int depth() const { return m_finallyDepth; }
+
+ unsigned numberOfBreaksOrContinues() const { return m_numberOfBreaksOrContinues; }
+ void incNumberOfBreaksOrContinues()
+ {
+ ASSERT(m_numberOfBreaksOrContinues < INT_MAX);
+ m_numberOfBreaksOrContinues++;
+ }
+
+ bool handlesReturns() const { return m_handlesReturns; }
+ void setHandlesReturns() { m_handlesReturns = true; }
+
+ void registerJump(int jumpID, int lexicalScopeIndex, Label* targetLabel)
+ {
+ if (!m_jumps)
+ m_jumps = std::make_unique<Vector<FinallyJump>>();
+ m_jumps->append(FinallyJump(jumpID, lexicalScopeIndex, targetLabel));
+ }
+
+ size_t numberOfJumps() const { return m_jumps ? m_jumps->size() : 0; }
+ FinallyJump& jumps(size_t i) { return (*m_jumps)[i]; }
+
+ private:
+ FinallyContext* m_outerContext { nullptr };
+ Label* m_finallyLabel { nullptr };
+ int m_finallyDepth { 0 };
+ unsigned m_numberOfBreaksOrContinues { 0 };
+ bool m_handlesReturns { false };
+ std::unique_ptr<Vector<FinallyJump>> m_jumps;
};
struct ControlFlowScope {
- bool isFinallyBlock;
+ typedef uint8_t Type;
+ enum {
+ Label,
+ Finally
+ };
+ ControlFlowScope(Type type, int lexicalScopeIndex, FinallyContext&& finallyContext = FinallyContext())
+ : type(type)
+ , lexicalScopeIndex(lexicalScopeIndex)
+ , finallyContext(std::forward<FinallyContext>(finallyContext))
+ { }
+
+ bool isLabelScope() const { return type == Label; }
+ bool isFinallyScope() const { return type == Finally; }
+
+ Type type;
+ int lexicalScopeIndex;
FinallyContext finallyContext;
};
@@ -605,7 +661,8 @@
RegisterID* emitGetTemplateObject(RegisterID* dst, TaggedTemplateNode*);
- RegisterID* emitReturn(RegisterID* src);
+ enum class ReturnFrom { Normal, Finally };
+ RegisterID* emitReturn(RegisterID* src, ReturnFrom = ReturnFrom::Normal);
RegisterID* emitEnd(RegisterID* src) { return emitUnaryNoDstOp(op_end, src); }
RegisterID* emitConstruct(RegisterID* dst, RegisterID* func, ExpectedFunction, CallArguments&, const JSTextPosition& divot, const JSTextPosition& divotStart, const JSTextPosition& divotEnd);
@@ -626,7 +683,6 @@
PassRefPtr<Label> emitJumpIfFalse(RegisterID* cond, Label* target);
PassRefPtr<Label> emitJumpIfNotFunctionCall(RegisterID* cond, Label* target);
PassRefPtr<Label> emitJumpIfNotFunctionApply(RegisterID* cond, Label* target);
- void emitPopScopes(RegisterID* srcDst, int targetScopeDepth);
void emitEnter();
void emitWatchdog();
@@ -649,6 +705,7 @@
RegisterID* emitIsMap(RegisterID* dst, RegisterID* src) { return emitIsCellWithType(dst, src, JSMapType); }
RegisterID* emitIsSet(RegisterID* dst, RegisterID* src) { return emitIsCellWithType(dst, src, JSSetType); }
RegisterID* emitIsObject(RegisterID* dst, RegisterID* src);
+ RegisterID* emitIsNumber(RegisterID* dst, RegisterID* src);
RegisterID* emitIsUndefined(RegisterID* dst, RegisterID* src);
RegisterID* emitIsEmpty(RegisterID* dst, RegisterID* src);
RegisterID* emitIsDerivedArray(RegisterID* dst, RegisterID* src) { return emitIsCellWithType(dst, src, DerivedArrayType); }
@@ -663,10 +720,30 @@
bool emitReadOnlyExceptionIfNeeded(const Variable&);
// Start a try block. 'start' must have been emitted.
- TryData* pushTry(Label* start);
+ TryData* pushTry(Label* start, Label* handlerLabel, HandlerType);
// End a try block. 'end' must have been emitted.
- void popTryAndEmitCatch(TryData*, RegisterID* exceptionRegister, RegisterID* thrownValueRegister, Label* end, HandlerType);
+ void popTry(TryData*, Label* end);
+ void emitCatch(RegisterID* exceptionRegister, RegisterID* thrownValueRegister);
+ private:
+ static const int CurrentLexicalScopeIndex = -2;
+ static const int OutermostLexicalScopeIndex = -1;
+
+ public:
+ void restoreScopeRegister();
+ void restoreScopeRegister(int lexicalScopeIndex);
+
+ int currentLexicalScopeIndex() const
+ {
+ int size = static_cast<int>(m_lexicalScopeStack.size());
+ ASSERT(static_cast<size_t>(size) == m_lexicalScopeStack.size());
+ ASSERT(size >= 0);
+ int index = size - 1;
+ return index;
+ }
+
+ int labelScopeDepthToLexicalScopeIndex(int labelScopeDepth);
+
void emitThrow(RegisterID* exc)
{
m_usesExceptions = true;
@@ -698,13 +775,102 @@
void emitDebugHook(ExpressionNode*);
void emitWillLeaveCallFrameDebugHook();
- bool isInFinallyBlock() { return m_finallyDepth > 0; }
+ class FinallyRegistersScope {
+ public:
+ FinallyRegistersScope(BytecodeGenerator& generator, bool needFinallyRegisters = true)
+ : m_generator(generator)
+ {
+ if (needFinallyRegisters && m_generator.allocateFinallyRegisters())
+ m_needToReleaseOnDestruction = true;
+ }
+ ~FinallyRegistersScope()
+ {
+ if (m_needToReleaseOnDestruction)
+ m_generator.releaseFinallyRegisters();
+ }
- void pushFinallyControlFlowScope(StatementNode* finallyBlock);
- void popFinallyControlFlowScope();
- void pushIteratorCloseControlFlowScope(RegisterID* iterator, ThrowableExpressionData* enumerationNode);
- void popIteratorCloseControlFlowScope();
+ private:
+ BytecodeGenerator& m_generator;
+ bool m_needToReleaseOnDestruction { false };
+ };
+ RegisterID* finallyActionRegister() const
+ {
+ ASSERT(m_finallyActionRegister);
+ return m_finallyActionRegister.get();
+ }
+ RegisterID* finallyReturnValueRegister() const
+ {
+ ASSERT(m_finallyReturnValueRegister);
+ return m_finallyReturnValueRegister.get();
+ }
+
+ void emitSetFinallyActionToNormalCompletion()
+ {
+ emitMoveEmptyValue(m_finallyActionRegister.get());
+ }
+ void emitSetFinallyActionToReturnCompletion()
+ {
+ emitLoad(finallyActionRegister(), JSValue(static_cast<int>(CompletionType::Return)));
+ }
+ void emitSetFinallyActionToJumpID(int jumpID)
+ {
+ emitLoad(finallyActionRegister(), JSValue(jumpID));
+ }
+ void emitSetFinallyReturnValueRegister(RegisterID* reg)
+ {
+ emitMove(finallyReturnValueRegister(), reg);
+ }
+
+ void emitJumpIfFinallyActionIsNormalCompletion(Label* jumpTarget)
+ {
+ emitJumpIfTrue(emitIsEmpty(newTemporary(), finallyActionRegister()), jumpTarget);
+ }
+
+ void emitJumpIfFinallyActionIsNotJump(int jumpID, Label* jumpTarget)
+ {
+ emitCompareFinallyActionAndJumpIf(op_nstricteq, jumpID, jumpTarget);
+ }
+
+ void emitJumpIfFinallyActionIsReturnCompletion(Label* jumpTarget)
+ {
+ emitCompareFinallyActionAndJumpIf(op_stricteq, static_cast<int>(CompletionType::Return), jumpTarget);
+ }
+ void emitJumpIfFinallyActionIsNotReturnCompletion(Label* jumpTarget)
+ {
+ emitCompareFinallyActionAndJumpIf(op_nstricteq, static_cast<int>(CompletionType::Return), jumpTarget);
+ }
+
+ void emitJumpIfFinallyActionIsNotThrowCompletion(Label* jumpTarget)
+ {
+ emitJumpIfTrue(emitIsNumber(newTemporary(), finallyActionRegister()), jumpTarget);
+ }
+ void emitJumpIfCompletionTypeIsThrow(RegisterID* reg, Label* jumpTarget)
+ {
+ emitJumpIfFalse(emitIsNumber(newTemporary(), reg), jumpTarget);
+ }
+
+ bool emitJumpViaFinallyIfNeeded(int targetLabelScopeDepth, Label* jumpTarget);
+ bool emitReturnViaFinallyIfNeeded(RegisterID* returnRegister);
+ void emitFinallyCompletion(FinallyContext&, Label* normalCompletionLabel);
+
+ private:
+ void emitCompareFinallyActionAndJumpIf(OpcodeID compareOpcode, int value, Label* jumpTarget);
+
+ int bytecodeOffsetToJumpID(unsigned offset)
+ {
+ int jumpID = offset + static_cast<int>(CompletionType::NumberOfTypes);
+ ASSERT(jumpID >= static_cast<int>(CompletionType::NumberOfTypes));
+ return jumpID;
+ }
+
+ bool allocateFinallyRegisters();
+ void releaseFinallyRegisters();
+
+ public:
+ FinallyContext* pushFinallyControlFlowScope(Label* finallyLabel);
+ FinallyContext popFinallyControlFlowScope();
+
void pushIndexedForInScope(RegisterID* local, RegisterID* index);
void popIndexedForInScope(RegisterID* local);
void pushStructureForInScope(RegisterID* local, RegisterID* index, RegisterID* property, RegisterID* enumerator);
@@ -797,7 +963,6 @@
void allocateCalleeSaveSpace();
void allocateAndEmitScope();
- void emitComplexPopScopes(RegisterID*, ControlFlowScope* topScope, ControlFlowScope* bottomScope);
typedef HashMap<double, JSValue> NumberMap;
typedef HashMap<UniquedStringImpl*, JSString*, IdentifierRepHash> IdentifierStringMap;
@@ -935,6 +1100,35 @@
RegisterID* m_arrowFunctionContextLexicalEnvironmentRegister { nullptr };
RegisterID* m_promiseCapabilityRegister { nullptr };
+ // The spec at https://tc39.github.io/ecma262/#sec-completion-record-specification-type says
+ // that there are 5 types of completions. Conceptually, we'll set m_finallyActionRegister
+ // to one of these completion types. However, to optimize our implementation, we'll encode
+ // these type info as follows:
+ //
+ // CompletionType::Normal - m_finallyActionRegister is empty.
+ // CompletionType::Break - m_finallyActionRegister is an int JSValue jumpID.
+ // CompletionType::Continue - m_finallyActionRegister is an int JSValue jumpID.
+ // CompletionType::Return - m_finallyActionRegister is the Return enum as an int JSValue.
+ // CompletionType::Throw - m_finallyActionRegister is the Exception object to rethrow.
+ //
+ // Hence, of the 5 completion types, only the CompletionType::Return enum value is used in
+ // our implementation. The rest are just provided for completeness.
+
+ enum class CompletionType : int {
+ Normal,
+ Break,
+ Continue,
+ Return,
+ Throw,
+
+ NumberOfTypes
+ };
+
+ RefPtr<RegisterID> m_finallyActionRegister;
+ RefPtr<RegisterID> m_finallyReturnValueRegister;
+
+ FinallyContext* m_currentFinallyContext { nullptr };
+
SegmentedVector<RegisterID*, 16> m_localRegistersForCalleeSaveRegisters;
SegmentedVector<RegisterID, 32> m_constantPoolRegisters;
SegmentedVector<RegisterID, 32> m_calleeLocals;
@@ -949,7 +1143,9 @@
void pushLocalControlFlowScope();
void popLocalControlFlowScope();
- Vector<ControlFlowScope, 0, UnsafeVectorOverflow> m_controlFlowScopeStack;
+ // FIXME: Restore overflow checking with UnsafeVectorOverflow once SegmentVector supports it.
+ // https://bugs.webkit.org/show_bug.cgi?id=165980
+ SegmentedVector<ControlFlowScope, 16> m_controlFlowScopeStack;
Vector<SwitchInfo> m_switchContextStack;
Vector<RefPtr<ForInContext>> m_forInContextStack;
Vector<TryContext> m_tryContextStack;
Modified: trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp (209951 => 209952)
--- trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp 2016-12-17 00:48:31 UTC (rev 209951)
+++ trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp 2016-12-17 01:06:49 UTC (rev 209952)
@@ -2998,8 +2998,12 @@
LabelScopePtr scope = generator.continueTarget(m_ident);
ASSERT(scope);
- generator.emitPopScopes(generator.scopeRegister(), scope->scopeDepth());
- generator.emitJump(scope->continueTarget());
+ bool hasFinally = generator.emitJumpViaFinallyIfNeeded(scope->scopeDepth(), scope->continueTarget());
+ if (!hasFinally) {
+ int lexicalScopeIndex = generator.labelScopeDepthToLexicalScopeIndex(scope->scopeDepth());
+ generator.restoreScopeRegister(lexicalScopeIndex);
+ generator.emitJump(scope->continueTarget());
+ }
generator.emitProfileControlFlow(endOffset());
}
@@ -3025,8 +3029,12 @@
LabelScopePtr scope = generator.breakTarget(m_ident);
ASSERT(scope);
- generator.emitPopScopes(generator.scopeRegister(), scope->scopeDepth());
- generator.emitJump(scope->breakTarget());
+ bool hasFinally = generator.emitJumpViaFinallyIfNeeded(scope->scopeDepth(), scope->breakTarget());
+ if (!hasFinally) {
+ int lexicalScopeIndex = generator.labelScopeDepthToLexicalScopeIndex(scope->scopeDepth());
+ generator.restoreScopeRegister(lexicalScopeIndex);
+ generator.emitJump(scope->breakTarget());
+ }
generator.emitProfileControlFlow(endOffset());
}
@@ -3043,14 +3051,14 @@
RefPtr<RegisterID> returnRegister = m_value ? generator.emitNodeInTailPosition(dst, m_value) : generator.emitLoad(dst, jsUndefined());
generator.emitProfileType(returnRegister.get(), ProfileTypeBytecodeFunctionReturnStatement, divotStart(), divotEnd());
- if (generator.isInFinallyBlock()) {
- returnRegister = generator.emitMove(generator.newTemporary(), returnRegister.get());
- generator.emitPopScopes(generator.scopeRegister(), 0);
+
+ bool hasFinally = generator.emitReturnViaFinallyIfNeeded(returnRegister.get());
+ if (!hasFinally) {
+ generator.emitWillLeaveCallFrameDebugHook();
+ generator.emitReturn(returnRegister.get());
}
- generator.emitWillLeaveCallFrameDebugHook();
- generator.emitReturn(returnRegister.get());
- generator.emitProfileControlFlow(endOffset());
+ generator.emitProfileControlFlow(endOffset());
// Emitting an unreachable return here is needed in case this op_profile_control_flow is the
// last opcode in a CodeBlock because a CodeBlock's instructions must end with a terminal opcode.
if (generator.vm()->controlFlowProfiler())
@@ -3279,32 +3287,57 @@
// optimizer knows they may be jumped to from anywhere.
ASSERT(m_catchBlock || m_finallyBlock);
+ BytecodeGenerator::FinallyRegistersScope finallyRegistersScope(generator, m_finallyBlock);
+ RefPtr<Label> catchLabel;
+ RefPtr<Label> catchEndLabel;
+ RefPtr<Label> finallyViaThrowLabel;
+ RefPtr<Label> finallyLabel;
+ RefPtr<Label> finallyEndLabel;
+
RefPtr<Label> tryStartLabel = generator.newLabel();
generator.emitLabel(tryStartLabel.get());
-
- if (m_finallyBlock)
- generator.pushFinallyControlFlowScope(m_finallyBlock);
- TryData* tryData = generator.pushTry(tryStartLabel.get());
+ if (m_finallyBlock) {
+ finallyViaThrowLabel = generator.newLabel();
+ finallyLabel = generator.newLabel();
+ finallyEndLabel = generator.newLabel();
+
+ generator.pushFinallyControlFlowScope(finallyLabel.get());
+ }
+ if (m_catchBlock) {
+ catchLabel = generator.newLabel();
+ catchEndLabel = generator.newLabel();
+ }
+
+ Label* tryHandlerLabel = m_catchBlock ? catchLabel.get() : finallyViaThrowLabel.get();
+ HandlerType tryHandlerType = m_catchBlock ? HandlerType::Catch : HandlerType::Finally;
+ TryData* tryData = generator.pushTry(tryStartLabel.get(), tryHandlerLabel, tryHandlerType);
+
generator.emitNode(dst, m_tryBlock);
- if (m_catchBlock) {
- RefPtr<Label> catchEndLabel = generator.newLabel();
-
- // Normal path: jump over the catch block.
+ // The finallyActionRegister is an empty value by default, which implies CompletionType::Normal.
+ if (m_finallyBlock)
+ generator.emitJump(finallyLabel.get());
+ else
generator.emitJump(catchEndLabel.get());
+ RefPtr<Label> endTryLabel = generator.emitLabel(generator.newLabel().get());
+ generator.popTry(tryData, endTryLabel.get());
+
+ if (m_catchBlock) {
// Uncaught exception path: the catch block.
- RefPtr<Label> here = generator.emitLabel(generator.newLabel().get());
- RefPtr<RegisterID> exceptionRegister = generator.newTemporary();
+ generator.emitLabel(catchLabel.get());
RefPtr<RegisterID> thrownValueRegister = generator.newTemporary();
- generator.popTryAndEmitCatch(tryData, exceptionRegister.get(), thrownValueRegister.get(), here.get(), HandlerType::Catch);
-
+ RegisterID* unused = generator.newTemporary();
+ generator.emitCatch(unused, thrownValueRegister.get());
+ generator.restoreScopeRegister();
+
+ TryData* tryData = nullptr;
if (m_finallyBlock) {
// If the catch block throws an exception and we have a finally block, then the finally
// block should "catch" that exception.
- tryData = generator.pushTry(here.get());
+ tryData = generator.pushTry(catchLabel.get(), finallyViaThrowLabel.get(), HandlerType::Finally);
}
generator.emitPushCatchScope(m_lexicalVariables);
@@ -3316,37 +3349,38 @@
generator.emitNodeInTailPosition(dst, m_catchBlock);
generator.emitLoad(thrownValueRegister.get(), jsUndefined());
generator.emitPopCatchScope(m_lexicalVariables);
+
+ if (m_finallyBlock) {
+ generator.emitSetFinallyActionToNormalCompletion();
+ generator.emitJump(finallyLabel.get());
+ generator.popTry(tryData, finallyViaThrowLabel.get());
+ }
+
generator.emitLabel(catchEndLabel.get());
+ generator.emitProfileControlFlow(m_catchBlock->endOffset() + 1);
}
if (m_finallyBlock) {
- RefPtr<Label> preFinallyLabel = generator.emitLabel(generator.newLabel().get());
-
- generator.popFinallyControlFlowScope();
+ FinallyContext finallyContext = generator.popFinallyControlFlowScope();
- RefPtr<Label> finallyEndLabel = generator.newLabel();
+ // Entry to the finally block for CompletionType::Throw.
+ generator.emitLabel(finallyViaThrowLabel.get());
+ RegisterID* unused = generator.newTemporary();
+ generator.emitCatch(generator.finallyActionRegister(), unused);
+ // Setting the finallyActionRegister to the caught exception here implies CompletionType::Throw.
+ // Entry to the finally block for CompletionTypes other than Throw.
+ generator.emitLabel(finallyLabel.get());
+ generator.restoreScopeRegister();
+
int finallyStartOffset = m_catchBlock ? m_catchBlock->endOffset() + 1 : m_tryBlock->endOffset() + 1;
-
- // Normal path: run the finally code, and jump to the end.
generator.emitProfileControlFlow(finallyStartOffset);
generator.emitNodeInTailPosition(dst, m_finallyBlock);
- generator.emitProfileControlFlow(m_finallyBlock->endOffset() + 1);
- generator.emitJump(finallyEndLabel.get());
- // Uncaught exception path: invoke the finally block, then re-throw the exception.
- RefPtr<RegisterID> exceptionRegister = generator.newTemporary();
- RefPtr<RegisterID> thrownValueRegister = generator.newTemporary();
- generator.popTryAndEmitCatch(tryData, exceptionRegister.get(), thrownValueRegister.get(), preFinallyLabel.get(), HandlerType::Finally);
- generator.emitProfileControlFlow(finallyStartOffset);
- generator.emitNodeInTailPosition(dst, m_finallyBlock);
- generator.emitThrow(exceptionRegister.get());
-
+ generator.emitFinallyCompletion(finallyContext, finallyEndLabel.get());
generator.emitLabel(finallyEndLabel.get());
generator.emitProfileControlFlow(m_finallyBlock->endOffset() + 1);
- } else
- generator.emitProfileControlFlow(m_catchBlock->endOffset() + 1);
-
+ }
}
// ------------------------------ ScopeNode -----------------------------
Modified: trunk/Source/WTF/ChangeLog (209951 => 209952)
--- trunk/Source/WTF/ChangeLog 2016-12-17 00:48:31 UTC (rev 209951)
+++ trunk/Source/WTF/ChangeLog 2016-12-17 01:06:49 UTC (rev 209952)
@@ -1,3 +1,17 @@
+2016-12-16 Mark Lam <mark....@apple.com>
+
+ Add predecessor info to dumps from JSC_dumpBytecodeLivenessResults=true.
+ https://bugs.webkit.org/show_bug.cgi?id=165958
+
+ Reviewed by Keith Miller.
+
+ Added some methods to bring SegmentedVector closer to parity with Vector.
+
+ * wtf/SegmentedVector.h:
+ (WTF::SegmentedVector::first):
+ (WTF::SegmentedVector::last):
+ (WTF::SegmentedVector::takeLast):
+
2016-12-16 Michael Saboff <msab...@apple.com>
REGRESSION: HipChat and Mail sometimes hang beneath JSC::Heap::lastChanceToFinalize()
Modified: trunk/Source/WTF/wtf/SegmentedVector.h (209951 => 209952)
--- trunk/Source/WTF/wtf/SegmentedVector.h 2016-12-17 00:48:31 UTC (rev 209951)
+++ trunk/Source/WTF/wtf/SegmentedVector.h 2016-12-17 01:06:49 UTC (rev 209952)
@@ -127,9 +127,17 @@
return at(index);
}
- T& last()
+ T& first() { return at(0); }
+ const T& first() const { return at(0); }
+ T& last() { return at(size() - 1); }
+ const T& last() const { return at(size() - 1); }
+
+ T takeLast()
{
- return at(size() - 1);
+ ASSERT_WITH_SECURITY_IMPLICATION(!isEmpty());
+ T result = WTFMove(last());
+ --m_size;
+ return result;
}
template<typename... Args>