Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions include/AST/Attributes.def
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ ATTRIBUTE_KIND(Inline, "inline", FunctionDefinitionAttachment)
ATTRIBUTE_KIND(Unused, "unused", LocalAttachment)
ATTRIBUTE_KIND(Packed, "packed", StructAttachment)
ATTRIBUTE_WITH_PARAM(Alignment, "alignment", StructAttachment, LiteralExpr)
ATTRIBUTE_WITH_PARAM(LinkageName, "linkage_name", FunctionAttachment, LiteralExpr)

#undef ATTRIBUTE_KIND
#undef ATTRIBUTE_WITH_PARAM
19 changes: 17 additions & 2 deletions lib/IRGen/IRGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ struct IRGenVisitor : public glu::gil::InstVisitor<IRGenVisitor> {
ast::AttributeKind::NoManglingKind
)) {
// No mangling for functions marked as such
} else if (auto *linkageAttr = fn->getDecl()->getAttribute(
ast::AttributeKind::LinkageNameKind
)) {
// Use the specified linkage name
auto *literal
= llvm::dyn_cast<ast::LiteralExpr>(linkageAttr->getParameter());
assert(literal && "linkage_name parameter should be a literal");
assert(
std::holds_alternative<llvm::StringRef>(literal->getValue())
&& "linkage_name parameter should be a string literal"
);
linkageName = std::get<llvm::StringRef>(literal->getValue()).str();
} else {
linkageName = mangleFunctionName(fn->getDecl());
}
Expand All @@ -96,6 +108,9 @@ struct IRGenVisitor : public glu::gil::InstVisitor<IRGenVisitor> {
} else if (fn->getDecl() && fn->getDecl()->isPrivate()
&& !fn->getDecl()->hasAttribute(
ast::AttributeKind::NoManglingKind
)
&& !fn->getDecl()->hasAttribute(
ast::AttributeKind::LinkageNameKind
)) {
// Private functions should have internal linkage, unless marked as
// no_mangling
Expand Down Expand Up @@ -852,8 +867,8 @@ struct IRGenVisitor : public glu::gil::InstVisitor<IRGenVisitor> {
mapValue(inst->getResult(0), result);
}

// Macro to define visit methods for conversion instructions using the
// template
// Macro to define visit methods for conversion instructions using the
// template
#define DEFINE_CONVERSION_VISIT(InstClass, BuilderMethod) \
void visit##InstClass(glu::gil::InstClass *inst) \
{ \
Expand Down
54 changes: 54 additions & 0 deletions lib/Sema/SemanticPass/DuplicateFunctionChecker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class DuplicateFunctionChecker
: public ast::ASTWalker<DuplicateFunctionChecker, void> {
DiagnosticManager &_diagManager;
llvm::SmallVector<llvm::StringRef, 16> _noManglingFunctionNames;
llvm::SmallVector<llvm::StringRef, 16> _linkageNameFunctionNames;
Comment thread
LindonAliu marked this conversation as resolved.
Outdated

public:
explicit DuplicateFunctionChecker(DiagnosticManager &diagManager)
Expand All @@ -29,6 +30,8 @@ class DuplicateFunctionChecker

if (node->hasAttribute(ast::AttributeKind::NoManglingKind))
checkNoManglingDuplicate(node);
else if (node->hasAttribute(ast::AttributeKind::LinkageNameKind))
checkLinkageNameDuplicate(node);
else
checkDuplicateFunction(node);
}
Expand All @@ -48,6 +51,57 @@ class DuplicateFunctionChecker
} else {
_noManglingFunctionNames.push_back(functionName);
}

// Also check if this no_mangling function conflicts with any linkage
// names
if (llvm::find(_linkageNameFunctionNames, functionName)
!= _linkageNameFunctionNames.end()) {
_diagManager.error(
node->getLocation(),
"function with no_mangling conflicts with a function using "
"linkage_name '"
+ functionName.str() + "'"
);
}
}

void checkLinkageNameDuplicate(ast::FunctionDecl *node)
{
auto *linkageAttr
= node->getAttribute(ast::AttributeKind::LinkageNameKind);
if (!linkageAttr || !linkageAttr->getParameter())
return;

auto *literal
= llvm::dyn_cast<ast::LiteralExpr>(linkageAttr->getParameter());
if (!literal
|| !std::holds_alternative<llvm::StringRef>(literal->getValue()))
return;

llvm::StringRef linkageName
= std::get<llvm::StringRef>(literal->getValue());

if (llvm::find(_linkageNameFunctionNames, linkageName)
!= _linkageNameFunctionNames.end()) {
_diagManager.error(
node->getLocation(),
"duplicate function with linkage_name '" + linkageName.str()
+ "'"
);
} else {
_linkageNameFunctionNames.push_back(linkageName);
}

// Also check if this linkage name conflicts with any no_mangling
// functions
if (llvm::find(_noManglingFunctionNames, linkageName)
!= _noManglingFunctionNames.end()) {
_diagManager.error(
node->getLocation(),
"function with linkage_name '" + linkageName.str()
+ "' conflicts with a no_mangling function"
);
}
}

void checkDuplicateFunction(ast::FunctionDecl *node)
Expand Down
57 changes: 57 additions & 0 deletions lib/Sema/SemanticPass/ValidAttributeChecker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,44 @@ class ValidAttributeChecker
}
}

