Skip to content

Templatize _d_interface_cast #21473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion compiler/src/dmd/backend/drtlsym.d
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ Symbol* getRtlsym(RTLSYM i) @trusted
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;
case RTLSYM.INTERFACE_CAST: symbolz(ps,FL.func,FREGSAVED,"_d_interface_cast", 0, t); break;
case RTLSYM.ARRAYCATT: symbolz(ps,FL.func,FREGSAVED,"_d_arraycatT", 0, t); break;
case RTLSYM.ARRAYAPPENDCD: symbolz(ps,FL.func,FREGSAVED,"_d_arrayappendcd", 0, t); break;
case RTLSYM.ARRAYAPPENDWD: symbolz(ps,FL.func,FREGSAVED,"_d_arrayappendwd", 0, t); break;
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dmd/backend/rtlsym.d
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ enum RTLSYM
CALLFINALIZER,
CALLINTERFACEFINALIZER,
ALLOCMEMORY,
INTERFACE_CAST,
ARRAYCATT,
ARRAYAPPENDCD,
ARRAYAPPENDWD,
Expand Down
10 changes: 1 addition & 9 deletions compiler/src/dmd/e2ir.d
Original file line number Diff line number Diff line change
Expand Up @@ -4781,15 +4781,7 @@ elem* toElemCast(CastExp ce, elem* e, bool isLvalue, ref IRState irs)
}
else
{
if (cdfrom.isInterfaceDeclaration())
{
elem* ep = el_param(el_ptr(toExtSymbol(cdto)), e);
e = el_bin(OPcall, TYnptr, el_var(getRtlsym(RTLSYM.INTERFACE_CAST)), ep);
}
else
{
assert(ce.lowering, "This case should have been rewritten to `_d_cast` in the semantic phase");
}
assert(ce.lowering, "This case should have been rewritten to `_d_cast` in the semantic phase");
}
return Lret(ce, e);
}
Expand Down
115 changes: 60 additions & 55 deletions compiler/src/dmd/expressionsem.d
Original file line number Diff line number Diff line change
Expand Up @@ -3807,6 +3807,58 @@
return pkg;
}

/**
* Performs the lowering of a CastExp to a call to `.object._d_cast`,
* populating the `lowering` field of the CastExp.
* This is only done for casts between classes/interfaces.
*
* Params:
* cex = the CastExp to lower
* sc = the current scope
*/
private void lowerCastExp(CastExp cex, Scope* sc)
{
Type t1b = cex.e1.type.toBasetype();
Type tob = cex.to.toBasetype();

if (t1b.ty != Tclass || tob.ty != Tclass)
return;

ClassDeclaration cdfrom = t1b.isClassHandle();
ClassDeclaration cdto = tob.isClassHandle();

int offset;
if ((cdto.isBaseOf(cdfrom, &offset) && offset != ClassDeclaration.OFFSET_RUNTIME)
|| cdfrom.classKind == ClassKind.cpp)
return;

Identifier hook = Id._d_cast;
if (!verifyHookExist(cex.loc, *sc, hook, "d_cast", Id.object))
return;

Check warning on line 3837 in compiler/src/dmd/expressionsem.d

View check run for this annotation

Codecov / codecov/patch

compiler/src/dmd/expressionsem.d#L3837

Added line #L3837 was not covered by tests

// Lower to .object._d_cast!(To)(exp.e1)
Expression lowering = new IdentifierExp(cex.loc, Id.empty);
lowering = new DotIdExp(cex.loc, lowering, Id.object);

auto tiargs = new Objects();
// Unqualify the type being casted to, avoiding multiple instantiations
auto unqual_tob = tob.unqualify(MODFlags.wild | MODFlags.const_ |
MODFlags.immutable_ | MODFlags.shared_);
tiargs.push(unqual_tob);
lowering = new DotTemplateInstanceExp(cex.loc, lowering, hook, tiargs);

auto arguments = new Expressions();
// Unqualify the type being casted from to avoid multiple instantiations
auto unqual_t1b = t1b.unqualify(MODFlags.wild | MODFlags.const_ |
MODFlags.immutable_ | MODFlags.shared_);
Expression e1c = cex.e1.copy();
e1c.type = unqual_t1b;
arguments.push(e1c);

lowering = new CallExp(cex.loc, lowering, arguments);

cex.lowering = lowering.expressionSemantic(sc);
}

