1 /** 2 * Struct and union declarations. 3 * 4 * Specification: $(LINK2 https://dlang.org/spec/struct.html, Structs, Unions) 5 * 6 * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved 7 * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) 8 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 9 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dstruct.d, _dstruct.d) 10 * Documentation: https://dlang.org/phobos/dmd_dstruct.html 11 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dstruct.d 12 */ 13 14 module dmd.dstruct; 15 16 import core.stdc.stdio; 17 18 import dmd.aggregate; 19 import dmd.arraytypes; 20 import dmd.astenums; 21 import dmd.attrib; 22 import dmd.declaration; 23 import dmd.dmodule; 24 import dmd.dscope; 25 import dmd.dsymbol; 26 import dmd.dsymbolsem; 27 import dmd.dtemplate; 28 import dmd.errors; 29 import dmd.expression; 30 import dmd.func; 31 import dmd.globals; 32 import dmd.id; 33 import dmd.identifier; 34 import dmd.location; 35 import dmd.mtype; 36 import dmd.opover; 37 import dmd.target; 38 import dmd.tokens; 39 import dmd.typesem; 40 import dmd.typinf; 41 import dmd.visitor; 42 43 /*************************************** 44 * Search sd for a member function of the form: 45 * `extern (D) string toString();` 46 * Params: 47 * sd = struct declaration to search 48 * Returns: 49 * FuncDeclaration of `toString()` if found, `null` if not 50 */ 51 extern (C++) FuncDeclaration search_toString(StructDeclaration sd) 52 { 53 Dsymbol s = search_function(sd, Id.tostring); 54 FuncDeclaration fd = s ? s.isFuncDeclaration() : null; 55 if (fd) 56 { 57 __gshared TypeFunction tftostring; 58 if (!tftostring) 59 { 60 tftostring = new TypeFunction(ParameterList(), Type.tstring, LINK.d); 61 tftostring = tftostring.merge().toTypeFunction(); 62 } 63 fd = fd.overloadExactMatch(tftostring); 64 } 65 return fd; 66 } 67 68 /*************************************** 69 * Request additional semantic analysis for TypeInfo generation. 70 * Params: 71 * sc = context 72 * t = type that TypeInfo is being generated for 73 */ 74 extern (D) void semanticTypeInfo(Scope* sc, Type t) 75 { 76 if (sc) 77 { 78 if (sc.intypeof) 79 return; 80 if (!sc.needsCodegen()) 81 return; 82 } 83 84 if (!t) 85 return; 86 87 void visitVector(TypeVector t) 88 { 89 semanticTypeInfo(sc, t.basetype); 90 } 91 92 void visitAArray(TypeAArray t) 93 { 94 semanticTypeInfo(sc, t.index); 95 semanticTypeInfo(sc, t.next); 96 } 97 98 void visitStruct(TypeStruct t) 99 { 100 //printf("semanticTypeInfo.visit(TypeStruct = %s)\n", t.toChars()); 101 StructDeclaration sd = t.sym; 102 103 /* Step 1: create TypeInfoDeclaration 104 */ 105 if (!sc) // inline may request TypeInfo. 106 { 107 Scope scx; 108 scx.eSink = global.errorSink; 109 scx._module = sd.getModule(); 110 getTypeInfoType(sd.loc, t, &scx); 111 sd.requestTypeInfo = true; 112 } 113 else if (!sc.minst) 114 { 115 // don't yet have to generate TypeInfo instance if 116 // the typeid(T) expression exists in speculative scope. 117 } 118 else 119 { 120 getTypeInfoType(sd.loc, t, sc); 121 sd.requestTypeInfo = true; 122 123 // https://issues.dlang.org/show_bug.cgi?id=15149 124 // if the typeid operand type comes from a 125 // result of auto function, it may be yet speculative. 126 // unSpeculative(sc, sd); 127 } 128 129 /* Step 2: If the TypeInfo generation requires sd.semantic3, run it later. 130 * This should be done even if typeid(T) exists in speculative scope. 131 * Because it may appear later in non-speculative scope. 132 */ 133 if (!sd.members) 134 return; // opaque struct 135 if (!sd.xeq && !sd.xcmp && !sd.postblit && !sd.tidtor && !sd.xhash && !search_toString(sd)) 136 return; // none of TypeInfo-specific members 137 138 // If the struct is in a non-root module, run semantic3 to get 139 // correct symbols for the member function. 140 if (sd.semanticRun >= PASS.semantic3) 141 { 142 // semantic3 is already done 143 } 144 else if (TemplateInstance ti = sd.isInstantiated()) 145 { 146 if (ti.minst && !ti.minst.isRoot()) 147 Module.addDeferredSemantic3(sd); 148 } 149 else 150 { 151 if (sd.inNonRoot()) 152 { 153 //printf("deferred sem3 for TypeInfo - sd = %s, inNonRoot = %d\n", sd.toChars(), sd.inNonRoot()); 154 Module.addDeferredSemantic3(sd); 155 } 156 } 157 } 158 159 void visitTuple(TypeTuple t) 160 { 161 if (t.arguments) 162 { 163 foreach (arg; *t.arguments) 164 { 165 semanticTypeInfo(sc, arg.type); 166 } 167 } 168 } 169 170 /* Note structural similarity of this Type walker to that in isSpeculativeType() 171 */ 172 173 Type tb = t.toBasetype(); 174 switch (tb.ty) 175 { 176 case Tvector: visitVector(tb.isTypeVector()); break; 177 case Taarray: visitAArray(tb.isTypeAArray()); break; 178 case Tstruct: visitStruct(tb.isTypeStruct()); break; 179 case Ttuple: visitTuple (tb.isTypeTuple()); break; 180 181 case Tclass: 182 case Tenum: break; 183 184 default: semanticTypeInfo(sc, tb.nextOf()); break; 185 } 186 } 187 188 enum StructFlags : int 189 { 190 none = 0x0, 191 hasPointers = 0x1, // NB: should use noPointers as in ClassFlags 192 } 193 194 /*********************************************************** 195 * All `struct` declarations are an instance of this. 196 */ 197 extern (C++) class StructDeclaration : AggregateDeclaration 198 { 199 FuncDeclarations postblits; // Array of postblit functions 200 FuncDeclaration postblit; // aggregate postblit 201 202 FuncDeclaration xeq; // TypeInfo_Struct.xopEquals 203 FuncDeclaration xcmp; // TypeInfo_Struct.xopCmp 204 FuncDeclaration xhash; // TypeInfo_Struct.xtoHash 205 extern (C++) __gshared FuncDeclaration xerreq; // object.xopEquals 206 extern (C++) __gshared FuncDeclaration xerrcmp; // object.xopCmp 207 208 // ABI-specific type(s) if the struct can be passed in registers 209 TypeTuple argTypes; 210 211 structalign_t alignment; // alignment applied outside of the struct 212 ThreeState ispod; // if struct is POD 213 214 // `bool` fields that are compacted into bit fields in a string mixin 215 private extern (D) static struct BitFields 216 { 217 bool zeroInit; // !=0 if initialize with 0 fill 218 bool hasIdentityAssign; // true if has identity opAssign 219 bool hasBlitAssign; // true if opAssign is a blit 220 bool hasIdentityEquals; // true if has identity opEquals 221 bool hasNoFields; // has no fields 222 bool hasCopyCtor; // copy constructor 223 bool hasPointerField; // members with indirections 224 bool hasVoidInitPointers; // void-initialized unsafe fields 225 bool hasSystemFields; // @system members 226 bool hasFieldWithInvariant; // invariants 227 bool computedTypeProperties;// the above 3 fields are computed 228 // Even if struct is defined as non-root symbol, some built-in operations 229 // (e.g. TypeidExp, NewExp, ArrayLiteralExp, etc) request its TypeInfo. 230 // For those, today TypeInfo_Struct is generated in COMDAT. 231 bool requestTypeInfo; 232 } 233 234 import dmd.common.bitfields : generateBitFields; 235 mixin(generateBitFields!(BitFields, ushort)); 236 237 extern (D) this(const ref Loc loc, Identifier id, bool inObject) 238 { 239 super(loc, id); 240 zeroInit = false; // assume false until we do semantic processing 241 ispod = ThreeState.none; 242 // For forward references 243 type = new TypeStruct(this); 244 245 if (inObject) 246 { 247 if (id == Id.ModuleInfo && !Module.moduleinfo) 248 Module.moduleinfo = this; 249 } 250 } 251 252 static StructDeclaration create(const ref Loc loc, Identifier id, bool inObject) 253 { 254 return new StructDeclaration(loc, id, inObject); 255 } 256 257 override StructDeclaration syntaxCopy(Dsymbol s) 258 { 259 StructDeclaration sd = 260 s ? cast(StructDeclaration)s 261 : new StructDeclaration(loc, ident, false); 262 ScopeDsymbol.syntaxCopy(sd); 263 return sd; 264 } 265 266 override final Dsymbol search(const ref Loc loc, Identifier ident, int flags = SearchLocalsOnly) 267 { 268 //printf("%s.StructDeclaration::search('%s', flags = x%x)\n", toChars(), ident.toChars(), flags); 269 if (_scope && !symtab) 270 dsymbolSemantic(this, _scope); 271 272 if (!members || !symtab) // opaque or semantic() is not yet called 273 { 274 // .stringof is always defined (but may be hidden by some other symbol) 275 if(ident != Id.stringof && !(flags & IgnoreErrors) && semanticRun < PASS.semanticdone) 276 .error(loc, "%s `%s` is forward referenced when looking for `%s`", kind, toPrettyChars, ident.toChars()); 277 return null; 278 } 279 280 return ScopeDsymbol.search(loc, ident, flags); 281 } 282 283 override const(char)* kind() const 284 { 285 return "struct"; 286 } 287 288 override final void finalizeSize() 289 { 290 //printf("StructDeclaration::finalizeSize() %s, sizeok = %d\n", toChars(), sizeok); 291 assert(sizeok != Sizeok.done); 292 293 if (sizeok == Sizeok.inProcess) 294 { 295 return; 296 } 297 sizeok = Sizeok.inProcess; 298 299 //printf("+StructDeclaration::finalizeSize() %s, fields.length = %d, sizeok = %d\n", toChars(), fields.length, sizeok); 300 301 fields.setDim(0); // workaround 302 303 // Set the offsets of the fields and determine the size of the struct 304 FieldState fieldState; 305 bool isunion = isUnionDeclaration() !is null; 306 for (size_t i = 0; i < members.length; i++) 307 { 308 Dsymbol s = (*members)[i]; 309 s.setFieldOffset(this, fieldState, isunion); 310 } 311 if (type.ty == Terror) 312 { 313 errors = true; 314 return; 315 } 316 317 if (structsize == 0) 318 { 319 hasNoFields = true; 320 alignsize = 1; 321 322 // A fine mess of what size a zero sized struct should be 323 final switch (classKind) 324 { 325 case ClassKind.d: 326 case ClassKind.cpp: 327 structsize = 1; 328 break; 329 330 case ClassKind.c: 331 case ClassKind.objc: 332 if (target.c.bitFieldStyle == TargetC.BitFieldStyle.MS) 333 { 334 /* Undocumented MS behavior for: 335 * struct S { int :0; }; 336 */ 337 structsize = 4; 338 } 339 else if (target.c.bitFieldStyle == TargetC.BitFieldStyle.DM) 340 { 341 structsize = 0; 342 alignsize = 0; 343 } 344 else 345 structsize = 0; 346 break; 347 } 348 } 349 350 // Round struct size up to next alignsize boundary. 351 // This will ensure that arrays of structs will get their internals 352 // aligned properly. 353 if (alignment.isDefault() || alignment.isPack()) 354 structsize = (structsize + alignsize - 1) & ~(alignsize - 1); 355 else 356 structsize = (structsize + alignment.get() - 1) & ~(alignment.get() - 1); 357 358 sizeok = Sizeok.done; 359 360 //printf("-StructDeclaration::finalizeSize() %s, fields.length = %d, structsize = %d\n", toChars(), cast(int)fields.length, cast(int)structsize); 361 362 if (errors) 363 return; 364 365 // Calculate fields[i].overlapped 366 if (checkOverlappedFields()) 367 { 368 errors = true; 369 return; 370 } 371 372 // Determine if struct is all zeros or not 373 zeroInit = true; 374 foreach (vd; fields) 375 { 376 if (vd._init) 377 { 378 if (vd._init.isVoidInitializer()) 379 /* Treat as 0 for the purposes of putting the initializer 380 * in the BSS segment, or doing a mass set to 0 381 */ 382 continue; 383 384 // Zero size fields are zero initialized 385 if (vd.type.size(vd.loc) == 0) 386 continue; 387 388 // Examine init to see if it is all 0s. 389 auto exp = vd.getConstInitializer(); 390 if (!exp || !_isZeroInit(exp)) 391 { 392 zeroInit = false; 393 break; 394 } 395 } 396 else if (!vd.type.isZeroInit(loc)) 397 { 398 zeroInit = false; 399 break; 400 } 401 } 402 403 404 argTypes = target.toArgTypes(type); 405 } 406 407 /// Compute cached type properties for `TypeStruct` 408 extern(D) final void determineTypeProperties() 409 { 410 if (computedTypeProperties) 411 return; 412 foreach (vd; fields) 413 { 414 if (vd.storage_class & STC.ref_ || vd.hasPointers()) 415 hasPointerField = true; 416 417 if (vd._init && vd._init.isVoidInitializer() && vd.type.hasPointers()) 418 hasVoidInitPointers = true; 419 420 if (vd.storage_class & STC.system || vd.type.hasSystemFields()) 421 hasSystemFields = true; 422 423 if (!vd._init && vd.type.hasVoidInitPointers()) 424 hasVoidInitPointers = true; 425 426 if (vd.type.hasInvariant()) 427 hasFieldWithInvariant = true; 428 } 429 computedTypeProperties = true; 430 } 431 432 /*************************************** 433 * Determine if struct is POD (Plain Old Data). 434 * 435 * POD is defined as: 436 * $(OL 437 * $(LI not nested) 438 * $(LI no postblits, destructors, or assignment operators) 439 * $(LI no `ref` fields or fields that are themselves non-POD) 440 * ) 441 * The idea being these are compatible with C structs. 442 * 443 * Returns: 444 * true if struct is POD 445 */ 446 final bool isPOD() 447 { 448 // If we've already determined whether this struct is POD. 449 if (ispod != ThreeState.none) 450 return (ispod == ThreeState.yes); 451 452 ispod = ThreeState.yes; 453 454 if (enclosing || postblit || dtor || hasCopyCtor) 455 { 456 ispod = ThreeState.no; 457 return false; 458 } 459 460 // Recursively check all fields are POD. 461 for (size_t i = 0; i < fields.length; i++) 462 { 463 VarDeclaration v = fields[i]; 464 if (v.storage_class & STC.ref_) 465 { 466 ispod = ThreeState.no; 467 return false; 468 } 469 470 Type tv = v.type.baseElemOf(); 471 if (tv.ty == Tstruct) 472 { 473 TypeStruct ts = cast(TypeStruct)tv; 474 StructDeclaration sd = ts.sym; 475 if (!sd.isPOD()) 476 { 477 ispod = ThreeState.no; 478 return false; 479 } 480 } 481 } 482 483 return (ispod == ThreeState.yes); 484 } 485 486 /*************************************** 487 * Determine if struct has copy construction (copy constructor or postblit) 488 * Returns: 489 * true if struct has copy construction 490 */ 491 final bool hasCopyConstruction() 492 { 493 return postblit || hasCopyCtor; 494 } 495 496 override final inout(StructDeclaration) isStructDeclaration() inout @nogc nothrow pure @safe 497 { 498 return this; 499 } 500 501 override void accept(Visitor v) 502 { 503 v.visit(this); 504 } 505 506 final uint numArgTypes() const 507 { 508 return argTypes && argTypes.arguments ? cast(uint) argTypes.arguments.length : 0; 509 } 510 511 final Type argType(uint index) 512 { 513 return index < numArgTypes() ? (*argTypes.arguments)[index].type : null; 514 } 515 516 517 /*************************************** 518 * Verifies whether the struct declaration has a 519 * constructor that is not a copy constructor. 520 * Optionally, it can check whether the struct 521 * declaration has a regular constructor, that 522 * is not disabled. 523 * 524 * Params: 525 * checkDisabled = if the struct has a regular 526 non-disabled constructor 527 * Returns: 528 * true, if the struct has a regular (optionally, 529 * not disabled) constructor, false otherwise. 530 */ 531 final bool hasRegularCtor(bool checkDisabled = false) 532 { 533 if (!ctor) 534 return false; 535 536 bool result; 537 overloadApply(ctor, (Dsymbol s) 538 { 539 if (auto td = s.isTemplateDeclaration()) 540 { 541 if (checkDisabled && td.onemember) 542 { 543 if (auto ctorDecl = td.onemember.isCtorDeclaration()) 544 { 545 if (ctorDecl.storage_class & STC.disable) 546 return 0; 547 } 548 } 549 result = true; 550 return 1; 551 } 552 if (auto ctorDecl = s.isCtorDeclaration()) 553 { 554 if (!ctorDecl.isCpCtor && (!checkDisabled || !(ctorDecl.storage_class & STC.disable))) 555 { 556 result = true; 557 return 1; 558 } 559 } 560 return 0; 561 }); 562 return result; 563 } 564 } 565 566 /********************************** 567 * Determine if exp is all binary zeros. 568 * Params: 569 * exp = expression to check 570 * Returns: 571 * true if it's all binary 0 572 */ 573 bool _isZeroInit(Expression exp) 574 { 575 switch (exp.op) 576 { 577 case EXP.int64: 578 return exp.toInteger() == 0; 579 580 case EXP.null_: 581 return true; 582 583 case EXP.structLiteral: 584 { 585 auto sle = exp.isStructLiteralExp(); 586 if (sle.sd.isNested()) 587 return false; 588 const isCstruct = sle.sd.isCsymbol(); // C structs are default initialized to all zeros 589 foreach (i; 0 .. sle.sd.fields.length) 590 { 591 auto field = sle.sd.fields[i]; 592 if (field.type.size(field.loc)) 593 { 594 auto e = sle.elements && i < sle.elements.length ? (*sle.elements)[i] : null; 595 if (e ? !_isZeroInit(e) 596 : !isCstruct && !field.type.isZeroInit(field.loc)) 597 return false; 598 } 599 } 600 return true; 601 } 602 603 case EXP.arrayLiteral: 604 { 605 auto ale = cast(ArrayLiteralExp)exp; 606 607 const dim = ale.elements ? ale.elements.length : 0; 608 609 if (ale.type.toBasetype().ty == Tarray) // if initializing a dynamic array 610 return dim == 0; 611 612 foreach (i; 0 .. dim) 613 { 614 if (!_isZeroInit(ale[i])) 615 return false; 616 } 617 618 /* Note that true is returned for all T[0] 619 */ 620 return true; 621 } 622 623 case EXP.string_: 624 { 625 StringExp se = cast(StringExp)exp; 626 627 if (se.type.toBasetype().ty == Tarray) // if initializing a dynamic array 628 return se.len == 0; 629 630 foreach (i; 0 .. se.len) 631 { 632 if (se.getCodeUnit(i)) 633 return false; 634 } 635 return true; 636 } 637 638 case EXP.vector: 639 { 640 auto ve = cast(VectorExp) exp; 641 return _isZeroInit(ve.e1); 642 } 643 644 case EXP.float64: 645 case EXP.complex80: 646 { 647 import dmd.root.ctfloat : CTFloat; 648 return (exp.toReal() is CTFloat.zero) && 649 (exp.toImaginary() is CTFloat.zero); 650 } 651 652 default: 653 return false; 654 } 655 } 656 657 /*********************************************************** 658 * Unions are a variation on structs. 659 */ 660 extern (C++) final class UnionDeclaration : StructDeclaration 661 { 662 extern (D) this(const ref Loc loc, Identifier id) 663 { 664 super(loc, id, false); 665 } 666 667 override UnionDeclaration syntaxCopy(Dsymbol s) 668 { 669 assert(!s); 670 auto ud = new UnionDeclaration(loc, ident); 671 StructDeclaration.syntaxCopy(ud); 672 return ud; 673 } 674 675 override const(char)* kind() const 676 { 677 return "union"; 678 } 679 680 override inout(UnionDeclaration) isUnionDeclaration() inout 681 { 682 return this; 683 } 684 685 override void accept(Visitor v) 686 { 687 v.visit(this); 688 } 689 }