Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions changelog/dmd.cent-ucent.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Implement the 128-bit integer types `cent` and `ucent`

The previously reserved 128-bit integer types `cent` and `ucent` are now
implemented and usable. They behave like the other integer types:

- 16-byte values with 8-byte alignment, supporting all integer operators:
arithmetic, bitwise, shifts, comparisons, and compound assignment.
- Integer literals that do not fit in 64 bits now have type `cent` (or
`ucent` with a `u` suffix) instead of being an error.
- Constant folding and compile-time evaluation (CTFE) work with 128-bit
values, including `enum` constants, `static assert`, and compile-time
functions.
- On x86-64, arithmetic is lowered to inline hardware instructions,
including a single `DIV`/`IDIV` for division by a 64-bit divisor;
128/128-bit division and modulo fall back to the existing `core.int128`
runtime functions.
- On 32-bit x86, all arithmetic, division and comparisons are lowered to the
existing `core.int128` runtime functions (the platform has no 128-bit
registers). This also covers returning 128-bit values from functions,
conditional expressions, boolean tests, and casts to smaller integer types.
- The `.min` and `.max` properties and importC's `__int128`/`unsigned __int128`
are supported.
- `typeid(cent)` and `typeid(ucent)` are supported; the runtime types
`TypeInfo_zi`/`TypeInfo_zk` are provided by a rebuilt druntime.

Conversions between 128-bit integers and floating point types are not yet
implemented and produce a compile-time error.
20 changes: 20 additions & 0 deletions compiler/include/dmd/common/int128.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

/* Compiler implementation of the D programming language
* Copyright (C) 1999-2026 by The D Language Foundation, All Rights Reserved
* written by Walter Bright
* https://www.digitalmars.com
* Distributed under the Boost Software License, Version 1.0.
* https://www.boost.org/LICENSE_1_0.txt
* https://github.com/dlang/dmd/blob/master/src/dmd/common/int128.h
*/

#pragma once

#include "dsystem.h"

// Mirrors dmd.common.int128.Cent
struct alignas(16) Cent

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI this is not common code, so it's location is only a bit odd for that reason alone.

Other backends implement they own versions/library of double-int (and wide-int for N-sized integers), so maybe better to move to root library, along with overloads of all operators.

See longdouble as an example.

Though not really a problem. As downstreams can just move this file (and int128.d) to the bin and implement their own version that wraps their own backend type irrespective of where it is placed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not add dmd.common.int128.

Also, common is just root but with some -betterC stuff, like attributes that were added. It's something that Andrei worked on years ago but didn't get very far with.

{
uint64_t lo; // low 64 bits
uint64_t hi; // high 64 bits
};
12 changes: 12 additions & 0 deletions compiler/include/dmd/expression.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "root/complex_t.h"
#include "root/dcompat.h"
#include "root/optional.h"
#include "common/int128.h"

class Type;
class TypeVector;
Expand Down Expand Up @@ -117,6 +118,7 @@ class Expression : public ASTNode
}

IntegerExp* isIntegerExp();
BigIntegerExp* isBigIntegerExp();
ErrorExp* isErrorExp();
VoidInitExp* isVoidInitExp();
RealExp* isRealExp();
Expand Down Expand Up @@ -239,6 +241,16 @@ class IntegerExp final : public Expression
static IntegerExp literal();
};

class BigIntegerExp final : public Expression

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No to introducing a new integer expression. Instead adapt the existing IntegerExp. 99% of the time it'll still use the fast path and do arithmetic on value.lo directly only (ensuring that hi is always 0)

You can still keep around the helpers like isCentType below to keep the double and single int paths separate. At the same time you'll save on a lot of duplication.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some concerns with that, which is why I did not do it that way.

  1. Memory usage, from other experiments that @rainers has been doing the increase would be noticeable
  2. In practice its operating on sinteger_t's; and integer_t, I suspect that this would have a lot more effect than you may realize. So there will be duplication regardless.
  3. It'll scale to 256bit without hurting the smaller sizes (however they are not needed atm).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There'll be no growth in memory. It'll just be extended as needed, we already do this for general arrays.

