mirror of
https://github.com/AdaCore/langkit.git
synced 2026-02-12 12:28:12 -08:00
136 lines
5.1 KiB
ReStructuredText
136 lines
5.1 KiB
ReStructuredText
Properties memoization
|
|
======================
|
|
|
|
|
|
Property memoization is a mechanism to improve the run time performance of
|
|
properties: a memoized property caches its result for a given set of arguments
|
|
to avoid later recomputations. By default, no property is memoized, and the
|
|
language spec can request the memoization of each property explicitly.
|
|
|
|
|
|
Implementation
|
|
--------------
|
|
|
|
When called, a memoized property will look up in the memoization table if it
|
|
was already called with the same arguments: if it was, the previous result
|
|
(saved in the memoization table: a regular result or a ``PropertyError``
|
|
exception) is returned directly, and the property code is not evaluated.
|
|
|
|
Property memoization is implemented inside the body of a property. This means
|
|
that it is not possible to memoize external properties (its implementation is
|
|
outside the reach of code generation), and that memoizing one property does not
|
|
automatically memoize all the properties that override it. As a consequence, it
|
|
is not possible to memoize an abstract property (which has no implementation).
|
|
|
|
The goal of property memoization is only to enhance run time performance: it is
|
|
not supposed to change the result of any property (it would not be sound).
|
|
|
|
The memoization table is implemented with a hash table. For this reason, it is
|
|
not possible to memoize a property if at least one of its arguments is not
|
|
hashable (see ``CompiledType.hashable``).
|
|
|
|
|
|
Soundness checks
|
|
----------------
|
|
|
|
In order to automatically reject as many memoized properties that could break
|
|
the soundness principle, the memoization of a property P that creates side
|
|
effects is forbidden, and memoization of properties that can have P in their
|
|
call trees. Two kinds of side effects are automatically detected:
|
|
|
|
* When a property calls a logic equation's "solve" property: solves mutates
|
|
logic variables, so it is unsafe for a memoized property to access it.
|
|
Without this protection, the following could happen:
|
|
|
|
.. code-block:: python
|
|
|
|
@langkit_property(memoized=True)
|
|
def p1():
|
|
return Self.equation.solve
|
|
|
|
@langkit_property()
|
|
def p2():
|
|
return If(
|
|
Self.p1,
|
|
Self.logic_var.get_value,
|
|
No(FooNode)
|
|
)
|
|
|
|
The first time ``p2`` is called, it calls ``p1``, which is evaluated; it
|
|
returns True, which is saved for later calls, and most importantly, ``.solve``
|
|
mutates ``Self.logic_var`` to contain the result of the equation resolution.
|
|
|
|
The second time ``p2`` is called, ``p1`` is not evaluated, thanks to a
|
|
memoization hit, so ``.solve`` is not called, and thus ``Self.logic_var`` is
|
|
left with whatever value it contains. So ``p2`` may return an obsolete value
|
|
in this case.
|
|
|
|
* When a property tries to access the value of a logic variable. A variation of
|
|
the example above shows why:
|
|
|
|
.. code-block:: python
|
|
|
|
@langkit_property(memoized=True)
|
|
def p1():
|
|
return Self.logic_var.get_value
|
|
|
|
@langkit_property()
|
|
def p2():
|
|
v1 = Var(If(
|
|
Self.equation_1.solve,
|
|
Self.p1,
|
|
No(FooNode)
|
|
))
|
|
v2 = Var(If(
|
|
Self.equation_1.solve,
|
|
Self.p1,
|
|
No(FooNode)
|
|
))
|
|
return MyStruct.new(v1=v1, v2=v2)
|
|
|
|
The first time ``p1`` is called, it evaluates ``get_value`` and returns the
|
|
value that was assigned to the logic var during the resolution of
|
|
``equation_1``. However the second time it is called, it returns the same
|
|
value instead of getting the value assigned to ``logic_var`` during the
|
|
resolution of ``equation_2``.
|
|
|
|
External properties can do all sorts of side effects on their own and Langkit
|
|
cannot automatically get to know about them. In order to benefit from the
|
|
memoization soundness protection system, the declaration of external properties
|
|
can indicate that they are unsafe to memoize (see ``langkit_property``'s
|
|
``call_non_memoizable_because`` argument).
|
|
|
|
While this protection mechanism is useful to prevent the accidental
|
|
introduction of memoization bugs, it can be also too strict is some cases.
|
|
Consider for instance the following example:
|
|
|
|
.. code-block:: python
|
|
|
|
@langkit_property(memoized=True)
|
|
def p():
|
|
return If(
|
|
Self.equation.solve,
|
|
Self.logic_var.get_value,
|
|
No(FooNode)
|
|
)
|
|
|
|
Memoization is safe in this case, as all calls to ``.get_value`` are
|
|
necessarily preceeded by corresponding calls to ``.solve``, i.e. it is not
|
|
possible for ``.get_value`` to access stale data. In order to force Langkit to
|
|
allow the memoization of this property, use ``langkit_property``'s
|
|
``call_memoizable`` argument.
|
|
|
|
|
|
Interaction with PLE
|
|
--------------------
|
|
|
|
Since the very purpose of the Populate Lexical Env pass is to create side
|
|
effects (each step can modify lexical environment), trying to cache property
|
|
return values during this pass is unsound. For this reason, property
|
|
memoization is disabled during PLE.
|
|
|
|
For the rare cases where memoization is both necessary for reasonable run time
|
|
performance and is known not to trigger caching bugs (due to PLE side effects),
|
|
it is possible to force memoization during PLE: use ``langkit_property``'s
|
|
``memoize_in_populate`` argument.
|