mirror of
https://github.com/ldc-developers/ldc.git
synced 2025-04-27 13:40:33 +03:00

* WIP: Objective-C support * Further work on implementation * ObjC dynamic cast * Add swift stub class attribute * Classes, protocols and ivars * Fix compilation issues * Fix objc ir codegen * Add objc linker option * Add swift stub classref get ir gen * Minor cleanup * Fix objc link flag being added on non-darwin platforms * Refactor objc gen * remove use of std::nullopt * Emit protocol tables * Remove unused variable * Formatting * Fix build in release mode. Thanks for nothing, c++. * Fix consistency * Fix dynamic casts * Fix tocall parentfd ref and arm msgsend call * Make instance variables work * Implicitly add isa pointer to objc classes. * Fix protocol referencing & allow pragma mangle * Fix protocol linkage * Fix direct call support * always generate var type for methods * Fix test 16096a * Fix extern ivar symbol gen, retain method decls * Remove arm32 and x86 support * Check method and ivar info before pushing to member list * Make ObjcMethod info untyped. * Make ivar and method gen more robust * Generate optional protocol symbols * Use bitcasting instead of creating multiple type defs * Fix invalid protocol list struct gen * More codegen robustness * emit protocol table as const * Make protocol table anon struct * Fix callable type, generate protocol_list_t properly. * Cast vthis to argtype * Handle protorefs and classrefs properly * seperate label ref and deref * Fix method lookup * Enable objective-c tests * Enable objc_call_static test * Scan both classes and protocols for method ref * Enable objective-c tests on arm as well. * supress objc linker warning in tests * Fix class and protocol gen structure * Fix objc_protocol_sections test * ObjcMethod only get callee for functions with bodies * Fix protocol class method gen * Make ObjcMethod anon again * Fix missing emit calls * Fix classref gen * Implement some of the requested changes * Enable compilable tests * Fix property selector gen, ugly hack for final funcs. * Fix segfault in referencing fd->type * Refactor implementation * Fix null references in class and method lookup * include unordered_map * Get functionality on-par with prev impl. * Fix super context calls * Move -L-w flag to d_do_test and use IN_LLVM in objc.d/h * add LDC version tag to -L-w flag * Update CHANGELOG.md
504 lines
15 KiB
C++
504 lines
15 KiB
C++
//===-- classes.cpp -------------------------------------------------------===//
|
||
//
|
||
// LDC – the LLVM D compiler
|
||
//
|
||
// This file is distributed under the BSD-style LDC license. See the LICENSE
|
||
// file for details.
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
|
||
#include "gen/classes.h"
|
||
|
||
#include "dmd/aggregate.h"
|
||
#include "dmd/declaration.h"
|
||
#include "dmd/errors.h"
|
||
#include "dmd/expression.h"
|
||
#include "dmd/identifier.h"
|
||
#include "dmd/init.h"
|
||
#include "dmd/mtype.h"
|
||
#include "dmd/target.h"
|
||
#include "gen/arrays.h"
|
||
#include "gen/dvalue.h"
|
||
#include "gen/functions.h"
|
||
#include "gen/irstate.h"
|
||
#include "gen/llvm.h"
|
||
#include "gen/llvmhelpers.h"
|
||
#include "gen/logger.h"
|
||
#include "gen/nested.h"
|
||
#include "gen/optimizer.h"
|
||
#include "gen/runtime.h"
|
||
#include "gen/structs.h"
|
||
#include "gen/tollvm.h"
|
||
#include "ir/iraggr.h"
|
||
#include "ir/irdsymbol.h"
|
||
#include "ir/irfunction.h"
|
||
#include "ir/irtypeclass.h"
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
// FIXME: this needs to be cleaned up
|
||
|
||
void DtoResolveClass(ClassDeclaration *cd) {
|
||
if (cd->ir->isResolved()) {
|
||
return;
|
||
}
|
||
cd->ir->setResolved();
|
||
|
||
IF_LOG Logger::println("DtoResolveClass(%s): %s", cd->toPrettyChars(),
|
||
cd->loc.toChars());
|
||
LOG_SCOPE;
|
||
|
||
// make sure the base classes are processed first
|
||
for (auto bc : *cd->baseclasses) {
|
||
DtoResolveClass(bc->sym);
|
||
}
|
||
|
||
// make sure type exists
|
||
DtoType(cd->type);
|
||
|
||
// create IrAggr
|
||
getIrAggr(cd, true);
|
||
|
||
// make sure all fields really get their ir field
|
||
for (auto vd : cd->fields) {
|
||
IF_LOG {
|
||
if (isIrFieldCreated(vd)) {
|
||
Logger::println("class field already exists");
|
||
}
|
||
}
|
||
getIrField(vd, true);
|
||
}
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
DValue *DtoNewClass(const Loc &loc, TypeClass *tc, NewExp *newexp) {
|
||
// resolve type
|
||
DtoResolveClass(tc->sym);
|
||
const auto irClass = getIrAggr(tc->sym);
|
||
|
||
// allocate
|
||
LLValue *mem;
|
||
bool doInit = true;
|
||
if (newexp->onstack) {
|
||
mem = DtoRawAlloca(irClass->getLLStructType(), tc->sym->alignsize,
|
||
".newclass_alloca");
|
||
} else {
|
||
const bool useEHAlloc = global.params.ehnogc && newexp->thrownew;
|
||
llvm::Function *fn = getRuntimeFunction(
|
||
loc, gIR->module, useEHAlloc ? "_d_newThrowable" : "_d_allocclass");
|
||
LLConstant *ci = irClass->getClassInfoSymbol();
|
||
mem = gIR->CreateCallOrInvoke(
|
||
fn, ci, useEHAlloc ? ".newthrowable" : ".newclass_gc");
|
||
doInit = !useEHAlloc;
|
||
}
|
||
|
||
// init
|
||
if (doInit)
|
||
DtoInitClass(tc, mem);
|
||
|
||
// init inner-class outer reference
|
||
if (newexp->thisexp) {
|
||
Logger::println("Resolving outer class");
|
||
LOG_SCOPE;
|
||
unsigned idx = getFieldGEPIndex(tc->sym, tc->sym->vthis);
|
||
LLValue *src = DtoRVal(newexp->thisexp);
|
||
LLValue *dst = DtoGEP(irClass->getLLStructType(), mem, 0, idx);
|
||
IF_LOG Logger::cout() << "dst: " << *dst << "\nsrc: " << *src << '\n';
|
||
DtoStore(src, dst);
|
||
}
|
||
// set the context for nested classes
|
||
else if (tc->sym->isNested() && tc->sym->vthis) {
|
||
DtoResolveNestedContext(loc, tc->sym, mem);
|
||
}
|
||
|
||
// call constructor
|
||
if (newexp->member) {
|
||
// evaluate argprefix
|
||
if (newexp->argprefix) {
|
||
toElemDtor(newexp->argprefix);
|
||
}
|
||
|
||
Logger::println("Calling constructor");
|
||
assert(newexp->arguments != NULL);
|
||
DFuncValue dfn(newexp->member, DtoCallee(newexp->member), mem);
|
||
// ignore ctor return value (C++ ctors on Posix may not return `this`)
|
||
DtoCallFunction(newexp->loc, tc, &dfn, newexp->arguments);
|
||
return new DImValue(tc, mem);
|
||
}
|
||
|
||
assert(newexp->argprefix == NULL);
|
||
|
||
// return default constructed class
|
||
return new DImValue(tc, mem);
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
void DtoInitClass(TypeClass *tc, LLValue *dst) {
|
||
DtoResolveClass(tc->sym);
|
||
|
||
IrClass *irClass = getIrAggr(tc->sym);
|
||
llvm::StructType *st = irClass->getLLStructType();
|
||
|
||
// Set vtable field. Doing this seperately might be optimized better.
|
||
LLValue *tmp = DtoGEP(st, dst, 0u, 0, "vtbl");
|
||
LLValue *val = irClass->getVtblSymbol();
|
||
DtoStore(val, tmp);
|
||
|
||
// For D classes, set the monitor field to null.
|
||
const bool isCPPclass = tc->sym->isCPPclass() ? true : false;
|
||
if (!isCPPclass) {
|
||
tmp = DtoGEP(st, dst, 0, 1, "monitor");
|
||
val = LLConstant::getNullValue(st->getElementType(1));
|
||
DtoStore(val, tmp);
|
||
}
|
||
|
||
// Copy the rest from the static initializer, if any.
|
||
unsigned const firstDataIdx = isCPPclass ? 1 : 2;
|
||
uint64_t const dataBytes =
|
||
tc->sym->structsize - target.ptrsize * firstDataIdx;
|
||
if (dataBytes == 0) {
|
||
return;
|
||
}
|
||
|
||
LLValue *dstarr = DtoGEP(st, dst, 0, firstDataIdx);
|
||
|
||
// init symbols might not have valid types
|
||
LLValue *initsym = irClass->getInitSymbol();
|
||
LLValue *srcarr = DtoGEP(st, initsym, 0, firstDataIdx);
|
||
|
||
DtoMemCpy(dstarr, srcarr, DtoConstSize_t(dataBytes));
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
void DtoFinalizeClass(const Loc &loc, LLValue *inst) {
|
||
// get runtime function
|
||
llvm::Function *fn =
|
||
getRuntimeFunction(loc, gIR->module, "_d_callfinalizer");
|
||
|
||
gIR->CreateCallOrInvoke(fn, inst, "");
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
void DtoFinalizeScopeClass(const Loc &loc, DValue *dval,
|
||
bool dynTypeMatchesStaticType) {
|
||
llvm::Value *inst = DtoRVal(dval);
|
||
|
||
if (!isOptimizationEnabled() || !dynTypeMatchesStaticType) {
|
||
DtoFinalizeClass(loc, inst);
|
||
return;
|
||
}
|
||
|
||
bool hasDtor = false;
|
||
const auto cd = dval->type->toBasetype()->isTypeClass()->sym;
|
||
for (auto cd2 = cd; cd2; cd2 = cd2->baseClass) {
|
||
if (cd2->dtor) {
|
||
hasDtor = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (hasDtor) {
|
||
DtoFinalizeClass(loc, inst);
|
||
return;
|
||
}
|
||
|
||
// no dtors => only finalize (via druntime call) if monitor is set,
|
||
// see https://github.com/ldc-developers/ldc/issues/2515
|
||
llvm::BasicBlock *ifbb = gIR->insertBB("if");
|
||
llvm::BasicBlock *endbb = gIR->insertBBAfter(ifbb, "endif");
|
||
|
||
llvm::StructType *st =
|
||
isaStruct(getIrType(cd->type, true)->isClass()->getMemoryLLType());
|
||
const auto monitor =
|
||
DtoLoad(st->getElementType(1), DtoGEP(st, inst, 0, 1), ".monitor");
|
||
const auto hasMonitor =
|
||
gIR->ir->CreateICmp(llvm::CmpInst::ICMP_NE, monitor,
|
||
getNullValue(monitor->getType()), ".hasMonitor");
|
||
llvm::BranchInst::Create(ifbb, endbb, hasMonitor, gIR->scopebb());
|
||
|
||
gIR->ir->SetInsertPoint(ifbb);
|
||
DtoFinalizeClass(loc, inst);
|
||
gIR->ir->CreateBr(endbb);
|
||
|
||
gIR->ir->SetInsertPoint(endbb);
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
DValue *DtoCastClass(const Loc &loc, DValue *val, Type *_to) {
|
||
IF_LOG Logger::println("DtoCastClass(%s, %s)", val->type->toChars(),
|
||
_to->toChars());
|
||
LOG_SCOPE;
|
||
|
||
Type *to = _to->toBasetype();
|
||
|
||
// class -> pointer
|
||
if (to->ty == TY::Tpointer) {
|
||
IF_LOG Logger::println("to pointer");
|
||
return new DImValue(_to, DtoRVal(val));
|
||
}
|
||
// class -> bool
|
||
if (to->ty == TY::Tbool) {
|
||
IF_LOG Logger::println("to bool");
|
||
LLValue *llval = DtoRVal(val);
|
||
LLValue *zero = LLConstant::getNullValue(llval->getType());
|
||
return new DImValue(_to, gIR->ir->CreateICmpNE(llval, zero));
|
||
}
|
||
// class -> integer
|
||
if (to->isintegral()) {
|
||
IF_LOG Logger::println("to %s", to->toChars());
|
||
|
||
// get class ptr
|
||
LLValue *v = DtoRVal(val);
|
||
// cast to size_t
|
||
v = gIR->ir->CreatePtrToInt(v, DtoSize_t(), "");
|
||
// cast to the final int type
|
||
DImValue im(Type::tsize_t, v);
|
||
return DtoCastInt(loc, &im, _to);
|
||
}
|
||
// class -> typeof(null)
|
||
if (to->ty == TY::Tnull) {
|
||
IF_LOG Logger::println("to %s", to->toChars());
|
||
return new DImValue(_to, LLConstant::getNullValue(DtoType(_to)));
|
||
}
|
||
|
||
// must be class/interface
|
||
assert(to->ty == TY::Tclass);
|
||
TypeClass *tc = static_cast<TypeClass *>(to);
|
||
|
||
// from type
|
||
Type *from = val->type->toBasetype();
|
||
TypeClass *fc = static_cast<TypeClass *>(from);
|
||
|
||
// copy DMD logic:
|
||
// if to isBaseOf from with offset: (to ? to + offset : null)
|
||
// else if from is C++ and to is C++: to
|
||
// else if from is C++ and to is D: null
|
||
// else if from is interface: _d_interface_cast(to)
|
||
// else if from is class: _d_dynamic_cast(to)
|
||
|
||
int offset = 0;
|
||
if (tc->sym->isBaseOf(fc->sym, &offset)) {
|
||
Logger::println("static down cast");
|
||
// interface types don't cover the full object in case of multiple inheritence
|
||
// so GEP on the original type is inappropriate
|
||
|
||
// offset pointer
|
||
LLValue *orig = DtoRVal(val);
|
||
LLValue *v = orig;
|
||
if (offset != 0) {
|
||
assert(offset > 0);
|
||
v = DtoGEP1(getI8Type(), v, DtoConstUint(offset));
|
||
}
|
||
IF_LOG {
|
||
Logger::cout() << "V = " << *v << std::endl;
|
||
}
|
||
|
||
// Check whether the original value was null, and return null if so.
|
||
// Sure we could have jumped over the code above in this case, but
|
||
// it's just a GEP and (maybe) a pointer-to-pointer BitCast, so it
|
||
// should be pretty cheap and perfectly safe even if the original was
|
||
// null.
|
||
const auto nullPtr = getNullPtr();
|
||
LLValue *isNull = gIR->ir->CreateICmpEQ(orig, nullPtr, ".nullcheck");
|
||
v = gIR->ir->CreateSelect(isNull, nullPtr, v, ".interface");
|
||
// return r-value
|
||
return new DImValue(_to, v);
|
||
}
|
||
|
||
if (fc->sym->classKind == ClassKind::cpp) {
|
||
Logger::println("C++ class/interface cast");
|
||
LLValue *v =
|
||
tc->sym->classKind == ClassKind::cpp ? DtoRVal(val) : getNullPtr();
|
||
return new DImValue(_to, v);
|
||
}
|
||
|
||
// from interface
|
||
if (fc->sym->isInterfaceDeclaration()) {
|
||
Logger::println("interface cast");
|
||
return DtoDynamicCastInterface(loc, val, _to);
|
||
}
|
||
// from class
|
||
Logger::println("dynamic up cast");
|
||
return DtoDynamicCastObject(loc, val, _to);
|
||
}
|
||
|
||
bool DtoIsObjcLinkage(Type *_to) {
|
||
if (auto to = _to->isTypeClass()) {
|
||
DtoResolveClass(to->sym);
|
||
return to->sym->classKind == ClassKind::objc;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
static void resolveObjectAndClassInfoClasses() {
|
||
// check declarations in object.d
|
||
getObjectType();
|
||
getClassInfoType();
|
||
|
||
DtoResolveClass(ClassDeclaration::object);
|
||
DtoResolveClass(Type::typeinfoclass);
|
||
}
|
||
|
||
DValue *DtoDynamicCastObject(const Loc &loc, DValue *val, Type *_to) {
|
||
|
||
resolveObjectAndClassInfoClasses();
|
||
|
||
// Dynamic casting in Objective-C works differently from D.
|
||
// We call objc_opt_isKindOfClass to get a bool defining
|
||
// whether the cast is valid, if it is then we go ahead.
|
||
if (DtoIsObjcLinkage(_to)) {
|
||
llvm::Function *kindOfClassFunc =
|
||
getRuntimeFunction(loc, gIR->module, "objc_opt_isKindOfClass");
|
||
|
||
// Get the object.
|
||
LLValue *obj = DtoRVal(val);
|
||
|
||
// Get class_t handle
|
||
LLValue *objTy = getNullPtr();
|
||
if (auto thandle = _to->isClassHandle()) {
|
||
objTy = gIR->objc.deref(thandle, getOpaquePtrType());
|
||
}
|
||
|
||
// objc_opt_isKindOfClass will check if id is null
|
||
// by itself, so we don't need to add an extra check.
|
||
// objc_opt_isKindOfClass(id) ? id : null
|
||
LLValue *objCastable = gIR->CreateCallOrInvoke(kindOfClassFunc, obj, objTy);
|
||
LLValue *ret = gIR->ir->CreateSelect(objCastable, obj, getNullPtr());
|
||
return new DImValue(_to, ret);
|
||
}
|
||
|
||
// call:
|
||
// Object _d_dynamic_cast(Object o, ClassInfo c)
|
||
llvm::Function *func =
|
||
getRuntimeFunction(loc, gIR->module, "_d_dynamic_cast");
|
||
LLFunctionType *funcTy = func->getFunctionType();
|
||
|
||
// Object o
|
||
LLValue *obj = DtoRVal(val);
|
||
assert(funcTy->getParamType(0) == obj->getType());
|
||
|
||
// ClassInfo c
|
||
TypeClass *to = static_cast<TypeClass *>(_to->toBasetype());
|
||
DtoResolveClass(to->sym);
|
||
|
||
LLValue *cinfo = getIrAggr(to->sym)->getClassInfoSymbol();
|
||
assert(funcTy->getParamType(1) == cinfo->getType());
|
||
|
||
// call it
|
||
LLValue *ret = gIR->CreateCallOrInvoke(func, obj, cinfo);
|
||
|
||
return new DImValue(_to, ret);
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
DValue *DtoDynamicCastInterface(const Loc &loc, DValue *val, Type *_to) {
|
||
|
||
resolveObjectAndClassInfoClasses();
|
||
|
||
// Dynamic casting in Objective-C works differently from D.
|
||
// In this case we want to call the Objective-C runtime to first
|
||
// get a Class object from the `id`.
|
||
// Then check if class_conformsToProtocol returns true,
|
||
// if it does, then we can cast and return the casted value,
|
||
// otherwise return null.
|
||
if (DtoIsObjcLinkage(_to)) {
|
||
llvm::Function *getClassFunc =
|
||
getRuntimeFunction(loc, gIR->module, "object_getClass");
|
||
|
||
llvm::Function *kindOfProtocolFunc =
|
||
getRuntimeFunction(loc, gIR->module, "class_conformsToProtocol");
|
||
|
||
// id -> Class
|
||
LLValue *obj = DtoRVal(val);
|
||
LLValue *objClass = gIR->CreateCallOrInvoke(getClassFunc, obj);
|
||
|
||
// Get prototype_t handle
|
||
LLValue *protoTy = getNullPtr();
|
||
if (auto ifhndl = _to->isClassHandle()->isInterfaceDeclaration()) {
|
||
protoTy = gIR->objc.deref(ifhndl, getOpaquePtrType());
|
||
}
|
||
|
||
// Class && kindOfProtocolFunc(Class) ? id : null
|
||
LLValue *ret = gIR->ir->CreateSelect(
|
||
gIR->CreateCallOrInvoke(kindOfProtocolFunc, objClass, protoTy),
|
||
obj,
|
||
getNullPtr()
|
||
);
|
||
return new DImValue(_to, ret);
|
||
}
|
||
|
||
// call:
|
||
// Object _d_interface_cast(void* p, ClassInfo c)
|
||
llvm::Function *func =
|
||
getRuntimeFunction(loc, gIR->module, "_d_interface_cast");
|
||
|
||
// void* p
|
||
LLValue *ptr = DtoRVal(val);
|
||
|
||
// ClassInfo c
|
||
TypeClass *to = static_cast<TypeClass *>(_to->toBasetype());
|
||
DtoResolveClass(to->sym);
|
||
LLValue *cinfo = getIrAggr(to->sym)->getClassInfoSymbol();
|
||
|
||
// call it
|
||
LLValue *ret = gIR->CreateCallOrInvoke(func, ptr, cinfo);
|
||
|
||
return new DImValue(_to, ret);
|
||
}
|
||
|
||
////////////////////////////////////////////////////////////////////////////////
|
||
|
||
std::pair<llvm::Value *, llvm::Value *>
|
||
DtoVirtualFunctionPointer(DValue *inst, FuncDeclaration *fdecl) {
|
||
// sanity checks
|
||
assert(fdecl->isVirtual());
|
||
assert(!fdecl->isFinalFunc());
|
||
TypeClass *tc = inst->type->toBasetype()->isTypeClass();
|
||
assert(tc);
|
||
// slot 0 is always ClassInfo/Interface* unless it is a CPP class
|
||
assert(fdecl->vtblIndex > 0 ||
|
||
(fdecl->vtblIndex == 0 &&
|
||
inst->type->toBasetype()->isTypeClass()->sym->isCPPclass()));
|
||
|
||
// get instance
|
||
LLValue *vthis = DtoRVal(inst);
|
||
IF_LOG Logger::cout() << "vthis: " << *vthis << '\n';
|
||
|
||
const auto irtc = getIrType(tc->sym->type, true)->isClass();
|
||
const auto vtblType = irtc->getVtblType();
|
||
|
||
LLValue *vtable = vthis;
|
||
// get the vtbl for objects
|
||
vtable = DtoGEP(irtc->getMemoryLLType(), vthis, 0u, 0);
|
||
// load vtbl ptr
|
||
vtable = DtoLoad(vtblType->getPointerTo(), vtable);
|
||
// index vtbl
|
||
const std::string name = fdecl->toChars();
|
||
const auto vtblname = name + "@vtbl";
|
||
LLValue *funcval =
|
||
DtoGEP(vtblType, vtable, 0, fdecl->vtblIndex, vtblname.c_str());
|
||
// load opaque pointer.
|
||
funcval = DtoAlignedLoad(vtblType->getElementType(), funcval);
|
||
// Because vtables are immutable, LLVM's !invariant.load
|
||
// can be applied (helps with devirtualization).
|
||
llvm::cast<llvm::LoadInst>(funcval)->setMetadata(
|
||
"invariant.load", llvm::MDNode::get(gIR->context(), {}));
|
||
|
||
IF_LOG Logger::cout() << "funcval: " << *funcval << '\n';
|
||
|
||
// postpone naming until after casting to get the name in call instructions
|
||
funcval->setName(name);
|
||
|
||
IF_LOG Logger::cout() << "funcval casted: " << *funcval << '\n';
|
||
|
||
return std::make_pair(funcval, vtable);
|
||
}
|