Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions driver/cl_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,14 @@ cl::opt<bool>
fSplitStack("fsplit-stack", cl::ZeroOrMore,
cl::desc("Use segmented stack (see Clang documentation)"));

cl::opt<bool> 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<bool, true>
allinst("allinst", cl::ZeroOrMore, cl::location(global.params.allInst),
cl::desc("Generate code for all template instantiations"));
Expand Down
2 changes: 2 additions & 0 deletions driver/cl_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ extern cl::opt<bool> fNoExceptions;
extern cl::opt<bool> fNoModuleInfo;
extern cl::opt<bool> fNoRTTI;
extern cl::opt<bool> fSplitStack;
/// [EXPERIMENTAL] Use LLVM b8 in extern(C/C++/…) signatures (default off).
extern cl::opt<bool> fCInteropLLVMByte;

// Arguments to -d-debug
extern std::vector<std::string> debugArgs;
Expand Down
13 changes: 12 additions & 1 deletion gen/abi/aarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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";
}
Expand Down
23 changes: 23 additions & 0 deletions gen/abi/abi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions gen/abi/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 10 additions & 0 deletions gen/statements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions gen/tocall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions ir/irfuncty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
}

Expand Down
11 changes: 11 additions & 0 deletions tests/codegen/llvm_byte/inputs/llvm_byte_c.c
Original file line number Diff line number Diff line change
@@ -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);
}
47 changes: 47 additions & 0 deletions tests/codegen/llvm_byte/llvm_byte_extern_ir.d
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions tests/codegen/llvm_byte/llvm_byte_link_runtime.d
Original file line number Diff line number Diff line change
@@ -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);
}
20 changes: 20 additions & 0 deletions tests/linking/inputs/llvm_byte_lto_partner_aarch64_apple.ll
Original file line number Diff line number Diff line change
@@ -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 }
20 changes: 20 additions & 0 deletions tests/linking/inputs/llvm_byte_lto_partner_aarch64_linux.ll
Original file line number Diff line number Diff line change
@@ -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 }
19 changes: 19 additions & 0 deletions tests/linking/llvm_byte_full_lto_apple.d
Original file line number Diff line number Diff line change
@@ -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);
}
18 changes: 18 additions & 0 deletions tests/linking/llvm_byte_full_lto_linux.d
Original file line number Diff line number Diff line change
@@ -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);
}
6 changes: 6 additions & 0 deletions tests/lit.site.cfg.in
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down Expand Up @@ -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
Expand Down
Loading