Commit 2115673f authored by Yuki Shiino's avatar Yuki Shiino Committed by Commit Bot

bind-gen: Accumulate the rendering results in a single buffer

This patch improves the code generation speed.
Ex) Navigator 10.5 sec => 8.5 sec

Bug: 839389
Change-Id: I90b90f00d09b304fa7f5beb9247eefc75bb75cf3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1980603
Commit-Queue: Yuki Shiino <yukishiino@chromium.org>
Reviewed-by: default avatarHitoshi Yoshida <peria@chromium.org>
Cr-Commit-Position: refs/heads/master@{#728386}
parent 2248e201
......@@ -160,10 +160,15 @@ class CodeNode(object):
def __str__(self):
"""
Renders this CodeNode object into a Mako template. This is supposed to
be used in a Mako template as ${code_node}.
Renders this CodeNode object directly into the renderer's text buffer
and always returns the empty string. This is because it's faster to
accumulate the rendering result directly in a single text buffer than
making a lot of string pieces and concatenating them.
This function is supposed to be used in a Mako template as ${code_node}.
"""
return self.render()
self.render()
return ""
def render(self):
"""
......@@ -180,7 +185,7 @@ class CodeNode(object):
self._is_rendering = True
try:
text = self._render(
self._render(
renderer=renderer, last_render_state=last_render_state)
finally:
self._is_rendering = False
......@@ -192,8 +197,6 @@ class CodeNode(object):
request(accumulator)
self._accumulate_requests = []
return text
def _render(self, renderer, last_render_state):
"""
Renders this CodeNode object as a text string and also propagates
......@@ -201,7 +204,7 @@ class CodeNode(object):
Only limited subclasses may override this method.
"""
return renderer.render(
renderer.render(
caller=self,
template=self._template,
template_vars=self.template_vars)
......@@ -343,13 +346,16 @@ class LiteralNode(CodeNode):
"""
def __init__(self, literal_text):
literal_text_gensym = CodeNode.gensym()
template_text = format_template(
"${{{literal_text}}}", literal_text=literal_text_gensym)
template_vars = {literal_text_gensym: literal_text}
CodeNode.__init__(self)
CodeNode.__init__(
self, template_text=template_text, template_vars=template_vars)
self._literal_text = literal_text
def _render(self, renderer, last_render_state):
renderer.push_caller(self)
try:
renderer.render_text(str(self._literal_text))
finally:
renderer.pop_caller()
class TextNode(CodeNode):
......@@ -425,33 +431,12 @@ class ListNode(CodeNode):
assert isinstance(head, str)
assert isinstance(tail, str)
element_nodes_gensym = CodeNode.gensym()
element_nodes = []
template_text = format_template(
"""\
% if {element_nodes}:
{head}\\
% endif
% for node in {element_nodes}:
${node}\\
% if not loop.last:
{separator}\\
% endif
% endfor
% if {element_nodes}:
{tail}\\
% endif\
""",
element_nodes=element_nodes_gensym,
separator=separator,
head=head,
tail=tail)
template_vars = {element_nodes_gensym: element_nodes}
CodeNode.__init__(
self, template_text=template_text, template_vars=template_vars)
CodeNode.__init__(self)
self._element_nodes = element_nodes
self._element_nodes = []
self._separator = separator
self._head = head
self._tail = tail
if code_nodes is not None:
self.extend(code_nodes)
......@@ -465,6 +450,23 @@ ${node}\\
def __len__(self):
return len(self._element_nodes)
def _render(self, renderer, last_render_state):
renderer.push_caller(self)
try:
if self._element_nodes:
renderer.render_text(self._head)
is_first = True
for node in self._element_nodes:
if is_first:
is_first = False
else:
renderer.render_text(self._separator)
node.render()
if self._element_nodes:
renderer.render_text(self._tail)
finally:
renderer.pop_caller()
def append(self, node):
if node is None:
return
......@@ -760,6 +762,14 @@ class SymbolNode(CodeNode):
self._definition_constructor = definition_constructor
def _render(self, renderer, last_render_state):
self._request_symbol_definition(renderer)
renderer.render_text(self.name)
def request_symbol_definition(self):
self._request_symbol_definition(self.renderer)
def _request_symbol_definition(self, renderer):
symbol_scope_chain = tuple(
filter(lambda node: isinstance(node, SymbolScopeNode),
renderer.callers_from_first_to_last))
......@@ -775,8 +785,6 @@ class SymbolNode(CodeNode):
if scope is self.outer:
break
return self.name
@property
def name(self):
return self._name
......
......@@ -135,6 +135,7 @@ int var1 = var2 + var3;
]))
with self.assertRaises(NameError):
renderer.reset()
root.render()
callers_on_error = list(renderer.callers_on_error)
......
......@@ -150,11 +150,14 @@ def render_code_node(code_node):
Renders |code_node| and turns it into text letting |code_node| apply all
necessary changes (side effects). Returns the resulting text.
"""
prev = "_"
renderer = code_node.renderer
prev = None
current = ""
while current != prev:
renderer.reset()
prev = current
current = str(code_node)
code_node.render()
current = renderer.to_text()
return current
......
......@@ -1176,9 +1176,9 @@ def make_v8_set_return_value(cg_context):
T = TextNode
if cg_context.attribute_set or cg_context.return_type.unwrap().is_void:
# Render a SymbolNode |return_value| discarding the content text, and
# let a symbol definition be inserted.
return T("<% str(return_value) %>")
# Request a SymbolNode |return_value| to define itself without rendering
# any text.
return T("<% return_value.request_symbol_definition() %>")
return_type_body = cg_context.return_type.unwrap()
if (cg_context.for_world == cg_context.MAIN_WORLD
......
......@@ -4,7 +4,9 @@
import sys
import mako.runtime
import mako.template
import mako.util
_MAKO_TEMPLATE_PASS_KEY = object()
......@@ -38,9 +40,22 @@ class MakoRenderer(object):
"""Represents a renderer object implemented with Mako templates."""
def __init__(self):
self._text_buffer = None
self._caller_stack = []
self._caller_stack_on_error = []
def reset(self):
"""
Resets the rendering states of this object. Must be called before
the first call to |render| or |render_text|.
"""
self._text_buffer = mako.util.FastEncodingBuffer()
def to_text(self):
"""Returns the rendering result."""
assert self._text_buffer is not None
return self._text_buffer.getvalue()
def render(self, caller, template, template_vars):
"""
Renders the template with variable bindings.
......@@ -63,7 +78,9 @@ class MakoRenderer(object):
try:
mako_template = template.mako_template(
pass_key=_MAKO_TEMPLATE_PASS_KEY)
text = mako_template.render(**template_vars)
mako_context = mako.runtime.Context(self._text_buffer,
**template_vars)
mako_template.render_context(mako_context)
except:
# Print stacktrace of template rendering.
sys.stderr.write("\n")
......@@ -89,7 +106,16 @@ class MakoRenderer(object):
finally:
self._caller_stack.pop()
return text
def render_text(self, text):
"""Renders a plain text as is."""
assert isinstance(text, str)
self._text_buffer.write(text)
def push_caller(self, caller):
self._caller_stack.append(caller)
def pop_caller(self):
self._caller_stack.pop()
@property
def callers_from_first_to_last(self):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment