diff --git a/CHANGELOG.md b/CHANGELOG.md index 07cf4bb05a..2dda9721aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - ldc2.conf: %%ldcconfigpath%% placeholder added - specifies the directory where current configuration file is located. (#4717) - Add support for building against a system copy of zlib through `-DPHOBOS_SYSTEM_ZLIB=ON`. (#4742) - Emscripten: The compiler now mimicks a musl Linux platform wrt. extra predefined versions (`linux`, `Posix`, `CRuntime_Musl`, `CppRuntime_LLVM`). (#4750) +- Objective-C: The compiler now properly supports Objective-C classes and protocols, as well as swift stub classes (via the `@swift` UDA). (#4777) #### Platform support - Supports LLVM 15 - 19. diff --git a/dmd/id.d b/dmd/id.d index 7820523c7d..dc18b03cc9 100644 --- a/dmd/id.d +++ b/dmd/id.d @@ -627,6 +627,7 @@ immutable Msgtable[] msgtable = { "udaHidden", "_hidden" }, { "udaNoSanitize", "noSanitize" }, { "udaNoSplitStack", "_noSplitStack" }, + { "udaSwiftStub", "swift"}, // IN_LLVM: DCompute specific types and functionss { "dcompute" }, diff --git a/dmd/objc.d b/dmd/objc.d index 2e18303e4d..b5f7ea3961 100644 --- a/dmd/objc.d +++ b/dmd/objc.d @@ -156,6 +156,9 @@ extern (C++) struct ObjcClassDeclaration /// `true` if this class is externally defined. bool isExtern = false; + /// `true` if this class is a Swift stub + version(IN_LLVM) bool isSwiftStub = false; + /// Name of this class. Identifier identifier; @@ -264,6 +267,16 @@ extern(C++) abstract class Objc */ abstract void setAsOptional(FuncDeclaration functionDeclaration, Scope* sc) const; + /** + * Marks the given class as a Swift stub class. + * + * Params: + * cd = the class declaration to set as a swift stub + * sc = the scope from the semantic phase + */ + version(IN_LLVM) + abstract void setAsSwiftStub(ClassDeclaration cd, Scope* sc) const; + /** * Validates function declarations declared optional. * @@ -452,6 +465,12 @@ static if (!IN_LLVM) // noop } + version(IN_LLVM) + override void setAsSwiftStub(ClassDeclaration, Scope*) const + { + // noop + } + override void validateOptional(FuncDeclaration) const { // noop @@ -532,6 +551,7 @@ version (IN_LLVM) {} else { cd.classKind = ClassKind.objc; cd.objc.isExtern = (cd.storage_class & STC.extern_) > 0; + this.setAsSwiftStub(cd, cd._scope); } override void setObjc(InterfaceDeclaration id) @@ -826,6 +846,42 @@ version (IN_LLVM) {} else errorSupplemental(expression.loc, "`tupleof` is not available for members " ~ "of Objective-C classes. Please use the Objective-C runtime instead"); } + + version(IN_LLVM) { + override void setAsSwiftStub(ClassDeclaration cd, Scope* sc) const + { + const count = declaredAsSwiftStubCount(cd, sc); + cd.objc.isSwiftStub = count > 0; + + if (count > 1) + .error(cd.loc, "%s `%s` can only declare a class as a swift stub once", cd.kind, cd.toPrettyChars); + } + + /// Returns: the number of times `cd` has been declared as optional. + private int declaredAsSwiftStubCount(ClassDeclaration cd, Scope* sc) const + { + int count; + + foreachUda(cd, sc, (e) { + if (!e.isTypeExp()) + return 0; + + auto typeExp = e.isTypeExp(); + + if (typeExp.type.ty != Tenum) + return 0; + + auto typeEnum = cast(TypeEnum) typeExp.type; + + if (isCoreUda(typeEnum.sym, Id.udaSwiftStub)) + count++; + + return 0; + }); + + return count; + } + } } /* diff --git a/dmd/objc.h b/dmd/objc.h index 0390115aeb..03b72dec4e 100644 --- a/dmd/objc.h +++ b/dmd/objc.h @@ -37,6 +37,9 @@ struct ObjcClassDeclaration { d_bool isMeta; d_bool isExtern; +#if IN_LLVM + d_bool isSwiftStub; +#endif Identifier* identifier; ClassDeclaration* classDeclaration; @@ -67,6 +70,9 @@ class Objc virtual void checkLinkage(FuncDeclaration* fd) = 0; virtual bool isVirtual(const FuncDeclaration*) const = 0; virtual void setAsOptional(FuncDeclaration *fd, Scope *sc) const = 0; +#if IN_LLVM + virtual void setAsSwiftStub(ClassDeclaration* cd, Scope *sc) const = 0; +#endif virtual void validateOptional(FuncDeclaration *fd) const = 0; virtual ClassDeclaration* getParent(FuncDeclaration*, ClassDeclaration*) const = 0; virtual void addToClassMethodList(FuncDeclaration*, ClassDeclaration*) const = 0; diff --git a/driver/linker-gcc.cpp b/driver/linker-gcc.cpp index 800e134b56..3f0033e2ec 100644 --- a/driver/linker-gcc.cpp +++ b/driver/linker-gcc.cpp @@ -48,6 +48,10 @@ static llvm::cl::opt linkNoCpp( "link-no-cpp", llvm::cl::ZeroOrMore, llvm::cl::Hidden, llvm::cl::desc("Disable automatic linking with the C++ standard library.")); +static llvm::cl::opt linkNoObjc( + "link-no-objc", llvm::cl::ZeroOrMore, llvm::cl::Hidden, + llvm::cl::desc("Disable automatic linking with the Objective-C runtime library.")); + ////////////////////////////////////////////////////////////////////////////// namespace { @@ -72,6 +76,7 @@ class ArgsBuilder { virtual void addXRayLinkFlags(const llvm::Triple &triple); virtual bool addCompilerRTArchiveLinkFlags(llvm::StringRef baseName, const llvm::Triple &triple); + virtual void addObjcStdlibLinkFlags(const llvm::Triple &triple); virtual void addLinker(); virtual void addUserSwitches(); @@ -467,6 +472,13 @@ void ArgsBuilder::addCppStdlibLinkFlags(const llvm::Triple &triple) { } } +void ArgsBuilder::addObjcStdlibLinkFlags(const llvm::Triple &triple) { + if (linkNoObjc) + return; + + args.push_back("-lobjc"); +} + // Adds all required link flags for PGO. void ArgsBuilder::addProfileRuntimeLinkFlags(const llvm::Triple &triple) { const auto searchPaths = @@ -732,6 +744,13 @@ void ArgsBuilder::addDefaultPlatformLibs() { break; } + if (triple.isOSDarwin()) { + + // libobjc is more or less required, so we link against it here. + // This could be prettier, though. + addObjcStdlibLinkFlags(triple); + } + if (triple.isWindowsGNUEnvironment()) { // This is really more of a kludge, as linking in the Winsock functions // should be handled by the pragma(lib, ...) in std.socket, but it diff --git a/gen/abi/aarch64.cpp b/gen/abi/aarch64.cpp index 285bc56668..2a9f58639e 100644 --- a/gen/abi/aarch64.cpp +++ b/gen/abi/aarch64.cpp @@ -29,12 +29,11 @@ using namespace dmd; */ struct AArch64TargetABI : TargetABI { private: - const bool isDarwin; IndirectByvalRewrite indirectByvalRewrite; ArgTypesRewrite argTypesRewrite; bool isAAPCS64VaList(Type *t) { - if (isDarwin) + if (isDarwin()) return false; // look for a __va_list struct in a `std` C++ namespace @@ -51,7 +50,7 @@ struct AArch64TargetABI : TargetABI { } public: - AArch64TargetABI() : isDarwin(global.params.targetTriple->isOSDarwin()) {} + AArch64TargetABI() {} bool returnInArg(TypeFunction *tf, bool) override { if (tf->isref()) { @@ -108,7 +107,7 @@ struct AArch64TargetABI : TargetABI { } // https://developer.apple.com/library/archive/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html#//apple_ref/doc/uid/TP40013702-SW1 - if (isDarwin) { + if (isDarwin()) { if (auto ts = tb->isTypeStruct()) { if (ts->sym->fields.empty() && ts->sym->isPOD()) { fty.args.erase(fty.args.begin() + i); @@ -166,7 +165,7 @@ struct AArch64TargetABI : TargetABI { } Type *vaListType() override { - if (isDarwin) + if (isDarwin()) return TargetABI::vaListType(); // char* // We need to pass the actual va_list type for correct mangling. Simply @@ -176,9 +175,11 @@ struct AArch64TargetABI : TargetABI { return TypeIdentifier::create(Loc(), Identifier::idPool("__va_list")); } - const char *objcMsgSendFunc(Type *ret, IrFuncTy &fty) override { + const char *objcMsgSendFunc(Type *ret, IrFuncTy &fty, bool directcall) override { + assert(isDarwin()); + // see objc/message.h for objc_msgSend selection rules - return "objc_msgSend"; + return directcall ? "objc_msgSendSuper" : "objc_msgSend"; } }; diff --git a/gen/abi/abi.cpp b/gen/abi/abi.cpp index c3f61f18da..b034f65072 100644 --- a/gen/abi/abi.cpp +++ b/gen/abi/abi.cpp @@ -207,7 +207,7 @@ Type *TargetABI::vaListType() { ////////////////////////////////////////////////////////////////////////////// -const char *TargetABI::objcMsgSendFunc(Type *ret, IrFuncTy &fty) { +const char *TargetABI::objcMsgSendFunc(Type *ret, IrFuncTy &fty, bool directcall) { llvm_unreachable("Unknown Objective-C ABI"); } diff --git a/gen/abi/abi.h b/gen/abi/abi.h index c1f69046a1..3545fb531a 100644 --- a/gen/abi/abi.h +++ b/gen/abi/abi.h @@ -70,6 +70,7 @@ struct ABIRewrite { // interface called by codegen struct TargetABI { +public: virtual ~TargetABI() = default; /// Returns the ABI for the target we're compiling for @@ -117,6 +118,11 @@ struct TargetABI { global.params.targetTriple->getOS() == llvm::Triple::NetBSD; } + /// Returns true if the target is darwin-based. + bool isDarwin() { + return global.params.targetTriple->isOSDarwin(); + } + /// Returns true if the D function uses sret (struct return). /// `needsThis` is true if the function type is for a non-static member /// function. @@ -171,7 +177,7 @@ struct TargetABI { virtual Type *vaListType(); /// Returns Objective-C message send function - virtual const char *objcMsgSendFunc(Type *ret, IrFuncTy &fty); + virtual const char *objcMsgSendFunc(Type *ret, IrFuncTy &fty, bool directcall); /***** Static Helpers *****/ diff --git a/gen/abi/arm.cpp b/gen/abi/arm.cpp index 8821ff8a34..ed4c311a22 100644 --- a/gen/abi/arm.cpp +++ b/gen/abi/arm.cpp @@ -122,14 +122,6 @@ struct ArmTargetABI : TargetABI { // solution is found there, this should be adapted). return TypeIdentifier::create(Loc(), Identifier::idPool("__va_list")); } - - const char *objcMsgSendFunc(Type *ret, IrFuncTy &fty) override { - // see objc/message.h for objc_msgSend selection rules - if (fty.arg_sret) { - return "objc_msgSend_stret"; - } - return "objc_msgSend"; - } }; TargetABI *getArmTargetABI() { return new ArmTargetABI; } diff --git a/gen/abi/x86-64.cpp b/gen/abi/x86-64.cpp index 1a49791df5..f73d81f043 100644 --- a/gen/abi/x86-64.cpp +++ b/gen/abi/x86-64.cpp @@ -165,7 +165,7 @@ struct X86_64TargetABI : TargetABI { Type *vaListType() override; - const char *objcMsgSendFunc(Type *ret, IrFuncTy &fty) override; + const char *objcMsgSendFunc(Type *ret, IrFuncTy &fty, bool directcall) override; private: LLType *getValistType(); @@ -196,9 +196,6 @@ struct X86_64TargetABI : TargetABI { } }; -// The public getter for abi.cpp -TargetABI *getX86_64TargetABI() { return new X86_64TargetABI; } - bool X86_64TargetABI::returnInArg(TypeFunction *tf, bool) { if (tf->isref()) { return false; @@ -382,21 +379,19 @@ Type *X86_64TargetABI::vaListType() { TypeIdentifier::create(Loc(), Identifier::idPool("__va_list_tag"))); } -const char *X86_64TargetABI::objcMsgSendFunc(Type *ret, - IrFuncTy &fty) { +const char *X86_64TargetABI::objcMsgSendFunc(Type *ret, IrFuncTy &fty, bool directcall) { + assert(isDarwin()); + // see objc/message.h for objc_msgSend selection rules if (fty.arg_sret) { - return "objc_msgSend_stret"; + return directcall ? "objc_msgSendSuper_stret" : "objc_msgSend_stret"; } - if (ret) { - // complex long double return - if (ret->ty == TY::Tcomplex80) { - return "objc_msgSend_fp2ret"; - } - // long double return - if (ret->ty == TY::Tfloat80 || ret->ty == TY::Timaginary80) { - return "objc_msgSend_fpret"; - } + // float, double, long double return + if (ret && ret->isfloating()) { + return ret->ty == TY::Tcomplex80 ? "objc_msgSend_fp2ret" : "objc_msgSend_fpret"; } - return "objc_msgSend"; + return directcall ? "objc_msgSendSuper" : "objc_msgSend"; } + +// The public getter for abi.cpp +TargetABI *getX86_64TargetABI() { return new X86_64TargetABI; } diff --git a/gen/abi/x86.cpp b/gen/abi/x86.cpp index cd9acbe76f..5e8cd9eb94 100644 --- a/gen/abi/x86.cpp +++ b/gen/abi/x86.cpp @@ -23,15 +23,13 @@ using namespace dmd; struct X86TargetABI : TargetABI { - const bool isDarwin; const bool isMSVC; bool returnStructsInRegs; IntegerRewrite integerRewrite; IndirectByvalRewrite indirectByvalRewrite; X86TargetABI() - : isDarwin(global.params.targetTriple->isOSDarwin()), - isMSVC(global.params.targetTriple->isWindowsMSVCEnvironment()) { + : isMSVC(global.params.targetTriple->isWindowsMSVCEnvironment()) { using llvm::Triple; auto os = global.params.targetTriple->getOS(); returnStructsInRegs = @@ -230,7 +228,7 @@ struct X86TargetABI : TargetABI { // Clang does not pass empty structs, while it seems that GCC does, // at least on Linux x86. We don't know whether the C compiler will // be Clang or GCC, so just assume Clang on Darwin and G++ on Linux. - if (externD || !isDarwin) + if (externD || !isDarwin()) return; size_t i = 0; @@ -272,19 +270,6 @@ struct X86TargetABI : TargetABI { } } } - - const char *objcMsgSendFunc(Type *ret, IrFuncTy &fty) override { - // see objc/message.h for objc_msgSend selection rules - assert(isDarwin); - if (fty.arg_sret) { - return "objc_msgSend_stret"; - } - // float, double, long double return - if (ret && ret->isfloating() && !ret->iscomplex()) { - return "objc_msgSend_fpret"; - } - return "objc_msgSend"; - } }; // The public getter for abi.cpp. diff --git a/gen/classes.cpp b/gen/classes.cpp index 5d686f084d..51a7a554f3 100644 --- a/gen/classes.cpp +++ b/gen/classes.cpp @@ -327,6 +327,15 @@ DValue *DtoCastClass(const Loc &loc, DValue *val, Type *_to) { 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() { @@ -339,15 +348,39 @@ static void resolveObjectAndClassInfoClasses() { } 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(); - resolveObjectAndClassInfoClasses(); - // Object o LLValue *obj = DtoRVal(val); assert(funcTy->getParamType(0) == obj->getType()); @@ -368,14 +401,46 @@ DValue *DtoDynamicCastObject(const Loc &loc, DValue *val, Type *_to) { //////////////////////////////////////////////////////////////////////////////// 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"); - resolveObjectAndClassInfoClasses(); - // void* p LLValue *ptr = DtoRVal(val); diff --git a/gen/classes.h b/gen/classes.h index 766844fe7c..28dcd765d9 100644 --- a/gen/classes.h +++ b/gen/classes.h @@ -39,6 +39,8 @@ DValue *DtoDynamicCastObject(const Loc &loc, DValue *val, Type *to); DValue *DtoDynamicCastInterface(const Loc &loc, DValue *val, Type *to); +bool DtoIsObjcLinkage(Type *to); + /// Returns pair of function pointer and vtable pointer. std::pair DtoVirtualFunctionPointer(DValue *inst, FuncDeclaration *fdecl); diff --git a/gen/declarations.cpp b/gen/declarations.cpp index 31f1ec7966..9613d190bc 100644 --- a/gen/declarations.cpp +++ b/gen/declarations.cpp @@ -109,6 +109,12 @@ class CodegenVisitor : public Visitor { m->accept(this); } + // Objective-C protocols don't have TypeInfo. + if (decl->classKind == ClassKind::objc) { + gIR->objc.getProtocol(decl); + return; + } + // Emit TypeInfo. IrClass *ir = getIrAggr(decl); if (!ir->suppressTypeInfo()) { @@ -205,6 +211,12 @@ class CodegenVisitor : public Visitor { m->accept(this); } + // Objective-C class structure is initialized by calling getClassRef. + if (decl->classKind == ClassKind::objc) { + gIR->objc.getClass(decl); + return; + } + IrClass *ir = getIrAggr(decl); ir->getInitSymbol(/*define=*/true); diff --git a/gen/functions.cpp b/gen/functions.cpp index cd6dac19e9..d88dc91ab0 100644 --- a/gen/functions.cpp +++ b/gen/functions.cpp @@ -140,16 +140,31 @@ llvm::FunctionType *DtoFunctionType(Type *type, IrFuncTy &irFty, Type *thistype, } bool hasObjCSelector = false; - if (fd && fd->_linkage == LINK::objc && thistype) { + if (fd && fd->_linkage == LINK::objc) { + auto ftype = (TypeFunction*)fd->type; + if (fd->objc.selector) { hasObjCSelector = true; } else if (fd->parent->isClassDeclaration()) { - error(fd->loc, "%s `%s` is missing Objective-C `@selector`", fd->kind(), - fd->toPrettyChars()); + if(fd->isFinal() || ftype->isproperty()) { + + // HACK: Ugly hack, but final functions for some reason don't actually declare a selector. + // However, this does make it more flexible. + // Also this will automatically generate selectors for @property declared + // functions, which the DIP specifies. + // Final function selector gen should be fixed, however. + fd->objc.selector = ObjcSelector::create(fd); + hasObjCSelector = true; + } else { + error(fd->loc, "%s `%s` is missing Objective-C `@selector`", fd->kind(), + fd->toPrettyChars()); + } } } if (hasObjCSelector) { - // TODO: make arg_objcselector to match dmd type + + // SEL is in libobjc an opaque pointer. + // As such a void* is fine. newIrFty.arg_objcSelector = new IrFuncTyArg(Type::tvoidptr, false); ++nextLLArgIdx; } diff --git a/gen/llvm.h b/gen/llvm.h index 6dc2c51541..5dfe68e9e9 100644 --- a/gen/llvm.h +++ b/gen/llvm.h @@ -65,3 +65,6 @@ using llvm::IRBuilder; #define LLConstantFP llvm::ConstantFP #define LLSmallVector llvm::SmallVector + +#define LLConstantList std::vector +#define LLStringRef llvm::StringRef \ No newline at end of file diff --git a/gen/llvmhelpers.cpp b/gen/llvmhelpers.cpp index 0cc63e70b8..d4818de899 100644 --- a/gen/llvmhelpers.cpp +++ b/gen/llvmhelpers.cpp @@ -1841,6 +1841,18 @@ DLValue *DtoIndexAggregate(LLValue *src, AggregateDeclaration *ad, // ourselves, DtoType below would be enough. DtoResolveDsymbol(ad); + if (ad->classKind == ClassKind::objc) { + auto tHandle = getI32Type(); + auto tOffset = DtoLoad(tHandle, gIR->objc.getIvar(vd)->offset); + + // Offset is now stored in tOffset. + LLValue *ptr = src; + ptr = DtoBitCast(ptr, getOpaquePtrType()); + ptr = DtoGEP1(getI8Type(), ptr, tOffset); + + return new DLValue(vd->type, ptr); + } + // Look up field to index or offset to apply. auto irTypeAggr = getIrType(ad->type)->isAggr(); assert(irTypeAggr); diff --git a/gen/llvmhelpers.h b/gen/llvmhelpers.h index fdf1f5373c..23ad6b9e59 100644 --- a/gen/llvmhelpers.h +++ b/gen/llvmhelpers.h @@ -205,7 +205,7 @@ bool DtoLowerMagicIntrinsic(IRState *p, FuncDeclaration *fndecl, CallExp *e, /// DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval, - Expressions *arguments, LLValue *sretPointer = nullptr); + Expressions *arguments, LLValue *sretPointer = nullptr, bool directcall = false); Type *stripModifiers(Type *type, bool transitive = false); diff --git a/gen/objcgen.cpp b/gen/objcgen.cpp index a85ee68dd8..9e9fe28bfb 100644 --- a/gen/objcgen.cpp +++ b/gen/objcgen.cpp @@ -10,122 +10,909 @@ //===----------------------------------------------------------------------===// #include "gen/objcgen.h" - -#include "dmd/mtype.h" #include "dmd/objc.h" +#include "dmd/expression.h" +#include "dmd/declaration.h" +#include "dmd/identifier.h" #include "gen/irstate.h" - -namespace { -enum ABI { none = 0, fragile = 1, nonFragile = 2 }; -ABI abi = nonFragile; -} +#include "gen/runtime.h" +#include "ir/irfunction.h" bool objc_isSupported(const llvm::Triple &triple) { if (triple.isOSDarwin()) { + // Objective-C only supported on Darwin at this time + // Additionally only Objective-C 2 is supported. switch (triple.getArch()) { - case llvm::Triple::aarch64: // arm64 iOS, tvOS - case llvm::Triple::arm: // armv6 iOS - case llvm::Triple::thumb: // thumbv7 iOS, watchOS + case llvm::Triple::aarch64: // arm64 iOS, tvOS, macOS, watchOS, visionOS case llvm::Triple::x86_64: // OSX, iOS, tvOS sim - abi = nonFragile; - return true; - case llvm::Triple::x86: // OSX, iOS, watchOS sim - abi = fragile; return true; default: - break; + return false; } } return false; } -LLGlobalVariable *ObjCState::getCStringVar(const char *symbol, - const llvm::StringRef &str, - const char *section) { - auto init = llvm::ConstantDataArray::getString(module.getContext(), str); - auto var = new LLGlobalVariable(module, init->getType(), false, - LLGlobalValue::PrivateLinkage, init, symbol); - var->setSection(section); - return var; +// TYPE ENCODINGS +std::string objcGetTypeEncoding(Type *t) { + std::string tmp; + switch (t->ty) { + case TY::Tclass: { + if (auto klass = t->isTypeClass()) { + return klass->sym->classKind == ClassKind::objc ? "@" : "?"; + } + return "?"; + } + case TY::Tfunction: { + tmp = objcGetTypeEncoding(t->nextOf()); + tmp.append("@:"); + + if (auto func = t->isTypeFunction()) { + for (size_t i = 0; i < func->parameterList.length(); i++) + tmp.append(objcGetTypeEncoding(func->parameterList[i]->type)); + } + return tmp; + } + case TY::Tpointer: { + + // C string (char*) + if (t->nextOf()->ty == TY::Tchar) + return "*"; + + tmp.append("^"); + tmp.append(objcGetTypeEncoding(t->nextOf())); + return tmp; + } + case TY::Tsarray: { + + // Static arrays are encoded in the form of: + // [] + auto typ = t->isTypeSArray(); + uinteger_t count = typ->dim->toUInteger(); + tmp.append("["); + tmp.append(std::to_string(count)); + tmp.append(objcGetTypeEncoding(typ->next)); + tmp.append("]"); + return tmp; + } + case TY::Tstruct: { + + // Structs are encoded in the form of: + // {=} + // Unions are encoded as + // (=) + auto sym = t->isTypeStruct()->sym; + bool isUnion = sym->isUnionDeclaration(); + + tmp.append(isUnion ? "(" : "{"); + tmp.append(t->toChars()); + tmp.append("="); + + for(unsigned int i = 0; i < sym->numArgTypes(); i++) { + tmp.append(objcGetTypeEncoding(sym->argType(i))); + } + + tmp.append(isUnion ? ")" : "}"); + return tmp; + } + case TY::Tvoid: return "v"; + case TY::Tbool: return "B"; + case TY::Tint8: return "c"; + case TY::Tuns8: return "C"; + case TY::Tchar: return "C"; + case TY::Tint16: return "s"; + case TY::Tuns16: return "S"; + case TY::Twchar: return "S"; + case TY::Tint32: return "i"; + case TY::Tuns32: return "I"; + case TY::Tdchar: return "I"; + case TY::Tint64: return "q"; + case TY::Tuns64: return "Q"; + case TY::Tfloat32: return "f"; + case TY::Tcomplex32: return "jf"; + case TY::Tfloat64: return "d"; + case TY::Tcomplex64: return "jd"; + case TY::Tfloat80: return "D"; + case TY::Tcomplex80: return "jD"; + default: return "?"; // unknown + } +} + +// +// STRING HELPERS +// + +std::string objcGetClassRoSymbol(const char *name, bool meta) { + return objcGetSymbolName(meta ? "_OBJC_METACLASS_RO_$_" : "_OBJC_CLASS_RO_$_", name); +} + +std::string objcGetClassSymbol(const char *name, bool meta) { + return objcGetSymbolName(meta ? "OBJC_METACLASS_$_" : "OBJC_CLASS_$_", name); +} + +std::string objcGetClassLabelSymbol(const char *name) { + return objcGetSymbolName("OBJC_LABEL_CLASS_$_", name); +} + +std::string objcGetClassMethodListSymbol(const char *className, bool meta) { + return objcGetSymbolName(meta ? "_OBJC_$_CLASS_METHODS_" : "_OBJC_$_INSTANCE_METHODS_", className); +} + +std::string objcGetProtoMethodListSymbol(const char *className, bool meta, bool optional) { + return optional ? + objcGetSymbolName(meta ? "_OBJC_$_PROTOCOL_CLASS_METHODS_OPT_" : "_OBJC_$_PROTOCOL_INSTANCE_METHODS_OPT_", className) : + objcGetSymbolName(meta ? "_OBJC_$_PROTOCOL_CLASS_METHODS_" : "_OBJC_$_PROTOCOL_INSTANCE_METHODS_", className); +} + +std::string objcGetIvarListSymbol(const char *className) { + return objcGetSymbolName("_OBJC_$_INSTANCE_VARIABLES_", className); +} + +std::string objcGetProtoSymbol(const char *name) { + return objcGetSymbolName("_OBJC_PROTOCOL_$_", name); +} + +std::string objcGetProtoListSymbol(const char *name, bool isProtocol) { + return objcGetSymbolName(isProtocol ? "_OBJC_$_PROTOCOL_REFS_" : "_OBJC_CLASS_PROTOCOLS_$_", name); +} + +std::string objcGetProtoLabelSymbol(const char *name) { + return objcGetSymbolName("_OBJC_LABEL_PROTOCOL_$_", name); +} + +std::string objcGetIvarSymbol(const char *className, const char *varName) { + return ("OBJC_IVAR_$_" + std::string(className) + "." + std::string(varName)); +} + +std::string objcGetSymbolName(const char *dsymPrefix, const char *dsymName) { + return (std::string(dsymPrefix) + std::string(dsymName)); +} + +const char *objcResolveName(Dsymbol *decl) { + + // Function names are based on selector. + if (auto funcdecl = decl->isFuncDeclaration()) { + return funcdecl->objc.selector->stringvalue; + } + + // Class and interface names are determined by objc identifier. + if (auto classdecl = decl->isClassDeclaration()) { + return classdecl->objc.identifier->toChars(); + } + + return decl->ident->toChars(); +} + +// +// TYPE HELPERS +// + +LLStructType *objcGetStubClassType(const llvm::Module& module) { + auto stubClassType = LLStructType::getTypeByName(module.getContext(), OBJC_STRUCTNAME_STUBCLASS); + if (stubClassType) + return stubClassType; + + stubClassType = LLStructType::create( + module.getContext(), + { + getOpaquePtrType(), // objc_object* isa + getOpaquePtrType(), // function pointer. + }, + OBJC_STRUCTNAME_STUBCLASS + ); + return stubClassType; +} + +LLStructType *objcGetClassRoType(const llvm::Module& module) { + auto classRoType = LLStructType::getTypeByName(module.getContext(), OBJC_STRUCTNAME_CLASSRO); + if (classRoType) + return classRoType; + + classRoType = LLStructType::create( + module.getContext(), + { + getI32Type(), // uint32_t flags + getI32Type(), // uint32_t instanceStart + getI32Type(), // uint32_t instanceSize + getOpaquePtrType(), // void* layoutOrNonMetaClass + getOpaquePtrType(), // const char* name + getOpaquePtrType(), // method_list_t* baseMethods + getOpaquePtrType(), // protocol_list_t* baseProtocols + getOpaquePtrType(), // ivar_list_t* ivars + getOpaquePtrType(), // const uint8_t* weakIvarLayout + getOpaquePtrType(), // property_list_t* baseProperties + }, + OBJC_STRUCTNAME_CLASSRO + ); + return classRoType; +} + +LLStructType *objcGetClassType(const llvm::Module& module) { + auto classType = LLStructType::getTypeByName(module.getContext(), OBJC_STRUCTNAME_CLASS); + if (classType) + return classType; + + classType = LLStructType::create( + module.getContext(), + { + getOpaquePtrType(), // objc_object* isa + getOpaquePtrType(), // objc_object* superclass + getOpaquePtrType(), // cache_t* cache + getOpaquePtrType(), // void* vtbl; (unused, set to null) + getOpaquePtrType(), // class_ro_t* ro + }, + OBJC_STRUCTNAME_CLASS + ); + return classType; +} + +LLStructType *objcGetMethodType(const llvm::Module& module) { + auto methType = LLStructType::getTypeByName(module.getContext(), OBJC_STRUCTNAME_METHOD); + if (methType) + return methType; + + return LLStructType::create( + module.getContext(), + { + getOpaquePtrType(), // SEL name + getOpaquePtrType(), // const char *types + getOpaquePtrType(), // IMP imp + }, + OBJC_STRUCTNAME_METHOD + ); +} + +LLStructType *objcGetIvarType(const llvm::Module& module) { + auto ivarType = LLStructType::getTypeByName(module.getContext(), OBJC_STRUCTNAME_IVAR); + if (ivarType) + return ivarType; + + ivarType = LLStructType::create( + module.getContext(), + { + getOpaquePtrType(), // int32_t *offset + getOpaquePtrType(), // const char *name + getOpaquePtrType(), // const char *type + getI32Type(), // uint32_t alignment_raw + getI32Type(), // uint32_t size + }, + OBJC_STRUCTNAME_IVAR + ); + return ivarType; +} + +LLStructType *objcGetProtocolType(const llvm::Module& module) { + auto protoType = LLStructType::getTypeByName(module.getContext(), OBJC_STRUCTNAME_PROTO); + if (protoType) + return protoType; + + protoType = LLStructType::create( + module.getContext(), + { + getOpaquePtrType(), // objc_object* isa + getOpaquePtrType(), // protocol_list_t* protocols + getOpaquePtrType(), // const char *mangledName + getOpaquePtrType(), // method_list_t* instanceMethods + getOpaquePtrType(), // method_list_t* classMethods + getOpaquePtrType(), // method_list_t* optionalInstanceMethods + getOpaquePtrType(), // method_list_t* optionalClassMethods + getOpaquePtrType(), // property_list_t* instanceProperties + getI32Type(), // uint32_t size + getI32Type(), // uint32_t flags + + // Further fields follow but are optional and are fixed up at + // runtime. + }, + OBJC_STRUCTNAME_PROTO + ); + return protoType; +} + +// +// *_list_t helpers. +// + +LLConstant *objcEmitList(llvm::Module &module, LLConstantList objects, bool alignSizeT, bool countOnly) { + LLConstantList members; + + // Emit nullptr for empty lists. + if (objects.empty()) + return nullptr; + + if (!countOnly) { + + // Size of stored struct. + size_t allocSize = getTypeAllocSize(objects.front()->getType()); + members.push_back( + alignSizeT ? + DtoConstSize_t(allocSize) : + DtoConstUint(allocSize) + ); + } + + // Object count + size_t objCount = objects.size(); + members.push_back( + alignSizeT ? + DtoConstSize_t(objCount) : + DtoConstUint(objCount) + ); + + // Insert all the objects in to a constant array. + // This matches the codegen by the objective-c compiler. + auto arrayType = LLArrayType::get( + objects.front()->getType(), + objects.size() + ); + members.push_back(LLConstantArray::get( + arrayType, + objects + )); + + return LLConstantStruct::getAnon( + members, + true + ); +} + +// +// Other helpers +// + +LLConstant *objcOffsetIvar(size_t ivaroffset) { + return DtoConstUint(getPointerSize()+ivaroffset); +} + +size_t objcGetClassFlags(ClassDeclaration *decl) { + size_t flags = 0; + if (!decl->baseClass) + flags |= RO_ROOT; + + if (decl->objc.isMeta) + flags |= RO_META; + + return flags; +} + +ClassDeclaration *objcGetMetaClass(ClassDeclaration *decl) { + if (decl->objc.isMeta) { + auto curr = decl; + while (curr->baseClass) + curr = curr->baseClass; + + return curr; + } + + // Meta class for normal class. + return decl->objc.metaclass; +} + +ClassDeclaration *objcGetSuper(ClassDeclaration *decl) { + return (decl->objc.isRootClass() || !decl->baseClass) ? + decl : + decl->baseClass; +} + +// +// CLASSES +// + +ptrdiff_t objcGetInstanceStart(llvm::Module &module, ClassDeclaration *decl, bool meta) { + ptrdiff_t start = meta ? + getTypeAllocSize(objcGetClassType(module)) : + getPointerSize(); + + // Meta-classes have no body. + if (meta || !decl->members || decl->members->length == 0) + return start; + + for(d_size_t idx = 0; idx < decl->members->length; idx++) + { + auto var = (*decl->members)[idx]->isVarDeclaration(); + + if (var && var->isField()) + return start+var->offset; + } + return start; +} + +size_t objcGetInstanceSize(llvm::Module &module, ClassDeclaration *decl, bool meta) { + size_t start = meta ? + getTypeAllocSize(objcGetClassType(module)) : + getPointerSize(); + + if (meta) + return start; + + return start+decl->size(decl->loc); +} + +// Gets the empty cache variable, and creates a reference to it +// if needed. +LLGlobalVariable *getEmptyCache() { + static LLGlobalVariable *objcCache; + if(!objcCache) + objcCache = makeGlobal("_objc_empty_cache", nullptr, "", true, false); + return objcCache; } -LLGlobalVariable *ObjCState::getMethVarName(const llvm::StringRef &name) { - auto it = methVarNameMap.find(name); - if (it != methVarNameMap.end()) { +LLConstant *ObjCState::getClassRoTable(ClassDeclaration *decl) { + if (auto it = classRoTables.find(decl); it != classRoTables.end()) { return it->second; } + + // No need to generate RO tables for externs. + // nor for null declarations. + if (!decl || decl->objc.isExtern) + return getNullPtr(); + + // Base Methods + auto meta = decl->objc.isMeta; + auto name = objcResolveName(decl); + auto sym = objcGetClassRoSymbol(name, meta); + + LLConstantList members; + LLGlobalVariable *ivarList = nullptr; + LLGlobalVariable *protocolList = nullptr; + LLGlobalVariable *methodList = nullptr; + + if (auto baseMethods = createMethodList(decl)) { + methodList = getOrCreate(objcGetClassMethodListSymbol(name, meta), baseMethods->getType(), OBJC_SECNAME_CONST); + methodList->setInitializer(baseMethods); + } + + // Base Protocols + if (auto baseProtocols = createProtocolList(decl)) { + protocolList = getOrCreate(objcGetProtoListSymbol(name, false), baseProtocols->getType(), OBJC_SECNAME_CONST); + protocolList->setInitializer(baseProtocols); + } + + if (!meta) { + + // Instance variables + if (auto baseIvars = createIvarList(decl)) { + ivarList = getOrCreate(objcGetIvarListSymbol(name), baseIvars->getType(), OBJC_SECNAME_CONST); + ivarList->setInitializer(baseIvars); + } + } - auto var = getCStringVar("OBJC_METH_VAR_NAME_", name, - abi == nonFragile - ? "__TEXT,__objc_methname,cstring_literals" - : "__TEXT,__cstring,cstring_literals"); - methVarNameMap[name] = var; - retain(var); - return var; + // Build struct. + members.push_back(DtoConstUint(objcGetClassFlags(decl))); + members.push_back(DtoConstUint(objcGetInstanceStart(module, decl, meta))); + members.push_back(DtoConstUint(objcGetInstanceSize(module, decl, meta))); + members.push_back(getNullPtr()); + members.push_back(getClassName(decl)); + members.push_back(wrapNull(methodList)); + members.push_back(wrapNull(protocolList)); + members.push_back(wrapNull(ivarList)); + members.push_back(getNullPtr()); + members.push_back(getNullPtr()); + + auto table = makeGlobalWithBytes(sym, members, objcGetClassRoType(module)); + table->setSection(OBJC_SECNAME_DATA); + + classRoTables[decl] = table; + this->retain(table); + return table; +} + +LLConstant *ObjCState::getClassTable(ClassDeclaration *decl) { + if (auto it = classTables.find(decl); it != classTables.end()) { + return it->second; + } + + // If decl is null, just return a null pointer. + if (!decl) + return getNullPtr(); + + auto name = objcResolveName(decl); + auto sym = objcGetClassSymbol(name, decl->objc.isMeta); + + auto table = getOrCreate(sym, objcGetClassType(module), OBJC_SECNAME_DATA, decl->objc.isExtern); + classTables[decl] = table; + this->retain(table); + + // Extern tables don't need a body. + if (decl->objc.isExtern) + return table; + + LLConstantList members; + members.push_back(getClassTable(objcGetMetaClass(decl))); // isa + members.push_back(getClassTable(objcGetSuper(decl))); // super + members.push_back(getEmptyCache()); // cache + members.push_back(getNullPtr()); // vtbl + members.push_back(getClassRoTable(decl)); // ro + table->setInitializer(LLConstantStruct::get( + objcGetClassType(module), + members + )); + return table; +} + +ObjcClassInfo *ObjCState::getClass(ClassDeclaration *decl) { + assert(!decl->isInterfaceDeclaration() && "Attempted to pass protocol into getClass!"); + if (auto it = classes.find(decl); it != classes.end()) { + return &classes[decl]; + } + + // Since we may end up referring to this very quickly + // the name should be assigned ASAP. + classes[decl] = { .decl = decl }; + auto classInfo = &classes[decl]; + + classInfo->table = (LLGlobalVariable *)getClassTable(decl); + classInfo->name = (LLGlobalVariable *)getClassName(decl); + classInfo->ref = (LLGlobalVariable *)getClassRef(decl); + + if (!decl->objc.isMeta) + classInfo->ref->setInitializer(classInfo->table); + + return classInfo; } -LLGlobalVariable *ObjCState::getMethVarRef(const ObjcSelector &sel) { - llvm::StringRef s(sel.stringvalue, sel.stringlen); - auto it = methVarRefMap.find(s); - if (it != methVarRefMap.end()) { +LLConstant *ObjCState::getClassName(ClassDeclaration *decl) { + LLStringRef className(objcResolveName(decl)); + if (auto it = classNames.find(className); it != classNames.end()) { return it->second; } - auto gvar = getMethVarName(s); - auto selref = new LLGlobalVariable( - module, gvar->getType(), - false, // prevent const elimination optimization - LLGlobalValue::PrivateLinkage, gvar, "OBJC_SELECTOR_REFERENCES_", nullptr, - LLGlobalVariable::NotThreadLocal, 0, - true); // externally initialized - selref->setSection( - abi == nonFragile - ? "__DATA,__objc_selrefs,literal_pointers,no_dead_strip" - : "__OBJC,__message_refs,literal_pointers,no_dead_strip"); + auto retval = makeGlobalStr(className, "OBJC_CLASS_NAME_", OBJC_SECNAME_CLASSNAME); + classNames[className] = retval; + this->retain(retval); + return retval; +} + +LLConstant *ObjCState::getClassRef(ClassDeclaration *decl) { + LLStringRef className(objcResolveName(decl)); + if (auto it = classRefs.find(className); it != classRefs.end()) { + return it->second; + } + + auto retval = makeGlobal("OBJC_CLASSLIST_REFERENCES_$_", getOpaquePtrType(), OBJC_SECNAME_CLASSREFS); + classRefs[className] = retval; + this->retain(retval); + return retval; +} + + +// +// PROTOCOLS +// + +LLConstant *ObjCState::createProtocolTable(InterfaceDeclaration *decl) { + LLConstantList members; + LLGlobalVariable *protocolList = nullptr; + LLGlobalVariable *classMethodList = nullptr; + LLGlobalVariable *instanceMethodList = nullptr; + LLGlobalVariable *optClassMethodList = nullptr; + LLGlobalVariable *optInstanceMethodList = nullptr; + + auto protoInfo = &protocols[decl]; + auto name = objcResolveName(decl); + + // Base Protocols + if (auto baseProtocols = createProtocolList(decl)) { + auto sym = objcGetProtoListSymbol(name, true); + + protocolList = getOrCreateWeak(sym, baseProtocols->getType(), OBJC_SECNAME_CONST); + protocolList->setInitializer(baseProtocols); + } + + // Instance methods + if (auto instanceMethodConsts = createMethodList(decl, false)) { + auto sym = objcGetProtoMethodListSymbol(name, false, false); + instanceMethodList = makeGlobal(sym, instanceMethodConsts->getType(), OBJC_SECNAME_CONST); + instanceMethodList->setInitializer(instanceMethodConsts); + + instanceMethodList->setLinkage(llvm::GlobalValue::LinkageTypes::WeakAnyLinkage); + instanceMethodList->setVisibility(llvm::GlobalValue::VisibilityTypes::HiddenVisibility); + } + + // Optional instance methods + if (auto optInstanceMethodConsts = createMethodList(decl, true)) { + auto sym = objcGetProtoMethodListSymbol(name, false, true); + optInstanceMethodList = makeGlobal(sym, optInstanceMethodConsts->getType(), OBJC_SECNAME_CONST); + optInstanceMethodList->setInitializer(optInstanceMethodConsts); + + optInstanceMethodList->setLinkage(llvm::GlobalValue::LinkageTypes::WeakAnyLinkage); + optInstanceMethodList->setVisibility(llvm::GlobalValue::VisibilityTypes::HiddenVisibility); + } + + // Class methods + if (auto classMethodConsts = createMethodList(decl->objc.metaclass, false)) { + auto sym = objcGetProtoMethodListSymbol(name, true, false); + classMethodList = makeGlobal(sym, classMethodConsts->getType(), OBJC_SECNAME_CONST); + classMethodList->setInitializer(classMethodConsts); + + classMethodList->setLinkage(llvm::GlobalValue::LinkageTypes::WeakAnyLinkage); + classMethodList->setVisibility(llvm::GlobalValue::VisibilityTypes::HiddenVisibility); + } + + // Optional class methods + if (auto optClassMethodConsts = createMethodList(decl->objc.metaclass, true)) { + auto sym = objcGetProtoMethodListSymbol(name, true, true); + optClassMethodList = makeGlobal(sym, optClassMethodConsts->getType(), OBJC_SECNAME_CONST); + optClassMethodList->setInitializer(optClassMethodConsts); + + optClassMethodList->setLinkage(llvm::GlobalValue::LinkageTypes::WeakAnyLinkage); + optClassMethodList->setVisibility(llvm::GlobalValue::VisibilityTypes::HiddenVisibility); + } + + auto protoType = objcGetProtocolType(module); + auto allocSize = getTypeAllocSize(protoType); + + members.push_back(getNullPtr()); // isa + members.push_back(protoInfo->name); // mangledName + members.push_back(wrapNull(protocolList)); // protocols + members.push_back(wrapNull(instanceMethodList)); // instanceMethods + members.push_back(wrapNull(classMethodList)); // classMethods + members.push_back(wrapNull(optInstanceMethodList)); // optionalInstanceMethods + members.push_back(wrapNull(optClassMethodList)); // optionalClassMethods + members.push_back(getNullPtr()); // instanceProperties (TODO) + members.push_back(DtoConstUint(allocSize)); // size + members.push_back(DtoConstUint(0)); // flags + + return LLConstantStruct::getAnon( + members, + true + ); +} + +ObjcProtocolInfo *ObjCState::getProtocol(InterfaceDeclaration *decl) { + assert(decl->isInterfaceDeclaration() && "Attempted to pass class into getProtocol!"); + if (auto it = protocols.find(decl); it != protocols.end()) { + return &protocols[decl]; + } + + protocols[decl] = { .decl = decl }; + auto protoInfo = &protocols[decl]; + + auto name = objcResolveName(decl); + auto protoName = objcGetProtoSymbol(name); + auto protoLabel = objcGetProtoLabelSymbol(name); + protoInfo->name = makeGlobalStr(name, "OBJC_CLASS_NAME_", OBJC_SECNAME_CLASSNAME); + + // We want it to be locally hidden and weak since the protocols + // may be declared in multiple object files. + auto protoTableConst = createProtocolTable(decl); + protoInfo->table = getOrCreateWeak(protoName, protoTableConst->getType(), OBJC_SECNAME_DATA); + protoInfo->table->setInitializer(protoTableConst); - // Save for later lookup and prevent optimizer elimination - methVarRefMap[s] = selref; - retain(selref); + protoInfo->ref = getOrCreateWeak(protoLabel, getOpaquePtrType(), OBJC_SECNAME_PROTOLIST); + protoInfo->ref->setInitializer(protoInfo->table); - return selref; + this->retain(protoInfo->table); + this->retain(protoInfo->ref); + return protoInfo; } -void ObjCState::retain(LLConstant *sym) { - retainedSymbols.push_back(sym); +LLConstant *ObjCState::createProtocolList(ClassDeclaration *decl) { + LLConstantList protoList; + auto ifaces = decl->interfaces; + + // Protocols + for(size_t i = 0; i < ifaces.length; i++) { + if (auto iface = ifaces.ptr[i]) { + if (auto ifacesym = (InterfaceDeclaration *)iface->sym) { + + // Only add interfaces which have objective-c linkage + // TODO: throw an error if you try to include a non-objective-c interface? + if (ifacesym->classKind == ClassKind::objc) { + if (auto proto = getProtocol(ifacesym)) { + protoList.push_back(proto->table); + } + } + } + } + } + + return objcEmitList(module, protoList, true, true); } +// +// METHODS +// + +ObjcMethodInfo *ObjCState::getMethod(FuncDeclaration *decl) { + if (auto it = methods.find(decl); it != methods.end()) { + return &methods[decl]; + } + + // Skip functions not marked as extern(Objective-C). + if (decl->_linkage != LINK::objc) + return nullptr; + + methods[decl] = { .decl = decl }; + auto methodInfo = &methods[decl]; + + auto name = objcResolveName(decl); + auto type = objcGetTypeEncoding(decl->type); + methodInfo->name = makeGlobalStr(name, "OBJC_METH_VAR_NAME_", OBJC_SECNAME_METHNAME); + methodInfo->type = makeGlobalStr(type, "OBJC_METH_VAR_TYPE_", OBJC_SECNAME_METHTYPE); + methodInfo->selector = makeGlobalRef(methodInfo->name, "OBJC_SELECTOR_REFERENCES_", OBJC_SECNAME_SELREFS, false, true); + methodInfo->llfunction = decl->fbody ? + DtoBitCast(DtoCallee(decl), getOpaquePtrType()) : + getNullPtr(); + + this->retain(methodInfo->name); + this->retain(methodInfo->type); + this->retain(methodInfo->selector); + + return &methods[decl]; +} + +LLConstant *ObjCState::createMethodInfo(FuncDeclaration *decl) { + auto method = getMethod(decl); + return LLConstantStruct::get( + objcGetMethodType(module), + { method->name, method->type, method->llfunction } + ); +} + +LLConstant *ObjCState::createMethodList(ClassDeclaration *decl, bool optional) { + LLConstantList methodList; + + if (decl) { + + auto methodDeclList = getMethodsForType(decl, optional); + for(auto func : methodDeclList) { + methodList.push_back(createMethodInfo(func)); + } + } + return objcEmitList(module, methodList, false); +} + + +// +// INSTANCE VARIABLES +// + +ObjcIvarInfo* ObjCState::getIvar(VarDeclaration *decl) { + if (auto it = ivars.find(decl); it != ivars.end()) { + return &ivars[decl]; + } + + if (auto klass = decl->parent->isClassDeclaration()) { + auto ivarsym = objcGetIvarSymbol(objcResolveName(decl->parent), objcResolveName(decl)); + ivars[decl] = { .decl = decl }; + auto ivarInfo = &ivars[decl]; + + // Extern classes should generate globals + // which can be filled out by the Objective-C runtime. + if (klass->objc.isExtern) { + ivarInfo->name = makeGlobal("OBJC_METH_VAR_NAME_", nullptr, OBJC_SECNAME_METHNAME, true, true); + ivarInfo->type = makeGlobal("OBJC_METH_VAR_TYPE_", nullptr, OBJC_SECNAME_METHTYPE, true, true); + + // It will be filled out by the runtime, but make sure it's there nontheless. + ivarInfo->offset = getOrCreate(ivarsym, getI32Type(), OBJC_SECNAME_IVAR); + ivarInfo->offset->setInitializer(objcOffsetIvar(0)); + + this->retain(ivarInfo->name); + this->retain(ivarInfo->type); + this->retain(ivarInfo->offset); + return &ivars[decl]; + } + + // Non-extern ivars should emit all the data so that the + // objective-c runtime has a starting point. + // the offset *WILL* change during runtime! + ivarInfo->name = makeGlobalStr(decl->ident->toChars(), "OBJC_METH_VAR_NAME_", OBJC_SECNAME_METHNAME); + ivarInfo->type = makeGlobalStr(objcGetTypeEncoding(decl->type), "OBJC_METH_VAR_TYPE_", OBJC_SECNAME_METHTYPE); + ivarInfo->offset = getOrCreate(ivarsym, getI32Type(), OBJC_SECNAME_IVAR); + ivarInfo->offset->setInitializer(objcOffsetIvar(decl->offset)); + + this->retain(ivarInfo->name); + this->retain(ivarInfo->type); + this->retain(ivarInfo->offset); + return &ivars[decl]; + } + + return nullptr; +} + +LLConstant *ObjCState::createIvarInfo(VarDeclaration *decl) { + auto ivar = getIvar(decl); + LLConstantList members; + + members.push_back(ivar->offset); + members.push_back(ivar->name); + members.push_back(ivar->type); + members.push_back(DtoConstUint(decl->alignment.isDefault() ? -1 : decl->alignment.get())); + members.push_back(DtoConstUint(decl->size(decl->loc))); + + return LLConstantStruct::get( + objcGetIvarType(module), + members + ); +} + +LLConstant *ObjCState::createIvarList(ClassDeclaration *decl) { + LLConstantList ivarList; + + for(auto field : decl->fields) { + ivarList.push_back(createIvarInfo(field)); + } + return objcEmitList(module, ivarList, false); +} + +// +// HELPERS +// +LLValue *ObjCState::deref(ClassDeclaration *decl, LLType *as) { + + // Protocols can also have static functions + // as such we need to also be able to dereference them. + if (auto proto = decl->isInterfaceDeclaration()) { + return DtoLoad(as, getProtocol(proto)->ref); + } + + // Classes may be class stubs. + // in that case, we need to call objc_loadClassRef instead of just + // loading from the classref. + auto classref = getClass(decl)->ref; + if (decl->objc.isExtern && decl->objc.isSwiftStub) { + auto loadClassFunc = getRuntimeFunction(decl->loc, module, "objc_loadClassRef"); + return DtoBitCast( + gIR->CreateCallOrInvoke(loadClassFunc, classref, ""), + as + ); + } + + return DtoLoad(as, classref); +} + +ObjcList ObjCState::getMethodsForType(ClassDeclaration *decl, bool optional) { + ObjcList funcs; + bool isProtocol = decl->isInterfaceDeclaration(); + + if (decl) { + for(size_t i = 0; i < decl->objc.methodList.length; i++) { + auto method = decl->objc.methodList.ptr[i]; + + if (isProtocol) { + if (method->objc.isOptional == optional) + funcs.push_back(method); + continue; + } + + if (method->fbody) + funcs.push_back(method); + } + } + return funcs; +} + +// +// FINALIZATION +// + void ObjCState::finalize() { - if (!retainedSymbols.empty()) { - genImageInfo(); - // add in references so optimizer won't remove symbols. + if (retainedSymbols.size() > 0) { retainSymbols(); + genImageInfo(); } } +void ObjCState::retain(LLConstant *symbol) { + retainedSymbols.push_back(symbol); +} + void ObjCState::genImageInfo() { - // Use LLVM to generate image info - const char *section = - (abi == nonFragile ? "__DATA,__objc_imageinfo,regular,no_dead_strip" - : "__OBJC,__image_info"); - module.addModuleFlag(llvm::Module::Error, "Objective-C Version", - abi); // unused? - module.addModuleFlag(llvm::Module::Error, "Objective-C Image Info Version", - 0u); // version - module.addModuleFlag(llvm::Module::Error, "Objective-C Image Info Section", - llvm::MDString::get(module.getContext(), section)); - module.addModuleFlag(llvm::Module::Override, "Objective-C Garbage Collection", - 0u); // flags + module.addModuleFlag(llvm::Module::Error, "Objective-C Version", 2u); // Only support ABI 2. (Non-fragile) + module.addModuleFlag(llvm::Module::Error, "Objective-C Image Info Version", 0u); // version + module.addModuleFlag(llvm::Module::Error, "Objective-C Image Info Section", llvm::MDString::get(module.getContext(), OBJC_SECNAME_IMAGEINFO)); + module.addModuleFlag(llvm::Module::Override, "Objective-C Garbage Collection", 0u); // flags } void ObjCState::retainSymbols() { - // put all objc symbols in the llvm.compiler.used array so optimizer won't - // remove. - auto arrayType = LLArrayType::get(retainedSymbols.front()->getType(), - retainedSymbols.size()); - auto usedArray = LLConstantArray::get(arrayType, retainedSymbols); - auto var = new LLGlobalVariable(module, arrayType, false, - LLGlobalValue::AppendingLinkage, usedArray, - "llvm.compiler.used"); - var->setSection("llvm.metadata"); -} + if (!retainedSymbols.empty()) { + auto arrayType = LLArrayType::get(retainedSymbols.front()->getType(), + retainedSymbols.size()); + auto usedArray = LLConstantArray::get(arrayType, retainedSymbols); + auto var = new LLGlobalVariable(module, arrayType, false, + LLGlobalValue::AppendingLinkage, usedArray, + "llvm.compiler.used"); + var->setSection("llvm.metadata"); + } +} \ No newline at end of file diff --git a/gen/objcgen.h b/gen/objcgen.h index a050a09107..c5c1257388 100644 --- a/gen/objcgen.h +++ b/gen/objcgen.h @@ -14,7 +14,11 @@ #pragma once #include +#include #include "llvm/ADT/StringMap.h" +#include "gen/tollvm.h" +#include "dmd/mtype.h" +#include "dmd/errors.h" struct ObjcSelector; namespace llvm { @@ -24,31 +28,182 @@ class Module; class Triple; } +// Forward decl. +class Declaration; +class ClassDeclaration; +class FuncDeclaration; +class InterfaceDeclaration; +class VarDeclaration; +class Identifier; +class Type; + +// Fwd declaration. +class ObjCState; + +// class is a metaclass +#define RO_META (1<<0) + +// class is a root class +#define RO_ROOT (1<<1) + +// Section Names +#define OBJC_SECNAME_CLASSNAME "__TEXT,__objc_classname, cstring_literals" +#define OBJC_SECNAME_METHNAME "__TEXT,__objc_methname, cstring_literals" +#define OBJC_SECNAME_METHTYPE "__TEXT,__objc_methtype, cstring_literals" +#define OBJC_SECNAME_SELREFS "__DATA,__objc_selrefs, literal_pointers, no_dead_strip" +#define OBJC_SECNAME_IMAGEINFO "__DATA,__objc_imageinfo, regular, no_dead_strip" +#define OBJC_SECNAME_CLASSREFS "__DATA,__objc_classrefs, regular, no_dead_strip" +#define OBJC_SECNAME_CLASSLIST "__DATA,__objc_classlist, regular, no_dead_strip" +#define OBJC_SECNAME_STUBS "__DATA,__objc_stubs, regular, no_dead_strip" +#define OBJC_SECNAME_CATLIST "__DATA,__objc_catlist, regular, no_dead_strip" +#define OBJC_SECNAME_PROTOLIST "__DATA,__objc_protolist, coalesced, no_dead_strip" +#define OBJC_SECNAME_PROTOREFS "__DATA,__objc_protorefs, regular" +#define OBJC_SECNAME_CONST "__DATA,__objc_const" +#define OBJC_SECNAME_DATA "__DATA,__objc_data" +#define OBJC_SECNAME_IVAR "__DATA,__objc_ivar" + +// Names of Objective-C runtime structs +#define OBJC_STRUCTNAME_CLASSRO "class_ro_t" +#define OBJC_STRUCTNAME_CLASS "class_t" +#define OBJC_STRUCTNAME_STUBCLASS "stub_class_t" +#define OBJC_STRUCTNAME_PROTO "protocol_t" +#define OBJC_STRUCTNAME_IVAR "ivar_t" +#define OBJC_STRUCTNAME_METHOD "objc_method" + +#define ObjcList std::vector +#define ObjcMap std::unordered_map + +// Gets whether Objective-C is supported. bool objc_isSupported(const llvm::Triple &triple); +// Generate name strings +std::string objcGetClassRoSymbol(const char *name, bool meta); +std::string objcGetClassSymbol(const char *name, bool meta); +std::string objcGetClassLabelSymbol(const char *name); +std::string objcGetClassMethodListSymbol(const char *className, bool meta); +std::string objcGetIvarListSymbol(const char *className); +std::string objcGetIvarSymbol(const char *className, const char *varName); +std::string objcGetProtoMethodListSymbol(const char *className, bool meta, bool optional); +std::string objcGetProtoSymbol(const char *name); +std::string objcGetProtoListSymbol(const char *name); +std::string objcGetSymbolName(const char *dsymPrefix, const char *dsymName); + +// Utility which fetches the appropriate Objective-C +// name for a declaration. +const char *objcResolveName(Dsymbol *decl); + +// Gets the Objective-C type encoding for D type t +std::string objcGetTypeEncoding(Type *t); + +// class_t +LLStructType *objcGetClassType(const llvm::Module& module); + +// class_ro_t +LLStructType *objcGetClassRoType(const llvm::Module& module); + +// stub_class_t +LLStructType *objcGetStubClassType(const llvm::Module& module); + +// objc_method +LLStructType *objcGetMethodType(const llvm::Module& module); + +// ivar_t +LLStructType *objcGetIvarType(const llvm::Module& module); + +// protocol_t +LLStructType *objcGetProtocolType(const llvm::Module& module); + +// xyz_list_t (count-only) +LLConstant *objcEmitList(llvm::Module &module, LLConstantList objects, bool alignSizeT = false, bool countOnly = false); + +struct ObjcClassInfo { + ClassDeclaration *decl; + LLGlobalVariable *ref; + + LLGlobalVariable *name; + LLGlobalVariable *table; +}; + +struct ObjcProtocolInfo { + InterfaceDeclaration *decl; + LLGlobalVariable *ref; + + LLGlobalVariable *name; + LLGlobalVariable *table; +}; + +struct ObjcMethodInfo { + FuncDeclaration *decl; + + LLGlobalVariable *name; + LLGlobalVariable *type; + LLGlobalVariable *selector; + LLConstant *llfunction; +}; + +struct ObjcIvarInfo { + VarDeclaration *decl; + + LLGlobalVariable *name; + LLGlobalVariable *type; + LLGlobalVariable *offset; +}; + // Objective-C state tied to an LLVM module (object file). class ObjCState { public: - ObjCState(llvm::Module &module) : module(module) {} - llvm::GlobalVariable *getMethVarRef(const ObjcSelector &sel); + ObjCState(llvm::Module &module) : module(module) { } + + ObjcClassInfo *getClass(ClassDeclaration *decl); + ObjcProtocolInfo *getProtocol(InterfaceDeclaration *decl); + ObjcMethodInfo *getMethod(FuncDeclaration *decl); + ObjcIvarInfo *getIvar(VarDeclaration *decl); + + LLValue *deref(ClassDeclaration *decl, LLType *as); + void finalize(); private: llvm::Module &module; - // symbols that shouldn't be optimized away - std::vector retainedSymbols; + // Creates an ivar_t struct which can be + // used in ivar lists. + ObjcMap ivars; + LLConstant *createIvarInfo(VarDeclaration *decl); + LLConstant *createIvarList(ClassDeclaration *decl); + + // Creates an objc_method struct which can be + // used in method lists. + ObjcMap methods; + LLConstant *createMethodInfo(FuncDeclaration *decl); + LLConstant *createMethodList(ClassDeclaration *decl, bool optional = false); + + // class_t and class_ro_t generation. + ObjcMap classes; + ObjcMap classTables; + ObjcMap classRoTables; + LLConstant *getClassRoTable(ClassDeclaration *decl); + LLConstant *getClassTable(ClassDeclaration *decl); + + // Class names and refs need to be replicated + // for RO structs, as such we store + // then seperately. + llvm::StringMap classNames; + llvm::StringMap classRefs; + LLConstant *getClassName(ClassDeclaration *decl); + LLConstant *getClassRef(ClassDeclaration *decl); - llvm::StringMap methVarNameMap; - llvm::StringMap methVarRefMap; + // protocol_t generation. + ObjcMap protocols; + LLConstant *createProtocolTable(InterfaceDeclaration *decl); + LLConstant *createProtocolList(ClassDeclaration *decl); - llvm::GlobalVariable *getCStringVar(const char *symbol, - const llvm::StringRef &str, - const char *section); - llvm::GlobalVariable *getMethVarName(const llvm::StringRef &name); - void retain(llvm::Constant *sym); + // Private helpers + ObjcList getMethodsForType(ClassDeclaration *decl, bool optional = false); + ObjcList retainedSymbols; + void retain(LLConstant *symbol); void genImageInfo(); void retainSymbols(); }; diff --git a/gen/runtime.cpp b/gen/runtime.cpp index bff2731745..2a809b3417 100644 --- a/gen/runtime.cpp +++ b/gen/runtime.cpp @@ -824,6 +824,10 @@ static void buildRuntimeModule() { {stringTy, arrayOf(sizeTy), arrayOf(uintTy), ubyteTy}); } + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + if (target.objc.supported) { assert(global.params.targetTriple->isOSDarwin()); @@ -839,24 +843,58 @@ static void buildRuntimeModule() { {objectPtrTy, selectorPtrTy}, {}, AttrSet(NoAttrs, ~0U, llvm::Attribute::NonLazyBind)); + // id objc_msgSendSuper(obj_super_t *super, SEL op, ...) + // NOTE: obj_super_t is defined as struct { id, Class } + createFwdDecl(LINK::c, objectPtrTy, {"objc_msgSendSuper"}, + {objectPtrTy, selectorPtrTy}, {}, + AttrSet(NoAttrs, ~0U, llvm::Attribute::NonLazyBind)); + + // Class object_getClass(id obj) + createFwdDecl(LINK::c, objectPtrTy, {"object_getClass"}, + {objectPtrTy}, {}, + AttrSet(NoAttrs, ~0U, llvm::Attribute::NonLazyBind)); + + // Class objc_loadClassRef(Class function(Class* stub)) + // SEE: https://github.com/swiftlang/swift/blob/main/docs/ObjCInterop.md + createFwdDecl(LINK::c, objectPtrTy, {"objc_loadClassRef"}, + {objectPtrTy}, {}, + AttrSet(NoAttrs, ~0U, llvm::Attribute::NonLazyBind)); + + // Needed for safe casting + + // bool objc_opt_isKindOfClass(id obj, Class otherClass) + // This features a fast path over using the msgSend version. + // https://github.com/apple-oss-distributions/objc4/blob/main/runtime/NSObject.mm#L2123 + createFwdDecl(LINK::c, boolTy, {"objc_opt_isKindOfClass"}, + {objectPtrTy, objectPtrTy}, {}, + AttrSet(NoAttrs, ~0U, llvm::Attribute::NonLazyBind)); + + // bool class_conformsToProtocol(Class cls, Protocol *protocol) + createFwdDecl(LINK::c, boolTy, {"class_conformsToProtocol"}, + {objectPtrTy, objectPtrTy}, {}, + AttrSet(NoAttrs, ~0U, llvm::Attribute::NonLazyBind)); + + switch (global.params.targetTriple->getArch()) { case llvm::Triple::x86_64: // creal objc_msgSend_fp2ret(id self, SEL op, ...) createFwdDecl(LINK::c, Type::tcomplex80, {"objc_msgSend_fp2ret"}, {objectPtrTy, selectorPtrTy}); - // fall-thru - case llvm::Triple::x86: + // x86_64 real return only, x86 float, double, real return // real objc_msgSend_fpret(id self, SEL op, ...) createFwdDecl(LINK::c, realTy, {"objc_msgSend_fpret"}, {objectPtrTy, selectorPtrTy}); - // fall-thru - case llvm::Triple::arm: - case llvm::Triple::thumb: + // used when return value is aggregate via a hidden sret arg // void objc_msgSend_stret(T *sret_arg, id self, SEL op, ...) createFwdDecl(LINK::c, voidTy, {"objc_msgSend_stret"}, - {objectPtrTy, selectorPtrTy}); + {objectPtrTy, objectPtrTy, selectorPtrTy}); + + // See: https://github.com/apple-oss-distributions/objc4/blob/main/runtime/Messengers.subproj/objc-msg-x86_64.s#L1059 + // void objc_msgSend_stret(T *sret_arg, objc_super_t *super, SEL op, ...) + createFwdDecl(LINK::c, voidTy, {"objc_msgSendSuper_stret"}, + {objectPtrTy, objectPtrTy, selectorPtrTy}); break; default: break; diff --git a/gen/tocall.cpp b/gen/tocall.cpp index e9f3c899a3..9267e13214 100644 --- a/gen/tocall.cpp +++ b/gen/tocall.cpp @@ -646,13 +646,13 @@ class ImplicitArgumentsBuilder { tf(DtoTypeFunction(fnval)), llArgTypesBegin(llCalleeType->param_begin()) {} - void addImplicitArgs() { + void addImplicitArgs(bool directcall) { if (gABI->passThisBeforeSret(tf)) { - addContext(); + addContext(directcall); addSret(); } else { addSret(); - addContext(); + addContext(directcall); } addArguments(); @@ -702,15 +702,17 @@ class ImplicitArgumentsBuilder { } // Adds an optional context/this pointer argument and sets hasContext. - void addContext() { + void addContext(bool directcall) { const bool thiscall = irFty.arg_this; const bool nestedcall = irFty.arg_nest; + const bool objccall = irFty.type->linkage == LINK::objc; - hasContext = thiscall || nestedcall || isDelegateCall; + hasContext = thiscall || nestedcall || isDelegateCall || objccall; if (!hasContext) return; size_t index = args.size(); + auto argtype = *(llArgTypesBegin + index); if (dfnval && (dfnval->func->ident == Id::ensure || dfnval->func->ident == Id::require)) { @@ -732,8 +734,33 @@ class ImplicitArgumentsBuilder { } args.push_back(thisptrLval); } else if (thiscall && dfnval && dfnval->vthis) { - // ... or a normal 'this' argument - args.push_back(dfnval->vthis); + + if (objccall && directcall) { + + // ... or a Objective-c direct call argument + if (auto func = dfnval->func->isFuncDeclaration()) { + if (auto klass = func->isThis()->isClassDeclaration()) { + + // Create obj_super struct with (this, ) + auto obj_super = DtoAggrPair( + DtoBitCast(dfnval->vthis, argtype), + gIR->objc.deref(klass, getOpaquePtrType()), + "super" + ); + + + // Allocate and store obj_super struct into a new variable. + auto clsaddr = DtoRawAlloca(obj_super->getType(), 16, "super"); + DtoStore(obj_super, clsaddr); + + args.push_back(clsaddr); + } + } + } else { + + // ... or a normal 'this' argument + args.push_back(DtoBitCast(dfnval->vthis, argtype)); + } } else if (isDelegateCall) { // ... or a delegate context arg LLValue *ctxarg; @@ -767,10 +794,9 @@ class ImplicitArgumentsBuilder { if (irFty.arg_objcSelector) { assert(dfnval); - const auto selector = dfnval->func->objc.selector; - assert(selector); - LLGlobalVariable *selptr = gIR->objc.getMethVarRef(*selector); - args.push_back(DtoLoad(selptr->getValueType(), selptr)); + + auto methodptr = gIR->objc.getMethod(dfnval->func)->selector; + args.push_back(DtoLoad(methodptr->getType(), methodptr)); } } @@ -813,7 +839,7 @@ static LLValue *DtoCallableValue(DValue *fn) { // FIXME: this function is a mess ! DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval, - Expressions *arguments, LLValue *sretPointer) { + Expressions *arguments, LLValue *sretPointer, bool directcall) { IF_LOG Logger::println("DtoCallFunction()"); LOG_SCOPE @@ -858,7 +884,7 @@ DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval, // handle implicit arguments (sret, context/this, _arguments) ImplicitArgumentsBuilder iab(args, attrs, loc, fnval, callableTy, arguments, resulttype, sretPointer); - iab.addImplicitArgs(); + iab.addImplicitArgs(directcall); // handle explicit arguments @@ -880,9 +906,12 @@ DValue *DtoCallFunction(const Loc &loc, Type *resulttype, DValue *fnval, } if (irFty.arg_objcSelector) { + // Use runtime msgSend function bitcasted as original call - const char *msgSend = gABI->objcMsgSendFunc(resulttype, irFty); + const char *msgSend = gABI->objcMsgSendFunc(resulttype, irFty, directcall); + auto t = callable->getType(); callable = getRuntimeFunction(loc, gIR->module, msgSend); + callable = DtoBitCast(callable, t); } // call the function diff --git a/gen/toir.cpp b/gen/toir.cpp index 927a720e73..3ddee43be5 100644 --- a/gen/toir.cpp +++ b/gen/toir.cpp @@ -329,6 +329,30 @@ class ToElemVisitor : public Visitor { ////////////////////////////////////////////////////////////////////////////// + void visit(ObjcClassReferenceExp *e) override { + IF_LOG Logger::print("ObjcClassReferenceExp::toElem: %s @ %s\n", e->toChars(), + e->type->toChars()); + LOG_SCOPE; + + auto lType = DtoType(e->type); + + if (auto iface = e->classDeclaration->isInterfaceDeclaration()) { + + // Protocols + result = new DImValue(e->type, gIR->objc.deref(iface, lType)); + return; + } else { + + // Classes + result = new DImValue(e->type, gIR->objc.deref(e->classDeclaration, lType)); + return; + } + + llvm_unreachable("Unknown type for ObjcClassReferenceExp."); + } + + ////////////////////////////////////////////////////////////////////////////// + void visit(VarExp *e) override { IF_LOG Logger::print("VarExp::toElem: %s @ %s\n", e->toChars(), e->type->toChars()); @@ -801,7 +825,7 @@ class ToElemVisitor : public Visitor { } DValue *result = - DtoCallFunction(e->loc, e->type, fnval, e->arguments, sretPointer); + DtoCallFunction(e->loc, e->type, fnval, e->arguments, sretPointer, e->directcall); if (canEmitVTableUnchangedAssumption && dfnval->vtable) { // Reload vtable ptr. It's the first element so instead of GEP+load we can @@ -1017,10 +1041,9 @@ class ToElemVisitor : public Visitor { auto &PGO = gIR->funcGen().pgo; PGO.setCurrentStmt(e); - DValue *l = toElem(e->e1); - Type *e1type = e->e1->type->toBasetype(); + DValue *l = toElem(e->e1); if (VarDeclaration *vd = e->var->isVarDeclaration()) { AggregateDeclaration *ad; LLValue *aggrPtr; diff --git a/gen/tollvm.cpp b/gen/tollvm.cpp index 36af312736..0f0240b857 100644 --- a/gen/tollvm.cpp +++ b/gen/tollvm.cpp @@ -691,8 +691,122 @@ llvm::GlobalVariable *isaGlobalVar(LLValue *v) { //////////////////////////////////////////////////////////////////////////////// +LLGlobalVariable *makeGlobal(LLStringRef name, LLType* type, LLStringRef section, bool extern_, bool externInit) { + if (!type) + type = getOpaquePtrType(); + + auto var = new LLGlobalVariable( + gIR->module, + type, + false, + extern_ ? LLGlobalValue::ExternalLinkage : LLGlobalValue::PrivateLinkage, + nullptr, + name, + nullptr, + LLGlobalValue::NotThreadLocal, + 0u, + externInit + ); + + if (!section.empty()) + var->setSection(section); + + return var; +} + +LLGlobalVariable *makeGlobalWithBytes(LLStringRef name, LLConstantList packedContents, LLStructType* type, bool extern_, bool externInit) { + if (packedContents.empty()) { + packedContents.push_back(getNullPtr()); + } + + // Handle initializer. + LLConstant *init; + if (type) { + init = LLConstantStruct::get( + type, + packedContents + ); + } else { + init = LLConstantStruct::getAnon( + packedContents, + true + ); + type = reinterpret_cast(init->getType()); + } + + auto var = new LLGlobalVariable( + gIR->module, + type, + false, + extern_ ? LLGlobalValue::ExternalLinkage : LLGlobalValue::PrivateLinkage, + init, + name, + nullptr, + LLGlobalValue::NotThreadLocal, + 0u, + externInit + ); + + return var; +} + +LLGlobalVariable *makeGlobalRef(LLGlobalVariable *to, LLStringRef name, LLStringRef section, bool extern_, bool externInit) { + auto var = makeGlobal(name, to->getType(), section, extern_, externInit); + var->setInitializer(to); + return var; +} + +LLGlobalVariable *makeGlobalStr(LLStringRef text, LLStringRef name, LLStringRef section, bool extern_, bool externInit) { + auto init = llvm::ConstantDataArray::getString(gIR->context(), text); + auto var = new LLGlobalVariable( + gIR->module, + init->getType(), + false, + extern_ ? LLGlobalValue::ExternalLinkage : LLGlobalValue::PrivateLinkage, + init, + name, + nullptr, + LLGlobalValue::NotThreadLocal, + 0u, + externInit + ); + + if (!section.empty()) + var->setSection(section); + return var; +} + +LLGlobalVariable *getOrCreate(LLStringRef name, LLType* type, LLStringRef section, bool extInitializer) { + auto global = gIR->module.getGlobalVariable(name, true); + if (global) + return global; + + return makeGlobal(name, type, section, true, extInitializer); +} + +LLGlobalVariable *getOrCreateWeak(LLStringRef name, LLType* type, LLStringRef section, bool extInitializer) { + auto global = gIR->module.getGlobalVariable(name, true); + if (global) + return global; + + global = makeGlobal(name, type, section, false, extInitializer); + global->setLinkage(llvm::GlobalValue::LinkageTypes::WeakAnyLinkage); + global->setVisibility(llvm::GlobalValue::VisibilityTypes::HiddenVisibility); + return global; +} + +//////////////////////////////////////////////////////////////////////////////// + LLType *getI8Type() { return LLType::getInt8Ty(gIR->context()); } +LLType *getI16Type() { return LLType::getInt16Ty(gIR->context()); } + +LLType *getI32Type() { return LLType::getInt32Ty(gIR->context()); } + +LLType *getI64Type() { return LLType::getInt64Ty(gIR->context()); } + +LLType *getSizeTType() { return DtoSize_t(); } + LLPointerType *getOpaquePtrType(unsigned addressSpace) { return LLPointerType::get(gIR->context(), addressSpace); } @@ -703,6 +817,8 @@ llvm::ConstantPointerNull *getNullPtr() { LLConstant *getNullValue(LLType *t) { return LLConstant::getNullValue(t); } +LLConstant *wrapNull(LLConstant *v) { return v ? v : getNullPtr(); } + //////////////////////////////////////////////////////////////////////////////// size_t getTypeBitSize(LLType *t) { return gDataLayout->getTypeSizeInBits(t); } @@ -711,6 +827,10 @@ size_t getTypeStoreSize(LLType *t) { return gDataLayout->getTypeStoreSize(t); } size_t getTypeAllocSize(LLType *t) { return gDataLayout->getTypeAllocSize(t); } +size_t getPointerSize() { return gDataLayout->getPointerSize(0); } + +size_t getPointerSizeInBits() { return gDataLayout->getPointerSizeInBits(0); } + unsigned int getABITypeAlign(LLType *t) { return gDataLayout->getABITypeAlign(t).value(); } diff --git a/gen/tollvm.h b/gen/tollvm.h index f8e195d2ac..5dd04e0599 100644 --- a/gen/tollvm.h +++ b/gen/tollvm.h @@ -146,16 +146,32 @@ LLConstantInt *isaConstantInt(LLValue *v); llvm::Argument *isaArgument(LLValue *v); LLGlobalVariable *isaGlobalVar(LLValue *v); +// llvm::GlobalVariable wrappers for quickly making +// new global variables and references to them. +LLGlobalVariable *makeGlobal(LLStringRef name, LLType* type = nullptr, LLStringRef section = "", bool extern_ = false, bool externInit = false); +LLGlobalVariable *makeGlobalWithBytes(LLStringRef name, LLConstantList packedContents, LLStructType* type = nullptr, bool extern_ = false, bool externInit = false); +LLGlobalVariable *makeGlobalRef(LLGlobalVariable *to, LLStringRef name = "", LLStringRef section = "", bool extern_ = false, bool externInit = false); +LLGlobalVariable *makeGlobalStr(LLStringRef text, LLStringRef name = "", LLStringRef section = "", bool extern_ = false, bool externInit = false); +LLGlobalVariable *getOrCreate(LLStringRef name, LLType* type, LLStringRef section, bool extInitializer=false); +LLGlobalVariable *getOrCreateWeak(LLStringRef name, LLType* type, LLStringRef section, bool extInitializer=false); + // llvm::T::get(...) wrappers LLType *getI8Type(); +LLType *getI16Type(); +LLType *getI32Type(); +LLType *getI64Type(); +LLType *getSizeTType(); LLPointerType *getOpaquePtrType(unsigned addressSpace = 0); llvm::ConstantPointerNull *getNullPtr(); LLConstant *getNullValue(LLType *t); +LLConstant *wrapNull(LLConstant *v); // type sizes size_t getTypeBitSize(LLType *t); size_t getTypeStoreSize(LLType *t); size_t getTypeAllocSize(LLType *t); +size_t getPointerSize(); +size_t getPointerSizeInBits(); // type alignments unsigned int getABITypeAlign(LLType *t); diff --git a/ir/irclass.cpp b/ir/irclass.cpp index 064d59b9f4..990fb6f49a 100644 --- a/ir/irclass.cpp +++ b/ir/irclass.cpp @@ -55,6 +55,11 @@ IrClass::IrClass(ClassDeclaration *cd) : IrAggr(cd) { } void IrClass::addInterfaceVtbls(ClassDeclaration *cd) { + + // No interface vtables in Objective-C + if (cd->classKind == ClassKind::objc) + return; + if (cd->baseClass && !cd->isInterfaceDeclaration()) { addInterfaceVtbls(cd->baseClass); } diff --git a/ir/irtypeaggr.cpp b/ir/irtypeaggr.cpp index a8ab738769..3bde723fdf 100644 --- a/ir/irtypeaggr.cpp +++ b/ir/irtypeaggr.cpp @@ -64,6 +64,15 @@ void AggrTypeBuilder::addAggregate( if (n == 0) return; + // Objective-C instance variables are laid out at runtime. + // as such, we should not generate the aggregate body. + if (auto klass = ad->isClassDeclaration()) { + if (klass->classKind == ClassKind::objc) { + this->addType(getOpaquePtrType(), getPointerSize()); + return; + } + } + // Unions may lead to overlapping fields, and we need to flatten them for LLVM // IR. We usually take the first field (in declaration order) of an // overlapping set, but a literal with an explicit initializer for a dominated @@ -196,9 +205,9 @@ void AggrTypeBuilder::addAggregate( if (vd->offset < m_offset) { error(vd->loc, - "%s `%s` overlaps previous field. This is an ICE, please file an " + "%s `%s` @ %u overlaps previous field @ %u. This is an ICE, please file an " "LDC issue.", - vd->kind(), vd->toPrettyChars()); + vd->kind(), vd->toPrettyChars(), vd->offset, m_offset); fatal(); } diff --git a/ir/irtypeclass.cpp b/ir/irtypeclass.cpp index 8e4a77d6fb..85cd79d1d0 100644 --- a/ir/irtypeclass.cpp +++ b/ir/irtypeclass.cpp @@ -83,6 +83,14 @@ llvm::Type *IrTypeClass::getMemoryLLType() { AggrTypeBuilder builder; + // Objective-C just has an ISA pointer, so just + // throw that in there. + if (cd->classKind == ClassKind::objc) { + builder.addType(getOpaquePtrType(), target.ptrsize); + isaStruct(type)->setBody(builder.defaultTypes(), builder.isPacked()); + return type; + } + // add vtbl builder.addType(llvm::PointerType::get(vtbl_type, 0), target.ptrsize); diff --git a/runtime/druntime/src/core/attribute.d b/runtime/druntime/src/core/attribute.d index 95a67ea219..9f56908d7a 100644 --- a/runtime/druntime/src/core/attribute.d +++ b/runtime/druntime/src/core/attribute.d @@ -40,6 +40,7 @@ version (D_ObjectiveC) { version = UdaOptional; version = UdaSelector; + version = UdaSwift; } version (Posix) @@ -50,6 +51,7 @@ version (CoreDdoc) version = UdaGNUAbiTag; version = UdaOptional; version = UdaSelector; + version = UdaSwift; } /** @@ -183,6 +185,16 @@ version (UdaSelector) struct selector version (UdaOptional) enum optional; + +/** + * Use this attribute to indicate that a Objective-C class is a Swift stub class. + * + * This is only allowed on classes, and classes marked as swift Objective-C classes + * cannot be subclassed. + */ +version (UdaSwift) + enum swift; + /** * Use this attribute to declare an ABI tag on a C++ symbol. * diff --git a/tests/dmd/compilable/objc_class.d b/tests/dmd/compilable/objc_class.d index 2b595d47d2..57d4b0d6cf 100644 --- a/tests/dmd/compilable/objc_class.d +++ b/tests/dmd/compilable/objc_class.d @@ -1,4 +1,3 @@ -// DISABLED: LDC // EXTRA_OBJC_SOURCES: import core.attribute : selector; diff --git a/tests/dmd/compilable/objc_interface_final_19654.d b/tests/dmd/compilable/objc_interface_final_19654.d index fb8a4ff64c..01be3219c6 100644 --- a/tests/dmd/compilable/objc_interface_final_19654.d +++ b/tests/dmd/compilable/objc_interface_final_19654.d @@ -1,4 +1,3 @@ -// DISABLED: LDC // EXTRA_OBJC_SOURCES: import core.attribute : selector; diff --git a/tests/dmd/run.d b/tests/dmd/run.d index 1ef15b82e6..f2fc0be3f0 100755 --- a/tests/dmd/run.d +++ b/tests/dmd/run.d @@ -619,7 +619,9 @@ string[string] getEnvironment() } version(OSX) - version(X86_64) + version (IN_LLVM) + env["D_OBJC"] = "1"; + else version(X86_64) env["D_OBJC"] = "1"; } return env; diff --git a/tests/dmd/runnable/objc_call.d b/tests/dmd/runnable/objc_call.d index 481ee75d84..98199a0abb 100644 --- a/tests/dmd/runnable/objc_call.d +++ b/tests/dmd/runnable/objc_call.d @@ -1,4 +1,3 @@ -// DISABLED: LDC // EXTRA_OBJC_SOURCES: // REQUIRED_ARGS: -L-framework -LFoundation diff --git a/tests/dmd/runnable/objc_call_static.d b/tests/dmd/runnable/objc_call_static.d index c4f774dacb..2c51413cc6 100644 --- a/tests/dmd/runnable/objc_call_static.d +++ b/tests/dmd/runnable/objc_call_static.d @@ -1,5 +1,3 @@ -// LDC: not implemented yet (issue #2670) -// DISABLED: LDC // EXTRA_OBJC_SOURCES: // REQUIRED_ARGS: -L-framework -LFoundation diff --git a/tests/dmd/runnable/objc_class.d b/tests/dmd/runnable/objc_class.d index 7ca0fbfe5b..e66528ee6a 100644 --- a/tests/dmd/runnable/objc_class.d +++ b/tests/dmd/runnable/objc_class.d @@ -1,4 +1,3 @@ -// DISABLED: LDC // EXTRA_OBJC_SOURCES: objc_class.m // REQUIRED_ARGS: -L-framework -LFoundation diff --git a/tests/dmd/runnable/objc_external_class_19700.d b/tests/dmd/runnable/objc_external_class_19700.d index ff206fa270..074cb2cc05 100644 --- a/tests/dmd/runnable/objc_external_class_19700.d +++ b/tests/dmd/runnable/objc_external_class_19700.d @@ -1,4 +1,3 @@ -// DISABLED: LDC // EXTRA_OBJC_SOURCES: objc_instance_variable.m // REQUIRED_ARGS: -L-framework -LFoundation diff --git a/tests/dmd/runnable/objc_instance_variable.d b/tests/dmd/runnable/objc_instance_variable.d index 63e7e0581c..9e2ce1075c 100644 --- a/tests/dmd/runnable/objc_instance_variable.d +++ b/tests/dmd/runnable/objc_instance_variable.d @@ -1,4 +1,3 @@ -// DISABLED: LDC // EXTRA_OBJC_SOURCES: objc_instance_variable.m // REQUIRED_ARGS: -L-framework -LFoundation diff --git a/tests/dmd/runnable/objc_objc_msgSend.d b/tests/dmd/runnable/objc_objc_msgSend.d index ef8e5b09db..32f8574f17 100644 --- a/tests/dmd/runnable/objc_objc_msgSend.d +++ b/tests/dmd/runnable/objc_objc_msgSend.d @@ -1,4 +1,3 @@ -// DISABLED: LDC // EXTRA_OBJC_SOURCES: objc_objc_msgSend.m // REQUIRED_ARGS: -L-framework -LFoundation diff --git a/tests/dmd/runnable/objc_protocol.d b/tests/dmd/runnable/objc_protocol.d index 18102e83a7..3cac51e1c9 100644 --- a/tests/dmd/runnable/objc_protocol.d +++ b/tests/dmd/runnable/objc_protocol.d @@ -1,4 +1,3 @@ -// DISABLED: LDC // EXTRA_OBJC_SOURCES: objc_protocol.m // REQUIRED_ARGS: -L-lobjc diff --git a/tests/dmd/runnable/objc_protocol_sections.d b/tests/dmd/runnable/objc_protocol_sections.d index 1382bf2569..0f4a669f2d 100644 --- a/tests/dmd/runnable/objc_protocol_sections.d +++ b/tests/dmd/runnable/objc_protocol_sections.d @@ -1,4 +1,3 @@ -// DISABLED: LDC // EXTRA_OBJC_SOURCES: // REQUIRED_ARGS: -L-lobjc @@ -8,7 +7,7 @@ import core.attribute : selector, optional; -extern (Objective-C): +extern(C): struct Protocol; struct objc_selector; @@ -38,6 +37,8 @@ objc_method_description protocol_getMethodDescription( Protocol* proto, SEL aSel, bool isRequiredMethod, bool isInstanceMethod ); +extern (Objective-C): + interface Foo { void foo1() @selector("foo1"); diff --git a/tests/dmd/runnable/objc_super_call.d b/tests/dmd/runnable/objc_super_call.d index 7fc139a23c..f0926cb240 100644 --- a/tests/dmd/runnable/objc_super_call.d +++ b/tests/dmd/runnable/objc_super_call.d @@ -1,4 +1,3 @@ -// DISABLED: LDC // EXTRA_OBJC_SOURCES: objc_super_call.m // REQUIRED_ARGS: -L-framework -LFoundation diff --git a/tests/dmd/tools/d_do_test.d b/tests/dmd/tools/d_do_test.d index d362e76c82..4de96f38b3 100755 --- a/tests/dmd/tools/d_do_test.d +++ b/tests/dmd/tools/d_do_test.d @@ -816,6 +816,14 @@ bool gatherTestParameters(ref TestArgs testArgs, string input_dir, string input_ testArgs.objcSources = split(extraObjcSourcesStr); + // Using the ld64 linker may result in linker warnings being generated + // which are irrelevant to the parts of Objective-C ABI which D implements. + // As such we supress linker warnings for Objective-C tests to avoid + // linker errors when linking against Objective-C compiler output. + version(LDC) + if (objc) + testArgs.requiredArgs ~= " -L-w"; + // swap / with $SEP if (envData.sep && envData.sep != "/") foreach (ref s; testArgs.sources)