/// @brief Validates the @linkage_name attribute parameter
void validateLinkageNameAttribute(ast::Attribute *attr)
{
if (!attr->getParameter())
return;

auto *literal = llvm::dyn_cast<ast::LiteralExpr>(attr->getParameter());
if (!literal) {
_diagManager.error(
attr->getLocation(),
"Attribute '@linkage_name' expects a string literal parameter"
);
return;
}

if (!std::holds_alternative<llvm::StringRef>(literal->getValue())) {
_diagManager.error(
attr->getLocation(),
"Attribute '@linkage_name' expects a string literal, not a "
"numeric or other literal type"
);
return;
}

llvm::StringRef linkageName
= std::get<llvm::StringRef>(literal->getValue());

// Check if linkage name is non-empty
if (linkageName.empty()) {
_diagManager.error(
attr->getLocation(), "Linkage name cannot be empty"
);
}

// TODO: Add more validation for valid linkage names if needed
// (e.g., check for valid identifier characters)
Comment thread
LindonAliu marked this conversation as resolved.
Outdated
}

/// @brief Validates attribute-specific constraints
void validateAttributeValue(ast::Attribute *attr)
{
Expand All @@ -59,6 +97,9 @@ class ValidAttributeChecker
case ast::AttributeKind::AlignmentKind:
validateAlignmentAttribute(attr);
break;
case ast::AttributeKind::LinkageNameKind:
validateLinkageNameAttribute(attr);
break;
default: break;
}
}
Expand Down Expand Up @@ -138,6 +179,22 @@ class ValidAttributeChecker
"function prototypes"
);
}

// Check for mutual exclusivity between @linkage_name and @no_mangling
if (node->getAttributes()) {
bool hasLinkageName
= node->hasAttribute(ast::AttributeKind::LinkageNameKind);
bool hasNoMangling
= node->hasAttribute(ast::AttributeKind::NoManglingKind);

if (hasLinkageName && hasNoMangling) {
_diagManager.error(
node->getLocation(),
"Attributes '@linkage_name' and '@no_mangling' are "
"mutually exclusive"
);
}
}
}

void preVisitImportDecl(ast::ImportDecl *node)
Expand Down
33 changes: 33 additions & 0 deletions test/functional/Sema/linkage_name_duplicates.glu
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// RUN: not gluc -c %s -o %t.o 2>&1 | FileCheck -v %s
//
// Test duplicate linkage names and conflicts with no_mangling functions

// Test 1: Two functions with the same linkage name should fail
@linkage_name("duplicate_name")
func firstFunction();

// CHECK: error: duplicate function with linkage_name 'duplicate_name'
@linkage_name("duplicate_name")
func secondFunction(param: Int);

// Test 2: @linkage_name function conflicting with @no_mangling function
@no_mangling
func conflict_func();

// CHECK: error: function with linkage_name 'conflict_func' conflicts with a no_mangling function
@linkage_name("conflict_func")
func anotherFunction();

// Test 3: @no_mangling function conflicting with existing @linkage_name
@linkage_name("existing_name")
func existingLinkage();

// CHECK: error: function with no_mangling conflicts with a function using linkage_name 'existing_name'
@no_mangling
func existing_name();

// CHECK: error: Attributes '@linkage_name' and '@no_mangling' are mutually exclusive
@linkage_name("custom_name")
@no_mangling
func conflictingAttributes();
46 changes: 46 additions & 0 deletions test/functional/Sema/linkage_name_valid.glu
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
Comment thread
LindonAliu marked this conversation as resolved.
Outdated
// RUN: gluc -c %s -o %s.o > /dev/null
//
// Test valid uses of @linkage_name attribute (should parse and validate without errors)

// Valid function prototype with linkage_name
@linkage_name("custom_hello")
func world();

// Valid function definition with linkage_name
@linkage_name("implementation_name")
func myFunction() {
return;
}

// Valid function with parameters and linkage_name
@linkage_name("param_func")
func withParams(a: Int, b: Double) -> Int {
return a;
}

// Multiple functions with different linkage names (should be fine)
@linkage_name("func1")
func function1();

@linkage_name("func2")
func function2();

@linkage_name("func3")
func function3(x: Int);

// Function without linkage_name mixed with ones that have it (should be fine)
func normalFunction() {
return;
}

// Call the functions using their original names (not linkage names)
func testCalls() {
world(); // calls the function with linkage name "custom_hello"
myFunction();
let result = withParams(42, 3.14);
function1();
function2();
function3(100);
normalFunction();
}
31 changes: 31 additions & 0 deletions test/functional/Sema/linkage_name_validation.glu
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// RUN: not gluc %s 2>&1 | FileCheck %s
//
// Test that @linkage_name attribute parameter validation works correctly

// Test 1: @linkage_name requires a string parameter
// CHECK: error: Attribute '@linkage_name' expects a parameter of type LiteralExpr
@linkage_name
func missingParam();

// Test 2: @linkage_name with integer parameter (should fail)
// CHECK: error: Attribute '@linkage_name' expects a string literal, not a numeric or other literal type
@linkage_name(42)
func wrongParamType();

// Test 3: @linkage_name with empty string (should fail)
// CHECK: error: Linkage name cannot be empty
@linkage_name("")
func emptyLinkageName();

// Test 4: @linkage_name on wrong declaration type (should fail on structs)
// CHECK: error: Attribute '@linkage_name' is not valid on structs
@linkage_name("test")
struct InvalidOnStruct {
data: UInt64
}

// Test 5: @linkage_name on wrong declaration type (should fail on variables)
// CHECK: error: Attribute '@linkage_name' is not valid on global variables
@linkage_name("test")
var invalidOnVar: Int = 5;
Loading