mirror of
https://github.com/openRuyi-Project/gcc.git
synced 2026-09-06 14:01:37 +00:00
D front-end changes: - Import dmd v2.112.0. - Bitfields feature is now enabled by default. - The compiler now accepts `-std=d2024' and `-std=d202y'. - An error is now issued for dangling `else' statements. - `finally' statements are no longer rewritten to a sequence if no `Exception' was thrown. - Some forms of `printf' calls are now treated as `@safe'. - Implicit integer conversions in `int op= float` assignments has been deprecated. D runtime changes: - Import druntime v2.112.0. - Added `filterCaughtThrowable' in `core.thread.ThreadBase'. Phobos changes: - Import phobos v2.112.0. gcc/d/ChangeLog: * dmd/VERSION: Bump version to v2.112.0. * dmd/MERGE: Merge upstream dmd 24a41073c2. * d-attribs.cc (build_attributes): Update for new front-end interface. * d-builtins.cc (build_frontend_type): Likewise. (matches_builtin_type): Likewise. (d_init_versions): Predefine D_Profile when compiling with profile enabled. * d-codegen.cc (get_array_length): Update for new front-end interface. (lower_struct_comparison): Likewise. (build_array_from_val): Likewise. (get_function_type): Likewise. (get_frameinfo): Likewise. * d-compiler.cc (Compiler::paintAsType): Likewise. * d-convert.cc (convert_expr): Likewise. (convert_for_rvalue): Likewise. (convert_for_assignment): Likewise. (d_array_convert): Likewise. * d-diagnostic.cc (verrorReport): Rename to ... (vreportDiagnostic): ... this. (verrorReportSupplemental): Rename to ... (vsupplementalDiagnostic): ... this. * d-lang.cc (d_handle_option): Handle -std=d2024 and -std=d202y. (d_parse_file): Update for new front-end interface. * d-target.cc (Target::fieldalign): Likewise. (Target::isVectorTypeSupported): Likewise. (Target::isVectorOpSupported): Likewise. * decl.cc (get_symbol_decl): Likewise. (DeclVisitor::visit): Likewise. (DeclVisitor::visit (FuncDeclaration *)): Do NRVO on `__result' decl. * expr.cc (needs_postblit): Remove. (needs_dtor): Remove. (lvalue_p): Remove. (ExprVisitor::visit): Update for new front-end interface. (ExprVisitor::visit (AssignExp *)): Update for front-end lowering expression using templates. * imports.cc (ImportVisitor::visit): Update for new front-end interface. * intrinsics.def (INTRINSIC_VA_ARG): Update signature. (INTRINSIC_C_VA_ARG): Update signature. (INTRINSIC_VASTART): Update signature. * lang.opt: Add -std=d2024 and -std=d202y. * toir.cc (IRVisitor::visit): Update for new front-end interface. * typeinfo.cc (TypeInfoVisitor::visit): Likewise. (TypeInfoVisitor::visit (TypeInfoStructDeclaration *)): Ensure semantic is ran on all TypeInfo members. (base_vtable_offset): Update for new front-end interface. * types.cc (TypeVisitor::visit): Likewise. libphobos/ChangeLog: * libdruntime/MERGE: Merge upstream druntime 24a41073c2. * libdruntime/__importc_builtins.di: Reimplement. * src/MERGE: Merge upstream phobos 808314eb2. * testsuite/libphobos.aa/test_aa.d: Adjust test. * testsuite/libphobos.gc/forkgc2.d: Removed. * testsuite/libphobos.thread/filterthrownglobal.d: New test. * testsuite/libphobos.thread/filterthrownmethod.d: New test. gcc/testsuite/ChangeLog: * gdc.dg/pr90601.d: Adjust test. * lib/gdc-utils.exp: Handle new compiler options.
1051 lines
28 KiB
D
1051 lines
28 KiB
D
/**
|
|
* template implementation of associative arrays.
|
|
*
|
|
* Copyright: Copyright Digital Mars 2000 - 2015, Steven Schveighoffer 2022.
|
|
* License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
* Authors: Martin Nowak, Steven Schveighoffer, Rainer Schuetze
|
|
*
|
|
* Source: $(DRUNTIMESRC core/internal/_newaa.d)
|
|
*
|
|
* derived from rt/aaA.d
|
|
*/
|
|
module core.internal.newaa;
|
|
|
|
/// AA version for debuggers, bump whenever changing the layout
|
|
immutable int _aaVersion = 1;
|
|
|
|
import core.internal.util.math : min, max;
|
|
import core.internal.traits : substInout;
|
|
|
|
// grow threshold
|
|
private enum GROW_NUM = 4;
|
|
private enum GROW_DEN = 5;
|
|
// shrink threshold
|
|
private enum SHRINK_NUM = 1;
|
|
private enum SHRINK_DEN = 8;
|
|
// grow factor
|
|
private enum GROW_FAC = 4;
|
|
// growing the AA doubles it's size, so the shrink threshold must be
|
|
// smaller than half the grow threshold to have a hysteresis
|
|
static assert(GROW_FAC * SHRINK_NUM * GROW_DEN < GROW_NUM * SHRINK_DEN);
|
|
// initial load factor (for literals), mean of both thresholds
|
|
private enum INIT_NUM = (GROW_DEN * SHRINK_NUM + GROW_NUM * SHRINK_DEN) / 2;
|
|
private enum INIT_DEN = SHRINK_DEN * GROW_DEN;
|
|
|
|
private enum INIT_NUM_BUCKETS = 8;
|
|
// magic hash constants to distinguish empty, deleted, and filled buckets
|
|
private enum HASH_EMPTY = 0;
|
|
private enum HASH_DELETED = 0x1;
|
|
private enum HASH_FILLED_MARK = size_t(1) << 8 * size_t.sizeof - 1;
|
|
|
|
/// AA wrapper
|
|
struct AA(K, V)
|
|
{
|
|
Impl!(K,V)* impl;
|
|
alias impl this;
|
|
|
|
@property bool empty() const pure nothrow @nogc @safe
|
|
{
|
|
pragma(inline, true);
|
|
return impl is null || !impl.length;
|
|
}
|
|
@property size_t length() const pure nothrow @nogc @safe
|
|
{
|
|
pragma(inline, true);
|
|
return impl is null ? 0 : impl.length;
|
|
}
|
|
}
|
|
|
|
/// like core.internal.traits.Unconst, but stripping inout, too
|
|
private template Unconstify(T : const U, U)
|
|
{
|
|
static if (is(U == inout V, V))
|
|
alias Unconstify = V;
|
|
else
|
|
alias Unconstify = U;
|
|
}
|
|
|
|
ref _refAA(K, V)(ref V[K] aa) @trusted
|
|
{
|
|
pragma(inline, true);
|
|
return *(cast(AA!(substInout!K, substInout!V)*)&aa);
|
|
}
|
|
|
|
auto _toAA(K, V)(const V[K] aa) @trusted
|
|
{
|
|
pragma(inline, true);
|
|
return *(cast(const(AA!(K, V))*)&aa);
|
|
}
|
|
|
|
auto _toAA(K, V)(inout V[K] aa) @trusted
|
|
{
|
|
pragma(inline, true);
|
|
return *(cast(inout(AA!(K, V))*)&aa);
|
|
}
|
|
|
|
// for backward compatibility, but should be deprecated
|
|
auto _toAA(K, V)(shared const V[K] aa) @trusted
|
|
{
|
|
pragma(inline, true);
|
|
return *(cast(AA!(K, V)*)&aa);
|
|
}
|
|
|
|
// for backward compatibility, but should be deprecated
|
|
auto _toAA(K, V)(shared V[K] aa) @trusted
|
|
{
|
|
pragma(inline, true);
|
|
return *(cast(AA!(K, V)*)&aa);
|
|
}
|
|
|
|
// resolve ambiguity for immutable converting to const and shared const
|
|
auto _toAA(K, V)(immutable V[K] aa) @trusted
|
|
{
|
|
pragma(inline, true);
|
|
return *(cast(AA!(K, V)*)&aa);
|
|
}
|
|
|
|
static struct Entry(K, V)
|
|
{
|
|
K key;
|
|
V value;
|
|
}
|
|
|
|
// backward compatibility conversions
|
|
private ref compat_key(K, K2)(ref K2 key)
|
|
{
|
|
static if(!(is(K2 == struct) && __traits(isNested, K2)))
|
|
pragma(inline, true);
|
|
static if (is(K2 == const(char)[]) && is(K == string))
|
|
return (ref (ref return K2 k2) @trusted => *cast(string*)&k2)(key);
|
|
else
|
|
return key;
|
|
}
|
|
|
|
private void _aaMove(V)(ref V src, ref V dst) @trusted
|
|
{
|
|
import core.stdc.string : memcpy, memset;
|
|
// move without postblit!?
|
|
memcpy(&dst, &src, V.sizeof);
|
|
static if (__traits(isZeroInit, V))
|
|
memset(&src, 0, V.sizeof);
|
|
else
|
|
memcpy(&src, &V.init, V.sizeof);
|
|
}
|
|
|
|
// mimick behaviour of rt.aaA for initialization
|
|
Entry!(K, V)* _newEntry(K, V)(ref K key, auto ref V value)
|
|
{
|
|
static if (__traits(compiles, new Entry!(K, V)(key, value)))
|
|
{
|
|
auto entry = new Entry!(K, V)(key, value);
|
|
}
|
|
else static if (__traits(compiles, { K k; new Entry!(K, V)(k); }))
|
|
{
|
|
auto entry = new Entry!(K, V)(key);
|
|
_aaMove(value, entry.value);
|
|
}
|
|
else
|
|
{
|
|
auto entry = new Entry!(K, V);
|
|
_aaMove(key, entry.key);
|
|
_aaMove(value, entry.value);
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
// mimick behaviour of rt.aaA for initialization
|
|
Entry!(K, V)* _newEntry(K, V, K2)(ref K2 key)
|
|
{
|
|
static if (__traits(compiles, new Entry!(K, V)(key)) &&
|
|
!(is(V == struct) && __traits(isNested, V))) // not detected by "compiles"
|
|
{
|
|
auto entry = new Entry!(K, V)(key);
|
|
}
|
|
else static if (__traits(compiles, { K2 k; new Entry!(K, V)(k, V.init); }))
|
|
{
|
|
// with disabled ctor for V
|
|
auto entry = new Entry!(K, V)(key, V.init);
|
|
}
|
|
else
|
|
{
|
|
// with disabled ctor for K and V
|
|
auto entry = new Entry!(K, V);
|
|
entry.key = key;
|
|
}
|
|
static if (!__traits(isZeroInit, V))
|
|
{
|
|
() @trusted { (cast(ubyte*)&entry.value)[0..V.sizeof] = 0; }();
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
template pure_hashOf(K)
|
|
{
|
|
static if (!(is(K == struct) && __traits(isNested, K)) &&
|
|
__traits(compiles, function hash_t(scope const ref K key) pure nothrow @nogc @trusted { return hashOf(cast()key); }))
|
|
{
|
|
// avoid wrapper call in debug builds if pure nothrow @nogc is inferred
|
|
pragma(inline, true)
|
|
hash_t pure_hashOf(scope const ref K key) @trusted { return hashOf(cast()key); }
|
|
}
|
|
else
|
|
{
|
|
// for backward compatibility, do not require const in hashOf()
|
|
hash_t wrap_hashOf(K)(scope const ref K key) @trusted { return hashOf(cast()key); }
|
|
enum pure_hashOf = cast(hash_t function(scope ref const K key) pure nothrow @nogc @safe) &wrap_hashOf!K;
|
|
}
|
|
}
|
|
|
|
// for backward compatibilty pretend the comparison is @safe, pure, etc
|
|
// this also breaks cyclic inference on recursive data types
|
|
template pure_keyEqual(K1, K2 = K1)
|
|
{
|
|
static if (!(is(K1 == struct) && __traits(isNested, K1)) &&
|
|
!(is(K2 == struct) && __traits(isNested, K2)) &&
|
|
__traits(compiles, function bool(ref const K1 k1, ref const K2 k2) pure nothrow @nogc @trusted { return cast()k1 == cast()k2; }))
|
|
{
|
|
// avoid wrapper call in debug builds if pure nothrow @nogc is inferred
|
|
pragma(inline, true)
|
|
bool pure_keyEqual(ref const K1 k1, ref const K2 k2) @trusted { return cast()k1 == cast()k2; }
|
|
}
|
|
else
|
|
{
|
|
bool keyEqual(ref const K1 k1, ref const K2 k2) @trusted { return cast()k1 == cast()k2; }
|
|
enum pure_keyEqual = cast(bool function(ref const K1, ref const K2) pure nothrow @nogc @safe) &keyEqual;
|
|
}
|
|
}
|
|
|
|
private struct Impl(K, V)
|
|
{
|
|
private:
|
|
alias Bucket = .Bucket!(K, V);
|
|
|
|
this(size_t sz /* = INIT_NUM_BUCKETS */) nothrow
|
|
{
|
|
buckets = allocBuckets(sz);
|
|
firstUsed = cast(uint) buckets.length;
|
|
|
|
// only for binary compatibility
|
|
version(D_TypeInfo)
|
|
entryTI = typeid(Entry!(K, V));
|
|
hashFn = delegate size_t (scope ref const K key) nothrow pure @nogc @safe {
|
|
return pure_hashOf!K(key);
|
|
};
|
|
|
|
keysz = cast(uint) K.sizeof;
|
|
valsz = cast(uint) V.sizeof;
|
|
valoff = cast(uint) talign(keysz, V.alignof);
|
|
|
|
enum ctflags = () {
|
|
import core.internal.traits;
|
|
Impl.Flags flags;
|
|
static if (__traits(hasPostblit, K))
|
|
flags |= flags.keyHasPostblit;
|
|
static if (hasIndirections!K || hasIndirections!V)
|
|
flags |= flags.hasPointers;
|
|
return flags;
|
|
} ();
|
|
flags = ctflags;
|
|
}
|
|
|
|
Bucket[] buckets;
|
|
uint used;
|
|
uint deleted;
|
|
const(TypeInfo) entryTI; // only for binary compatibility
|
|
uint firstUsed;
|
|
immutable uint keysz; // only for binary compatibility
|
|
immutable uint valsz; // only for binary compatibility
|
|
immutable uint valoff; // only for binary compatibility
|
|
Flags flags; // only for binary compatibility
|
|
size_t delegate(scope ref const K) nothrow pure @nogc @safe hashFn;
|
|
|
|
enum Flags : ubyte
|
|
{
|
|
none = 0x0,
|
|
keyHasPostblit = 0x1,
|
|
hasPointers = 0x2,
|
|
}
|
|
|
|
@property size_t length() const pure nothrow @nogc @safe
|
|
{
|
|
pragma(inline, true);
|
|
assert(used >= deleted);
|
|
return used - deleted;
|
|
}
|
|
|
|
@property size_t dim() const pure nothrow @nogc @safe
|
|
{
|
|
pragma(inline, true);
|
|
return buckets.length;
|
|
}
|
|
|
|
@property size_t mask() const pure nothrow @nogc @safe
|
|
{
|
|
pragma(inline, true);
|
|
return dim - 1;
|
|
}
|
|
|
|
// find the first slot to insert a value with hash
|
|
size_t findSlotInsert(size_t hash) const pure nothrow @nogc @safe
|
|
{
|
|
for (size_t i = hash & mask, j = 1;; ++j)
|
|
{
|
|
if (!buckets[i].filled)
|
|
return i;
|
|
i = (i + j) & mask;
|
|
}
|
|
}
|
|
|
|
// lookup a key
|
|
inout(Bucket)* findSlotLookup(K2)(size_t hash, scope ref const K2 key) inout pure @safe nothrow
|
|
{
|
|
for (size_t i = hash & mask, j = 1;; ++j)
|
|
{
|
|
auto b = &buckets[i]; // avoid multiple bounds checks
|
|
if (b.hash == hash && b.entry)
|
|
if (pure_keyEqual!(K2, K)(key, b.entry.key))
|
|
return b;
|
|
if (b.empty)
|
|
return null;
|
|
i = (i + j) & mask;
|
|
}
|
|
}
|
|
|
|
void grow() pure nothrow @safe
|
|
{
|
|
// If there are so many deleted entries, that growing would push us
|
|
// below the shrink threshold, we just purge deleted entries instead.
|
|
if (length * SHRINK_DEN < GROW_FAC * dim * SHRINK_NUM)
|
|
resize(dim);
|
|
else
|
|
resize(GROW_FAC * dim);
|
|
}
|
|
|
|
void shrink() pure nothrow @safe
|
|
{
|
|
if (dim > INIT_NUM_BUCKETS)
|
|
resize(dim / GROW_FAC);
|
|
}
|
|
|
|
void resize(size_t ndim) pure nothrow @safe
|
|
{
|
|
auto obuckets = buckets;
|
|
buckets = allocBuckets(ndim);
|
|
|
|
foreach (ref b; obuckets[firstUsed .. $])
|
|
if (b.filled)
|
|
buckets[findSlotInsert(b.hash)] = b;
|
|
|
|
firstUsed = 0;
|
|
used -= deleted;
|
|
deleted = 0;
|
|
obuckets.length = 0; // safe to free b/c impossible to reference, but doesn't really free
|
|
}
|
|
|
|
void clear() pure nothrow
|
|
{
|
|
// clear all data, but don't change bucket array length
|
|
buckets[firstUsed .. $] = Bucket.init;
|
|
deleted = used = 0;
|
|
firstUsed = cast(uint) dim;
|
|
}
|
|
|
|
size_t calcHash(K2)(ref K2 key) const nothrow pure @nogc @safe
|
|
{
|
|
static if(is(K2* : K*)) // ref compatible?
|
|
hash_t hash = pure_hashOf!K(key);
|
|
else
|
|
hash_t hash = pure_hashOf!K2(key);
|
|
// highest bit is set to distinguish empty/deleted from filled buckets
|
|
return mix(hash) | HASH_FILLED_MARK;
|
|
}
|
|
|
|
static Bucket[] allocBuckets(size_t dim) pure nothrow @safe
|
|
{
|
|
// could allocate with BlkAttr.NO_INTERIOR, but that does not combine
|
|
// well with arrays and type info for precise scanning
|
|
return new Bucket[dim];
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
// Bucket
|
|
//------------------------------------------------------------------------------
|
|
|
|
private struct Bucket(K, V)
|
|
{
|
|
private pure nothrow @nogc:
|
|
size_t hash;
|
|
Entry!(K, V)* entry;
|
|
|
|
@property bool empty() const
|
|
{
|
|
pragma(inline, true);
|
|
return hash == HASH_EMPTY;
|
|
}
|
|
|
|
@property bool deleted() const
|
|
{
|
|
pragma(inline, true);
|
|
return hash == HASH_DELETED;
|
|
}
|
|
|
|
@property bool filled() const @safe
|
|
{
|
|
pragma(inline, true);
|
|
return cast(ptrdiff_t) hash < 0;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
// Helper functions
|
|
//------------------------------------------------------------------------------
|
|
|
|
private size_t talign(size_t tsize, size_t algn) @safe pure nothrow @nogc
|
|
{
|
|
immutable mask = algn - 1;
|
|
assert(!(mask & algn));
|
|
return (tsize + mask) & ~mask;
|
|
}
|
|
|
|
// mix hash to "fix" bad hash functions
|
|
private size_t mix(size_t h) @safe pure nothrow @nogc
|
|
{
|
|
// final mix function of MurmurHash2
|
|
enum m = 0x5bd1e995;
|
|
h ^= h >> 13;
|
|
h *= m;
|
|
h ^= h >> 15;
|
|
return h;
|
|
}
|
|
|
|
private size_t nextpow2(const size_t n) pure nothrow @nogc @safe
|
|
{
|
|
import core.bitop : bsr;
|
|
|
|
if (!n)
|
|
return 1;
|
|
|
|
const isPowerOf2 = !((n - 1) & n);
|
|
return 1 << (bsr(n) + !isPowerOf2);
|
|
}
|
|
|
|
pure nothrow @nogc unittest
|
|
{
|
|
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
|
|
foreach (const n, const pow2; [1, 1, 2, 4, 4, 8, 8, 8, 8, 16])
|
|
assert(nextpow2(n) == pow2);
|
|
}
|
|
|
|
//==============================================================================
|
|
// API Implementation
|
|
//------------------------------------------------------------------------------
|
|
|
|
/** Allocate associative array data.
|
|
* Called for `new SomeAA` expression.
|
|
* Returns:
|
|
* A new associative array.
|
|
* Note:
|
|
* not supported in CTFE
|
|
*/
|
|
V[K] _d_aaNew(K, V)()
|
|
{
|
|
AA!(K, V) aa;
|
|
aa.impl = new Impl!(K,V)(INIT_NUM_BUCKETS);
|
|
return *cast(V[K]*)&aa;
|
|
}
|
|
|
|
/// Determine number of entries in associative array.
|
|
/// Note:
|
|
/// emulated by the compiler during CTFE
|
|
size_t _d_aaLen(K, V)(inout V[K] a)
|
|
{
|
|
auto aa = _toAA!(K, V)(a);
|
|
return aa ? aa.length : 0;
|
|
}
|
|
|
|
/******************************
|
|
* Lookup key in aa.
|
|
* Called only from implementation of (aa[key]) expressions when value is mutable.
|
|
* Params:
|
|
* aa = associative array
|
|
* key = reference to the key value
|
|
* found = returns whether the key was found or a new entry was added
|
|
* Returns:
|
|
* if key was in the aa, a mutable pointer to the existing value.
|
|
* If key was not in the aa, a mutable pointer to newly inserted value which
|
|
* is set to zero
|
|
*/
|
|
V* _d_aaGetY(K, V, T : V1[K1], K1, V1, K2)(auto ref scope T aa, auto ref K2 key, out bool found)
|
|
{
|
|
ref aax = cast(V[K])cast(V1[K1])aa; // remove outer const from T
|
|
return _aaGetX!(K, V, K2)(aax, key, found, _noV2());
|
|
}
|
|
|
|
private struct _noV2 {}
|
|
|
|
/******************************
|
|
* Lookup key in aa.
|
|
* Called only from implementation of require, update and _d_aaGetY
|
|
* Params:
|
|
* a = associative array
|
|
* key = reference to the key value
|
|
* found = true if the value was found
|
|
* v2 = if key not found, init new value to this (except if type _noV2)
|
|
* Returns:
|
|
* if key was in the aa, a mutable pointer to the existing value.
|
|
* If key was not in the aa, a mutable pointer to newly inserted value which
|
|
* is set to v2 or is zero-initialized
|
|
*/
|
|
V* _aaGetX(K, V, K2, V2)(auto ref scope V[K] a, auto ref K2 key, out bool found, lazy V2 v2)
|
|
{
|
|
ref aa = _refAA!(K, V)(a);
|
|
|
|
// lazily alloc implementation
|
|
if (aa is null)
|
|
{
|
|
aa.impl = new Impl!(K, V)(INIT_NUM_BUCKETS);
|
|
}
|
|
|
|
ref key2 = compat_key!(K)(key);
|
|
|
|
// get hash and bucket for key
|
|
immutable hash = aa.calcHash(key2);
|
|
|
|
// found a value => return it
|
|
if (auto p = aa.findSlotLookup(hash, key2))
|
|
{
|
|
found = true;
|
|
return &p.entry.value;
|
|
}
|
|
|
|
auto pi = aa.findSlotInsert(hash);
|
|
// check load factor and possibly grow
|
|
if (!aa.buckets[pi].deleted && (aa.used + 1) * GROW_DEN > aa.dim * GROW_NUM)
|
|
{
|
|
aa.grow();
|
|
pi = aa.findSlotInsert(hash);
|
|
assert(aa.buckets[pi].empty);
|
|
}
|
|
|
|
// allocate entry and update search cache (if not throwing in _newEntry)
|
|
ref p = aa.buckets[pi];
|
|
static if (is(V2 == _noV2))
|
|
p.entry = _newEntry!(K, V)(key2);
|
|
else
|
|
p.entry = _newEntry!(K, V)(key2, v2);
|
|
if (p.deleted)
|
|
--aa.deleted;
|
|
else
|
|
aa.used++;
|
|
p.hash = hash;
|
|
aa.firstUsed = min(aa.firstUsed, cast(uint)pi);
|
|
return &p.entry.value;
|
|
}
|
|
|
|
/******************************
|
|
* Lookup key in aa.
|
|
* Called only from implementation of (aa[key]) expressions when value is not mutable.
|
|
* Params:
|
|
* aa = associative array
|
|
* key = key value
|
|
* Returns:
|
|
* pointer to value if present, null otherwise
|
|
*/
|
|
auto _d_aaGetRvalueX(K, V, K2)(inout V[K] aa, auto ref scope K2 key)
|
|
{
|
|
return _d_aaIn(aa, key);
|
|
}
|
|
|
|
/// ditto
|
|
auto _d_aaGetRvalueX(K, V, K2)(shared(V[K]) aa, auto ref scope K2 key)
|
|
{
|
|
// accept shared for backward compatibility, should be deprecated
|
|
return cast(shared(V)*)_d_aaIn(cast(V[K]) aa, key);
|
|
}
|
|
|
|
/// ditto
|
|
auto _d_aaGetRvalueX(K, V, K2)(shared const(V[K]) aa, auto ref scope K2 key)
|
|
{
|
|
// accept shared for backward compatibility, should be deprecated
|
|
return cast(const shared(V)*)_d_aaIn(cast(V[K]) aa, key);
|
|
}
|
|
|
|
/// ditto
|
|
auto _d_aaGetRvalueX(K, V, K2)(immutable(V[K]) aa, auto ref scope K2 key)
|
|
{
|
|
// resolve ambiguity for immutable converting to const and shared const
|
|
return _d_aaIn((() @trusted => cast(V[K]) aa) (), key);
|
|
}
|
|
|
|
/***********************************
|
|
* Creates a new associative array of the same size and copies the contents of
|
|
* the associative array into it.
|
|
* Params:
|
|
* a = The associative array.
|
|
*/
|
|
auto _aaDup(T : V[K], K, V)(T a)
|
|
{
|
|
auto aa = _toAA!(K, V)(a);
|
|
immutable len = aa.length;
|
|
if (len == 0)
|
|
return null;
|
|
|
|
auto impl = new Impl!(K, V)(aa.dim);
|
|
// copy the entries
|
|
bool sameHash = aa.hashFn == impl.hashFn; // can be different if coming from template/rt
|
|
foreach (b; aa.buckets[aa.firstUsed .. $])
|
|
{
|
|
if (!b.filled)
|
|
continue;
|
|
hash_t hash = sameHash ? b.hash : impl.calcHash(b.entry.key);
|
|
auto pi = impl.findSlotInsert(hash);
|
|
auto p = &impl.buckets[pi];
|
|
p.hash = hash;
|
|
p.entry = new Entry!(K, V)(b.entry.key, b.entry.value);
|
|
impl.firstUsed = min(impl.firstUsed, cast(uint)pi);
|
|
}
|
|
impl.used = cast(uint) len;
|
|
return () @trusted { return *cast(Unconstify!V[K]*)&impl; }();
|
|
}
|
|
|
|
/******************************
|
|
* Lookup key in aa.
|
|
* Called only from implementation of (key in aa) expressions.
|
|
* Params:
|
|
* a = associative array opaque pointer
|
|
* key = reference to the key value
|
|
* Returns:
|
|
* pointer to value if present, null otherwise
|
|
*/
|
|
auto _d_aaIn(T : V[K], K, V, K2)(inout T a, auto ref scope K2 key)
|
|
{
|
|
auto aa = _toAA!(K, V)(a);
|
|
if (aa.empty)
|
|
return null;
|
|
|
|
ref key2 = compat_key!(K)(key);
|
|
|
|
immutable hash = aa.calcHash(key2);
|
|
if (auto p = aa.findSlotLookup(hash, key2))
|
|
return &p.entry.value;
|
|
return null;
|
|
}
|
|
|
|
// fake purity for backward compatibility with runtime hooks
|
|
private extern(C) bool gc_inFinalizer() pure nothrow @safe;
|
|
|
|
/// Delete entry scope const AA, return true if it was present
|
|
auto _d_aaDel(T : V[K], K, V, K2)(T a, auto ref K2 key)
|
|
{
|
|
auto aa = _toAA!(K, V)(a);
|
|
if (aa.empty)
|
|
return false;
|
|
|
|
ref key2 = compat_key!(K)(key);
|
|
|
|
immutable hash = aa.calcHash(key2);
|
|
if (auto p = aa.findSlotLookup(hash, key2))
|
|
{
|
|
// clear entry
|
|
p.hash = HASH_DELETED;
|
|
p.entry = null;
|
|
|
|
++aa.deleted;
|
|
// `shrink` reallocates, and allocating from a finalizer leads to
|
|
// InvalidMemoryError: https://issues.dlang.org/show_bug.cgi?id=21442
|
|
if (aa.length * SHRINK_DEN < aa.dim * SHRINK_NUM && !__ctfe && !gc_inFinalizer())
|
|
aa.shrink();
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Remove all elements from AA.
|
|
void _aaClear(K, V)(V[K] a)
|
|
{
|
|
auto aa = _toAA!(K, V)(a);
|
|
if (!aa.empty)
|
|
{
|
|
aa.clear();
|
|
}
|
|
}
|
|
|
|
/// Rehash AA
|
|
V[K] _aaRehash(K, V)(V[K] a)
|
|
{
|
|
auto aa = _toAA!(K, V)(a);
|
|
if (!aa.empty)
|
|
aa.resize(nextpow2(INIT_DEN * aa.length / INIT_NUM));
|
|
return a;
|
|
}
|
|
|
|
/// Return a GC allocated array of all values
|
|
auto _aaValues(K, V)(inout V[K] a)
|
|
{
|
|
auto aa = _toAA!(K, V)(a);
|
|
if (aa.empty)
|
|
return null;
|
|
|
|
static if (__traits(compiles, { V val = aa.buckets[0].entry.value; } ))
|
|
V[] res; // if value has no const indirections
|
|
else
|
|
typeof([aa.buckets[0].entry.value]) res; // as mutable as it can get
|
|
res = new typeof(res[0])[aa.length];
|
|
|
|
if (false) // never execute, but infer function attributes from this operation
|
|
res ~= aa.buckets[0].entry.value;
|
|
|
|
size_t i = 0;
|
|
foreach (b; aa.buckets[aa.firstUsed .. $])
|
|
{
|
|
if (!b.filled)
|
|
continue;
|
|
import core.lifetime;
|
|
() @trusted { copyEmplace(b.entry.value, res[i++]); }();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/// Return a GC allocated array of all keys
|
|
auto _aaKeys(K, V)(inout V[K] a)
|
|
{
|
|
auto aa = _toAA!(K, V)(a);
|
|
if (aa.empty)
|
|
return null;
|
|
|
|
static if (__traits(compiles, { K key = aa.buckets[0].entry.key; } ))
|
|
K[] res; // if key has no const indirections
|
|
else
|
|
typeof([aa.buckets[0].entry.key]) res; // as mutable as it can get
|
|
res = new typeof(res[0])[aa.length];
|
|
|
|
if (false) // never execute, but infer function attributes from this operation
|
|
res ~= aa.buckets[0].entry.key;
|
|
|
|
size_t i = 0;
|
|
foreach (b; aa.buckets[aa.firstUsed .. $])
|
|
{
|
|
if (!b.filled)
|
|
continue;
|
|
// res ~= b.entry.key;
|
|
import core.lifetime;
|
|
() @trusted { copyEmplace(b.entry.key, res[i++]); }();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/// foreach opApply over all values
|
|
/// Note:
|
|
/// emulated by the compiler during CTFE
|
|
int _d_aaApply(K, V, DG)(inout V[K] a, DG dg)
|
|
{
|
|
auto aa = () @trusted { return cast(AA!(K, V))_toAA!(K, V)(a); }();
|
|
if (aa.empty)
|
|
return 0;
|
|
|
|
foreach (b; aa.buckets)
|
|
{
|
|
if (!b.filled)
|
|
continue;
|
|
if (auto res = dg(b.entry.value))
|
|
return res;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int _d_aaApply(K, V, DG)(shared V[K] a, DG dg)
|
|
{
|
|
return _d_aaApply!(K, V, DG)(cast(V[K]) a, dg);
|
|
}
|
|
|
|
int _d_aaApply(K, V, DG)(shared const V[K] a, DG dg)
|
|
{
|
|
return _d_aaApply!(K, V, DG)(cast(const V[K]) a, dg);
|
|
}
|
|
|
|
int _d_aaApply(K, V, DG)(immutable V[K] a, DG dg)
|
|
{
|
|
return _d_aaApply!(K, V, DG)(cast(const V[K]) a, dg);
|
|
}
|
|
|
|
/// foreach opApply over all key/value pairs
|
|
/// Note:
|
|
/// emulated by the compiler during CTFE
|
|
int _d_aaApply2(K, V, DG)(inout V[K] a, DG dg)
|
|
{
|
|
auto aa = () @trusted { return cast(AA!(K, V))_toAA!(K, V)(a); }();
|
|
if (aa.empty)
|
|
return 0;
|
|
|
|
foreach (b; aa.buckets)
|
|
{
|
|
if (!b.filled)
|
|
continue;
|
|
if (auto res = dg(b.entry.key, b.entry.value))
|
|
return res;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int _d_aaApply2(K, V, DG)(shared V[K] a, DG dg)
|
|
{
|
|
return _d_aaApply2!(K, V, DG)(cast(V[K]) a, dg);
|
|
}
|
|
|
|
int _d_aaApply2(K, V, DG)(shared const V[K] a, DG dg)
|
|
{
|
|
return _d_aaApply2!(K, V, DG)(cast(const V[K]) a, dg);
|
|
}
|
|
|
|
int _d_aaApply2(K, V, DG)(immutable V[K] a, DG dg)
|
|
{
|
|
return _d_aaApply2!(K, V, DG)(cast(const V[K]) a, dg);
|
|
}
|
|
|
|
/** Construct an associative array of type ti from corresponding keys and values.
|
|
* Called for an AA literal `[k1:v1, k2:v2]`.
|
|
* Params:
|
|
* keys = array of keys
|
|
* vals = array of values
|
|
* Returns:
|
|
* A new associative array opaque pointer, or null if `keys` is empty.
|
|
*/
|
|
Impl!(K, V)* _d_assocarrayliteralTX(K, V)(K[] keys, V[] vals)
|
|
{
|
|
assert(keys.length == vals.length);
|
|
|
|
immutable length = keys.length;
|
|
|
|
if (!length)
|
|
return null;
|
|
|
|
auto aa = new Impl!(K, V)(nextpow2(INIT_DEN * length / INIT_NUM));
|
|
size_t duplicates = 0;
|
|
foreach (i; 0 .. length)
|
|
{
|
|
immutable hash = aa.calcHash(keys[i]);
|
|
|
|
auto p = aa.findSlotLookup!K(hash, keys[i]);
|
|
if (p)
|
|
{
|
|
static if (__traits(compiles, p.entry.value = vals[i])) // immutable?
|
|
p.entry.value = vals[i];
|
|
else
|
|
p.entry = _newEntry!(K, V)(keys[i], vals[i]);
|
|
duplicates++;
|
|
continue;
|
|
}
|
|
auto pi = aa.findSlotInsert(hash);
|
|
p = &aa.buckets[pi];
|
|
p.hash = hash;
|
|
p.entry = _newEntry!(K, V)(keys[i], vals[i]); // todo: move key and value?
|
|
aa.firstUsed = min(aa.firstUsed, cast(uint)pi);
|
|
}
|
|
aa.used = cast(uint) (length - duplicates);
|
|
return aa;
|
|
}
|
|
|
|
/// compares 2 AAs for equality
|
|
bool _aaEqual(T : AA!(K, V), K, V)(scope T aa1, scope T aa2)
|
|
{
|
|
if (aa1 is aa2)
|
|
return true;
|
|
|
|
immutable len = aa1.length;
|
|
if (len != aa2.length)
|
|
return false;
|
|
|
|
if (!len) // both empty
|
|
return true;
|
|
|
|
bool sameHash = aa1.hashFn == aa2.hashFn; // can be different if coming from template/rt
|
|
// compare the entries
|
|
foreach (b1; aa1.buckets[aa1.firstUsed .. $])
|
|
{
|
|
if (!b1.filled)
|
|
continue;
|
|
hash_t hash = sameHash ? b1.hash : aa2.calcHash(b1.entry.key);
|
|
auto pb2 = aa2.findSlotLookup!K(hash, b1.entry.key);
|
|
if (pb2 is null || !pure_keyEqual!(V, V)(b1.entry.value, pb2.entry.value)) // rarely, inference on opEqual breaks builds here
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// compares 2 AAs for equality (compiler hook)
|
|
bool _d_aaEqual(K, V)(scope const V[K] a1, scope const V[K] a2)
|
|
{
|
|
scope aa1 = _toAA!(K, V)(a1);
|
|
scope aa2 = _toAA!(K, V)(a2);
|
|
return _aaEqual(aa1, aa2);
|
|
}
|
|
|
|
/// callback from TypeInfo_AssociativeArray.equals (ignore const for now)
|
|
bool _aaOpEqual(K, V)(scope /* const */ AA!(K, V)* aa1, scope /* const */ AA!(K, V)* aa2)
|
|
{
|
|
return _aaEqual(*aa1, *aa2);
|
|
}
|
|
|
|
/// compute a hash callback from TypeInfo_AssociativeArray.xtoHash (ignore scope const for now)
|
|
hash_t _aaGetHash(K, V)(/* scope const */ AA!(K, V)* paa)
|
|
{
|
|
const aa = *paa;
|
|
|
|
if (aa.empty)
|
|
return 0;
|
|
|
|
size_t h;
|
|
foreach (b; aa.buckets)
|
|
{
|
|
// use addition here, so that hash is independent of element order
|
|
if (b.filled)
|
|
h += hashOf(pure_hashOf!V(b.entry.value), pure_hashOf!K(b.entry.key));
|
|
}
|
|
|
|
return h;
|
|
}
|
|
|
|
/**
|
|
* _aaRange implements a ForwardRange
|
|
*/
|
|
struct AARange(K, V)
|
|
{
|
|
alias Key = substInout!K;
|
|
alias Value = substInout!V;
|
|
|
|
Impl!(Key, Value)* impl;
|
|
size_t idx;
|
|
alias impl this;
|
|
}
|
|
|
|
AARange!(K, V) _aaRange(K, V)(V[K] a)
|
|
{
|
|
auto aa = _toAA!(K, V)(a);
|
|
if (!aa)
|
|
return AARange!(K, V)();
|
|
|
|
foreach (i; aa.firstUsed .. aa.dim)
|
|
{
|
|
if (aa.buckets[i].filled)
|
|
return AARange!(K, V)(aa, i);
|
|
}
|
|
return AARange!(K, V)(aa, aa.dim);
|
|
}
|
|
|
|
bool _aaRangeEmpty(K, V)(AARange!(K, V) r)
|
|
{
|
|
return r.impl is null || r.idx >= r.dim;
|
|
}
|
|
|
|
K* _aaRangeFrontKey(K, V)(AARange!(K, V) r)
|
|
{
|
|
assert(!_aaRangeEmpty(r));
|
|
if (r.idx >= r.dim)
|
|
return null;
|
|
auto entry = r.buckets[r.idx].entry;
|
|
return entry is null ? null : &r.buckets[r.idx].entry.key;
|
|
}
|
|
|
|
V* _aaRangeFrontValue(K, V)(AARange!(K, V) r)
|
|
{
|
|
assert(!_aaRangeEmpty(r));
|
|
if (r.idx >= r.dim)
|
|
return null;
|
|
|
|
auto entry = r.buckets[r.idx].entry;
|
|
return entry is null ? null : &r.buckets[r.idx].entry.value;
|
|
}
|
|
|
|
void _aaRangePopFront(K, V)(ref AARange!(K, V) r)
|
|
{
|
|
if (r.idx >= r.dim) return;
|
|
for (++r.idx; r.idx < r.dim; ++r.idx)
|
|
{
|
|
if (r.buckets[r.idx].filled)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// test postblit for AA literals
|
|
unittest
|
|
{
|
|
import core.memory;
|
|
|
|
static struct T
|
|
{
|
|
ubyte field;
|
|
static size_t postblit, dtor;
|
|
this(this)
|
|
{
|
|
++postblit;
|
|
}
|
|
|
|
~this()
|
|
{
|
|
++dtor;
|
|
}
|
|
}
|
|
|
|
T t;
|
|
auto aa1 = [0 : t, 1 : t];
|
|
assert(T.dtor == 2 && T.postblit == 4);
|
|
aa1[0] = t;
|
|
assert(T.dtor == 3 && T.postblit == 5);
|
|
|
|
T.dtor = 0;
|
|
T.postblit = 0;
|
|
|
|
auto aa2 = [0 : t, 1 : t, 0 : t]; // literal with duplicate key => value overwritten
|
|
assert(T.dtor == 4 && T.postblit == 6);
|
|
|
|
T.dtor = 0;
|
|
T.postblit = 0;
|
|
|
|
auto aa3 = [t : 0];
|
|
assert(T.dtor == 1 && T.postblit == 2);
|
|
aa3[t] = 1;
|
|
assert(T.dtor == 1 && T.postblit == 2);
|
|
aa3.remove(t);
|
|
assert(T.dtor == 1 && T.postblit == 2);
|
|
aa3[t] = 2;
|
|
assert(T.dtor == 1 && T.postblit == 3);
|
|
|
|
// dtor will be called by GC finalizers
|
|
aa1 = null;
|
|
aa2 = null;
|
|
aa3 = null;
|
|
auto dtor1 = typeid(TypeInfo_AssociativeArray.Entry!(int, T)).xdtor;
|
|
GC.runFinalizers((cast(char*)dtor1)[0 .. 1]);
|
|
auto dtor2 = typeid(TypeInfo_AssociativeArray.Entry!(T, int)).xdtor;
|
|
GC.runFinalizers((cast(char*)dtor2)[0 .. 1]);
|
|
assert(T.dtor == 7 && T.postblit == 3);
|
|
}
|
|
|
|
// create a binary-compatible AA structure that can be used directly as an
|
|
// associative array.
|
|
// NOTE: this must only be called during CTFE
|
|
AA!(K, V) makeAA(K, V)(V[K] src) @trusted
|
|
{
|
|
assert(__ctfe, "makeAA Must only be called at compile time");
|
|
// keys and values are cheap operations in CTFE, so just reuse _d_assocarrayliteralTX
|
|
auto impl = _d_assocarrayliteralTX!(K, V)(cast(K[])src.keys, cast(V[])src.values);
|
|
auto aa = AA!(K, V)(impl);
|
|
return aa;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
static struct Foo
|
|
{
|
|
ubyte x;
|
|
double d;
|
|
}
|
|
static int[Foo] utaa = [Foo(1, 2.0) : 5];
|
|
auto k = Foo(1, 2.0);
|
|
// verify that getHash doesn't match hashOf for Foo
|
|
assert(typeid(Foo).getHash(&k) != hashOf(k));
|
|
assert(utaa[Foo(1, 2.0)] == 5);
|
|
}
|