2384 строки
95 KiB
TypeScript
2384 строки
95 KiB
TypeScript
///<reference path='refs.ts'/>
|
|
|
|
module TDev.AST {
|
|
// a visitor to find the set of ``next'' statements after provided one
|
|
// the next finder is conservative and can find false nodes!
|
|
export class NextFinder
|
|
extends NodeVisitor {
|
|
constructor()
|
|
{
|
|
super();
|
|
}
|
|
|
|
static enclosingStmt(stmt: Stmt) {
|
|
return stmt.parentBlock() && stmt.parentBlock().parent;
|
|
}
|
|
|
|
static asLoop(stmt: Stmt): LoopStmt{
|
|
if (stmt instanceof While || stmt instanceof For || stmt instanceof Foreach) return <LoopStmt><any>stmt;
|
|
else return null;
|
|
}
|
|
|
|
private nextInBlock(stmt: Stmt) {
|
|
if (!stmt || !stmt.parentBlock()) return [];
|
|
|
|
var container = stmt.parentBlock().stmts;
|
|
var ix = container.indexOf(stmt);
|
|
var enclosing = NextFinder.enclosingStmt(stmt);
|
|
|
|
++ix;
|
|
while ((ix < container.length) && (container[ix].isPlaceholder() || container[ix].nodeType() === "comment")) ++ix;
|
|
|
|
var ret = []
|
|
|
|
if(ix >= container.length) {
|
|
// we are last child
|
|
ret = this.nextInBlock(enclosing);
|
|
} else {
|
|
ret = [container[ix]];
|
|
}
|
|
|
|
return ret.concat(this.visitLoop(NextFinder.asLoop(enclosing)));
|
|
}
|
|
|
|
static firstInCodeBlock(body: CodeBlock) {
|
|
if (body == null) return null;
|
|
|
|
var ix = 0;
|
|
var container = body.stmts;
|
|
|
|
while ((ix < container.length) && (container[ix].isPlaceholder() || container[ix].nodeType() === "comment"))++ix;
|
|
|
|
if (ix >= container.length) {
|
|
// nothing useful inside
|
|
return null;
|
|
}
|
|
|
|
return container[ix];
|
|
}
|
|
|
|
public visitCodeBlock(body: CodeBlock) {
|
|
var ret = NextFinder.firstInCodeBlock(body);
|
|
return ret ? [ret] : [];
|
|
}
|
|
|
|
private visitLoop(stmt: LoopStmt) {
|
|
if (stmt == null) return [];
|
|
|
|
var ret = this.visitCodeBlock(stmt.body);
|
|
return ret.concat(this.nextInBlock(<Stmt><any>stmt));
|
|
}
|
|
|
|
public visitFor(stmt: For) { return this.visitLoop(stmt); }
|
|
public visitForeach(stmt: Foreach) { return this.visitLoop(stmt); }
|
|
public visitWhile(stmt: While) { return this.visitLoop(stmt); }
|
|
|
|
public visitExprStmt(stmt: ExprStmt) {
|
|
return this.nextInBlock(stmt);
|
|
}
|
|
|
|
public visitBox(box: Box) {
|
|
return this.visitCodeBlock(box.body);
|
|
}
|
|
|
|
public visitInlineActions(ia: InlineActions) {
|
|
return this.visitExprStmt(ia);
|
|
}
|
|
|
|
public visitAnyIf(stmt: If) {
|
|
var arrs = stmt.parentIf.bodies().map(b => this.visitCodeBlock(b))
|
|
arrs.push(this.nextInBlock(stmt)) // this is not really the case most of the time, but whatever
|
|
return Util.concatArraysVA(arrs)
|
|
}
|
|
|
|
static find(stmt: Stmt): Stmt[]{
|
|
if (!stmt) return [];
|
|
|
|
var ret = new NextFinder().dispatch(stmt);
|
|
if (!ret) {
|
|
return [];
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
export class InnerNextFinder
|
|
extends NodeVisitor {
|
|
called: Action[] = [];
|
|
|
|
visitAstNode(n: AstNode) {
|
|
this.visitChildren(n);
|
|
return null;
|
|
}
|
|
|
|
visitCall(n: Call) {
|
|
var act = n.calledAction();
|
|
if (act && this.called.indexOf(act) < 0) {
|
|
this.called.push(act);
|
|
} else {
|
|
super.visitCall(n);
|
|
}
|
|
}
|
|
|
|
visitExprHolder(n: ExprHolder) {
|
|
if (n.parsed) this.dispatch(n.parsed);
|
|
}
|
|
|
|
static find(stmt: Stmt): Stmt[]{
|
|
if (!stmt) return [];
|
|
|
|
var finder = new InnerNextFinder();
|
|
finder.dispatch(stmt);
|
|
return finder.called.filter(a => !!a.body).map(a => {
|
|
var fst = NextFinder.firstInCodeBlock(a.body);
|
|
return fst ? [fst] : [];
|
|
}).reduce((a,b) => a.concat(b), []);
|
|
}
|
|
|
|
}
|
|
|
|
class AwaitChecker extends NodeVisitor {
|
|
private res = false;
|
|
|
|
visitAstNode(n: AstNode) {
|
|
if (!n) return;
|
|
this.visitChildren(n);
|
|
}
|
|
|
|
visitExprHolder(eh: ExprHolder) {
|
|
if (!eh) return;
|
|
|
|
this.dispatch(eh.parsed);
|
|
}
|
|
|
|
visitCall(n: Call) {
|
|
if (!n) return;
|
|
|
|
super.visitCall(n);
|
|
if (n.awaits()) this.res = true;
|
|
}
|
|
|
|
static isAwait(n: Stmt) {
|
|
if (!n) return;
|
|
|
|
var checker = new AwaitChecker();
|
|
checker.dispatch(n);
|
|
return checker.res;
|
|
}
|
|
}
|
|
|
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
// Visitor classes that enable Dataflow Analyses to walk through the AST
|
|
|
|
// PredecessorsFinder extracts the list of predecessors statements of another statement
|
|
// by walking the AST. These are the predecessors when converting the AST to a CFG.
|
|
// NOTE: Valid nodes for all dataflow analyses are Statement nodes
|
|
// that contain an ExprHolder instance (therefore, consumes an expression).
|
|
// PredecessorsFinder and SuccessorsFinder both walk through these nodes
|
|
// and bypasses the rest (Box es, for instance).
|
|
export class PredecessorsFinder
|
|
extends NodeVisitor {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
// Convenience methods
|
|
static enclosingStmt(stmt: Stmt): Stmt {
|
|
return stmt.parentBlock() && stmt.parentBlock().parent;
|
|
}
|
|
|
|
static asLoop(stmt: Stmt): Stmt {
|
|
if (stmt instanceof While || stmt instanceof For || stmt instanceof Foreach) return stmt;
|
|
else return null;
|
|
}
|
|
|
|
// Tries to dig the last statement of a block that belongs to the
|
|
// current stmt. If it does not contain a block, returns itself.
|
|
static unpeelAndGetLast(stmt: Stmt): Stmt[]{
|
|
var ret: Stmt[] = [];
|
|
// Comments and placeholders are not excluded from the dataflow
|
|
// visiting path. They just propagate the information through,
|
|
// but we need to handle them.
|
|
if (stmt instanceof Comment) {
|
|
ret = [stmt];
|
|
} else if (stmt instanceof For) {
|
|
ret = ret.concat(PredecessorsFinder.lastInCodeBlock((<For>stmt).body));
|
|
} else if (stmt instanceof Foreach) {
|
|
ret = ret.concat(PredecessorsFinder.lastInCodeBlock((<Foreach>stmt).body));
|
|
} else if (stmt instanceof While) {
|
|
ret = ret.concat(PredecessorsFinder.lastInCodeBlock((<While>stmt).body));
|
|
} else if (stmt instanceof If) {
|
|
Util.assert(!((<If>stmt).isElseIf));
|
|
// "If" nodes contains many last statements, one for each block. This should
|
|
// always return at least two nodes, even when else is empty - in this
|
|
// case it returns the placeholder.
|
|
ret = ret.concat(Util.concatArraysVA((<If>stmt).bodies().map(PredecessorsFinder.lastInCodeBlock)))
|
|
} else if (stmt instanceof Box) {
|
|
ret = ret.concat(PredecessorsFinder.lastInCodeBlock((<Box>stmt).body));
|
|
} else if (stmt instanceof ExprStmt) {
|
|
ret = [stmt];
|
|
}
|
|
if (ret.length == 0)
|
|
ret = [stmt];
|
|
return ret;
|
|
}
|
|
|
|
// Find the previous IF statement (used only to find predecessors
|
|
// of ifelse conditions).
|
|
private findPreviousIf(ifstmt: If): Stmt[] {
|
|
Util.assert(!!ifstmt && !!(ifstmt.parentBlock()));
|
|
|
|
var container = ifstmt.parentBlock().stmts;
|
|
var ix = container.indexOf(ifstmt);
|
|
|
|
--ix;
|
|
|
|
Util.assert(ix >= 0);
|
|
var previous = container[ix];
|
|
Util.assert(previous instanceof If);
|
|
return [previous];
|
|
}
|
|
|
|
// Find the previous statement considering the enclosing block
|
|
private previousInBlock(stmt: Stmt): Stmt[] {
|
|
if (!stmt || !stmt.parentBlock()) return [];
|
|
|
|
var container = stmt.parentBlock().stmts;
|
|
var ix = container.indexOf(stmt);
|
|
var enclosing = PredecessorsFinder.enclosingStmt(stmt);
|
|
|
|
--ix;
|
|
|
|
var ret = [];
|
|
|
|
if (ix < 0) {
|
|
// We are the first statement in a block
|
|
// The previous is the loop condition check or if statement,
|
|
// if they exist. Otherwise, we need to dig further and go for
|
|
// the previous statement of the enclosing block.
|
|
var l = PredecessorsFinder.asLoop(enclosing);
|
|
if (!!l || (enclosing instanceof If)) {
|
|
ret = ret.concat(enclosing);
|
|
} else if (enclosing instanceof InlineAction) {
|
|
ret = [];
|
|
} else {
|
|
ret = ret.concat(this.previousInBlock(enclosing));
|
|
}
|
|
} else {
|
|
var prev = container[ix];
|
|
// In case of a block of statements, we need to unpeel and go
|
|
// inside it. For loops, the loop itself is the statement.
|
|
if ((prev instanceof For)
|
|
|| (prev instanceof While)
|
|
|| (prev instanceof Foreach)) {
|
|
ret = ret.concat([prev]);
|
|
} else if ((prev instanceof If)
|
|
&& ((<If>prev).isElseIf)) {
|
|
ret = ret.concat(PredecessorsFinder.unpeelAndGetLast((<If>prev).parentIf));
|
|
} else {
|
|
ret = ret.concat(PredecessorsFinder.unpeelAndGetLast(prev));
|
|
}
|
|
if (ret.length == 0)
|
|
ret = this.previousInBlock(prev);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// Extracts the last statement in a code block. Useful for finding
|
|
// predecessors of loop structures.
|
|
static lastInCodeBlock(body: CodeBlock): Stmt[] {
|
|
if (body == null) return [];
|
|
|
|
var enclosing = PredecessorsFinder.enclosingStmt(body);
|
|
var container = body.stmts;
|
|
var ix = container.length - 1;
|
|
|
|
if (ix < 0) {
|
|
return [];
|
|
}
|
|
|
|
var last = container[ix];
|
|
|
|
// In case of a block of statements, we need to unpeel and go
|
|
// inside it. For loops, the loop itself is the statement.
|
|
if ((last instanceof For)
|
|
|| (last instanceof While)
|
|
|| (last instanceof Foreach))
|
|
return [last];
|
|
if (last instanceof If && (<If>last).isElseIf) {
|
|
return PredecessorsFinder.unpeelAndGetLast((<If>last).parentIf);
|
|
}
|
|
return PredecessorsFinder.unpeelAndGetLast(last);
|
|
}
|
|
|
|
public visitCodeBlock(body: CodeBlock): Stmt[] {
|
|
return PredecessorsFinder.lastInCodeBlock(body);
|
|
}
|
|
|
|
// The predecessor of a loop is the pair of the previous statement in
|
|
// its enclosing block and the last statement inside the loop
|
|
private visitLoop(stmt: Stmt): Stmt[] {
|
|
if (stmt == null) return [];
|
|
|
|
var ret = this.previousInBlock(<Stmt><any>stmt);
|
|
return ret.concat(PredecessorsFinder.unpeelAndGetLast(stmt));
|
|
}
|
|
|
|
public visitFor(stmt: For): Stmt[] { return this.visitLoop(stmt); }
|
|
public visitForeach(stmt: Foreach): Stmt[] { return this.visitLoop(stmt); }
|
|
public visitWhile(stmt: While): Stmt[] { return this.visitLoop(stmt); }
|
|
|
|
// The predecessor of a regular statement is simply the previous
|
|
// statement in the block
|
|
public visitStmt(stmt: Stmt): Stmt[] {
|
|
return this.previousInBlock(stmt);
|
|
}
|
|
|
|
// The predecessor of an If is simply the previous statement in its
|
|
// enclosing block.
|
|
public visitIf(stmt: If): Stmt[]{
|
|
Util.assert(!stmt.isElseIf);
|
|
return this.previousInBlock(stmt);
|
|
}
|
|
// If it is an Ifelse, then its predecessor is the previous If.
|
|
public visitElseIf(n: If) {
|
|
return this.findPreviousIf(n);
|
|
}
|
|
|
|
// Returns the list of predecessor statements for "stmt". Uses a
|
|
// visitor to handle different node types.
|
|
static find(stmt: Stmt): Stmt[] {
|
|
if (!stmt) return [];
|
|
|
|
return new PredecessorsFinder().dispatch(stmt);
|
|
}
|
|
}
|
|
|
|
// Used for dataflow equations, analogous to the PredecessorFinder.
|
|
// Predecessor and Successors Finders must satisfy the property that
|
|
// Succs[ Preds[x] ] = x
|
|
// Otherwise analyses will break. Successors are not just used in backward
|
|
// analysis, but also in regular forward analysis in order to discover
|
|
// which nodes to analyze when its Outs[] set is update, and vice-versa.
|
|
//
|
|
// NOTE: Valid nodes for all dataflow analyses are Statement nodes
|
|
// that contain an ExprHolder instance (therefore, consumes an expression).
|
|
// PredecessorsFinder and SuccessorsFinder both walk through these nodes
|
|
// and bypasses the rest (Box es, for instance).
|
|
export class SuccessorsFinder
|
|
extends NodeVisitor {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
// Convenience methods
|
|
static enclosingStmt(stmt: Stmt): Stmt {
|
|
return stmt.parentBlock() && stmt.parentBlock().parent;
|
|
}
|
|
|
|
static asLoop(stmt: Stmt): Stmt {
|
|
if (stmt instanceof While || stmt instanceof For || stmt instanceof Foreach) return stmt;
|
|
else return null;
|
|
}
|
|
|
|
// This is not the same as "unpeelAndGetLast" of Predecessors because
|
|
// it rarely needs to actually unpeel and get the statements inside a
|
|
// block. The reason is that the first statement of a "If" or loop node
|
|
// are really the "If" or loop themselves, except for Boxes.
|
|
static unpeelAndGetFirst(stmt: Stmt): Stmt[]{
|
|
var ret: Stmt[] = [];
|
|
if (stmt instanceof Comment) {
|
|
ret = ret.concat(stmt);
|
|
} else if (stmt instanceof For)
|
|
ret = ret.concat(stmt);
|
|
else if (stmt instanceof Foreach) {
|
|
ret = ret.concat(stmt);
|
|
} else if (stmt instanceof While) {
|
|
ret = ret.concat(stmt);
|
|
} else if (stmt instanceof If) {
|
|
ret = ret.concat(stmt);
|
|
} else if (stmt instanceof Box) {
|
|
ret = ret.concat(SuccessorsFinder.firstInCodeBlock((<Box>stmt).body));
|
|
} else if (stmt instanceof ExprStmt) {
|
|
ret = ret.concat(stmt);
|
|
}
|
|
if (ret.length == 0)
|
|
ret = [stmt];
|
|
return ret;
|
|
}
|
|
|
|
// Find the next IF statement (used only to find successors
|
|
// of if/ifelse nodes).
|
|
private findNextIf(ifstmt: If): Stmt[] {
|
|
Util.assert(!!ifstmt && !!(ifstmt.parentBlock()));
|
|
|
|
var container = ifstmt.parentBlock().stmts;
|
|
var ix = container.indexOf(ifstmt);
|
|
|
|
++ix;
|
|
|
|
if (ix >= container.length)
|
|
return [];
|
|
var next = container[ix];
|
|
if (!(next instanceof If) || !((<If>next).isElseIf))
|
|
return [];
|
|
return [next];
|
|
}
|
|
|
|
// clone of nextInBlock, but jumps over IFELSE stmts.
|
|
// Goes to the next stmt after a sequence of ifelse. Useful to
|
|
// jump to the end of the structure when finding the successors of
|
|
// the last statement of a codeblock inside an if.
|
|
private jumpToEndOfIfElse(ifstmt: Stmt): Stmt[] {
|
|
Util.assert(!!ifstmt && !!(ifstmt.parentBlock()));
|
|
|
|
var container = ifstmt.parentBlock().stmts;
|
|
var ix = container.indexOf(ifstmt);
|
|
var enclosing = PredecessorsFinder.enclosingStmt(ifstmt);
|
|
|
|
++ix;
|
|
while (container[ix] && container[ix] instanceof If && (<If>(container[ix])).isElseIf) ++ix;
|
|
|
|
var ret = [];
|
|
if (ix >= container.length) {
|
|
// We are the last statement, so we can only find the next
|
|
// statement looking for our parents: it is either the
|
|
// enclosing loop or the successor of our parent. NOTE: We do not
|
|
// jump directly from the last statement of a loop to outside
|
|
// the loop: it must first go to the loop node to check the
|
|
// condition, therefore we only have a single successor in
|
|
// this case.
|
|
var l = SuccessorsFinder.asLoop(enclosing);
|
|
if (!!l) {
|
|
ret = ret.concat(enclosing);
|
|
} else if (enclosing instanceof If) {
|
|
ret = ret.concat(this.jumpToEndOfIfElse(enclosing));
|
|
} else if (enclosing instanceof InlineAction) {
|
|
ret = [];
|
|
} else {
|
|
ret = ret.concat(this.nextInBlock(enclosing));
|
|
}
|
|
} else {
|
|
var next = container[ix];
|
|
// If this is a statement that contains statements, we want its children,
|
|
// but first check if it is an statement that contains an ExprHolder
|
|
ret = ret.concat(SuccessorsFinder.unpeelAndGetFirst(next));
|
|
if (ret.length == 0)
|
|
ret = this.nextInBlock(next);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Look for the next statement considering its enclosing block.
|
|
private nextInBlock(stmt: Stmt): Stmt[] {
|
|
if (!stmt || !stmt.parentBlock()) return [];
|
|
|
|
var container = stmt.parentBlock().stmts;
|
|
var ix = container.indexOf(stmt);
|
|
var enclosing = PredecessorsFinder.enclosingStmt(stmt);
|
|
|
|
++ix;
|
|
|
|
|
|
var ret = [];
|
|
if (ix >= container.length) {
|
|
// We are the last statement, so we can only find the next
|
|
// statement looking for our parents: it is either the
|
|
// enclosing loop or the successor of our parent. NOTE: We do not
|
|
// jump directly from the last statement of a loop to outside
|
|
// the loop: it must first go to the loop node to check the
|
|
// condition, therefore we only have a single successor in
|
|
// this case.
|
|
var l = SuccessorsFinder.asLoop(enclosing);
|
|
if (!!l) {
|
|
ret = ret.concat(enclosing);
|
|
} else if (enclosing instanceof If) {
|
|
ret = ret.concat(this.jumpToEndOfIfElse(enclosing));
|
|
} else if (enclosing instanceof InlineAction) {
|
|
ret = [];
|
|
} else {
|
|
ret = ret.concat(this.nextInBlock(enclosing));
|
|
}
|
|
} else {
|
|
var next = container[ix];
|
|
// If this is a statement that contains statements, we want its children,
|
|
// but first check if it is an statement that contains an ExprHolder
|
|
ret = ret.concat(SuccessorsFinder.unpeelAndGetFirst(next));
|
|
if (ret.length == 0)
|
|
ret = this.nextInBlock(next);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Extracts the first statement in a code block, useful for finding
|
|
// successors of loop structures.
|
|
static firstInCodeBlock(body: CodeBlock): Stmt[] {
|
|
if (body == null) return [];
|
|
|
|
var container = body.stmts;
|
|
var ix = 0;
|
|
var enclosing = SuccessorsFinder.enclosingStmt(body);
|
|
|
|
if (ix >= container.length) {
|
|
// nothing useful inside
|
|
return [];
|
|
}
|
|
|
|
var first = container[ix];
|
|
return SuccessorsFinder.unpeelAndGetFirst(first);
|
|
}
|
|
|
|
public visitCodeBlock(body: CodeBlock): Stmt[] {
|
|
return SuccessorsFinder.firstInCodeBlock(body);
|
|
}
|
|
|
|
// The successors of a loop is the pair of the first statement in its
|
|
// body and the next statement after the loop, since the execution flow
|
|
// skips the loop body after its condition evaluates to false.
|
|
private visitLoop(stmt: Stmt): Stmt[] {
|
|
if (stmt == null) return [];
|
|
|
|
var ret = this.nextInBlock(stmt);
|
|
return ret.concat(this.visitCodeBlock((<LoopStmt><any>stmt).body));
|
|
}
|
|
public visitFor(stmt: For): Stmt[] { return this.visitLoop(stmt); }
|
|
public visitForeach(stmt: Foreach): Stmt[] { return this.visitLoop(stmt); }
|
|
public visitWhile(stmt: While): Stmt[] { return this.visitLoop(stmt); }
|
|
|
|
// The succ for a generic statement is the next stmt in its enclosing
|
|
// block.
|
|
public visitStmt(stmt: Stmt): Stmt[] {
|
|
return this.nextInBlock(stmt);
|
|
}
|
|
|
|
// Successors of the "If" node are the first statements of its child
|
|
// code blocks ("then" and "else"). If the else path includes another
|
|
// condition check (ifelse node), then the first statement of "then"
|
|
// block and the next ifelse node are the successors.
|
|
public visitIf(stmt: If): Stmt[]{
|
|
var ret = SuccessorsFinder.firstInCodeBlock(stmt.rawThenBody);
|
|
if (stmt.displayElse)
|
|
return ret.concat(SuccessorsFinder.firstInCodeBlock(stmt.rawElseBody));
|
|
return ret.concat(this.findNextIf(stmt));
|
|
}
|
|
public visitElseIf(n: If) { return this.visitIf(n); }
|
|
|
|
// Returns the list of successor statements for "stmt". Only returns
|
|
// "valid" nodes as described in the note above (Statement nodes that
|
|
// contains ExprHolder instances).
|
|
static find(stmt: Stmt): Stmt[] {
|
|
if (!stmt) return [];
|
|
|
|
return new SuccessorsFinder().dispatch(stmt);
|
|
}
|
|
}
|
|
|
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
// Dataflow set data strucutes
|
|
|
|
// An element may be anything worth storing in Ins/Outs sets for each point
|
|
// of the program and depends on the analysis. Reaching definitions will
|
|
// store the stringified def expression as the "key" and a reference to the
|
|
// def expression itself. "Id" is bookkeeping maintained by the
|
|
// SetElementsPool class.
|
|
export class SetElement {
|
|
constructor(public id: number, public key: string, public refNode: Expr) { }
|
|
}
|
|
|
|
// The pool keeps the elements alive and assign the lowest possible id
|
|
// to reference them, and Set instances reference them using a lightweight
|
|
// bitset representation. If IDs are large, more memory will be necessary
|
|
// to represent the bitset.
|
|
// After an analysis is done, a pool contains all elements generated by
|
|
// Gen equations.
|
|
class SetElementsPool {
|
|
private map: { [s: string]: number; };
|
|
private pool: SetElement[];
|
|
|
|
constructor(private curId = 0) {
|
|
this.map = {};
|
|
this.pool = [];
|
|
}
|
|
|
|
// Builds a new SetElement and assign to it the lowest possible id. Get
|
|
// it if an element with the same id already exists.
|
|
public getElm(key: string, refNode: Expr): SetElement {
|
|
var idx = this.map[["a_", key].join("")];
|
|
if (idx == undefined) {
|
|
idx = this.curId++;
|
|
var elm = new SetElement(idx, key, refNode);
|
|
this.pool.push(elm);
|
|
this.map[["a_", key].join("")] = idx;
|
|
}
|
|
return this.pool[idx];
|
|
}
|
|
|
|
public getElmById(id: number): SetElement {
|
|
if (id >= this.pool.length) return null;
|
|
return this.pool[id];
|
|
}
|
|
|
|
public size(): number {
|
|
return this.pool.length;
|
|
}
|
|
|
|
}
|
|
|
|
// The memory representation of all Ins/Outs sets for all points of the
|
|
// programs.
|
|
export class BitSet {
|
|
private _myset: number[];
|
|
private setSize: number;
|
|
// largestIndex is important for bookkeeping and directly reflects
|
|
// our current size, signaling when it should be expanded. It is also
|
|
// used to limit which indexes to traverse when forEach is called for
|
|
// an allSet set (that is supposed to contain all known elements).
|
|
private largestIndex: number;
|
|
|
|
// The allSet flag is meant to be used in a set that is supposed to
|
|
// start containing all possible elements. Therefore, it expands
|
|
// with 1s, meaning it contains even those elements that it has never
|
|
// seen before. Useful for intersection-confluence analyses.
|
|
constructor(public allSet = false) {
|
|
this._myset = [];
|
|
if (allSet)
|
|
this._myset.push(0xFFFFFFFF);
|
|
else
|
|
this._myset.push(0);
|
|
this.setSize = 1;
|
|
this.largestIndex = -1;
|
|
}
|
|
|
|
// Helper function to expand the set when a large index is used
|
|
// to access an element that is currently not being represented.
|
|
private growSet(idx: number): void {
|
|
idx -= this.setSize * 32;
|
|
while (idx >= 0) {
|
|
if (this.allSet)
|
|
this._myset.push(0xFFFFFFFF);
|
|
else
|
|
this._myset.push(0);
|
|
++this.setSize;
|
|
idx -= 32;
|
|
}
|
|
}
|
|
|
|
private cloneSet(): number[]{
|
|
var a: number[] = [];
|
|
for (var i = 0; i < this.setSize; ++i) {
|
|
a.push(this._myset[i]);
|
|
}
|
|
return a;
|
|
}
|
|
|
|
// Makes this an allSet set (contains all elements).
|
|
public makeAllSet(): void {
|
|
this._myset = [0xFFFFFFFF];
|
|
this.setSize = 1;
|
|
this.allSet = true;
|
|
this.largestIndex = -1;
|
|
}
|
|
|
|
public add(elm: number): void {
|
|
if (elm >= this.setSize * 32)
|
|
this.growSet(elm);
|
|
if (elm > this.largestIndex)
|
|
this.largestIndex = elm;
|
|
this._myset[Math.floor(elm / 32)] |= 1 << elm % 32;
|
|
}
|
|
|
|
public setLargestIndex(idx: number): void {
|
|
if (idx > this.setSize * 32)
|
|
this.growSet(idx);
|
|
this.largestIndex = idx;
|
|
}
|
|
|
|
public remove(elm: number): void {
|
|
if (elm >= this.setSize * 32)
|
|
this.growSet(elm);
|
|
if (elm > this.largestIndex)
|
|
this.largestIndex = elm;
|
|
this._myset[Math.floor(elm / 32)] &= ~(1 << elm % 32);
|
|
}
|
|
|
|
public contains(elm: number): boolean {
|
|
if (elm >= this.setSize * 32)
|
|
this.growSet(elm);
|
|
if (elm > this.largestIndex)
|
|
this.largestIndex = elm;
|
|
return !!(this._myset[Math.floor(elm / 32)] & (1 << elm % 32));
|
|
}
|
|
|
|
public union(a: BitSet): void {
|
|
if (a.largestIndex > this.largestIndex) {
|
|
this.setLargestIndex(a.largestIndex);
|
|
} else if (this.largestIndex > a.largestIndex) {
|
|
a.setLargestIndex(this.largestIndex);
|
|
}
|
|
for (var i = 0; i < a.setSize; ++i) {
|
|
this._myset[i] |= a._myset[i];
|
|
}
|
|
if (a.allSet)
|
|
this.allSet = true;
|
|
}
|
|
|
|
public intersection(a: BitSet): void {
|
|
if (a.largestIndex > this.largestIndex) {
|
|
this.setLargestIndex(a.largestIndex);
|
|
} else if (this.largestIndex > a.largestIndex) {
|
|
a.setLargestIndex(this.largestIndex);
|
|
}
|
|
for (var i = 0; i < a.setSize; ++i) {
|
|
this._myset[i] &= a._myset[i];
|
|
}
|
|
if (this.allSet && !a.allSet)
|
|
this.allSet = false;
|
|
}
|
|
|
|
public forEach(cb: (elm: number) => void ): void {
|
|
if (this.largestIndex < 0)
|
|
return;
|
|
for (var i = 0; i <= this.largestIndex / 32; ++i) {
|
|
var idx = i * 32;
|
|
var val = this._myset[i];
|
|
while (val != 0 && idx <= this.largestIndex) {
|
|
if (val & 1)
|
|
cb(idx);
|
|
++idx; val = val >>> 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
public clone(): BitSet {
|
|
var a = new BitSet(this.allSet);
|
|
a._myset = this.cloneSet();
|
|
a.setSize = this.setSize;
|
|
a.largestIndex = this.largestIndex;
|
|
return a;
|
|
}
|
|
|
|
public equals(a: BitSet): boolean {
|
|
if ((this.allSet && !a.allSet) || (!this.allSet && a.allSet))
|
|
return false;
|
|
if (a.largestIndex > this.largestIndex) {
|
|
this.setLargestIndex(a.largestIndex);
|
|
} else if (this.largestIndex > a.largestIndex) {
|
|
a.setLargestIndex(this.largestIndex);
|
|
}
|
|
for (var i = 0; i < a.setSize; ++i) {
|
|
if (this._myset[i] != a._myset[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
// Dataflow framework classes
|
|
|
|
// Every dataflow analysis should implement this interface.
|
|
// DataflowVisitor will call your analysis for every Statement that
|
|
// contains expressions (ExprHolder instances) to calculate gen/kill
|
|
// sets.
|
|
export interface IDataflowAnalysis {
|
|
// Change _set to reflect the In set of the entry node of
|
|
// the action (or exit node if backwards analysis).
|
|
buildStartNodeSet(a: Action, _set: BitSet): void;
|
|
// All sets are intialized by cloning the set specified by this
|
|
// function. You should start with an allSet set for analysis that
|
|
// use intersection confluence.
|
|
buildStartingSet(a: Action, _set: BitSet): void;
|
|
// Change _set to reflect Gen[n], where n is an expression.
|
|
// inSet: the original set before Kill kicked in
|
|
// isIV: true when this expression does not actually exist, but was
|
|
// generated to simulate the behavior experienced by induction
|
|
// variables of "For" nodes.
|
|
gen(n: ExprHolder, _set: BitSet, inSet: BitSet, isIV: boolean): void;
|
|
// Change _set to reflext Kill[n], where n is an expression.
|
|
// isIV: true when this expression does not actually exist, but was
|
|
// generated to simulate the behavior experienced by induction
|
|
// variables when "For" nodes are executed.
|
|
kill(n: ExprHolder, _set: BitSet, isIV: boolean): void;
|
|
// Attach the calculated information from expression "n" in node "s",
|
|
// which owns this expression. This should be the result of this
|
|
// analysis and remains attached to the AST.
|
|
updateNode(s: Stmt, n: ExprHolder): void;
|
|
}
|
|
|
|
// DataflowVisitor manages all common duties of a dataflow analysis, leaving
|
|
// only the gen/kill sets calculation for the analysis itself. It works once
|
|
// per Action and starts by determining the order of nodes to visit in this
|
|
// action, but only consider nodes that are Statement and that contains
|
|
// expressions (ExprHolder instances), skipping other nodes.
|
|
// Uses BitSet for union and intersection set operations, topological sort
|
|
// for the initial visit order and a worklist for the remaining visits
|
|
// NOTE: If the analysis is backwards, notice that Ins/Outs sets are
|
|
// reversed.
|
|
class DataflowVisitor
|
|
extends NodeVisitor {
|
|
private worklist: Stmt[] = [];
|
|
public Ins: { [k: number]: BitSet; } = {};
|
|
public Outs: { [k: number]: BitSet; } = {};
|
|
private startingSet: BitSet;
|
|
private startNodeSet: BitSet;
|
|
private starting: boolean = false;
|
|
private changed: boolean = false;
|
|
private visited: { [id: number]: boolean; } = {};
|
|
|
|
// df: Reference to the actual Analysis that will be called for each
|
|
// expression.
|
|
// pool: Reference to the pool to create/reference elements of sets.
|
|
// backwards: direction, true if backwards.
|
|
// intersection: confluence operator, false if union, true if
|
|
// intersection
|
|
// useWorklist: true if uses a worklist to visit nodes, false will
|
|
// use the blind algorithm of revisiting all nodes each time a
|
|
// modification is detected.
|
|
constructor(public df: IDataflowAnalysis, public pool: SetElementsPool,
|
|
public backwards: boolean = false, public intersection: boolean = false,
|
|
public useWorklist = true) {
|
|
super();
|
|
}
|
|
|
|
// Keep the same id for generated assignzero expressions
|
|
private genZeroMap: { [name: string]: Expr; } = {};
|
|
|
|
// Helper function to generate an expression that mimics the behavior
|
|
// of a "For" node, initializing the induction variable with zero.
|
|
private generateAssignZero(l: LocalDef): Expr {
|
|
var res = this.genZeroMap[["d_", l.getName()].join("")];
|
|
if (res != undefined)
|
|
return res;
|
|
var localThing = mkThing(l.getName());
|
|
(<ThingRef>localThing).def = l;
|
|
var initialVal = mkLit(0);
|
|
res = mkCall(PropertyRef.mkProp(api.core.AssignmentProp),
|
|
[localThing, initialVal]);
|
|
this.genZeroMap[["d_", l.getName()].join("")] = res;
|
|
return res;
|
|
}
|
|
|
|
// Keep the same id for generated inc expressions
|
|
private genIncMap: { [name: string]: Expr; } = {};
|
|
|
|
// Helper function to generate an expression that mimics the behavior
|
|
// of a "For" node, incrementing the induction variable
|
|
private generateInc(l: LocalDef): Expr {
|
|
var res = this.genIncMap[["d_", l.getName()].join("")];
|
|
if (res != undefined)
|
|
return res;
|
|
var localThing = mkThing(l.getName());
|
|
(<ThingRef>localThing).def = l;
|
|
var incVal = mkLit(1);
|
|
var sum = mkCall(PropertyRef.mkProp(api.core.Number.getProperty("+")),
|
|
[localThing, incVal]);
|
|
res = mkCall(PropertyRef.mkProp(api.core.AssignmentProp),
|
|
[localThing, sum]);
|
|
this.genIncMap[["d_", l.getName()].join("")] = res;
|
|
return res;
|
|
}
|
|
|
|
public visitAstNode(node: AstNode): any {
|
|
this.visitChildren(node);
|
|
}
|
|
|
|
// This is the core of DataflowVisitor and handles our subject of
|
|
// interest: AST Statements that contains ExprHolder instances.
|
|
private visitExprHolderHolder(stmt: Stmt) {
|
|
var preds = this.backwards ? AST.SuccessorsFinder.find(stmt) : AST.PredecessorsFinder.find(stmt);
|
|
var newIn: BitSet;
|
|
if (this.starting) {
|
|
newIn = this.startNodeSet.clone();
|
|
this.starting = false;
|
|
} else {
|
|
var validPreds = 0;
|
|
newIn = this.intersection ? new BitSet(/*allset*/true) : new BitSet();
|
|
preds.forEach((x: Stmt) => {
|
|
var nOut = this.Outs[x.nodeId];
|
|
if (nOut != undefined) {
|
|
++validPreds;
|
|
if (this.intersection)
|
|
newIn.intersection(nOut);
|
|
else
|
|
newIn.union(nOut);
|
|
}
|
|
});
|
|
if (validPreds == 0) {
|
|
newIn = this.startingSet.clone();
|
|
}
|
|
}
|
|
this.Ins[stmt.nodeId] = newIn;
|
|
var newOut = newIn.clone();
|
|
|
|
// Calculate kill/gen sets. Ignore placeholders.
|
|
if (stmt instanceof ExprStmt && !stmt.isPlaceholder()) {
|
|
this.df.kill((<ExprStmt>stmt).expr, newOut, false);
|
|
this.df.gen((<ExprStmt>stmt).expr, newOut, newIn, false);
|
|
} else if (!!stmt.calcNode()) {
|
|
// A "For" node implicitly updates the induction variable each
|
|
// time it is run, and some analyses need to know about this.
|
|
// We generate fake expressions that represent this behavior.
|
|
if (stmt instanceof For) {
|
|
var forInitialAssgn = exprToStmt(this.generateAssignZero((<For>stmt).boundLocal));
|
|
var forIncAssgn = exprToStmt(this.generateInc((<For>stmt).boundLocal));
|
|
this.df.kill(forInitialAssgn.expr, newOut, true);
|
|
this.df.kill(forIncAssgn.expr, newOut, true);
|
|
this.df.gen(forIncAssgn.expr, newOut, newIn, true);
|
|
this.df.gen(forInitialAssgn.expr, newOut, newIn, true);
|
|
}
|
|
this.df.kill(stmt.calcNode(), newOut, false);
|
|
this.df.gen(stmt.calcNode(), newOut, newIn, false);
|
|
}
|
|
|
|
// Check to see if we are stuck at a fixed point or if we need
|
|
// to continue running the analysis for succs nodes.
|
|
var oldOut = this.Outs[stmt.nodeId];
|
|
if (!oldOut || !oldOut.equals(newOut)) {
|
|
if (this.useWorklist) {
|
|
if (this.backwards) {
|
|
AST.PredecessorsFinder.find(stmt).forEach((s: Stmt) => {
|
|
this.worklist.push(s);
|
|
this.visited[s.nodeId] = false;
|
|
});
|
|
} else {
|
|
AST.SuccessorsFinder.find(stmt).forEach((s: Stmt) => {
|
|
this.worklist.push(s);
|
|
this.visited[s.nodeId] = false;
|
|
});
|
|
}
|
|
} else {
|
|
this.changed = true;
|
|
}
|
|
this.Outs[stmt.nodeId] = newOut;
|
|
}
|
|
// Attach the analysis info into the AST
|
|
if (stmt instanceof ExprStmt) {
|
|
this.df.updateNode(stmt, (<ExprStmt>stmt).expr);
|
|
} else if (!!stmt.calcNode()) {
|
|
this.df.updateNode(stmt, stmt.calcNode());
|
|
}
|
|
|
|
if (!this.useWorklist)
|
|
this.visitChildren(stmt);
|
|
}
|
|
|
|
visitComment(n: Comment) {
|
|
this.visitExprHolderHolder(n);
|
|
}
|
|
visitFor(n: For) {
|
|
this.visitExprHolderHolder(n);
|
|
}
|
|
visitIf(n: If) {
|
|
this.visitExprHolderHolder(n);
|
|
}
|
|
visitElseIf(n: If) {
|
|
this.visitExprHolderHolder(n);
|
|
}
|
|
visitForeach(n: Foreach) {
|
|
this.visitExprHolderHolder(n);
|
|
}
|
|
visitWhile(n: While) {
|
|
this.visitExprHolderHolder(n);
|
|
}
|
|
visitExprStmt(n: ExprStmt) {
|
|
this.visitExprHolderHolder(n);
|
|
}
|
|
|
|
// Non-recursive version of a topological sort to order our first
|
|
// visit to the Action's nodes. Recursive versions are simpler
|
|
// and more elegant but crash on shallow stack mobile Safari browsers.
|
|
private topologicalSort(a: Action) {
|
|
var incomingEdges : { [id: number]: number; } = { };
|
|
var visited: { [id: number]: boolean; } = {};
|
|
var noIncomingEdges: { [id: number]: boolean; } = {};
|
|
var idToNode: { [id: number]: Stmt; } = {};
|
|
var numNodesVisited = 0;
|
|
|
|
// Populate map with frequency of incoming edges
|
|
a.body.forEach((s: Stmt) => {
|
|
var todo = this.backwards ? PredecessorsFinder.unpeelAndGetLast(s) :
|
|
SuccessorsFinder.unpeelAndGetFirst(s);
|
|
while (todo.length > 0) {
|
|
var cur = todo.shift();
|
|
if (visited[cur.nodeId])
|
|
continue;
|
|
visited[cur.nodeId] = true;
|
|
++numNodesVisited;
|
|
idToNode[cur.nodeId] = cur;
|
|
if (!(incomingEdges[cur.nodeId] > 0)) {
|
|
noIncomingEdges[cur.nodeId] = true;
|
|
}
|
|
var succs = this.backwards ? AST.PredecessorsFinder.find(cur)
|
|
: AST.SuccessorsFinder.find(cur);
|
|
succs.forEach((x: ExprStmt) => {
|
|
if (incomingEdges[x.nodeId] == undefined)
|
|
incomingEdges[x.nodeId] = 1;
|
|
else
|
|
incomingEdges[x.nodeId]++;
|
|
noIncomingEdges[x.nodeId] = false;
|
|
todo.push(x);
|
|
});
|
|
}
|
|
});
|
|
|
|
// Get nodes with no incoming edges
|
|
var s: Stmt[] = [];
|
|
for (var key in noIncomingEdges) {
|
|
if (noIncomingEdges[key]) {
|
|
var node = idToNode[key];
|
|
Util.assert(node !== undefined);
|
|
s.push(node);
|
|
}
|
|
}
|
|
|
|
// Sort
|
|
while (this.worklist.length < numNodesVisited) {
|
|
if (s.length == 0) {
|
|
var found = false;
|
|
for (var key in visited) {
|
|
var cand = idToNode[key];
|
|
if (cand !== undefined && incomingEdges[key] > 0) {
|
|
incomingEdges[key] = 0;
|
|
s.push(cand);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
Util.assert(found);
|
|
}
|
|
var cur = s.shift();
|
|
this.worklist.push(cur);
|
|
var succs = this.backwards ? AST.PredecessorsFinder.find(cur)
|
|
: AST.SuccessorsFinder.find(cur);
|
|
for (var i = 0; i < succs.length; ++i) {
|
|
var x = succs[i];
|
|
if (--incomingEdges[x.nodeId] == 0)
|
|
s.unshift(x);
|
|
else if (incomingEdges[x.nodeId] > 0 && i == succs.length - 1 && s.length == 0) {
|
|
// break the cycle
|
|
incomingEdges[x.nodeId] = 0;
|
|
s.push(x);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private dfTesting(s: Stmt) {
|
|
// Testing to ensure x C Succs[Preds[x]]
|
|
var preds = AST.PredecessorsFinder.find(s);
|
|
var succs;
|
|
for (var i = 0; i < preds.length; ++i) {
|
|
var elmi = preds[i];
|
|
succs = AST.SuccessorsFinder.find(elmi);
|
|
var found = false;
|
|
for (var j = 0; j < succs.length; ++j) {
|
|
var elmj = succs[j];
|
|
if (elmj === s)
|
|
found = true;
|
|
}
|
|
Util.assert(found);
|
|
}
|
|
// Testing to ensure x C Preds[Succs[x]]
|
|
succs = AST.SuccessorsFinder.find(s);
|
|
for (var i = 0; i < succs.length; ++i) {
|
|
var elmi2 = succs[i];
|
|
preds = AST.PredecessorsFinder.find(elmi2);
|
|
var found = false;
|
|
for (var j = 0; j < preds.length; ++j) {
|
|
var elmj2 = preds[j];
|
|
if (elmj2 === s)
|
|
found = true;
|
|
}
|
|
Util.assert(found);
|
|
}
|
|
}
|
|
|
|
// Entry point for the dataflow analysis. Initialize all data
|
|
// structures and start visiting the statements of Action n.
|
|
visitAction(n: Action) {
|
|
if (n instanceof LibraryRefAction)
|
|
return;
|
|
if (n.isPage())
|
|
return;
|
|
|
|
if (this.useWorklist) {
|
|
this.worklist = [];
|
|
this.topologicalSort(n);
|
|
this.visited = {};
|
|
this.startNodeSet = new BitSet();
|
|
this.df.buildStartNodeSet(n, this.startNodeSet);
|
|
this.startingSet = new BitSet();
|
|
this.df.buildStartingSet(n, this.startingSet);
|
|
this.starting = true;
|
|
while (this.worklist.length > 0) {
|
|
var cur = this.worklist.shift();
|
|
if (this.visited[cur.nodeId])
|
|
continue;
|
|
this.visited[cur.nodeId] = true;
|
|
//this.dfTesting(cur); // Check if preds/succs are alright
|
|
cur.accept(this);
|
|
}
|
|
} else {
|
|
this.startNodeSet = new BitSet();
|
|
this.df.buildStartNodeSet(n, this.startNodeSet);
|
|
this.startingSet = new BitSet();
|
|
this.df.buildStartingSet(n, this.startingSet);
|
|
this.changed = true;
|
|
this.starting = true;
|
|
while (this.changed) {
|
|
this.changed = false;
|
|
n.body.forEach((c) => {
|
|
c.accept(this);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
// Dataflow Analyses
|
|
|
|
// * REACHING DEFINITIONS *
|
|
|
|
// Motivation: RD seeks to eliminate "ok checks" that slows down script
|
|
// execution in IE and Firefox by checking if the invalid definition
|
|
// reaches an expression.
|
|
// Flags to activate: options.okElimination, URL ?okElimination
|
|
|
|
// Manages Reaching Definitions data attached to the AST as the result
|
|
// of the analysis.
|
|
export class ReachingDefsMgr {
|
|
constructor(public defs: Expr[], public node: Stmt) { }
|
|
|
|
private getLocal(e: Token): String {
|
|
if (e instanceof ThingRef) {
|
|
var d = (<ThingRef>e).def;
|
|
if (d instanceof LocalDef) return (<LocalDef>d).getName();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public toString(): string {
|
|
return "RD #" + this.node.nodeId + ": {" + this.defs.join() + "}";
|
|
}
|
|
|
|
// The compiler will ask whether this definition may be invalid, at
|
|
// this point of the program. If it may be, then it needs to put an
|
|
// "ok check".
|
|
public mayBeInvalid(d: LocalDef): boolean {
|
|
var ret = false;
|
|
this.defs.forEach((e: Expr) => {
|
|
var refcall = <Call>e;
|
|
if (refcall.prop() != api.core.AssignmentProp)
|
|
return;
|
|
refcall.args[0].flatten(api.core.TupleProp).forEach((a) => {
|
|
var l = this.getLocal(a);
|
|
if (l && l == d.getName()) {
|
|
if (refcall.args.length > 1) {
|
|
refcall.args.slice(1).forEach((val: Expr) => {
|
|
// check for invalid
|
|
if (val instanceof Call &&
|
|
(<Call>val).args.length > 0 &&
|
|
(<Call>val).args[0] instanceof ThingRef &&
|
|
(<ThingRef>(<Call>val).args[0]).data == "invalid") {
|
|
ret = true;
|
|
}
|
|
// check for non-robust call
|
|
if (val instanceof Call &&
|
|
!((<Call>val).prop().getFlags() & PropertyFlags.Robust)) {
|
|
ret = true;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
// ReachingDefinitions is the classic analysis to compute local variable
|
|
// definitions that reaches a certain point. It is used by the compiler
|
|
// to detect whether an "invalid" definition reaches some point, in which
|
|
// case we need to emit an "ok check" to dynamically detect if it is truly
|
|
// invalid value and stop the script before it is used.
|
|
//
|
|
// Main characteristics: Forward, union confluence
|
|
export class ReachingDefinitions
|
|
implements IDataflowAnalysis {
|
|
private df: DataflowVisitor;
|
|
private curNode: Stmt;
|
|
private pool: SetElementsPool;
|
|
constructor() {
|
|
}
|
|
|
|
// Convenience methods
|
|
private getLocal(e: Token): LocalDef {
|
|
if (e instanceof ThingRef) {
|
|
var d = (<ThingRef>e).def;
|
|
if (d instanceof LocalDef) return <LocalDef>d;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private getLocalName(e: Token): String {
|
|
var ld = this.getLocal(e);
|
|
if (ld != null) {
|
|
return ld.getName();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Helper function to calculate the Gen set when the expression
|
|
// is a variable copy. We need to copy all the definitions of the
|
|
// source variable to the destination variable.
|
|
private handleCopy(c: Call, _set: BitSet): boolean {
|
|
var bypassGen = false;
|
|
// Check for a regular copy
|
|
if (c.prop() == api.core.AssignmentProp
|
|
&& c.args.length == 2
|
|
&& c.args[0] instanceof ThingRef
|
|
&& c.args[1] instanceof ThingRef) {
|
|
var dst = this.getLocal(c.args[0]);
|
|
var src = this.getLocal(c.args[1]);
|
|
if (src != null && dst != null) {
|
|
_set.forEach((idx: number) => {
|
|
var elm = this.pool.getElmById(idx);
|
|
var refcall = <Call> elm.refNode;
|
|
if (refcall) {
|
|
refcall.args[0].flatten(api.core.TupleProp).forEach((a) => {
|
|
var l2 = this.getLocalName(a);
|
|
if (l2 == src.getName()) {
|
|
var newCall = this.cloneAndChangeDst(refcall, dst);
|
|
var newElm = this.pool.getElm(newCall.getText(), newCall);
|
|
_set.add(newElm.id);
|
|
bypassGen = true; // we dont need to keep "x = y" definitions
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
// Check for a conditional copy (or/and)
|
|
if (c.prop() == api.core.AssignmentProp
|
|
&& c.args.length == 2
|
|
&& c.args[0] instanceof ThingRef
|
|
&& c.args[1] instanceof Call) {
|
|
var dst = this.getLocal(c.args[0]);
|
|
var srcCall = <Call> c.args[1];
|
|
if (dst != null && srcCall != null
|
|
&& srcCall.args.length == 2
|
|
&& (srcCall.prop() == api.core.AndProp ||
|
|
srcCall.prop() == api.core.OrProp)
|
|
&& srcCall.args[1] instanceof ThingRef) {
|
|
var src = this.getLocal(srcCall.args[1]);
|
|
if (src != null && dst != null) {
|
|
_set.forEach((idx: number) => {
|
|
var elm = this.pool.getElmById(idx);
|
|
var refcall = <Call> elm.refNode;
|
|
if (refcall) {
|
|
refcall.args[0].flatten(api.core.TupleProp).forEach((a) => {
|
|
var l2 = this.getLocalName(a);
|
|
if (l2 == src.getName()) {
|
|
var newCall = this.cloneAndChangeDst(refcall, dst);
|
|
var newElm = this.pool.getElm(newCall.getText(), newCall);
|
|
_set.add(newElm.id);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return bypassGen;
|
|
}
|
|
|
|
// The core function of this Analysis, calculates the GEN and KILL sets
|
|
// for the expression "n". Updates "_set" to reflect this.
|
|
private updateSet(n: ExprHolder, _set: BitSet, genKill: boolean): void {
|
|
if (!n.parsed || !(n.parsed instanceof Call))
|
|
return;
|
|
var c = <Call>n.parsed;
|
|
//... Traverse nodes non-recursively
|
|
var exprVisitQueue: Expr[] = [];
|
|
exprVisitQueue.push(c);
|
|
while (exprVisitQueue.length > 0) {
|
|
var cur = exprVisitQueue.shift();
|
|
if (!(cur instanceof Call))
|
|
continue;
|
|
var curCall = <Call> cur;
|
|
exprVisitQueue = exprVisitQueue.concat(curCall.children());
|
|
var prop = curCall.prop();
|
|
// We are only looking for assignments to local variables
|
|
if (prop == api.core.AssignmentProp) {
|
|
c.args[0].flatten(api.core.TupleProp).forEach((e) => {
|
|
var l = this.getLocalName(e);
|
|
if (l) {
|
|
if (genKill) { // Generate
|
|
// First check if it is a local copy expression
|
|
if (!this.handleCopy(curCall, _set)) {
|
|
// If it is not, put this assignment into the Out set
|
|
var elm = this.pool.getElm(curCall.getText(), curCall);
|
|
_set.add(elm.id);
|
|
}
|
|
} else { // Remove all defs of the same var from the In set
|
|
_set.forEach((idx: number) => {
|
|
var elm = this.pool.getElmById(idx);
|
|
var refcall = <Call> elm.refNode;
|
|
var remove = false;
|
|
if (refcall) {
|
|
refcall.args[0].flatten(api.core.TupleProp).forEach((a) => {
|
|
var l2 = this.getLocalName(a);
|
|
if (l2 == l)
|
|
remove = true;
|
|
});
|
|
}
|
|
if (remove)
|
|
_set.remove(idx);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wrappers for updateSet
|
|
gen(n: ExprHolder, _set: BitSet, inSet: BitSet, isIV = false): void {
|
|
this.updateSet(n, _set, true);
|
|
}
|
|
|
|
kill(n: ExprHolder, _set: BitSet, isIV = false): void {
|
|
this.updateSet(n, _set, false);
|
|
}
|
|
|
|
// Extract our calculated RD set by using Set and SetElementsPool data
|
|
// structure, then stick this into the AST.
|
|
updateNode(s: Stmt, n: ExprHolder) {
|
|
n.reachingDefs = null;
|
|
var defs:Expr[] = [];
|
|
this.df.Ins[s.nodeId].forEach((idx: number) => {
|
|
defs.push(this.pool.getElmById(idx).refNode);
|
|
});
|
|
if (defs.length > 0)
|
|
n.reachingDefs = new ReachingDefsMgr(defs, s);
|
|
}
|
|
|
|
// Helper function to generate a fake invalid assignment.
|
|
private generateInvalidAssignmentFor(l: LocalDef): Expr {
|
|
var localThing = mkThing(l.getName());
|
|
(<ThingRef>localThing).def = l;
|
|
var invalidThing = mkThing("invalid");
|
|
(<ThingRef>invalidThing).def = api.getKind("Invalid").singleton;
|
|
return mkCall(PropertyRef.mkProp(api.core.AssignmentProp),
|
|
[localThing, mkCall(PropertyRef.mkProp(api.getKind("Invalid").getProperty("number")), [invalidThing])]);
|
|
}
|
|
|
|
// Helper function used when handling copy expressions, necessary
|
|
// when copying all the definitions of the source variable to the
|
|
// destination variable.
|
|
private cloneAndChangeDst(c: Call, dst: LocalDef): Expr {
|
|
var args = c.args.slice(0);
|
|
args[0] = mkThing(dst.getName());
|
|
(<ThingRef>args[0]).def = dst;
|
|
return mkCall(c.propRef, args);
|
|
}
|
|
|
|
// Our start node for the action assigns all input parameters to
|
|
// invalid, assuming (conservatively) that they are undefined.
|
|
buildStartNodeSet(a: Action, _set: BitSet): void {
|
|
a.allLocals.forEach((e: Decl) => {
|
|
if (!(e instanceof LocalDef))
|
|
return;
|
|
var l = (<LocalDef>e).getName();
|
|
var elm = this.pool.getElm(l, this.generateInvalidAssignmentFor(<LocalDef>e));
|
|
_set.add(elm.id);
|
|
});
|
|
}
|
|
|
|
// All nodes start empty in this analysis.
|
|
buildStartingSet(a: Action, _set: BitSet): void {
|
|
}
|
|
|
|
// Entry point for this analysis. Analyze the App Action-wise.
|
|
visitApp(n: App) {
|
|
n.things.forEach((a: Decl) => {
|
|
if (a instanceof Action && !(<Action>a).isPage()) {
|
|
this.pool = new SetElementsPool();
|
|
this.df = new DataflowVisitor(this, this.pool,/*backwards*/false, /*intersection*/false, /*useWorklist*/true);
|
|
this.df.dispatch(a);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// * DOMINATOR SET *
|
|
|
|
// Motivation:
|
|
// The Dominator Set Analysis is used only to debug intersection-confluence
|
|
// analysis, since it is the simplest analysis you can build with the
|
|
// intersection confluence operator. May be used as boilerplate code for
|
|
// other analyses as well.
|
|
// Flags to activate: no one
|
|
|
|
export class DominatorsMgr {
|
|
constructor(public doms: Expr[], public node: Stmt) { }
|
|
|
|
public toString() {
|
|
var str = "DOMS(" + this.node.nodeId + ") : {";
|
|
str += this.doms.map((e: Expr) => {
|
|
return e.nodeId.toString();
|
|
}).join();
|
|
return str + "}";
|
|
}
|
|
}
|
|
|
|
// Dominators will compute the set of statements that dominate each
|
|
// other. A statement x dominates y if all paths from the start of
|
|
// the action to y includes x.
|
|
//
|
|
// Main characteristics: Forward, intersection confluence
|
|
export class Dominators
|
|
implements IDataflowAnalysis {
|
|
private df: DataflowVisitor;
|
|
private curNode: Stmt;
|
|
private pool: SetElementsPool;
|
|
constructor() {
|
|
}
|
|
|
|
// Our gen set is simply our own expression id.
|
|
gen(n: ExprHolder, _set: BitSet, inSet: BitSet, isIV = false): void {
|
|
if (!n.parsed)
|
|
return;
|
|
var elm = this.pool.getElm(n.parsed.nodeId.toString(), n.parsed);
|
|
_set.add(elm.id);
|
|
}
|
|
|
|
// We do not need to kill. The intersection confluence operator takes
|
|
// care of eliminating IDs that do not dominate this statement.
|
|
kill(n: ExprHolder, _set: BitSet, isIV = false): void {
|
|
}
|
|
|
|
// Put our calculated set into the AST node of this statement
|
|
updateNode(s: Stmt, n: ExprHolder) {
|
|
n.dominators = null;
|
|
var doms: Expr[] = [];
|
|
this.df.Ins[s.nodeId].forEach((idx: number) => {
|
|
doms.push(this.pool.getElmById(idx).refNode);
|
|
});
|
|
if (doms.length > 0)
|
|
n.dominators = new DominatorsMgr(doms, s);
|
|
}
|
|
|
|
// We assume no one dominates the first node.
|
|
buildStartNodeSet(a: Action, _set: BitSet): void {
|
|
}
|
|
|
|
// All nodes must be initialized with the set that contains all
|
|
// elements (allSet), otherwise we will not reach the maximum
|
|
// fixed point solution.
|
|
buildStartingSet(a: Action, _set: BitSet): void {
|
|
_set.makeAllSet();
|
|
}
|
|
|
|
// Entry point for this analysis. Analyze the App Action-wise.
|
|
visitApp(n: App) {
|
|
n.things.forEach((a: Decl) => {
|
|
if (a instanceof Action && !(<Action>a).isPage()) {
|
|
this.pool = new SetElementsPool();
|
|
this.df = new DataflowVisitor(this, this.pool,/*backwards*/false, /*intersection*/true, /*useWorklist*/true);
|
|
this.df.dispatch(a);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// * USED SET ANALYSIS *
|
|
|
|
// Motivation:
|
|
// ReachingDefinitions is not enough to eliminate all unnecessary "ok
|
|
// checks". Since all checks are done once a value is used, there are
|
|
// time in which the program already used the value and thus it was
|
|
// already checked, and all the remaining checks may be eliminated.
|
|
// Uset set analysis calculates the set of local variables that were
|
|
// already used at a certain point of the program, but were not
|
|
// redefined since the last use, therefore, enabling us to remove
|
|
// redundant "ok checks".
|
|
// Flags to activate: options.okElimination, URL ?okElimination
|
|
|
|
// Manages the information we stick into the AST statament nodes.
|
|
export class UsedSetMgr {
|
|
constructor(public used: Expr[], public node: Stmt) { }
|
|
|
|
// Convenience methods accessible for everyone
|
|
public static getLocal(e: Token): LocalDef {
|
|
if (e instanceof ThingRef) {
|
|
var d = (<ThingRef>e).def;
|
|
if (d instanceof LocalDef) return <LocalDef>d;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static getLocalName(e: Token): String {
|
|
var ld = this.getLocal(e);
|
|
if (ld != null) {
|
|
return ld.getName();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// The compiler asks whether the local ld was already used at this
|
|
// point in order to eliminate redundant "ok checks".
|
|
public alreadyUsed(ld: LocalDef): boolean {
|
|
var res = false;
|
|
this.used.forEach((e: Expr) => {
|
|
var name1 = UsedSetMgr.getLocalName(e);
|
|
if (name1 && name1 == ld.getName()) {
|
|
res = true;
|
|
}
|
|
});
|
|
return res;
|
|
}
|
|
|
|
// Debugging
|
|
public toString() {
|
|
var str = "USED(" + this.node.nodeId + ") : {";
|
|
str += this.used.map((e: Expr) => {
|
|
return UsedSetMgr.getLocalName(e);
|
|
}).join();
|
|
return str + "}";
|
|
}
|
|
}
|
|
|
|
// UsedAnalysis main class
|
|
//
|
|
// Main characteristics: Forward, intersection confluence
|
|
// NOTE: gen/kill functions are reversed in order to compensate for the
|
|
// calling order of DataflowVisitor.
|
|
export class UsedAnalysis
|
|
implements IDataflowAnalysis {
|
|
private df: DataflowVisitor;
|
|
private curNode: Stmt;
|
|
private pool: SetElementsPool;
|
|
constructor() {
|
|
}
|
|
|
|
// This is actually the kill set. Since, for UsedAnalysis, we need to
|
|
// kill after generation (as opposed to the common case), we reverse
|
|
// the gen/kill functions.
|
|
gen(n: ExprHolder, _set: BitSet, inSet: BitSet, isIV = false): void {
|
|
// We are only interested in Call expressions
|
|
if (!n.parsed || !(n.parsed instanceof Call))
|
|
return;
|
|
// .. Traverse nodes non-recursively
|
|
var c = <Call>n.parsed;
|
|
var exprVisitQueue: Expr[] = [];
|
|
exprVisitQueue.push(c);
|
|
while (exprVisitQueue.length > 0) {
|
|
var cur = exprVisitQueue.shift();
|
|
if (!(cur instanceof Call))
|
|
continue;
|
|
var curCall = <Call> cur;
|
|
exprVisitQueue = exprVisitQueue.concat(curCall.children());
|
|
var prop = curCall.prop();
|
|
// We are only interested in assignments
|
|
if (prop == api.core.AssignmentProp) {
|
|
c.args[0].flatten(api.core.TupleProp).forEach((e) => {
|
|
var l = UsedSetMgr.getLocalName(e);
|
|
if (l) {
|
|
{ // Kill all uses of the var that was defined
|
|
// in this expression, since it was redefined
|
|
_set.forEach((idx: number) => {
|
|
var elm = this.pool.getElmById(idx);
|
|
if (elm.key == l)
|
|
_set.remove(idx);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is actually the gen set. We look for all uses of local
|
|
// variables and add them to the set.
|
|
kill(n: ExprHolder, _set: BitSet, isIV = false): void {
|
|
if (!n.parsed)
|
|
return;
|
|
var exprVisitQueue: Expr[] = [];
|
|
exprVisitQueue.push(n.parsed);
|
|
while (exprVisitQueue.length > 0) {
|
|
var e = exprVisitQueue.shift();
|
|
var refcall = <Call>e;
|
|
if (!(e instanceof Call)) {
|
|
if (e instanceof ThingRef) {
|
|
var ed = (<ThingRef> e).def;
|
|
if (ed instanceof LocalDef) {
|
|
var elm = this.pool.getElm((<LocalDef>ed).getName(), e);
|
|
_set.add(elm.id);
|
|
}
|
|
}
|
|
} else {
|
|
if (refcall.prop() && refcall.prop().getName() == "is invalid") {
|
|
// x->is_invalid is not a use of x
|
|
} else if (refcall.prop() != api.core.AssignmentProp) {
|
|
exprVisitQueue = exprVisitQueue.concat(refcall.children());
|
|
} else {
|
|
exprVisitQueue = exprVisitQueue.concat(refcall.args.slice(1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Put our calculated UsedSet into the AST, so the compiler can query
|
|
// later.
|
|
updateNode(s: Stmt, n: ExprHolder) {
|
|
n.usedSet = null;
|
|
var usedSet: Expr[] = [];
|
|
this.df.Ins[s.nodeId].forEach((idx: number) => {
|
|
usedSet.push(this.pool.getElmById(idx).refNode);
|
|
});
|
|
if (usedSet.length > 0)
|
|
n.usedSet = new UsedSetMgr(usedSet, s);
|
|
}
|
|
|
|
// Our start node begins with no used variables.
|
|
buildStartNodeSet(a: Action, _set: BitSet): void {
|
|
}
|
|
|
|
// All remaining nodes are initialized with allSet, necessary for
|
|
// intersection analyses.
|
|
buildStartingSet(a: Action, _set: BitSet): void {
|
|
_set.makeAllSet();
|
|
}
|
|
|
|
// Entry point for this analysis. Analyze the App Action-wise.
|
|
visitApp(n: App) {
|
|
n.things.forEach((a: Decl) => {
|
|
if (a instanceof Action && !(<Action>a).isPage()) {
|
|
this.pool = new SetElementsPool();
|
|
this.df = new DataflowVisitor(this, this.pool,/*backwards*/false, /*intersection*/true, /*useWorklist*/true);
|
|
this.df.dispatch(a);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// * AVAILABLE EXPRESSIONS *
|
|
|
|
// Motivation:
|
|
// The Available Expressions classic analysis is used to detect
|
|
// opportunities for common subexpression elimination. Since the compiler
|
|
// may separate an action into several steps, each one being a different
|
|
// native JavaScript function, the JIT engine will miss optimization
|
|
// opportunities beyond the step granularity, and this analysis can help
|
|
// in these cases by performing global common subexpression elimination.
|
|
// Flags to activate: options.commonSubexprElim, URL ?commonSubexprElim
|
|
|
|
export class AvailableExpressionsMgr {
|
|
constructor(public aeSet: Expr[], public node: Stmt) { }
|
|
|
|
// Helper method to check if the entire expression is idempotent,
|
|
// which means there is no effect in recalculating it or not.
|
|
private hasOnlyIdempotentNodes(e: Expr) {
|
|
var exprVisitQueue: Expr[] = [];
|
|
exprVisitQueue.push(e);
|
|
while (exprVisitQueue.length > 0) {
|
|
var e = exprVisitQueue.shift();
|
|
var refcall = <Call>e;
|
|
if (!(e instanceof Call)) {
|
|
if (!(e instanceof Literal || (e instanceof ThingRef
|
|
&& (<ThingRef>e).def.nodeType() == "localDef")))
|
|
return false;
|
|
} else {
|
|
if (refcall.prop() != api.core.AssignmentProp &&
|
|
refcall.prop().getFlags() & PropertyFlags.Idempotent) {
|
|
exprVisitQueue = exprVisitQueue.concat(refcall.children());
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// The compiler will ask if this expression was already calculated at
|
|
// this point and if they are idempotent. Returns the set of all such
|
|
// expressions.
|
|
public checkForIdenticalExpressions(e: Expr): Expr[]{
|
|
var res: Expr[] = [];
|
|
if (e == undefined)
|
|
return;
|
|
this.aeSet.forEach((ae: Expr) => {
|
|
if (ae.toString() == e.toString()
|
|
&& this.hasOnlyIdempotentNodes(e))
|
|
res.push(ae);
|
|
});
|
|
return res;
|
|
}
|
|
|
|
// Debugging
|
|
public toString() {
|
|
var str = "AE(" + this.node.nodeId + ") : {";
|
|
str += this.aeSet.join();
|
|
return str + "}";
|
|
}
|
|
}
|
|
|
|
// Main characteriscs: Forward, intersection confluence
|
|
// NOTE: gen/kill functions are reversed in order to compensate for the
|
|
// calling order of DataflowVisitor.
|
|
export class AvailableExpressions
|
|
implements IDataflowAnalysis {
|
|
private df: DataflowVisitor;
|
|
private curNode: Stmt;
|
|
private pool: SetElementsPool;
|
|
constructor() {
|
|
}
|
|
|
|
// Helper function to detect whether expression "e" uses local variable
|
|
// "l"
|
|
private usesLocal(e: Expr, l: String): boolean {
|
|
var exprVisitQueue: Expr[] = [];
|
|
exprVisitQueue.push(e);
|
|
while (exprVisitQueue.length > 0) {
|
|
var e = exprVisitQueue.shift();
|
|
var refcall = <Call>e;
|
|
if (!(e instanceof Call)) {
|
|
if (UsedSetMgr.getLocalName(e) == l)
|
|
return true;
|
|
} else {
|
|
if (refcall.prop() != api.core.AssignmentProp) {
|
|
exprVisitQueue = exprVisitQueue.concat(refcall.children());
|
|
} else {
|
|
exprVisitQueue = exprVisitQueue.concat(refcall.args.slice(1));
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// This is actually the kill set. Since, for AvailableExpressions, we
|
|
// need to kill after generation (as opposed to the common case), we
|
|
// reverse the gen/kill functions.
|
|
gen(n: ExprHolder, _set: BitSet, inSet: BitSet, isIV = false): void {
|
|
if (isIV)
|
|
return;
|
|
if (!n.parsed || !(n.parsed instanceof Call))
|
|
return;
|
|
var c = <Call>n.parsed;
|
|
var exprVisitQueue: Expr[] = [];
|
|
exprVisitQueue.push(c);
|
|
while (exprVisitQueue.length > 0) {
|
|
var cur = exprVisitQueue.shift();
|
|
if (!(cur instanceof Call))
|
|
continue;
|
|
var curCall = <Call> cur;
|
|
exprVisitQueue = exprVisitQueue.concat(curCall.children());
|
|
var prop = curCall.prop();
|
|
if (prop == api.core.AssignmentProp) {
|
|
c.args[0].flatten(api.core.TupleProp).forEach((e) => {
|
|
var l = UsedSetMgr.getLocalName(e);
|
|
if (l) {
|
|
{ // Kill all expressions that the same var
|
|
_set.forEach((idx: number) => {
|
|
var elm = this.pool.getElmById(idx);
|
|
if (this.usesLocal(elm.refNode, l))
|
|
_set.remove(idx);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is actually the gen set. We look for all uses of local
|
|
// variables and add them to the set.
|
|
kill(n: ExprHolder, _set: BitSet, isIV = false): void {
|
|
if (isIV)
|
|
return;
|
|
if (!n.parsed)
|
|
return;
|
|
var exprVisitQueue: Expr[] = [];
|
|
exprVisitQueue.push(n.parsed);
|
|
while (exprVisitQueue.length > 0) {
|
|
var e = exprVisitQueue.shift();
|
|
var refcall = <Call>e;
|
|
if (e instanceof Call) {
|
|
if (refcall.prop() != api.core.AssignmentProp) {
|
|
var elm = this.pool.getElm(refcall.toString(), refcall);
|
|
_set.add(elm.id);
|
|
exprVisitQueue = exprVisitQueue.concat(refcall.args.slice(1));
|
|
} else {
|
|
exprVisitQueue = exprVisitQueue.concat(refcall.children());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Put our calculated UsedSet into the AST, so the compiler can query
|
|
// later.
|
|
updateNode(s: Stmt, n: ExprHolder) {
|
|
n.aeSet = null;
|
|
var aeSet: Expr[] = [];
|
|
this.df.Ins[s.nodeId].forEach((idx: number) => {
|
|
aeSet.push(this.pool.getElmById(idx).refNode);
|
|
});
|
|
if (aeSet.length > 0)
|
|
n.aeSet = new AvailableExpressionsMgr(aeSet, s);
|
|
}
|
|
|
|
// Our start node begins with no available expressions.
|
|
buildStartNodeSet(a: Action, _set: BitSet): void {
|
|
}
|
|
|
|
// All remaining nodes are initialized with allSet, necessary for
|
|
// intersection analyses.
|
|
buildStartingSet(a: Action, _set: BitSet): void {
|
|
_set.makeAllSet();
|
|
}
|
|
|
|
// Entry point for this analysis. Analyze the App Action-wise.
|
|
visitApp(n: App) {
|
|
n.things.forEach((a: Decl) => {
|
|
if (a instanceof Action && !(<Action>a).isPage()) {
|
|
this.pool = new SetElementsPool();
|
|
this.df = new DataflowVisitor(this, this.pool,/*backwards*/false, /*intersection*/true, /*useWorklist*/true);
|
|
this.df.dispatch(a);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// * CONSTANT FOLDING/PROPAGATION FRAMEWORK *
|
|
|
|
// Motivation:
|
|
// This analysis computes the set of locals defined as constants, yielding
|
|
// pairs <local, constant value>, and also folds an idempotent expression
|
|
// that works with constants. A pair <local, constant value> may be the
|
|
// result of many folded expressions. As with CSE, since the compiler
|
|
// may separate an action into several steps, each one being a different
|
|
// native JavaScript function, the JIT engine will miss optimization
|
|
// opportunities beyond the step granularity, and this analysis can help
|
|
// in these cases by performing global constant folding.
|
|
// Flags to activate: options.constantPropagation, URL ?constantPropagation
|
|
|
|
// Manages the information we stick into the AST statement nodes. It also
|
|
// has the logic to perform folding for selected idempotent operators.
|
|
export class ConstantPropagationMgr {
|
|
constructor(public constantSet: Expr[], public node: Stmt) { }
|
|
|
|
// Helper function.
|
|
// Get the constant value of the local "d" by using the information
|
|
// computed for this point of the program.
|
|
public getLiteralValueFor(d: LocalDef) {
|
|
var res = null;
|
|
this.constantSet.forEach((e: Expr) => {
|
|
if (e instanceof Call
|
|
&& (<Call>e).prop() == api.core.AssignmentProp
|
|
&& (<Call>e).args.length == 2
|
|
&& (<Call>e).args[0] instanceof ThingRef
|
|
&& (<ThingRef>((<Call>e).args[0])).def instanceof LocalDef
|
|
&& (<LocalDef>((<ThingRef>((<Call>e).args[0])).def)).getName() == d.getName()
|
|
&& (<Call>e).args[1] instanceof Literal
|
|
&& (<Literal>((<Call>e).args[1])).data != null)
|
|
res = (<Literal>((<Call>e).args[1])).data;
|
|
});
|
|
return res;
|
|
}
|
|
|
|
// Helper function.
|
|
// Avoids using JavaScript's "eval" and implement our own function
|
|
// to evaluate expressions. Return null if we don't know how to
|
|
// calculate this at compile time.
|
|
public static evaluate(c: Call, args: any[]) {
|
|
if (c.prop().getCategory() == PropertyCategory.Builtin) {
|
|
if (c.prop().getSpecialApply() == "+")
|
|
return args[0] + args[1];
|
|
else if (c.prop().getSpecialApply() == "-")
|
|
return args[0] - args[1];
|
|
else if (c.prop().getSpecialApply() == "/")
|
|
return args[0] / args[1];
|
|
else if (c.prop().getSpecialApply() == "*")
|
|
return args[0] * args[1];
|
|
else if (c.prop().getSpecialApply() == "===")
|
|
return args[0] === args[1];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Entry point for expression evaluation. By using the information
|
|
// available at this point of the program (pairs <local, value>),
|
|
// tries to evaluate the result of the expression "e". If it is
|
|
// possible to know this at compile time, returns the value,
|
|
// otherwise returns null.
|
|
public precomputeLiteralExpression(e: Expr) {
|
|
var visitExpr = (exp: Expr) => {
|
|
var refcall = <Call>exp;
|
|
if (!(exp instanceof Call)) {
|
|
if (exp instanceof Literal)
|
|
return (<Literal>exp).data;
|
|
if (exp instanceof ThingRef
|
|
&& (<ThingRef>exp).def.nodeType() == "localDef"
|
|
&& this.getLiteralValueFor(<LocalDef>(<ThingRef>exp).def) != null)
|
|
return this.getLiteralValueFor(<LocalDef>(<ThingRef>exp).def);
|
|
} else {
|
|
if (refcall.prop() != api.core.AssignmentProp &&
|
|
refcall.prop().getFlags() & PropertyFlags.Idempotent) {
|
|
var values = refcall.children().map((ea: Expr) => visitExpr(ea));
|
|
var hasNull = false;
|
|
values.forEach((ea: Expr) => {
|
|
if (ea == null) hasNull = true;
|
|
});
|
|
if (!hasNull) {
|
|
return ConstantPropagationMgr.evaluate(refcall, values);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
return visitExpr(e);
|
|
}
|
|
|
|
// Debugging purposes
|
|
public toString() {
|
|
var str = "CP(" + this.node.nodeId + ") : {";
|
|
str += this.constantSet.join();
|
|
return str + "}";
|
|
}
|
|
}
|
|
|
|
// ConstantPropagation will compute gen/kill sets to achieve the maximum
|
|
// number of constant folding/propagation for locals of the action. It
|
|
// uses ConstantPropagationMgr methods to help fold expressions with
|
|
// literal leaves, which are also used by the compiler when emitting
|
|
// code. This differs from the other analysis because they leave their
|
|
// Manager to be used solely by the compiler.
|
|
//
|
|
// Main characteristics: Forward, intersection confluence
|
|
export class ConstantPropagation
|
|
implements IDataflowAnalysis {
|
|
private df: DataflowVisitor;
|
|
private curNode: Stmt;
|
|
private pool: SetElementsPool;
|
|
constructor() {
|
|
}
|
|
|
|
// Helper function to generate a fake assignment of literal "value"
|
|
// to local "l". We need to generate these assingments to express
|
|
// the result of folding, since they don't really exist in the original
|
|
// source code.
|
|
private generateAssignmentFor(l: LocalDef, value: any): Expr {
|
|
var localThing = mkThing(l.getName());
|
|
(<ThingRef>localThing).def = l;
|
|
var literal = mkLit(value);
|
|
return mkCall(PropertyRef.mkProp(api.core.AssignmentProp),
|
|
[localThing, literal]);
|
|
}
|
|
|
|
// Helper function to handle copy assignments
|
|
private cloneAndChangeDst(c: Call, dst: LocalDef): Expr {
|
|
var args = c.args.slice(0);
|
|
args[0] = mkThing(dst.getName());
|
|
(<ThingRef>args[0]).def = dst;
|
|
return mkCall(c.propRef, args);
|
|
}
|
|
|
|
// Convenience methods
|
|
private getLocal(e: Token): LocalDef {
|
|
if (e instanceof ThingRef) {
|
|
var d = (<ThingRef>e).def;
|
|
if (d instanceof LocalDef) return <LocalDef>d;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private getLocalName(e: Token): String {
|
|
var ld = this.getLocal(e);
|
|
if (ld != null) {
|
|
return ld.getName();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Handle copy. Copies the literal value of src to dst.
|
|
private handleCopy(c: Call, _set: BitSet): boolean {
|
|
var bypassGen = false;
|
|
// Check for a regular copy
|
|
if (c.prop() == api.core.AssignmentProp
|
|
&& c.args.length == 2
|
|
&& c.args[0] instanceof ThingRef
|
|
&& c.args[1] instanceof ThingRef) {
|
|
var dst = this.getLocal(c.args[0]);
|
|
var src = this.getLocal(c.args[1]);
|
|
if (src != null && dst != null) {
|
|
// Find the literal value of src, if any
|
|
_set.forEach((idx: number) => {
|
|
var elm = this.pool.getElmById(idx);
|
|
var refcall = <Call> elm.refNode;
|
|
if (refcall) {
|
|
refcall.args[0].flatten(api.core.TupleProp).forEach((a) => {
|
|
var l2 = this.getLocalName(a);
|
|
if (l2 == src.getName()) { // Found
|
|
// Copy to the destination generating a fake assingment
|
|
var newCall = this.cloneAndChangeDst(refcall, dst);
|
|
var newElm = this.pool.getElm(newCall.getText(), newCall);
|
|
_set.add(newElm.id);
|
|
bypassGen = true;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
return bypassGen;
|
|
}
|
|
|
|
// Entry point for generating/killing elements.
|
|
private updateSet(n: ExprHolder, _set: BitSet, genKill: boolean): void {
|
|
if (!n.parsed || !(n.parsed instanceof Call))
|
|
return;
|
|
var c = <Call>n.parsed;
|
|
// Traverse all nodes of the expression non-recursively
|
|
var exprVisitQueue: Expr[] = [];
|
|
exprVisitQueue.push(c);
|
|
while (exprVisitQueue.length > 0) {
|
|
var cur = exprVisitQueue.shift();
|
|
if (!(cur instanceof Call))
|
|
continue;
|
|
var curCall = <Call> cur;
|
|
exprVisitQueue = exprVisitQueue.concat(curCall.children());
|
|
var prop = curCall.prop();
|
|
// Look for assignments
|
|
if (prop == api.core.AssignmentProp) {
|
|
c.args[0].flatten(api.core.TupleProp).forEach((e) => {
|
|
var l = this.getLocalName(e);
|
|
if (l) {
|
|
if (genKill) { // Generate
|
|
if (!this.handleCopy(curCall, _set)) {
|
|
// Try to fold the src expression with the information we currently have
|
|
// at this point and, if successful, assign it to the local
|
|
var lit = (new ConstantPropagationMgr(this.generateCpSet(_set), null)).precomputeLiteralExpression(c.args[1]);
|
|
if (lit != null) {
|
|
var assgn = this.generateAssignmentFor(this.getLocal(e), lit);
|
|
var elm = this.pool.getElm(assgn.getText(), assgn);
|
|
_set.add(elm.id);
|
|
}
|
|
|
|
}
|
|
} else { /// Kill all locals that this expression redefined
|
|
_set.forEach((idx: number) => {
|
|
var elm = this.pool.getElmById(idx);
|
|
var refcall = <Call> elm.refNode;
|
|
var remove = false;
|
|
if (refcall) {
|
|
refcall.args[0].flatten(api.core.TupleProp).forEach((a) => {
|
|
var l2 = this.getLocalName(a);
|
|
if (l2 == l)
|
|
remove = true;
|
|
});
|
|
}
|
|
if (remove)
|
|
_set.remove(idx);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wrappers to updateSet
|
|
gen(n: ExprHolder, _set: BitSet, inSet: BitSet, isIV = false): void {
|
|
if (!isIV)
|
|
this.updateSet(n, _set, true);
|
|
}
|
|
|
|
kill(n: ExprHolder, _set: BitSet, isIV = false): void {
|
|
if (!isIV)
|
|
this.updateSet(n, _set, false);
|
|
}
|
|
|
|
// Helper function to compute the information the
|
|
// ConstantPropagationMgr instance needs, converting the elements
|
|
// of the BitSet into a regular JavaScript object array.
|
|
private generateCpSet(_set: BitSet): Expr[] {
|
|
var cpSet: Expr[] = [];
|
|
_set.forEach((idx: number) => {
|
|
cpSet.push(this.pool.getElmById(idx).refNode);
|
|
});
|
|
return cpSet;
|
|
}
|
|
|
|
// Update the AST with the info we calculated for this node,
|
|
// use generateCpSet
|
|
updateNode(s: Stmt, n: ExprHolder) {
|
|
var cpSet = this.generateCpSet(this.df.Ins[s.nodeId]);
|
|
n.cpSet = new ConstantPropagationMgr(cpSet, s);
|
|
}
|
|
|
|
// We start with no definitions
|
|
buildStartNodeSet(a: Action, _set: BitSet): void {
|
|
}
|
|
|
|
// Sets are initialized full
|
|
buildStartingSet(a: Action, _set: BitSet): void {
|
|
_set.makeAllSet();
|
|
}
|
|
|
|
// Entry point for this analysis. Analyze the App Action-wise.
|
|
visitApp(n: App) {
|
|
n.things.forEach((a: Decl) => {
|
|
if (a instanceof Action && !(<Action>a).isPage()) {
|
|
this.pool = new SetElementsPool();
|
|
this.df = new DataflowVisitor(this, this.pool,/*backwards*/false, /*intersection*/true, /*useWorklist*/true);
|
|
this.df.dispatch(a);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
// Non-Dataflow Analyses
|
|
|
|
// * INLINE ANALYSIS *
|
|
|
|
// Motivation:
|
|
// The inline analysis looks for actions that can be inlined and flags them
|
|
// to the compiler. This means these actions will be called as native
|
|
// JavaScript functions in the final code, bypassing the interpreter and
|
|
// saving time.
|
|
// Flags to activate: options.inlining or URL: ?inlining
|
|
|
|
export class CallGraphNode
|
|
{
|
|
constructor(public action: Action, public succs: CallGraphNode[], public preds: CallGraphNode[],
|
|
public canInline: boolean = false) { }
|
|
|
|
public addSucc(succ: CallGraphNode)
|
|
{
|
|
for (var i = 0; i < this.succs.length; ++i)
|
|
{
|
|
if (this.succs[i] == succ)
|
|
return;
|
|
}
|
|
this.succs.push(succ);
|
|
}
|
|
|
|
public addPred(pred: CallGraphNode)
|
|
{
|
|
for (var i = 0; i < this.preds.length; ++i)
|
|
{
|
|
if (this.preds[i] == pred)
|
|
return;
|
|
}
|
|
this.preds.push(pred);
|
|
}
|
|
|
|
}
|
|
|
|
// InlineAnalysis (multi-level)
|
|
//
|
|
// Step1: Build a callgraph for the App using the
|
|
// visitor pattern. While it is visiting each Action, it checks not only
|
|
// for calls that helps to build the callgraph, but also for statements
|
|
// that precludes an action from being inlined (i.e. loop statements).
|
|
//
|
|
// Step2: Afterwards, it sorts the callgraph using topological
|
|
// sort, allowing it to easily perform a bottom-up traversal of the tree.
|
|
// Then, it marks actions as inlined if they satisfy some
|
|
// predefined properties in "actionHasDesiredProperties()", if it doesn't
|
|
// have any unwanted statements and finally if all the actions it calls
|
|
// are also inlined.
|
|
export class InlineAnalysis
|
|
extends NodeVisitor {
|
|
public hasChanged = false;
|
|
|
|
private nodeMap: { [s: string]: CallGraphNode; };
|
|
private nodes: CallGraphNode[];
|
|
private nowVisiting: CallGraphNode;
|
|
|
|
constructor() {
|
|
super();
|
|
this.nodes = [];
|
|
this.nodeMap = {};
|
|
}
|
|
|
|
visitAstNode(n: AstNode) {
|
|
this.visitChildren(n);
|
|
}
|
|
|
|
visitExprHolder(n: ExprHolder) {
|
|
if (n.parsed)
|
|
this.dispatch(n.parsed);
|
|
this.visitChildren(n);
|
|
}
|
|
|
|
// Unwanted statements in an inlined action
|
|
visitFor(n: For) {
|
|
this.nowVisiting.canInline = false;
|
|
this.visitChildren(n);
|
|
}
|
|
visitForeach(n: Foreach) {
|
|
this.nowVisiting.canInline = false;
|
|
this.visitChildren(n);
|
|
}
|
|
visitWhile(n: While) {
|
|
this.nowVisiting.canInline = false;
|
|
this.visitChildren(n);
|
|
}
|
|
visitBox(n: Box) {
|
|
this.nowVisiting.canInline = false;
|
|
this.visitChildren(n);
|
|
}
|
|
visitForeachClause(n: ForeachClause) {
|
|
this.nowVisiting.canInline = false;
|
|
this.visitChildren(n);
|
|
}
|
|
visitInlineActions(n: InlineActions) {
|
|
this.nowVisiting.canInline = false;
|
|
this.visitChildren(n);
|
|
}
|
|
visitInlineAction(n: InlineAction) {
|
|
this.nowVisiting.canInline = false;
|
|
this.visitChildren(n);
|
|
}
|
|
visitOptionalParameter(n: OptionalParameter) {
|
|
this.nowVisiting.canInline = false;
|
|
this.visitChildren(n);
|
|
}
|
|
|
|
// Build a new edge of the callgraph if calling another action
|
|
visitCall(n: Call) {
|
|
var a = n.calledAction();
|
|
var prop = n.prop();
|
|
// Check for unwanted calls
|
|
if (prop && prop.getCategory() == PropertyCategory.Library) {
|
|
this.nowVisiting.canInline = false;
|
|
return;
|
|
}
|
|
if (prop && prop.shouldPauseInterperter()) {
|
|
this.nowVisiting.canInline = false;
|
|
return;
|
|
}
|
|
if (prop && prop.getName() == "run" && prop.parentKind.isAction) {
|
|
this.nowVisiting.canInline = false;
|
|
return;
|
|
}
|
|
if (a == null) {
|
|
this.visitChildren(n);
|
|
return;
|
|
}
|
|
if (a instanceof LibraryRefAction) {
|
|
this.nowVisiting.canInline = false;
|
|
return;
|
|
}
|
|
// This is a call to another action inside this App, create the
|
|
// callgraph node if not available and create the edge to it.
|
|
var cgNode = this.nodeMap[["a_", a.getName()].join("")];
|
|
if (cgNode == undefined) {
|
|
cgNode = new CallGraphNode(a, [], []);
|
|
this.nodeMap[["a_", a.getName()].join("")] = cgNode;
|
|
this.nodes.push(cgNode);
|
|
}
|
|
this.nowVisiting.addSucc(cgNode);
|
|
cgNode.addPred(this.nowVisiting);
|
|
this.visitChildren(n);
|
|
}
|
|
|
|
// Check if this action can be inlined (apart from the callgraph
|
|
// analysis)
|
|
actionHasDesiredProperties(a: Action) {
|
|
return (a.isNormalAction()
|
|
&& a.isPrivate
|
|
&& !a.isLambda
|
|
&& !a.isTest()
|
|
&& a.isAtomic
|
|
&& a.getOutParameters().length <= 1);
|
|
}
|
|
|
|
// Create a new callgraph node to this action and start visiting it.
|
|
visitAction(n: Action) {
|
|
if (n instanceof LibraryRefAction)
|
|
return;
|
|
var cgNode = this.nodeMap[["a_", n.getName()].join("")];
|
|
if (cgNode == undefined) {
|
|
cgNode = new CallGraphNode(n, [], []);
|
|
this.nodeMap[["a_", n.getName()].join("")] = cgNode;
|
|
this.nodes.push(cgNode);
|
|
}
|
|
this.nowVisiting = cgNode;
|
|
this.nowVisiting.canInline = true;
|
|
this.visitChildren(n);
|
|
if (this.nowVisiting.canInline
|
|
&& this.actionHasDesiredProperties(n)
|
|
&& this.nowVisiting.succs.length == 0) {
|
|
n.canBeInlined = true;
|
|
}
|
|
}
|
|
|
|
// Sort the callgraph node to allow an easy bottom-up traversal of
|
|
// of the call tree.
|
|
public topologicalSort() : CallGraphNode[] {
|
|
var traversalOrder : CallGraphNode[] = [];
|
|
var visited : { [idx: number] : boolean; } = <any>{};
|
|
var indexMap: { [name: string] : number;} = {};
|
|
for (var i = 0; i < this.nodes.length; ++i) {
|
|
visited[i] = false;
|
|
indexMap[["a_", this.nodes[i].action.getName()].join("")] = i;
|
|
}
|
|
var visit = (cur: number) => {
|
|
if (visited[cur])
|
|
return;
|
|
visited[cur] = true;
|
|
for (var i = 0; i < this.nodes[cur].preds.length; ++i) {
|
|
visit(indexMap[["a_", this.nodes[cur].preds[i].action.getName()].join("")]);
|
|
}
|
|
traversalOrder.unshift(this.nodes[cur]);
|
|
}
|
|
for (var i = 0; i < this.nodes.length; ++i) {
|
|
visit(i);
|
|
}
|
|
return traversalOrder;
|
|
}
|
|
|
|
// Entry point for the inline analysis.
|
|
public nestedInlineAnalysis() {
|
|
var sorted = this.topologicalSort();
|
|
for (var i = 0; i < sorted.length; ++i) {
|
|
if (sorted[i].action.canBeInlined)
|
|
continue;
|
|
var canInline = this.actionHasDesiredProperties(sorted[i].action)
|
|
&& sorted[i].canInline;
|
|
if (!canInline)
|
|
continue;
|
|
for (var j = 0; j < sorted[i].succs.length; ++j) {
|
|
if (!sorted[i].succs[j].action.canBeInlined)
|
|
canInline = false;
|
|
}
|
|
sorted[i].action.canBeInlined = canInline;
|
|
}
|
|
}
|
|
|
|
// Debugging purposes
|
|
public dumpCallGraph() {
|
|
for (var i = 0; i < this.nodes.length; ++i) {
|
|
if (this.nodes[i].action.canBeInlined)
|
|
Util.log("Node " + i + ": " + this.nodes[i].action.toString()
|
|
+ " [can be inlined]");
|
|
else
|
|
Util.log("Node " + i + ": " + this.nodes[i].action.toString());
|
|
for (var j = 0; j < this.nodes[i].succs.length; ++j) {
|
|
Util.log(" |-> calls " + this.nodes[i].succs[j].action.toString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|