Skip to content

[SystemZ][z/OS] Add visibility features for z/OS (eg. _Export, pragma export) #111035

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e8d355c
initial work for pragma export & _Export keyword
perry-ca Oct 2, 2024
6cf4355
Merge remote-tracking branch 'origin/main' into perry/pragma-export
perry-ca Oct 2, 2024
e1cbba0
Add pragma export & _Export
perry-ca Oct 3, 2024
b23cfff
Merge remote-tracking branch 'origin/main' into perry/pragma-export
perry-ca Oct 3, 2024
39a1411
restore to main
perry-ca Oct 3, 2024
9cfc3cc
Reset pragma handler was missing
perry-ca Oct 3, 2024
e0cb769
formating
perry-ca Oct 3, 2024
82e9962
more formating
perry-ca Oct 3, 2024
e7e560a
missing requires & s390x-none-zos targets in some
perry-ca Oct 3, 2024
b5c7f01
merge conflict
perry-ca Oct 7, 2024
b99182f
Merge branch 'llvm:main' into perry/pragma-export
perry-ca Nov 26, 2024
5d40056
wip
perry-ca Oct 28, 2024
266840a
change func names
perry-ca Nov 26, 2024
1deb06a
Merge branch 'llvm:main' into perry/pragma-export
perry-ca Dec 12, 2024
5fb85ea
Merge branch 'llvm:main' into perry/pragma-export
perry-ca Mar 7, 2025
0fcfcfe
address comments
perry-ca Mar 28, 2025
b54bd17
handle arrays & func ptrs better
perry-ca Mar 31, 2025
6ba783c
Add error checking
perry-ca Apr 4, 2025
b407888
resolve merge conflict
perry-ca Apr 4, 2025
a1faeca
remove dead code
perry-ca Apr 4, 2025
7871a6d
formatting
perry-ca Apr 4, 2025
9672490
add release note
perry-ca Apr 4, 2025
a156ebd
add parsing lit tests
perry-ca Apr 8, 2025
c6eb30f
Merge remote-tracking branch 'origin/main' into perry/pragma-export
perry-ca Apr 8, 2025
5f0a685
Improve locations in messages
perry-ca Apr 8, 2025
a5187aa
more error handling & better reuse
perry-ca May 2, 2025
507960b
update tests
perry-ca May 2, 2025
aa8ffc7
test case updates
perry-ca May 2, 2025
34b2109
merge latest
perry-ca May 2, 2025
8432125
remove unused function and improve interface to other
perry-ca May 5, 2025
1198786
Merge remote-tracking branch 'origin/main' into perry/pragma-export
perry-ca May 5, 2025
9e23c5d
handle externC properly for C++
perry-ca May 5, 2025
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
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,11 @@ WebAssembly Support
AVR Support
^^^^^^^^^^^

SystemZ Support
^^^^^^^^^^^^^^^

- Add support for `#pragma export` and `_Export` keyword for z/OS

DWARF Support in Clang
----------------------

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/AST/ASTConsumer.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ class ASTConsumer {
/// completed.
virtual void CompleteExternalDeclaration(DeclaratorDecl *D) {}

/// CompletePragmaExport - complete #pragma export statements.
virtual void CompletePragmaExport(Decl *D) {}

/// Callback invoked when an MSInheritanceAttr has been attached to a
/// CXXRecordDecl.
virtual void AssignInheritanceModel(CXXRecordDecl *RD) {}
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -4663,6 +4663,12 @@ def ReleaseHandle : InheritableParamAttr {
let Documentation = [ReleaseHandleDocs];
}

def zOSExport : InheritableAttr {
let Spellings = [CustomKeyword<"_Export">];
let Subjects = SubjectList<[Function, Var, CXXRecord]>;
let Documentation = [zOSExportDocs];
}

def UnsafeBufferUsage : InheritableAttr {
let Spellings = [Clang<"unsafe_buffer_usage">];
let Subjects = SubjectList<[Function, Field]>;
Expand Down
21 changes: 21 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -7504,6 +7504,27 @@ attribute requires a string literal argument to identify the handle being releas
}];
}

def zOSExportDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Use the ``_Export`` keyword on a function or external variable to declare
that it is to be exported (made available to other modules). You must define
the object name in the same translation unit in which you use the ``_Export``
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This documentation should also mention the pragma spelling and document the whole thing. Also, do we diagnose that anothony isn't defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't diagnose that anthony isn't defined. This was never diagnosed in the existing z/OS compiler and neither with the visibility attribute. The wording is a little strong. We should say the object is only exported if it is also defined in the translation unit.

keyword. For example:

.. code-block:: c

int _Export anthony(float);

This statement exports the function ``anthony``, if you define the function in the
translation unit. The ``_Export`` keyword must immediately precede the declaration name.
If you apply the ``_Export`` keyword to a class, the compiler automatically exports
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you apply the ``_Export`` keyword to a class, the compiler automatically exports
If you apply the ``_Export`` keyword to a class or struct, the compiler automatically exports

What does it do with unions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unions are handled the same way as classes or structs. Any member function or static data member of the union will be exported.

all static data members and member functions of the class. However, if you want
it to apply to individual class members, then you must apply it to each member
that can be referenced.
}];
}

def UnsafeBufferUsageDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,13 @@ def err_pragma_pop_visibility_mismatch : Error<
"#pragma visibility pop with no matching #pragma visibility push">;
def note_surrounding_namespace_starts_here : Note<
"surrounding namespace with visibility attribute starts here">;
def warn_failed_to_resolve_pragma : Warning<
"failed to resolve '#pragma %0' to a declaration">,
InGroup<IgnoredPragmas>;
def warn_pragma_not_applied : Warning<
"#pragma %0 is applicable to symbols with external linkage only; "
"not applied to %1">,
InGroup<IgnoredPragmas>;
def err_pragma_loop_invalid_argument_type : Error<
"invalid argument of type %0; expected an integer type">;
def err_pragma_loop_compatibility : Error<
Expand Down Expand Up @@ -9444,6 +9451,8 @@ def warn_redefine_extname_not_applied : Warning<
"#pragma redefine_extname is applicable to external C declarations only; "
"not applied to %select{function|variable}0 %1">,
InGroup<Pragmas>;
def err_cannot_be_exported : Error<
"cannot be '_Export` qualified">;
} // End of general sema category.

// inline asm.
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,9 @@ KEYWORD(__objc_no , KEYALL)
// C2y
UNARY_EXPR_OR_TYPE_TRAIT(_Countof, CountOf, KEYNOCXX)

// z/OS specific keywords
KEYWORD(_Export , KEYZOS)

// C++ 2.11p1: Keywords.
KEYWORD(asm , KEYCXX|KEYGNU)
KEYWORD(bool , BOOLSUPPORT|KEYC23)
Expand Down Expand Up @@ -1008,6 +1011,9 @@ PRAGMA_ANNOTATION(pragma_fp)
// Annotation for the attribute pragma directives - #pragma clang attribute ...
PRAGMA_ANNOTATION(pragma_attribute)

// Annotation for C/C++ #pragma export(ident)
PRAGMA_ANNOTATION(pragma_export)

// Annotation for the riscv pragma directives - #pragma clang riscv intrinsic ...
PRAGMA_ANNOTATION(pragma_riscv)

