dcompile-timebigintphobosrecursive-templates

Unable to recursively multiply BigInt beyond a certain number of iterations at compile-time in D


I need to get the product of an arbitrary number of variables. The actual number of variables and their values will be known at compile-time, however I cannot hardcode these because they come from reflection done on types at compile-time, using templates.

I can get the product of these into a BigInt at runtime just fine, however if I try to do so at compile-time using templates and immutable variables, I can only get the product for a small number of variables before I get a compiler error.

Here is a condensed example that doesn't use type-traits, but suffers from the same issue:

import std.bigint;   // BigInt
import std.stdio;    // writeln

template Product(ulong value) {
    immutable BigInt Product = value;
}

template Product(ulong value, values...) {
    immutable BigInt Product = Product!value * Product!values;
}

immutable BigInt NO_PROBLEM = cast(BigInt)ulong.max * ulong.max * ulong.max;
immutable BigInt ERROR = Product!(ulong.max, ulong.max, ulong.max);

void main() {
    writeln(NO_PROBLEM, " ", ERROR);
}

Trying to compile this with dmd compiler gives the error message:

/opt/compiler-explorer/dmd2-nightly/dmd2/linux/bin64/../../src/druntime/import/core/cpuid.d(121): Error: static variable `_dataCaches` cannot be read at compile time
/opt/compiler-explorer/dmd2-nightly/dmd2/linux/bin64/../../src/phobos/std/internal/math/biguintcore.d(200):        called from here: `dataCaches()`
/opt/compiler-explorer/dmd2-nightly/dmd2/linux/bin64/../../src/phobos/std/internal/math/biguintcore.d(1547):        called from here: `getCacheLimit()`
/opt/compiler-explorer/dmd2-nightly/dmd2/linux/bin64/../../src/phobos/std/internal/math/biguintcore.d(758):        called from here: `mulInternal(result, cast(const(uint)[])y.data, cast(const(uint)[])x.data)`
/opt/compiler-explorer/dmd2-nightly/dmd2/linux/bin64/../../src/phobos/std/bigint.d(380):        called from here: `mul(this.data, y.data)`
/opt/compiler-explorer/dmd2-nightly/dmd2/linux/bin64/../../src/phobos/std/bigint.d(380):        called from here: `this.data.opAssign(mul(this.data, y.data))`
/opt/compiler-explorer/dmd2-nightly/dmd2/linux/bin64/../../src/phobos/std/bigint.d(430):        called from here: `r.opOpAssign(y)`
<source>(9):        called from here: `Product.opBinary(Product)`
<source>(13): Error: template instance `example.Product!(18446744073709551615LU, 18446744073709551615LU, 18446744073709551615LU)` error instantiating
ASM generation compiler returned: 1

I'm quite puzzled by this. At initial glance, it would appear that too much memory is being requested at compile-time (I would understand if there were less heap available during compile-time execution than at runtime), however I'm not sure this is actually the problem, as I can generate the result at compile-time, just not through the recursive template.

Could it be a bug in the Phobos runtime, or an undocumented limitation?
std.bigint appears to be designed to be able to produce huge values at compile-time, with lines such as this compiling and executing fine (and bloating the size of the executable!):

immutable BigInt VERY_BIG = BigInt(2) ^^ 10000000;

Solution

  • The error happens on the last line of this function:

    https://github.com/dlang/phobos/blob/e0af01c8adf75b164b43832dd7544e297347cf6f/std/internal/math/biguintcore.d#L1824-L1844

    It looks like std.bigint is currently not written to work in CTFE in this circumstance. Perhaps simply making the GC.free call conditional on __ctfe will fix the problem.

    As to why it happens with 10 iterations but not 11, the function has a branch which allows performing the calculation for small numbers without dynamic memory allocation.