diff --git a/changelog/dmd.aa-lowered-to-templates.dd b/changelog/dmd.aa-lowered-to-templates.dd new file mode 100644 index 000000000000..e9950dc23180 --- /dev/null +++ b/changelog/dmd.aa-lowered-to-templates.dd @@ -0,0 +1,26 @@ +The compiler now lowers associative array operations to a templated implementation in druntime + +The compiler now lowers associative array operations to templates defined in core.internal.newaa +instead of relying on a precompiled implementation based on TypeInfo. + +In addition to better performance by not having to go through TypeInfo and allowing inlining of +simple operations this paves the way for proper inference of +function attributes inherited from key and value constructors, toHash() on the key and comparison of +key values. This inference is currently mostly avoided to keep backward compatibility with +the runtime implementation that doesn't check function attributes. + +Some changes that are the result of the refactoring: + +- AA support no longer available at CTFE without object.d/newaa.d. Some operations used to work, + but now need the lowerings before these can be intercepted by the interpreter. + +- creating an AA literal at runtime passes keys and values as arguments normally, not with + special move/blit operations, so more postblit/dtor operations can happen + +- _d_assocarrayliteralTXTrace removed. Apart from the creation of associative array literals + no other AA operation is hooked for -profile-gc. You will now see the allocations as done + by the AA implementation. + +- aa[key] = S() with S.ctor and S.opAssign is no longer a double lookup + +- aa.remove(key) now works with alias this diff --git a/compiler/src/dmd/backend/drtlsym.d b/compiler/src/dmd/backend/drtlsym.d index 4e27bd6e5b26..707642daec0f 100644 --- a/compiler/src/dmd/backend/drtlsym.d +++ b/compiler/src/dmd/backend/drtlsym.d @@ -103,7 +103,6 @@ Symbol* getRtlsym(RTLSYM i) @trusted case RTLSYM.MEMSETDOUBLE: symbolz(ps,FL.func,FREGSAVED,"_memsetDouble", 0, t); break; case RTLSYM.MEMSETSIMD: symbolz(ps,FL.func,FREGSAVED,"_memsetSIMD",0, t); break; case RTLSYM.MEMSETN: symbolz(ps,FL.func,FREGSAVED,"_memsetn", 0, t); break; - case RTLSYM.ASSOCARRAYLITERALTX: symbolz(ps,FL.func,FREGSAVED,"_d_assocarrayliteralTX", 0, t); break; case RTLSYM.CALLFINALIZER: symbolz(ps,FL.func,FREGSAVED,"_d_callfinalizer", 0, t); break; case RTLSYM.CALLINTERFACEFINALIZER: symbolz(ps,FL.func,FREGSAVED,"_d_callinterfacefinalizer", 0, t); break; case RTLSYM.ALLOCMEMORY: symbolz(ps,FL.func,FREGSAVED,"_d_allocmemory", 0, t); break; @@ -113,14 +112,6 @@ Symbol* getRtlsym(RTLSYM i) @trusted case RTLSYM.ARRAYASSIGN_R: symbolz(ps,FL.func,FREGSAVED,"_d_arrayassign_r", 0, t); break; case RTLSYM.ARRAYASSIGN_L: symbolz(ps,FL.func,FREGSAVED,"_d_arrayassign_l", 0, t); break; - /* Associative Arrays https://github.com/dlang/dmd/blob/master/druntime/src/rt/aaA.d */ - case RTLSYM.AANEW: symbolz(ps,FL.func,FREGSAVED,"_aaNew", 0, t); break; - case RTLSYM.AAEQUAL: symbolz(ps,FL.func,FREGSAVED,"_aaEqual", 0, t); break; - case RTLSYM.AAINX: symbolz(ps,FL.func,FREGSAVED,"_aaInX", 0, t); break; - case RTLSYM.AADELX: symbolz(ps,FL.func,FREGSAVED,"_aaDelX", 0, t); break; - case RTLSYM.AAGETY: symbolz(ps,FL.func,FREGSAVED,"_aaGetY", 0, t); break; - case RTLSYM.AAGETRVALUEX: symbolz(ps,FL.func,FREGSAVED,"_aaGetRvalueX", 0, t); break; - case RTLSYM.EXCEPT_HANDLER3: symbolz(ps,FL.func,fregsaved,"_except_handler3", 0, tsclib); break; case RTLSYM.CPP_HANDLER: symbolz(ps,FL.func,FREGSAVED,"_cpp_framehandler", 0, tsclib); break; case RTLSYM.D_HANDLER: symbolz(ps,FL.func,FREGSAVED,"_d_framehandler", 0, tsclib); break; @@ -155,7 +146,6 @@ Symbol* getRtlsym(RTLSYM i) @trusted case RTLSYM.TRACECALLFINALIZER: symbolz(ps,FL.func,FREGSAVED,"_d_callfinalizerTrace", 0, t); break; case RTLSYM.TRACECALLINTERFACEFINALIZER: symbolz(ps,FL.func,FREGSAVED,"_d_callinterfacefinalizerTrace", 0, t); break; - case RTLSYM.TRACEASSOCARRAYLITERALTX: symbolz(ps,FL.func,FREGSAVED,"_d_assocarrayliteralTXTrace", 0, t); break; case RTLSYM.TRACEARRAYAPPENDCD: symbolz(ps,FL.func,FREGSAVED,"_d_arrayappendcdTrace", 0, t); break; case RTLSYM.TRACEARRAYAPPENDWD: symbolz(ps,FL.func,FREGSAVED,"_d_arrayappendwdTrace", 0, t); break; case RTLSYM.TRACEALLOCMEMORY: symbolz(ps,FL.func,FREGSAVED,"_d_allocmemoryTrace", 0, t); break; diff --git a/compiler/src/dmd/backend/rtlsym.d b/compiler/src/dmd/backend/rtlsym.d index cc0bbc0b7ef9..b06f1325d4f2 100644 --- a/compiler/src/dmd/backend/rtlsym.d +++ b/compiler/src/dmd/backend/rtlsym.d @@ -48,7 +48,6 @@ enum RTLSYM MEMSETSIMD, MEMSETN, - ASSOCARRAYLITERALTX, CALLFINALIZER, CALLINTERFACEFINALIZER, ALLOCMEMORY, @@ -58,12 +57,6 @@ enum RTLSYM ARRAYASSIGN_R, ARRAYASSIGN_L, ARRAYEQ2, - AANEW, - AAEQUAL, - AAINX, - AADELX, - AAGETY, - AAGETRVALUEX, EXCEPT_HANDLER3, CPP_HANDLER, @@ -99,7 +92,6 @@ enum RTLSYM TRACECALLFINALIZER, TRACECALLINTERFACEFINALIZER, - TRACEASSOCARRAYLITERALTX, TRACEARRAYAPPENDCD, TRACEARRAYAPPENDWD, TRACEALLOCMEMORY, diff --git a/compiler/src/dmd/ctfeexpr.d b/compiler/src/dmd/ctfeexpr.d index a7180066fa2d..58a00341d4e0 100644 --- a/compiler/src/dmd/ctfeexpr.d +++ b/compiler/src/dmd/ctfeexpr.d @@ -241,6 +241,7 @@ UnionExp copyLiteral(Expression e) AssocArrayLiteralExp r = ue.exp().isAssocArrayLiteralExp(); r.type = aae.type; r.lowering = aae.lowering; + r.loweringCtfe = aae.loweringCtfe; r.ownedByCtfe = OwnedBy.ctfe; return ue; } @@ -1459,7 +1460,7 @@ UnionExp ctfeCat(Loc loc, Type type, Expression e1, Expression e2) /* Given an AA literal 'ae', and a key 'e2': * Return ae[e2] if present, or NULL if not found. */ -Expression findKeyInAA(Loc loc, AssocArrayLiteralExp ae, Expression e2) +Expression findKeyInAA(Loc loc, AssocArrayLiteralExp ae, Expression e2, size_t* pidx = null) { /* Search the keys backwards, in case there are duplicate keys */ @@ -1470,6 +1471,8 @@ Expression findKeyInAA(Loc loc, AssocArrayLiteralExp ae, Expression e2) const int eq = ctfeEqual(loc, EXP.equal, ekey, e2); if (eq) { + if (pidx) + *pidx = i; return (*ae.values)[i]; } } @@ -1583,11 +1586,6 @@ Expression ctfeCast(UnionExp* pue, Loc loc, Type type, Type to, Expression e, bo */ void assignInPlace(Expression dest, Expression src) { - if (!(dest.op == EXP.structLiteral || dest.op == EXP.arrayLiteral || dest.op == EXP.string_)) - { - printf("invalid op %d %d\n", src.op, dest.op); - assert(0); - } Expressions* oldelems; Expressions* newelems; if (dest.op == EXP.structLiteral) @@ -1622,6 +1620,16 @@ void assignInPlace(Expression dest, Expression src) sliceAssignStringFromArrayLiteral(dest.isStringExp(), src.isArrayLiteralExp(), 0); return; } + else if (dest.op == EXP.int64 && src.op == EXP.int64) + { + dest.isIntegerExp().setInteger(src.isIntegerExp().getInteger()); + return; + } + else if (dest.op == EXP.float64 && src.op == EXP.float64) + { + dest.isRealExp().value = src.isRealExp().value; + return; + } else { printf("invalid op %d %d\n", src.op, dest.op); @@ -1829,6 +1837,9 @@ bool isCtfeValueValid(Expression newval) ( (e1.op == EXP.structLiteral || e1.op == EXP.arrayLiteral) && isCtfeValueValid(e1) || e1.op == EXP.variable || + e1.op == EXP.int64 || + e1.op == EXP.float64 || + e1.op == EXP.string_ || e1.op == EXP.dotVariable && isCtfeReferenceValid(e1) || e1.op == EXP.index && isCtfeReferenceValid(e1) || e1.op == EXP.slice && e1.type.toBasetype().ty == Tsarray diff --git a/compiler/src/dmd/dcast.d b/compiler/src/dmd/dcast.d index 9e35dde29697..4ac8b1f44711 100644 --- a/compiler/src/dmd/dcast.d +++ b/compiler/src/dmd/dcast.d @@ -2862,7 +2862,9 @@ Expression castTo(Expression e, Scope* sc, Type t, Type att = null) (*ae.keys)[i] = ex; } ae.type = t; - semanticTypeInfo(sc, ae.type); + ae.lowering = null; // we need a different lowering + ae.loweringCtfe = null; + ae.expressionSemantic(sc); return ae; } return visit(e); diff --git a/compiler/src/dmd/declaration.d b/compiler/src/dmd/declaration.d index 25f438b86133..782ce75314fe 100644 --- a/compiler/src/dmd/declaration.d +++ b/compiler/src/dmd/declaration.d @@ -957,7 +957,7 @@ extern (C++) class VarDeclaration : Declaration { isdataseg = 2; // The Variables does not go into the datasegment - if (!canTakeAddressOf()) + if (!canTakeAddressOf() || (storage_class & STC.exptemp)) { return false; } @@ -1395,6 +1395,8 @@ extern (C++) final class TypeInfoStaticArrayDeclaration : TypeInfoDeclaration extern (C++) final class TypeInfoAssociativeArrayDeclaration : TypeInfoDeclaration { Type entry; // type of TypeInfo_AssociativeArray.Entry!(t.index, t.next) + Dsymbol xopEqual; // implementation of TypeInfo_AssociativeArray.equals + Dsymbol xtoHash; // implementation of TypeInfo_AssociativeArray.getHash extern (D) this(Type tinfo) { diff --git a/compiler/src/dmd/declaration.h b/compiler/src/dmd/declaration.h index 2dc1f414f588..55ea7ccae94c 100644 --- a/compiler/src/dmd/declaration.h +++ b/compiler/src/dmd/declaration.h @@ -396,6 +396,8 @@ class TypeInfoAssociativeArrayDeclaration final : public TypeInfoDeclaration { public: Type* entry; + Dsymbol* xopEqual; + Dsymbol* xtoHash; static TypeInfoAssociativeArrayDeclaration *create(Type *tinfo); diff --git a/compiler/src/dmd/delegatize.d b/compiler/src/dmd/delegatize.d index 1f5dca66b598..f37c83cbca7c 100644 --- a/compiler/src/dmd/delegatize.d +++ b/compiler/src/dmd/delegatize.d @@ -180,6 +180,11 @@ private void lambdaSetParent(Expression e, FuncDeclaration fd) iz.accept(this); } } + override void visit(AssocArrayLiteralExp e) + { + if (e.lowering) + walkPostorder(e.lowering, this); + } } scope LambdaSetParent lsp = new LambdaSetParent(fd); diff --git a/compiler/src/dmd/dinterpret.d b/compiler/src/dmd/dinterpret.d index 0694670caa9a..408cd238ba63 100644 --- a/compiler/src/dmd/dinterpret.d +++ b/compiler/src/dmd/dinterpret.d @@ -50,7 +50,7 @@ import dmd.rootobject; import dmd.root.utf; import dmd.statement; import dmd.tokens; -import dmd.typesem : mutableOf, equivalent, pointerTo, sarrayOf, arrayOf, size; +import dmd.typesem : mutableOf, equivalent, pointerTo, sarrayOf, arrayOf, size, merge; import dmd.utils : arrayCastBigEndian; import dmd.visitor; @@ -2348,6 +2348,9 @@ public: if (ExpInitializer ie = v._init.isExpInitializer()) { result = interpretRegion(ie.exp, istate, goal); + if (result !is null && v.ctfeAdrOnStack != VarDeclaration.AdrOnStackNone) + if (!getValue(v)) + setValueWithoutChecking(v, result); // a temporary from extractSideEffects can be a ref return; } else if (v._init.isVoidInitializer()) @@ -2362,7 +2365,12 @@ public: { result = v._init.initializerToExpression(v.type); if (result !is null) + { + if (v.ctfeAdrOnStack != VarDeclaration.AdrOnStackNone) + if (!getValue(v)) + setValueWithoutChecking(v, result); // a temporary from extractSideEffects can be a ref return; + } } error(e.loc, "declaration `%s` is not yet implemented in CTFE", e.toChars()); result = CTFEExp.cantexp; @@ -2641,6 +2649,8 @@ public: valuesx !is e.values); auto aae = ctfeEmplaceExp!AssocArrayLiteralExp(e.loc, keysx, valuesx); aae.type = e.type; + aae.lowering = e.lowering; + aae.loweringCtfe = e.loweringCtfe; aae.ownedByCtfe = OwnedBy.ctfe; result = aae; } @@ -3406,129 +3416,10 @@ public: // --------------------------------------- // Interpret left hand side // --------------------------------------- - AssocArrayLiteralExp existingAA = null; - Expression lastIndex = null; Expression oldval = null; if (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray) { - // --------------------------------------- - // Deal with AA index assignment - // --------------------------------------- - /* This needs special treatment if the AA doesn't exist yet. - * There are two special cases: - * (1) If the AA is itself an index of another AA, we may need to create - * multiple nested AA literals before we can insert the new value. - * (2) If the ultimate AA is null, no insertion happens at all. Instead, - * we create nested AA literals, and change it into a assignment. - */ - IndexExp ie = e1.isIndexExp(); - int depth = 0; // how many nested AA indices are there? - while (ie.e1.op == EXP.index && ie.e1.isIndexExp().e1.type.toBasetype().ty == Taarray) - { - assert(ie.modifiable); - ie = ie.e1.isIndexExp(); - ++depth; - } - - // Get the AA value to be modified. - Expression aggregate = interpretRegion(ie.e1, istate); - if (exceptionOrCant(aggregate)) - return; - if ((existingAA = aggregate.isAssocArrayLiteralExp()) !is null) - { - // Normal case, ultimate parent AA already exists - // We need to walk from the deepest index up, checking that an AA literal - // already exists on each level. - lastIndex = interpretRegion(e1.isIndexExp().e2, istate); - lastIndex = resolveSlice(lastIndex); // only happens with AA assignment - if (exceptionOrCant(lastIndex)) - return; - - while (depth > 0) - { - // Walk the syntax tree to find the indexExp at this depth - IndexExp xe = e1.isIndexExp(); - foreach (d; 0 .. depth) - xe = xe.e1.isIndexExp(); - - Expression ekey = interpretRegion(xe.e2, istate); - if (exceptionOrCant(ekey)) - return; - UnionExp ekeyTmp = void; - ekey = resolveSlice(ekey, &ekeyTmp); // only happens with AA assignment - - // Look up this index in it up in the existing AA, to get the next level of AA. - AssocArrayLiteralExp newAA = cast(AssocArrayLiteralExp)findKeyInAA(e.loc, existingAA, ekey); - if (exceptionOrCant(newAA)) - return; - if (!newAA) - { - // Doesn't exist yet, create an empty AA... - auto keysx = new Expressions(); - auto valuesx = new Expressions(); - newAA = ctfeEmplaceExp!AssocArrayLiteralExp(e.loc, keysx, valuesx); - newAA.type = xe.type; - newAA.ownedByCtfe = OwnedBy.ctfe; - //... and insert it into the existing AA. - existingAA.keys.push(ekey); - existingAA.values.push(newAA); - } - existingAA = newAA; - --depth; - } - - if (fp) - { - oldval = findKeyInAA(e.loc, existingAA, lastIndex); - if (!oldval) - oldval = copyLiteral(e.e1.type.defaultInitLiteral(e.loc)).copy(); - } - } - else - { - /* The AA is currently null. 'aggregate' is actually a reference to - * whatever contains it. It could be anything: var, dotvarexp, ... - * We rewrite the assignment from: - * aa[i][j] op= newval; - * into: - * aa = [i:[j:T.init]]; - * aa[j] op= newval; - */ - oldval = copyLiteral(e.e1.type.defaultInitLiteral(e.loc)).copy(); - - Expression newaae = oldval; - while (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray) - { - Expression ekey = interpretRegion(e1.isIndexExp().e2, istate); - if (exceptionOrCant(ekey)) - return; - ekey = resolveSlice(ekey); // only happens with AA assignment - - auto keysx = new Expressions(ekey); - auto valuesx = new Expressions(newaae); - - auto aae = ctfeEmplaceExp!AssocArrayLiteralExp(e.loc, keysx, valuesx); - aae.type = e1.isIndexExp().e1.type; - aae.ownedByCtfe = OwnedBy.ctfe; - if (!existingAA) - { - existingAA = aae; - lastIndex = ekey; - } - newaae = aae; - e1 = e1.isIndexExp().e1; - } - - // We must set to aggregate with newaae - e1 = interpretRegion(e1, istate, CTFEGoal.LValue); - if (exceptionOrCant(e1)) - return; - e1 = assignToLvalue(e, e1, newaae); - if (exceptionOrCant(e1)) - return; - } - assert(existingAA && lastIndex); - e1 = null; // stomp + assert(false, "indexing AA should have been lowered in semantic analysis"); } else if (e1.op == EXP.arrayLength) { @@ -3568,10 +3459,7 @@ public: if (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray) { - IndexExp ie = e1.isIndexExp(); - assert(ie.e1.op == EXP.assocArrayLiteral); - existingAA = ie.e1.isAssocArrayLiteralExp(); - lastIndex = ie.e2; + assert(false, "indexing AA should have been lowered in semantic analysis"); } } @@ -3662,23 +3550,6 @@ public: } } - if (existingAA) - { - if (existingAA.ownedByCtfe != OwnedBy.ctfe) - { - error(e.loc, "cannot modify read-only constant `%s`", existingAA.toChars()); - result = CTFEExp.cantexp; - return; - } - - //printf("\t+L%d existingAA = %s, lastIndex = %s, oldval = %s, newval = %s\n", - // __LINE__, existingAA.toChars(), lastIndex.toChars(), oldval ? oldval.toChars() : NULL, newval.toChars()); - assignAssocArrayElement(e.loc, existingAA, lastIndex, newval); - - // Determine the return value - result = ctfeCast(pue, e.loc, e.type, e.type, fp && post ? oldval : newval); - return; - } if (e1.op == EXP.arrayLength) { /* Change the assignment from: @@ -3723,7 +3594,7 @@ public: if (newval == pue.exp()) newval = pue.copy(); - e1 = assignToLvalue(e, e1, newval); + e1 = assignToLvalue(e, e1, newval, istate); if (exceptionOrCant(e1)) return; @@ -3797,7 +3668,7 @@ public: /* Assignment to a CTFE reference. */ - if (Expression ex = assignToLvalue(e, e1, newval)) + if (Expression ex = assignToLvalue(e, e1, newval, istate)) result = ex; return; @@ -3805,7 +3676,7 @@ public: /* Set all sibling fields which overlap with v to VoidExp. */ - private void stompOverlappedFields(StructLiteralExp sle, VarDeclaration v) + private static void stompOverlappedFields(StructLiteralExp sle, VarDeclaration v) { if (!v.overlapped) return; @@ -3819,7 +3690,7 @@ public: } } - private Expression assignToLvalue(BinExp e, Expression e1, Expression newval) + private static Expression assignToLvalue(BinExp e, Expression e1, Expression newval, InterState* istate) { //printf("assignToLvalue() e: %s e1: %s newval: %s\n", e.toChars(), e1.toChars(), newval.toChars()); VarDeclaration vd = null; @@ -3913,7 +3784,8 @@ public: ArrayLiteralExp existingAE = aggregate.isArrayLiteralExp(); if (existingAE.ownedByCtfe != OwnedBy.ctfe) { - error(e.loc, "cannot modify read-only constant `%s`", existingAE.toChars()); + Expression literal = existingAE.aaLiteral ? existingAE.aaLiteral : existingAE; + error(e.loc, "cannot modify read-only constant `%s`", literal.toChars()); return CTFEExp.cantexp; } @@ -5237,16 +5109,6 @@ public: dinteger_t ofs; Expression agg = getAggregateFromPointer(e1, &ofs); - if (agg.op == EXP.null_) - { - error(e.loc, "cannot index through null pointer `%s`", e.e1.toChars()); - return false; - } - if (agg.op == EXP.int64) - { - error(e.loc, "cannot index through invalid pointer `%s` of value `%s`", e.e1.toChars(), e1.toChars()); - return false; - } // Pointer to a non-array variable if (agg.op == EXP.symbolOffset) { @@ -5265,9 +5127,15 @@ public: } else { + // agg is the value accessed, it is not dereferenced again, so offset 0 is always ok if (ofs + indx != 0) { - error(e.loc, "pointer index `[%lld]` lies outside memory block `[0..1]`", ofs + indx); + if (agg.op == EXP.null_) + error(e.loc, "cannot index through null pointer `%s`", e.e1.toChars()); + else if (agg.op == EXP.int64) + error(e.loc, "cannot index through invalid pointer `%s` of value `%s`", e.e1.toChars(), e1.toChars()); + else + error(e.loc, "pointer index `[%lld]` lies outside memory block `[0..1]`", ofs + indx); return false; } } @@ -5392,47 +5260,7 @@ public: if (e.e1.type.toBasetype().ty == Taarray) { - Expression e1 = interpretRegion(e.e1, istate); - if (exceptionOrCant(e1)) - return; - if (e1.op == EXP.null_) - { - if (goal == CTFEGoal.LValue && e1.type.ty == Taarray && e.modifiable) - { - assert(0); // does not reach here? - } - error(e.loc, "cannot index null array `%s`", e.e1.toChars()); - result = CTFEExp.cantexp; - return; - } - Expression e2 = interpretRegion(e.e2, istate); - if (exceptionOrCant(e2)) - return; - - if (goal == CTFEGoal.LValue) - { - // Pointer or reference of a scalar type - if (e1 == e.e1 && e2 == e.e2) - result = e; - else - { - emplaceExp!(IndexExp)(pue, e.loc, e1, e2); - result = pue.exp(); - result.type = e.type; - } - return; - } - - assert(e1.op == EXP.assocArrayLiteral); - UnionExp e2tmp = void; - e2 = resolveSlice(e2, &e2tmp); - result = findKeyInAA(e.loc, e1.isAssocArrayLiteralExp(), e2); - if (!result) - { - error(e.loc, "key `%s` not found in associative array `%s`", e2.toChars(), e.e1.toChars()); - result = CTFEExp.cantexp; - } - return; + assert(false, "indexing AA should have been lowered in semantic analysis"); } Expression agg; @@ -5660,50 +5488,6 @@ public: result.type = e.type; } - override void visit(InExp e) - { - debug (LOG) - { - printf("%s InExp::interpret() %s\n", e.loc.toChars(), e.toChars()); - } - Expression e1 = interpretRegion(e.e1, istate); - if (exceptionOrCant(e1)) - return; - Expression e2 = interpretRegion(e.e2, istate); - if (exceptionOrCant(e2)) - return; - if (e2.op == EXP.null_) - { - emplaceExp!(NullExp)(pue, e.loc, e.type); - result = pue.exp(); - return; - } - if (e2.op != EXP.assocArrayLiteral) - { - error(e.loc, "`%s` cannot be interpreted at compile time", e.toChars()); - result = CTFEExp.cantexp; - return; - } - - e1 = resolveSlice(e1); - result = findKeyInAA(e.loc, e2.isAssocArrayLiteralExp(), e1); - if (exceptionOrCant(result)) - return; - if (!result) - { - emplaceExp!(NullExp)(pue, e.loc, e.type); - result = pue.exp(); - } - else - { - // Create a CTFE pointer &aa[index] - result = ctfeEmplaceExp!IndexExp(e.loc, e2, e1); - result.type = e.type.nextOf(); - emplaceExp!(AddrExp)(pue, e.loc, result, e.type); - result = pue.exp(); - } - } - override void visit(CatExp e) { debug (LOG) @@ -6418,45 +6202,6 @@ public: } } - override void visit(RemoveExp e) - { - debug (LOG) - { - printf("%s RemoveExp::interpret() %s\n", e.loc.toChars(), e.toChars()); - } - Expression agg = interpret(e.e1, istate); - if (exceptionOrCant(agg)) - return; - Expression index = interpret(e.e2, istate); - if (exceptionOrCant(index)) - return; - if (agg.op == EXP.null_) - { - result = CTFEExp.voidexp; - return; - } - - AssocArrayLiteralExp aae = agg.isAssocArrayLiteralExp(); - Expressions* keysx = aae.keys; - Expressions* valuesx = aae.values; - size_t removed = 0; - foreach (j, evalue; *valuesx) - { - Expression ekey = (*keysx)[j]; - int eq = ctfeEqual(e.loc, EXP.equal, ekey, index); - if (eq) - ++removed; - else if (removed != 0) - { - (*keysx)[j - removed] = ekey; - (*valuesx)[j - removed] = evalue; - } - } - valuesx.length = valuesx.length - removed; - keysx.length = keysx.length - removed; - result = IntegerExp.createBool(removed != 0); - } - override void visit(ClassReferenceExp e) { //printf("ClassReferenceExp::interpret() %s\n", e.value.toChars()); @@ -7149,6 +6894,53 @@ private Expression interpret_values(UnionExp* pue, InterState* istate, Expressio return pue.exp(); } +// signature is bool _d_aaDel(V[K] aa, K key) +private Expression interpret_aaDel(UnionExp* pue, InterState* istate, Expression aa, Expression key) +{ + Expression agg = interpret(aa, istate); + if (exceptionOrCantInterpret(agg)) + return agg; + Expression index = interpret(key, istate); + if (exceptionOrCantInterpret(index)) + return index; + if (agg.op == EXP.null_) + return CTFEExp.voidexp; //??? + + AssocArrayLiteralExp aae = agg.isAssocArrayLiteralExp(); + Expressions* keysx = aae.keys; + Expressions* valuesx = aae.values; + size_t removed = 0; + foreach (j, evalue; *valuesx) + { + Expression ekey = (*keysx)[j]; + int eq = ctfeEqual(aa.loc, EXP.equal, ekey, index); + if (eq) + ++removed; + else if (removed != 0) + { + (*keysx)[j - removed] = ekey; + (*valuesx)[j - removed] = evalue; + } + } + valuesx.length = valuesx.length - removed; + keysx.length = keysx.length - removed; + return IntegerExp.createBool(removed != 0); +} + +// signature is bool _d_aaDel(V[K] aa1, V[K] aa2) +private Expression interpret_aaEqual(UnionExp* pue, InterState* istate, Expression aa1, Expression aa2) +{ + Expression e1 = interpret(aa1, istate); + if (exceptionOrCantInterpret(e1)) + return e1; + Expression e2 = interpret(aa2, istate); + if (exceptionOrCantInterpret(e2)) + return e2; + + bool equal = ctfeEqual(aa1.loc, EXP.equal, e1, e2); + return IntegerExp.createBool(equal); +} + private Expression interpret_dup(UnionExp* pue, InterState* istate, Expression earg) { debug (LOG) @@ -7173,11 +6965,131 @@ private Expression interpret_dup(UnionExp* pue, InterState* istate, Expression e if (Expression e = evaluatePostblit(istate, (*aae.values)[i])) return e; } - aae.type = earg.type.mutableOf(); // repaint type from const(int[int]) to const(int)[int] + // repaint type from const(int[int]) to int[int] + if (auto taa = earg.type.toBasetype().isTypeAArray()) + { + auto aatype = new TypeAArray(taa.next.mutableOf(), taa.index); + aae.type = aatype.merge(); + } + else + aae.type = earg.type.mutableOf(); //printf("result is %s\n", aae.toChars()); return aae; } +// signature is bool V* _d_aaIn(V[K] aa, K key) +private Expression interpret_aaIn(UnionExp* pue, InterState* istate, Expression aa, Expression key) +{ + debug (LOG) + { + printf("%s _d_aaIn::interpret() %s in %s\n", aa.loc.toChars(), key.toChars(), aa.toChars()); + } + Expression eaa = interpretRegion(aa, istate); + if (exceptionOrCantInterpret(eaa)) + return eaa; + Expression ekey = interpretRegion(key, istate); + if (exceptionOrCantInterpret(ekey)) + return ekey; + + if (eaa.op != EXP.null_) + { + auto aalit = eaa.isAssocArrayLiteralExp(); + if (!aalit) + { + error(aa.loc, "`%s` cannot be interpreted at compile time", aa.toChars()); + return CTFEExp.cantexp; + } + + size_t idx; + auto result = findKeyInAA(aa.loc, aalit, ekey, &idx); + if (exceptionOrCantInterpret(result)) + return result; + if (result) + return pointerToAAValue(pue, aa, aalit, idx); + } + emplaceExp!(NullExp)(pue, aa.loc, aa.type.nextOf().pointerTo()); + return pue.exp(); +} + +// signature is V* _d_aaGetRvalueX(V[K] aa, K key) +private Expression interpret_aaGetRvalueX(UnionExp* pue, InterState* istate, Expression aa, Expression key) +{ + Expression e1 = interpret(aa, istate); + if (exceptionOrCantInterpret(e1)) + return e1; + Expression e2 = interpretRegion(key, istate); + if (exceptionOrCantInterpret(e2)) + return e2; + + auto aalit = e1.isAssocArrayLiteralExp(); + if (!aalit) + { + error(aa.loc, "cannot index null array `%s`", aa.toChars()); + return CTFEExp.cantexp; + } + size_t idx; + Expression result = findKeyInAA(aa.loc, aalit, e2, &idx); + if (!result) + { + error(aa.loc, "key `%s` not found in associative array `%s`", key.toChars(), aa.toChars()); + return CTFEExp.cantexp; + } + + return pointerToAAValue(pue, aa, aalit, idx); +} + +// signature is V* _d_aaGetY(ref V[K] aa, K key, out bool found) +private Expression interpret_aaGetY(UnionExp* pue, InterState* istate, Expression aa, Expression key, Expression found) +{ + Expression eaa = interpretRegion(aa, istate, CTFEGoal.LValue); + if (exceptionOrCantInterpret(eaa)) + return eaa; + Expression ekey = interpretRegion(key, istate); + if (exceptionOrCantInterpret(ekey)) + return ekey; + Expression efound = interpretRegion(found, istate, CTFEGoal.LValue); + if (exceptionOrCantInterpret(efound)) + return efound; + + auto ie = ctfeEmplaceExp!IndexExp(aa.loc, aa, key); // any BinExp for location in assignToLvalue + Expression evalaa = interpretRegion(eaa, istate); + auto aalit = evalaa.isAssocArrayLiteralExp(); + if (!aalit) + { + auto keysx = new Expressions(); + auto valuesx = new Expressions(); + aalit = ctfeEmplaceExp!AssocArrayLiteralExp(aa.loc, keysx, valuesx); + aalit.type = aa.type; + aalit.ownedByCtfe = OwnedBy.ctfe; + Interpreter.assignToLvalue(ie, eaa, aalit, istate); + } + size_t idx; + auto result = findKeyInAA(aa.loc, aalit, ekey, &idx); + if (found) + Interpreter.assignToLvalue(ie, efound, IntegerExp.createBool(result !is null), istate); + if (!result) + { + aalit.keys.push(ekey); + result = copyLiteral(aa.type.nextOf().defaultInitLiteral(aa.loc)).copy(); + idx = aalit.values.length; + aalit.values.push(result); + } + return pointerToAAValue(pue, aa, aalit, idx); +} + +private Expression pointerToAAValue(UnionExp* pue, Expression aa, AssocArrayLiteralExp aalit, size_t idx) +{ + auto arr = ctfeEmplaceExp!(ArrayLiteralExp)(aa.loc, aa.type.nextOf().arrayOf(), aalit.values); + arr.ownedByCtfe = aalit.ownedByCtfe; + arr.aaLiteral = aalit; + auto len = ctfeEmplaceExp!(IntegerExp)(aa.loc, idx, Type.tsize_t); + auto idxexp = ctfeEmplaceExp!(IndexExp)(aa.loc, arr, len); + idxexp.type = arr.type.nextOf(); + emplaceExp!(AddrExp)(pue, aa.loc, idxexp); + pue.exp().type = idxexp.type.pointerTo(); + return pue.exp(); +} + // signature is int delegate(ref Value) OR int delegate(ref Key, ref Value) private Expression interpret_aaApply(UnionExp* pue, InterState* istate, Expression aa, Expression deleg) { @@ -7222,7 +7134,10 @@ private Expression interpret_aaApply(UnionExp* pue, InterState* istate, Expressi if (wantRefValue) { Type t = evalue.type; - evalue = ctfeEmplaceExp!IndexExp(deleg.loc, ae, ekey); + auto arr = ctfeEmplaceExp!(ArrayLiteralExp)(aa.loc, t.arrayOf(), ae.values); + arr.ownedByCtfe = ae.ownedByCtfe; + auto idx = ctfeEmplaceExp!(IntegerExp)(aa.loc, i, Type.tsize_t); + evalue = ctfeEmplaceExp!(IndexExp)(aa.loc, arr, idx); evalue.type = t; } args[numParams - 1] = evalue; @@ -7480,7 +7395,7 @@ private Expression evaluateIfBuiltin(UnionExp* pue, InterState* istate, Loc loc, } if (!pthis) { - if (nargs == 1 || nargs == 3) + if (nargs >= 1 && nargs <= 3) { Expression firstarg = (*arguments)[0]; if (auto firstAAtype = firstarg.type.toBasetype().isTypeAArray()) @@ -7488,7 +7403,7 @@ private Expression evaluateIfBuiltin(UnionExp* pue, InterState* istate, Loc loc, const id = fd.ident; if (nargs == 1) { - if (id == Id.aaLen) + if (id == Id._d_aaLen) return interpret_length(pue, istate, firstarg); if (fd.toParent2().ident == Id.object) @@ -7503,12 +7418,25 @@ private Expression evaluateIfBuiltin(UnionExp* pue, InterState* istate, Loc loc, return interpret_dup(pue, istate, firstarg); } } + else if (nargs == 2) + { + if (id == Id._d_aaGetRvalueX) + return interpret_aaGetRvalueX(pue, istate, firstarg, (*arguments)[1]); + if (id == Id._d_aaIn) + return interpret_aaIn(pue, istate, firstarg, (*arguments)[1]); + if (id == Id._d_aaDel) + return interpret_aaDel(pue, istate, firstarg, (*arguments)[1]); + if (id == Id._d_aaEqual) + return interpret_aaEqual(pue, istate, firstarg, (*arguments)[1]); + if (id == Id._d_aaApply) + return interpret_aaApply(pue, istate, firstarg, (*arguments)[1]); + if (id == Id._d_aaApply2) + return interpret_aaApply(pue, istate, firstarg, (*arguments)[1]); + } else // (nargs == 3) { - if (id == Id._aaApply) - return interpret_aaApply(pue, istate, firstarg, (*arguments)[2]); - if (id == Id._aaApply2) - return interpret_aaApply(pue, istate, firstarg, (*arguments)[2]); + if (id == Id._d_aaGetY) + return interpret_aaGetY(pue, istate, firstarg, (*arguments)[1], (*arguments)[2]); } } } diff --git a/compiler/src/dmd/dsymbolsem.d b/compiler/src/dmd/dsymbolsem.d index 11f6411f5b5d..d55d43933508 100644 --- a/compiler/src/dmd/dsymbolsem.d +++ b/compiler/src/dmd/dsymbolsem.d @@ -6918,7 +6918,7 @@ private extern(C++) class SearchVisitor : Visitor { //printf("ArrayScopeSymbol::search('%s', flags = %d)\n", ident.toChars(), flags); if (ident != Id.dollar) - return setResult(null); + return visit(cast(ScopeDsymbol)ass); VarDeclaration* pvar; Expression ce; diff --git a/compiler/src/dmd/e2ir.d b/compiler/src/dmd/e2ir.d index a0874e8619ce..7dd6e4042ca1 100644 --- a/compiler/src/dmd/e2ir.d +++ b/compiler/src/dmd/e2ir.d @@ -350,8 +350,6 @@ void toTraceGC(ref IRState irs, elem* e, Loc loc) [ RTLSYM.CALLFINALIZER, RTLSYM.TRACECALLFINALIZER ], [ RTLSYM.CALLINTERFACEFINALIZER, RTLSYM.TRACECALLINTERFACEFINALIZER ], - [ RTLSYM.ASSOCARRAYLITERALTX, RTLSYM.TRACEASSOCARRAYLITERALTX ], - [ RTLSYM.ARRAYAPPENDCD, RTLSYM.TRACEARRAYAPPENDCD ], [ RTLSYM.ARRAYAPPENDWD, RTLSYM.TRACEARRAYAPPENDWD ], @@ -1457,13 +1455,8 @@ elem* toElem(Expression e, ref IRState irs) } else if (auto taa = t.isTypeAArray()) { - Symbol* s = getRtlsym(RTLSYM.AANEW); - elem* ti = getTypeInfo(ne, t, irs); - // aaNew(ti) - elem* ep = el_params(ti, null); - e = el_bin(OPcall, TYnptr, el_var(s), ep); - elem_setLoc(e, ne.loc); - return e; + assert(ne.lowering, "This case should have been rewritten to `_d_aaNew` in the semantic phase"); + return toElem(ne.lowering, irs); } else { @@ -2169,18 +2162,7 @@ elem* toElem(Expression e, ref IRState irs) } else if (t1.ty == Taarray && t2.ty == Taarray) { - TypeAArray taa = cast(TypeAArray)t1; - Symbol* s = getRtlsym(RTLSYM.AAEQUAL); - elem* ti = getTypeInfo(ee, taa, irs); - elem* ea1 = toElem(ee.e1, irs); - elem* ea2 = toElem(ee.e2, irs); - // aaEqual(ti, e1, e2) - elem* ep = el_params(ea2, ea1, ti, null); - e = el_bin(OPcall, TYint, el_var(s), ep); - if (ee.op == EXP.notEqual) - e = el_bin(OPxor, TYint, e, el_long(TYint, 1)); - elem_setLoc(e, ee.loc); - return e; + assert(false, "This case should have been rewritten to `_d_aaEqual` in the semantic phase"); } else if (eop == OPne && t1.ty == Tvector) { @@ -2270,19 +2252,7 @@ elem* toElem(Expression e, ref IRState irs) elem* visitIn(InExp ie) { - elem* key = toElem(ie.e1, irs); - elem* aa = toElem(ie.e2, irs); - TypeAArray taa = cast(TypeAArray)ie.e2.type.toBasetype(); - - // aaInX(aa, keyti, key); - key = addressElem(key, ie.e1.type); - Symbol* s = getRtlsym(RTLSYM.AAINX); - elem* keyti = getTypeInfo(ie, taa.index, irs); - elem* ep = el_params(key, keyti, aa, null); - elem* e = el_bin(OPcall, totym(ie.type), el_var(s), ep); - - elem_setLoc(e, ie.loc); - return e; + assert(false, "This case should have been rewritten to `_d_aaIn` in the semantic phase"); } /*************************************** @@ -2290,19 +2260,7 @@ elem* toElem(Expression e, ref IRState irs) elem* visitRemove(RemoveExp re) { - auto taa = re.e1.type.toBasetype().isTypeAArray(); - assert(taa); - elem* ea = toElem(re.e1, irs); - elem* ekey = toElem(re.e2, irs); - - ekey = addressElem(ekey, re.e2.type); - Symbol* s = getRtlsym(RTLSYM.AADELX); - elem* keyti = getTypeInfo(re, taa.index, irs); - elem* ep = el_params(ekey, keyti, ea, null); - elem* e = el_bin(OPcall, TYbool, el_var(s), ep); - - elem_setLoc(e, re.loc); - return e; + assert(false, "This case should have been rewritten to `_d_aaDel` in the semantic phase"); } /*************************************** @@ -4012,55 +3970,7 @@ elem* toElem(Expression e, ref IRState irs) Type t1 = ie.e1.type.toBasetype(); if (auto taa = t1.isTypeAArray()) { - // set to: - // *aaGetY(aa, aati, valuesize, &key); - // or - // *aaGetRvalueX(aa, keyti, valuesize, &key); - - uint vsize = cast(uint)taa.next.size(); - - // n2 becomes the index, also known as the key - elem* n2 = toElem(ie.e2, irs); - - /* Turn n2 into a pointer to the index. If it's an lvalue, - * take the address of it. If not, copy it to a temp and - * take the address of that. - */ - n2 = addressElem(n2, taa.index); - - elem* valuesize = el_long(TYsize_t, vsize); - //printf("valuesize: "); elem_print(valuesize); - Symbol* s; - elem* ti; - if (ie.modifiable) - { - n1 = el_una(OPaddr, TYnptr, n1); - s = getRtlsym(RTLSYM.AAGETY); - ti = getTypeInfo(ie.e1, taa.unSharedOf().mutableOf(), irs); - } - else - { - s = getRtlsym(RTLSYM.AAGETRVALUEX); - ti = getTypeInfo(ie.e1, taa.index, irs); - } - //printf("taa.index = %s\n", taa.index.toChars()); - //printf("ti:\n"); elem_print(ti); - elem* ep = el_params(n2, valuesize, ti, n1, null); - e = el_bin(OPcall, TYnptr, el_var(s), ep); - if (irs.arrayBoundsCheck()) - { - elem* n = el_same(e); - - // Construct: ((e || arrayBoundsError), n) - auto ea = buildRangeError(irs, ie.loc); - e = el_bin(OPoror,TYvoid,e,ea); - e = el_bin(OPcomma, TYnptr, e, n); - } - e = el_una(OPind, totym(ie.type), e); - if (tybasic(e.Ety) == TYstruct) - e.ET = Type_toCtype(ie.type); - elem_setLoc(e, ie.loc); - return e; + assert(false, "no index lowering for associative array literal"); } elem* einit = resolveLengthVar(ie.lengthVar, &n1, t1); @@ -4208,51 +4118,10 @@ elem* toElem(Expression e, ref IRState irs) elem* visitAssocArrayLiteral(AssocArrayLiteralExp aale) { //printf("AssocArrayLiteralExp.toElem() %s\n", aale.toChars()); + if (aale.lowering) + return toElem(aale.lowering, irs); - Type t = aale.type.toBasetype().mutableOf(); - - size_t dim = aale.keys.length; - if (!dim) - { - elem* e = el_long(TYnptr, 0); // empty associative array is the null pointer - if (t.ty != Taarray) - e = addressElem(e, Type.tvoidptr); - return e; - } - - // call _d_assocarrayliteralTX(TypeInfo_AssociativeArray ti, void[] keys, void[] values) - // Prefer this to avoid the varargs fiasco in 64 bit code - - assert(t.ty == Taarray); - Type ta = t; - - Symbol* skeys = null; - elem* ekeys = ExpressionsToStaticArray(irs, aale.loc, aale.keys, &skeys); - - Symbol* svalues = null; - elem* evalues = ExpressionsToStaticArray(irs, aale.loc, aale.values, &svalues); - - elem* ev = el_pair(TYdarray, el_long(TYsize_t, dim), el_ptr(svalues)); - elem* ek = el_pair(TYdarray, el_long(TYsize_t, dim), el_ptr(skeys )); - if (irs.target.os == Target.OS.Windows && irs.target.isX86_64) - { - ev = addressElem(ev, Type.tvoid.arrayOf()); - ek = addressElem(ek, Type.tvoid.arrayOf()); - } - elem* e = el_params(ev, ek, - getTypeInfo(aale, ta, irs), - null); - - // call _d_assocarrayliteralTX(ti, keys, values) - e = el_bin(OPcall,TYnptr,el_var(getRtlsym(RTLSYM.ASSOCARRAYLITERALTX)),e); - toTraceGC(irs, e, aale.loc); - if (t != ta) - e = addressElem(e, ta); - elem_setLoc(e, aale.loc); - - e = el_combine(evalues, e); - e = el_combine(ekeys, e); - return e; + assert(false, "no lowering for associative array literal"); } elem* visitStructLiteral(StructLiteralExp sle) @@ -6235,8 +6104,11 @@ elem* sarray_toDarray(Loc loc, Type tfrom, Type tto, elem* e) uint tsize = cast(uint)tto.nextOf().size(); // Should have been caught by Expression::castTo - assert(tsize != 0 && (dim * fsize) % tsize == 0); - dim = (dim * fsize) / tsize; + if (tsize != fsize) // allow both 0 + { + assert(tsize != 0 && (dim * fsize) % tsize == 0); + dim = (dim * fsize) / tsize; + } } elem* elen = el_long(TYsize_t, dim); e = addressElem(e, tfrom); diff --git a/compiler/src/dmd/enumsem.d b/compiler/src/dmd/enumsem.d index 2bae01220cb2..cdf8a61bd88a 100644 --- a/compiler/src/dmd/enumsem.d +++ b/compiler/src/dmd/enumsem.d @@ -240,7 +240,9 @@ void enumSemantic(Scope* sc, EnumDeclaration ed) em.dsymbolSemantic(em._scope); }); - if (!ed.errors && global.params.useTypeInfo && Type.dtypeinfo && !ed.inNonRoot()) + if (ed.errors) + ed.memtype = Type.terror; // avoid infinite recursion in toBaseType + else if (global.params.useTypeInfo && Type.dtypeinfo && !ed.inNonRoot()) semanticTypeInfo(sc, ed.memtype); //printf("ed.defaultval = %lld\n", ed.defaultval); diff --git a/compiler/src/dmd/expression.d b/compiler/src/dmd/expression.d index 077de2017b18..b900bf3a777c 100644 --- a/compiler/src/dmd/expression.d +++ b/compiler/src/dmd/expression.d @@ -1894,6 +1894,10 @@ extern (C++) final class ArrayLiteralExp : Expression Expression lowering; + // aaLiteral is set if this is an array of values of an AA literal + // only used during CTFE to show the original AA in error messages instead + AssocArrayLiteralExp aaLiteral; + extern (D) this(Loc loc, Type type, Expressions* elements) @safe { super(loc, EXP.arrayLiteral); @@ -2046,8 +2050,9 @@ extern (C++) final class AssocArrayLiteralExp : Expression Expressions* keys; Expressions* values; - /// Lower to core.internal.newaa for static initializaton - Expression lowering; + + Expression lowering; // call to _d_assocarrayliteralTX() + Expression loweringCtfe; // result of interpreting lowering for static initializaton extern (D) this(Loc loc, Expressions* keys, Expressions* values) @safe { @@ -3249,6 +3254,7 @@ extern (C++) final class CallExp : UnaExp bool ignoreAttributes; /// don't enforce attributes (e.g. call @gc function in @nogc code) bool isUfcsRewrite; /// the first argument was pushed in here by a UFCS rewrite VarDeclaration vthis2; // container for multi-context + Expression loweredFrom; // set if this is the result of a lowering /// Puts the `arguments` and `names` into an `ArgumentList` for easily passing them around. /// The fields are still separate for backwards compatibility @@ -3511,6 +3517,8 @@ extern (C++) final class ComExp : UnaExp */ extern (C++) final class NotExp : UnaExp { + Expression loweredFrom; // for lowering of `aa1 != aa2` to `!_d_aaEqual(aa1, aa2)` + extern (D) this(Loc loc, Expression e) @safe { super(loc, EXP.not, e); @@ -3735,6 +3743,7 @@ extern (C++) final class ArrayExp : UnaExp size_t currentDimension; // for opDollar VarDeclaration lengthVar; + bool modifiable = false; // is this expected to be an lvalue in an AssignExp? propagate to IndexExp extern (D) this(Loc loc, Expression e1, Expression index = null) { @@ -3932,6 +3941,7 @@ extern (C++) final class DelegateFuncptrExp : UnaExp extern (C++) final class IndexExp : BinExp { VarDeclaration lengthVar; + Expression loweredFrom; // for associative array lowering to _d_aaGetY or _d_aaGetRvalueX bool modifiable = false; // assume it is an rvalue bool indexIsInBounds; // true if 0 <= e2 && e2 <= e1.length - 1 @@ -4698,7 +4708,6 @@ extern (C++) final class RemoveExp : BinExp extern (D) this(Loc loc, Expression e1, Expression e2) { super(loc, EXP.remove, e1, e2); - type = Type.tbool; } override void accept(Visitor v) diff --git a/compiler/src/dmd/expression.h b/compiler/src/dmd/expression.h index 8fffbe484a9e..1bb00d66d567 100644 --- a/compiler/src/dmd/expression.h +++ b/compiler/src/dmd/expression.h @@ -408,6 +408,7 @@ class ArrayLiteralExp final : public Expression Expression *basis; Expressions *elements; Expression *lowering; + AssocArrayLiteralExp* aaLiteral; // set if this is an array of keys/values of an AA literal static ArrayLiteralExp *create(Loc loc, Expressions *elements); ArrayLiteralExp *syntaxCopy() override; @@ -426,6 +427,7 @@ class AssocArrayLiteralExp final : public Expression Expressions *keys; Expressions *values; Expression* lowering; + Expression* loweringCtfe; bool equals(const RootObject * const o) const override; AssocArrayLiteralExp *syntaxCopy() override; @@ -834,6 +836,7 @@ class CallExp final : public UnaExp d_bool ignoreAttributes; // don't enforce attributes (e.g. call @gc function in @nogc code) d_bool isUfcsRewrite; // the first argument was pushed in here by a UFCS rewrite VarDeclaration *vthis2; // container for multi-context + Expression* loweredFrom; // set if this is the result of a lowering static CallExp *create(Loc loc, Expression *e, Expressions *exps); static CallExp *create(Loc loc, Expression *e); @@ -881,6 +884,7 @@ class ComExp final : public UnaExp class NotExp final : public UnaExp { public: + Expression* loweredFrom; // for lowering of `aa1 != aa2` to `!_d_aaEqual(aa1, aa2)` void accept(Visitor *v) override { v->visit(this); } }; @@ -988,6 +992,7 @@ class ArrayExp final : public UnaExp Expressions *arguments; // Array of Expression's size_t currentDimension; // for opDollar VarDeclaration *lengthVar; + d_bool modifiable; ArrayExp *syntaxCopy() override; bool isLvalue() override; @@ -1018,6 +1023,7 @@ class IndexExp final : public BinExp { public: VarDeclaration *lengthVar; + Expression* loweredFrom; // for associative array lowering to _d_aaGetY or _d_aaGetRvalueX d_bool modifiable; d_bool indexIsInBounds; // true if 0 <= e2 && e2 <= e1.length - 1 diff --git a/compiler/src/dmd/expressionsem.d b/compiler/src/dmd/expressionsem.d index 8e05fa8588ce..88182fdff9af 100644 --- a/compiler/src/dmd/expressionsem.d +++ b/compiler/src/dmd/expressionsem.d @@ -411,54 +411,6 @@ extern (D) Expression incompatibleTypes(BinExp e, Scope* sc = null) return ErrorExp.get(); } -private Expression reorderSettingAAElem(BinExp exp, Scope* sc) -{ - BinExp be = exp; - - auto ie = be.e1.isIndexExp(); - if (!ie) - return be; - if (ie.e1.type.toBasetype().ty != Taarray) - return be; - - /* Fix evaluation order of setting AA element - * https://issues.dlang.org/show_bug.cgi?id=3825 - * Rewrite: - * aa[k1][k2][k3] op= val; - * as: - * auto ref __aatmp = aa; - * auto ref __aakey3 = k1, __aakey2 = k2, __aakey1 = k3; - * auto ref __aaval = val; - * __aatmp[__aakey3][__aakey2][__aakey1] op= __aaval; // assignment - */ - - Expression e0; - while (1) - { - Expression de; - ie.e2 = extractSideEffect(sc, "__aakey", de, ie.e2); - e0 = Expression.combine(de, e0); - - auto ie1 = ie.e1.isIndexExp(); - if (!ie1 || - ie1.e1.type.toBasetype().ty != Taarray) - { - break; - } - ie = ie1; - } - assert(ie.e1.type.toBasetype().ty == Taarray); - - Expression de; - ie.e1 = extractSideEffect(sc, "__aatmp", de, ie.e1); - e0 = Expression.combine(de, e0); - - be.e2 = extractSideEffect(sc, "__aaval", e0, be.e2, true); - - //printf("-e0 = %s, be = %s\n", e0.toChars(), be.toChars()); - return Expression.combine(e0, be); -} - private Expression checkOpAssignTypes(BinExp binExp, Scope* sc) { auto e1 = binExp.e1; @@ -1310,7 +1262,8 @@ private Expression resolveUFCS(Scope* sc, CallExp ce) semanticTypeInfo(sc, taa.index); - return new RemoveExp(loc, eleft, key); + e = new RemoveExp(loc, eleft, key); + return e.expressionSemantic(sc); } } else @@ -2415,7 +2368,9 @@ private bool checkNogc(FuncDeclaration f, ref Loc loc, Scope* sc) // so don't print anything to avoid double error messages. if (!(f.ident == Id._d_HookTraceImpl || f.ident == Id._d_arraysetlengthT || f.ident == Id._d_arrayappendT || f.ident == Id._d_arrayappendcTX - || f.ident == Id._d_arraycatnTX || f.ident == Id._d_newclassT)) + || f.ident == Id._d_arraycatnTX || f.ident == Id._d_newclassT + || f.ident == Id._d_assocarrayliteralTX || f.ident == Id._d_arrayliteralTX + || f.ident == Id._d_aaGetY)) { error(loc, "`@nogc` %s `%s` cannot call non-@nogc %s `%s`", sc.func.kind(), sc.func.toPrettyChars(), f.kind(), f.toPrettyChars()); @@ -4078,28 +4033,26 @@ private bool checkNestedFuncReference(FuncDeclaration fd, Scope* sc, Loc loc) return false; } -private Expression markSettingAAElem(IndexExp ie) +Expression lowerArrayLiteral(ArrayLiteralExp ale, Scope* sc) { - if (ie.e1.type.toBasetype().ty != Taarray) - return ie; + const dim = ale.elements ? ale.elements.length : 0; - Type t2b = ie.e2.type.toBasetype(); - if (t2b.ty == Tarray && t2b.nextOf().isMutable()) - { - error(ie.loc, "associative arrays can only be assigned values with immutable keys, not `%s`", ie.e2.type.toChars()); - return ErrorExp.get(); - } - ie.modifiable = true; + Identifier hook = global.params.tracegc ? Id._d_arrayliteralTXTrace : Id._d_arrayliteralTX; + if (!verifyHookExist(ale.loc, *sc, hook, "creating array literals")) + return null; - if (auto ie2 = ie.e1.isIndexExp()) - { - Expression ex = ie2.markSettingAAElem(); - if (ex.op == EXP.error) - return ex; - assert(ex == ie.e1); - } + Expression lowering = new IdentifierExp(ale.loc, Id.empty); + lowering = new DotIdExp(ale.loc, lowering, Id.object); + // Remove `inout`, `const`, `immutable` and `shared` to reduce template instances + auto t = ale.type.nextOf().unqualify(MODFlags.wild | MODFlags.const_ | MODFlags.immutable_ | MODFlags.shared_); + auto tiargs = new Objects(t); + lowering = new DotTemplateInstanceExp(ale.loc, lowering, hook, tiargs); - return ie; + auto arguments = new Expressions(new IntegerExp(dim)); + lowering = new CallExp(ale.loc, lowering, arguments); + ale.lowering = lowering.expressionSemantic(sc); + + return ale.lowering; } private extern (C++) final class ExpressionSemanticVisitor : Visitor @@ -4854,6 +4807,34 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor result = e; } + void tryLowerAALiteral(AssocArrayLiteralExp aaExp) + { + auto hookId = Id._d_assocarrayliteralTX; + if (!verifyHookExist(aaExp.loc, *sc, hookId, "initializing associative arrays", Id.object)) + return; + + auto aaType = aaExp.type.toBasetype().isTypeAArray(); + assert(aaType); + Expression hookFunc = new IdentifierExp(aaExp.loc, Id.empty); + hookFunc = new DotIdExp(aaExp.loc, hookFunc, Id.object); + auto keytype = aaType.index.substWildTo(MODFlags.const_); + auto valtype = aaType.nextOf().substWildTo(MODFlags.const_); + auto tiargs = new Objects(keytype, valtype); + hookFunc = new DotTemplateInstanceExp(aaExp.loc, hookFunc, hookId, tiargs); + auto ale1 = new ArrayLiteralExp(aaExp.loc, keytype.arrayOf(), aaExp.keys); + auto ale2 = new ArrayLiteralExp(aaExp.loc, valtype.arrayOf(), aaExp.values); + lowerArrayLiteral(ale1, sc); + lowerArrayLiteral(ale2, sc); + auto arguments = new Expressions(ale1, ale2); + + Expression loweredExp = new CallExp(aaExp.loc, hookFunc, arguments); + loweredExp = loweredExp.expressionSemantic(sc); + loweredExp = resolveProperties(sc, loweredExp); + aaExp.lowering = loweredExp; + + semanticTypeInfo(sc, loweredExp.type); + } + override void visit(AssocArrayLiteralExp e) { static if (LOGSEMANTIC) @@ -4864,6 +4845,8 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor { // already done, but we might have missed generating type info semanticTypeInfo(sc, e.type); + if (!e.lowering) + tryLowerAALiteral(e); result = e; return; } @@ -4890,6 +4873,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor e.type = new TypeAArray(tvalue, tkey); e.type = e.type.typeSemantic(e.loc, sc); + tryLowerAALiteral(e); semanticTypeInfo(sc, e.type); if (checkAssocArrayLiteralEscape(*sc, e, false)) @@ -5178,6 +5162,38 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor ne.lowering = id.expressionSemantic(sc); } + /** + * Sets the `lowering` field of a `NewExp` to a call to `_d_newAA` unless + * compiling with `-betterC` or within `__traits(compiles)`. + * + * Params: + * ne = the `NewExp` to lower + */ + private void tryLowerToNewAA(NewExp ne) + { + if (!global.params.useGC || !sc.needsCodegen()) + return; + + Identifier hook = Id._d_aaNew; + if (!verifyHookExist(ne.loc, *sc, hook, "new AA")) + return; + + /* Lower the memory allocation and initialization of `new V[K]` to + * `_d_newAA!(V[K])()`. + */ + Expression id = new IdentifierExp(ne.loc, Id.empty); + id = new DotIdExp(ne.loc, id, Id.object); + auto taa = ne.type.isTypeAArray(); + assert(taa); + auto tiargs = new Objects(taa.index, taa.next); + id = new DotTemplateInstanceExp(ne.loc, id, hook, tiargs); + + auto arguments = new Expressions(); + id = new CallExp(ne.loc, id, arguments); + + ne.lowering = id.expressionSemantic(sc); + } + override void visit(NewExp exp) { static if (LOGSEMANTIC) @@ -5816,6 +5832,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor error(exp.loc, "`new` cannot take arguments for an associative array"); return setError(); } + tryLowerToNewAA(exp); } else { @@ -8022,7 +8039,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } assert(e.op == EXP.assign || e == exp); - result = (cast(BinExp)e).reorderSettingAAElem(sc); + result = e; } private Expression compileIt(MixinExp exp, Scope* sc) @@ -8239,7 +8256,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor // Defensively assume that function calls may have side effects even // though it's not detected by hasSideEffect (e.g. `debug puts("Hello")` ) // Rewriting CallExp's also avoids some issues with the inliner/debug generation - if (op.hasSideEffect(true)) + if (op.hasSideEffect(true) || op.isAssocArrayLiteralExp()) { // Don't create an invalid temporary for void-expressions // Further semantic will issue an appropriate error @@ -10376,9 +10393,16 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } semanticTypeInfo(sc, taa); - checkNewEscape(*sc, exp.e2, false); + bool escape = checkNewEscape(*sc, exp.e2, false); + if (escape) + return setError(); exp.type = taa.next; + if (!exp.modifiable) + { + result = lowerAAIndexRead(exp, sc); + return; + } break; } case Ttuple: @@ -10484,6 +10508,9 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } } + if (auto ae = exp.e1.isArrayExp()) + markArrayExpModifiable(ae); + if (Expression ex = binSemantic(exp, sc)) { result = ex; @@ -10497,6 +10524,12 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } exp.e1 = e1x; + Type[2] aliasThisStop; + if (auto res = rewriteIndexAssign(exp, sc, aliasThisStop)) + { + result = res; + return; + } if (exp.e1.checkReadModifyWrite(exp.op)) return setError(); @@ -10566,6 +10599,9 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor override void visit(PreExp exp) { + if (auto ae = exp.e1.isArrayExp()) + markArrayExpModifiable(ae); + // printf("PreExp::semantic('%s')\n", toChars()); if (Expression e = exp.opOverloadUnary(sc)) { @@ -10656,6 +10692,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor if (auto ae = exp.e1.isArrayExp()) { Expression res; + markArrayExpModifiable(ae); ae.e1 = ae.e1.expressionSemantic(sc); ae.e1 = resolveProperties(sc, ae.e1); @@ -10996,18 +11033,6 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor auto t = exp.type; exp = new ConstructExp(exp.loc, exp.e1, exp.e2); exp.type = t; - - // https://issues.dlang.org/show_bug.cgi?id=13515 - // set Index::modifiable flag for complex AA element initialization - if (auto ie1 = exp.e1.isIndexExp()) - { - Expression e1x = ie1.markSettingAAElem(); - if (e1x.op == EXP.error) - { - result = e1x; - return; - } - } } else if (exp.op == EXP.construct && exp.e1.op == EXP.variable && (cast(VarExp)exp.e1).var.storage_class & (STC.out_ | STC.ref_)) @@ -11023,6 +11048,12 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor checkUnsafeAccess(sc, exp.e2, true, true); // Initializer must always be checked + if (auto res = rewriteIndexAssign(exp, sc, aliasThisStop)) + { + result = res; + return; + } + /* If it is an assignment from a 'foreign' type, * check for operator overloading. */ @@ -11312,91 +11343,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor } else if (exp.op == EXP.assign) { - if (e1x.op == EXP.index && (cast(IndexExp)e1x).e1.type.toBasetype().ty == Taarray) - { - /* - * Rewrite: - * aa[key] = e2; - * as: - * ref __aatmp = aa; - * ref __aakey = key; - * ref __aaval = e2; - * (__aakey in __aatmp - * ? __aatmp[__aakey].opAssign(__aaval) - * : ConstructExp(__aatmp[__aakey], __aaval)); - */ - // ensure we keep the expr modifiable - Expression esetting = (cast(IndexExp)e1x).markSettingAAElem(); - if (esetting.op == EXP.error) - { - result = esetting; - return; - } - assert(esetting.op == EXP.index); - IndexExp ie = cast(IndexExp) esetting; - Type t2 = e2x.type.toBasetype(); - - Expression e0 = null; - Expression ea = extractSideEffect(sc, "__aatmp", e0, ie.e1); - Expression ek = extractSideEffect(sc, "__aakey", e0, ie.e2); - Expression ev = extractSideEffect(sc, "__aaval", e0, e2x); - - AssignExp ae = cast(AssignExp)exp.copy(); - ae.e1 = new IndexExp(exp.loc, ea, ek); - ae.e1 = ae.e1.expressionSemantic(sc); - ae.e1 = ae.e1.optimize(WANTvalue); - ae.e2 = ev; - if (Expression e = ae.opOverloadAssign(sc, aliasThisStop)) - { - Expression ey = null; - if (t2.ty == Tstruct && sd == t2.toDsymbol(sc)) - { - ey = ev; - } - else if (!ev.implicitConvTo(ie.type) && sd.ctor) - { - // Look for implicit constructor call - // Rewrite as S().ctor(e2) - ey = new StructLiteralExp(exp.loc, sd, null); - ey = new DotIdExp(exp.loc, ey, Id.ctor); - ey = new CallExp(exp.loc, ey, ev); - ey = ey.trySemantic(sc); - } - if (ey) - { - Expression ex; - ex = new IndexExp(exp.loc, ea, ek); - ex = ex.expressionSemantic(sc); - ex = ex.modifiableLvalue(sc); // allocate new slot - ex = ex.optimize(WANTvalue); - - ey = new ConstructExp(exp.loc, ex, ey); - ey = ey.expressionSemantic(sc); - if (ey.op == EXP.error) - { - result = ey; - return; - } - ex = e; - - // https://issues.dlang.org/show_bug.cgi?id=14144 - // The whole expression should have the common type - // of opAssign() return and assigned AA entry. - // Even if there's no common type, expression should be typed as void. - if (!typeMerge(sc, EXP.question, ex, ey)) - { - ex = new CastExp(ex.loc, ex, Type.tvoid); - ey = new CastExp(ey.loc, ey, Type.tvoid); - } - e = new CondExp(exp.loc, new InExp(exp.loc, ek, ea), ex, ey); - } - e = Expression.combine(e0, e); - e = e.expressionSemantic(sc); - result = e; - return; - } - } - else if (Expression e = exp.isAssignExp().opOverloadAssign(sc, aliasThisStop)) + if (Expression e = exp.isAssignExp().opOverloadAssign(sc, aliasThisStop)) { result = e; return; @@ -11895,16 +11842,8 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor exp.type = exp.e1.type; assert(exp.type); auto assignElem = exp.e2; - auto res = exp.op == EXP.assign ? exp.reorderSettingAAElem(sc) : exp; - /* https://issues.dlang.org/show_bug.cgi?id=22366 - * - * `reorderSettingAAElem` creates a tree of comma expressions, however, - * `checkAssignExp` expects only AssignExps. - */ - if (res == exp) // no `AA[k] = v` rewrite was performed - checkAssignEscape(*sc, res, false, false); - else - checkNewEscape(*sc, assignElem, false); // assigning to AA puts it on heap + Expression res = exp; + checkAssignEscape(*sc, res, false, false); if (auto ae = res.isConstructExp()) { @@ -12138,7 +12077,7 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor if ((exp.e1.type.isIntegral() || exp.e1.type.isFloating()) && (exp.e2.type.isIntegral() || exp.e2.type.isFloating())) { Expression e0 = null; - Expression e = exp.reorderSettingAAElem(sc); + Expression e = exp; e = Expression.extractLast(e, e0); assert(e == exp); @@ -12285,10 +12224,8 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor exp.type = exp.e1.type; auto assignElem = exp.e2; - auto res = exp.reorderSettingAAElem(sc); - if (res != exp) // `AA[k] = v` rewrite was performed - checkNewEscape(*sc, assignElem, false); - else if (exp.op == EXP.concatenateElemAssign || exp.op == EXP.concatenateDcharAssign) + auto res = exp; + if (exp.op == EXP.concatenateElemAssign || exp.op == EXP.concatenateDcharAssign) checkAssignEscape(*sc, res, false, false); result = res; @@ -13491,6 +13428,59 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor return; } + /** + * lowers an `InExp` to a call to `_d_aaIn!(V[K])`. + * + * Params: + * ie = the `InExp` to lower + * Returns: + * the lowered expression or an ErrorExp + */ + private Expression lowerToAAIn(InExp ie) + { + Identifier hook = Id._d_aaIn; + if (!verifyHookExist(ie.loc, *sc, hook, "key in AA")) + return ErrorExp.get(); + + Expression id = new IdentifierExp(ie.loc, Id.empty); + id = new DotIdExp(ie.loc, id, Id.object); + auto tiargs = new Objects(); + id = new DotIdExp(ie.loc, id, hook); + + Expression e1; + Expression ekey = extractSideEffect(sc, "__aakey", e1, ie.e1); + auto arguments = new Expressions(ie.e2, ekey); + auto ce = new CallExp(ie.loc, id, arguments); + ce.loweredFrom = ie; + e1 = Expression.combine(e1, ce); + return e1.expressionSemantic(sc); + } + + /** + * lowers a `RemoveExp` to a call to `_d_aaDel!(V[K])`. + * + * Params: + * re = the `RemoveExp` to lower + * Returns: + * the lowered expression or an ErrorExp + */ + private Expression lowerToAADel(RemoveExp re) + { + Identifier hook = Id._d_aaDel; + if (!verifyHookExist(re.loc, *sc, hook, "remove key in AA")) + return ErrorExp.get(); + + Expression id = new IdentifierExp(re.loc, Id.empty); + id = new DotIdExp(re.loc, id, Id.object); + auto tiargs = new Objects(); + id = new DotIdExp(re.loc, id, hook); + + auto arguments = new Expressions(re.e1, re.e2); + auto ce = new CallExp(re.loc, id, arguments); + ce.loweredFrom = re; + return ce.expressionSemantic(sc); + } + override void visit(InExp exp) { if (Expression e = exp.opOverloadBinary(sc, aliasThisStop)) @@ -13513,14 +13503,19 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor exp.e1 = exp.e1.implicitCastTo(sc, ta.index); } + // for backward compatibility, type accepted during semantic, but errors in glue layer + if (exp.e2.isTypeExp()) + { + exp.type = ta.nextOf().pointerTo(); + break; + } + result = lowerToAAIn(exp); + // even though the glue layer only needs the type info of the index, // this might be the first time an AA literal is accessed, so check // the full type info semanticTypeInfo(sc, ta); - - // Return type is pointer to value - exp.type = ta.nextOf().pointerTo(); - break; + return; } case Terror: @@ -13548,7 +13543,39 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor result = ex; return; } - result = e; + + result = lowerToAADel(e); + } + + /** + * lower a `EqualExp` to a call to `_d_aaEqual!(aa1, aa2)`. + * + * Params: + * ee = the `EqualExp` to lower + * Returns: + * the lowered expression or an ErrorExp + */ + private Expression lowerToAAEqual(EqualExp ee) + { + Identifier hook = Id._d_aaEqual; + if (!verifyHookExist(ee.loc, *sc, hook, "compare AAs")) + return ErrorExp.get(); + + Expression id = new IdentifierExp(ee.loc, Id.empty); + id = new DotIdExp(ee.loc, id, Id.object); + auto tiargs = new Objects(); + id = new DotIdExp(ee.loc, id, hook); + + auto arguments = new Expressions(ee.e1, ee.e2); + auto ce = new CallExp(ee.loc, id, arguments); + if (ee.op == EXP.notEqual) + { + auto ne = new NotExp(ee.loc, ce); + ne.loweredFrom = ee; + return ne.expressionSemantic(sc); + } + ce.loweredFrom = ee; + return ce.expressionSemantic(sc); } override void visit(EqualExp exp) @@ -13833,7 +13860,9 @@ private extern (C++) final class ExpressionSemanticVisitor : Visitor if (exp.e1.type.toBasetype().ty == Taarray) { + result = lowerToAAEqual(exp); semanticTypeInfo(sc, exp.e1.type.toBasetype()); + return; } if (!target.isVectorOpSupported(t1, exp.op, t2)) @@ -14322,7 +14351,8 @@ private bool expressionSemanticDone(Expression e) || e.isTypeExp() // stores its type in the Expression.type field || e.isCompoundLiteralExp() // stores its `(type) {}` in type field, gets rewritten to struct literal || e.isVarExp() // type sometimes gets set already before semantic - || (e.isAssocArrayLiteralExp() && !e.type.vtinfo) // semanticTypeInfo not run during initialization + || (e.isAssocArrayLiteralExp() && // semanticTypeInfo not run during initialization + (!e.type.vtinfo || !e.isAssocArrayLiteralExp().lowering)) ); } @@ -15744,6 +15774,9 @@ Expression resolveLoc(Expression exp, Loc loc, Scope* sc) element = element.resolveLoc(loc, sc); } + if (exp.lowering) + exp.lowering.resolveLoc(loc, sc); + return exp; } @@ -16494,16 +16527,6 @@ private Expression modifiableLvalueImpl(Expression _this, Scope* sc, Expression return visit(exp); } - Expression visitIndex(IndexExp exp) - { - //printf("IndexExp::modifiableLvalue(%s)\n", exp.toChars()); - Expression ex = exp.markSettingAAElem(); - if (ex.op == EXP.error) - return ex; - - return visit(exp); - } - Expression visitCond(CondExp exp) { if (!exp.e1.isLvalue() && !exp.e2.isLvalue()) @@ -16526,7 +16549,6 @@ private Expression modifiableLvalueImpl(Expression _this, Scope* sc, Expression case EXP.comma: return visitComma(_this.isCommaExp()); case EXP.delegatePointer: return visitDelegatePtr(_this.isDelegatePtrExp()); case EXP.delegateFunctionPointer: return visitDelegateFuncptr(_this.isDelegateFuncptrExp()); - case EXP.index: return visitIndex(_this.isIndexExp()); case EXP.question: return visitCond(_this.isCondExp()); } } @@ -17606,6 +17628,8 @@ private Expression expandInitializer(VarDeclaration vd, Loc loc) } e = e.copy(); + if (auto aae = e.isAssocArrayLiteralExp()) + aae.lowering = null; // need to redo lowering as it contains temporary variables that must be renamed e.loc = loc; // for better error message return e; } @@ -18315,3 +18339,320 @@ private extern(C++) class IncludeVisitor : Visitor { result = (sic.inc == Include.yes); } } + +/*************************************** +* mark ArrayExp on the left side of an assignment recursively as modifiable before +* semantic analysis to defer potential lowerings into the assignment when +* the right side of the expression is analyzed, too, i.e. +* +* a[i][j] = 1 +* +* is parsed to +* +* AssignExp(ArrayExp(ArrayExp(Id('a'), Id('i')), Id('j')), 1) +* +* ArrayExp is converted to IndexExp during semantic analysis, but the lowering +* for IndexExp on associative arrays is different for reading or writing. For +* struct types, it can even depend on the right hand side of an assignment. +* +* Params: +* ae = the ArrayExp to mark +*/ +void markArrayExpModifiable(ArrayExp ae) +{ + ae.modifiable = true; + for (auto ae1 = ae.e1.isArrayExp(); ae1; ae1 = ae1.e1.isArrayExp()) + ae1.modifiable = true; +} + +/*************************************** +* convert an IndexExp on an associative array `aa[key]` +* to `_d_aaGetRvalueX!(K,V)(aa, key)[0]` +* +* Params: +* ie = the IndexExp to lower +* sc = context +* Returns: +* on success, the lowered expression that is still an IndexExp +* with its `loweredFrom` field set to the original expression +* on failure, null is returned +*/ +Expression lowerAAIndexRead(IndexExp ie, Scope* sc) +{ + Expression ce = buildAAIndexRValueX(ie.e1.type, ie.e1, ie.e2, sc); + if (!ce) + return ie; + auto rv = new IndexExp(ie.loc, ce, IntegerExp.literal!0); + rv.loweredFrom = ie; + + return rv.expressionSemantic(sc); +} + +// helper for lowerAAIndexRead and revertIndexAssignToRvalues +// to rewrite `aa[key]` to `_d_aaGetRvalueX!(K,V)(aa, key)[0]` +private Expression buildAAIndexRValueX(Type t, Expression eaa, Expression ekey, Scope* sc) +{ + auto taa = t.toBasetype().isTypeAArray(); + if (!taa) + return null; + + auto loc = eaa.loc; + Identifier hook = Id._d_aaGetRvalueX; + if (!verifyHookExist(loc, *sc, hook, "indexing AA")) + return null; + + Expression func = new IdentifierExp(loc, Id.empty); + func = new DotIdExp(loc, func, Id.object); + auto tiargs = new Objects(taa.index, taa.next); + func = new DotTemplateInstanceExp(loc, func, hook, tiargs); + + Expression e0; + auto arguments = new Expressions(eaa, ekey); + auto call = new CallExp(loc, func, arguments); + e0 = Expression.combine(e0, call); + + if (arrayBoundsCheck(sc.func)) + { + // __aaget = _d_aaGetRvalueX(aa, key), __aaget ? __aaget : onRangeError(__FILE__, __LINE__) + auto ei = new ExpInitializer(loc, e0); + auto vartmp = Identifier.generateId("__aaget"); + auto vardecl = new VarDeclaration(loc, null, vartmp, ei, STC.exptemp); + auto declexp = new DeclarationExp(loc, vardecl); + + //Expression idrange = new IdentifierExp(loc, Identifier.idPool("_d_arraybounds")); + Expression idrange = new IdentifierExp(loc, Id.empty); + idrange = new DotIdExp(loc, idrange, Id.object); + idrange = new DotIdExp(loc, idrange, Identifier.idPool("_d_arraybounds")); + auto locargs = new Expressions(new FileInitExp(loc, EXP.file), new LineInitExp(loc)); + auto ex = new CallExp(loc, idrange, locargs); + + auto idvar1 = new IdentifierExp(loc, vartmp); + auto idvar2 = new IdentifierExp(loc, vartmp); + auto cond = new CondExp(loc, idvar1, idvar2, ex); + auto comma = new CommaExp(loc, declexp, cond); + return comma; + } + return e0; +} + +/*************************************** +* in a multi-dimensional array assignment without the out-most access being to an AA, +* convert AA accesses back to rvalues +* +* Params: +* ie = the IndexExp to lower +* sc = context +* Returns: +* the lowered expression if the assignment was actually on an associative array, +* the original expression otherwise, +* an ErorrExp on failure +*/ +Expression revertIndexAssignToRvalues(IndexExp ie, Scope* sc) +{ + if (auto ie1 = ie.e1.isIndexExp()) + { + // convert inner access first, otherwise we'd have to crawl into the lowered AST + ie.e1 = revertIndexAssignToRvalues(ie1, sc); + } + if (!ie.e1.type.isTypeAArray()) + return ie; + + assert(ie.modifiable); + return lowerAAIndexRead(ie, sc); +} + +// helper for rewriteAAIndexAssign +private Expression implicitConvertToStruct(Expression ev, StructDeclaration sd, Scope* sc) +{ + Type t2 = ev.type.toBasetype(); + Expression ey = null; + if (t2.ty == Tstruct && sd == t2.toDsymbol(sc)) + { + ey = ev; + } + else if (!ev.implicitConvTo(sd.type) && sd.ctor) + { + // Look for implicit constructor call + // Rewrite as S().ctor(e2) + ey = new StructLiteralExp(ev.loc, sd, null); + ey = new DotIdExp(ev.loc, ey, Id.ctor); + ey = new CallExp(ev.loc, ey, ev); + ey = ey.trySemantic(sc); + } + return ey; +} + +// helper for rewriteIndexAssign +private Expression rewriteAAIndexAssign(BinExp exp, Scope* sc, ref Type[2] aliasThisStop) +{ + auto ie = exp.e1.isIndexExp(); + assert(ie); + auto loc = ie.e1.loc; + + Identifier hook = Id._d_aaGetY; + if (!verifyHookExist(loc, *sc, hook, "modifying AA")) + return ErrorExp.get(); + + bool escape = checkNewEscape(*sc, exp.e2, false); + if (escape) + return ErrorExp.get(); + auto gcexp = exp.checkGC(sc); + if (gcexp.op == EXP.error) + return gcexp; + + // build `bool __aafound, auto __aaget = _d_aaGetY(aa, key, found);` + auto idfound = Identifier.generateId("__aafound"); + auto varfound = new VarDeclaration(loc, Type.tbool, idfound, null, STC.temp | STC.ctfe); + auto declfound = new DeclarationExp(loc, varfound); + + // handle side effects before the actual AA insert + Expression e0 = declfound.expressionSemantic(sc); + Expression eaa = ie.e1; + // find the AA of multi dimensional access + for (auto ieaa = ie.e1.isIndexExp(); ieaa && ieaa.e1.type.isTypeAArray(); ieaa = ieaa.e1.isIndexExp()) + eaa = ieaa.e1; + eaa = extractSideEffect(sc, "__aatmp", e0, eaa); + // collect all keys of multi dimensional access + Expressions ekeys; + ekeys.push(ie.e2); + for (auto ieaa = ie.e1.isIndexExp(); ieaa && ieaa.e1.type.isTypeAArray(); ieaa = ieaa.e1.isIndexExp()) + ekeys.push(ieaa.e2); + foreach (ekey; ekeys) + { + Type tidx = ekey.type.toBasetype(); + if (tidx.ty == Tarray && tidx.nextOf().isMutable()) + { + error(loc, "associative arrays can only be assigned values with immutable keys, not `%s`", tidx.toChars()); + return ErrorExp.get(); + } + } + // extract side effects in lexical order + for (size_t i = ekeys.length; i > 0; --i) + ekeys[i-1] = extractSideEffect(sc, "__aakey", e0, ekeys[i-1]); + Expression ev = extractSideEffect(sc, "__aaval", e0, exp.e2); + + // generate series of calls to _d_aaGetY + for (size_t i = ekeys.length; i > 0; --i) + { + auto taa = eaa.type.isTypeAArray(); + assert (taa); // type must not have changed during rewrite + Expression func = new IdentifierExp(loc, Id.empty); + func = new DotIdExp(loc, func, Id.object); + auto tiargs = new Objects(taa.index, taa.next); + func = new DotTemplateInstanceExp(loc, func, hook, tiargs); + + auto arguments = new Expressions(eaa, ekeys[i-1], new IdentifierExp(loc, idfound)); + eaa = new CallExp(loc, func, arguments); + if (i > 1) + { + // extractSideEffect() cannot be used to ref the IndexExp, + // because ConstructExp below will not insert a postblit + auto ie1 = new IndexExp(loc, eaa, IntegerExp.literal!0); + ie1.modifiable = true; + ie1.loweredFrom = eaa; + eaa = ie1; + } + eaa = eaa.expressionSemantic(sc); + if (eaa.op == EXP.error) + return eaa; + } + Expression eg = extractSideEffect(sc, "__aaget", e0, eaa); + auto ie1 = new IndexExp(loc, eg, IntegerExp.literal!0); + ie1.modifiable = true; + ie1.loweredFrom = ie; + auto ex = ie1.expressionSemantic(sc); + ex = ex.optimize(WANTvalue); + if (ex.op == EXP.error) + return ex; + if (!exp.isAssignExp()) + { + // modifying assignments work with zero-initialized inserted value + BinExp bex = cast(BinExp)exp.copy(); + bex.e1 = ex; + bex.e2 = ev; + ex = Expression.combine(e0, bex); + ex.isCommaExp().originalExp = exp; + return ex.expressionSemantic(sc); + } + AssignExp ae = new AssignExp(loc, ex, ev); + auto ts = ie.type.isTypeStruct(); + if (Expression overexp = ts ? ae.opOverloadAssign(sc, aliasThisStop) : null) + { + if (overexp.op == EXP.error) + return overexp; + if (auto ey = implicitConvertToStruct(ev, ts.sym, sc)) + { + // __aafound ? __aaget.opAssign(__aaval) : __aaget.ctor(__aaval) + ey = new ConstructExp(loc, ex, ey); + ey = ey.expressionSemantic(sc); + if (ey.op == EXP.error) + return ey; + ex = overexp; + + // https://issues.dlang.org/show_bug.cgi?id=14144 + // The whole expression should have the common type + // of opAssign() return and assigned AA entry. + // Even if there's no common type, expression should be typed as void. + if (!typeMerge(sc, EXP.question, ex, ey)) + { + ex = new CastExp(ex.loc, ex, Type.tvoid); + ey = new CastExp(ey.loc, ey, Type.tvoid); + } + Expression condfound = new IdentifierExp(loc, idfound); + ex = new CondExp(loc, condfound, ex, ey); + ex = Expression.combine(e0, ex); + ex.isCommaExp().originalExp = exp; + } + else + { + // write back to _aaGetRValueX(aa, key)[0].opAssign(__aaval) + auto call = buildAAIndexRValueX(ie.e1.type, ie.e1, ie.e2, sc); + ie1 = new IndexExp(loc, call, IntegerExp.literal!0); + ie1.loweredFrom = ie; + ex = ie1.expressionSemantic(sc); + ex = ex.optimize(WANTvalue); + ae = new AssignExp(loc, ex, ev); + ex = ae.opOverloadAssign(sc, aliasThisStop); + assert(ex); + } + } + else + { + ex = Expression.combine(e0, ae); + ex.isCommaExp().originalExp = exp; + } + ex = ex.expressionSemantic(sc); + return ex; +} + +/*************************************** +* rewrite multi-dimensional array modifying assignments on associative arrays +* +* Params: +* exp = the assignment expression, AssignExp, ConstructExp, BinAssignExp or PostExp +* sc = context +* aliasThisStop = for recursion check on `alias this` +* Returns: +* the lowered expression if the assignment was actually on an associative array, +* null if no modifying index expression was found on the lhs of the assignemnt +*/ +Expression rewriteIndexAssign(BinExp exp, Scope* sc, Type[2] aliasThisStop) +{ + if (auto ie1 = exp.e1.isIndexExp()) + { + if (ie1.e1.type.isTypeAArray()) + { + if (exp.op == EXP.construct) + exp.e1 = exp.e1.toLvalue(sc, "initialize"); + else + exp.e1 = exp.e1.modifiableLvalue(sc); + if (exp.e1.op == EXP.error) + return exp.e1; + assert(exp.e1 == ie1); + assert(ie1.modifiable); + return rewriteAAIndexAssign(exp, sc, aliasThisStop); + } + exp.e1 = revertIndexAssignToRvalues(ie1, sc); + } + return null; +} diff --git a/compiler/src/dmd/frontend.h b/compiler/src/dmd/frontend.h index 149ad5762523..1b679dda085f 100644 --- a/compiler/src/dmd/frontend.h +++ b/compiler/src/dmd/frontend.h @@ -2626,6 +2626,7 @@ class ArrayExp final : public UnaExp Array* arguments; size_t currentDimension; VarDeclaration* lengthVar; + bool modifiable; ArrayExp* syntaxCopy() override; bool isLvalue() override; void accept(Visitor* v) override; @@ -2652,6 +2653,7 @@ class ArrayLiteralExp final : public Expression Expression* basis; Array* elements; Expression* lowering; + AssocArrayLiteralExp* aaLiteral; static ArrayLiteralExp* create(Loc loc, Array* elements); ArrayLiteralExp* syntaxCopy() override; bool equals(const RootObject* const o) const override; @@ -2692,6 +2694,7 @@ class AssocArrayLiteralExp final : public Expression Array* keys; Array* values; Expression* lowering; + Expression* loweringCtfe; bool equals(const RootObject* const o) const override; AssocArrayLiteralExp* syntaxCopy() override; Optional toBool() override; @@ -2719,6 +2722,7 @@ class CallExp final : public UnaExp bool ignoreAttributes; bool isUfcsRewrite; VarDeclaration* vthis2; + Expression* loweredFrom; static CallExp* create(Loc loc, Expression* e, Array* exps); static CallExp* create(Loc loc, Expression* e); static CallExp* create(Loc loc, Expression* e, Expression* earg1); @@ -3276,6 +3280,7 @@ class IndexExp final : public BinExp { public: VarDeclaration* lengthVar; + Expression* loweredFrom; bool modifiable; bool indexIsInBounds; IndexExp* syntaxCopy() override; @@ -3443,6 +3448,7 @@ class NewExp final : public Expression class NotExp final : public UnaExp { public: + Expression* loweredFrom; void accept(Visitor* v) override; }; @@ -5572,14 +5578,14 @@ struct UnionExp final char complexexp[64LLU]; char symoffexp[56LLU]; char stringexp[44LLU]; - char arrayliteralexp[48LLU]; - char assocarrayliteralexp[48LLU]; + char arrayliteralexp[56LLU]; + char assocarrayliteralexp[56LLU]; char structliteralexp[64LLU]; char compoundliteralexp[32LLU]; char nullexp[22LLU]; char dotvarexp[41LLU]; char addrexp[32LLU]; - char indexexp[50LLU]; + char indexexp[58LLU]; char sliceexp[57LLU]; char vectorexp[45LLU]; }; @@ -6992,6 +6998,8 @@ class TypeInfoAssociativeArrayDeclaration final : public TypeInfoDeclaration { public: Type* entry; + Dsymbol* xopEqual; + Dsymbol* xtoHash; static TypeInfoAssociativeArrayDeclaration* create(Type* tinfo); void accept(Visitor* v) override; }; @@ -8887,6 +8895,7 @@ struct Id final static Identifier* keys; static Identifier* values; static Identifier* rehash; + static Identifier* dup; static Identifier* future; static Identifier* property; static Identifier* nogc; @@ -8924,11 +8933,15 @@ struct Id final static Identifier* Fback; static Identifier* FpopFront; static Identifier* FpopBack; - static Identifier* aaLen; - static Identifier* aaKeys; - static Identifier* aaValues; - static Identifier* aaRehash; - static Identifier* _aaAsStruct; + static Identifier* _d_aaGetY; + static Identifier* _d_aaGetRvalueX; + static Identifier* _d_aaDel; + static Identifier* _d_aaEqual; + static Identifier* _d_aaIn; + static Identifier* _d_aaNew; + static Identifier* _d_aaLen; + static Identifier* _d_aaApply; + static Identifier* _d_aaApply2; static Identifier* monitorenter; static Identifier* monitorexit; static Identifier* criticalenter; @@ -8946,9 +8959,6 @@ struct Id final static Identifier* _d_arrayliteralTX; static Identifier* _d_arrayliteralTXTrace; static Identifier* _d_assert_fail; - static Identifier* dup; - static Identifier* _aaApply; - static Identifier* _aaApply2; static Identifier* _d_arrayctor; static Identifier* _d_arraysetctor; static Identifier* _d_arraysetassign; @@ -8985,6 +8995,7 @@ struct Id final static Identifier* _d_arrayappendcTXTrace; static Identifier* _d_arraycatnTX; static Identifier* _d_arraycatnTXTrace; + static Identifier* _d_assocarrayliteralTX; static Identifier* stdc; static Identifier* stdarg; static Identifier* va_start; diff --git a/compiler/src/dmd/funcsem.d b/compiler/src/dmd/funcsem.d index 684142e19b6d..06e3d3db5616 100644 --- a/compiler/src/dmd/funcsem.d +++ b/compiler/src/dmd/funcsem.d @@ -3754,3 +3754,34 @@ extern (D) bool checkNestedReference(VarDeclaration vd, Scope* sc, Loc loc) return false; } + +/********************** + * Check to see if array bounds checking code has to be generated + * + * Params: + * fd = function for which code is to be generated + * Returns: + * true if do array bounds checking for the given function + */ +bool arrayBoundsCheck(FuncDeclaration fd) +{ + final switch (global.params.useArrayBounds) + { + case CHECKENABLE.off: + return false; + case CHECKENABLE.on: + return true; + case CHECKENABLE.safeonly: + { + if (fd) + { + Type t = fd.type; + if (t.ty == Tfunction && (cast(TypeFunction)t).trust == TRUST.safe) + return true; + } + return false; + } + case CHECKENABLE._default: + assert(0); + } +} diff --git a/compiler/src/dmd/hdrgen.d b/compiler/src/dmd/hdrgen.d index 6b9442892a84..73f430e3db87 100644 --- a/compiler/src/dmd/hdrgen.d +++ b/compiler/src/dmd/hdrgen.d @@ -2450,6 +2450,11 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt void visitAssocArrayLiteral(AssocArrayLiteralExp e) { + if (hgs.vcg_ast && e.lowering) + { + expToBuffer(e.lowering, PREC.assign, buf, hgs); + return; + } buf.put('['); foreach (i, key; *e.keys) { @@ -2525,6 +2530,11 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt void visitNew(NewExp e) { + if (hgs.vcg_ast && e.lowering) + { + expToBuffer(e.lowering, PREC.primary, buf, hgs); + return; + } if (e.thisexp) { expToBuffer(e.thisexp, PREC.primary, buf, hgs); @@ -2765,7 +2775,7 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt if (commaExtract) { - expToBuffer(commaExtract, precedence[exp.op], buf, hgs); + expToBuffer(commaExtract, expPrecedence(hgs, exp), buf, hgs); return; } } @@ -2865,12 +2875,27 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt e.e1.expressionPrettyPrint(buf, hgs); } else + { + if (!hgs.vcg_ast && e.loweredFrom) + { + // restore original syntax for expressions lowered to calls + expressionToBuffer(e.loweredFrom, buf, hgs); + return; + } expToBuffer(e.e1, precedence[e.op], buf, hgs); + } buf.put('('); argsToBuffer(e.arguments, buf, hgs, null, e.names); buf.put(')'); } + void visitNot(NotExp e) + { + if (!hgs.vcg_ast && e.loweredFrom) + return expressionToBuffer(e.loweredFrom, buf, hgs); + return visitUna(e); + } + void visitPtr(PtrExp e) { buf.put('*'); @@ -2969,8 +2994,29 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt expToBuffer(e.e2, PREC.primary, buf, hgs); } + void visitCat(CatExp e) + { + if (hgs.vcg_ast && e.lowering) + expressionToBuffer(e.lowering, buf, hgs); + else + visitBin(e); + } + + void visitCatAssign(CatAssignExp e) + { + if (hgs.vcg_ast && e.lowering) + expressionToBuffer(e.lowering, buf, hgs); + else + visitBin(e); + } + void visitIndex(IndexExp e) { + if (!hgs.vcg_ast && e.loweredFrom) + { + expressionToBuffer(e.loweredFrom, buf, hgs); + return; + } expToBuffer(e.e1, PREC.primary, buf, hgs); buf.put('['); sizeToBuffer(e.e2, buf, hgs); @@ -3077,6 +3123,7 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt case EXP.delegate_: return visitDelegate(e.isDelegateExp()); case EXP.dotType: return visitDotType(e.isDotTypeExp()); case EXP.call: return visitCall(e.isCallExp()); + case EXP.not: return visitNot(e.isNotExp()); case EXP.star: return visitPtr(e.isPtrExp()); case EXP.delete_: return visitDelete(e.isDeleteExp()); case EXP.cast_: return visitCast(e.isCastExp()); @@ -3090,6 +3137,10 @@ private void expressionPrettyPrint(Expression e, ref OutBuffer buf, ref HdrGenSt case EXP.array: return visitArray(e.isArrayExp()); case EXP.dot: return visitDot(e.isDotExp()); case EXP.index: return visitIndex(e.isIndexExp()); + case EXP.concatenate: return visitCat(e.isCatExp()); + case EXP.concatenateAssign: return visitCatAssign(e.isCatAssignExp()); + case EXP.concatenateElemAssign: return visitCatAssign(e.isCatElemAssignExp()); + case EXP.concatenateDcharAssign: return visitCatAssign(e.isCatDcharAssignExp()); case EXP.minusMinus: case EXP.plusPlus: return visitPost(e.isPostExp()); case EXP.preMinusMinus: @@ -3788,15 +3839,33 @@ private void expressionToBuffer(Expression e, ref OutBuffer buf, ref HdrGenState expressionPrettyPrint(e, buf, hgs); } +// to be called if e could be loweredFrom another expression instead of acessing precedence[e.op] directly +private PREC expPrecedence(ref HdrGenState hgs, Expression e) +{ + if (!hgs.vcg_ast) + { + if (auto ce = e.isCallExp()) + { + if (ce.loweredFrom) + e = ce.loweredFrom; + } + else if (auto ne = e.isNotExp()) + if (ne.loweredFrom) + e = ne.loweredFrom; + } + return precedence[e.op]; +} + /************************************************** * Write expression out to buf, but wrap it * in ( ) if its precedence is less than pr. */ private void expToBuffer(Expression e, PREC pr, ref OutBuffer buf, ref HdrGenState hgs) { + auto prec = expPrecedence(hgs, e); debug { - if (precedence[e.op] == PREC.zero) + if (prec == PREC.zero) printf("precedence not defined for token '%s'\n", EXPtoString(e.op).ptr); } if (e.op == 0xFF) @@ -3804,13 +3873,13 @@ private void expToBuffer(Expression e, PREC pr, ref OutBuffer buf, ref HdrGenSta buf.put(""); return; } - assert(precedence[e.op] != PREC.zero); + assert(prec != PREC.zero); assert(pr != PREC.zero); /* Despite precedence, we don't allow a= PREC.or && pr <= PREC.and && precedence[e.op] == PREC.rel)) + if (prec < pr || (pr == PREC.rel && prec == pr) + || (pr >= PREC.or && pr <= PREC.and && prec == PREC.rel)) { buf.put('('); e.expressionToBuffer(buf, hgs); diff --git a/compiler/src/dmd/id.d b/compiler/src/dmd/id.d index 9de36e477b92..c5988203d1b2 100644 --- a/compiler/src/dmd/id.d +++ b/compiler/src/dmd/id.d @@ -206,6 +206,7 @@ immutable Msgtable[] msgtable = { "keys" }, { "values" }, { "rehash" }, + { "dup" }, { "future", "__future" }, { "property" }, @@ -259,11 +260,15 @@ immutable Msgtable[] msgtable = { "FpopBack", "popBack" }, // For internal functions - { "aaLen", "_aaLen" }, - { "aaKeys", "_aaKeys" }, - { "aaValues", "_aaValues" }, - { "aaRehash", "_aaRehash" }, - { "_aaAsStruct" }, + { "_d_aaGetY" }, + { "_d_aaGetRvalueX" }, + { "_d_aaDel" }, + { "_d_aaEqual" }, + { "_d_aaIn" }, + { "_d_aaNew" }, + { "_d_aaLen" }, + { "_d_aaApply" }, + { "_d_aaApply2" }, { "monitorenter", "_d_monitorenter" }, { "monitorexit", "_d_monitorexit" }, { "criticalenter", "_d_criticalenter2" }, @@ -283,9 +288,6 @@ immutable Msgtable[] msgtable = { "_d_arrayliteralTX" }, { "_d_arrayliteralTXTrace" }, { "_d_assert_fail" }, - { "dup" }, - { "_aaApply" }, - { "_aaApply2" }, { "_d_arrayctor" }, { "_d_arraysetctor" }, { "_d_arraysetassign" }, @@ -335,6 +337,7 @@ immutable Msgtable[] msgtable = { "_d_arrayappendcTXTrace" }, { "_d_arraycatnTX" }, { "_d_arraycatnTXTrace" }, + { "_d_assocarrayliteralTX" }, // varargs implementation { "stdc" }, diff --git a/compiler/src/dmd/inline.d b/compiler/src/dmd/inline.d index 5b7611fb52b3..be16e30c4f1d 100644 --- a/compiler/src/dmd/inline.d +++ b/compiler/src/dmd/inline.d @@ -731,7 +731,9 @@ public: auto lowering = ne.lowering; if (lowering) if (auto ce = lowering.isCallExp()) - if (ce.f.ident == Id._d_newarrayT || ce.f.ident == Id._d_newarraymTX) + if (ce.f.ident == Id._d_newarrayT || + ce.f.ident == Id._d_newarraymTX || + ce.f.ident == Id._d_aaNew) { ne.lowering = doInlineAs!Expression(lowering, ids); goto LhasLowering; @@ -943,6 +945,9 @@ public: auto ce = e.copy().isAssocArrayLiteralExp(); ce.keys = arrayExpressionDoInline(e.keys); ce.values = arrayExpressionDoInline(e.values); + if (e.lowering) + ce.lowering = doInlineAs!Expression(e.lowering, ids); + result = ce; semanticTypeInfo(null, e.type); diff --git a/compiler/src/dmd/nogc.d b/compiler/src/dmd/nogc.d index 8c782cdb819b..f528d411bfd5 100644 --- a/compiler/src/dmd/nogc.d +++ b/compiler/src/dmd/nogc.d @@ -154,25 +154,11 @@ public: return; } - Identifier hook = global.params.tracegc ? Id._d_arrayliteralTXTrace : Id._d_arrayliteralTX; - if (!verifyHookExist(e.loc, *sc, hook, "creating array literals")) + if (!lowerArrayLiteral(e, sc)) { err = true; return; } - Expression lowering = new IdentifierExp(e.loc, Id.empty); - lowering = new DotIdExp(e.loc, lowering, Id.object); - auto tiargs = new Objects(); - // Remove `inout`, `const`, `immutable` and `shared` to reduce template instances - auto t = e.type.nextOf().unqualify(MODFlags.wild | MODFlags.const_ | MODFlags.immutable_ | MODFlags.shared_); - tiargs.push(t); - lowering = new DotTemplateInstanceExp(e.loc, lowering, hook, tiargs); - - auto arguments = new Expressions(); - arguments.push(new IntegerExp(dim)); - - lowering = new CallExp(e.loc, lowering, arguments); - e.lowering = lowering.expressionSemantic(sc); f.printGCUsage(e.loc, "array literal may cause a GC allocation"); } diff --git a/compiler/src/dmd/opover.d b/compiler/src/dmd/opover.d index bc8e015022bd..791762ac18b6 100644 --- a/compiler/src/dmd/opover.d +++ b/compiler/src/dmd/opover.d @@ -338,7 +338,11 @@ Expression opOverloadArray(ArrayExp ae, Scope* sc) // Convert to IndexExp if (ae.arguments.length == 1) - return new IndexExp(ae.loc, ae.e1, (*ae.arguments)[0]).expressionSemantic(sc); + { + auto idxexp = new IndexExp(ae.loc, ae.e1, (*ae.arguments)[0]); + idxexp.modifiable = ae.modifiable; + return idxexp.expressionSemantic(sc); + } } break; } @@ -904,6 +908,8 @@ Expression opOverloadBinaryAssign(BinAssignExp e, Scope* sc, Type[2] aliasThisSt { if (auto ae = e.e1.isArrayExp()) { + markArrayExpModifiable(ae); + ae.e1 = ae.e1.expressionSemantic(sc); ae.e1 = resolveProperties(sc, ae.e1); Expression ae1old = ae.e1; @@ -991,6 +997,9 @@ Expression opOverloadBinaryAssign(BinAssignExp e, Scope* sc, Type[2] aliasThisSt if (Expression result = e.binSemanticProp(sc)) return result; + if (auto result = rewriteIndexAssign(e, sc, aliasThisStop)) + return result; + // Don't attempt 'alias this' if an error occurred if (e.e1.type.isTypeError() || e.e2.type.isTypeError()) return ErrorExp.get(); diff --git a/compiler/src/dmd/semantic2.d b/compiler/src/dmd/semantic2.d index 3d167966716a..c66171b2201d 100644 --- a/compiler/src/dmd/semantic2.d +++ b/compiler/src/dmd/semantic2.d @@ -813,10 +813,11 @@ private void doGNUABITagSemantic(ref Expression e, ref Expression* lastTag) */ void lowerStaticAAs(VarDeclaration vd, Scope* sc) { - if (vd.storage_class & STC.manifest) - return; if (auto ei = vd._init.isExpInitializer()) - lowerStaticAAs(ei.exp, sc); + { + scope v = new StaticAAVisitor(sc, vd.storage_class); + ei.exp.accept(v); + } } /** @@ -828,7 +829,7 @@ void lowerStaticAAs(VarDeclaration vd, Scope* sc) */ void lowerStaticAAs(Expression e, Scope* sc) { - scope v = new StaticAAVisitor(sc); + scope v = new StaticAAVisitor(sc, STC.none); e.accept(v); } @@ -837,32 +838,22 @@ private extern(C++) final class StaticAAVisitor : SemanticTimeTransitiveVisitor { alias visit = SemanticTimeTransitiveVisitor.visit; Scope* sc; + STC storage_class; - this(Scope* sc) scope @safe + this(Scope* sc, STC storage_class) scope @safe { this.sc = sc; + this.storage_class = storage_class; } override void visit(AssocArrayLiteralExp aaExp) { - if (!verifyHookExist(aaExp.loc, *sc, Id._aaAsStruct, "initializing static associative arrays", Id.object)) - return; - - Expression hookFunc = new IdentifierExp(aaExp.loc, Id.empty); - hookFunc = new DotIdExp(aaExp.loc, hookFunc, Id.object); - hookFunc = new DotIdExp(aaExp.loc, hookFunc, Id._aaAsStruct); - auto arguments = new Expressions(aaExp); - Expression loweredExp = new CallExp(aaExp.loc, hookFunc, arguments); - - sc = sc.startCTFE(); - loweredExp = loweredExp.expressionSemantic(sc); - loweredExp = resolveProperties(sc, loweredExp); - sc = sc.endCTFE(); - loweredExp = loweredExp.ctfeInterpret(); - - aaExp.lowering = loweredExp; - - semanticTypeInfo(sc, loweredExp.type); + if (!aaExp.lowering) + expressionSemantic(aaExp, sc); + assert(aaExp.lowering); + if (!(storage_class & STC.manifest)) // manifest constants create runtime copies + aaExp.loweringCtfe = aaExp.lowering.ctfeInterpret(); + semanticTypeInfo(sc, aaExp.lowering.type); } // https://issues.dlang.org/show_bug.cgi?id=24602 diff --git a/compiler/src/dmd/semantic3.d b/compiler/src/dmd/semantic3.d index 717ebea65654..4968b3089ddc 100644 --- a/compiler/src/dmd/semantic3.d +++ b/compiler/src/dmd/semantic3.d @@ -1628,6 +1628,55 @@ private extern(C++) final class Semantic3Visitor : Visitor sd.semanticTypeInfoMembers(); ad.semanticRun = PASS.semantic3done; } + + override void visit(TypeInfoAssociativeArrayDeclaration ti) + { + auto t = ti.tinfo.isTypeAArray(); + Loc loc = t.loc; + auto sc2 = sc ? sc : ti._scope; + + auto makeDotExp(Identifier hook) + { + // always called with naked types + auto tiargs = new Objects(t.index, t.next); + + Expression id = new IdentifierExp(loc, Id.empty); + id = new DotIdExp(loc, id, Id.object); + id = new DotIdExp(loc, id, Id.TypeInfo_AssociativeArray); + return new DotTemplateInstanceExp(loc, id, hook, tiargs); + } + + // generate ti.entry + auto tempinst = makeDotExp(Id.Entry); + auto e = expressionSemantic(tempinst, sc2); + assert(e.type); + ti.entry = e.type; + if (auto ts = ti.entry.isTypeStruct()) + { + ts.sym.requestTypeInfo = true; + if (auto tmpl = ts.sym.isInstantiated()) + tmpl.minst = sc2._module.importedFrom; // ensure it gets emitted + } + semanticTypeInfo(sc2, ti.entry); // might get deferred + + // generate ti.xtoHash + auto hashinst = makeDotExp(Identifier.idPool("aaGetHash")); + e = expressionSemantic(hashinst, sc2); + assert(e.isVarExp() && e.type.isTypeFunction()); + ti.xtoHash = e.isVarExp().var; + if (auto tmpl = ti.xtoHash.parent.isTemplateInstance()) + tmpl.minst = sc2._module.importedFrom; // ensure it gets emitted + + // generate ti.xopEqual + auto equalinst = makeDotExp(Identifier.idPool("aaOpEqual")); + e = expressionSemantic(equalinst, sc2); + assert(e.isVarExp() && e.type.isTypeFunction()); + ti.xopEqual = e.isVarExp().var; + if (auto tmpl = ti.xopEqual.parent.isTemplateInstance()) + tmpl.minst = sc2._module.importedFrom; // ensure it gets emitted + + visit(cast(ASTCodegen.TypeInfoDeclaration)ti); + } } /// Helper for semantic3 analysis of functions. diff --git a/compiler/src/dmd/statementsem.d b/compiler/src/dmd/statementsem.d index 7fbf7b91c91f..cfe99023b530 100644 --- a/compiler/src/dmd/statementsem.d +++ b/compiler/src/dmd/statementsem.d @@ -3881,7 +3881,6 @@ private extern(D) Expression applyArray(ForeachStatement fs, Expression flde, private extern(D) Expression applyAssocArray(ForeachStatement fs, Expression flde, Type tab) { auto taa = tab.isTypeAArray(); - Expression ec; const dim = fs.parameters.length; // Check types Parameter p = (*fs.parameters)[0]; @@ -3909,46 +3908,22 @@ private extern(D) Expression applyAssocArray(ForeachStatement fs, Expression fld } /* Call: - * extern(C) int _aaApply(void*, in size_t, int delegate(void*)) - * _aaApply(aggr, keysize, flde) - * - * extern(C) int _aaApply2(void*, in size_t, int delegate(void*, void*)) - * _aaApply2(aggr, keysize, flde) + * int _d_aaApply(V[K] aa, int delegate(V*)) + * or + * int _d_aaApply2(V[K] aa, int delegate(K*, V*)) */ - __gshared FuncDeclaration* fdapply = [null, null]; - __gshared TypeDelegate* fldeTy = [null, null]; - ubyte i = (dim == 2 ? 1 : 0); - if (!fdapply[i]) - { - auto params = new Parameters(new Parameter(Loc.initial, STC.none, Type.tvoid.pointerTo(), null, null, null), - new Parameter(Loc.initial, STC.const_, Type.tsize_t, null, null, null)); - auto dgparams = new Parameters(new Parameter(Loc.initial, STC.none, Type.tvoidptr, null, null, null)); - if (dim == 2) - dgparams.push(new Parameter(Loc.initial, STC.none, Type.tvoidptr, null, null, null)); - fldeTy[i] = new TypeDelegate(new TypeFunction(ParameterList(dgparams), Type.tint32, LINK.d)); - params.push(new Parameter(Loc.initial, STC.none, fldeTy[i], null, null, null)); - fdapply[i] = FuncDeclaration.genCfunc(params, Type.tint32, i ? Id._aaApply2 : Id._aaApply); - } - - auto exps = new Expressions(fs.aggr); - auto keysize = taa.index.size(); - if (keysize == SIZE_INVALID) - return null; - assert(keysize < keysize.max - target.ptrsize); - keysize = (keysize + (target.ptrsize - 1)) & ~(target.ptrsize - 1); - // paint delegate argument to the type runtime expects - Expression fexp = flde; - if (!fldeTy[i].equals(flde.type)) - { - fexp = new CastExp(fs.loc, flde, flde.type); - fexp.type = fldeTy[i]; - } - exps.push(new IntegerExp(Loc.initial, keysize, Type.tsize_t)); - exps.push(fexp); - ec = new VarExp(Loc.initial, fdapply[i], false); - ec = new CallExp(fs.loc, ec, exps); - ec.type = Type.tint32; // don't run semantic() on ec - return ec; + auto loc = fs.loc; + Identifier hook = dim == 2 ? Id._d_aaApply2 : Id._d_aaApply; + Expression func = new IdentifierExp(loc, Id.empty); + func = new DotIdExp(loc, func, Id.object); + auto tiargs = new Objects(taa.index.substWildTo(MODFlags.const_), + taav.substWildTo(MODFlags.const_), + flde.type.substWildTo(MODFlags.const_)); + func = new DotTemplateInstanceExp(loc, func, hook, tiargs); + + auto arguments = new Expressions(fs.aggr, flde); + auto call = new CallExp(loc, func, arguments); + return call; } private extern(D) Statement loopReturn(Expression e, Statements* cases, Loc loc) @@ -4001,6 +3976,7 @@ private FuncExp foreachBodyToFunction(Scope* sc, ForeachStatement fs, TypeFuncti p.type = p.type.typeSemantic(fs.loc, sc); p.type = p.type.addStorageClass(p.storageClass); + p.type = p.type.substWildTo(MODFlags.const_); if (tfld) { Parameter param = tfld.parameterList[i]; @@ -4036,7 +4012,7 @@ private FuncExp foreachBodyToFunction(Scope* sc, ForeachStatement fs, TypeFuncti Statement s = new ExpStatement(fs.loc, v); fs._body = new CompoundStatement(fs.loc, s, fs._body); if (taa) - p.type = i == 1 || fs.parameters.length == 1 ? taa.nextOf() : taa.index; + p.type = (i == 1 || fs.parameters.length == 1 ? taa.nextOf() : taa.index).substWildTo(MODFlags.const_); } params.push(new Parameter(fs.loc, stc, p.type, id, null, null)); } diff --git a/compiler/src/dmd/todt.d b/compiler/src/dmd/todt.d index 8af6d77e83a1..7a6f4ab1e5c4 100644 --- a/compiler/src/dmd/todt.d +++ b/compiler/src/dmd/todt.d @@ -248,13 +248,19 @@ void Expression_toDt(Expression e, ref DtBuilder dtb) } if (!e.lwr && !e.upr) return Expression_toDt(e.e1, dtb); + + size_t len; if (auto strExp = e.e1.isStringExp()) - { - auto lwr = e.lwr.isIntegerExp(); - auto upr = e.upr.isIntegerExp(); - if (lwr && upr && lwr.toInteger() == 0 && upr.toInteger() == strExp.len) - return Expression_toDt(e.e1, dtb); - } + len = strExp.len; + else if (auto arrExp = e.e1.isArrayLiteralExp()) + len = arrExp.elements.length; + else + return nonConstExpError(e); + + auto lwr = e.lwr.isIntegerExp(); + auto upr = e.upr.isIntegerExp(); + if (lwr && upr && lwr.toInteger() == 0 && upr.toInteger() == len) + return Expression_toDt(e.e1, dtb); nonConstExpError(e); } @@ -496,12 +502,12 @@ void Expression_toDt(Expression e, ref DtBuilder dtb) */ void visitAssocArrayLiteral(AssocArrayLiteralExp e) { - if (!e.lowering) + if (!e.loweringCtfe) { error(e.loc, "internal compiler error: failed to detect static initialization of associative array"); assert(0); } - Expression_toDt(e.lowering, dtb); + Expression_toDt(e.loweringCtfe, dtb); return; } @@ -1351,7 +1357,7 @@ private extern (C++) class TypeInfoDtVisitor : Visitor override void visit(TypeInfoAssociativeArrayDeclaration d) { //printf("TypeInfoAssociativeArrayDeclaration.toDt()\n"); - verifyStructSize(Type.typeinfoassociativearray, 5 * target.ptrsize); + verifyStructSize(Type.typeinfoassociativearray, 7 * target.ptrsize); dtb.xoff(toVtblSymbol(Type.typeinfoassociativearray), 0); // vtbl for TypeInfo_AssociativeArray if (Type.typeinfoassociativearray.hasMonitor()) @@ -1367,6 +1373,9 @@ private extern (C++) class TypeInfoDtVisitor : Visitor TypeInfo_toObjFile(null, d.loc, d.entry); dtb.xoff(toSymbol(d.entry.vtinfo), 0); // TypeInfo for key,value-pair + + dtb.xoff(toSymbol(d.xopEqual), 0); + dtb.xoff(toSymbol(d.xtoHash), 0); } override void visit(TypeInfoFunctionDeclaration d) diff --git a/compiler/src/dmd/toir.d b/compiler/src/dmd/toir.d index 20928ca49ea6..3f965c979a8f 100644 --- a/compiler/src/dmd/toir.d +++ b/compiler/src/dmd/toir.d @@ -46,6 +46,7 @@ import dmd.toctype; import dmd.e2ir; import dmd.errorsink; import dmd.func; +import dmd.funcsem; import dmd.globals : Param; import dmd.glue; import dmd.identifier; @@ -119,25 +120,7 @@ struct IRState { if (m.filetype == FileType.c) return false; - final switch (params.useArrayBounds) - { - case CHECKENABLE.off: - return false; - case CHECKENABLE.on: - return true; - case CHECKENABLE.safeonly: - { - if (FuncDeclaration fd = getFunc()) - { - Type t = fd.type; - if (t.ty == Tfunction && (cast(TypeFunction)t).trust == TRUST.safe) - return true; - } - return false; - } - case CHECKENABLE._default: - assert(0); - } + return dmd.funcsem.arrayBoundsCheck(getFunc()); } /**************************** diff --git a/compiler/src/dmd/typesem.d b/compiler/src/dmd/typesem.d index 5fdcc646470e..ca0d4c617fc9 100644 --- a/compiler/src/dmd/typesem.d +++ b/compiler/src/dmd/typesem.d @@ -5097,20 +5097,15 @@ Expression dotExp(Type mt, Scope* sc, Expression e, Identifier ident, DotExpFlag } if (ident == Id.length) { - __gshared FuncDeclaration fd_aaLen = null; - if (fd_aaLen is null) - { - auto fparams = new Parameters(new Parameter(Loc.initial, STC.const_ | STC.scope_, mt, - null, null, null)); - fd_aaLen = FuncDeclaration.genCfunc(fparams, Type.tsize_t, Id.aaLen); - TypeFunction tf = fd_aaLen.type.toTypeFunction(); - tf.purity = PURE.const_; - tf.isNothrow = true; - tf.isNogc = false; - } - Expression ev = new VarExp(e.loc, fd_aaLen, false); - e = new CallExp(e.loc, ev, e); - e.type = fd_aaLen.type.toTypeFunction().next; + auto loc = e.loc; + Expression hookFunc = new IdentifierExp(loc, Id.empty); + hookFunc = new DotIdExp(loc, hookFunc, Id.object); + auto keytype = mt.index.substWildTo(MODFlags.const_); + auto valtype = mt.nextOf().substWildTo(MODFlags.const_); + auto tiargs = new Objects(keytype, valtype); + hookFunc = new DotTemplateInstanceExp(loc, hookFunc, Id._d_aaLen, tiargs); + Expression e = new CallExp(loc, hookFunc, e); + e = e.expressionSemantic(sc); return e; } else diff --git a/compiler/src/dmd/typinf.d b/compiler/src/dmd/typinf.d index 5888d837eac9..5f54900a244e 100644 --- a/compiler/src/dmd/typinf.d +++ b/compiler/src/dmd/typinf.d @@ -176,26 +176,9 @@ TypeInfoDeclaration getTypeInfoAssocArrayDeclaration(TypeAArray t, Scope* sc) auto ti = TypeInfoAssociativeArrayDeclaration.create(t); t.vtinfo = ti; // assign it early to avoid recursion in expressionSemantic - Loc loc = t.loc; - auto tiargs = new Objects(t.index, // always called with naked types - t.next); - - Expression id = new IdentifierExp(loc, Id.empty); - id = new DotIdExp(loc, id, Id.object); - id = new DotIdExp(loc, id, Id.TypeInfo_AssociativeArray); - auto tempinst = new DotTemplateInstanceExp(loc, id, Id.Entry, tiargs); - auto e = expressionSemantic(tempinst, sc); - assert(e.type); - ti.entry = e.type; - if (auto ts = ti.entry.isTypeStruct()) - { - ts.sym.requestTypeInfo = true; - if (auto tmpl = ts.sym.isInstantiated()) - tmpl.minst = sc._module.importedFrom; // ensure it get's emitted - } - getTypeInfoType(loc, ti.entry, sc); - assert(ti.entry.vtinfo); - + ti._scope = sc; + sc.setNoFree(); + Module.addDeferredSemantic3(ti); return ti; } diff --git a/compiler/test/fail_compilation/aa_assign.d b/compiler/test/fail_compilation/aa_assign.d new file mode 100644 index 000000000000..3c6c5d2b1d4d --- /dev/null +++ b/compiler/test/fail_compilation/aa_assign.d @@ -0,0 +1,13 @@ +/* TEST_OUTPUT: +--- +fail_compilation\aa_assign.d(11): Error: associative arrays can only be assigned values with immutable keys, not `char[]` +--- +*/ + +void test_mutable_key() +{ + int[char[]] aa; + char[] str = "123".dup; + aa[str] = 3; + assert(str in aa); +} diff --git a/compiler/test/fail_compilation/aaerrors.d b/compiler/test/fail_compilation/aaerrors.d new file mode 100644 index 000000000000..571f47225437 --- /dev/null +++ b/compiler/test/fail_compilation/aaerrors.d @@ -0,0 +1,43 @@ +/* TEST_OUTPUT: +TRANSFORM_OUTPUT: remove_lines(called from here) +--- +fail_compilation\aaerrors.d-mixin-29(29): Error: `assert(aai[1] == 0)` failed +fail_compilation\aaerrors.d-mixin-30(30): Error: `assert((aai[1] = 1) == 0)` failed +fail_compilation\aaerrors.d-mixin-31(31): Error: `assert(*(1 in aai) == 3)` failed +fail_compilation\aaerrors.d-mixin-32(32): Error: `assert(aai.remove(2))` failed +fail_compilation\aaerrors.d-mixin-33(33): Error: `assert(aai != [1:2])` failed +fail_compilation\aaerrors.d-mixin-34(34): Error: `assert(aai == [1:3])` failed +fail_compilation\aaerrors.d-mixin-41(41): Error: `assert(aas[1].x == 0)` failed +fail_compilation\aaerrors.d-mixin-42(42): Error: `assert((aas[1] = 1).x == 0)` failed +fail_compilation\aaerrors.d-mixin-43(43): Error: `assert((*(1 in aas)).x == 0)` failed +--- +*/ + + +struct S +{ + int x; + this(int _x){ x = _x; } + ref S opAssign(int _x){ x = _x; return this; } +} + +string gentest_ii(string expr) +{ + return "() { int[int] aai = [ 1 : 2 ]; assert(" ~ expr ~ ");\n return true; }()\n"; +} + +const ii1 = mixin(gentest_ii("aai[1] == 0")); +const ii2 = mixin(gentest_ii("(aai[1] = 1) == 0")); +const ii3 = mixin(gentest_ii("*(1 in aai) == 3")); +const ii4 = mixin(gentest_ii("aai.remove(2)")); +const ii5 = mixin(gentest_ii("aai != [1:2]")); +const ii6 = mixin(gentest_ii("aai == [1:3]")); + +string gentest_is(string expr) +{ + return "() { S[int] aas = [ 1 : S(2) ]; assert(" ~ expr ~ ");\n return true; }()\n"; +} + +const is1 = mixin(gentest_is("aas[1].x == 0")); +const is2 = mixin(gentest_is("(aas[1] = 1).x == 0")); +const is3 = mixin(gentest_is("(1 in aas).x == 0")); diff --git a/compiler/test/fail_compilation/fail6795.d b/compiler/test/fail_compilation/fail6795.d index dbdd5d5666b5..e566aff570a9 100644 --- a/compiler/test/fail_compilation/fail6795.d +++ b/compiler/test/fail_compilation/fail6795.d @@ -7,10 +7,10 @@ fail_compilation/fail6795.d(20): Error: cannot modify expression `[0:0][0]` beca fail_compilation/fail6795.d(22): Error: cannot modify expression `[0][0]` because it is not an lvalue fail_compilation/fail6795.d(23): Error: cannot modify expression `[0:0][0]` because it is not an lvalue fail_compilation/fail6795.d(25): Error: cannot take address of expression `[0][0]` because it is not an lvalue -fail_compilation/fail6795.d(26): Error: cannot take address of expression `[0:0][0]` because it is not an lvalue fail_compilation/fail6795.d(30): Error: cannot modify expression `Some["zz"]` because it is not an lvalue --- */ + void test_wrong_line_num() { enum int[1] sa = [0]; @@ -23,7 +23,7 @@ void test_wrong_line_num() aa[0] /= 3; auto ps = &sa[0]; - auto pa = &aa[0]; + auto pa = &aa[0]; // ok with AA lowering, just as `pa = 0 in aa` // https://issues.dlang.org/show_bug.cgi?id=24845 enum Maps : int[string] { Some = ["aa" : 12], Other = ["bb" : 24] } diff --git a/compiler/test/runnable/testaa2.d b/compiler/test/runnable/testaa2.d index 3d199d984125..74da97d55e70 100644 --- a/compiler/test/runnable/testaa2.d +++ b/compiler/test/runnable/testaa2.d @@ -310,9 +310,9 @@ void testinenum() // https://github.com/dlang/dmd/issues/21258 void test21258() { - alias AliasSeq(TList...) = TList; + alias AliasSeq(TList...) = TList; - struct S { int x; } // use a local type to not generate required TypeInfo elsewhere + struct S { int x; } // use a local type to not generate required TypeInfo elsewhere foreach (T; AliasSeq!(S[int])) enum E { a = T.init, } // bug report uses bad syntax here, but this crashed, too } @@ -329,6 +329,53 @@ void test21207() /************************************************/ +void testEvaluationOrder() +{ + static int last; + int[int] aa; + int[4] arr; + + int seqi(int n, int i, int ln = __LINE__) + { + n += ln * 100; + assert(n > last); + last = n; + return i; + } + ref int[int] seqaa(int n, int ln = __LINE__) + { + n += ln * 100; + assert(n > last); + last = n; + return aa; + } + seqaa(1)[seqi(2, 0)] = seqi(3, 1); // aa[0] = 1 + int x = seqaa(1)[seqi(2, 0)]; // x = aa[0] + seqaa(1)[seqi(2, 1)] = seqaa(3)[seqi(4, 0)]; // aa[1] = aa[0] + assert(seqi(1, 0) in seqaa(2)); // 0 in aa + + // only executed once? + auto naa = seqaa(1).dup; + auto len = seqaa(1).length; + auto keys = seqaa(1).keys; + auto values = seqaa(1).values; + auto hash = hashOf(seqaa(1)); + seqaa(1).rehash; + seqaa(1).clear; + + version (none) + seqaa(1) = [seqi(2, 1) : seqi(3, 1), seqi(4, 2) : seqi(5, 4)]; // aa = [1:1, 2:4] + else + aa = [1:1, 2:4]; + + assert(seqaa(1).remove(seqi(2, 1))); // aa.remove(1) + + assert(seqaa(1) == seqaa(2)); // aa == aa + assert(!(seqaa(1) != seqaa(2))); // aa != aa +} + +/************************************************/ + int main() { testaa(); @@ -339,6 +386,7 @@ int main() testinout(); testinenum(); test21258(); + testEvaluationOrder(); printf("Success\n"); return 0; diff --git a/compiler/test/runnable/testaa3.d b/compiler/test/runnable/testaa3.d index 4aac1fba1a4e..2aadc35afc0a 100644 --- a/compiler/test/runnable/testaa3.d +++ b/compiler/test/runnable/testaa3.d @@ -314,6 +314,29 @@ int[int] testRetx() void aafunc(int[int] aa) {} +void testTypeinfo() +{ + int[int] a = [ 1:1, 2:4, 3:9, 4:16 ]; + int[int] b = [ 1:1, 2:4, 3:9, 4:16 ]; + assert(a == b); + + hash_t ahash1 = hashOf(a); + hash_t ahash2 = typeid(a).getHash(&a); + assert(ahash1 == ahash2); + hash_t bhash1 = hashOf(a); + hash_t bhash2 = typeid(a).getHash(&a); + assert(bhash1 == bhash2); + assert(ahash1 == bhash1); + assert(typeid(a).equals(&a, &b)); + + string[string] c = [ "1":"one", "2":"two", "3":"three" ]; + hash_t chash1 = hashOf(c); + hash_t chash2 = typeid(c).getHash(&c); + assert(chash1 == chash2); + assert(chash1 != ahash2); + assert(typeid(c).equals(&c, &c)); +} + /***************************************************/ // https://issues.dlang.org/show_bug.cgi?id=12214 @@ -370,6 +393,7 @@ void main() assert(testRet()); static assert(testRet()); + testTypeinfo(); test12220(); test12403(); } diff --git a/druntime/mak/DOCS b/druntime/mak/DOCS index adb685dc0bf7..d09ff5df3b02 100644 --- a/druntime/mak/DOCS +++ b/druntime/mak/DOCS @@ -32,6 +32,7 @@ DOCS=\ $(DOCDIR)\core_internal_execinfo.html \ $(DOCDIR)\core_internal_hash.html \ $(DOCDIR)\core_internal_moving.html \ + $(DOCDIR)\core_internal_newaa.html \ $(DOCDIR)\core_internal_parseoptions.html \ $(DOCDIR)\core_internal_postblit.html \ $(DOCDIR)\core_internal_spinlock.html \ @@ -559,7 +560,6 @@ DOCS=\ $(DOCDIR)\rt_tlsgc.html \ $(DOCDIR)\rt_trace.html \ $(DOCDIR)\rt_tracegc.html \ - $(DOCDIR)\rt_aaA.html \ $(DOCDIR)\rt_cmath2.html \ $(DOCDIR)\rt_critical_.html \ $(DOCDIR)\rt_dmain2.html \ diff --git a/druntime/mak/SRCS b/druntime/mak/SRCS index 5da022f7d8c7..f3ca6c42cd0d 100644 --- a/druntime/mak/SRCS +++ b/druntime/mak/SRCS @@ -561,7 +561,6 @@ SRCS=\ \ src\rt\aApply.d \ src\rt\aApplyR.d \ - src\rt\aaA.d \ src\rt\alloca.d \ src\rt\arraycat.d \ src\rt\cmath2.d \ diff --git a/druntime/src/core/internal/hash.d b/druntime/src/core/internal/hash.d index 73a7020e9ec5..e5c6ab1f0f33 100644 --- a/druntime/src/core/internal/hash.d +++ b/druntime/src/core/internal/hash.d @@ -733,7 +733,7 @@ Params: dataKnownToBeAligned = whether the data is known at compile time to be uint-aligned. +/ @nogc nothrow pure @trusted -private size_t bytesHash(bool dataKnownToBeAligned)(scope const(ubyte)[] bytes, size_t seed) +private size_t _bytesHash(bool dataKnownToBeAligned)(scope const(ubyte)[] bytes, size_t seed) { auto len = bytes.length; auto data = bytes.ptr; @@ -787,6 +787,34 @@ private size_t bytesHash(bool dataKnownToBeAligned)(scope const(ubyte)[] bytes, return h1; } +// precompile bytesHash into the runtime to also get optimized versions in debug builds +@nogc nothrow pure @trusted +private size_t _bytesHashAligned(scope const(ubyte)[] bytes, size_t seed) +{ + pragma(inline, true); + return _bytesHash!true(bytes, seed); +} +@nogc nothrow pure @trusted +private size_t _bytesHashUnaligned(scope const(ubyte)[] bytes, size_t seed) +{ + pragma(inline, true); + return _bytesHash!false(bytes, seed); +} + +/+ +Params: +dataKnownToBeAligned = whether the data is known at compile time to be uint-aligned. ++/ +@nogc nothrow pure @trusted +private size_t bytesHash(bool dataKnownToBeAligned)(scope const(ubyte)[] bytes, size_t seed) +{ + pragma(inline, true); + static if (dataKnownToBeAligned) + return _bytesHashAligned(bytes, seed); + else + return _bytesHashUnaligned(bytes, seed); +} + // Check that bytesHash works with CTFE pure nothrow @system @nogc unittest { diff --git a/druntime/src/core/internal/newaa.d b/druntime/src/core/internal/newaa.d index 47283f280301..55236f498e90 100644 --- a/druntime/src/core/internal/newaa.d +++ b/druntime/src/core/internal/newaa.d @@ -1,17 +1,21 @@ /** - Turn an Associative Array into a binary compatible struct for static initialization. - - This does not implement all the pieces of - the associative array type in druntime, just enough to create an AA from an - existing range of key/value pairs. - - 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 -*/ + * 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; -import core.memory; +/// 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; @@ -31,26 +35,224 @@ 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; -private struct Bucket +/// AA wrapper +struct AA(K, V) { - size_t hash; - void *entry; + 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); } -struct Impl +// 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) +{ + 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, 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 (__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 (__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 + 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; + const(TypeInfo) entryTI; // only for binary compatibility uint firstUsed; - immutable uint keysz; - immutable uint valsz; - immutable uint valoff; - Flags flags; - size_t delegate(scope const void*) nothrow hashFn; + 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 { @@ -58,13 +260,150 @@ struct Impl 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]; + } } -private struct AAShell +//============================================================================== +// 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 { - Impl *impl; + 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 @@ -75,74 +414,612 @@ private size_t mix(size_t h) @safe pure nothrow @nogc return h; } -// create a binary-compatible AA structure that can be used directly as an -// associative array. -// NOTE: this must only be called during CTFE -AAShell makeAA(K, V)(V[K] src) @trusted +private size_t nextpow2(const size_t n) pure nothrow @nogc @safe { - assert(__ctfe, "makeAA Must only be called at compile time"); - immutable srclen = src.length; - assert(srclen <= uint.max); - alias E = TypeInfo_AssociativeArray.Entry!(K, V); - if (srclen == 0) - return AAShell.init; - // first, determine the size that would be used if we grew the bucket list - // one element at a time using the standard AA algorithm. - size_t dim = INIT_NUM_BUCKETS; - while (srclen * GROW_DEN > dim * GROW_NUM) - dim = dim * GROW_FAC; - - // used during runtime. - typeof(Impl.hashFn) hashFn = (scope const void* val) { - auto x = cast(K*)val; - return hashOf(*x); - }; + import core.bitop : bsr; - Bucket[] buckets; - // Allocate and fill the buckets - if (__ctfe) - buckets = new Bucket[dim]; + 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)(aax, key, found); +} + +/****************************** + * 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 + * 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 V.init + */ +V* _aaGetX(K, V, K2)(auto ref scope V[K] a, auto ref K2 key, out bool found) +{ + 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); + if (aa.buckets[pi].deleted) + --aa.deleted; + // check load factor and possibly grow + else if (++aa.used * GROW_DEN > aa.dim * GROW_NUM) + { + aa.grow(); + pi = aa.findSlotInsert(hash); + assert(aa.buckets[pi].empty); + } + + // update search cache and allocate entry + aa.firstUsed = min(aa.firstUsed, cast(uint)pi); + ref p = aa.buckets[pi]; + p.hash = hash; + p.entry = _newEntry!(K, V)(key2); + 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 - assert(0); + typeof([aa.buckets[0].entry.key]) res; // as mutable as it can get + res = new typeof(res[0])[aa.length]; - assert(buckets.length >= dim); + if (false) // never execute, but infer function attributes from this operation + res ~= aa.buckets[0].entry.key; - immutable mask = dim - 1; - assert((dim & mask) == 0); // must be a power of 2 + 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; +} - Bucket* findSlotInsert(immutable size_t hash) +/// 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) { - for (size_t i = hash & mask, j = 1;; ++j) + 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) { - if (buckets[i].hash == HASH_EMPTY) - return &buckets[i]; - i = (i + j) & mask; + 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; } } - uint firstUsed = cast(uint) buckets.length; - foreach (k, v; src) - { - immutable h = hashOf(k).mix | HASH_FILLED_MARK; - auto location = findSlotInsert(h); - immutable nfu = cast(uint) (location - &buckets[0]); - if (nfu < firstUsed) - firstUsed = nfu; - *location = Bucket(h, new E(k, v)); - } - - enum flags = () { - import core.internal.traits; - Impl.Flags flags; - static if (__traits(hasPostblit, K)) - flags |= flags.keyHasPostblit; - static if (hasIndirections!E) - flags |= flags.hasPointers; - return flags; - } (); - // return the new implementation - return AAShell(new Impl(buckets, cast(uint)srclen, 0, typeid(E), firstUsed, - K.sizeof, V.sizeof, E.value.offsetof, flags, hashFn)); + 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 diff --git a/druntime/src/object.d b/druntime/src/object.d index a6ede90a3574..80629ed760ac 100644 --- a/druntime/src/object.d +++ b/druntime/src/object.d @@ -1301,12 +1301,12 @@ class TypeInfo_AssociativeArray : TypeInfo override bool equals(in void* p1, in void* p2) @trusted const { - return !!_aaEqual(this, *cast(const AA*) p1, *cast(const AA*) p2); + return xopEquals(p1, p2); } override hash_t getHash(scope const void* p) nothrow @trusted const { - return _aaGetHash(cast(AA*)p, this); + return xtoHash(p); } // BUG: need to add the rest of the functions @@ -1325,16 +1325,19 @@ class TypeInfo_AssociativeArray : TypeInfo override @property uint flags() nothrow pure const { return 1; } // TypeInfo entry is generated from the type of this template to help rt/aaA.d - static struct Entry(K, V) - { - K key; - V value; - } + private static import core.internal.newaa; + alias Entry(K, V) = core.internal.newaa.Entry!(K, V); TypeInfo value; TypeInfo key; TypeInfo entry; + bool function(scope const void* p1, scope const void* p2) nothrow @safe xopEquals; + hash_t function(scope const void*) nothrow @safe xtoHash; + + alias aaOpEqual(K, V) = core.internal.newaa._aaOpEqual!(K, V); + alias aaGetHash(K, V) = core.internal.newaa._aaGetHash!(K, V); + override @property size_t talign() nothrow pure const { return (char[int]).alignof; @@ -2955,54 +2958,21 @@ class Error : Throwable } } -extern (C) -{ - // from druntime/src/rt/aaA.d - - private struct AA { void* impl; } - // size_t _aaLen(in AA aa) pure nothrow @nogc; - private void* _aaGetY(scope AA* paa, const TypeInfo_AssociativeArray ti, const size_t valsz, const scope void* pkey) pure nothrow; - private void* _aaGetX(scope AA* paa, const TypeInfo_AssociativeArray ti, const size_t valsz, const scope void* pkey, out bool found) pure nothrow; - // inout(void)* _aaGetRvalueX(inout AA aa, in TypeInfo keyti, in size_t valsz, in void* pkey); - inout(void[]) _aaValues(inout AA aa, const size_t keysz, const size_t valsz, const TypeInfo tiValueArray) pure nothrow; - inout(void[]) _aaKeys(inout AA aa, const size_t keysz, const TypeInfo tiKeyArray) pure nothrow; - void* _aaRehash(AA* paa, const scope TypeInfo keyti) pure nothrow; - void _aaClear(AA aa) pure nothrow; +public import core.internal.newaa : _d_aaIn, _d_aaDel, _d_aaNew, _d_aaEqual, _d_assocarrayliteralTX; +public import core.internal.newaa : _d_aaLen, _d_aaGetY, _d_aaGetRvalueX, _d_aaApply, _d_aaApply2; +// public import core.exception : onRangeError; // causes extra messages with -transition=fields +extern (C) noreturn _d_arraybounds(string file, uint line) @trusted pure nothrow @nogc; - // alias _dg_t = extern(D) int delegate(void*); - // int _aaApply(AA aa, size_t keysize, _dg_t dg); - - // alias _dg2_t = extern(D) int delegate(void*, void*); - // int _aaApply2(AA aa, size_t keysize, _dg2_t dg); - - private struct AARange { AA impl; size_t idx; } - AARange _aaRange(AA aa) pure nothrow @nogc @safe; - bool _aaRangeEmpty(AARange r) pure nothrow @nogc @safe; - void* _aaRangeFrontKey(AARange r) pure nothrow @nogc @safe; - void* _aaRangeFrontValue(AARange r) pure nothrow @nogc @safe; - void _aaRangePopFront(ref AARange r) pure nothrow @nogc @safe; - - int _aaEqual(scope const TypeInfo tiRaw, scope const AA aa1, scope const AA aa2); - hash_t _aaGetHash(scope const AA* aa, scope const TypeInfo tiRaw) nothrow; - - /* - _d_assocarrayliteralTX marked as pure, because aaLiteral can be called from pure code. - This is a typesystem hole, however this is existing hole. - Early compiler didn't check purity of toHash or postblit functions, if key is a UDT thus - copiler allowed to create AA literal with keys, which have impure unsafe toHash methods. - */ - void* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti, void[] keys, void[] values) pure; -} +private import core.internal.newaa; -void* aaLiteral(Key, Value)(Key[] keys, Value[] values) @trusted pure +void* aaLiteral(Key, Value)(Key[] keys, Value[] values) { - return _d_assocarrayliteralTX(typeid(Value[Key]), *cast(void[]*)&keys, *cast(void[]*)&values); + return _d_assocarrayliteralTX(keys, values); } // Lower an Associative Array to a newaa struct for static initialization. auto _aaAsStruct(K, V)(V[K] aa) @safe { - import core.internal.newaa : makeAA; assert(__ctfe); return makeAA!(K, V)(aa); } @@ -3016,13 +2986,13 @@ alias AssociativeArray(Key, Value) = Value[Key]; */ void clear(Value, Key)(Value[Key] aa) @trusted { - _aaClear(*cast(AA *) &aa); + _aaClear(aa); } /** ditto */ void clear(Value, Key)(Value[Key]* aa) @trusted { - _aaClear(*cast(AA *) aa); + (*aa).clear(); } /// @@ -3059,32 +3029,30 @@ void clear(Value, Key)(Value[Key]* aa) @trusted * aa = The associative array. * Returns: * The rehashed associative array. + * Note: + * emulated by the compiler during CTFE */ -T rehash(T : Value[Key], Value, Key)(T aa) +Value[Key] rehash(Value, Key)(Value[Key] aa) { - _aaRehash(cast(AA*)&aa, typeid(Value[Key])); - return aa; + return _aaRehash(aa); } /** ditto */ -T rehash(T : Value[Key], Value, Key)(T* aa) +Value[Key] rehash(T : Value[Key], Value, Key)(T* aa) { - _aaRehash(cast(AA*)aa, typeid(Value[Key])); - return *aa; + return (*aa).rehash(); // CTFE only intercepts the non-pointer overload } /** ditto */ -T rehash(T : shared Value[Key], Value, Key)(T aa) +Value[Key] rehash(T : shared Value[Key], Value, Key)(auto ref T aa) { - _aaRehash(cast(AA*)&aa, typeid(Value[Key])); - return aa; + return (cast(Value[Key])aa).rehash(); // CTFE only intercepts the V[K] overload } /** ditto */ -T rehash(T : shared Value[Key], Value, Key)(T* aa) +Value[Key] rehash(T : shared Value[Key], Value, Key)(T* aa) { - _aaRehash(cast(AA*)aa, typeid(Value[Key])); - return *aa; + return (cast(Value[Key])*aa).rehash(); // CTFE only intercepts the non-pointer overload } /*********************************** @@ -3092,46 +3060,20 @@ T rehash(T : shared Value[Key], Value, Key)(T* aa) * the associative array into it. * Params: * aa = The associative array. + * Note: + * emulated by the compiler during CTFE */ -V[K] dup(T : V[K], K, V)(T aa) +auto dup(T : V[K], K, V)(T aa) { - //pragma(msg, "K = ", K, ", V = ", V); - // Bug10720 - check whether V is copyable static assert(is(typeof({ V v = aa[K.init]; })), "cannot call " ~ T.stringof ~ ".dup because " ~ V.stringof ~ " is not copyable"); - V[K] result; - - //foreach (k, ref v; aa) - // result[k] = v; // Bug13701 - won't work if V is not mutable - - ref V duplicateElem(ref K k, ref const V v) @trusted pure nothrow - { - import core.stdc.string : memcpy; - - void* pv = _aaGetY(cast(AA*)&result, typeid(V[K]), V.sizeof, &k); - memcpy(pv, &v, V.sizeof); - return *cast(V*)pv; - } - - foreach (k, ref v; aa) - { - static if (!__traits(hasPostblit, V)) - duplicateElem(k, v); - else static if (__traits(isStaticArray, V)) - _doPostblit(duplicateElem(k, v)[]); - else static if (!is(typeof(v.__xpostblit())) && is(immutable V == immutable UV, UV)) - (() @trusted => *cast(UV*) &duplicateElem(k, v))().__xpostblit(); - else - duplicateElem(k, v).__xpostblit(); - } - - return result; + return _aaDup(aa); } /** ditto */ -V[K] dup(T : V[K], K, V)(T* aa) +auto dup(T : V[K], K, V)(T* aa) { return (*aa).dup; } @@ -3146,14 +3088,14 @@ V[K] dup(T : V[K], K, V)(T* aa) } // this should never be made public. -private AARange _aaToRange(T: V[K], K, V)(ref T aa) pure nothrow @nogc @safe +private auto _aaToRange(K, V)(auto ref inout V[K] aa) @trusted { - // ensure we are dealing with a genuine AA. - static if (is(const(V[K]) == const(T))) - alias realAA = aa; - else - const(V[K]) realAA = aa; - return _aaRange(() @trusted { return *cast(AA*)&realAA; } ()); + import core.internal.traits : substInout; + + alias K2 = substInout!K; + alias V2 = substInout!V; + auto aa2 = cast(V2[K2])aa; + return _aaRange(aa2); } /*********************************** @@ -3183,25 +3125,27 @@ auto byKey(T : V[K], K, V)(T aa) pure nothrow @nogc @safe { import core.internal.traits : substInout; + const(V[K]) aa2 = aa; + static struct Result { - AARange r; + typeof(_aaToRange(aa2)) r; pure nothrow @nogc: @property bool empty() @safe { return _aaRangeEmpty(r); } @property ref front() @trusted { - return *cast(substInout!K*) _aaRangeFrontKey(r); + return *cast(substInout!K*)_aaRangeFrontKey(r); } void popFront() @safe { _aaRangePopFront(r); } @property Result save() { return this; } } - return Result(_aaToRange(aa)); + return Result(_aaToRange(aa2)); } /** ditto */ -auto byKey(T : V[K], K, V)(T* aa) pure nothrow @nogc +auto byKey(K, V)(V[K]* aa) pure nothrow @nogc { return (*aa).byKey(); } @@ -3244,25 +3188,27 @@ auto byValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe { import core.internal.traits : substInout; + const(V[K]) aa2 = aa; + static struct Result { - AARange r; + typeof(_aaToRange(aa2)) r; pure nothrow @nogc: @property bool empty() @safe { return _aaRangeEmpty(r); } @property ref front() @trusted { - return *cast(substInout!V*) _aaRangeFrontValue(r); + return *cast(substInout!V*)_aaRangeFrontValue(r); } void popFront() @safe { _aaRangePopFront(r); } @property Result save() { return this; } } - return Result(_aaToRange(aa)); + return Result(_aaToRange(aa2)); } /** ditto */ -auto byValue(T : V[K], K, V)(T* aa) pure nothrow @nogc +auto byValue(K, V)(V[K]* aa) pure nothrow @nogc { return (*aa).byValue(); } @@ -3317,9 +3263,11 @@ auto byKeyValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe { import core.internal.traits : substInout; + const(V[K]) aa2 = aa; + static struct Result { - AARange r; + typeof(_aaToRange(aa2)) r; pure nothrow @nogc: @property bool empty() @safe { return _aaRangeEmpty(r); } @@ -3329,16 +3277,16 @@ auto byKeyValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe { // We save the pointers here so that the Pair we return // won't mutate when Result.popFront is called afterwards. - private void* keyp; - private void* valp; + private const(substInout!K)* keyp; + private const(substInout!V)* valp; @property ref key() inout @trusted { - return *cast(substInout!K*) keyp; + return *cast(K*)keyp; } @property ref value() inout @trusted { - return *cast(substInout!V*) valp; + return *cast(V*)valp; } } return Pair(_aaRangeFrontKey(r), @@ -3348,7 +3296,7 @@ auto byKeyValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe @property Result save() { return this; } } - return Result(_aaToRange(aa)); + return Result(_aaToRange(aa2)); } /** ditto */ @@ -3381,27 +3329,18 @@ auto byKeyValue(T : V[K], K, V)(T* aa) pure nothrow @nogc * aa = The associative array. * Returns: * A dynamic array containing a copy of the keys. + * Note: + * emulated by the compiler during CTFE */ -Key[] keys(T : Value[Key], Value, Key)(T aa) @property +auto keys(Value, Key)(inout Value[Key] aa) @property { - // ensure we are dealing with a genuine AA. - static if (is(const(Value[Key]) == const(T))) - alias realAA = aa; - else - const(Value[Key]) realAA = aa; - auto res = () @trusted { - auto a = cast(void[])_aaKeys(*cast(inout(AA)*)&realAA, Key.sizeof, typeid(Key[])); - return *cast(Key[]*)&a; - }(); - static if (__traits(hasPostblit, Key)) - _doPostblit(res); - return res; + return _aaKeys!(Key, Value)(aa); } /** ditto */ -Key[] keys(T : Value[Key], Value, Key)(T *aa) @property +auto keys(T : Value[Key], Value, Key)(T *aa) @property { - return (*aa).keys; + return (*aa).keys; // CTFE only intercepts the non-pointer overload } /// @@ -3465,27 +3404,18 @@ Key[] keys(T : Value[Key], Value, Key)(T *aa) @property * aa = The associative array. * Returns: * A dynamic array containing a copy of the values. + * Note: + * emulated by the compiler during CTFE */ -Value[] values(T : Value[Key], Value, Key)(T aa) @property +auto values(Value, Key)(inout Value[Key] aa) @property { - // ensure we are dealing with a genuine AA. - static if (is(const(Value[Key]) == const(T))) - alias realAA = aa; - else - const(Value[Key]) realAA = aa; - auto res = () @trusted { - auto a = cast(void[])_aaValues(*cast(inout(AA)*)&realAA, Key.sizeof, Value.sizeof, typeid(Value[])); - return *cast(Value[]*)&a; - }(); - static if (__traits(hasPostblit, Value)) - _doPostblit(res); - return res; + return _aaValues!(Key, Value)(aa); } /** ditto */ -Value[] values(T : Value[Key], Value, Key)(T *aa) @property +auto values(T : Value[Key], Value, Key)(T *aa) @property { - return (*aa).values; + return (*aa).values; // CTFE only intercepts the non-pointer overload } /// @@ -3585,25 +3515,11 @@ inout(V) get(K, V)(inout(V[K])* aa, K key, lazy inout(V) defaultValue) ref V require(K, V)(ref V[K] aa, K key, lazy V value = V.init) { bool found; - // if key is @safe-ly copyable, `require` can infer @safe - static if (isSafeCopyable!K) - { - auto p = () @trusted - { - return cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found); - } (); - } - else - { - auto p = cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found); - } + auto p = _aaGetX(aa, key, found); if (found) return *p; - else - { - *p = value; // Not `return (*p = value)` since if `=` is overloaded - return *p; // this might not return a ref to the left-hand side. - } + *p = value; // Not `return (*p = value)` since if `=` is overloaded + return *p; // this might not return a ref to the left-hand side. } /// @@ -3622,7 +3538,7 @@ private enum bool isSafeCopyable(T) = is(typeof(() @safe { union U { T x; } T *x * Calls `create` if `key` doesn't exist in the associative array, * otherwise calls `update`. * `create` returns a corresponding value for `key`. - * `update` accepts a key parameter. If it returns a value, the value is + * `update` accepts a value parameter. If it returns a value, the value is * set for `key`. * Params: * aa = The associative array. @@ -3630,24 +3546,13 @@ private enum bool isSafeCopyable(T) = is(typeof(() @safe { union U { T x; } T *x * create = The callable to create a value for `key`. * Must return V. * update = The callable to call if `key` exists. - * Takes a K argument, returns a V or void. + * Takes a V argument, returns a V or void. */ void update(K, V, C, U)(ref V[K] aa, K key, scope C create, scope U update) if (is(typeof(create()) : V) && (is(typeof(update(aa[K.init])) : V) || is(typeof(update(aa[K.init])) == void))) { bool found; - // if key is @safe-ly copyable, `update` may infer @safe - static if (isSafeCopyable!K) - { - auto p = () @trusted - { - return cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found); - } (); - } - else - { - auto p = cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found); - } + auto p = _aaGetX(aa, key, found); if (!found) *p = create(); else diff --git a/druntime/src/rt/aaA.d b/druntime/src/rt/aaA.d deleted file mode 100644 index 3b4bee040582..000000000000 --- a/druntime/src/rt/aaA.d +++ /dev/null @@ -1,867 +0,0 @@ -/** - * Implementation of associative arrays. - * - * Copyright: Copyright Digital Mars 2000 - 2015. - * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). - * Authors: Martin Nowak - * Source: $(DRUNTIMESRC rt/_aaA.d) - */ -module rt.aaA; - -/// AA version for debuggers, bump whenever changing the layout -extern (C) immutable int _aaVersion = 1; - -import core.memory : GC; -import core.internal.util.math : min, max; - -// 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; - -/// Opaque AA wrapper -struct AA -{ - Impl* impl; - alias impl this; - - private @property bool empty() const pure nothrow @nogc @safe - { - return impl is null || !impl.length; - } -} - -private struct Impl -{ -private: - this(scope const TypeInfo_AssociativeArray ti, size_t sz = INIT_NUM_BUCKETS) nothrow - { - keysz = cast(uint) ti.key.tsize; - valsz = cast(uint) ti.value.tsize; - buckets = allocBuckets(sz); - firstUsed = cast(uint) buckets.length; - valoff = cast(uint) talign(keysz, ti.value.talign); - hashFn = &ti.key.getHash; - - import rt.lifetime : hasPostblit, unqualify; - - if (hasPostblit(unqualify(ti.key))) - flags |= Flags.keyHasPostblit; - if ((ti.key.flags | ti.value.flags) & 1) - flags |= Flags.hasPointers; - - entryTI = ti.entry; - } - - Bucket[] buckets; - uint used; - uint deleted; - const(TypeInfo) entryTI; - uint firstUsed; - immutable uint keysz; - immutable uint valsz; - immutable uint valoff; - Flags flags; - - // function that calculates hash of a key. Set on creation - // the parameter is a pointer to the key. - size_t delegate(scope const void*) nothrow hashFn; - - enum Flags : ubyte - { - none = 0x0, - keyHasPostblit = 0x1, - hasPointers = 0x2, - } - - @property size_t length() const pure nothrow @nogc @safe - { - assert(used >= deleted); - return used - deleted; - } - - @property size_t dim() const pure nothrow @nogc @safe - { - return buckets.length; - } - - @property size_t mask() const pure nothrow @nogc - { - return dim - 1; - } - - // find the first slot to insert a value with hash - inout(Bucket)* findSlotInsert(size_t hash) inout pure nothrow @nogc - { - for (size_t i = hash & mask, j = 1;; ++j) - { - if (!buckets[i].filled) - return &buckets[i]; - i = (i + j) & mask; - } - } - - // lookup a key - inout(Bucket)* findSlotLookup(size_t hash, scope const void* pkey, scope const TypeInfo keyti) inout - { - for (size_t i = hash & mask, j = 1;; ++j) - { - if (buckets[i].hash == hash && keyti.equals(pkey, buckets[i].entry)) - return &buckets[i]; - else if (buckets[i].empty) - return null; - i = (i + j) & mask; - } - } - - void grow(scope const TypeInfo keyti) pure nothrow - { - // 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(scope const TypeInfo keyti) pure nothrow - { - if (dim > INIT_NUM_BUCKETS) - resize(dim / GROW_FAC); - } - - void resize(size_t ndim) pure nothrow - { - auto obuckets = buckets; - buckets = allocBuckets(ndim); - - foreach (ref b; obuckets[firstUsed .. $]) - if (b.filled) - *findSlotInsert(b.hash) = b; - - firstUsed = 0; - used -= deleted; - deleted = 0; - GC.free(obuckets.ptr); // safe to free b/c impossible to reference - } - - void clear() pure nothrow @trusted - { - import core.stdc.string : memset; - // clear all data, but don't change bucket array length - memset(&buckets[firstUsed], 0, (buckets.length - firstUsed) * Bucket.sizeof); - deleted = used = 0; - firstUsed = cast(uint) dim; - } -} - -//============================================================================== -// Bucket -//------------------------------------------------------------------------------ - -private struct Bucket -{ -private pure nothrow @nogc: - size_t hash; - void* entry; - - @property bool empty() const - { - return hash == HASH_EMPTY; - } - - @property bool deleted() const - { - return hash == HASH_DELETED; - } - - @property bool filled() const @safe - { - return cast(ptrdiff_t) hash < 0; - } -} - -Bucket[] allocBuckets(size_t dim) @trusted pure nothrow -{ - enum attr = GC.BlkAttr.NO_INTERIOR; - immutable sz = dim * Bucket.sizeof; - return (cast(Bucket*) GC.calloc(sz, attr))[0 .. dim]; -} - -//============================================================================== -// Entry -//------------------------------------------------------------------------------ - -private void* allocEntry(scope const Impl* aa, scope const void* pkey) -{ - import rt.lifetime : _d_newitemU; - import core.stdc.string : memcpy, memset; - - immutable akeysz = aa.valoff; - void* res = void; - if (aa.entryTI) - res = _d_newitemU(aa.entryTI); - else - { - auto flags = (aa.flags & Impl.Flags.hasPointers) ? 0 : GC.BlkAttr.NO_SCAN; - res = GC.malloc(akeysz + aa.valsz, flags); - } - - memcpy(res, pkey, aa.keysz); // copy key - memset(res + akeysz, 0, aa.valsz); // zero value - - return res; -} - -private bool hasDtor(const TypeInfo ti) pure nothrow -{ - import rt.lifetime : unqualify; - - if (typeid(ti) is typeid(TypeInfo_Struct)) - if ((cast(TypeInfo_Struct) cast(void*) ti).xdtor) - return true; - if (typeid(ti) is typeid(TypeInfo_StaticArray)) - return hasDtor(unqualify(ti.next)); - - return false; -} - -private immutable(void)* getRTInfo(const TypeInfo ti) pure nothrow -{ - // classes are references - const isNoClass = ti && typeid(ti) !is typeid(TypeInfo_Class); - return isNoClass ? ti.rtInfo() : rtinfoHasPointers; -} - -unittest -{ - void test(K, V)() - { - static struct Entry - { - K key; - V val; - } - auto keyti = typeid(K); - auto valti = typeid(V); - auto valrti = getRTInfo(valti); - auto keyrti = getRTInfo(keyti); - - auto impl = new Impl(typeid(V[K])); - if (valrti is rtinfoNoPointers && keyrti is rtinfoNoPointers) - { - assert(!(impl.flags & Impl.Flags.hasPointers)); - } - else if (valrti is rtinfoHasPointers && keyrti is rtinfoHasPointers) - { - assert(impl.flags & Impl.Flags.hasPointers); - } - else - { - auto rtInfo = cast(size_t*) impl.entryTI.rtInfo(); - auto refInfo = cast(size_t*) typeid(Entry).rtInfo(); - assert(rtInfo[0] == refInfo[0]); // size - enum bytesPerWord = 8 * size_t.sizeof * (void*).sizeof; - size_t words = (rtInfo[0] + bytesPerWord - 1) / bytesPerWord; - foreach (i; 0 .. words) - assert(rtInfo[1 + i] == refInfo[i + 1]); - } - } - test!(long, int)(); - test!(string, string); - test!(ubyte[16], Object); - - static struct Small - { - ubyte[16] guid; - string name; - } - test!(string, Small); - - static struct Large - { - ubyte[1024] data; - string[412] names; - ubyte[1024] moredata; - } - version (OnlyLowMemUnittests) {} else - test!(Large, Large); -} - -//============================================================================== -// 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 calcHash(scope const void *pkey, scope const Impl* impl) nothrow -{ - immutable hash = impl.hashFn(pkey); - // highest bit is set to distinguish empty/deleted from filled buckets - return mix(hash) | HASH_FILLED_MARK; -} - -private size_t nextpow2(const size_t n) pure nothrow @nogc -{ - 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. - * Params: - * ti = TypeInfo for the associative array - * Returns: - * A new associative array. - */ -extern (C) Impl* _aaNew(const TypeInfo_AssociativeArray ti) -{ - return new Impl(ti); -} - -/// Determine number of entries in associative array. -extern (C) size_t _aaLen(scope const AA aa) pure nothrow @nogc -{ - return aa ? aa.length : 0; -} - -/****************************** - * Lookup *pkey in aa. - * Called only from implementation of (aa[key]) expressions when value is mutable. - * Params: - * paa = associative array opaque pointer - * ti = TypeInfo for the associative array - * valsz = ignored - * pkey = pointer to the key value - * 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 all zeros - */ -extern (C) void* _aaGetY(scope AA* paa, const TypeInfo_AssociativeArray ti, - const size_t valsz, scope const void* pkey) -{ - bool found; - return _aaGetX(paa, ti, valsz, pkey, found); -} - -/****************************** - * Lookup *pkey in aa. - * Called only from implementation of require - * Params: - * paa = associative array opaque pointer - * ti = TypeInfo for the associative array - * valsz = ignored - * pkey = pointer to the key value - * found = true if the value was found - * 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 all zeros - */ -extern (C) void* _aaGetX(scope AA* paa, const TypeInfo_AssociativeArray ti, - const size_t valsz, scope const void* pkey, out bool found) -{ - // lazily alloc implementation - AA aa = *paa; - if (aa is null) - { - aa = new Impl(ti); - *paa = aa; - } - - // get hash and bucket for key - immutable hash = calcHash(pkey, aa); - - // found a value => return it - if (auto p = aa.findSlotLookup(hash, pkey, ti.key)) - { - found = true; - return p.entry + aa.valoff; - } - - auto p = aa.findSlotInsert(hash); - if (p.deleted) - --aa.deleted; - // check load factor and possibly grow - else if (++aa.used * GROW_DEN > aa.dim * GROW_NUM) - { - aa.grow(ti.key); - p = aa.findSlotInsert(hash); - assert(p.empty); - } - - // update search cache and allocate entry - aa.firstUsed = min(aa.firstUsed, cast(uint)(p - aa.buckets.ptr)); - p.hash = hash; - p.entry = allocEntry(aa, pkey); - // postblit for key - if (aa.flags & Impl.Flags.keyHasPostblit) - { - import rt.lifetime : __doPostblit, unqualify; - - __doPostblit(p.entry, aa.keysz, unqualify(ti.key)); - } - // return pointer to value - return p.entry + aa.valoff; -} - -/****************************** - * Lookup *pkey in aa. - * Called only from implementation of (aa[key]) expressions when value is not mutable. - * Params: - * aa = associative array opaque pointer - * keyti = TypeInfo for the key - * valsz = ignored - * pkey = pointer to the key value - * Returns: - * pointer to value if present, null otherwise - */ -extern (C) inout(void)* _aaGetRvalueX(inout AA aa, scope const TypeInfo keyti, const size_t valsz, - scope const void* pkey) -{ - return _aaInX(aa, keyti, pkey); -} - -/****************************** - * Lookup *pkey in aa. - * Called only from implementation of (key in aa) expressions. - * Params: - * aa = associative array opaque pointer - * keyti = TypeInfo for the key - * pkey = pointer to the key value - * Returns: - * pointer to value if present, null otherwise - */ -extern (C) inout(void)* _aaInX(inout AA aa, scope const TypeInfo keyti, scope const void* pkey) -{ - if (aa.empty) - return null; - - immutable hash = calcHash(pkey, aa); - if (auto p = aa.findSlotLookup(hash, pkey, keyti)) - return p.entry + aa.valoff; - return null; -} - -/// Delete entry scope const AA, return true if it was present -extern (C) bool _aaDelX(AA aa, scope const TypeInfo keyti, scope const void* pkey) -{ - if (aa.empty) - return false; - - immutable hash = calcHash(pkey, aa); - if (auto p = aa.findSlotLookup(hash, pkey, keyti)) - { - // 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 && !GC.inFinalizer()) - aa.shrink(keyti); - - return true; - } - return false; -} - -/// Remove all elements from AA. -extern (C) void _aaClear(AA aa) pure nothrow @safe -{ - if (!aa.empty) - { - aa.clear(); - } -} - -/// Rehash AA -extern (C) void* _aaRehash(AA* paa, scope const TypeInfo keyti) pure nothrow -{ - AA aa = *paa; - if (!aa.empty) - aa.resize(nextpow2(INIT_DEN * aa.length / INIT_NUM)); - return aa; -} - -/// Return a GC allocated array of all values -extern (C) inout(void[]) _aaValues(inout AA aa, const size_t keysz, const size_t valsz, - const TypeInfo tiValueArray) pure nothrow -{ - if (aa.empty) - return null; - - import rt.lifetime : _d_newarrayU; - - auto res = _d_newarrayU(tiValueArray, aa.length).ptr; - auto pval = res; - - immutable off = aa.valoff; - foreach (b; aa.buckets[aa.firstUsed .. $]) - { - if (!b.filled) - continue; - pval[0 .. valsz] = cast(void[]) b.entry[off .. valsz + off]; - pval += valsz; - } - // postblit is done in object.values - return (cast(inout(void)*) res)[0 .. aa.length]; // fake length, return number of elements -} - -/// Return a GC allocated array of all keys -extern (C) inout(void[]) _aaKeys(inout AA aa, const size_t keysz, const TypeInfo tiKeyArray) pure nothrow -{ - if (aa.empty) - return null; - - import rt.lifetime : _d_newarrayU; - - auto res = _d_newarrayU(tiKeyArray, aa.length).ptr; - auto pkey = res; - - foreach (b; aa.buckets[aa.firstUsed .. $]) - { - if (!b.filled) - continue; - pkey[0 .. keysz] = cast(void[]) b.entry[0 .. keysz]; - pkey += keysz; - } - // postblit is done in object.keys - return (cast(inout(void)*) res)[0 .. aa.length]; // fake length, return number of elements -} - -// opApply callbacks are extern(D) -extern (D) alias dg_t = int delegate(void*); -extern (D) alias dg2_t = int delegate(void*, void*); - -/// foreach opApply over all values -extern (C) int _aaApply(AA aa, const size_t keysz, dg_t dg) -{ - if (aa.empty) - return 0; - - immutable off = aa.valoff; - foreach (b; aa.buckets) - { - if (!b.filled) - continue; - if (auto res = dg(b.entry + off)) - return res; - } - return 0; -} - -/// foreach opApply over all key/value pairs -extern (C) int _aaApply2(AA aa, const size_t keysz, dg2_t dg) -{ - if (aa.empty) - return 0; - - immutable off = aa.valoff; - foreach (b; aa.buckets) - { - if (!b.filled) - continue; - if (auto res = dg(b.entry, b.entry + off)) - return res; - } - return 0; -} - -/** Construct an associative array of type ti from corresponding keys and values. - * Called for an AA literal `[k1:v1, k2:v2]`. - * Params: - * ti = TypeInfo for the associative array - * keys = array of keys - * vals = array of values - * Returns: - * A new associative array opaque pointer, or null if `keys` is empty. - */ -extern (C) Impl* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti, void[] keys, - void[] vals) -{ - assert(keys.length == vals.length); - - immutable keysz = ti.key.tsize; - immutable valsz = ti.value.tsize; - immutable length = keys.length; - - if (!length) - return null; - - auto aa = new Impl(ti, nextpow2(INIT_DEN * length / INIT_NUM)); - - void* pkey = keys.ptr; - void* pval = vals.ptr; - immutable off = aa.valoff; - uint actualLength = 0; - foreach (_; 0 .. length) - { - immutable hash = calcHash(pkey, aa); - - auto p = aa.findSlotLookup(hash, pkey, ti.key); - if (p is null) - { - p = aa.findSlotInsert(hash); - p.hash = hash; - p.entry = allocEntry(aa, pkey); // move key, no postblit - aa.firstUsed = min(aa.firstUsed, cast(uint)(p - aa.buckets.ptr)); - actualLength++; - } - else if (aa.entryTI && hasDtor(ti.value)) - { - // destroy existing value before overwriting it - ti.value.destroy(p.entry + off); - } - // set hash and blit value - auto pdst = p.entry + off; - pdst[0 .. valsz] = pval[0 .. valsz]; // move value, no postblit - - pkey += keysz; - pval += valsz; - } - aa.used = actualLength; - return aa; -} - -/// compares 2 AAs for equality -extern (C) int _aaEqual(scope const TypeInfo tiRaw, scope const AA aa1, scope const AA aa2) -{ - if (aa1 is aa2) - return true; - - immutable len = _aaLen(aa1); - if (len != _aaLen(aa2)) - return false; - - if (!len) // both empty - return true; - - import rt.lifetime : unqualify; - - auto uti = unqualify(tiRaw); - auto ti = *cast(TypeInfo_AssociativeArray*)&uti; - // compare the entries - immutable off = aa1.valoff; - foreach (b1; aa1.buckets) - { - if (!b1.filled) - continue; - auto pb2 = aa2.findSlotLookup(b1.hash, b1.entry, ti.key); - if (pb2 is null || !ti.value.equals(b1.entry + off, pb2.entry + off)) - return false; - } - return true; -} - -/// compute a hash -extern (C) hash_t _aaGetHash(scope const AA* paa, scope const TypeInfo tiRaw) nothrow -{ - const AA aa = *paa; - - if (aa.empty) - return 0; - - import rt.lifetime : unqualify; - - auto uti = unqualify(tiRaw); - auto ti = *cast(TypeInfo_AssociativeArray*)&uti; - immutable off = aa.valoff; - auto keyHash = &ti.key.getHash; - auto valHash = &ti.value.getHash; - - size_t h; - foreach (b; aa.buckets) - { - // use addition here, so that hash is independent of element order - if (b.filled) - h += hashOf(valHash(b.entry + off), keyHash(b.entry)); - } - - return h; -} - -/** - * _aaRange implements a ForwardRange - */ -struct Range -{ - Impl* impl; - size_t idx; - alias impl this; -} - -extern (C) pure nothrow @nogc @safe -{ - Range _aaRange(return scope AA aa) - { - if (!aa) - return Range(); - - foreach (i; aa.firstUsed .. aa.dim) - { - if (aa.buckets[i].filled) - return Range(aa, i); - } - return Range(aa, aa.dim); - } - - bool _aaRangeEmpty(Range r) - { - return r.impl is null || r.idx >= r.dim; - } - - void* _aaRangeFrontKey(Range r) - { - assert(!_aaRangeEmpty(r)); - if (r.idx >= r.dim) - return null; - return r.buckets[r.idx].entry; - } - - void* _aaRangeFrontValue(Range r) - { - assert(!_aaRangeEmpty(r)); - if (r.idx >= r.dim) - return null; - - auto entry = r.buckets[r.idx].entry; - return entry is null ? - null : - (() @trusted { return entry + r.valoff; } ()); - } - - void _aaRangePopFront(ref Range r) - { - if (r.idx >= r.dim) return; - for (++r.idx; r.idx < r.dim; ++r.idx) - { - if (r.buckets[r.idx].filled) - break; - } - } -} - -// Most tests are now in test_aa.d - -// test postblit for AA literals -unittest -{ - 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 == 0 && T.postblit == 2); - aa1[0] = t; - assert(T.dtor == 1 && T.postblit == 3); - - T.dtor = 0; - T.postblit = 0; - - auto aa2 = [0 : t, 1 : t, 0 : t]; // literal with duplicate key => value overwritten - assert(T.dtor == 1 && T.postblit == 3); - - T.dtor = 0; - T.postblit = 0; - - auto aa3 = [t : 0]; - assert(T.dtor == 0 && T.postblit == 1); - aa3[t] = 1; - assert(T.dtor == 0 && T.postblit == 1); - aa3.remove(t); - assert(T.dtor == 0 && T.postblit == 1); - aa3[t] = 2; - assert(T.dtor == 0 && T.postblit == 2); - - // 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 == 6 && T.postblit == 2); -} - -// Ensure the newaa struct layout (used for static initialization) is in sync -unittest -{ - import newaa = core.internal.newaa; - static assert(newaa.Impl.sizeof == Impl.sizeof); - // ensure compatible types and offsets - static foreach (i; 0 .. Impl.tupleof.length) - { - // for bucket array and Flags, we "compatible" types, not exactly the same types. - static if (__traits(identifier, Impl.tupleof[i]) == "buckets" - || __traits(identifier, Impl.tupleof[i]) == "flags") - static assert(Impl.tupleof[i].sizeof == newaa.Impl.tupleof[i].sizeof); - else - static assert(is(typeof(Impl.tupleof[i]) == typeof(newaa.Impl.tupleof[i]))); - - static assert(Impl.tupleof[i].offsetof == newaa.Impl.tupleof[i].offsetof); - } -} diff --git a/druntime/src/rt/tracegc.d b/druntime/src/rt/tracegc.d index 58c604503152..30cebd66f13d 100644 --- a/druntime/src/rt/tracegc.d +++ b/druntime/src/rt/tracegc.d @@ -20,8 +20,6 @@ extern (C) void _d_callinterfacefinalizer(void *p); extern (C) void _d_delclass(Object* p); extern (C) void _d_delinterface(void** p); extern (C) void _d_delmemory(void* *p); -extern (C) void* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti, - void[] keys, void[] vals); extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c); extern (C) void[] _d_arrayappendwd(ref byte[] x, dchar c); extern (C) void* _d_allocmemory(size_t sz); diff --git a/druntime/test/aa/src/test_aa.d b/druntime/test/aa/src/test_aa.d index 5c3ba05d83d3..4f8cac52d3c1 100644 --- a/druntime/test/aa/src/test_aa.d +++ b/druntime/test/aa/src/test_aa.d @@ -41,6 +41,8 @@ void main() testTombstonePurging(); testClear(); testTypeInfoCollect(); + testNew(); + testAliasThis(); } void testKeysValues1() @@ -947,3 +949,42 @@ void testTypeInfoCollect() s = null; // clear any reference to the entry GC.collect(); // used to segfault. } + +void testNew() +{ + auto aa = new long[int]; // call _d_newAA + assert(aa.length == 0); + foreach (i; 0 .. 100) + aa[i] = i * 2; + assert(aa.length == 100); + + // not supported in CTFE (it doesn't do much anyway): + // static auto aa = new long[int]; +} + +void testAliasThis() +{ + static struct S + { + __gshared int numCopies; + + ubyte[long] aa; + S* next; + + this(this) { numCopies++; } + + alias aa this; + } + S s; + long key = 1; + s.aa[1] = 1; // create and insert + assert(S.numCopies == 0); + if (auto p = 1 in s) + *p = 2; + if (auto p = key in s) + *p = 3; + if (auto p = () { return 1; }() in s) + *p = 4; + s.remove(1); + assert(S.numCopies == 0); +} diff --git a/druntime/test/profile/myprofilegc.log.freebsd.32.exp b/druntime/test/profile/myprofilegc.log.freebsd.32.exp index c610fbdbd728..bdf8dd0a9b20 100644 --- a/druntime/test/profile/myprofilegc.log.freebsd.32.exp +++ b/druntime/test/profile/myprofilegc.log.freebsd.32.exp @@ -1,5 +1,4 @@ bytes allocated, allocations, type, function, file:line - 160 1 immutable(char)[][int] profilegc.main src/profilegc.d:23 128 1 float profilegc.main src/profilegc.d:18 128 1 int profilegc.main src/profilegc.d:15 64 1 double[] profilegc.main src/profilegc.d:56 diff --git a/druntime/test/profile/myprofilegc.log.freebsd.64.exp b/druntime/test/profile/myprofilegc.log.freebsd.64.exp index 7cc4c6de6619..0a882256c9de 100644 --- a/druntime/test/profile/myprofilegc.log.freebsd.64.exp +++ b/druntime/test/profile/myprofilegc.log.freebsd.64.exp @@ -1,5 +1,4 @@ bytes allocated, allocations, type, function, file:line - 320 1 immutable(char)[][int] profilegc.main src/profilegc.d:23 160 1 float profilegc.main src/profilegc.d:18 160 1 int profilegc.main src/profilegc.d:15 64 1 double[] profilegc.main src/profilegc.d:56 diff --git a/druntime/test/profile/myprofilegc.log.linux.32.exp b/druntime/test/profile/myprofilegc.log.linux.32.exp index c610fbdbd728..bdf8dd0a9b20 100644 --- a/druntime/test/profile/myprofilegc.log.linux.32.exp +++ b/druntime/test/profile/myprofilegc.log.linux.32.exp @@ -1,5 +1,4 @@ bytes allocated, allocations, type, function, file:line - 160 1 immutable(char)[][int] profilegc.main src/profilegc.d:23 128 1 float profilegc.main src/profilegc.d:18 128 1 int profilegc.main src/profilegc.d:15 64 1 double[] profilegc.main src/profilegc.d:56 diff --git a/druntime/test/profile/myprofilegc.log.linux.64.exp b/druntime/test/profile/myprofilegc.log.linux.64.exp index 7cc4c6de6619..0a882256c9de 100644 --- a/druntime/test/profile/myprofilegc.log.linux.64.exp +++ b/druntime/test/profile/myprofilegc.log.linux.64.exp @@ -1,5 +1,4 @@ bytes allocated, allocations, type, function, file:line - 320 1 immutable(char)[][int] profilegc.main src/profilegc.d:23 160 1 float profilegc.main src/profilegc.d:18 160 1 int profilegc.main src/profilegc.d:15 64 1 double[] profilegc.main src/profilegc.d:56 diff --git a/druntime/test/profile/myprofilegc.log.osx.32.exp b/druntime/test/profile/myprofilegc.log.osx.32.exp index e5db24779eed..e7afb11325ab 100644 --- a/druntime/test/profile/myprofilegc.log.osx.32.exp +++ b/druntime/test/profile/myprofilegc.log.osx.32.exp @@ -1,5 +1,4 @@ bytes allocated, allocations, type, function, file:line - 160 1 immutable(char)[][int] profilegc.main src/profilegc.d:23 128 1 float profilegc.main src/profilegc.d:18 128 1 int profilegc.main src/profilegc.d:15 64 1 float[] profilegc.main src/profilegc.d:42 diff --git a/druntime/test/profile/myprofilegc.log.osx.64.exp b/druntime/test/profile/myprofilegc.log.osx.64.exp index 7cc4c6de6619..0a882256c9de 100644 --- a/druntime/test/profile/myprofilegc.log.osx.64.exp +++ b/druntime/test/profile/myprofilegc.log.osx.64.exp @@ -1,5 +1,4 @@ bytes allocated, allocations, type, function, file:line - 320 1 immutable(char)[][int] profilegc.main src/profilegc.d:23 160 1 float profilegc.main src/profilegc.d:18 160 1 int profilegc.main src/profilegc.d:15 64 1 double[] profilegc.main src/profilegc.d:56 diff --git a/druntime/test/profile/myprofilegc.log.windows.32.exp b/druntime/test/profile/myprofilegc.log.windows.32.exp index 91cfedb3588c..7c2fb1ae70d2 100644 --- a/druntime/test/profile/myprofilegc.log.windows.32.exp +++ b/druntime/test/profile/myprofilegc.log.windows.32.exp @@ -1,5 +1,4 @@ bytes allocated, allocations, type, function, file:line - 160 1 immutable(char)[][int] profilegc.main src\profilegc.d:23 128 1 float profilegc.main src\profilegc.d:18 128 1 int profilegc.main src\profilegc.d:15 64 1 double[] profilegc.main src\profilegc.d:56 diff --git a/druntime/test/profile/myprofilegc.log.windows.64.exp b/druntime/test/profile/myprofilegc.log.windows.64.exp index 80fff9431139..6156989e1224 100644 --- a/druntime/test/profile/myprofilegc.log.windows.64.exp +++ b/druntime/test/profile/myprofilegc.log.windows.64.exp @@ -1,17 +1,24 @@ bytes allocated, allocations, type, function, file:line - 320 1 immutable(char)[][int] profilegc.main src\profilegc.d:23 + 4304 1 Bucket!(int, string) core.internal.newaa.allocBuckets!(int, string).allocBuckets ../../src\core\internal\newaa.d:264 160 1 float profilegc.main src\profilegc.d:18 160 1 int profilegc.main src\profilegc.d:15 + 96 3 Entry!(int, string) core.internal.newaa._d_assocarrayliteralTX!(int, string)._d_assocarrayliteralTX ../../src\core\internal\newaa.d:636 + 96 3 core.internal.newaa.Entry!(int, immutable(char)[]).Entry core.lifetime._d_newitemT!(Entry!(int, string))._d_newitemT ../../src\core\lifetime.d:2835 + 96 1 Impl!(int, string) core.internal.newaa._d_assocarrayliteralTX!(int, string)._d_assocarrayliteralTX ../../src\core\internal\newaa.d:621 + 96 1 core.internal.newaa.Impl!(int, immutable(char)[]).Impl core.lifetime._d_newitemT!(Impl!(int, string))._d_newitemT ../../src\core\lifetime.d:2835 64 1 double[] profilegc.main src\profilegc.d:56 48 1 float[] profilegc.main src\profilegc.d:42 48 1 int[] profilegc.main src\profilegc.d:41 32 1 C profilegc.main src\profilegc.d:12 + 32 1 profilegc.main.C core.lifetime._d_newclassT!(C)._d_newclassT ../../src\core\lifetime.d:2763 32 1 void[] profilegc.main src\profilegc.d:55 16 1 char[] profilegc.main src\profilegc.d:34 16 1 char[] profilegc.main src\profilegc.d:36 16 1 closure profilegc.main.foo src\profilegc.d:45 + 16 1 float core.lifetime._d_newitemT!float._d_newitemT ../../src\core\lifetime.d:2835 16 1 float profilegc.main src\profilegc.d:16 16 1 float profilegc.main src\profilegc.d:17 + 16 1 int core.lifetime._d_newitemT!int._d_newitemT ../../src\core\lifetime.d:2835 16 1 int profilegc.main src\profilegc.d:13 16 1 int profilegc.main src\profilegc.d:14 16 1 int[] profilegc.main src\profilegc.d:22