openlang

D's pure lets a function write through its arguments

D's pure is weaker than the word suggests, and I only noticed because an evaluator experiment wanted to cache calls.

int[] log;

pure void bump(int[] a)
{
    a[0]++;          // fine, writes through the argument
    // log ~= a[0];  // rejected, touches a mutable global
}

pure in D means no access to mutable global or static state. It says nothing against modifying memory reachable from the parameters. The spec calls this weak purity. A call only becomes something you could cache when the parameters can't carry mutable indirections, immutable data or plain values, and that's strong purity. Same keyword for both, and the parameter types pick which one you get.

So if the evaluator caches results of pure host functions, keying on the attribute alone would cache bump too, and the second call would skip the write. Key on strongly pure signatures instead? Or does that list of conditions get long enough that an explicit opt-in attribute on the host side is simpler?

后续讨论 4

顺着彼此的依据读下去
  1. 固定链接

    Swap the parameter for const(int)[] and see whether the cache gets any safer. bump stops compiling, good. But a function taking const(int)[] still isn't something you can cache, because const only says this function won't write. The caller can hold a mutable reference to the same array, change an element between two calls, and the cached result is stale. Only immutable promises nobody writes. So the rule for the evaluator is about the parameter's qualifier, not about the absence of writes inside the function, and I'd want that spelled out wherever the host registers a function.

  2. 固定链接

    Does the compiler itself do anything with strong purity, or is it only permission? If DMD already treats two identical strongly pure calls as one, that's a hint the classification is reliable enough to build on. If it's only something the spec allows, then the evaluator's cache would be the first thing in the chain that actually depends on it, and I'd want a test that fails when someone loosens a parameter type.

  3. 固定链接

    C has the opposite arrangement, which may help frame the choice. C23 added the [[reproducible]] and [[unsequenced]] attributes for function types, two strengths of a similar promise. The compiler does not check them. If a function marked that way does not behave as promised, the behavior is undefined. D checks pure and leaves the caching decision to whoever reads the signature. C lets the optimizer act on the promise and checks nothing. For an evaluator cache, the D arrangement is the safer one to copy, provided the key is the checked signature and not a separate flag the host can set freely.

  4. 固定链接

    Honest answer, I don't know what DMD does with it, and I'd rather not build on whatever the optimizer happens to do anyway. So yes, the cache would be the first thing that really depends on the classification. A test that registers a host function, flips one parameter from immutable to const, and expects registration to refuse it is cheap to write. Funny side effect of nullbyte's point. If the key is the checked signature, my explicit opt-in attribute has nothing left to do, and I don't miss it.

同一个问题的其他角度

  1. Same Int, two targets, two answers

    Found this while running the same Gleam module on both targets, and it's a runtime fact more than a language one. gleam run p…

    回复 3最近活动 gleam
  2. CTFE happens where the declaration asks for it

    Had D's CTFE pictured wrong for a while. I assumed that once a function is fit for compile time evaluation, the compiler woul…

    回复 4最近活动 d
  3. Braces around expr decide what the bytecode compiler sees

    Unbraced expr is the one Tcl habit I'd ban if I could! It looks like a style nit and it isn't. The braced line prints 0. The…

    回复 16最近活动 tcl