Expand Down
13 changes: 13 additions & 0 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ class Parser : public CodeCompletionHandler {
std::unique_ptr<PragmaHandler> AttributePragmaHandler;
std::unique_ptr<PragmaHandler> MaxTokensHerePragmaHandler;
std::unique_ptr<PragmaHandler> MaxTokensTotalPragmaHandler;
std::unique_ptr<PragmaHandler> ExportHandler;
std::unique_ptr<PragmaHandler> RISCVPragmaHandler;

std::unique_ptr<CommentHandler> CommentSemaHandler;
Expand Down Expand Up @@ -856,6 +857,18 @@ class Parser : public CodeCompletionHandler {

void HandlePragmaAttribute();

/// Helper functions for handling zOS pragmas.
NestedNameSpecifier *zOSParseIdentifier(StringRef PragmaName,
const IdentifierInfo *IdentName);
bool zOSParseParameterList(StringRef PragmaName,
std::optional<SmallVector<QualType, 4>> &TypeList,
Qualifiers &CVQual);
bool zOSHandlePragmaHelper(tok::TokenKind);

/// Handle the annotation token produced for
/// #pragma export ...
void HandlePragmaExport();

/// GetLookAheadToken - This peeks ahead N tokens and returns that token
/// without consuming any tokens. LookAhead(0) returns 'Tok', LookAhead(1)
/// returns the token after Tok, etc.
Expand Down
38 changes: 34 additions & 4 deletions clang/include/clang/Sema/DeclSpec.h
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ class DeclSpec {
unsigned FS_virtual_specified : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned FS_noreturn_specified : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned ExportSpecified : 1; // z/OS extension

// friend-specifier
LLVM_PREFERRED_TYPE(bool)
Expand Down Expand Up @@ -443,6 +445,7 @@ class DeclSpec {
SourceLocation FS_forceinlineLoc;
SourceLocation FriendLoc, ModulePrivateLoc, ConstexprLoc;
SourceLocation TQ_pipeLoc;
SourceLocation ExportLoc;

WrittenBuiltinSpecs writtenBS;
void SaveWrittenBuiltinSpecs();
Expand Down Expand Up @@ -491,9 +494,9 @@ class DeclSpec {
TypeSpecPipe(false), TypeSpecSat(false), ConstrainedAuto(false),
TypeQualifiers(TQ_unspecified), FS_inline_specified(false),
FS_forceinline_specified(false), FS_virtual_specified(false),
FS_noreturn_specified(false), FriendSpecifiedFirst(false),
ConstexprSpecifier(
static_cast<unsigned>(ConstexprSpecKind::Unspecified)),
FS_noreturn_specified(false), ExportSpecified(false),
FriendSpecifiedFirst(false), ConstexprSpecifier(static_cast<unsigned>(
ConstexprSpecKind::Unspecified)),
Attrs(attrFactory), writtenBS(), ObjCQualifiers(nullptr) {}

// storage-class-specifier
Expand Down Expand Up @@ -660,6 +663,9 @@ class DeclSpec {
bool isNoreturnSpecified() const { return FS_noreturn_specified; }
SourceLocation getNoreturnSpecLoc() const { return FS_noreturnLoc; }

bool isExportSpecified() const { return ExportSpecified; }
SourceLocation getExportSpecLoc() const { return ExportLoc; }

void ClearFunctionSpecs() {
FS_inline_specified = false;
FS_inlineLoc = SourceLocation();
Expand Down Expand Up @@ -810,6 +816,8 @@ class DeclSpec {
bool setFunctionSpecNoreturn(SourceLocation Loc, const char *&PrevSpec,
unsigned &DiagID);

bool setExportSpec(SourceLocation Loc);

bool SetFriendSpec(SourceLocation Loc, const char *&PrevSpec,
unsigned &DiagID);
bool setModulePrivateSpec(SourceLocation Loc, const char *&PrevSpec,
Expand Down Expand Up @@ -1955,6 +1963,10 @@ class Declarator {
LLVM_PREFERRED_TYPE(bool)
unsigned InlineStorageUsed : 1;

/// Indicates whether this is set as _Export.
LLVM_PREFERRED_TYPE(bool)
unsigned ExportSpecified : 1; // z/OS extension

/// Indicates whether this declarator has an initializer.
LLVM_PREFERRED_TYPE(bool)
unsigned HasInitializer : 1;
Expand Down Expand Up @@ -2001,6 +2013,9 @@ class Declarator {
/// this declarator as a parameter pack.
SourceLocation EllipsisLoc;

/// The source location of the _Export keyword on this declarator.
SourceLocation ExportLoc;

Expr *PackIndexingExpr;

friend struct DeclaratorChunk;
Expand Down Expand Up @@ -2030,7 +2045,8 @@ class Declarator {
FunctionDefinitionKind::Declaration)),
Redeclaration(false), Extension(false), ObjCIvar(false),
ObjCWeakProperty(false), InlineStorageUsed(false),
HasInitializer(false), Attrs(DS.getAttributePool().getFactory()),
ExportSpecified(false), HasInitializer(false),
Attrs(DS.getAttributePool().getFactory()),
DeclarationAttrs(DeclarationAttrs), AsmLabel(nullptr),
TrailingRequiresClause(nullptr),
InventedTemplateParameterList(nullptr) {
Expand Down Expand Up @@ -2109,6 +2125,18 @@ class Declarator {
Range.setEnd(SR.getEnd());
}

/// Set this declarator as _Export.
void SetExport(SourceLocation Loc) {
ExportSpecified = true;
ExportLoc = Loc;
}

/// Whether this declarator is marked as _Export.
bool IsExport() const { return ExportSpecified; }

/// Get the location of the _Export keyword.
SourceLocation getExportLoc() const { return ExportLoc; }

/// Reset the contents of this Declarator.
void clear() {
SS.clear();
Expand All @@ -2125,8 +2153,10 @@ class Declarator {
HasInitializer = false;
ObjCIvar = false;
ObjCWeakProperty = false;
ExportSpecified = false;
CommaLoc = SourceLocation();
EllipsisLoc = SourceLocation();
ExportLoc = SourceLocation();
PackIndexingExpr = nullptr;
}

Expand Down
30 changes: 30 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -1952,6 +1952,36 @@ class Sema final : public SemaBase {
ActOnPragmaMSFunction(SourceLocation Loc,
const llvm::SmallVectorImpl<StringRef> &NoBuiltins);

/// A label from a C++ #pragma export, for a symbol that we
/// haven't seen the declaration for yet. The TypeList is the argument list
/// the function must match if HasTypeList is true.
struct SymbolLabel {
std::optional<SmallVector<QualType, 4>> TypeList;
StringRef MappedName;
SourceLocation NameLoc;
bool HasTypeList;
Qualifiers CVQual;
};

typedef SmallVector<SymbolLabel, 1> PendingSymbolOverloads;
typedef llvm::DenseMap<NestedNameSpecifier *, PendingSymbolOverloads>
SymbolNames;
SymbolNames PendingExportNames;

FunctionDecl *tryFunctionLookUpInPragma(NestedNameSpecifier *NestedName,
SourceLocation NameLoc);

/// trySymbolLookUp try to look up a decl matching the nested specifier
/// with optional type list.
NamedDecl *trySymbolLookUpInPragma(NestedNameSpecifier *NestedName,
const clang::Sema::SymbolLabel &Label);

/// ActonPragmaExport - called on well-formed '\#pragma export'.
void ActOnPragmaExport(NestedNameSpecifier *NestedId,
SourceLocation ExportNameLoc,
std::optional<SmallVector<QualType, 4>> &&TypeList,
Qualifiers CVQual);

/// Only called on function definitions; if there is a pragma in scope
/// with the effect of a range-based optnone, consider marking the function
/// with attribute optnone.
Expand Down
1 change: 1 addition & 0 deletions clang/lib/CodeGen/BackendConsumer.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class BackendConsumer : public ASTConsumer {
void HandleTagDeclRequiredDefinition(const TagDecl *D) override;
void CompleteTentativeDefinition(VarDecl *D) override;
void CompleteExternalDeclaration(DeclaratorDecl *D) override;
void CompletePragmaExport(Decl *D) override;
void AssignInheritanceModel(CXXRecordDecl *RD) override;
void HandleVTable(CXXRecordDecl *RD) override;

Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CodeGen/CodeGenAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ void BackendConsumer::CompleteExternalDeclaration(DeclaratorDecl *D) {
Gen->CompleteExternalDeclaration(D);
}

void BackendConsumer::CompletePragmaExport(Decl *D) {
Gen->CompletePragmaExport(D);
}

void BackendConsumer::AssignInheritanceModel(CXXRecordDecl *RD) {
Gen->AssignInheritanceModel(RD);
}
Expand Down
15 changes: 15 additions & 0 deletions clang/lib/CodeGen/CodeGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5356,6 +5356,21 @@ void CodeGenModule::EmitExternalDeclaration(const DeclaratorDecl *D) {
EmitExternalFunctionDeclaration(FD);
}

void CodeGenModule::EmitPragmaExport(const Decl *D) {
StringRef MangledName;
if (auto FD = dyn_cast<FunctionDecl>(D))
MangledName = getMangledName(GlobalDecl(FD));
else if (auto VD = dyn_cast<VarDecl>(D))
MangledName = getMangledName(GlobalDecl(VD));
else
assert(0 && "Unsupported pragma export Decl type");

if (llvm::GlobalValue *GV = GetGlobalValue(MangledName)) {
GV->setVisibility(llvm::GlobalValue::DefaultVisibility);
GV->setDSOLocal(false);
}
}

CharUnits CodeGenModule::GetTargetTypeStoreSize(llvm::Type *Ty) const {
return Context.toCharUnitsFromBits(
getDataLayout().getTypeStoreSizeInBits(Ty));
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/CodeGen/CodeGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,8 @@ class CodeGenModule : public CodeGenTypeCache {

void EmitExternalDeclaration(const DeclaratorDecl *D);

void EmitPragmaExport(const Decl *D);

void EmitVTable(CXXRecordDecl *Class);

void RefreshTypeCacheForClass(const CXXRecordDecl *Class);
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CodeGen/ModuleBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ namespace {
Builder->EmitExternalDeclaration(D);
}

void CompletePragmaExport(Decl *D) override {
Builder->EmitPragmaExport(D);
}

void HandleVTable(CXXRecordDecl *RD) override {
if (Diags.hasUnrecoverableErrorOccurred())
return;
Expand Down
11 changes: 7 additions & 4 deletions clang/lib/Driver/ToolChains/ZOS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ void ZOS::addClangTargetOptions(const ArgList &DriverArgs,
options::OPT_fno_aligned_allocation))
CC1Args.push_back("-faligned-alloc-unavailable");

if (!DriverArgs.hasArg(options::OPT_fvisibility_EQ,
options::OPT_fvisibility_ms_compat))
CC1Args.push_back("-fvisibility=hidden");

if (DriverArgs.hasFlag(options::OPT_fxl_pragma_pack,
options::OPT_fno_xl_pragma_pack, true))
CC1Args.push_back("-fxl-pragma-pack");
Expand Down Expand Up @@ -153,11 +157,10 @@ void zos::Linker::ConstructJob(Compilation &C, const JobAction &JA,
StringRef OutputName = Output.getFilename();
// Strip away the last file suffix in presence from output name and add
// a new .x suffix.
size_t Suffix = OutputName.find_last_of('.');
const char *SideDeckName =
Args.MakeArgString(OutputName.substr(0, Suffix) + ".x");
SmallString<128> SideDeckName = OutputName;
llvm::sys::path::replace_extension(SideDeckName, "x");
CmdArgs.push_back("-x");
CmdArgs.push_back(SideDeckName);
CmdArgs.push_back(Args.MakeArgString(SideDeckName));
} else {
// We need to direct side file to /dev/null to suppress linker warning when
// the object file contains exported symbols, and -shared or
Expand Down
16 changes: 16 additions & 0 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4459,6 +4459,11 @@ void Parser::ParseDeclarationSpecifiers(
isInvalid = DS.setFunctionSpecNoreturn(Loc, PrevSpec, DiagID);
break;

case tok::kw__Export:
// We're done with the declaration-specifiers.
goto DoneWithDeclSpec;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean _Export has to be last? Not a parser guy here, but this one doesn't feel right to me? @AaronBallman can you double check here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_Export is part of the declarator. If you see the keyword, the declspec is done and and you are now in the declarator.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_Export is not really a declaration specifier (at least in the traditional implementation).
It is more like a C++11 attribute except that it lives on the left of a declarator-id instead of on the right.
In some realizations, the (less clean) conceptual model is that it is a declarator like * is, except it binds more strongly than (<function parameters>) or [<array bound>].

@perry-ca, I don't see any test in this PR for _Export in positions like:

int (*_Export x)(void) = 0;

Is the above accepted with this PR? Which conceptual model is being implemented here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test case above is valid and exports x.

@x = global ptr null, align 8

I'll add a test case to cover this.

You are correct. This keyword is expected to appear right before the identifier of the symbol being exported.

break;

// friend
case tok::kw_friend:
if (DSContext == DeclSpecContext::DSC_class) {
Expand Down Expand Up @@ -6774,6 +6779,17 @@ void Parser::ParseDeclaratorInternal(Declarator &D,

tok::TokenKind Kind = Tok.getKind();

// If this variable or function is marked as _Export, set the bit.
if (Kind == tok::kw__Export) {
SourceLocation loc = ConsumeToken();
D.SetExport(loc);
D.SetRangeEnd(loc);

if (DirectDeclParser)
(this->*DirectDeclParser)(D);
return;
}

if (D.getDeclSpec().isTypeSpecPipe() && !isPipeDeclarator(D)) {
DeclSpec DS(AttrFactory);
ParseTypeQualifierListOpt(DS);
Expand Down
Loading