blob: d07be2f131933eed36ea7b3727027ef93c7896cf [file] [log] [blame]
/**
* Struct and union declarations.
*
* Specification: $(LINK2 https://dlang.org/spec/struct.html, Structs, Unions)
*
* Copyright: Copyright (C) 1999-2025 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/dstruct.d, _dstruct.d)
* Documentation: https://dlang.org/phobos/dmd_dstruct.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/dstruct.d
*/
module dmd.dstruct;
import core.stdc.stdio;
import dmd.aggregate;
import dmd.arraytypes;
import dmd.astenums;
import dmd.attrib;
import dmd.declaration;
import dmd.dmodule;
import dmd.dscope;
import dmd.dsymbol;
import dmd.dsymbolsem : search, setFieldOffset;
import dmd.dtemplate;
import dmd.errors;
import dmd.expression;
import dmd.func;
import dmd.funcsem;
import dmd.globals;
import dmd.id;
import dmd.identifier;
import dmd.location;
import dmd.mtype;
import dmd.opover;
import dmd.target;
import dmd.tokens;
import dmd.typesem : isZeroInit, merge, size, hasPointers;
import dmd.typinf;
import dmd.visitor;
/***************************************
* Search sd for a member function of the form:
* `extern (D) string toString();`
* Params:
* sd = struct declaration to search
* Returns:
* FuncDeclaration of `toString()` if found, `null` if not
*/
FuncDeclaration search_toString(StructDeclaration sd)
{
Dsymbol s = search_function(sd, Id.tostring);
FuncDeclaration fd = s ? s.isFuncDeclaration() : null;
if (fd)
{
__gshared TypeFunction tftostring;
if (!tftostring)
{
tftostring = new TypeFunction(ParameterList(), Type.tstring, LINK.d);
tftostring = tftostring.merge().toTypeFunction();
}
fd = fd.overloadExactMatch(tftostring);
}
return fd;
}
enum StructFlags : int
{
none = 0x0,
hasPointers = 0x1, // NB: should use noPointers as in ClassFlags
}
/***********************************************************
* All `struct` declarations are an instance of this.
*/
extern (C++) class StructDeclaration : AggregateDeclaration
{
FuncDeclarations postblits; // Array of postblit functions
FuncDeclaration postblit; // aggregate postblit
FuncDeclaration xeq; // TypeInfo_Struct.xopEquals
FuncDeclaration xcmp; // TypeInfo_Struct.xopCmp
FuncDeclaration xhash; // TypeInfo_Struct.xtoHash
extern (C++) __gshared FuncDeclaration xerreq; // object.xopEquals
extern (C++) __gshared FuncDeclaration xerrcmp; // object.xopCmp
// ABI-specific type(s) if the struct can be passed in registers
TypeTuple argTypes;
structalign_t alignment; // alignment applied outside of the struct
ThreeState ispod; // if struct is POD
// `bool` fields that are compacted into bit fields in a string mixin
private extern (D) static struct BitFields
{
bool zeroInit; // !=0 if initialize with 0 fill
bool hasIdentityAssign; // true if has identity opAssign
bool hasBlitAssign; // true if opAssign is a blit
bool hasIdentityEquals; // true if has identity opEquals
bool hasNoFields; // has no fields
bool hasCopyCtor; // copy constructor
bool hasMoveCtor; // move constructor
bool hasPointerField; // members with indirections
bool hasVoidInitPointers; // void-initialized unsafe fields
bool hasUnsafeBitpatterns; // @system members, pointers, bool
bool hasFieldWithInvariant; // invariants
bool computedTypeProperties;// the above 3 fields are computed
// Even if struct is defined as non-root symbol, some built-in operations
// (e.g. TypeidExp, NewExp, ArrayLiteralExp, etc) request its TypeInfo.
// For those, today TypeInfo_Struct is generated in COMDAT.
bool requestTypeInfo;
}
import dmd.common.bitfields : generateBitFields;
mixin(generateBitFields!(BitFields, ushort));
extern (D) this(Loc loc, Identifier id, bool inObject)
{
super(loc, id);
this.dsym = DSYM.structDeclaration;
zeroInit = false; // assume false until we do semantic processing
ispod = ThreeState.none;
// For forward references
type = new TypeStruct(this);
if (inObject)
{
if (id == Id.ModuleInfo && !Module.moduleinfo)
Module.moduleinfo = this;
}
}
static StructDeclaration create(Loc loc, Identifier id, bool inObject)
{
return new StructDeclaration(loc, id, inObject);
}
override StructDeclaration syntaxCopy(Dsymbol s)
{
StructDeclaration sd =
s ? cast(StructDeclaration)s
: new StructDeclaration(loc, ident, false);
ScopeDsymbol.syntaxCopy(sd);
return sd;
}
override const(char)* kind() const
{
return "struct";
}
override final void finalizeSize()
{
//printf("StructDeclaration::finalizeSize() %s, sizeok = %d\n", toChars(), sizeok);
assert(sizeok != Sizeok.done);
if (sizeok == Sizeok.inProcess)
{
return;
}
sizeok = Sizeok.inProcess;
//printf("+StructDeclaration::finalizeSize() %s, fields.length = %d, sizeok = %d\n", toChars(), fields.length, sizeok);
fields.setDim(0); // workaround
// Set the offsets of the fields and determine the size of the struct
FieldState fieldState;
bool isunion = isUnionDeclaration() !is null;
for (size_t i = 0; i < members.length; i++)
{
Dsymbol s = (*members)[i];
s.setFieldOffset(this, &fieldState, isunion);
if (type.ty == Terror)
{
errorSupplemental(s.loc, "error on member `%s`", s.toPrettyChars);
errors = true;
return;
}
}
if (structsize == 0)
{
hasNoFields = true;
alignsize = 1;
// A fine mess of what size a zero sized struct should be
final switch (classKind)
{
case ClassKind.d:
case ClassKind.cpp:
structsize = 1;
break;
case ClassKind.c:
case ClassKind.objc:
if (target.c.bitFieldStyle == TargetC.BitFieldStyle.MS)
{
/* Undocumented MS behavior for:
* struct S { int :0; };
*/
structsize = 4;
}
else
structsize = 0;
break;
}
}
// Round struct size up to next alignsize boundary.
// This will ensure that arrays of structs will get their internals
// aligned properly.
if (alignment.isDefault() || alignment.isPack())
structsize = (structsize + alignsize - 1) & ~(alignsize - 1);
else
structsize = (structsize + alignment.get() - 1) & ~(alignment.get() - 1);
sizeok = Sizeok.done;
//printf("-StructDeclaration::finalizeSize() %s, fields.length = %d, structsize = %d\n", toChars(), cast(int)fields.length, cast(int)structsize);
if (errors)
return;
// Calculate fields[i].overlapped
if (checkOverlappedFields())
{
errors = true;
return;
}
// Determine if struct is all zeros or not
zeroInit = true;
auto lastOffset = -1;
foreach (vd; fields)
{
// First skip zero sized fields
if (vd.type.size(vd.loc) == 0)
continue;
// only consider first sized member of an (anonymous) union
if (vd.overlapped && vd.offset == lastOffset)
continue;
lastOffset = vd.offset;
if (vd._init)
{
if (vd._init.isVoidInitializer())
/* Treat as 0 for the purposes of putting the initializer
* in the BSS segment, or doing a mass set to 0
*/
continue;
// Examine init to see if it is all 0s.
auto exp = vd.getConstInitializer();
if (!exp || !_isZeroInit(exp))
{
zeroInit = false;
break;
}
}
else if (!vd.type.isZeroInit(loc))
{
zeroInit = false;
break;
}
}
argTypes = target.toArgTypes(type);
}
/// Compute cached type properties for `TypeStruct`
extern(D) final void determineTypeProperties()
{
if (computedTypeProperties)
return;
foreach (vd; fields)
{
if (vd.storage_class & STC.ref_ || vd.hasPointers())
{
hasPointerField = true;
hasUnsafeBitpatterns = true;
}
if (vd._init && vd._init.isVoidInitializer() && vd.type.hasPointers())
hasVoidInitPointers = true;
if (vd.storage_class & STC.system || vd.type.hasUnsafeBitpatterns())
hasUnsafeBitpatterns = true;
if (!vd._init && vd.type.hasVoidInitPointers())
hasVoidInitPointers = true;
if (vd.type.hasInvariant())
hasFieldWithInvariant = true;
}
computedTypeProperties = true;
}
/***************************************
* Determine if struct is POD (Plain Old Data).
*
* POD is defined as:
* $(OL
* $(LI not nested)
* $(LI no postblits, destructors, or assignment operators)
* $(LI no `ref` fields or fields that are themselves non-POD)
* )
* The idea being these are compatible with C structs.
*
* Returns:
* true if struct is POD
*/
final bool isPOD()
{
// If we've already determined whether this struct is POD.
if (ispod != ThreeState.none)
return (ispod == ThreeState.yes);
import dmd.clone;
bool hasCpCtorLocal;
bool hasMoveCtorLocal;
bool needCopyCtor;
bool needMoveCtor;
needCopyOrMoveCtor(this, hasCpCtorLocal, hasMoveCtorLocal, needCopyCtor, needMoveCtor);
if (enclosing || // is nested
search(this, loc, Id.postblit) || // has postblit
search(this, loc, Id.dtor) || // has destructor
/* This is commented out because otherwise buildkite vibe.d:
`canCAS!Task` fails to compile
*/
//hasMoveCtorLocal || // has move constructor
hasCpCtorLocal) // has copy constructor
{
ispod = ThreeState.no;
return false;
}
// Recursively check all fields are POD.
for (size_t i = 0; i < fields.length; i++)
{
VarDeclaration v = fields[i];
if (v.storage_class & STC.ref_)
{
ispod = ThreeState.no;
return false;
}
if (auto ts = v.type.baseElemOf().isTypeStruct())
{
if (!ts.sym.isPOD())
{
ispod = ThreeState.no;
return false;
}
}
}
ispod = ThreeState.yes;
return true;
}
/***************************************
* Determine if struct has copy construction (copy constructor or postblit)
* Returns:
* true if struct has copy construction
*/
final bool hasCopyConstruction()
{
return postblit || hasCopyCtor;
}
override void accept(Visitor v)
{
v.visit(this);
}
final uint numArgTypes() const
{
return argTypes && argTypes.arguments ? cast(uint) argTypes.arguments.length : 0;
}
final Type argType(uint index)
{
return index < numArgTypes() ? (*argTypes.arguments)[index].type : null;
}
/***************************************
* Verifies whether the struct declaration has a
* constructor that is not a copy constructor.
* Optionally, it can check whether the struct
* declaration has a regular constructor, that
* is not disabled.
*
* Params:
* checkDisabled = if the struct has a regular
non-disabled constructor
* Returns:
* true, if the struct has a regular (optionally,
* not disabled) constructor, false otherwise.
*/
final bool hasRegularCtor(bool checkDisabled = false)
{
if (!ctor)
return false;
bool result;
overloadApply(ctor, (Dsymbol s)
{
if (auto td = s.isTemplateDeclaration())
{
if (checkDisabled && td.onemember)
{
if (auto ctorDecl = td.onemember.isCtorDeclaration())
{
if (ctorDecl.storage_class & STC.disable)
return 0;
}
}
result = true;
return 1;
}
if (auto ctorDecl = s.isCtorDeclaration())
{
if (!ctorDecl.isCpCtor && (!checkDisabled || !(ctorDecl.storage_class & STC.disable)))
{
result = true;
return 1;
}
}
return 0;
});
return result;
}
}
/**********************************
* Determine if exp is all binary zeros.
* Params:
* exp = expression to check
* Returns:
* true if it's all binary 0
*/
bool _isZeroInit(Expression exp)
{
switch (exp.op)
{
case EXP.int64:
return exp.toInteger() == 0;
case EXP.null_:
return true;
case EXP.structLiteral:
{
auto sle = exp.isStructLiteralExp();
if (sle.sd.isNested())
return false;
const isCstruct = sle.sd.isCsymbol(); // C structs are default initialized to all zeros
foreach (i; 0 .. sle.sd.fields.length)
{
auto field = sle.sd.fields[i];
if (field.type.size(field.loc))
{
auto e = sle.elements && i < sle.elements.length ? (*sle.elements)[i] : null;
if (e ? !_isZeroInit(e)
: !isCstruct && !field.type.isZeroInit(field.loc))
return false;
}
}
return true;
}
case EXP.arrayLiteral:
{
auto ale = cast(ArrayLiteralExp)exp;
const dim = ale.elements ? ale.elements.length : 0;
if (ale.type.toBasetype().ty == Tarray) // if initializing a dynamic array
return dim == 0;
foreach (i; 0 .. dim)
{
if (!_isZeroInit(ale[i]))
return false;
}
/* Note that true is returned for all T[0]
*/
return true;
}
case EXP.string_:
{
auto se = cast(StringExp)exp;
if (se.type.toBasetype().ty == Tarray) // if initializing a dynamic array
return se.len == 0;
foreach (i; 0 .. se.len)
{
if (se.getIndex(i) != 0)
return false;
}
return true;
}
case EXP.vector:
{
auto ve = cast(VectorExp) exp;
return _isZeroInit(ve.e1);
}
case EXP.float64:
case EXP.complex80:
{
import dmd.root.ctfloat : CTFloat;
return (exp.toReal() is CTFloat.zero) &&
(exp.toImaginary() is CTFloat.zero);
}
default:
return false;
}
}
/***********************************************************
* Unions are a variation on structs.
*/
extern (C++) final class UnionDeclaration : StructDeclaration
{
extern (D) this(Loc loc, Identifier id)
{
super(loc, id, false);
this.dsym = DSYM.unionDeclaration;
}
override UnionDeclaration syntaxCopy(Dsymbol s)
{
assert(!s);
auto ud = new UnionDeclaration(loc, ident);
StructDeclaration.syntaxCopy(ud);
return ud;
}
override const(char)* kind() const
{
return "union";
}
override void accept(Visitor v)
{
v.visit(this);
}
}