Skip to content

Commit

Permalink
[BUG #4615] fixed variantoptimizer by re-writing large parts with new…
Browse files Browse the repository at this point in the history
… modules

- new module optimizer/reducer.py to take over ast re-writing from
  variantoptimizer (the NodeVisitor approach makes this a lot nicer)
- reducer uses .evaluated attribs from evaluate.py
  • Loading branch information
thron7 committed Jul 31, 2012
1 parent 23ec439 commit e54d61a
Show file tree
Hide file tree
Showing 6 changed files with 772 additions and 141 deletions.
4 changes: 2 additions & 2 deletions tool/pylib/ecmascript/backend/formatter.py
Expand Up @@ -59,7 +59,7 @@ def format(self, optns, state):
r += ' '
r += self.get("value")
r += ' '
r += self.getChild("second").format(optns, state)
r += self.getChild(1).format(optns, state)
return r
symbol(id_).format = format

Expand All @@ -75,7 +75,7 @@ def format(self, optns, state): # adapt the output
r += self.space()
r += self.get("value")
r += self.space()
r += self.getChild("second").format(optns, state)
r += self.getChild(1).format(optns, state)
return r
symbol(id_).format = format

Expand Down
Empty file.
1 change: 1 addition & 0 deletions tool/pylib/ecmascript/transform/evaluate/evaluate.py
Expand Up @@ -156,3 +156,4 @@ def _visit_monadic(self, node, operator):
def evaluate(node):
evaluator = OperationEvaluator(node)
evaluator.visit(node)
return node
80 changes: 80 additions & 0 deletions tool/pylib/ecmascript/transform/optimizer/reducer.py
@@ -0,0 +1,80 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# qooxdoo - the new era of web development
#
# http://qooxdoo.org
#
# Copyright:
# 2006-2012 1&1 Internet AG, Germany, http://www.1und1.de
#
# License:
# LGPL: http://www.gnu.org/licenses/lgpl.html
# EPL: http://www.eclipse.org/org/documents/epl-v10.php
# See the LICENSE file in the project's top-level directory for details.
#
# Authors:
# * Thomas Herchenroeder (thron7)
#
################################################################################

##
# Reduce conditional expressions that are decidable. Requires .evaluated.
# Modifies tree structure.
##

import os, sys, re, types, operator, functools as func
from ecmascript.frontend import treeutil, treegenerator

class ConditionReducer(treeutil.NodeVisitor):

def __init__(self, root_node):
super(ConditionReducer, self).__init__()
self.root_node = root_node


def visit_loop(self, node):
loop_type = node.get("loopType")
if loop_type == "IF":
cond_node = node.children[0]
if hasattr(cond_node, "evaluated") and cond_node.evaluated!=():
self._rewrite_if(node, bool(cond_node.evaluated))
for child in node.children:
self.visit(child)

def visit_operation(self, node): # catch HOOK operations
op_type = node.get("operator")
if op_type == "HOOK":
cond_node = node.children[0]
if hasattr(cond_node, "evaluated") and cond_node.evaluated!=():
self._rewrite_hook(node, bool(cond_node.evaluated))
for child in node.children:
self.visit(child)


def _rewrite_if(self, if_node, cond_val):
if cond_val:
# use then branch
replacement = if_node.children[1]
else:
# use else branch or empty
if len(if_node.children) == 3:
replacement = if_node.children[2]
else:
# don't leave single-statement parent loops empty (if_node.parent.type not in ("block", "file")
replacement = treegenerator.symbol("block")(if_node.get("line"), if_node.get("column"))
if_node.parent.replaceChild(if_node, replacement)

def _rewrite_hook(self, hook_node, cond_val):
if cond_val: # use then expr
replacement = hook_node.children[1]
else: # use else expr
replacement = hook_node.children[2]
hook_node.parent.replaceChild(hook_node, replacement)

# - ---------------------------------------------------------------------------

def reduce(node):
reducer = ConditionReducer(node)
reducer.visit(node)
167 changes: 28 additions & 139 deletions tool/pylib/ecmascript/transform/optimizer/variantoptimizer.py
Expand Up @@ -25,6 +25,8 @@
from ecmascript.frontend.treeutil import *
from ecmascript.frontend import treeutil
from ecmascript.frontend.treegenerator import symbol, PackerFlags as pp
from ecmascript.transform.evaluate import evaluate
from ecmascript.transform.optimizer import reducer
from misc import json

global verbose
Expand Down Expand Up @@ -60,6 +62,9 @@ def search(node, variantMap, fileId_="", verb=False):
fileId = fileId_
modified = False

#if fileId == "qx.data.marshal.Json":
# import pydb; pydb.debugger()

variantNodes = findVariantNodes(node)
for variantNode in variantNodes:
variantMethod = variantNode.toJS(pp).rsplit('.',1)[1]
Expand Down Expand Up @@ -173,22 +178,12 @@ def processVariantGet(callNode, variantMap):
# Simple sanity checks
params = callNode.getChild("arguments")
if len(params.children) != 1:
log("Warning", "Expecting exactly one argument for qx.core.Environment.get. Ignoring this occurrence.", params)
return treeModified

firstParam = params.getChildByPosition(0)
if not isStringLiteral(firstParam):
# warning is currently covered in parsing code
#log("Warning", "First argument must be a string literal! Ignoring this occurrence.", firstParam)
return treeModified

# skipping "relative" calls like "a.b.qx.core.Environment.get()"
#(with the new ast and the isEnvironmentCall check, this cannot happen anymore)
#qxIdentifier = treeutil.selectNode(callNode, "operand/variable/identifier[1]")
#if not treeutil.checkFirstChainChild(qxIdentifier):
# log("Warning", "Skipping relative qx.core.Environment.get call. Ignoring this occurrence ('%s')." % treeutil.findChainRoot(qxIdentifier).toJavascript())
# return treeModified

variantKey = firstParam.get("value");
if variantKey in variantMap:
confValue = variantMap[variantKey]
Expand Down Expand Up @@ -245,11 +240,14 @@ def nextNongroupParent(node, stopnode=None):

def getOtherOperand(opNode, oneOperand):
operands = opNode.getChildren(True)
if operands[0] == oneOperand.parent: # switch between "first" and "second"
otherOperand = operands[1].getFirstChild(ignoreComments=True)
#if operands[0] == oneOperand.parent: # switch between "first" and "second"
if operands[0] == oneOperand: # switch between "first" and "second"
#otherOperand = operands[1].getFirstChild(ignoreComments=True)
otherOperand = operands[1]
otherPosition = 1
else:
otherOperand = operands[0].getFirstChild(ignoreComments=True)
#otherOperand = operands[0].getFirstChild(ignoreComments=True)
otherOperand = operands[0]
otherPosition = 0
return otherOperand, otherPosition

Expand Down Expand Up @@ -343,148 +341,46 @@ def reduceCall(callNode, value):
# arithmetic ("3+4" => "7"), logical ("true && false" => false)
def reduceOperation(literalNode):

resultNode = None
resultNode = literalNode
treeModified = False

# can only reduce with constants
if literalNode.type != "constant":
return literalNode, False
else:
literalValue = constNodeToPyValue(literalNode)

# check if we're in an operation
ngParent = nextNongroupParent(literalNode) # could be "first", "second" etc. in ops
if not ngParent or not ngParent.parent or ngParent.parent.type != "operation":
ngParent = nextNongroupParent(literalNode) # could be operand in ops
if not ngParent or ngParent.type != "operation":
return literalNode, False
else:
operationNode = ngParent.parent
# get operator
operator = operationNode.get("operator")

# normalize expression
noperationNode = normalizeExpression(operationNode)
# re-gain knownn literal node
for node in treeutil.nodeIterator(noperationNode, [literalNode.type]):
if literalNode.attributes == node.attributes:
nliteralNode = node
break

# equal, unequal
if operator in ["EQ", "SHEQ", "NE", "SHNE"]:
otherOperand, _ = getOtherOperand(noperationNode, nliteralNode)
if otherOperand.type != "constant":
return literalNode, False
if operator in ["EQ", "SHEQ"]:
cmpFcn = operators.eq
elif operator in ["NE", "SHNE"]:
cmpFcn = operators.ne

operands = [literalValue]
otherVal = constNodeToPyValue(otherOperand)
operands.append(otherVal)

result = cmpFcn(operands[0],operands[1])
resultNode = symbol("constant")(
noperationNode.get("line"), noperationNode.get("column"))
resultNode.set("constantType","boolean")
resultNode.set("value", str(result).lower())

# order compares <, =<, ...
elif operator in ["LT", "LE", "GT", "GE"]:
otherOperand, otherPosition = getOtherOperand(noperationNode, nliteralNode)
if otherOperand.type != "constant":
return literalNode, False
if operator == "LT":
cmpFcn = operators.lt
elif operator == "LE":
cmpFcn = operators.le
elif operator == "GT":
cmpFcn = operators.gt
elif operator == "GE":
cmpFcn = operators.ge

operands = {}
operands[1 - otherPosition] = literalValue
otherVal = constNodeToPyValue(otherOperand)
operands[otherPosition] = otherVal

result = cmpFcn(operands[0], operands[1])
resultNode = symbol("constant")(
noperationNode.get("line"), noperationNode.get("column"))
resultNode.set("constantType","boolean")
resultNode.set("value", str(result).lower())
operationNode = ngParent

# logical ! (not)
elif operator in ["NOT"]:
result = not literalValue
# try to evaluate expr
operationNode = evaluate.evaluate(operationNode)
if operationNode.evaluated != (): # we have a value
# create replacement
resultNode = symbol("constant")(
noperationNode.get("line"), noperationNode.get("column"))
operationNode.get("line"), operationNode.get("column"))
resultNode.set("constantType","boolean")
resultNode.set("value", str(result).lower())

