Skip to content

Commit 7c70876

Browse files
authored
Merge pull request #676 from glu-lang/feature/attributes/linkage-name
Feature: Add @linkage_name attribute
2 parents 7da802e + 8459db6 commit 7c70876

8 files changed

Lines changed: 215 additions & 14 deletions

File tree

include/AST/Attributes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ ATTRIBUTE_KIND(Inline, "inline", FunctionDefinitionAttachment)
1313
ATTRIBUTE_KIND(Unused, "unused", LocalAttachment)
1414
ATTRIBUTE_KIND(Packed, "packed", StructAttachment)
1515
ATTRIBUTE_WITH_PARAM(Alignment, "alignment", StructAttachment, LiteralExpr)
16+
ATTRIBUTE_WITH_PARAM(LinkageName, "linkage_name", FunctionAttachment, LiteralExpr)
1617

1718
#undef ATTRIBUTE_KIND
1819
#undef ATTRIBUTE_WITH_PARAM

lib/IRGen/IRGen.cpp

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ struct IRGenVisitor : public glu::gil::InstVisitor<IRGenVisitor> {
8585
ast::AttributeKind::NoManglingKind
8686
)) {
8787
// No mangling for functions marked as such
88+
} else if (auto *linkageAttr = fn->getDecl()->getAttribute(
89+
ast::AttributeKind::LinkageNameKind
90+
)) {
91+
// Use the specified linkage name
92+
auto *literal
93+
= llvm::dyn_cast<ast::LiteralExpr>(linkageAttr->getParameter());
94+
assert(literal && "linkage_name parameter should be a literal");
95+
assert(
96+
std::holds_alternative<llvm::StringRef>(literal->getValue())
97+
&& "linkage_name parameter should be a string literal"
98+
);
99+
linkageName = std::get<llvm::StringRef>(literal->getValue()).str();
88100
} else {
89101
linkageName = mangleFunctionName(fn->getDecl());
90102
}
@@ -96,6 +108,9 @@ struct IRGenVisitor : public glu::gil::InstVisitor<IRGenVisitor> {
96108
} else if (fn->getDecl() && fn->getDecl()->isPrivate()
97109
&& !fn->getDecl()->hasAttribute(
98110
ast::AttributeKind::NoManglingKind
111+
)
112+
&& !fn->getDecl()->hasAttribute(
113+
ast::AttributeKind::LinkageNameKind
99114
)) {
100115
// Private functions should have internal linkage, unless marked as
101116
// no_mangling
@@ -852,8 +867,8 @@ struct IRGenVisitor : public glu::gil::InstVisitor<IRGenVisitor> {
852867
mapValue(inst->getResult(0), result);
853868
}
854869

855-
// Macro to define visit methods for conversion instructions using the
856-
// template
870+
// Macro to define visit methods for conversion instructions using the
871+
// template
857872
#define DEFINE_CONVERSION_VISIT(InstClass, BuilderMethod) \
858873
void visit##InstClass(glu::gil::InstClass *inst) \
859874
{ \

lib/Sema/SemanticPass/DuplicateFunctionChecker.hpp

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace glu::sema {
1414
class DuplicateFunctionChecker
1515
: public ast::ASTWalker<DuplicateFunctionChecker, void> {
1616
DiagnosticManager &_diagManager;
17-
llvm::SmallVector<llvm::StringRef, 16> _noManglingFunctionNames;
17+
llvm::SmallVector<llvm::StringRef, 16> _usedLinkageNames;
1818

1919
public:
2020
explicit DuplicateFunctionChecker(DiagnosticManager &diagManager)
@@ -28,28 +28,52 @@ class DuplicateFunctionChecker
2828
return;
2929

3030
if (node->hasAttribute(ast::AttributeKind::NoManglingKind))
31-
checkNoManglingDuplicate(node);
31+
checkLinkageNameDuplicate(node, node->getName(), "no_mangling");
32+
else if (node->hasAttribute(ast::AttributeKind::LinkageNameKind))
33+
checkLinkageNameFromAttribute(node);
3234
else
3335
checkDuplicateFunction(node);
3436
}
3537

3638
private:
37-
void checkNoManglingDuplicate(ast::FunctionDecl *node)
39+
/// @brief Check for duplicate linkage names (used by both @no_mangling and
40+
/// @linkage_name)
41+
void checkLinkageNameDuplicate(
42+
ast::FunctionDecl *node, llvm::StringRef linkageName,
43+
llvm::StringRef attributeType
44+
)
3845
{
39-
llvm::StringRef functionName = node->getName();
40-
41-
if (llvm::find(_noManglingFunctionNames, functionName)
42-
!= _noManglingFunctionNames.end()) {
46+
if (llvm::find(_usedLinkageNames, linkageName)
47+
!= _usedLinkageNames.end()) {
4348
_diagManager.error(
4449
node->getLocation(),
45-
"duplicate function with no_mangling attribute: "
46-
+ functionName.str()
50+
"duplicate function with " + attributeType.str()
51+
+ " attribute '" + linkageName.str() + "'"
4752
);
4853
} else {
49-
_noManglingFunctionNames.push_back(functionName);
54+
_usedLinkageNames.push_back(linkageName);
5055
}
5156
}
5257

58+
/// @brief Handle @linkage_name attribute specifically
59+
void checkLinkageNameFromAttribute(ast::FunctionDecl *node)
60+
{
61+
auto *linkageAttr
62+
= node->getAttribute(ast::AttributeKind::LinkageNameKind);
63+
if (!linkageAttr || !linkageAttr->getParameter())
64+
return;
65+
66+
auto *literal
67+
= llvm::dyn_cast<ast::LiteralExpr>(linkageAttr->getParameter());
68+
if (!literal
69+
|| !std::holds_alternative<llvm::StringRef>(literal->getValue()))
70+
return;
71+
72+
llvm::StringRef linkageName
73+
= std::get<llvm::StringRef>(literal->getValue());
74+
checkLinkageNameDuplicate(node, linkageName, "linkage_name");
75+
}
76+
5377
void checkDuplicateFunction(ast::FunctionDecl *node)
5478
{
5579
auto *module = node->getModule();

lib/Sema/SemanticPass/ValidAttributeChecker.hpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,41 @@ class ValidAttributeChecker
5151
}
5252
}
5353

54+
/// @brief Validates the @linkage_name attribute parameter
55+
void validateLinkageNameAttribute(ast::Attribute *attr)
56+
{
57+
if (!attr->getParameter())
58+
return;
59+
60+
auto *literal = llvm::dyn_cast<ast::LiteralExpr>(attr->getParameter());
61+
if (!literal) {
62+
_diagManager.error(
63+
attr->getLocation(),
64+
"Attribute '@linkage_name' expects a string literal parameter"
65+
);
66+
return;
67+
}
68+
69+
if (!std::holds_alternative<llvm::StringRef>(literal->getValue())) {
70+
_diagManager.error(
71+
attr->getLocation(),
72+
"Attribute '@linkage_name' expects a string literal, not a "
73+
"numeric or other literal type"
74+
);
75+
return;
76+
}
77+
78+
llvm::StringRef linkageName
79+
= std::get<llvm::StringRef>(literal->getValue());
80+
81+
// Check if linkage name is non-empty
82+
if (linkageName.empty()) {
83+
_diagManager.error(
84+
attr->getLocation(), "Linkage name cannot be empty"
85+
);
86+
}
87+
}
88+
5489
/// @brief Validates attribute-specific constraints
5590
void validateAttributeValue(ast::Attribute *attr)
5691
{
@@ -59,6 +94,9 @@ class ValidAttributeChecker
5994
case ast::AttributeKind::AlignmentKind:
6095
validateAlignmentAttribute(attr);
6196
break;
97+
case ast::AttributeKind::LinkageNameKind:
98+
validateLinkageNameAttribute(attr);
99+
break;
62100
default: break;
63101
}
64102
}
@@ -138,6 +176,22 @@ class ValidAttributeChecker
138176
"function prototypes"
139177
);
140178
}
179+
180+
// Check for mutual exclusivity between @linkage_name and @no_mangling
181+
if (node->getAttributes()) {
182+
bool hasLinkageName
183+
= node->hasAttribute(ast::AttributeKind::LinkageNameKind);
184+
bool hasNoMangling
185+
= node->hasAttribute(ast::AttributeKind::NoManglingKind);
186+
187+
if (hasLinkageName && hasNoMangling) {
188+
_diagManager.error(
189+
node->getLocation(),
190+
"Attributes '@linkage_name' and '@no_mangling' are "
191+
"mutually exclusive"
192+
);
193+
}
194+
}
141195
}
142196

