From 7134b09e502ca2ff17b165e72b04e88f7973fbab Mon Sep 17 00:00:00 2001 From: David Majda Date: Sun, 15 Jul 2012 17:17:25 +0200 Subject: [PATCH] Merge |allocateRegisters| and |computeParams| passes The purpose of this change is to avoid the need to index register variables storing match results of sequences whose elements are labeled. The indexing happened when match results of labeled elements were passed to action/predicate functions. In order to avoid indexing, the register allocator needs to ensure that registers storing match results of any labeled sequence elements are still "alive" after finishing parsing of the sequence. They should not be used to store anything else at least until code of all actions and predicates that can see the labels is executed. This requires that the |allocateRegisters| pass has the knowledge of scoping. Because that knowledge was already implicitly embedded in the |coputeParams| pass, the logical step to prevent duplication was to merge it with the |allocateRegisters| pass. This is what this commit does. As a part of the merge the tests of both passes were largely refactored. This is both to accomodate the merge and to make the tests in sync with the code again (the tests became a bit out-of-sync during the last few commits -- they tested more than was needed). The speed/size impact is slightly positive: Speed impact ------------ Before: 849.86 kB/s After: 858.16 kB/s Difference: 0.97% Size impact ----------- Before: 876618 b After: 875602 b Difference: -0.12% (Measured by /tools/impact with Node.js v0.6.18 on x86_64 GNU/Linux.) --- .../passes/allocate-registers.spec.js | 419 +++++++++++------- spec/compiler/passes/compute-params.spec.js | 207 --------- spec/index.html | 1 - src/compiler.js | 1 - src/compiler/passes.js | 1 - src/compiler/passes/allocate-registers.js | 246 +++++++--- src/compiler/passes/compute-params.js | 109 ----- src/compiler/passes/generate-code.js | 11 +- src/parser.js | 62 +-- 9 files changed, 468 insertions(+), 589 deletions(-) delete mode 100644 spec/compiler/passes/compute-params.spec.js delete mode 100644 src/compiler/passes/compute-params.js diff --git a/spec/compiler/passes/allocate-registers.spec.js b/spec/compiler/passes/allocate-registers.spec.js index ab35df0..645ca42 100644 --- a/spec/compiler/passes/allocate-registers.spec.js +++ b/spec/compiler/passes/allocate-registers.spec.js @@ -1,203 +1,288 @@ describe("compiler pass |allocateRegisters|", function() { var pass = PEG.compiler.passes.allocateRegisters; - var leafDetails = { resultIndex: 0 }, - choiceDetails = { - resultIndex: 0, - alternatives: [ - { resultIndex: 0, posIndex: 1 }, - { resultIndex: 0, posIndex: 1 }, - { resultIndex: 0, posIndex: 1 } + function ruleDetails(details) { return { rules: [details] }; } + + function expressionDetails(details) { + return ruleDetails({ expression: details }); + } + + function innerExpressionDetails(details) { + return expressionDetails({ expression: details }); + } + + var reuseResultDetails = innerExpressionDetails({ resultIndex: 0 }), + allocResultDetails = innerExpressionDetails({ resultIndex: 1 }), + savePosDetails = expressionDetails({ posIndex: 1 }), + scopedDetails = expressionDetails({ params: {} }), + blockedDetails = expressionDetails({ + elements: [ + {}, + { + resultIndex: 3, + posIndex: 5, + elements: [{ resultIndex: 6 }, { resultIndex: 7 }] + } ] - }, - sequenceDetails = { - resultIndex: 0, - posIndex: 1, - elements: [ - { resultIndex: 2, posIndex: 3 }, - { resultIndex: 3, posIndex: 4 }, - { resultIndex: 4, posIndex: 5 } + }), + unblockedDetails = expressionDetails({ + elements: [ + {}, + { + resultIndex: 3, + posIndex: 4, + elements: [{ resultIndex: 5 }, { resultIndex: 6 }] + } ] - }; + }); - function ruleDetails(details) { return { rules: [details] }; } + describe("for rule", function() { + it("allocates a new result register for the expression", function() { + expect(pass).toChangeAST('start = "a"', expressionDetails({ + resultIndex: 0 + })); + }); - it("allocates registers for a named", function() { - expect(pass).toChangeAST('start "start" = &"a"', ruleDetails({ - registerCount: 2, - expression: { - resultIndex: 0, - expression: { resultIndex: 0, posIndex: 1 } - } - })); - }); + it("counts used registers", function() { + expect(pass).toChangeAST('start = "a"', ruleDetails({ + registerCount: 1 + })); + expect(pass).toChangeAST('start = "a"*', ruleDetails({ + registerCount: 2 + })); + expect(pass).toChangeAST('start = ("a"*)*', ruleDetails({ + registerCount: 3 + })); + }); - it("allocates registers for a choice", function() { - expect(pass).toChangeAST('start = &"a" / &"b" / &"c"', ruleDetails({ - registerCount: 2, - expression: choiceDetails - })); - expect(pass).toChangeAST('start = &"a" / &"b"* / &"c"', ruleDetails({ - registerCount: 3, - expression: choiceDetails - })); - expect(pass).toChangeAST('start = &"a" / &(&"b") / &"c"', ruleDetails({ - registerCount: 3, - expression: choiceDetails - })); + it("resets used registers counter", function() { + expect(pass).toChangeAST('a = "a"*; b = "b"', { + rules: [ { registerCount: 2 }, { registerCount: 1 }] + }); + }); }); - it("allocates registers for an action", function() { - expect(pass).toChangeAST('start = &"a" { code }', ruleDetails({ - registerCount: 3, - expression: { - resultIndex: 0, - posIndex: 1, - expression: { resultIndex: 0, posIndex: 2 } - } - })); + describe("for named", function() { + it("reuses its own result register for the expression", function() { + expect(pass).toChangeAST('start "start" = "a"', reuseResultDetails); + }); }); - it("allocates registers for a sequence", function() { - expect(pass).toChangeAST('start = ', ruleDetails({ - registerCount: 2, - expression: { resultIndex: 0, posIndex: 1 } - })); - expect(pass).toChangeAST('start = &"a" &"b" &"c"', ruleDetails({ - registerCount: 6, - expression: sequenceDetails - })); - expect(pass).toChangeAST('start = &"a" &"b" &"c"*', ruleDetails({ - registerCount: 7, - expression: sequenceDetails - })); - expect(pass).toChangeAST('start = &"a" &"b"* &"c"', ruleDetails({ - registerCount: 6, - expression: sequenceDetails - })); - expect(pass).toChangeAST('start = &"a" &("b"*)* &"c"', ruleDetails({ - registerCount: 7, - expression: sequenceDetails - })); - expect(pass).toChangeAST('start = &"a"* &"b" &"c"', ruleDetails({ - registerCount: 6, - expression: sequenceDetails - })); - expect(pass).toChangeAST('start = &("a"*)* &"b" &"c"', ruleDetails({ - registerCount: 6, - expression: sequenceDetails - })); - expect(pass).toChangeAST('start = &(("a"*)*)* &"b" &"c"', ruleDetails({ - registerCount: 7, - expression: sequenceDetails - })); - expect(pass).toChangeAST('start = &"a" &(&"b") &"c"', ruleDetails({ - registerCount: 6, - expression: sequenceDetails - })); - }); + describe("for choice", function() { + it("reuses its own result register for the alternatives", function() { + expect(pass).toChangeAST('start = "a" / "b" / "c"', expressionDetails({ + alternatives: [ + { resultIndex: 0 }, + { resultIndex: 0 }, + { resultIndex: 0 } + ] + })); + }); - it("allocates registers for a labeled", function() { - expect(pass).toChangeAST('start = label:&"a"', ruleDetails({ - registerCount: 2, - expression: { - resultIndex: 0, - expression: { resultIndex: 0, posIndex: 1 } - } - })); - }); + it("creates a new scope", function() { + expect(pass).toChangeAST('start = (a:"a" / "b" / "c") { }', scopedDetails); + }); - it("allocates registers for a simple and", function() { - expect(pass).toChangeAST('start = &(&"a")', ruleDetails({ - registerCount: 3, - expression: { - resultIndex: 0, - posIndex: 1, - expression: { resultIndex: 0, posIndex: 2 } - } - })); + it("unblocks registers blocked by its children", function() { + expect(pass).toChangeAST( + 'start = ((a:"a" / "b" / "c") "d") ("e" "f")', + unblockedDetails + ); + }); }); - it("allocates registers for a simple not", function() { - expect(pass).toChangeAST('start = !(&"a")', ruleDetails({ - registerCount: 3, - expression: { - resultIndex: 0, - posIndex: 1, - expression: { resultIndex: 0, posIndex: 2 } - } - })); + describe("for action", function() { + it("allocates a position register", function() { + expect(pass).toChangeAST('start = "a" { }', savePosDetails); + }); + + it("reuses its own result register for the expression", function() { + expect(pass).toChangeAST('start = "a" { }', reuseResultDetails); + }); + + it("creates a new scope", function() { + expect(pass).toChangeAST('start = (a:"a" { }) { }', scopedDetails); + }); + + it("unblocks registers blocked by its children", function() { + expect(pass).toChangeAST( + 'start = ((a:"a" { }) "b") ("c" "d")', + unblockedDetails + ); + }); + + it("computes params", function() { + expect(pass).toChangeAST('start = a:"a" b:"b" c:"c" { }', expressionDetails({ + params: { a: 3, b: 4, c: 5 } + })); + }); }); - it("allocates registers for a semantic and", function() { - expect(pass).toChangeAST('start = &{ code }', ruleDetails({ - registerCount: 1, - expression: leafDetails - })); + describe("for sequence", function() { + it("allocates a position register", function() { + expect(pass).toChangeAST('start = ', savePosDetails); + expect(pass).toChangeAST('start = "a" "b" "c"', savePosDetails); + }); + + it("allocates new result registers for the elements", function() { + expect(pass).toChangeAST('start = "a" "b" "c"', expressionDetails({ + elements: [{ resultIndex: 2 }, { resultIndex: 3 }, { resultIndex: 4 }] + })); + }); + + it("does not create a new scope", function() { + expect(pass).toChangeAST( + 'start = a:"a" b:"b" c:"c" { }', + expressionDetails({ params: { a: 3, b: 4, c: 5 } }) + ); + }); + + it("does not unblock blocked result registers from children", function() { + expect(pass).toChangeAST( + 'start = (a:"a" "b") ("c" "d")', + blockedDetails + ); + }); }); - it("allocates registers for a semantic not", function() { - expect(pass).toChangeAST('start = !{ code }', ruleDetails({ - registerCount: 1, - expression: leafDetails - })); + describe("for labeled", function() { + it("reuses its own result register for the expression", function() { + expect(pass).toChangeAST('start = a:"a"', reuseResultDetails); + }); + + it("creates a new scope", function() { + expect(pass).toChangeAST('start = a:(b:"b") { }', expressionDetails({ + params: { a: 0 } + })); + }); + + it("unblocks registers blocked by its children", function() { + expect(pass).toChangeAST( + 'start = (a:(b:"b") "c") ("d" "e")', + blockedDetails + ); + }); + + it("adds label to the environment", function() { + expect(pass).toChangeAST('start = a:"a" { }', expressionDetails({ + params: { a: 0 } + })); + }); + + it("blocks its own result register", function() { + expect(pass).toChangeAST( + 'start = (a:"a" "b") ("c" "d")', + blockedDetails + ); + }); }); - it("allocates registers for an optional", function() { - expect(pass).toChangeAST('start = (&"a")?', ruleDetails({ - registerCount: 2, - expression: { - resultIndex: 0, - expression: { resultIndex: 0, posIndex: 1 } - } - })); + describe("for simple and", function() { + it("allocates a position register", function() { + expect(pass).toChangeAST('start = &"a"', savePosDetails); + }); + + it("reuses its own result register for the expression", function() { + expect(pass).toChangeAST('start = &"a"', reuseResultDetails); + }); + + it("creates a new scope", function() { + expect(pass).toChangeAST('start = &(a:"a") { }', scopedDetails); + }); + + it("unblocks registers blocked by its children", function() { + expect(pass).toChangeAST( + 'start = (&(a:"a") "b") ("c" "d")', + unblockedDetails + ); + }); }); - it("allocates registers for a zero or more", function() { - expect(pass).toChangeAST('start = (&"a")*', ruleDetails({ - registerCount: 3, - expression: { - resultIndex: 0, - expression: { resultIndex: 1, posIndex: 2 } - } - })); + describe("for simple not", function() { + it("allocates a position register", function() { + expect(pass).toChangeAST('start = !"a"', savePosDetails); + }); + + it("reuses its own result register for the expression", function() { + expect(pass).toChangeAST('start = !"a"', reuseResultDetails); + }); + + it("creates a new scope", function() { + expect(pass).toChangeAST('start = !(a:"a") { }', scopedDetails); + }); + + it("unblocks registers blocked by its children", function() { + expect(pass).toChangeAST( + 'start = (!(a:"a") "b") ("c" "d")', + unblockedDetails + ); + }); }); - it("allocates registers for a one or more", function() { - expect(pass).toChangeAST('start = (&"a")+', ruleDetails({ - registerCount: 3, - expression: { - resultIndex: 0, - expression: { resultIndex: 1, posIndex: 2 } - } - })); + describe("for semantic and", function() { + it("computes params", function() { + expect(pass).toChangeAST('start = a:"a" b:"b" c:"c" &{ }', expressionDetails({ + elements: [{}, {}, {}, { params: { a: 2, b: 3, c: 4 } }] + })); + }); }); - it("allocates registers for a rule reference", function() { - expect(pass).toChangeAST('start = a', ruleDetails({ - registerCount: 1, - expression: leafDetails - })); + describe("for semantic not", function() { + it("computes params", function() { + expect(pass).toChangeAST('start = a:"a" b:"b" c:"c" !{ }', expressionDetails({ + elements: [{}, {}, {}, { params: { a: 2, b: 3, c: 4 } }] + })); + }); }); - it("allocates registers for a literal", function() { - expect(pass).toChangeAST('start = "a"', ruleDetails({ - registerCount: 1, - expression: leafDetails - })); + describe("for optional", function() { + it("reuses its own result register for the expression", function() { + expect(pass).toChangeAST('start = "a"?', reuseResultDetails); + }); + + it("creates a new scope", function() { + expect(pass).toChangeAST('start = (a:"a")? { }', scopedDetails); + }); + + it("unblocks registers blocked by its children", function() { + expect(pass).toChangeAST( + 'start = ((a:"a")? "b") ("c" "d")', + unblockedDetails + ); + }); }); - it("allocates registers for a class", function() { - expect(pass).toChangeAST('start = [a-z]', ruleDetails({ - registerCount: 1, - expression: leafDetails - })); + describe("for zero or more", function() { + it("allocates a new result register for the expression", function() { + expect(pass).toChangeAST('start = "a"*', allocResultDetails); + }); + + it("creates a new scope", function() { + expect(pass).toChangeAST('start = (a:"a")* { }', scopedDetails); + }); + + it("unblocks registers blocked by its children", function() { + expect(pass).toChangeAST( + 'start = ((a:"a")* "b") ("c" "d")', + unblockedDetails + ); + }); }); - it("allocates registers for an any", function() { - expect(pass).toChangeAST('start = .', ruleDetails({ - registerCount: 1, - expression: leafDetails - })); + describe("for one or more", function() { + it("allocates a new result register for the expression", function() { + expect(pass).toChangeAST('start = "a"+', allocResultDetails); + }); + + it("creates a new scope", function() { + expect(pass).toChangeAST('start = (a:"a")+ { }', scopedDetails); + }); + + it("unblocks registers blocked by its children", function() { + expect(pass).toChangeAST( + 'start = ((a:"a")+ "b") ("c" "d")', + unblockedDetails + ); + }); }); }); diff --git a/spec/compiler/passes/compute-params.spec.js b/spec/compiler/passes/compute-params.spec.js deleted file mode 100644 index a5be9da..0000000 --- a/spec/compiler/passes/compute-params.spec.js +++ /dev/null @@ -1,207 +0,0 @@ -describe("compiler pass |computeParams|", function() { - function pass(ast) { - PEG.compiler.passes.allocateRegisters(ast); - PEG.compiler.passes.computeParams(ast); - } - - var result0 = { resultIndex: 0, subindices: [] }, - result0_0 = { resultIndex: 0, subindices: [0] }, - result0_1 = { resultIndex: 0, subindices: [1] }, - result0_1_0 = { resultIndex: 0, subindices: [1, 0] }, - result0_1_1 = { resultIndex: 0, subindices: [1, 1] }, - result0_1_2 = { resultIndex: 0, subindices: [1, 2] }, - result0_2 = { resultIndex: 0, subindices: [2] }, - result1_9 = { resultIndex: 1, subindices: [9] }, - result1 = { resultIndex: 1, subindices: [] }; - result2 = { resultIndex: 2, subindices: [] }, - result4 = { resultIndex: 4, subindices: [] }; - - function ruleDetails(details) { return { rules: [details] }; } - - function expressionDetails(details) { - return ruleDetails({ expression: details }); - } - - function innerExpressionDetails(details) { - return expressionDetails({ expression: details }); - } - - describe("basic cases", function() { - it("computes params for an action", function() { - expect(pass).toChangeAST('start = a:"a" { }', expressionDetails({ - params: { a: result0 } - })); - }); - - it("computes params for a semantic and", function() { - expect(pass).toChangeAST('start = a:"a" &{ }', expressionDetails({ - elements: [ - {}, - { params: { a: result2 } } - ] - })); - }); - - it("computes params for a semantic not", function() { - expect(pass).toChangeAST('start = a:"a" !{ }', expressionDetails({ - elements: [ - {}, - { params: { a: result2 } } - ] - })); - }); - }); - - describe("recursive walk", function() { - it("computes params for a named", function() { - expect(pass).toChangeAST( - 'start "start" = a:"a" { }', - innerExpressionDetails({ params: { a: result0 } }) - ); - }); - - it("computes params for a choice", function() { - expect(pass).toChangeAST( - 'start = a:"a" { } / "b" / "c"', - expressionDetails({ - alternatives: [{ params: { a: result0 } }, {}, {}] - }) - ); - expect(pass).toChangeAST( - 'start = "a" / "b" / c:"c" { }', - expressionDetails({ - alternatives: [{}, {}, { params: { c: result0 } }] - }) - ); - }); - - it("computes params for an action", function() { - expect(pass).toChangeAST('start = (a:"a" { }) { }', innerExpressionDetails({ - params: { a: result0 } - })); - }); - - it("computes params for a sequence", function() { - expect(pass).toChangeAST( - 'start = (a:"a" { }) "b" "c"', - expressionDetails({ - elements: [{ params: { a: result2 } }, {}, {}] - }) - ); - expect(pass).toChangeAST( - 'start = "a" "b" (c:"c" { })', - expressionDetails({ - elements: [{}, {}, { params: { c: result4 } }] - }) - ); - }); - - it("computes params for a labeled", function() { - expect(pass).toChangeAST('start = a:(b:"b" { })', innerExpressionDetails({ - params: { b: result0 } - })); - }); - - it("computes params for a simple and", function() { - expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({ - params: { a: result0 } - })); - }); - - it("computes params for a simple not", function() { - expect(pass).toChangeAST('start = &(a:"a" { })', innerExpressionDetails({ - params: { a: result0 } - })); - }); - - it("computes params for an optional", function() { - expect(pass).toChangeAST('start = (a:"a" { })?', innerExpressionDetails({ - params: { a: result0 } - })); - }); - - it("computes params for a zero or more", function() { - expect(pass).toChangeAST('start = (a:"a" { })*', innerExpressionDetails({ - params: { a: result1 } - })); - }); - - it("computes params for a one or more", function() { - expect(pass).toChangeAST('start = (a:"a" { })+', innerExpressionDetails({ - params: { a: result1 } - })); - }); - }); - - describe("scoping", function() { - it("creates a new scope for a choice", function() { - expect(pass).toChangeAST( - 'start = (a:"a" / b:"b" / c:"c") { }', - expressionDetails({ params: {} }) - ); - }); - - it("creates a new scope for an action", function() { - expect(pass).toChangeAST('start = (a:"a" { }) { }', expressionDetails({ - params: {} - })); - }); - - it("does not create a new scope for a sequence", function() { - expect(pass).toChangeAST( - 'start = a:"a" b:"b" c:"c" { }', - expressionDetails({ - params: { a: result0_0, b: result0_1, c: result0_2 } - }) - ); - expect(pass).toChangeAST( - 'start = a:"a" (b:"b" c:"c" d:"d") e:"e"{ }', - expressionDetails({ - params: { - a: result0_0, - b: result0_1_0, - c: result0_1_1, - d: result0_1_2, - e: result0_2 - } - }) - ); - }); - - it("creates a new scope for a labeled", function() { - expect(pass).toChangeAST('start = a:(b:"b") { }', expressionDetails({ - params: { a: result0 } - })); - }); - - it("creates a new scope for a simple and", function() { - expect(pass).toChangeAST('start = &(a:"a") { }', expressionDetails({ - params: {} - })); - }); - - it("creates a new scope for a simple not", function() { - expect(pass).toChangeAST('start = !(a:"a") { }', expressionDetails({ - params: {} - })); - }); - - it("creates a new scope for an optional", function() { - expect(pass).toChangeAST('start = (a:"a")? { }', expressionDetails({ - params: {} - })); - }); - - it("creates a new scope for a zero or more", function() { - expect(pass).toChangeAST('start = (a:"a")* { }', expressionDetails({ - params: {} - })); - }); - - it("creates a new scope for a one or more", function() { - expect(pass).toChangeAST('start = (a:"a")+ { }', expressionDetails({ - params: {} - })); - }); - }); -}); diff --git a/spec/index.html b/spec/index.html index a328230..4a5db67 100644 --- a/spec/index.html +++ b/spec/index.html @@ -13,7 +13,6 @@ -