# logical operators &&, ||
elif operator in ["AND", "OR"]:
result = None
otherOperand, otherPosition = getOtherOperand(noperationNode, nliteralNode)
if operator == "AND":
#if otherPosition==1 and not literalValue: # short circuit
# result = False
#else:
cmpFcn = (lambda x,y: x and y)
elif operator == "OR":
#if otherPosition==1 and literalValue: # short circuit
# result = True
#else:
cmpFcn = (lambda x,y: x or y)

if result == None:
if otherOperand.type != "constant":
return literalNode, False
operands = {}
operands[1 - otherPosition] = literalValue
otherVal = constNodeToPyValue(otherOperand)
operands[otherPosition] = otherVal
result = cmpFcn(operands[0], operands[1])
resultNode = {literalValue:literalNode, otherVal:otherOperand}[result]

# hook ?: operator
elif operator in ["HOOK"]:
if ngParent == noperationNode.children[0]: # optimize a literal condition
if bool(literalValue):
resultNode = noperationNode.children[1]
else:
resultNode = noperationNode.children[2]

# unsupported operation
else:
pass

if resultNode != None:
#print "optimizing: operation"
resultNode.set("value", str(operationNode.evaluated).lower())
# modify tree
operationNode.parent.replaceChild(operationNode, resultNode)
treeModified = True
else:
resultNode = literalNode
treeModified = False

