https://issues.dlang.org/show_bug.cgi?id=15984
--- Comment #11 from Martin Nowak <c...@dawg.eu> --- An analysis of the current state of affairs, took me a while to dig through the numerous hacks, related changes and PRs. ========== Context information ========== - Accessing upvalues (variables from outer scopes) in nested functions is currently done via 2 different mechanisms, either by passing a pointer to a GC closure [¹] or by direct-stack access [²][³]. The layouts of those 2 are very different and it would be difficult to make them identical, the former is generated by the frontend the latter by the backend. - frequire (in contracts) gets treated like a nested function and would therefor also uses either of the access variants. At the moment frequire also supports escaping of parameters (triggers a GC closure allocation), just like a normal nested function. - frequire of base classes/interfaces are also called as nested functions, but from the class implementing/overriding the method. So currently Derived.foo is calling Base.foo.frequire (nested in Base.foo), but uses the access method (passes sclosure or sthis pointer) for Derived.foo.frequire. - Code for Base.foo is generated independent of any derived classes, thus the current implementation requires that all nested frequire implementations use the same access method, or that both access methods have the same memory layout. Furthermore the offsets of all accessed variables must be identical. - Whether or not a closure for parameters and variables is allocated, depends on the individual and separately compiled, i.e. invisible, implementation of each implementing/overriding method (see issue 9383). [¹]: [buildClosure](https://github.com/dlang/dmd/blob/e087760eda0dd0242d6edb1c4acdfea36bce8821/src/toir.d#L779) [²]: [getEthis](https://github.com/dlang/dmd/blob/0e4152265a2e16ca49ea8ea34a82109ce6c59cbc/src/toir.d#L99) [³]: [e2ir/SymbolExp](https://github.com/dlang/dmd/blob/e087760eda0dd0242d6edb1c4acdfea36bce8821/src/e2ir.d#L1049)) ========== Recent attempts at fixing the problem ========== Issue 9383 was somewhat fixed by https://github.com/dlang/dmd/pull/4788. This is a hack that tried to make frequire a special nested function, that will always access upvalues directly through the stack [¹]. It relies on very specific and fragile backend details of the function prologue. - As a first step in a function with GC closure, memory will be allocated. Before calling _d_allocmemory, all parameters need to be saved to the stack. So indeed anything that could be referenced from a nested frequire is already on the stack. - But frequire is only called after the closure allocation has been completed, so all parameters (and variables) have already been moved to the GC closure, and the variable offsets in the frontend are adjusted accordingly. - The forceStackAccess hack ignores those offsets, and instead uses the now stale backend offsets for the initial parameter spilling [²]. - Nobody ever told the backend, that those initial stack locations are referenced by the frontend. This of course led to subtle bugs when trying to bend (in the frontend) how parameters are accessed (https://github.com/dlang/dmd/pull/5420/files). https://github.com/dlang/dmd/pull/5765 tries to fix `this` usage in interfaces, b/c accessing that was bend to the spilled `this` parameter in some class' function implementation. The idea of that PR is to pass the class-to-interface offset as "hidden" argument to the nested frequire function. [¹]: https://github.com/dlang/dmd/pull/4788/files#diff-6e3ab8a500e476994f345ede433811bbR961 [²]: https://github.com/dlang/dmd/pull/4788/files#diff-6e3ab8a500e476994f345ede433811bbR982 ========== Currently remaining issues ========== - When the parameters aren't used in the implementation class, they won't get saved on the stack and the nested frequire function will find garbage instead. This works for normal nested functions, so likely some of the frequire hacks break this. - Adjusting the this pointer for interfaces isn't yet solved, the PR adds a lot more hacks to an already hard to maintain implementation. - Escaping parameters in frequire, e.g. by using them in a delegate, will no longer work when being forced to use the stack access. ========== Proposed resolution ========== - We make frequire a special nested function that always get's called with a closure. When no GC closure is needed, the closure can be allocated with the same layout on the stack. - Allows to get rid of existing Ffakeeh, frame pointer, and function prolog hacks. - The variable locations in the closure (on the stack) can replace any other stack location of that variable, so no overhead on stack space should be necessary. It might be somewhat tricky to get the backend to enregister variables after the frequire contracts have been validated. - One way to support interfaces would be to set a 'this' pointer in the closure to the correct offset before each frequire call in the chain. Somewhat similar to what's already been done here [¹]. [¹]: https://github.com/dlang/dmd/blob/91f0c943368d50efe70816bc499bb8cbcbd5c9b5/src/toir.d#L171 --