private extern (C++) final class ExpressionSemanticVisitor : Visitor
{
Expand Down Expand Up @@ -6948,7 +7000,12 @@
if (tf.next.isBaseOf(t, &offset) && offset)
{
exp.type = tf.next;
result = Expression.combine(argprefix, exp.castTo(sc, t));
auto casted_exp = exp.castTo(sc, t);
if (auto cex = casted_exp.isCastExp())
{
lowerCastExp(cex, sc);
}
result = Expression.combine(argprefix, casted_exp);
return;
}
}
Expand Down Expand Up @@ -9366,63 +9423,11 @@
}
}

// If the cast is from an alias this, we need to unalias it
if (t1b.ty == Tstruct)
if (auto cex = ex.isCastExp())
{
if (auto t1b_unalias = t1b.aliasthisOf())
{
t1b = t1b_unalias;
}
}

if (t1b.ty == Tclass && tob.ty == Tclass)
{
CastExp cex = ex.isCastExp();

if (cex is null)
goto LskipCastLowering;

ClassDeclaration cdfrom = t1b.isClassHandle();
ClassDeclaration cdto = tob.isClassHandle();

int offset;
if (!(cdto.isBaseOf(cdfrom, &offset) && offset != ClassDeclaration.OFFSET_RUNTIME)
&& cdfrom.classKind != ClassKind.cpp)
{
if (!cdfrom.isInterfaceDeclaration())
{

Identifier hook = Id._d_cast;
if (!verifyHookExist(cex.loc, *sc, hook, "d_cast", Id.object))
goto LskipCastLowering;

// Lower to .object._d_cast!(To)(exp.e1)
Expression lowering = new IdentifierExp(cex.loc, Id.empty);
lowering = new DotIdExp(cex.loc, lowering, Id.object);

auto tiargs = new Objects();
// Unqualify the type being casted to, avoiding multiple instantiations
auto unqual_tob = tob.unqualify(MODFlags.wild | MODFlags.const_ |
MODFlags.immutable_ | MODFlags.shared_);
tiargs.push(unqual_tob);
lowering = new DotTemplateInstanceExp(cex.loc, lowering, hook, tiargs);

auto arguments = new Expressions();
// Unqualify the type being casted from to avoid multiple instantiations
auto unqual_t1b = t1b.unqualify(MODFlags.wild | MODFlags.const_ |
MODFlags.immutable_ | MODFlags.shared_);
cex.e1.type = unqual_t1b;
arguments.push(cex.e1);

lowering = new CallExp(cex.loc, lowering, arguments);

cex.lowering = lowering.expressionSemantic(sc);
}
}
lowerCastExp(cex, sc);
}

LskipCastLowering:

result = ex;
}

Expand Down
1 change: 0 additions & 1 deletion druntime/mak/DOCS
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,6 @@ DOCS=\
$(DOCDIR)\rt_adi.html \
$(DOCDIR)\rt_alloca.html \
$(DOCDIR)\rt_arraycat.html \
$(DOCDIR)\rt_cast_.html \
$(DOCDIR)\rt_config.html \
$(DOCDIR)\rt_deh.html \
$(DOCDIR)\rt_deh_win32.html \
Expand Down
1 change: 0 additions & 1 deletion druntime/mak/SRCS
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,6 @@ SRCS=\
src\rt\adi.d \
src\rt\alloca.d \
src\rt\arraycat.d \
src\rt\cast_.d \
src\rt\cmath2.d \
src\rt\config.d \
src\rt\cover.d \
Expand Down
70 changes: 69 additions & 1 deletion druntime/src/core/internal/cast_.d
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,27 @@ private void* _d_class_cast(To)(const return scope Object o)
return _d_class_cast_impl(o, typeid(To));
}