return resultNode, treeModified



##
# 3. pass:
# now reduce all 'if's with constant conditions "if (true)..." => <then>-branch
def reduceLoop(startNode):
treeModified = False
conditionNode = None

# Can only reduce constant condition expression
if startNode.type != "constant":
return treeModified

# Can only reduce a condition expression
# of a loop context
# find the loop's condition node
node = startNode
while(node): # find the loop's condition node
while(node):
if node.parent and node.parent.type == "loop" and node.parent.getFirstChild(ignoreComments=True)==node:
conditionNode = node
break
Expand All @@ -495,16 +391,9 @@ def reduceLoop(startNode):
# handle "if" statements
if conditionNode.parent.get("loopType") == "IF":
loopNode = conditionNode.parent
# startNode must be only condition
if startNode==conditionNode or isDirectDescendant(startNode, conditionNode):
value = startNode.get("value")
if startNode.get("constantType") == 'string':
value = '"' + value + '"'
# re-parse into an internal value
value = json.loads(value)
condValue = bool(value)
#print "optimizing: if"
treeutil.inlineIfStatement(loopNode, condValue)
evaluate.evaluate(conditionNode)
if conditionNode.evaluated!=():
reducer.reduce(loopNode)
treeModified = True

return treeModified
Expand Down

0 comments on commit e54d61a

Please sign in to comment.