Commit 0f53ed4f authored by Yuki Shiino's avatar Yuki Shiino Committed by Commit Bot

bind-gen: Implement attribute / operation callback (1 of N)

This patch is exploring how we can implement V8 callback
functions in the new bindings generator.

Attempts to implement the following utilities:
- make_v8_to_blink_value
- bind_blink_api_arguments
- bind_blink_api_call
- bind_return_value
- bind_v8_set_return_value

Bug: 839389
Change-Id: Ie949eca6119806611125fc4e8265601a6888e52b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1880904Reviewed-by: default avatarHitoshi Yoshida <peria@chromium.org>
Commit-Queue: Yuki Shiino <yukishiino@chromium.org>
Cr-Commit-Position: refs/heads/master@{#710333}
parent 67bcad56
...@@ -29,6 +29,7 @@ class CodeGenerationContext(object): ...@@ -29,6 +29,7 @@ class CodeGenerationContext(object):
# attribute name: default value # attribute name: default value
cls._context_attrs = { cls._context_attrs = {
# Top-level definition # Top-level definition
"callback_function": None,
"callback_interface": None, "callback_interface": None,
"dictionary": None, "dictionary": None,
"interface": None, "interface": None,
...@@ -132,6 +133,10 @@ class CodeGenerationContext(object): ...@@ -132,6 +133,10 @@ class CodeGenerationContext(object):
return (self.callback_interface or self.dictionary or self.interface return (self.callback_interface or self.dictionary or self.interface
or self.namespace) or self.namespace)
@property
def function_like(self):
return (self.callback_function or self.constructor or self.operation)
@property @property
def is_return_by_argument(self): def is_return_by_argument(self):
if self.return_type is None: if self.return_type is None:
...@@ -164,6 +169,8 @@ class CodeGenerationContext(object): ...@@ -164,6 +169,8 @@ class CodeGenerationContext(object):
def return_type(self): def return_type(self):
if self.attribute_get: if self.attribute_get:
return self.attribute.idl_type return self.attribute.idl_type
if self.callback_function:
return self.callback_function.return_type
if self.operation: if self.operation:
return self.operation.return_type return self.operation.return_type
return None return None
......
...@@ -4,25 +4,41 @@ ...@@ -4,25 +4,41 @@
import os.path import os.path
from blinkbuild.name_style_converter import NameStyleConverter
from .clang_format import clang_format from .clang_format import clang_format
from .code_generation_context import CodeGenerationContext from .code_generation_context import CodeGenerationContext
from .code_node import CodeNode from .code_node import CodeNode
from .code_node import FunctionDefinitionNode from .code_node import FunctionDefinitionNode
from .code_node import LiteralNode from .code_node import LiteralNode
from .code_node import SequenceNode
from .code_node import SymbolDefinitionNode
from .code_node import SymbolNode from .code_node import SymbolNode
from .code_node import SymbolScopeNode from .code_node import SymbolScopeNode
from .code_node import TextNode from .code_node import TextNode
from .code_node import UnlikelyExitNode
from .mako_renderer import MakoRenderer from .mako_renderer import MakoRenderer
_format = CodeNode.format_template
def _snake_case(name):
return NameStyleConverter(name).to_snake_case()
def _upper_camel_case(name):
return NameStyleConverter(name).to_upper_camel_case()
def make_common_local_vars(cg_context):
def bind_common_local_vars(code_node, cg_context):
assert isinstance(code_node, SymbolScopeNode)
assert isinstance(cg_context, CodeGenerationContext) assert isinstance(cg_context, CodeGenerationContext)
S = SymbolNode S = SymbolNode
T = TextNode T = TextNode
local_vars = [] local_vars = []
supplemental_bindings = {} template_vars = {}
local_vars.extend([ local_vars.extend([
S("class_like_name", ("const char* const ${class_like_name} = " S("class_like_name", ("const char* const ${class_like_name} = "
...@@ -30,11 +46,9 @@ def make_common_local_vars(cg_context): ...@@ -30,11 +46,9 @@ def make_common_local_vars(cg_context):
S("current_context", ("v8::Local<v8::Context> ${current_context} = " S("current_context", ("v8::Local<v8::Context> ${current_context} = "
"${isolate}->GetCurrentContext();")), "${isolate}->GetCurrentContext();")),
S("current_script_state", ("ScriptState* ${current_script_state} = " S("current_script_state", ("ScriptState* ${current_script_state} = "
"ScriptState::ForCurrentRealm(${info});")), "ScriptState::From(${current_context});")),
S("exception_state", S("execution_context", ("ExecutionContext* ${execution_context} = "
("ExceptionState ${exception_state}(" "ExecutionContext::From(${script_state});")),
"${isolate}, ${exception_state_context_type}, ${class_like_name}, "
"${property_name});${reject_promise_scope_definition}")),
S("isolate", "v8::Isolate* ${isolate} = ${info}.GetIsolate();"), S("isolate", "v8::Isolate* ${isolate} = ${info}.GetIsolate();"),
S("per_context_data", ("V8PerContextData* ${per_context_data} = " S("per_context_data", ("V8PerContextData* ${per_context_data} = "
"${script_state}->PerContextData();")), "${script_state}->PerContextData();")),
...@@ -44,23 +58,33 @@ def make_common_local_vars(cg_context): ...@@ -44,23 +58,33 @@ def make_common_local_vars(cg_context):
"const char* const ${property_name} = \"${property.identifier}\";"), "const char* const ${property_name} = \"${property.identifier}\";"),
S("receiver", "v8::Local<v8::Object> ${receiver} = ${info}.This();"), S("receiver", "v8::Local<v8::Object> ${receiver} = ${info}.This();"),
S("receiver_context", ("v8::Local<v8::Context> ${receiver_context} = " S("receiver_context", ("v8::Local<v8::Context> ${receiver_context} = "
"${receiver_script_state}->GetContext();")), "${receiver}->CreationContext();")),
S("receiver_script_state", S("receiver_script_state",
("ScriptState* ${receiver_script_state} = " ("ScriptState* ${receiver_script_state} = "
"ScriptState::ForRelevantRealm(${info});")), "ScriptState::From(${receiver_context});")),
]) ])
is_receiver_context = (cg_context.member_like
and not cg_context.member_like.is_static)
# creation_context
pattern = "const v8::Local<v8::Context>& ${creation_context} = {_1};"
_1 = "${receiver_context}" if is_receiver_context else "${current_context}"
local_vars.append(S("creation_context", _format(pattern, _1=_1)))
# creation_context_object
text = ("${receiver}"
if is_receiver_context else "${current_context}->Global()")
template_vars["creation_context_object"] = T(text)
# script_state # script_state
def_text = "ScriptState* ${script_state} = {_1};" pattern = "ScriptState* ${script_state} = {_1};"
if cg_context.member_like and not cg_context.member_like.is_static: _1 = ("${receiver_script_state}"
_1 = "${receiver_script_state}" if is_receiver_context else "${current_script_state}")
else: local_vars.append(S("script_state", _format(pattern, _1=_1)))
_1 = "${current_script_state}"
local_vars.append(
S("script_state", CodeNode.format_template(def_text, _1=_1)))
# exception_state_context_type # exception_state_context_type
def_text = ( pattern = (
"const ExceptionState::ContextType ${exception_state_context_type} = " "const ExceptionState::ContextType ${exception_state_context_type} = "
"{_1};") "{_1};")
if cg_context.attribute_get: if cg_context.attribute_get:
...@@ -72,21 +96,171 @@ def make_common_local_vars(cg_context): ...@@ -72,21 +96,171 @@ def make_common_local_vars(cg_context):
else: else:
_1 = "ExceptionState::kExecutionContext" _1 = "ExceptionState::kExecutionContext"
local_vars.append( local_vars.append(
S("exception_state_context_type", S("exception_state_context_type", _format(pattern, _1=_1)))
CodeNode.format_template(def_text, _1=_1)))
# exception_state
# reject_promise_scope_definition pattern = "ExceptionState ${exception_state}({_1});{_2}"
if ((cg_context.attribute_get and cg_context.attribute.idl_type.is_promise) _1 = [
or (cg_context.operation "${isolate}", "${exception_state_context_type}", "${class_like_name}",
and cg_context.operation.return_type.is_promise)): "${property_name}"
template_text = ("\n" ]
"ExceptionToRejectPromiseScope reject_promise_scope" _2 = ""
"(${info}, ${exception_state});") if cg_context.return_type and cg_context.return_type.unwrap().is_promise:
_2 = ("\n"
"ExceptionToRejectPromiseScope reject_promise_scope"
"(${info}, ${exception_state});")
local_vars.append(
S("exception_state", _format(pattern, _1=", ".join(_1), _2=_2)))
code_node.register_code_symbols(local_vars)
code_node.add_template_vars(template_vars)
def make_v8_to_blink_value(blink_var_name, v8_value_expr, idl_type):
assert isinstance(blink_var_name, str)
assert isinstance(v8_value_expr, str)
pattern = "NativeValueTraits<{_1}>::NativeValue({_2})"
_1 = "IDL{}".format(idl_type.type_name)
_2 = ["${isolate}", v8_value_expr, "${exception_state}"]
blink_value = _format(pattern, _1=_1, _2=", ".join(_2))
idl_type_tag = _1
pattern = "{_1}& ${{{_2}}} = {_3};"
_1 = "NativeValueTraits<{}>::ImplType".format(idl_type_tag)
_2 = blink_var_name
_3 = blink_value
text = _format(pattern, _1=_1, _2=_2, _3=_3)
def create_definition(symbol_node):
return SymbolDefinitionNode(symbol_node, [
TextNode(text),
UnlikelyExitNode(
cond=TextNode("${exception_state}.HadException()"),
body=SymbolScopeNode([TextNode("return;")])),
])
return SymbolNode(blink_var_name, definition_constructor=create_definition)
def bind_blink_api_arguments(code_node, cg_context):
assert isinstance(code_node, SymbolScopeNode)
assert isinstance(cg_context, CodeGenerationContext)
if cg_context.attribute_get:
return
if cg_context.attribute_set:
name = "arg1_value"
v8_value = "${info}[0]"
code_node.register_code_symbol(
make_v8_to_blink_value(name, v8_value,
cg_context.attribute.idl_type))
return
for index, argument in enumerate(cg_context.function_like.arguments, 1):
name = "arg{}_{}".format(index, _snake_case(argument.identifier))
if argument.is_variadic:
assert False, "Variadic arguments are not yet supported"
else:
v8_value = "${{info}}[{}]".format(argument.index)
code_node.register_code_symbol(
make_v8_to_blink_value(name, v8_value, argument.idl_type))
def bind_blink_api_call(code_node, cg_context):
assert isinstance(code_node, SymbolScopeNode)
assert isinstance(cg_context, CodeGenerationContext)
property_implemented_as = (
cg_context.member_like.code_generator_info.property_implemented_as)
func_name = property_implemented_as or cg_context.member_like.identifier
if cg_context.attribute_set:
func_name = "set{}".format(_upper_camel_case(func_name))
if cg_context.member_like.is_static:
receiver_implemented_as = (
cg_context.member_like.code_generator_info.receiver_implemented_as)
class_name = receiver_implemented_as or cg_context.class_like.identifier
func_name = "{}::{}".format(class_name, func_name)
arguments = []
ext_attrs = cg_context.member_like.extended_attributes
values = ext_attrs.values_of("CallWith") + (
ext_attrs.values_of("SetterCallWith") if cg_context.attribute_set else
())
if "Isolate" in values:
arguments.append("${isolate}")
if "ScriptState" in values:
arguments.append("${script_state}")
if "ExecutionContext" in values:
arguments.append("${execution_context}")
if cg_context.attribute_get:
pass
elif cg_context.attribute_set:
arguments.append("${arg1_value}")
else: else:
template_text = "" for index, argument in enumerate(cg_context.function_like.arguments,
supplemental_bindings["reject_promise_scope_definition"] = T(template_text) 1):
arguments.append("${{arg{}_{}}}".format(
index, _snake_case(argument.identifier)))
if cg_context.is_return_by_argument:
arguments.append("${return_value}")
if cg_context.may_throw_exception:
arguments.append("${exception_state}")
text = _format("{_1}({_2})", _1=func_name, _2=", ".join(arguments))
code_node.add_template_var("blink_api_call", TextNode(text))
def bind_return_value(code_node, cg_context):
assert isinstance(code_node, SymbolScopeNode)
assert isinstance(cg_context, CodeGenerationContext)
def create_definition(symbol_node):
if cg_context.return_type.unwrap().is_void:
text = "${blink_api_call};"
elif cg_context.is_return_by_argument:
text = "${blink_return_type} ${return_value};\n${blink_api_call};"
else:
text = "const auto& ${return_value} = ${blink_api_call};"
node = SymbolDefinitionNode(symbol_node, [TextNode(text)])
if cg_context.may_throw_exception:
node.append(
UnlikelyExitNode(
cond=TextNode("${exception_state}.HadException()"),
body=SymbolScopeNode([TextNode("return;")])))
return node
return local_vars, supplemental_bindings code_node.register_code_symbol(
SymbolNode("return_value", definition_constructor=create_definition))
def bind_v8_set_return_value(code_node, cg_context):
assert isinstance(code_node, SymbolScopeNode)
assert isinstance(cg_context, CodeGenerationContext)
pattern = "{_1}({_2});"
_1 = "V8SetReturnValue"
_2 = ["${info}", "${return_value}"]
if cg_context.return_type.unwrap().is_void:
# Render a SymbolNode |return_value| discarding the content text, and
# let a symbol definition be added.
pattern = "<% str(return_value) %>"
elif (cg_context.world == cg_context.MAIN_WORLD
and cg_context.return_type.unwrap().is_interface):
_1 = "V8SetReturnValueForMainWorld"
elif cg_context.return_type.unwrap().is_interface:
_2.append("${creation_context_object}")
text = _format(pattern, _1=_1, _2=", ".join(_2))
code_node.add_template_var("v8_set_return_value", TextNode(text))
def make_attribute_get_def(cg_context): def make_attribute_get_def(cg_context):
...@@ -97,21 +271,59 @@ def make_attribute_get_def(cg_context): ...@@ -97,21 +271,59 @@ def make_attribute_get_def(cg_context):
cg_context = cg_context.make_copy(attribute_get=True) cg_context = cg_context.make_copy(attribute_get=True)
local_vars, local_vars_bindings = make_common_local_vars(cg_context)
func_def = FunctionDefinitionNode( func_def = FunctionDefinitionNode(
name=L("AttributeGetCallback"), name=L("AttributeGetCallback"),
arg_decls=[L("const v8::FunctionCallbackInfo<v8::Value>& info")], arg_decls=[L("const v8::FunctionCallbackInfo<v8::Value>& info")],
return_type=L("void"), return_type=L("void"))
local_vars=local_vars)
body = func_def.body
body.add_template_var("info", "info")
body.add_template_vars(cg_context.template_bindings())
binders = [
bind_common_local_vars,
bind_blink_api_arguments,
bind_blink_api_call,
bind_return_value,
bind_v8_set_return_value,
]
for bind in binders:
bind(body, cg_context)
body.extend([
T("${v8_set_return_value}"),
])
return func_def
def make_operation_def(cg_context):
assert isinstance(cg_context, CodeGenerationContext)
L = LiteralNode
T = TextNode
func_def = FunctionDefinitionNode(
name=L("OperationCallback"),
arg_decls=[L("const v8::FunctionCallbackInfo<v8::Value>& info")],
return_type=L("void"))
body = func_def.body body = func_def.body
body.add_template_var("info", "info") body.add_template_var("info", "info")
body.add_template_vars(local_vars_bindings)
body.add_template_vars(cg_context.template_bindings()) body.add_template_vars(cg_context.template_bindings())
binders = [
bind_common_local_vars,
bind_blink_api_arguments,
bind_blink_api_call,
bind_return_value,
bind_v8_set_return_value,
]
for bind in binders:
bind(body, cg_context)
body.extend([ body.extend([
T("(void)(${per_context_data}, ${exception_state});"), T("${v8_set_return_value}"),
]) ])
return func_def return func_def
...@@ -124,13 +336,19 @@ def run_example(web_idl_database, output_dirs): ...@@ -124,13 +336,19 @@ def run_example(web_idl_database, output_dirs):
filepath = os.path.join(output_dirs['core'], filename) filepath = os.path.join(output_dirs['core'], filename)
namespace = list(web_idl_database.namespaces)[0] namespace = list(web_idl_database.namespaces)[0]
attribute = namespace.attributes[0]
cg_context = CodeGenerationContext( cg_context = CodeGenerationContext(namespace=namespace)
namespace=namespace, attribute=attribute)
root_node = SymbolScopeNode(renderer=renderer) root_node = SymbolScopeNode(separator_last="\n", renderer=renderer)
root_node.append(make_attribute_get_def(cg_context)) for attribute in namespace.attributes:
root_node.append(
make_attribute_get_def(cg_context.make_copy(attribute=attribute)))
for operation_group in namespace.operation_groups:
for operation in operation_group:
root_node.append(
make_operation_def(
cg_context.make_copy(
operation_group=operation_group, operation=operation)))
prev = '' prev = ''
current = str(root_node) current = str(root_node)
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
}; };
namespace TestNamespace { namespace TestNamespace {
readonly attribute Promise<boolean> attr1; [RaisesException] readonly attribute Promise<boolean> attr1;
readonly attribute DOMString attr2; readonly attribute TestInterfaceConstructor attr2;
void op1(); void op1(long x, boolean y, TestInterfaceConstructor z);
}; };
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