diff --git a/driver/cl_options.cpp b/driver/cl_options.cpp index c101cf199c8..2679ba546c6 100644 --- a/driver/cl_options.cpp +++ b/driver/cl_options.cpp @@ -616,6 +616,14 @@ cl::opt fSplitStack("fsplit-stack", cl::ZeroOrMore, cl::desc("Use segmented stack (see Clang documentation)")); +cl::opt fCInteropLLVMByte( + "fc-interop-llvm-byte", cl::ZeroOrMore, cl::init(false), + cl::desc( + "[EXPERIMENTAL] Use LLVM b8 for extern(C/C++/…) 8-bit integer " + "parameters and returns in function signatures. Default off: Clang may " + "still emit i8, so mixed LTO can fail until LLVM and Clang agree on " + "byte types in IR.")); + cl::opt allinst("allinst", cl::ZeroOrMore, cl::location(global.params.allInst), cl::desc("Generate code for all template instantiations")); diff --git a/driver/cl_options.h b/driver/cl_options.h index 481b83adcc4..d02c5aee48f 100644 --- a/driver/cl_options.h +++ b/driver/cl_options.h @@ -105,6 +105,8 @@ extern cl::opt fNoExceptions; extern cl::opt fNoModuleInfo; extern cl::opt fNoRTTI; extern cl::opt fSplitStack; +/// [EXPERIMENTAL] Use LLVM b8 in extern(C/C++/…) signatures (default off). +extern cl::opt fCInteropLLVMByte; // Arguments to -d-debug extern std::vector debugArgs; diff --git a/gen/abi/aarch64.cpp b/gen/abi/aarch64.cpp index 5a2ff3d6a3b..d2c5dc1d64d 100644 --- a/gen/abi/aarch64.cpp +++ b/gen/abi/aarch64.cpp @@ -14,6 +14,7 @@ #include "dmd/identifier.h" #include "dmd/nspace.h" +#include "driver/cl_options.h" #include "gen/abi/abi.h" #include "gen/abi/generic.h" @@ -113,6 +114,16 @@ struct AArch64TargetABI : TargetABI { void rewriteArgument(IrFuncTy &fty, IrFuncTyArg &arg) override { Type *t = arg.type->toBasetype(); + #if LLVM_VERSION_MAJOR >= 23 + // ignore byte(TY::Tint8) here, because mostle i8 is used for arithemtic purposes + if ((t->ty == TY::Tuns8 || t->ty == TY::Tchar) && + TargetABI::shouldUseLLVMByteInExternSignature(fty.type)) { + arg.ltype = llvm::Type::getByte8Ty(gIR->context()); + arg.attrs.addAttribute(llvm::Attribute::ZExt); + return; + } + #endif + if (!isAggregate(t)) return; @@ -164,7 +175,7 @@ struct AArch64TargetABI : TargetABI { const char *objcMsgSendFunc(Type *ret, IrFuncTy &fty, bool directcall) override { assert(isDarwin()); - + // see objc/message.h for objc_msgSend selection rules return directcall ? "objc_msgSendSuper" : "objc_msgSend"; } diff --git a/gen/abi/abi.cpp b/gen/abi/abi.cpp index 5e5a1baa203..5050983a69b 100644 --- a/gen/abi/abi.cpp +++ b/gen/abi/abi.cpp @@ -9,6 +9,7 @@ #include "gen/abi/abi.h" +#include "driver/cl_options.h" #include "dmd/argtypes.h" #include "dmd/expression.h" #include "dmd/id.h" @@ -133,6 +134,28 @@ bool TargetABI::isExternD(TypeFunction *tf) { return tf->linkage == LINK::d && tf->parameterList.varargs != VARARGvariadic; } +bool TargetABI::shouldUseLLVMByteInExternSignature(TypeFunction *tf) { +#if LLVM_VERSION_MAJOR < 23 + (void)tf; + return false; +#else + if (!opts::fCInteropLLVMByte) + return false; + + switch (tf->linkage) { + case LINK::c: + case LINK::cpp: + case LINK::windows: + case LINK::objc: + case LINK::system: + return true; + case LINK::d: + case LINK::default_: + return false; + } +#endif +} + bool TargetABI::skipReturnValueRewrite(IrFuncTy &fty) { if (fty.ret->byref) return true; diff --git a/gen/abi/abi.h b/gen/abi/abi.h index 5c7667837db..d098a85d2f6 100644 --- a/gen/abi/abi.h +++ b/gen/abi/abi.h @@ -199,6 +199,11 @@ struct TargetABI { /// be passed correctly in registers. static llvm::Type *getRewrittenArgType(Type *t); + /// True if LLVM `b8` should be used for 8-bit extern-interop parameter/return + /// types (experimental). Requires a new enough LLVM, `-fc-interop-llvm-byte`, + /// and C-family linkage on `tf` (not `extern(D)`). + static bool shouldUseLLVMByteInExternSignature(TypeFunction *tf); + protected: /// Returns true if the D type is an aggregate: diff --git a/gen/statements.cpp b/gen/statements.cpp index b1ef6a73010..12d622c8877 100644 --- a/gen/statements.cpp +++ b/gen/statements.cpp @@ -254,6 +254,16 @@ class ToIRVisitor : public Visitor { // do abi specific transformations on the return value returnValue = getIrFunc(fd)->irFty.putRet(dval); +#if LLVM_VERSION_MAJOR >= 23 + // b8 in the LLVM signature only comes from ABI lowering gated on + // -fc-interop-llvm-byte (see TargetABI::shouldUseLLVMByteInExternSignature), + // so no separate opts::fCInteropLLVMByte check here. + if (funcType->getReturnType()->isByteTy(8) && + returnValue->getType()->isIntegerTy(8)) { + returnValue = DtoBitCast(returnValue, funcType->getReturnType()); + } +#endif + // Hack around LDC assuming structs and static arrays are in memory: // If the function returns a struct or a static array, and the return // value is a pointer to a struct or a static array, load from it diff --git a/gen/tocall.cpp b/gen/tocall.cpp index c1b0bc1b3c1..4bbbaec086d 100644 --- a/gen/tocall.cpp +++ b/gen/tocall.cpp @@ -16,6 +16,7 @@ #include "dmd/target.h" #include "dmd/template.h" #include "gen/abi/abi.h" +#include "gen/abi/abi.h" #include "gen/arrays.h" #include "gen/classes.h" #include "gen/dvalue.h" @@ -944,6 +945,19 @@ DValue *DtoCallFunction(Loc loc, Type *resulttype, DValue *fnval, } } +#if LLVM_VERSION_MAJOR >= 23 + // A b8 LLVM callee return type only appears when byte-interop ABI lowering + // ran (see TargetABI::shouldUseLLVMByteInExternSignature). + if (!retValIsLVal && returnTy != TY::Tvoid && returnTy != TY::Tnoreturn) { + LLType *callRetTy = callableTy->getReturnType(); + LLType *dRetTy = DtoType(returntype->toBasetype()); + if (callRetTy != dRetTy && dRetTy->isIntegerTy(8) && + callRetTy->isByteTy(8)) { + retllval = DtoBitCast(retllval, dRetTy); + } + } +#endif + // repaint the type if necessary Type *rbase = stripModifiers(resulttype->toBasetype(), true); Type *nextbase = stripModifiers(returntype->toBasetype(), true); diff --git a/ir/irfuncty.cpp b/ir/irfuncty.cpp index a7f8adb4112..78675d7a431 100644 --- a/ir/irfuncty.cpp +++ b/ir/irfuncty.cpp @@ -10,8 +10,10 @@ #include "ir/irfuncty.h" #include "dmd/mtype.h" +#include "driver/cl_options.h" #include "gen/abi/abi.h" #include "gen/dvalue.h" +#include "gen/irstate.h" #include "gen/llvm.h" #include "gen/llvmhelpers.h" #include "gen/logger.h" @@ -117,6 +119,14 @@ LLValue *IrFuncTy::getParamLVal(Type *dty, size_t idx, LLValue *val) { return args[idx]->rewrite->getLVal(dty, val); } + LLType *dLLTy = DtoType(dty); +#if LLVM_VERSION_MAJOR >= 23 + if ( val->getType()->isByteTy(8) && dLLTy->isIntegerTy(8)) { + IF_LOG Logger::println("getParamLVal: bitcast b8 param to i8"); + val = DtoBitCast(val, dLLTy); + } +#endif + return DtoAllocaDump(val, dty); } diff --git a/tests/codegen/llvm_byte/inputs/llvm_byte_c.c b/tests/codegen/llvm_byte/inputs/llvm_byte_c.c new file mode 100644 index 00000000000..090dc1a0a70 --- /dev/null +++ b/tests/codegen/llvm_byte/inputs/llvm_byte_c.c @@ -0,0 +1,11 @@ +/* Input for llvm_byte_link_runtime.d: C side of extern(C) ubyte interop. + * Built with the host C compiler as a relocatable object, then linked with LDC + * using -fc-interop-llvm-byte (LLVM 23+ AArch64). + */ +unsigned char llvm_byte_c_add_uchar(unsigned char a, unsigned char b) { + return (unsigned char)(a + b); +} + +unsigned char llvm_byte_c_inc_uchar(unsigned char x) { + return (unsigned char)(x + 1); +} diff --git a/tests/codegen/llvm_byte/llvm_byte_extern_ir.d b/tests/codegen/llvm_byte/llvm_byte_extern_ir.d new file mode 100644 index 00000000000..ea44a3847b4 --- /dev/null +++ b/tests/codegen/llvm_byte/llvm_byte_extern_ir.d @@ -0,0 +1,47 @@ +// Verify -fc-interop-llvm-byte lowers extern(C) ubyte/char parameters and returns +// to LLVM b8 on AArch64 (see gen/abi/aarch64.cpp). +// +// CI / older LLVM: unmet REQUIRES => UNSUPPORTED (skipped), not FAIL. +// - atleast_llvm23, llvm_ir_b8: from lit.site.cfg when LLVM >= 23 (b8 in IR). +// - target_AArch64: from LLVM_TARGETS_TO_BUILD (cross-compile uses AArch64 backend). + +// REQUIRES: atleast_llvm23 && llvm_ir_b8 && target_AArch64 + +// RUN: %ldc -fc-interop-llvm-byte -mtriple=aarch64-linux-gnu -c -output-ll -of=%t.ll %s && FileCheck %s < %t.ll + +extern (C) void import_ubyte(ubyte x); +extern (C) void import_char(char x); + +// Call sites: D passes constants; IR should pass b8 to the call. +void call_sites() { + import_ubyte(cast(ubyte) 3); + import_char(cast(char) 4); +} + +// Callee definitions: parameters and return should be b8; body uses i8 storage + bitcasts. +extern (C) ubyte export_ubyte_param(ubyte x) { + return cast(ubyte)(x + 1); +} + +extern (C) char export_char_param(char x) { + return cast(char)(x - 1); +} + +// IR order: definitions before forward declares for callees. + +// CHECK-LABEL: define{{.*}} @{{.*}}call_sites +// CHECK: call void @import_ubyte(b8 zeroext +// CHECK: call void @import_char(b8 zeroext + +// CHECK: declare void @import_ubyte(b8 zeroext +// CHECK: declare void @import_char(b8 zeroext + +// CHECK-LABEL: define{{.*}} @export_ubyte_param +// CHECK-SAME: (b8 zeroext +// CHECK: bitcast b8 %x_arg to i8 +// CHECK: bitcast i8{{.*}} to b8 +// CHECK: ret b8 + +// CHECK-LABEL: define{{.*}} @export_char_param +// CHECK-SAME: (b8 zeroext +// CHECK: ret b8 diff --git a/tests/codegen/llvm_byte/llvm_byte_link_runtime.d b/tests/codegen/llvm_byte/llvm_byte_link_runtime.d new file mode 100644 index 00000000000..5aba4eb88e1 --- /dev/null +++ b/tests/codegen/llvm_byte/llvm_byte_link_runtime.d @@ -0,0 +1,21 @@ +module tests.codegen.llvm_byte.llvm_byte_link_runtime; + +// Runtime test: D with -fc-interop-llvm-byte links against a C object and runs. +// Requires AArch64 host so the default target matches the b8 AArch64 ABI path +// and -run executes a native binary (see docs/byteType.md staged tests). + +// REQUIRES: atleast_llvm23 && llvm_ir_b8 && target_AArch64 && host_AArch64 + +// Host C compiler; skip Windows where `cc` is not in the lit environment. +// UNSUPPORTED: Windows + +// RUN: cc -c -o %t_c.o %S/inputs/llvm_byte_c.c +// RUN: %ldc -fc-interop-llvm-byte %t_c.o %s -run + +extern (C) ubyte llvm_byte_c_add_uchar(ubyte a, ubyte b); +extern (C) ubyte llvm_byte_c_inc_uchar(ubyte x); + +void main() { + assert(llvm_byte_c_add_uchar(3, 40) == 43); + assert(llvm_byte_c_inc_uchar(cast(ubyte) 41) == 42); +} diff --git a/tests/linking/inputs/llvm_byte_lto_partner_aarch64_apple.ll b/tests/linking/inputs/llvm_byte_lto_partner_aarch64_apple.ll new file mode 100644 index 00000000000..e67582964fe --- /dev/null +++ b/tests/linking/inputs/llvm_byte_lto_partner_aarch64_apple.ll @@ -0,0 +1,20 @@ +; Partner for llvm_byte_full_lto_apple.d: b8 signatures for Full LTO with LDC. +; Triple/layout match arm64-apple-macos (Apple AArch64 hosts). + +target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32" +target triple = "arm64-apple-macos11.0" + +define zeroext b8 @llvm_byte_lto_add_one(b8 zeroext %x) #0 { +entry: + %xi = bitcast b8 %x to i8 + %y = add i8 %xi, 1 + %r = bitcast i8 %y to b8 + ret b8 %r +} + +define void @llvm_byte_lto_sink_uchar(b8 zeroext %x) #0 { +entry: + ret void +} + +attributes #0 = { nounwind uwtable } diff --git a/tests/linking/inputs/llvm_byte_lto_partner_aarch64_linux.ll b/tests/linking/inputs/llvm_byte_lto_partner_aarch64_linux.ll new file mode 100644 index 00000000000..f6026f21dff --- /dev/null +++ b/tests/linking/inputs/llvm_byte_lto_partner_aarch64_linux.ll @@ -0,0 +1,20 @@ +; Partner for llvm_byte_full_lto_linux.d: b8 signatures for Full LTO with LDC +; (-fc-interop-llvm-byte). Full LTO (not ThinLTO): llvm-as has no module summary. + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32" +target triple = "aarch64-unknown-linux-gnu" + +define zeroext b8 @llvm_byte_lto_add_one(b8 zeroext %x) #0 { +entry: + %xi = bitcast b8 %x to i8 + %y = add i8 %xi, 1 + %r = bitcast i8 %y to b8 + ret b8 %r +} + +define void @llvm_byte_lto_sink_uchar(b8 zeroext %x) #0 { +entry: + ret void +} + +attributes #0 = { nounwind uwtable } diff --git a/tests/linking/llvm_byte_full_lto_apple.d b/tests/linking/llvm_byte_full_lto_apple.d new file mode 100644 index 00000000000..eac7ccf5b24 --- /dev/null +++ b/tests/linking/llvm_byte_full_lto_apple.d @@ -0,0 +1,19 @@ +// Full LTO + b8 partner (hand-written IR), native Apple AArch64 link. +// See docs/byteType.md layer 3. Partner: inputs/llvm_byte_lto_partner_aarch64_apple.ll +// Darwin: link via the system clang driver (see fulllto_1.d), not -link-internally. +// +// REQUIRES: LTO && atleast_llvm23 && llvm_ir_b8 && target_AArch64 && host_AArch64 && Darwin + +// UNSUPPORTED: Windows + +// RUN: %llvm-as %S/inputs/llvm_byte_lto_partner_aarch64_apple.ll -o %t_p.bc +// RUN: %ldc -mtriple=arm64-apple-macos11.0 -fc-interop-llvm-byte -flto=full -O1 %t_p.bc %s -of=%t_x%exe +// RUN: test -f %t_x%exe + +extern (C) ubyte llvm_byte_lto_add_one(ubyte x); +extern (C) void llvm_byte_lto_sink_uchar(ubyte x); + +void main() { + llvm_byte_lto_sink_uchar(0); + assert(llvm_byte_lto_add_one(5) == 6); +} diff --git a/tests/linking/llvm_byte_full_lto_linux.d b/tests/linking/llvm_byte_full_lto_linux.d new file mode 100644 index 00000000000..c8b0729f5f6 --- /dev/null +++ b/tests/linking/llvm_byte_full_lto_linux.d @@ -0,0 +1,18 @@ +// Full LTO + b8 partner (hand-written IR), native AArch64 Linux link. +// See docs/byteType.md layer 3. Partner: inputs/llvm_byte_lto_partner_aarch64_linux.ll + +// REQUIRES: LTO && internal_lld && atleast_llvm23 && llvm_ir_b8 && target_AArch64 && host_AArch64 && Linux + +// UNSUPPORTED: Windows + +// RUN: %llvm-as %S/inputs/llvm_byte_lto_partner_aarch64_linux.ll -o %t_p.bc +// RUN: %ldc -mtriple=aarch64-unknown-linux-gnu -fc-interop-llvm-byte -flto=full -O1 -link-internally %t_p.bc %s -of=%t_x%exe +// RUN: test -f %t_x%exe + +extern (C) ubyte llvm_byte_lto_add_one(ubyte x); +extern (C) void llvm_byte_lto_sink_uchar(ubyte x); + +void main() { + llvm_byte_lto_sink_uchar(0); + assert(llvm_byte_lto_add_one(5) == 6); +} diff --git a/tests/lit.site.cfg.in b/tests/lit.site.cfg.in index ed6d98153ba..963b3b0eadc 100644 --- a/tests/lit.site.cfg.in +++ b/tests/lit.site.cfg.in @@ -96,6 +96,11 @@ for version in range(18, config.llvm_major+1): for version in range(config.llvm_major, 30): config.available_features.add("atmost_llvm%d" % version) +# LLVM 23+: IR scalar type b8 (e.g. Type::getByte8Ty); used by -fc-interop-llvm-byte. +# Matches LDC sources gated with LLVM_VERSION_MAJOR >= 23. +if config.llvm_major >= 23: + config.available_features.add("llvm_ir_b8") + # Define OS as available feature (Windows, Darwin, Linux, FreeBSD...) config.available_features.add(platform.system()) @@ -166,6 +171,7 @@ config.substitutions.append( ('%buildplugin', config.ldcbuildplugin_bin + " --ld config.substitutions.append( ('%timetrace2txt', config.timetrace2txt_bin) ) config.substitutions.append( ('%llvm-spirv', os.path.join(config.llvm_tools_dir, 'llvm-spirv')) ) config.substitutions.append( ('%llc', os.path.join(config.llvm_tools_dir, 'llc')) ) +config.substitutions.append( ('%llvm-as', os.path.join(config.llvm_tools_dir, 'llvm-as')) ) config.substitutions.append( ('%runtimedir', config.ldc2_runtime_dir ) ) # Add platform-dependent file extension substitutions