143197
void preVisitImportDecl(ast::ImportDecl *node)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//
2+
// RUN: split-file %s %t
3+
// RUN: gluc -c %t/libname.glu -o %t/libname.o
4+
// RUN: gluc %t/tester.glu --print-llvm-ir | FileCheck -v %t/tester.glu
5+
//
6+
// Test that @linkage_name attribute generates correct linkage names in LLVM IR
7+
8+
9+
//--- libname.glu
10+
11+
@linkage_name("custom_hello")
12+
public func world() -> *Char {
13+
return "Hello, World!";
14+
}
15+
16+
//--- tester.glu
17+
18+
import libname::*;
19+
20+
// CHECK: define void @implementation_name()
21+
@linkage_name("implementation_name")
22+
func myFunction() {
23+
return;
24+
}
25+
26+
// CHECK: define i32 @param_func(i32 %0)
27+
@linkage_name("param_func")
28+
func withParams(a: Int) -> Int {
29+
return a;
30+
}
31+
32+
33+
// CHECK: define i32 @main()
34+
// CHECK: call ptr @custom_hello()
35+
// CHECK: call void @implementation_name()
36+
// CHECK: call i32 @param_func(i32 42)
37+
func main() -> Int {
38+
world(); // calls function with linkage name "custom_hello"
39+
myFunction(); // calls function with linkage name "implementation_name"
40+
return withParams(42); // calls function with linkage name "param_func"
41+
}
42+
43+
// CHECK: declare {{.*}} @custom_hello()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// RUN: not gluc -c %s -o %t.o 2>&1 | FileCheck -v %s
3+
//
4+
// Test duplicate linkage names and conflicts with no_mangling functions
5+
6+
// Test 1: Two functions with the same linkage name should fail
7+
@linkage_name("duplicate_name")
8+
func firstFunction();
9+
10+
// CHECK: error: duplicate function with linkage_name attribute 'duplicate_name'
11+
@linkage_name("duplicate_name")
12+
func secondFunction(param: Int);
13+
14+
// Test 2: @linkage_name function conflicting with @no_mangling function
15+
@no_mangling
16+
func conflict_func();
17+
18+
// CHECK: error: duplicate function with linkage_name attribute 'conflict_func'
19+
@linkage_name("conflict_func")
20+
func anotherFunction();
21+
22+
// Test 3: @no_mangling function conflicting with existing @linkage_name
23+
@linkage_name("existing_name")
24+
func existingLinkage();
25+
26+
// CHECK: error: duplicate function with no_mangling attribute 'existing_name'
27+
@no_mangling
28+
func existing_name();
29+
30+
// CHECK: error: Attributes '@linkage_name' and '@no_mangling' are mutually exclusive
31+
@linkage_name("custom_name")
32+
@no_mangling
33+
func conflictingAttributes();
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// RUN: not gluc %s 2>&1 | FileCheck %s
3+
//
4+
// Test that @linkage_name attribute parameter validation works correctly
5+
6+
// Test 1: @linkage_name requires a string parameter
7+
// CHECK: error: Attribute '@linkage_name' expects a parameter of type LiteralExpr
8+
@linkage_name
9+
func missingParam();
10+
11+
// Test 2: @linkage_name with integer parameter (should fail)
12+
// CHECK: error: Attribute '@linkage_name' expects a string literal, not a numeric or other literal type
13+
@linkage_name(42)
14+
func wrongParamType();
15+
16+
// Test 3: @linkage_name with empty string (should fail)
17+
// CHECK: error: Linkage name cannot be empty
18+
@linkage_name("")
19+
func emptyLinkageName();
20+
21+
// Test 4: @linkage_name on wrong declaration type (should fail on structs)
22+
// CHECK: error: Attribute '@linkage_name' is not valid on structs
23+
@linkage_name("test")
24+
struct InvalidOnStruct {
25+
data: UInt64
26+
}
27+
28+
// Test 5: @linkage_name on wrong declaration type (should fail on variables)
29+
// CHECK: error: Attribute '@linkage_name' is not valid on global variables
30+
@linkage_name("test")
31+
var invalidOnVar: Int = 5;

test/functional/Sema/multiple_no_mangling_functions.glu

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
return;
77
}
88

9-
// CHECK: 10:14: error: duplicate function with no_mangling attribute: toto
9+
// CHECK: 10:14: error: duplicate function with no_mangling attribute 'toto'
1010
@no_mangling func toto(a: Int, argv: **Char) -> Int {
1111
return a;
1212
}
1313

14-
//CHECK: 15:14: error: duplicate function with no_mangling attribute: toto
14+
//CHECK: 15:14: error: duplicate function with no_mangling attribute 'toto'
1515
@no_mangling func toto(wesh: Float) {
1616
return;
1717
}

0 commit comments

Comments
 (0)