提交 33851f68 authored 作者: Pascal Lamblin's avatar Pascal Lamblin

Fix more errors and warnings in doc generation

上级 bfd8a94a
......@@ -4,7 +4,8 @@
New C code generation?
======================
== Issues ==
Issues
======
There are several issues with the current way C code is generated:
* Ops cannot declare their own persistent variables.
......@@ -13,56 +14,58 @@ There are several issues with the current way C code is generated:
* It is currently impossible to specialize support code based on the self.
* Caching of the generated code for graphs is greatly suboptimal.
== Structure ==
Structure
=========
Currently, the general structure of the generated C code is approximately as follows:
{{{
<imports>
<weave type converters>
<op/result support code>
.. code-block:: c
struct my_computation {
<imports>
<weave type converters>
<op/result support code>
struct my_computation {
<input/output storage>
<persistent fields>
init(<input/output storage>) { <initialize persistent fields> }
cleanup { <clean up persistent fields> }
run { <run the computation> }
};
};
<runner for the struct>
PyObject* instantiate(PyObject* args) {
<runner for the struct>
PyObject* instantiate(PyObject* args) {
<weave stuff>
<make up a CObject out of the runner and a my_computation instance>
<weave stuff>
}
<python exports for instantiate>
}}}
}
<python exports for instantiate>
The module produced via that method then has to be used as such::
obj = module.instantiate(error_storage, input_storage, output_storage, orphan_storage)
cutils.run_cthunk(obj)
The module produced via that method then has to be used as such:
{{{
obj = module.instantiate(error_storage, input_storage, output_storage, orphan_storage)
cutils.run_cthunk(obj)
}}}
We would like to get rid of weave dependencies, avoid name conflicts with the support code and have a nicer user interface for the produced module. The proposed new structure is as follows:
{{{
<imports>
.. code-block:: c
<imports>
struct op1 {
struct op1 {
<persistent variables>
<support code>
init() { <initialize persistent fields> }
cleanup { <clean up persistent fields> }
run(<inputs>) { <run the computation for op1> }
};
};
struct op2 { <same> };
...
struct opN { <ditto> };
struct op2 { <same> };
...
struct opN { <ditto> };
struct driver {
struct driver {
op1 o1; op2 o2; ... opN oN;
<input storage>
<output storage>
......@@ -76,28 +79,27 @@ struct driver {
oN.run(...);
<sync outputs>
}
}
}
PyObject* <name>(PyObject* inputs) {
PyObject* <name>(PyObject* inputs) {
<init driver, input/output storage>
<put inputs in input storage>
driver.run()
<free input storage>
<return output storage>
}
}
PyObject* <name>_driver(PyObject* storage) {
PyObject* <name>_driver(PyObject* storage) {
<init driver with storage>
<return driver>
}
}
<export <name> and <name>_driver>
}}}
<export <name> and <name>_driver>
Gains:
* support code can be put inside a struct and become private to the Op
* we can export several functions that can be used directly, eg {{{z = module.add(1, 2)}}}
* this won't do filtering like {{{Result.filter}}} so the usefulness is limited by that
* we can export several functions that can be used directly, eg ``z = module.add(1, 2)``
* this won't do filtering like ``Result.filter`` so the usefulness is limited by that
* the sequence of operations might be clearer to read
* we can use more descriptive names in each Op struct representing its input names (if we can find them using the inspect module) without worrying about name conflicts
......@@ -106,14 +108,15 @@ Losses:
* make functions static and inline as much as possible
== Caching ==
Caching
=======
The current way of caching is from a hash of the generated code. That is inefficient because code has to be generated each time, which might be a costly process. Furthermore, usage of hashing in sets make it difficult to ensure a consistent ordering of Ops in graphs where several orderings are valid, so the generated C code is potentially different each time. Here is a proposal for a better way to compute the hash:
* Result_hash = Result version + Result desc
* Op_hash = Op version + Op desc + input/output hashes
* Env_hash = Env version + combination of the Op hashes and their traversal order wrt a consistent traversal method
The version could be set explicitly via a {{{__version__}}} field or it could simply be equal to the file's last modification date. We could also have a {{{__nocache__}}} field indicating that code produced by the Op or Result cannot be cached.
The version could be set explicitly via a ``__version__`` field or it could simply be equal to the file's last modification date. We could also have a ``__nocache__`` field indicating that code produced by the Op or Result cannot be cached.
It should also be easier to bypass the cache (eg an option to CLinker to regenerate the code).
......
......@@ -174,7 +174,8 @@ internal state, and returns the old state value.
>>> accumulator = function([inc], state, updates=[(state, state+inc)])
This code introduces a few new concepts. The ``shared`` function constructs
so-called :term:`shared variables`. These are hybrid symbolic and non-symbolic
so-called :term:`shared variables <shared variable>`.
These are hybrid symbolic and non-symbolic
variables. Shared variables can be used in symbolic expressions just like
the objects returned by ``dmatrices(...)`` but they also have an internal
value, that defines the value taken by this symbolic variable in *all* the
......
......@@ -593,7 +593,8 @@ class Composite(Component):
"""
Generator that yields (path, component) pairs in a flattened
hierarchy of composites and components, where path is a
sequence of keys such that
sequence of keys such that::
component is self[path[0]][path[1]]...
If include_self is True, the list will include the Composite
......@@ -865,12 +866,13 @@ def register_wrapper(condition, wrapper):
"""
:type condition: function x -> bool
:param condition: this function should return True iff `wrapper` can sensibly turn x into a
Component.
:param condition: this function should return True iff `wrapper` can
sensibly turn x into a Component.
:type wrapper: function x -> Component
:param wrapper: this function should convert `x` into an instance of a Component subclass.
:param wrapper: this function should convert `x` into an instance of
a Component subclass.
"""
__autowrappers.append((condition, wrapper))
......@@ -1169,7 +1171,8 @@ FancyModuleInstance = ModuleInstance
def func_to_mod(f):
"""
Creates a dummy module, with external member variables for the input
parameters required by the function f, and a member output defined as:
parameters required by the function f, and a member output defined as::
output <= f(**kwinit)
"""
def make(**kwinit):
......
......@@ -59,17 +59,19 @@ class CLinkerType(CLinkerObject):
:type name: string
:param sub: a dictionary of special codes. Most importantly sub['fail']. See CLinker
for more info on `sub` and ``fail``.
:param sub: a dictionary of special codes. Most importantly
sub['fail']. See CLinker for more info on `sub` and ``fail``.
:type sub: dict string -> string
:note: It is important to include the `name` inside of variables which are declared
here, so that name collisions do not occur in the source file that is generated.
:note: It is important to include the `name` inside of variables which
are declared here, so that name collisions do not occur in the
source file that is generated.
:note: The variable called ``name`` is not necessarily defined yet where this code is
inserted. This code might be inserted to create class variables for example, whereas
the variable ``name`` might only exist inside certain functions in that class.
:note: The variable called ``name`` is not necessarily defined yet
where this code is inserted. This code might be inserted to
create class variables for example, whereas the variable ``name``
might only exist inside certain functions in that class.
:todo: Why should variable declaration fail? Is it even allowed to?
......@@ -87,9 +89,10 @@ class CLinkerType(CLinkerObject):
return "addr_of_%(name)s = NULL;"
:note: The variable called ``name`` is not necessarily defined yet where this code is
inserted. This code might be inserted in a class constructor for example, whereas
the variable ``name`` might only exist inside certain functions in that class.
:note: The variable called ``name`` is not necessarily defined yet
where this code is inserted. This code might be inserted in a
class constructor for example, whereas the variable ``name``
might only exist inside certain functions in that class.
:todo: Why should variable initialization fail? Is it even allowed to?
"""
......@@ -116,15 +119,17 @@ class CLinkerType(CLinkerObject):
return "if (py_%(name)s == Py_None)" + \\\
addr_of_%(name)s = &py_%(name)s;" + \\\
"else" + \\\
{ PyErr_SetString(PyExc_ValueError, 'was expecting None'); %(fail)s;}"
{ PyErr_SetString(PyExc_ValueError, \\\
'was expecting None'); %(fail)s;}"
:param name: the name of the ``PyObject *`` pointer that will the value for this Type
:param name: the name of the ``PyObject *`` pointer that will
store the value for this Type
:type name: string
:param sub: a dictionary of special codes. Most importantly sub['fail']. See CLinker
for more info on `sub` and ``fail``.
:param sub: a dictionary of special codes. Most importantly
sub['fail']. See CLinker for more info on `sub` and ``fail``.
:type sub: dict string -> string
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论