Skip to content

Commit 6f99512

Browse files
committed
wasm: introduct Dispatch and ApplyGeneralized to the IR
1 parent 0538b7c commit 6f99512

2 files changed

Lines changed: 98 additions & 61 deletions

File tree

packages/wasm/src/index.js

Lines changed: 42 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -866,28 +866,17 @@ export class Compiler {
866866
const {rules} = this;
867867
const patternsByRule = new Map();
868868

869-
const visit = irExp => {
870-
switch (irExp.type) {
871-
case 'Alt':
872-
return ir.alt(irExp.children.map(e => visit(e)));
873-
case 'Any':
874-
case 'End':
875-
case 'LiftedTerminal':
876-
return irExp;
877-
case 'Apply': {
878-
const {ruleName, children} = irExp;
869+
const specialize = exp =>
870+
ir.rewrite(exp, {
871+
Apply: app => {
872+
const {ruleName, children} = app;
879873
// Inline these. TODO: Handle this elsewhere.
880874
if (['caseInsensitive', 'liquidRawTagImpl', 'liquidTagRule'].includes(ruleName)) {
881875
const ruleInfo = getNotNull(rules, ruleName);
882-
return visit(ir.substituteParams(ruleInfo.body, children));
876+
return specialize(ir.substituteParams(ruleInfo.body, children));
883877
}
884878

885-
const specializedName = ir.specializedName(irExp);
886-
if (specializedName !== ruleName) {
887-
// Record this pattern.
888-
const rulePatterns = setdefault(patternsByRule, ruleName, () => new Map());
889-
rulePatterns.set(specializedName, children);
890-
}
879+
const specializedName = ir.specializedName(app);
891880
this._ensureRuleId(specializedName);
892881

893882
// If not yet seen, recursively visit the body of the specialized
@@ -898,49 +887,51 @@ export class Compiler {
898887

899888
// Visit the body with the parameter substituted, to ensure we
900889
// discover all possible applications that can occur at runtime.
901-
let body = visit(ir.substituteParams(ruleInfo.body, children));
902-
903-
if (children.length !== 0) {
904-
// The specialized rule just applies the generalized rule.
905-
// Note that we *don't* visit this application, and it won't be
906-
// assigned a rule id yet!
907-
body = ir.apply(irExp.ruleName);
890+
let body = specialize(ir.substituteParams(ruleInfo.body, children));
891+
892+
// If there are any args, replace the body with an application of
893+
// the generalized rule.
894+
if (children.length > 0) {
895+
// This is the first time we've seen this pattern; record it.
896+
const rulePatterns = setdefault(patternsByRule, ruleName, () => new Map());
897+
rulePatterns.set(specializedName, children);
898+
899+
// Note that we deliberately *don't* visit this application yet,
900+
// so it won't be assigned a rule ID.
901+
const caseIdx = rulePatterns.size - 1;
902+
body = ir.applyGeneralized(ruleName, caseIdx);
908903
}
909904
newRules.set(specializedName, {...ruleInfo, body, formals: []});
910905
}
911906
// Replace with an application of the specialized rule.
912907
return ir.apply(specializedName);
913-
}
914-
case 'Lex':
915-
case 'Lookahead':
916-
case 'Not':
917-
case 'Opt':
918-
case 'Plus':
919-
case 'Star':
920-
return {type: irExp.type, child: visit(irExp.child)};
921-
case 'Seq':
922-
return {type: irExp.type, children: irExp.children.map(visit)};
923-
case 'Param':
924-
case 'Range':
925-
case 'Terminal':
926-
case 'UnicodeChar':
927-
return irExp; // Leaf nodes can be shared.
928-
default:
929-
throw new Error(`not handled: ${irExp.type}`);
930-
}
931-
};
932-
visit(ir.apply(this.grammar.defaultStartRule));
908+
},
909+
});
910+
specialize(ir.apply(this.grammar.defaultStartRule));
933911
this.rules = newRules;
934912

913+
const insertDispatches = (exp, patterns) =>
914+
ir.rewrite(exp, {
915+
Apply: app => {
916+
if (app.children.length === 0) return app;
917+
return {type: 'Dispatch', child: app, patterns};
918+
},
919+
Param: p => {
920+
return {type: 'Dispatch', child: p, patterns};
921+
},
922+
});
923+
935924
// Save the observed patterns of the parameterized rules.
936925
// All non-parameterized & specialized rules have been discovered and
937926
// assigned IDs; any rule IDs assigned here won't be memoized.
938927
for (const [name, patterns] of patternsByRule.entries()) {
939928
this._ensureRuleId(name, {notMemoized: true});
940929
const ruleInfo = getNotNull(rules, name);
930+
const patternsArr = [...patterns.values()];
941931
newRules.set(name, {
942932
...ruleInfo,
943-
patterns: [...patterns.values()],
933+
body: insertDispatches(ruleInfo.body, patternsArr),
934+
patterns: patternsArr,
944935
});
945936
}
946937
}
@@ -1101,7 +1092,7 @@ export class Compiler {
11011092
// rule; they take an i32 `caseIdx` argument that selects the behaviour.
11021093
// Then, for any Param -- or Apply that involves a Param -- we dynamically
11031094
// dispatch to the correct specialized version of the rule.
1104-
emitDispatch(exp, patterns) {
1095+
emitDispatch({child: exp, patterns}) {
11051096
const {asm} = this;
11061097

11071098
const cases = patterns.map((actuals, i) => () => {
@@ -1144,8 +1135,8 @@ export class Compiler {
11441135
this.emitApply(exp);
11451136
return;
11461137
}
1147-
if (this._currRuleInfo.patterns && ['Apply', 'Param'].includes(exp.type)) {
1148-
this.emitDispatch(exp, this._currRuleInfo.patterns);
1138+
if (exp.type === 'ApplyGeneralized') {
1139+
this.emitApplyGeneralized(exp);
11491140
return;
11501141
}
11511142

@@ -1161,6 +1152,7 @@ export class Compiler {
11611152
switch (exp.type) {
11621153
case 'Alt': this.emitAlt(exp); break;
11631154
case 'Any': this.emitAny(); break;
1155+
case 'Dispatch': this.emitDispatch(exp); break;
11641156
case 'End': this.emitEnd(); break;
11651157
case 'LiftedTerminal': this.emitApplyTerm(exp); break;
11661158
case 'Lookahead': this.emitLookahead(exp, true); break;
@@ -1214,27 +1206,16 @@ export class Compiler {
12141206
// Need to know which case we're applying!
12151207
emitApplyGeneralized(exp) {
12161208
const {asm} = this;
1217-
const {patterns} = getNotNull(this.rules, exp.ruleName);
1218-
// TODO: Should we cache these? We'll reconstruct them many times for the same set of patterns.
1219-
const keys = patterns.map(actuals => ir.specializedName(ir.apply(exp.ruleName, actuals)));
1220-
const caseIdx = keys.indexOf(this._currRuleName);
1221-
assert(caseIdx >= 0);
12221209
asm.i32Const(this.ruleId(exp.ruleName));
1223-
asm.i32Const(caseIdx);
1210+
asm.i32Const(exp.caseIdx);
12241211
asm.callPrebuiltFunc('evalApplyGeneralized');
12251212
asm.localSet('ret');
12261213
}
12271214

12281215
emitApply(exp) {
1229-
const {asm} = this;
1230-
1231-
// Are we applying a generalized rule from a specialized one?
1232-
if (getNotNull(this.rules, exp.ruleName).formals.length > 0) {
1233-
this.emitApplyGeneralized(exp);
1234-
return;
1235-
}
12361216
assert(exp.children.length === 0);
12371217

1218+
const {asm} = this;
12381219
asm.i32Const(this.ruleId(exp.ruleName));
12391220

12401221
// TODO: Handle this at grammar parse time, not here.

packages/wasm/src/ir.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ export type Expr =
55
| Alt
66
| Any
77
| Apply
8+
| ApplyGeneralized
89
| CaseInsensitive
10+
| Dispatch
911
| End
1012
| Lex
1113
| LiftedTerminal
@@ -45,6 +47,18 @@ export const apply = (ruleName: string, children: (Apply | Param)[] = []): Apply
4547
children
4648
});
4749

50+
export interface ApplyGeneralized {
51+
type: 'ApplyGeneralized';
52+
ruleName: string;
53+
caseIdx: number;
54+
}
55+
56+
export const applyGeneralized = (ruleName: string, caseIdx: number): ApplyGeneralized => ({
57+
type: 'ApplyGeneralized',
58+
ruleName,
59+
caseIdx
60+
});
61+
4862
export interface CaseInsensitive {
4963
type: 'CaseInsensitive';
5064
value: string;
@@ -55,6 +69,11 @@ export const caseInsensitive = (value: string): CaseInsensitive => ({
5569
value
5670
});
5771

72+
export interface Dispatch {
73+
child: Apply | Param;
74+
patterns: Expr[][];
75+
}
76+
5877
// TODO: Eliminate this, and replace with Not(Any())?
5978
export interface End {
6079
type: 'End';
@@ -238,3 +257,40 @@ export function specializedName(app: Apply | LiftedTerminal): string {
238257
.join(',');
239258
return app.ruleName + (argsNames.length > 0 ? `<${argsNames}>` : '');
240259
}
260+
261+
export type ExprType = Expr extends {type: infer U} ? U : never;
262+
263+
export type RewriteActions = {
264+
[K in ExprType]?: (exp: Extract<Expr, {type: K}>) => Expr;
265+
};
266+
267+
export function rewrite(exp: Expr, actions: RewriteActions) {
268+
const action = actions[exp.type];
269+
if (action) {
270+
return action(exp as any);
271+
}
272+
273+
switch (exp.type) {
274+
case 'Alt':
275+
case 'Seq':
276+
return {type: exp.type, children: exp.children.map((e: Expr) => rewrite(e, actions))};
277+
case 'Any':
278+
case 'Apply':
279+
case 'End':
280+
case 'LiftedTerminal':
281+
case 'Param':
282+
case 'Range':
283+
case 'Terminal':
284+
case 'UnicodeChar':
285+
return exp;
286+
case 'Lex':
287+
case 'Lookahead':
288+
case 'Not':
289+
case 'Opt':
290+
case 'Plus':
291+
case 'Star':
292+
return {type: exp.type, child: rewrite(exp.child, actions)};
293+
default:
294+
throw new Error(`not handled: ${exp.type}`);
295+
}
296+
}

0 commit comments

Comments
 (0)