union {
  dinteger_t _value;
  Cent* _widevalue;
}

And we can get away with gating on the Type for now when reading the value as it's only cent to support.

To future proof for _BitInt or if D opts to have it's own N-sized integers, we'll have to give up the existing implementation and layout anyway (can store length in a ushort). The pervasiveness of assuming dinteger_t == 64 bits everywhere needs to be ejected from the compiler. Attempts to mitigate by not unifying all internal integer representations will just make things worse in the long run.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've checked Expression, and there's even enough padding room to put in the length of a variable-sized integer here.

static struct BitFields

Obviously, just store a ushort with a generic name and then derived expressions can use it for whatever they like. Possibly even to move their bitfields into Expression itself to reduce even more memory.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
public:
Cent value;

static BigIntegerExp *create(Loc loc, Cent value, Type *type);
void accept(Visitor *v) override { v->visit(this); }
BigIntegerExp *syntaxCopy() override { return this; }
};

class ErrorExp final : public Expression
{
public:
Expand Down
3 changes: 3 additions & 0 deletions compiler/include/dmd/tokens.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "root/dcompat.h"
#include "root/port.h"
#include "globals.h"
#include "common/int128.h"

class Identifier;

Expand Down Expand Up @@ -402,6 +403,7 @@ enum class EXP : unsigned char
// Basic types
void_,
int64,
bigInteger,
float64,
complex80,
import_,
Expand Down Expand Up @@ -454,6 +456,7 @@ struct Token
// Integers
sinteger_t intvalue;
uinteger_t unsvalue;
Cent centvalue;

// Floats
real_t floatvalue;
Expand Down
3 changes: 3 additions & 0 deletions compiler/include/dmd/visitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ class CInitializer;

class Expression;
class IntegerExp;
class BigIntegerExp;
class ErrorExp;
class RealExp;
class ComplexExp;
Expand Down Expand Up @@ -473,6 +474,7 @@ class ParseTimeVisitor
// Expressions
virtual void visit(DeclarationExp *e) { visit((Expression *)e); }
virtual void visit(IntegerExp *e) { visit((Expression *)e); }
virtual void visit(BigIntegerExp *e) { visit((Expression *)e); }
virtual void visit(NewAnonClassExp *e) { visit((Expression *)e); }
virtual void visit(IsExp *e) { visit((Expression *)e); }
virtual void visit(RealExp *e) { visit((Expression *)e); }
Expand Down Expand Up @@ -632,6 +634,7 @@ class Visitor : public ParseTimeVisitor
virtual void visit(FuncAliasDeclaration *s) { visit((FuncDeclaration *)s); }
virtual void visit(ErrorInitializer *i) { visit((Initializer *)i); }
virtual void visit(ErrorExp *e) { visit((Expression *)e); }
virtual void visit(BigIntegerExp *e) { visit((Expression *)e); }
virtual void visit(ComplexExp *e) { visit((Expression *)e); }
virtual void visit(StructLiteralExp *e) { visit((Expression *)e); }
virtual void visit(CompoundLiteralExp *e) { visit((Expression *)e); }
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/build.d
Original file line number Diff line number Diff line change
Expand Up @@ -1565,7 +1565,7 @@ auto sourceFiles()
bitfields.d file.d int128.d blake3.d sha.d outbuffer.d smallbuffer.d charactertables.d identifiertables.d
"),
commonHeaders: fileArray(env["COMMON"], "
outbuffer.h
outbuffer.h int128.h
"),
root: fileArray(env["ROOT"], "
aav.d complex.d env.d longdouble.d man.d optional.d response.d speller.d string.d strtold.d
Expand Down
26 changes: 26 additions & 0 deletions compiler/src/dmd/astbase.d
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import dmd.astenums;
import dmd.visitor.parsetime;
import dmd.tokens : EXP;
import dmd.expression;
import dmd.common.int128;

/** The ASTBase family defines a family of AST nodes appropriate for parsing with
* no semantic information. It defines all the AST nodes that the parser needs
Expand Down Expand Up @@ -4573,6 +4574,7 @@ struct ASTBase
final pure inout nothrow @nogc @trusted
{
inout(IntegerExp) isIntegerExp() { return op == EXP.int64 ? cast(typeof(return))this : null; }
inout(BigIntegerExp) isBigIntegerExp() { return op == EXP.bigInteger ? cast(typeof(return))this : null; }
inout(ErrorExp) isErrorExp() { return op == EXP.error ? cast(typeof(return))this : null; }
inout(RealExp) isRealExp() { return op == EXP.float64 ? cast(typeof(return))this : null; }
inout(IdentifierExp) isIdentifierExp() { return op == EXP.identifier ? cast(typeof(return))this : null; }
Expand Down Expand Up @@ -4795,6 +4797,30 @@ struct ASTBase
}
}

extern (C++) final class BigIntegerExp : Expression
{
Cent value;

extern (D) this(Loc loc, Cent value, Type type)
{
super(loc, EXP.bigInteger, __traits(classInstanceSize, BigIntegerExp));
assert(type);
if (!type.isScalar())
{
if (type.ty != Terror)
error(loc, "integral constant must be scalar type, not %s", type.toChars());
type = Type.terror;
}
this.type = type;
this.value = value;
}

override void accept(Visitor v)
{
v.visit(this);
}
}

extern (C++) final class NewAnonClassExp : Expression
{
Expression thisexp; // if !=null, 'this' for class being allocated
Expand Down
10 changes: 10 additions & 0 deletions compiler/src/dmd/backend/backconfig.d
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,9 @@ if (exe & (EX_LINUX | EX_LINUX64 | EX_FREEBSD | EX_FREEBSD64 | EX_OPENBSD | EX_O
_tyalignsize[TYreal] = 4;
_tyalignsize[TYireal] = 4;
_tyalignsize[TYcreal] = 4;
// _Alignof(_BitInt(128)) == 4 on i386 System V
_tyalignsize[TYcent] = 4;
_tyalignsize[TYucent] = 4;
}
else if (exe & (EX_OSX | EX_OSX64))
{
Expand Down Expand Up @@ -650,6 +653,13 @@ void util_setAArch64(exefmt_t exe)
{
util_set64(exe);

// _Alignof(_BitInt(128)) == 16 on AArch64 (AAPCS).
// Note: on 64-bit TYdelegate == TYcent and TYdarray == TYucent, so
// delegates/darrays inherit the 16-byte alignment; that is only an
// over-alignment of their storage and is harmless.
_tyalignsize[TYcent] = 16;
_tyalignsize[TYucent] = 16;

if (exe & EX_windos)
{
_tysize[TYreal] = 16;
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dmd/backend/cgcs.d
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,11 @@ void ecom(ref CGCS cgcs, ref elem* pe)
tym == TYvoid ||
e.Ety & mTYvolatile)
return;
/* Don't CSE 128-bit values on 32-bit x86: they cannot be held in
* registers, so the register-based CSE reload has no target registers.
*/
if (I32 && (tym == TYcent || tym == TYucent))
return;
if (tyfloating(tym) && config.inline8087)
{
/* can CSE XMM code, but not x87
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dmd/backend/cgelem.d
Original file line number Diff line number Diff line change
Expand Up @@ -4646,7 +4646,7 @@ private elem* elcmp(elem* e, Goal goal)
// Only need to examine MSW
tym_t ty = sz == 4 ? TYint :
sz == 8 ? TYint :
TYlong; // for TYcent's
TYllong; // for TYcent's
e.E1 = el_una(OPmsw, ty, e1);
e2.Ety = ty;
return optelem(e, Goal.value);
Expand Down
37 changes: 37 additions & 0 deletions compiler/src/dmd/backend/el.d
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import core.stdc.stdio;
import core.stdc.stdlib;
import core.stdc.string;

import dmd.common.int128 : Cent;
import dmd.backend.cdef;
import dmd.backend.cc;
import dmd.backend.oper;
Expand Down Expand Up @@ -976,6 +977,23 @@ elem* el_long(tym_t t,targ_llong val)
return e;
}

/***************************
* Make a constant element.
* Params:
* t = type of constant
* val = 128-bit value
* Returns:
* constant element
*/
elem* el_cent(tym_t t, Cent val)
{
elem* e = el_calloc();
e.Eoper = OPconst;
e.Ety = t;
e.Vcent = val;
return e;
}

/******************************
* Create a const integer vector elem
* Params:
Expand Down Expand Up @@ -2245,6 +2263,15 @@ bool el_allbits(const elem* e,int bit)

case 8: break;

case 16:
// 128-bit constant: 0, 1, or -1
if (bit == -1)
return e.Vcent.lo == cast(ulong)-1L && e.Vcent.hi == cast(ulong)-1L;
else if (bit == 0)
return e.Vcent.lo == 0 && e.Vcent.hi == 0;
else // bit == 1
return e.Vcent.lo == 1 && e.Vcent.hi == 0;

default:
assert(0);
}
Expand All @@ -2268,6 +2295,16 @@ bool el_signx32(const elem* e)
if (e.Vullong != cast(int)e.Vullong)
return false;
}
else if (tysize(e.Ety) == 16)
{
const ulong lo = e.Vcent.lo;
const ulong hi = e.Vcent.hi;
if (hi == 0 && lo <= int.max)
return true;
if (hi == cast(ulong)-1L && lo >= cast(ulong)int.min)
return true;
return false;
}
return true;
}

Expand Down
21 changes: 19 additions & 2 deletions compiler/src/dmd/backend/evalu8.d
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,24 @@ static if (0)
{
targ_llong rem, quo;

assert(!(tym == TYcent || tym == TYucent)); // not yet
if (tym == TYcent || tym == TYucent)
{
// 128-bit dividend / 64-bit divisor; the result packs
// {lo = quotient, hi = remainder} (see toElemCentDivMod).
Cent divisor;
divisor.lo = e2.Vcent.lo;
divisor.hi = (uns || !(divisor.lo >> 63)) ? 0 : -1L;
Cent modulus;
Cent q;
if (uns)
q = dmd.common.int128.udivmod(e1.Vcent, divisor, modulus);
else
q = dmd.common.int128.divmod(e1.Vcent, divisor, modulus);
e.Vcent.lo = q.lo;
e.Vcent.hi = modulus.lo;
break;
}

assert(!tyfloating(tym));
if (!boolres(e2))
goto div0;
Expand Down Expand Up @@ -1742,7 +1759,7 @@ else
break;

case OPmsw:
switch (tysize(tym))
switch (tysize(tybasic(e1.Ety)))
{
case 4:
e.Vllong = (l1 >> 16) & 0xFFFF;
Expand Down
19 changes: 18 additions & 1 deletion compiler/src/dmd/backend/gdag.d
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ private void aewalk(ref GlobalOptimizer go, ref elem* pn, vec_t ae)
else if (n != e &&
el_match(n,e) &&
e.Ecount < 0xFF-1 && // must fit in unsigned char
cse_float(n)
cse_float(n) &&
cse_cent(n)
)
{
pn = e; // replace n with e
Expand Down Expand Up @@ -954,3 +955,19 @@ private bool cse_float(const elem* e)
e.Eoper != OPvar && e.Eoper != OPconst) ||
(tyxmmreg(e.Ety) && config.fpxmmregs);
}

/*************************************
* Determine if 128-bit integers should be cse'd.
* Params:
* e = elem to be tested
* Returns:
* true if should be cse'd
*/

@trusted
private bool cse_cent(const elem* e)
{
// Don't CSE 128-bit values on 32-bit x86: they cannot be held in
// registers, so the register-based CSE reload has no target registers.
return !(I32 && (tybasic(e.Ety) == TYcent || tybasic(e.Ety) == TYucent));
}
Loading