/*************************************
* Attempts to cast interface Object o to class type `To`.
* Returns o if successful, null if not.
*/
private void* _d_interface_cast(To)(void* p) @trusted
{
if (!p)
return null;

Interface* pi = **cast(Interface***) p;

Object o2 = cast(Object)(p - pi.offset);
void* res = null;
size_t offset = 0;
if (o2 && _d_isbaseof2!To(typeid(o2), offset))
{
res = cast(void*) o2 + offset;
}
return res;
}

/**
* Hook that detects the type of cast performed and calls the appropriate function.
* Params:
Expand Down Expand Up @@ -149,7 +170,21 @@ void* _d_cast(To, From)(From o) @trusted
return null;
}

return null;
static if (is(From == interface))
{
static if (is(From == To))
{
return cast(void*)o;
}
else
{
return _d_interface_cast!To(cast(void*)o);
}
}
else
{
return null;
}
}

private bool _d_isbaseof2(To)(scope ClassInfo oc, scope ref size_t offset)
Expand Down Expand Up @@ -241,3 +276,36 @@ private bool _d_isbaseof2(To)(scope ClassInfo oc, scope ref size_t offset)
assert(_d_cast!D(a) is null); // A(a) to D
assert(_d_class_cast!D(a) is null);
}

@safe pure unittest
{
interface I1 {}
interface I2 {}
interface I3 {}
class A {}
class B : A, I1, I2 {}
class C : B, I3 {}

I1 bi = new B();
assert(_d_cast!I2(bi) !is null); // I1(b) to I2
assert(_d_interface_cast!I2(cast(void*)bi) !is null);

assert(_d_cast!A(bi) !is null); // I1(b) to A
assert(_d_interface_cast!A(cast(void*)bi) !is null);

assert(_d_cast!B(bi) !is null); // I1(b) to B
assert(_d_interface_cast!B(cast(void*)bi) !is null);

assert(_d_cast!I3(bi) is null); // I1(b) to I3
assert(_d_interface_cast!I3(cast(void*)bi) is null);

assert(_d_cast!C(bi) is null); // I1(b) to C
assert(_d_interface_cast!C(cast(void*)bi) is null);

assert(_d_cast!I1(bi) !is null); // I1(b) to I1
assert(_d_interface_cast!I1(cast(void*)bi) !is null);

I3 ci = new C();
assert(_d_cast!I1(ci) !is null); // I3(c) to I1
assert(_d_interface_cast!I1(cast(void*)ci) !is null);
}
53 changes: 51 additions & 2 deletions druntime/src/object.d
Original file line number Diff line number Diff line change
Expand Up @@ -1572,8 +1572,57 @@ class TypeInfo_Delegate : TypeInfo
}

private extern (C) Object _d_newclass(const TypeInfo_Class ci);
private extern (C) int _d_isbaseof(scope TypeInfo_Class child,
scope const TypeInfo_Class parent) @nogc nothrow pure @safe; // rt.cast_

extern(C) int _d_isbaseof(scope ClassInfo oc, scope const ClassInfo c) @nogc nothrow pure @safe
{
import core.internal.cast_ : areClassInfosEqual;

if (areClassInfosEqual(oc, c))
return true;

do
{
if (oc.base && areClassInfosEqual(oc.base, c))
return true;

// Bugzilla 2013: Use depth-first search to calculate offset
// from the derived (oc) to the base (c).
foreach (iface; oc.interfaces)
{
if (areClassInfosEqual(iface.classinfo, c) || _d_isbaseof(iface.classinfo, c))
return true;
}

oc = oc.base;
} while (oc);

return false;
}

/******************************************
* Given a pointer:
* If it is an Object, return that Object.
* If it is an interface, return the Object implementing the interface.
* If it is null, return null.
* Else, undefined crash
*/
extern(C) Object _d_toObject(return scope void* p) @nogc nothrow pure @trusted
{
if (!p)
return null;

Object o = cast(Object) p;
Interface* pi = **cast(Interface***) p;

/* Interface.offset lines up with ClassInfo.name.ptr,
* so we rely on pointers never being less than 64K,
* and Objects never being greater.
*/
if (pi.offset < 0x10000)
return cast(Object)(p - pi.offset);

return o;
}

/**
* Runtime type information about a class.
Expand Down
Loading
Loading