473 changes: 313 additions & 160 deletions .github/workflows/release-binaries.yml

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions .github/workflows/release-tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,20 @@ jobs:
needs:
- validate-tag
- release-create
strategy:
fail-fast: false
matrix:
runs-on:
- ubuntu-22.04
- windows-2022
- macos-13
- macos-14

uses: ./.github/workflows/release-binaries.yml
with:
release-version: ${{ needs.validate-tag.outputs.release-version }}
upload: true
runs-on: ${{ matrix.runs-on }}

release-sources:
name: Package Release Sources
Expand Down
5 changes: 4 additions & 1 deletion bolt/include/bolt/Core/DebugData.h
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ class DebugStrOffsetsWriter {
}

/// Update Str offset in .debug_str in .debug_str_offsets.
void updateAddressMap(uint32_t Index, uint32_t Address);
void updateAddressMap(uint32_t Index, uint32_t Address,
const DWARFUnit &Unit);

/// Get offset for given index in original .debug_str_offsets section.
uint64_t getOffset(uint32_t Index) const { return StrOffsets[Index]; }
Expand Down Expand Up @@ -507,6 +508,8 @@ class DebugStrOffsetsWriter {
std::unique_ptr<DebugStrOffsetsBufferVector> StrOffsetsBuffer;
std::unique_ptr<raw_svector_ostream> StrOffsetsStream;
std::map<uint32_t, uint32_t> IndexToAddressMap;
[[maybe_unused]]
DenseSet<uint64_t> DebugStrOffsetFinalized;
SmallVector<uint32_t, 5> StrOffsets;
std::unordered_map<uint64_t, uint64_t> ProcessedBaseOffsets;
bool StrOffsetSectionWasModified = false;
Expand Down
2 changes: 1 addition & 1 deletion bolt/lib/Core/DIEBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ static void addStringHelper(DebugStrOffsetsWriter &StrOffstsWriter,
uint32_t NewOffset = StrWriter.addString(Str);
if (Unit.getVersion() >= 5) {
StrOffstsWriter.updateAddressMap(DIEAttrInfo.getDIEInteger().getValue(),
NewOffset);
NewOffset, Unit);
return;
}
DIEBldr.replaceValue(&Die, DIEAttrInfo.getAttribute(), DIEAttrInfo.getForm(),
Expand Down
8 changes: 7 additions & 1 deletion bolt/lib/Core/DebugData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,11 @@ void DebugStrOffsetsWriter::initialize(DWARFUnit &Unit) {
StrOffsetsSection.Data.data() + Contr->Base + Offset));
}

void DebugStrOffsetsWriter::updateAddressMap(uint32_t Index, uint32_t Address) {
void DebugStrOffsetsWriter::updateAddressMap(uint32_t Index, uint32_t Address,
const DWARFUnit &Unit) {
assert(DebugStrOffsetFinalized.count(Unit.getOffset()) == 0 &&
"Cannot update address map since debug_str_offsets was already "
"finalized for this CU.");
IndexToAddressMap[Index] = Address;
StrOffsetSectionWasModified = true;
}
Expand Down Expand Up @@ -906,6 +910,8 @@ void DebugStrOffsetsWriter::finalizeSection(DWARFUnit &Unit,
}

StrOffsetSectionWasModified = false;
assert(DebugStrOffsetFinalized.insert(Unit.getOffset()).second &&
"debug_str_offsets was already finalized for this CU.");
clear();
}

Expand Down
8 changes: 7 additions & 1 deletion bolt/lib/Rewrite/DWARFRewriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "llvm/MC/MCAssembler.h"
#include "llvm/MC/MCObjectWriter.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCTargetOptionsCommandFlags.h"
#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/CommandLine.h"
Expand All @@ -56,6 +57,8 @@
#undef DEBUG_TYPE
#define DEBUG_TYPE "bolt"

static mc::RegisterMCTargetOptionsFlags MOF;

static void printDie(const DWARFDie &DIE) {
DIDumpOptions DumpOpts;
DumpOpts.ShowForm = true;
Expand Down Expand Up @@ -711,7 +714,8 @@ void DWARFRewriter::updateDebugInfo() {
RangesBase = RangesSectionWriter.getSectionOffset() +
getDWARF5RngListLocListHeaderSize();
RangesSectionWriter.initSection(Unit);
StrOffstsWriter->finalizeSection(Unit, DIEBlder);
if (!SplitCU)
StrOffstsWriter->finalizeSection(Unit, DIEBlder);
} else if (SplitCU) {
RangesBase = LegacyRangesSectionWriter.get()->getSectionOffset();
}
Expand Down Expand Up @@ -757,6 +761,8 @@ void DWARFRewriter::updateDebugInfo() {
: std::optional<std::string>(opts::DwarfOutputPath.c_str());
std::string DWOName = DIEBlder.updateDWONameCompDir(
*StrOffstsWriter, *StrWriter, *CU, DwarfOutputPath, std::nullopt);
if (CU->getVersion() >= 5)
StrOffstsWriter->finalizeSection(*CU, DIEBlder);
processSplitCU(*CU, **SplitCU, DIEBlder, *TempRangesSectionWriter,
AddressWriter, DWOName, DwarfOutputPath);
}
Expand Down
4 changes: 3 additions & 1 deletion bolt/test/AArch64/dummy-return.s
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# REQUIRES: system-linux,target=aarch64{{.*}}
# This test checks instrumentation of static binary on AArch64.

# REQUIRES: system-linux,bolt-runtime,target=aarch64{{.*}}

# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o
# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -static
Expand Down
28 changes: 28 additions & 0 deletions bolt/test/X86/dwarf5-df-larger-batch-size.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
; RUN: rm -rf %t
; RUN: mkdir %t
; RUN: cd %t
; RUN: llvm-mc -dwarf-version=5 -filetype=obj -triple x86_64-unknown-linux %p/Inputs/dwarf5-df-input-lowpc-ranges-main.s \
; RUN: -split-dwarf-file=main.dwo -o main.o
; RUN: llvm-mc -dwarf-version=5 -filetype=obj -triple x86_64-unknown-linux %p/Inputs/dwarf5-df-input-lowpc-ranges-other.s \
; RUN: -split-dwarf-file=mainOther.dwo -o other.o
; RUN: %clang %cflags main.o other.o -o main.exe
; RUN: llvm-bolt main.exe -o main.exe.bolt --update-debug-sections --cu-processing-batch-size=1
; RUN: llvm-bolt main.exe -o main-batch.exe.bolt --update-debug-sections --cu-processing-batch-size=2
; RUN: llvm-dwarfdump --show-form --verbose --debug-info main.exe.bolt >> %t/foo.txt
; RUN: cat %t/foo.txt | FileCheck -check-prefix=BOLT %s
; RUN: llvm-dwarfdump --show-form --verbose --debug-info main.exe.bolt >> %t/foo-batch.txt
; RUN: cat %t/foo-batch.txt | FileCheck -check-prefix=BOLT-BATCH %s

;; Tests that BOLT correctly handles DWO name strings with larger batch sizes.

; BOLT: DW_TAG_skeleton_unit
; BOLT: DW_AT_dwo_name [DW_FORM_strx1] (indexed (00000001) string = "main.dwo.dwo")

; BOLT: DW_TAG_skeleton_unit
; BOLT: DW_AT_dwo_name [DW_FORM_strx1] (indexed (00000001) string = "mainOther.dwo.dwo")

; BOLT-BATCH: DW_TAG_skeleton_unit
; BOLT-BATCH: DW_AT_dwo_name [DW_FORM_strx1] (indexed (00000001) string = "main.dwo.dwo")

; BOLT-BATCH: DW_TAG_skeleton_unit
; BOLT-BATCH: DW_AT_dwo_name [DW_FORM_strx1] (indexed (00000001) string = "mainOther.dwo.dwo")
118 changes: 102 additions & 16 deletions clang-tools-extra/clang-tidy/add_new_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@

import argparse
import io
import itertools
import os
import re
import sys
import textwrap


# Adapts the module's CMakelist file. Returns 'True' if it could add a new
# entry and 'False' if the entry already existed.
def adapt_cmake(module_path, check_name_camel):
Expand Down Expand Up @@ -55,13 +57,28 @@ def adapt_cmake(module_path, check_name_camel):

# Adds a header for the new check.
def write_header(
module_path, module, namespace, check_name, check_name_camel, description
module_path,
module,
namespace,
check_name,
check_name_camel,
description,
lang_restrict,
):
wrapped_desc = "\n".join(
textwrap.wrap(
description, width=80, initial_indent="/// ", subsequent_indent="/// "
)
)
if lang_restrict:
override_supported = """
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return %s;
}""" % (
lang_restrict % {"lang": "LangOpts"}
)
else:
override_supported = ""
filename = os.path.join(module_path, check_name_camel) + ".h"
print("Creating %s..." % filename)
with io.open(filename, "w", encoding="utf8", newline="\n") as f:
Expand Down Expand Up @@ -102,7 +119,7 @@ class %(check_name_camel)s : public ClangTidyCheck {
%(check_name_camel)s(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;%(override_supported)s
};
} // namespace clang::tidy::%(namespace)s
Expand All @@ -116,6 +133,7 @@ class %(check_name_camel)s : public ClangTidyCheck {
"module": module,
"namespace": namespace,
"description": wrapped_desc,
"override_supported": override_supported,
}
)

Expand Down Expand Up @@ -306,7 +324,9 @@ def add_release_notes(module_path, module, check_name, description):


# Adds a test for the check.
def write_test(module_path, module, check_name, test_extension):
def write_test(module_path, module, check_name, test_extension, test_standard):
if test_standard:
test_standard = f"-std={test_standard}-or-later "
check_name_dashes = module + "-" + check_name
filename = os.path.normpath(
os.path.join(
Expand All @@ -323,7 +343,7 @@ def write_test(module_path, module, check_name, test_extension):
print("Creating %s..." % filename)
with io.open(filename, "w", encoding="utf8", newline="\n") as f:
f.write(
"""// RUN: %%check_clang_tidy %%s %(check_name_dashes)s %%t
"""// RUN: %%check_clang_tidy %(standard)s%%s %(check_name_dashes)s %%t
// FIXME: Add something that triggers the check here.
void f();
Expand All @@ -338,7 +358,7 @@ def write_test(module_path, module, check_name, test_extension):
// FIXME: Add something that doesn't trigger the check here.
void awesome_f2();
"""
% {"check_name_dashes": check_name_dashes}
% {"check_name_dashes": check_name_dashes, "standard": test_standard}
)


Expand Down Expand Up @@ -511,7 +531,10 @@ def format_link_alias(doc_file):
if (match or (check_name.startswith("clang-analyzer-"))) and check_name:
module = doc_file[0]
check_file = doc_file[1].replace(".rst", "")
if not match or match.group(1) == "https://clang.llvm.org/docs/analyzer/checkers":
if (
not match
or match.group(1) == "https://clang.llvm.org/docs/analyzer/checkers"
):
title = "Clang Static Analyzer " + check_file
# Preserve the anchor in checkers.html from group 2.
target = "" if not match else match.group(1) + ".html" + match.group(2)
Expand All @@ -529,29 +552,31 @@ def format_link_alias(doc_file):
if target:
# The checker is just a redirect.
return (
" :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(ref_begin)s`%(title)s <%(target)s>`%(ref_end)s,%(autofix)s\n"
" :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(ref_begin)s`%(title)s <%(target)s>`%(ref_end)s,%(autofix)s\n"
% {
"check_name": check_name,
"module": module,
"check_file": check_file,
"target": target,
"title": title,
"autofix": autofix,
"ref_begin" : ref_begin,
"ref_end" : ref_end
})
"ref_begin": ref_begin,
"ref_end": ref_end,
}
)
else:
# The checker is just a alias without redirect.
return (
" :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(title)s,%(autofix)s\n"
" :doc:`%(check_name)s <%(module)s/%(check_file)s>`, %(title)s,%(autofix)s\n"
% {
"check_name": check_name,
"module": module,
"check_file": check_file,
"target": target,
"title": title,
"autofix": autofix,
})
}
)
return ""

checks = map(format_link, doc_files)
Expand Down Expand Up @@ -613,6 +638,22 @@ def main():
"objc": "m",
"objc++": "mm",
}
cpp_language_to_requirements = {
"c++98": "CPlusPlus",
"c++11": "CPlusPlus11",
"c++14": "CPlusPlus14",
"c++17": "CPlusPlus17",
"c++20": "CPlusPlus20",
"c++23": "CPlusPlus23",
"c++26": "CPlusPlus26",
}
c_language_to_requirements = {
"c99": None,
"c11": "C11",
"c17": "C17",
"c23": "C23",
"c27": "C2Y",
}
parser = argparse.ArgumentParser()
parser.add_argument(
"--update-docs",
Expand All @@ -623,7 +664,7 @@ def main():
"--language",
help="language to use for new check (defaults to c++)",
choices=language_to_extension.keys(),
default="c++",
default=None,
metavar="LANG",
)
parser.add_argument(
Expand All @@ -633,6 +674,16 @@ def main():
default="FIXME: Write a short description",
type=str,
)
parser.add_argument(
"--standard",
help="Specify a specific version of the language",
choices=list(
itertools.chain(
cpp_language_to_requirements.keys(), c_language_to_requirements.keys()
)
),
default=None,
)
parser.add_argument(
"module",
nargs="?",
Expand Down Expand Up @@ -677,14 +728,49 @@ def main():
if not description.endswith("."):
description += "."

language = args.language

if args.standard:
if args.standard in cpp_language_to_requirements:
if language and language != "c++":
raise ValueError("C++ standard chosen when language is not C++")
language = "c++"
elif args.standard in c_language_to_requirements:
if language and language != "c":
raise ValueError("C standard chosen when language is not C")
language = "c"

if not language:
language = "c++"

language_restrict = None

if language == "c":
language_restrict = "!%(lang)s.CPlusPlus"
extra = c_language_to_requirements.get(args.standard, None)
if extra:
language_restrict += f" && %(lang)s.{extra}"
elif language == "c++":
language_restrict = (
f"%(lang)s.{cpp_language_to_requirements.get(args.standard, 'CPlusPlus')}"
)
elif language in ["objc", "objc++"]:
language_restrict = "%(lang)s.ObjC"

write_header(
module_path, module, namespace, check_name, check_name_camel, description
module_path,
module,
namespace,
check_name,
check_name_camel,
description,
language_restrict,
)
write_implementation(module_path, module, namespace, check_name_camel)
adapt_module(module_path, module, check_name, check_name_camel)
add_release_notes(module_path, module, check_name, description)
test_extension = language_to_extension.get(args.language)
write_test(module_path, module, check_name, test_extension)
test_extension = language_to_extension.get(language)
write_test(module_path, module, check_name, test_extension, args.standard)
write_docs(module_path, module, check_name)
update_checks_list(clang_tidy_path)
print("Done. Now it's your turn!")
Expand Down
50 changes: 35 additions & 15 deletions clang-tools-extra/test/clang-tidy/check_clang_tidy.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,11 @@ def run_clang_tidy(self):
self.temp_file_name,
]
+ [
"-fix"
if self.export_fixes is None
else "--export-fixes=" + self.export_fixes
(
"-fix"
if self.export_fixes is None
else "--export-fixes=" + self.export_fixes
)
]
+ [
"--checks=-*," + self.check_name,
Expand Down Expand Up @@ -299,19 +301,37 @@ def run(self):
self.check_notes(clang_tidy_output)


CPP_STANDARDS = [
"c++98",
"c++11",
("c++14", "c++1y"),
("c++17", "c++1z"),
("c++20", "c++2a"),
("c++23", "c++2b"),
("c++26", "c++2c"),
]
C_STANDARDS = ["c99", ("c11", "c1x"), "c17", ("c23", "c2x"), "c2y"]


def expand_std(std):
if std == "c++98-or-later":
return ["c++98", "c++11", "c++14", "c++17", "c++20", "c++23", "c++2c"]
if std == "c++11-or-later":
return ["c++11", "c++14", "c++17", "c++20", "c++23", "c++2c"]
if std == "c++14-or-later":
return ["c++14", "c++17", "c++20", "c++23", "c++2c"]
if std == "c++17-or-later":
return ["c++17", "c++20", "c++23", "c++2c"]
if std == "c++20-or-later":
return ["c++20", "c++23", "c++2c"]
if std == "c++23-or-later":
return ["c++23", "c++2c"]
split_std, or_later, _ = std.partition("-or-later")

if not or_later:
return [split_std]

for standard_list in (CPP_STANDARDS, C_STANDARDS):
item = next(
(
i
for i, v in enumerate(standard_list)
if (split_std in v if isinstance(v, (list, tuple)) else split_std == v)
),
None,
)
if item is not None:
return [split_std] + [
x if isinstance(x, str) else x[0] for x in standard_list[item + 1 :]
]
return [std]


Expand Down
6 changes: 5 additions & 1 deletion clang/cmake/caches/Release.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@ endfunction()
# cache file to CMake via -C. e.g.
#
# cmake -D LLVM_RELEASE_ENABLE_PGO=ON -C Release.cmake
set (DEFAULT_RUNTIMES "compiler-rt;libcxx")
if (NOT WIN32)
list(APPEND DEFAULT_RUNTIMES "libcxxabi" "libunwind")
endif()
set(LLVM_RELEASE_ENABLE_LTO THIN CACHE STRING "")
set(LLVM_RELEASE_ENABLE_PGO ON CACHE BOOL "")
set(LLVM_RELEASE_ENABLE_RUNTIMES "compiler-rt;libcxx;libcxxabi;libunwind" CACHE STRING "")
set(LLVM_RELEASE_ENABLE_RUNTIMES ${DEFAULT_RUNTIMES} CACHE STRING "")
set(LLVM_RELEASE_ENABLE_PROJECTS "clang;lld;lldb;clang-tools-extra;bolt;polly;mlir;flang" CACHE STRING "")
# Note we don't need to add install here, since it is one of the pre-defined
# steps.
Expand Down
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ Attribute Changes in Clang

- Introduced a new format attribute ``__attribute__((format(syslog, 1, 2)))`` from OpenBSD.

- The ``hybrid_patchable`` attribute is now supported on ARM64EC targets. It can be used to specify
that a function requires an additional x86-64 thunk, which may be patched at runtime.

Improvements to Clang's diagnostics
-----------------------------------

Expand All @@ -133,6 +136,8 @@ Improvements to Clang's diagnostics

- Clang now diagnoses undefined behavior in constant expressions more consistently. This includes invalid shifts, and signed overflow in arithmetic.

- -Wdangling-assignment-gsl is enabled by default.

Improvements to Clang's time-trace
----------------------------------

Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,9 @@ def TargetELF : TargetSpec {
def TargetELFOrMachO : TargetSpec {
let ObjectFormats = ["ELF", "MachO"];
}
def TargetWindowsArm64EC : TargetSpec {
let CustomCode = [{ Target.getTriple().isWindowsArm64EC() }];
}

def TargetSupportsInitPriority : TargetSpec {
let CustomCode = [{ !Target.getTriple().isOSzOS() }];
Expand Down Expand Up @@ -4027,6 +4030,12 @@ def SelectAny : InheritableAttr {
let SimpleHandler = 1;
}

def HybridPatchable : InheritableAttr, TargetSpecificAttr<TargetWindowsArm64EC> {
let Spellings = [Declspec<"hybrid_patchable">, Clang<"hybrid_patchable">];
let Subjects = SubjectList<[Function]>;
let Documentation = [HybridPatchableDocs];
}

def Thread : Attr {
let Spellings = [Declspec<"thread">];
let LangOpts = [MicrosoftExt];
Expand Down
10 changes: 10 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -5985,6 +5985,16 @@ For more information see
or `msvc documentation <https://docs.microsoft.com/pl-pl/cpp/cpp/selectany>`_.
}]; }

def HybridPatchableDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
The ``hybrid_patchable`` attribute declares an ARM64EC function with an additional
x86-64 thunk, which may be patched at runtime.

For more information see
`ARM64EC ABI documentation <https://learn.microsoft.com/en-us/windows/arm/arm64ec-abi>`_.
}]; }

def WebAssemblyExportNameDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Expand Down
5 changes: 4 additions & 1 deletion clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3681,6 +3681,9 @@ def err_attribute_weak_static : Error<
"weak declaration cannot have internal linkage">;
def err_attribute_selectany_non_extern_data : Error<
"'selectany' can only be applied to data items with external linkage">;
def warn_attribute_hybrid_patchable_non_extern : Warning<
"'hybrid_patchable' is ignored on functions without external linkage">,
InGroup<IgnoredAttributes>;
def err_declspec_thread_on_thread_variable : Error<
"'__declspec(thread)' applied to variable that already has a "
"thread-local storage specifier">;
Expand Down Expand Up @@ -10131,7 +10134,7 @@ def warn_dangling_lifetime_pointer : Warning<
InGroup<DanglingGsl>;
def warn_dangling_lifetime_pointer_assignment : Warning<"object backing the "
"pointer %0 will be destroyed at the end of the full-expression">,
InGroup<DanglingAssignmentGsl>, DefaultIgnore;
InGroup<DanglingAssignmentGsl>;
def warn_new_dangling_initializer_list : Warning<
"array backing "
"%select{initializer list subobject of the allocated object|"
Expand Down
2 changes: 0 additions & 2 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5986,8 +5986,6 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
getTarget().getTriple().isAMDGCN() ||
(getTarget().getTriple().isSPIRV() &&
getTarget().getTriple().getVendor() == Triple::VendorType::AMD)) {
if (getLangOpts().OpenMPIsTargetDevice)
return EmitOpenMPDevicePrintfCallExpr(E);
if (getTarget().getTriple().isNVPTX())
return EmitNVPTXDevicePrintfCallExpr(E);
if ((getTarget().getTriple().isAMDGCN() ||
Expand Down
29 changes: 0 additions & 29 deletions clang/lib/CodeGen/CGGPUBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,6 @@ llvm::Function *GetVprintfDeclaration(llvm::Module &M) {
VprintfFuncType, llvm::GlobalVariable::ExternalLinkage, "vprintf", &M);
}

llvm::Function *GetOpenMPVprintfDeclaration(CodeGenModule &CGM) {
const char *Name = "__llvm_omp_vprintf";
llvm::Module &M = CGM.getModule();
llvm::Type *ArgTypes[] = {llvm::PointerType::getUnqual(M.getContext()),
llvm::PointerType::getUnqual(M.getContext()),
llvm::Type::getInt32Ty(M.getContext())};
llvm::FunctionType *VprintfFuncType = llvm::FunctionType::get(
llvm::Type::getInt32Ty(M.getContext()), ArgTypes, false);

if (auto *F = M.getFunction(Name)) {
if (F->getFunctionType() != VprintfFuncType) {
CGM.Error(SourceLocation(),
"Invalid type declaration for __llvm_omp_vprintf");
return nullptr;
}
return F;
}

return llvm::Function::Create(
VprintfFuncType, llvm::GlobalVariable::ExternalLinkage, Name, &M);
}

// Transforms a call to printf into a call to the NVPTX vprintf syscall (which
// isn't particularly special; it's invoked just like a regular function).
// vprintf takes two args: A format string, and a pointer to a buffer containing
Expand Down Expand Up @@ -213,10 +191,3 @@ RValue CodeGenFunction::EmitAMDGPUDevicePrintfCallExpr(const CallExpr *E) {
Builder.SetInsertPoint(IRB.GetInsertBlock(), IRB.GetInsertPoint());
return RValue::get(Printf);
}

RValue CodeGenFunction::EmitOpenMPDevicePrintfCallExpr(const CallExpr *E) {
assert(getTarget().getTriple().isNVPTX() ||
getTarget().getTriple().isAMDGCN());
return EmitDevicePrintfCallExpr(E, this, GetOpenMPVprintfDeclaration(CGM),
true);
}
3 changes: 3 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -991,6 +991,9 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
if (D && D->hasAttr<NoProfileFunctionAttr>())
Fn->addFnAttr(llvm::Attribute::NoProfile);

if (D && D->hasAttr<HybridPatchableAttr>())
Fn->addFnAttr(llvm::Attribute::HybridPatchable);

if (D) {
// Function attributes take precedence over command line flags.
if (auto *A = D->getAttr<FunctionReturnThunksAttr>()) {
Expand Down
1 change: 0 additions & 1 deletion clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -4536,7 +4536,6 @@ class CodeGenFunction : public CodeGenTypeCache {

RValue EmitNVPTXDevicePrintfCallExpr(const CallExpr *E);
RValue EmitAMDGPUDevicePrintfCallExpr(const CallExpr *E);
RValue EmitOpenMPDevicePrintfCallExpr(const CallExpr *E);

RValue EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
const CallExpr *E, ReturnValueSlot ReturnValue);
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6890,6 +6890,11 @@ static void checkAttributesAfterMerging(Sema &S, NamedDecl &ND) {
}
}

if (HybridPatchableAttr *Attr = ND.getAttr<HybridPatchableAttr>()) {
if (!ND.isExternallyVisible())
S.Diag(Attr->getLocation(),
diag::warn_attribute_hybrid_patchable_non_extern);
}
if (const InheritableAttr *Attr = getDLLAttr(&ND)) {
auto *VD = dyn_cast<VarDecl>(&ND);
bool IsAnonymousNS = false;
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6871,6 +6871,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_MSConstexpr:
handleMSConstexprAttr(S, D, AL);
break;
case ParsedAttr::AT_HybridPatchable:
handleSimpleAttribute<HybridPatchableAttr>(S, D, AL);
break;

// HLSL attributes:
case ParsedAttr::AT_HLSLNumThreads:
Expand Down
34 changes: 34 additions & 0 deletions clang/test/CodeGen/arm64ec-hybrid-patchable.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// REQUIRES: aarch64-registered-target
// RUN: %clang_cc1 -triple arm64ec-pc-windows -fms-extensions -emit-llvm -o - %s -verify | FileCheck %s

// CHECK: ; Function Attrs: hybrid_patchable noinline nounwind optnone
// CHECK-NEXT: define dso_local i32 @func() #0 {
int __attribute__((hybrid_patchable)) func(void) { return 1; }

// CHECK: ; Function Attrs: hybrid_patchable noinline nounwind optnone
// CHECK-NEXT: define dso_local i32 @func2() #0 {
int __declspec(hybrid_patchable) func2(void) { return 2; }

// CHECK: ; Function Attrs: hybrid_patchable noinline nounwind optnone
// CHECK-NEXT: define dso_local i32 @func3() #0 {
int __declspec(hybrid_patchable) func3(void);
int func3(void) { return 3; }

// CHECK: ; Function Attrs: hybrid_patchable noinline nounwind optnone
// CHECK-NEXT: define dso_local i32 @func4() #0 {
[[clang::hybrid_patchable]] int func4(void);
int func4(void) { return 3; }

// CHECK: ; Function Attrs: hybrid_patchable noinline nounwind optnone
// CHECK-NEXT: define internal void @static_func() #0 {
// expected-warning@+1 {{'hybrid_patchable' is ignored on functions without external linkage}}
static void __declspec(hybrid_patchable) static_func(void) {}

// CHECK: ; Function Attrs: hybrid_patchable noinline nounwind optnone
// CHECK-NEXT: define linkonce_odr dso_local i32 @func5() #0 comdat {
int inline __declspec(hybrid_patchable) func5(void) { return 4; }

void caller(void) {
static_func();
func5();
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
// CHECK-NEXT: HIPManaged (SubjectMatchRule_variable)
// CHECK-NEXT: HLSLResourceClass (SubjectMatchRule_record_not_is_union)
// CHECK-NEXT: Hot (SubjectMatchRule_function)
// CHECK-NEXT: HybridPatchable (SubjectMatchRule_function)
// CHECK-NEXT: IBAction (SubjectMatchRule_objc_method_is_instance)
// CHECK-NEXT: IFunc (SubjectMatchRule_function)
// CHECK-NEXT: InitPriority (SubjectMatchRule_variable)
Expand Down
179 changes: 0 additions & 179 deletions clang/test/OpenMP/nvptx_target_printf_codegen.c

This file was deleted.

49 changes: 23 additions & 26 deletions flang/include/flang/Evaluate/tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -1243,22 +1243,30 @@ bool CheckForCoindexedObject(parser::ContextualMessages &,
const std::optional<ActualArgument> &, const std::string &procName,
const std::string &argName);

// Get the number of distinct symbols with CUDA attribute in the expression.
inline bool IsCUDADeviceSymbol(const Symbol &sym) {
if (const auto *details =
sym.GetUltimate().detailsIf<semantics::ObjectEntityDetails>()) {
if (details->cudaDataAttr() &&
*details->cudaDataAttr() != common::CUDADataAttr::Pinned) {
return true;
}
}
return false;
}

// Get the number of distinct symbols with CUDA device
// attribute in the expression.
template <typename A> inline int GetNbOfCUDADeviceSymbols(const A &expr) {
semantics::UnorderedSymbolSet symbols;
for (const Symbol &sym : CollectCudaSymbols(expr)) {
if (const auto *details =
sym.GetUltimate().detailsIf<semantics::ObjectEntityDetails>()) {
if (details->cudaDataAttr() &&
*details->cudaDataAttr() != common::CUDADataAttr::Pinned) {
symbols.insert(sym);
}
if (IsCUDADeviceSymbol(sym)) {
symbols.insert(sym);
}
}
return symbols.size();
}

// Check if any of the symbols part of the expression has a CUDA data
// Check if any of the symbols part of the expression has a CUDA device
// attribute.
template <typename A> inline bool HasCUDADeviceAttrs(const A &expr) {
return GetNbOfCUDADeviceSymbols(expr) > 0;
Expand All @@ -1270,26 +1278,15 @@ inline bool HasCUDAImplicitTransfer(const Expr<SomeType> &expr) {
unsigned hostSymbols{0};
unsigned deviceSymbols{0};
for (const Symbol &sym : CollectCudaSymbols(expr)) {
if (const auto *details =
sym.GetUltimate().detailsIf<semantics::ObjectEntityDetails>()) {
if (details->cudaDataAttr() &&
*details->cudaDataAttr() != common::CUDADataAttr::Pinned) {
++deviceSymbols;
} else {
if (sym.owner().IsDerivedType()) {
if (const auto *details =
sym.owner()
.GetSymbol()
->GetUltimate()
.detailsIf<semantics::ObjectEntityDetails>()) {
if (details->cudaDataAttr() &&
*details->cudaDataAttr() != common::CUDADataAttr::Pinned) {
++deviceSymbols;
}
}
if (IsCUDADeviceSymbol(sym)) {
++deviceSymbols;
} else {
if (sym.owner().IsDerivedType()) {
if (IsCUDADeviceSymbol(sym.owner().GetSymbol()->GetUltimate())) {
++deviceSymbols;
}
++hostSymbols;
}
++hostSymbols;
}
}
return hostSymbols > 0 && deviceSymbols > 0;
Expand Down
5 changes: 5 additions & 0 deletions flang/lib/Optimizer/Builder/FIRBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ mlir::Value fir::FirOpBuilder::allocateLocal(

/// Get the block for adding Allocas.
mlir::Block *fir::FirOpBuilder::getAllocaBlock() {
if (auto accComputeRegionIface =
getRegion().getParentOfType<mlir::acc::ComputeRegionOpInterface>()) {
return accComputeRegionIface.getAllocaBlock();
}

if (auto ompOutlineableIface =
getRegion()
.getParentOfType<mlir::omp::OutlineableOpenMPOpInterface>()) {
Expand Down
2 changes: 1 addition & 1 deletion flang/test/Lower/OpenACC/acc-loop.f90
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,10 @@ subroutine sub1(i, j, k)
end subroutine

! CHECK: func.func @_QPsub1
! CHECK: acc.parallel
! CHECK: %[[DC_K:.*]] = fir.alloca i32 {bindc_name = "k"}
! CHECK: %[[DC_J:.*]] = fir.alloca i32 {bindc_name = "j"}
! CHECK: %[[DC_I:.*]] = fir.alloca i32 {bindc_name = "i"}
! CHECK: acc.parallel
! CHECK: %[[P_I:.*]] = acc.private varPtr(%[[DC_I]] : !fir.ref<i32>) -> !fir.ref<i32> {implicit = true, name = ""}
! CHECK: %[[P_J:.*]] = acc.private varPtr(%[[DC_J]] : !fir.ref<i32>) -> !fir.ref<i32> {implicit = true, name = ""}
! CHECK: %[[P_K:.*]] = acc.private varPtr(%[[DC_K]] : !fir.ref<i32>) -> !fir.ref<i32> {implicit = true, name = ""}
Expand Down
2 changes: 2 additions & 0 deletions libc/benchmarks/gpu/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ function(add_benchmark benchmark_name)
libc.src.stdio.printf
${BENCHMARK_DEPENDS}
${BENCHMARK_UNPARSED_ARGUMENTS}
COMPILE_OPTIONS
-flto
)
get_fq_target_name(${benchmark_name} fq_target_name)
set(fq_build_target_name ${fq_target_name}.__build__)
Expand Down
1 change: 1 addition & 0 deletions libc/benchmarks/gpu/LibcGpuBenchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ BenchmarkResult benchmark(const BenchmarkOptions &options,
if (samples >= options.max_samples || iterations >= options.max_iterations)
break;
if (total_time >= options.min_duration && samples >= options.min_samples &&
total_iterations >= options.min_iterations &&
change_ratio < options.epsilon)
break;

Expand Down
5 changes: 3 additions & 2 deletions libc/benchmarks/gpu/LibcGpuBenchmark.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ namespace benchmarks {

struct BenchmarkOptions {
uint32_t initial_iterations = 1;
uint32_t min_iterations = 50;
uint32_t max_iterations = 10000000;
uint32_t min_samples = 4;
uint32_t max_samples = 1000;
int64_t min_duration = 0; // in nanoseconds (ns)
int64_t min_duration = 500 * 1000; // 500 * 1000 nanoseconds = 500 us
int64_t max_duration = 1000 * 1000 * 1000; // 1e9 nanoseconds = 1 second
double epsilon = 0.01;
double epsilon = 0.0001;
double scaling_factor = 1.4;
};

Expand Down
16 changes: 3 additions & 13 deletions libc/cmake/modules/LLVMLibCLibraryRules.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,9 @@ function(add_bitcode_entrypoint_library target_name base_target_name)
list(APPEND objects ${object})
endforeach()

set(output ${CMAKE_CURRENT_BINARY_DIR}/${target_name}.bc)
add_custom_command(
OUTPUT ${output}
COMMAND ${LIBC_LLVM_LINK} ${objects} -o ${output}
DEPENDS ${all_deps} ${base_target_name}
COMMENT "Linking LLVM-IR bitcode for ${base_target_name}"
COMMAND_EXPAND_LISTS
)
add_custom_target(${target_name} DEPENDS ${output} ${all_deps})
set_target_properties(${target_name} PROPERTIES TARGET_OBJECT ${output})
if(TARGET llvm-link)
add_dependencies(${target_name} llvm-link)
endif()
add_executable(${target_name} ${objects})
target_link_options(${target_name} PRIVATE
"-r" "-nostdlib" "-flto" "-Wl,--lto-emit-llvm" "-march= ")
endfunction(add_bitcode_entrypoint_library)

# A rule to build a library from a collection of entrypoint objects.
Expand Down
4 changes: 4 additions & 0 deletions libc/cmake/modules/LLVMLibCTestRules.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@ function(add_integration_test test_name)
target_link_options(${fq_build_target_name} PRIVATE
${LIBC_COMPILE_OPTIONS_DEFAULT} -Wno-multi-gpu
"-Wl,--suppress-stack-size-warning"
"-Wl,-mllvm,-nvptx-lower-global-ctor-dtor=1"
"-Wl,-mllvm,-nvptx-emit-init-fini-kernel"
-march=${LIBC_GPU_TARGET_ARCHITECTURE} -nostdlib -static
"--cuda-path=${LIBC_CUDA_ROOT}")
elseif(LIBC_CC_SUPPORTS_NOSTDLIBPP)
Expand Down Expand Up @@ -657,6 +659,8 @@ function(add_libc_hermetic test_name)
target_link_options(${fq_build_target_name} PRIVATE
${LIBC_COMPILE_OPTIONS_DEFAULT} -Wno-multi-gpu
"-Wl,--suppress-stack-size-warning"
"-Wl,-mllvm,-nvptx-lower-global-ctor-dtor=1"
"-Wl,-mllvm,-nvptx-emit-init-fini-kernel"
-march=${LIBC_GPU_TARGET_ARCHITECTURE} -nostdlib -static
"--cuda-path=${LIBC_CUDA_ROOT}")
elseif(LIBC_CC_SUPPORTS_NOSTDLIBPP)
Expand Down
30 changes: 2 additions & 28 deletions libc/cmake/modules/prepare_libc_gpu_build.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,10 @@ if(LIBC_TARGET_TRIPLE)
set(CMAKE_REQUIRED_FLAGS "--target=${LIBC_TARGET_TRIPLE}")
endif()
if(LIBC_TARGET_ARCHITECTURE_IS_AMDGPU)
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -nogpulib")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -nogpulib -nostdlib")
elseif(LIBC_TARGET_ARCHITECTURE_IS_NVPTX)
set(CMAKE_REQUIRED_FLAGS
"${CMAKE_REQUIRED_FLAGS} -flto -c -Wno-unused-command-line-argument")
endif()

# Identify the program used to package multiple images into a single binary.
get_filename_component(compiler_path ${CMAKE_CXX_COMPILER} DIRECTORY)
if(TARGET clang-offload-packager)
get_target_property(LIBC_CLANG_OFFLOAD_PACKAGER clang-offload-packager LOCATION)
else()
find_program(LIBC_CLANG_OFFLOAD_PACKAGER
NAMES clang-offload-packager NO_DEFAULT_PATH
PATHS ${LLVM_BINARY_DIR}/bin ${compiler_path})
if(NOT LIBC_CLANG_OFFLOAD_PACKAGER)
message(FATAL_ERROR "Cannot find the 'clang-offload-packager' for the GPU "
"build")
endif()
endif()

# Identify llvm-link program so we can merge the output IR into a single blob.
if(TARGET llvm-link)
get_target_property(LIBC_LLVM_LINK llvm-link LOCATION)
else()
find_program(LIBC_LLVM_LINK
NAMES llvm-link NO_DEFAULT_PATH
PATHS ${LLVM_BINARY_DIR}/bin ${compiler_path})
if(NOT LIBC_LLVM_LINK)
message(FATAL_ERROR "Cannot find 'llvm-link' for the GPU build")
endif()
"${CMAKE_REQUIRED_FLAGS} -flto -c -Wno-unused-command-line-argument -nostdlib")
endif()

# Optionally set up a job pool to limit the number of GPU tests run in parallel.
Expand Down
1 change: 0 additions & 1 deletion libc/config/gpu/entrypoints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ set(TARGET_LIBC_ENTRYPOINTS

# gpu/rpc.h entrypoints
libc.src.gpu.rpc_host_call
libc.src.gpu.rpc_fprintf
)

set(TARGET_LIBM_ENTRYPOINTS
Expand Down
2 changes: 1 addition & 1 deletion libc/docs/dev/header_generation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Instructions
------------

Required Versions:
- Python Version: 3.6
- Python Version: 3.8
- PyYAML Version: 5.1

1. Keep full-build mode on when building, otherwise headers will not be
Expand Down
27 changes: 9 additions & 18 deletions libc/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ foreach(archive IN ZIP_LISTS
PROPERTIES
OUTPUT_NAME ${archive_1}.bc
)
list(APPEND added_gpu_bitcode_targets ${archive_1}bitcode)
list(APPEND added_bitcode_targets ${archive_1}bitcode)
endif()
endforeach()

Expand All @@ -61,24 +61,13 @@ install(
COMPONENT libc
)

if(LIBC_TARGET_OS_IS_GPU)
set(gpu_install_dir lib${LLVM_LIBDIR_SUFFIX})
if(LLVM_ENABLE_PER_TARGET_RUNTIME_DIR)
set(gpu_install_dir lib${LLVM_LIBDIR_SUFFIX}/${LLVM_HOST_TRIPLE})
endif()
install(
TARGETS ${added_gpu_archive_targets}
ARCHIVE DESTINATION ${gpu_install_dir}
COMPONENT libc
foreach(file ${added_bitcode_targets})
install(FILES $<TARGET_FILE:${file}>
DESTINATION ${LIBC_INSTALL_LIBRARY_DIR}
RENAME $<TARGET_PROPERTY:${file},OUTPUT_NAME>
COMPONENT libc
)
foreach(file ${added_gpu_bitcode_targets})
install(FILES $<TARGET_PROPERTY:${file},TARGET_OBJECT>
DESTINATION ${LIBC_INSTALL_LIBRARY_DIR}
RENAME $<TARGET_PROPERTY:${file},OUTPUT_NAME>
COMPONENT libc
)
endforeach()
endif()
endforeach()

if(NOT LIBC_TARGET_OS_IS_BAREMETAL)
# For now we will disable libc-startup installation for baremetal. The
Expand All @@ -93,13 +82,15 @@ endif()

add_custom_target(install-libc
DEPENDS ${added_archive_targets}
${added_bitcode_targets}
${startup_target}
${header_install_target}
COMMAND "${CMAKE_COMMAND}"
-DCMAKE_INSTALL_COMPONENT=libc
-P "${CMAKE_BINARY_DIR}/cmake_install.cmake")
add_custom_target(install-libc-stripped
DEPENDS ${added_archive_targets}
${added_bitcode_targets}
${startup_target}
${header_install_target}
COMMAND "${CMAKE_COMMAND}"
Expand Down
8 changes: 0 additions & 8 deletions libc/spec/gpu_ext.td
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ def GPUExtensions : StandardSpec<"GPUExtensions"> {
RetValSpec<VoidType>,
[ArgSpec<VoidPtr>, ArgSpec<VoidPtr>, ArgSpec<SizeTType>]
>,
FunctionSpec<
"rpc_fprintf",
RetValSpec<IntType>,
[ArgSpec<FILERestrictedPtr>,
ArgSpec<ConstCharRestrictedPtr>,
ArgSpec<VoidPtr>,
ArgSpec<SizeTType>]
>,
]
>;
let Headers = [
Expand Down
12 changes: 0 additions & 12 deletions libc/src/gpu/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,3 @@ add_entrypoint_object(
libc.src.__support.RPC.rpc_client
libc.src.__support.GPU.utils
)

add_entrypoint_object(
rpc_fprintf
SRCS
rpc_fprintf.cpp
HDRS
rpc_fprintf.h
DEPENDS
libc.src.stdio.gpu.gpu_file
libc.src.__support.RPC.rpc_client
libc.src.__support.GPU.utils
)
75 changes: 0 additions & 75 deletions libc/src/gpu/rpc_fprintf.cpp

This file was deleted.

23 changes: 0 additions & 23 deletions libc/src/gpu/rpc_fprintf.h

This file was deleted.

15 changes: 14 additions & 1 deletion libcxx/include/__atomic/atomic_ref.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ _LIBCPP_BEGIN_NAMESPACE_STD

#if _LIBCPP_STD_VER >= 20

// These types are required to make __atomic_is_always_lock_free work across GCC and Clang.
// The purpose of this trick is to make sure that we provide an object with the correct alignment
// to __atomic_is_always_lock_free, since that answer depends on the alignment.
template <size_t _Alignment>
struct __alignment_checker_type {
alignas(_Alignment) char __data;
};

template <size_t _Alignment>
struct __get_aligner_instance {
static constexpr __alignment_checker_type<_Alignment> __instance{};
};

template <class _Tp>
struct __atomic_ref_base {
protected:
Expand Down Expand Up @@ -105,7 +118,7 @@ struct __atomic_ref_base {
// that the pointer is going to be aligned properly at runtime because that is a (checked) precondition
// of atomic_ref's constructor.
static constexpr bool is_always_lock_free =
__atomic_always_lock_free(sizeof(_Tp), reinterpret_cast<void*>(-required_alignment));
__atomic_always_lock_free(sizeof(_Tp), &__get_aligner_instance<required_alignment>::__instance);

_LIBCPP_HIDE_FROM_ABI bool is_lock_free() const noexcept { return __atomic_is_lock_free(sizeof(_Tp), __ptr_); }

Expand Down
165 changes: 165 additions & 0 deletions libcxx/test/std/atomics/atomics.lockfree/is_always_lock_free.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// UNSUPPORTED: c++03, c++11, c++14

// <atomic>
//
// template <class T>
// class atomic;
//
// static constexpr bool is_always_lock_free;

#include <atomic>
#include <cassert>
#include <cstddef>

#include "test_macros.h"
#include "atomic_helpers.h"

template <typename T>
void check_always_lock_free(std::atomic<T> const& a) {
using InfoT = LockFreeStatusInfo<T>;

constexpr std::same_as<const bool> decltype(auto) is_always_lock_free = std::atomic<T>::is_always_lock_free;

// If we know the status of T for sure, validate the exact result of the function.
if constexpr (InfoT::status_known) {
constexpr LockFreeStatus known_status = InfoT::value;
if constexpr (known_status == LockFreeStatus::always) {
static_assert(is_always_lock_free, "is_always_lock_free is inconsistent with known lock-free status");
assert(a.is_lock_free() && "is_lock_free() is inconsistent with known lock-free status");
} else if constexpr (known_status == LockFreeStatus::never) {
static_assert(!is_always_lock_free, "is_always_lock_free is inconsistent with known lock-free status");
assert(!a.is_lock_free() && "is_lock_free() is inconsistent with known lock-free status");
} else {
assert(a.is_lock_free() || !a.is_lock_free()); // This is kinda dumb, but we might as well call the function once.
}
}

// In all cases, also sanity-check it based on the implication always-lock-free => lock-free.
if (is_always_lock_free) {
std::same_as<bool> decltype(auto) is_lock_free = a.is_lock_free();
assert(is_lock_free);
}
ASSERT_NOEXCEPT(a.is_lock_free());
}

#define CHECK_ALWAYS_LOCK_FREE(T) \
do { \
typedef T type; \
type obj{}; \
std::atomic<type> a(obj); \
check_always_lock_free(a); \
} while (0)

void test() {
char c = 'x';
check_always_lock_free(std::atomic<char>(c));

int i = 0;
check_always_lock_free(std::atomic<int>(i));

float f = 0.f;
check_always_lock_free(std::atomic<float>(f));

int* p = &i;
check_always_lock_free(std::atomic<int*>(p));

CHECK_ALWAYS_LOCK_FREE(bool);
CHECK_ALWAYS_LOCK_FREE(char);
CHECK_ALWAYS_LOCK_FREE(signed char);
CHECK_ALWAYS_LOCK_FREE(unsigned char);
#if TEST_STD_VER > 17 && defined(__cpp_char8_t)
CHECK_ALWAYS_LOCK_FREE(char8_t);
#endif
CHECK_ALWAYS_LOCK_FREE(char16_t);
CHECK_ALWAYS_LOCK_FREE(char32_t);
CHECK_ALWAYS_LOCK_FREE(wchar_t);
CHECK_ALWAYS_LOCK_FREE(short);
CHECK_ALWAYS_LOCK_FREE(unsigned short);
CHECK_ALWAYS_LOCK_FREE(int);
CHECK_ALWAYS_LOCK_FREE(unsigned int);
CHECK_ALWAYS_LOCK_FREE(long);
CHECK_ALWAYS_LOCK_FREE(unsigned long);
CHECK_ALWAYS_LOCK_FREE(long long);
CHECK_ALWAYS_LOCK_FREE(unsigned long long);
CHECK_ALWAYS_LOCK_FREE(std::nullptr_t);
CHECK_ALWAYS_LOCK_FREE(void*);
CHECK_ALWAYS_LOCK_FREE(float);
CHECK_ALWAYS_LOCK_FREE(double);
CHECK_ALWAYS_LOCK_FREE(long double);
#if __has_attribute(vector_size) && defined(_LIBCPP_VERSION)
CHECK_ALWAYS_LOCK_FREE(int __attribute__((vector_size(1 * sizeof(int)))));
CHECK_ALWAYS_LOCK_FREE(int __attribute__((vector_size(2 * sizeof(int)))));
CHECK_ALWAYS_LOCK_FREE(int __attribute__((vector_size(4 * sizeof(int)))));
CHECK_ALWAYS_LOCK_FREE(int __attribute__((vector_size(16 * sizeof(int)))));
CHECK_ALWAYS_LOCK_FREE(int __attribute__((vector_size(32 * sizeof(int)))));
CHECK_ALWAYS_LOCK_FREE(float __attribute__((vector_size(1 * sizeof(float)))));
CHECK_ALWAYS_LOCK_FREE(float __attribute__((vector_size(2 * sizeof(float)))));
CHECK_ALWAYS_LOCK_FREE(float __attribute__((vector_size(4 * sizeof(float)))));
CHECK_ALWAYS_LOCK_FREE(float __attribute__((vector_size(16 * sizeof(float)))));
CHECK_ALWAYS_LOCK_FREE(float __attribute__((vector_size(32 * sizeof(float)))));
CHECK_ALWAYS_LOCK_FREE(double __attribute__((vector_size(1 * sizeof(double)))));
CHECK_ALWAYS_LOCK_FREE(double __attribute__((vector_size(2 * sizeof(double)))));
CHECK_ALWAYS_LOCK_FREE(double __attribute__((vector_size(4 * sizeof(double)))));
CHECK_ALWAYS_LOCK_FREE(double __attribute__((vector_size(16 * sizeof(double)))));
CHECK_ALWAYS_LOCK_FREE(double __attribute__((vector_size(32 * sizeof(double)))));
#endif // __has_attribute(vector_size) && defined(_LIBCPP_VERSION)
CHECK_ALWAYS_LOCK_FREE(struct Empty{});
CHECK_ALWAYS_LOCK_FREE(struct OneInt { int i; });
CHECK_ALWAYS_LOCK_FREE(struct IntArr2 { int i[2]; });
CHECK_ALWAYS_LOCK_FREE(struct FloatArr3 { float i[3]; });
CHECK_ALWAYS_LOCK_FREE(struct LLIArr2 { long long int i[2]; });
CHECK_ALWAYS_LOCK_FREE(struct LLIArr4 { long long int i[4]; });
CHECK_ALWAYS_LOCK_FREE(struct LLIArr8 { long long int i[8]; });
CHECK_ALWAYS_LOCK_FREE(struct LLIArr16 { long long int i[16]; });
CHECK_ALWAYS_LOCK_FREE(struct Padding {
char c; /* padding */
long long int i;
});
CHECK_ALWAYS_LOCK_FREE(union IntFloat {
int i;
float f;
});
CHECK_ALWAYS_LOCK_FREE(enum class CharEnumClass : char{foo});

// C macro and static constexpr must be consistent.
enum class CharEnumClass : char { foo };
static_assert(std::atomic<bool>::is_always_lock_free == (2 == ATOMIC_BOOL_LOCK_FREE), "");
static_assert(std::atomic<char>::is_always_lock_free == (2 == ATOMIC_CHAR_LOCK_FREE), "");
static_assert(std::atomic<CharEnumClass>::is_always_lock_free == (2 == ATOMIC_CHAR_LOCK_FREE), "");
static_assert(std::atomic<signed char>::is_always_lock_free == (2 == ATOMIC_CHAR_LOCK_FREE), "");
static_assert(std::atomic<unsigned char>::is_always_lock_free == (2 == ATOMIC_CHAR_LOCK_FREE), "");
#if TEST_STD_VER > 17 && defined(__cpp_char8_t)
static_assert(std::atomic<char8_t>::is_always_lock_free == (2 == ATOMIC_CHAR8_T_LOCK_FREE), "");
#endif
static_assert(std::atomic<char16_t>::is_always_lock_free == (2 == ATOMIC_CHAR16_T_LOCK_FREE), "");
static_assert(std::atomic<char32_t>::is_always_lock_free == (2 == ATOMIC_CHAR32_T_LOCK_FREE), "");
static_assert(std::atomic<wchar_t>::is_always_lock_free == (2 == ATOMIC_WCHAR_T_LOCK_FREE), "");
static_assert(std::atomic<short>::is_always_lock_free == (2 == ATOMIC_SHORT_LOCK_FREE), "");
static_assert(std::atomic<unsigned short>::is_always_lock_free == (2 == ATOMIC_SHORT_LOCK_FREE), "");
static_assert(std::atomic<int>::is_always_lock_free == (2 == ATOMIC_INT_LOCK_FREE), "");
static_assert(std::atomic<unsigned int>::is_always_lock_free == (2 == ATOMIC_INT_LOCK_FREE), "");
static_assert(std::atomic<long>::is_always_lock_free == (2 == ATOMIC_LONG_LOCK_FREE), "");
static_assert(std::atomic<unsigned long>::is_always_lock_free == (2 == ATOMIC_LONG_LOCK_FREE), "");
static_assert(std::atomic<long long>::is_always_lock_free == (2 == ATOMIC_LLONG_LOCK_FREE), "");
static_assert(std::atomic<unsigned long long>::is_always_lock_free == (2 == ATOMIC_LLONG_LOCK_FREE), "");
static_assert(std::atomic<void*>::is_always_lock_free == (2 == ATOMIC_POINTER_LOCK_FREE), "");
static_assert(std::atomic<std::nullptr_t>::is_always_lock_free == (2 == ATOMIC_POINTER_LOCK_FREE), "");

#if TEST_STD_VER >= 20
static_assert(std::atomic_signed_lock_free::is_always_lock_free, "");
static_assert(std::atomic_unsigned_lock_free::is_always_lock_free, "");
#endif
}

int main(int, char**) {
test();
return 0;
}
120 changes: 0 additions & 120 deletions libcxx/test/std/atomics/atomics.lockfree/isalwayslockfree.pass.cpp

This file was deleted.

34 changes: 30 additions & 4 deletions libcxx/test/std/atomics/atomics.ref/is_always_lock_free.pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17

// <atomic>

//
// template <class T>
// class atomic_ref;
//
// static constexpr bool is_always_lock_free;
// bool is_lock_free() const noexcept;

Expand All @@ -18,10 +21,29 @@
#include <concepts>

#include "test_macros.h"
#include "atomic_helpers.h"

template <typename T>
void check_always_lock_free(std::atomic_ref<T> const a) {
std::same_as<const bool> decltype(auto) is_always_lock_free = std::atomic_ref<T>::is_always_lock_free;
void check_always_lock_free(std::atomic_ref<T> const& a) {
using InfoT = LockFreeStatusInfo<T>;

constexpr std::same_as<const bool> decltype(auto) is_always_lock_free = std::atomic_ref<T>::is_always_lock_free;

// If we know the status of T for sure, validate the exact result of the function.
if constexpr (InfoT::status_known) {
constexpr LockFreeStatus known_status = InfoT::value;
if constexpr (known_status == LockFreeStatus::always) {
static_assert(is_always_lock_free, "is_always_lock_free is inconsistent with known lock-free status");
assert(a.is_lock_free() && "is_lock_free() is inconsistent with known lock-free status");
} else if constexpr (known_status == LockFreeStatus::never) {
static_assert(!is_always_lock_free, "is_always_lock_free is inconsistent with known lock-free status");
assert(!a.is_lock_free() && "is_lock_free() is inconsistent with known lock-free status");
} else {
assert(a.is_lock_free() || !a.is_lock_free()); // This is kinda dumb, but we might as well call the function once.
}
}

// In all cases, also sanity-check it based on the implication always-lock-free => lock-free.
if (is_always_lock_free) {
std::same_as<bool> decltype(auto) is_lock_free = a.is_lock_free();
assert(is_lock_free);
Expand All @@ -33,10 +55,14 @@ void check_always_lock_free(std::atomic_ref<T> const a) {
do { \
typedef T type; \
type obj{}; \
check_always_lock_free(std::atomic_ref<type>(obj)); \
std::atomic_ref<type> a(obj); \
check_always_lock_free(a); \
} while (0)

void test() {
char c = 'x';
check_always_lock_free(std::atomic_ref<char>(c));

int i = 0;
check_always_lock_free(std::atomic_ref<int>(i));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@

// iter_type put(iter_type s, ios_base& iob, char_type fill, double v) const;

// With the Microsoft UCRT, printf("%a", 0.0) produces "0x0.0000000000000p+0"
// while other C runtimes produce just "0x0p+0".
// https://developercommunity.visualstudio.com/t/Printf-formatting-of-float-as-hex-prints/1660844
// XFAIL: msvc
// XFAIL: win32-broken-printf-a-precision

// XFAIL: LIBCXX-AIX-FIXME

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@

// iter_type put(iter_type s, ios_base& iob, char_type fill, long double v) const;

// With the Microsoft UCRT, printf("%a", 0.0) produces "0x0.0000000000000p+0"
// while other C runtimes produce just "0x0p+0".
// https://developercommunity.visualstudio.com/t/Printf-formatting-of-float-as-hex-prints/1660844
// XFAIL: msvc
// XFAIL: win32-broken-printf-a-precision

// XFAIL: LIBCXX-AIX-FIXME

Expand Down
103 changes: 103 additions & 0 deletions libcxx/test/support/atomic_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,112 @@

#include <cassert>
#include <cstdint>
#include <cstddef>
#include <type_traits>

#include "test_macros.h"

#if defined(TEST_COMPILER_CLANG)
# define TEST_ATOMIC_CHAR_LOCK_FREE __CLANG_ATOMIC_CHAR_LOCK_FREE
# define TEST_ATOMIC_SHORT_LOCK_FREE __CLANG_ATOMIC_SHORT_LOCK_FREE
# define TEST_ATOMIC_INT_LOCK_FREE __CLANG_ATOMIC_INT_LOCK_FREE
# define TEST_ATOMIC_LONG_LOCK_FREE __CLANG_ATOMIC_LONG_LOCK_FREE
# define TEST_ATOMIC_LLONG_LOCK_FREE __CLANG_ATOMIC_LLONG_LOCK_FREE
# define TEST_ATOMIC_POINTER_LOCK_FREE __CLANG_ATOMIC_POINTER_LOCK_FREE
#elif defined(TEST_COMPILER_GCC)
# define TEST_ATOMIC_CHAR_LOCK_FREE __GCC_ATOMIC_CHAR_LOCK_FREE
# define TEST_ATOMIC_SHORT_LOCK_FREE __GCC_ATOMIC_SHORT_LOCK_FREE
# define TEST_ATOMIC_INT_LOCK_FREE __GCC_ATOMIC_INT_LOCK_FREE
# define TEST_ATOMIC_LONG_LOCK_FREE __GCC_ATOMIC_LONG_LOCK_FREE
# define TEST_ATOMIC_LLONG_LOCK_FREE __GCC_ATOMIC_LLONG_LOCK_FREE
# define TEST_ATOMIC_POINTER_LOCK_FREE __GCC_ATOMIC_POINTER_LOCK_FREE
#elif TEST_COMPILER_MSVC
// This is lifted from STL/stl/inc/atomic on github for the purposes of
// keeping the tests compiling for MSVC's STL. It's not a perfect solution
// but at least the tests will keep running.
//
// Note MSVC's STL never produces a type that is sometimes lock free, but not always lock free.
template <class T, size_t Size = sizeof(T)>
constexpr bool msvc_is_lock_free_macro_value() {
return (Size <= 8 && (Size & Size - 1) == 0) ? 2 : 0;
}
# define TEST_ATOMIC_CHAR_LOCK_FREE ::msvc_is_lock_free_macro_value<char>()
# define TEST_ATOMIC_SHORT_LOCK_FREE ::msvc_is_lock_free_macro_value<short>()
# define TEST_ATOMIC_INT_LOCK_FREE ::msvc_is_lock_free_macro_value<int>()
# define TEST_ATOMIC_LONG_LOCK_FREE ::msvc_is_lock_free_macro_value<long>()
# define TEST_ATOMIC_LLONG_LOCK_FREE ::msvc_is_lock_free_macro_value<long long>()
# define TEST_ATOMIC_POINTER_LOCK_FREE ::msvc_is_lock_free_macro_value<void*>()
#else
# error "Unknown compiler"
#endif

#ifdef TEST_COMPILER_CLANG
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wc++11-extensions"
#endif

enum class LockFreeStatus : int { unknown = -1, never = 0, sometimes = 1, always = 2 };

// We should really be checking whether the alignment of T is greater-than-or-equal-to the alignment required
// for T to be atomic, but this is basically impossible to implement portably. Instead, we assume that any type
// aligned to at least its size is going to be atomic if there exists atomic operations for that size at all,
// which is true on most platforms. This technically reduces our test coverage in the sense that if a type has
// an alignment requirement less than its size but could still be made lockfree, LockFreeStatusInfo will report
// that we don't know whether it is lockfree or not.
#define COMPARE_TYPES(T, FundamentalT) (sizeof(T) == sizeof(FundamentalT) && TEST_ALIGNOF(T) >= sizeof(T))

template <class T>
struct LockFreeStatusInfo {
static const LockFreeStatus value = LockFreeStatus(
COMPARE_TYPES(T, char)
? TEST_ATOMIC_CHAR_LOCK_FREE
: (COMPARE_TYPES(T, short)
? TEST_ATOMIC_SHORT_LOCK_FREE
: (COMPARE_TYPES(T, int)
? TEST_ATOMIC_INT_LOCK_FREE
: (COMPARE_TYPES(T, long)
? TEST_ATOMIC_LONG_LOCK_FREE
: (COMPARE_TYPES(T, long long)
? TEST_ATOMIC_LLONG_LOCK_FREE
: (COMPARE_TYPES(T, void*) ? TEST_ATOMIC_POINTER_LOCK_FREE : -1))))));

static const bool status_known = LockFreeStatusInfo::value != LockFreeStatus::unknown;
};

#undef COMPARE_TYPES

// This doesn't work in C++03 due to issues with scoped enumerations. Just disable the test.
#if TEST_STD_VER >= 11
static_assert(LockFreeStatusInfo<char>::status_known, "");
static_assert(LockFreeStatusInfo<short>::status_known, "");
static_assert(LockFreeStatusInfo<int>::status_known, "");
static_assert(LockFreeStatusInfo<long>::status_known, "");
static_assert(LockFreeStatusInfo<void*>::status_known, "");

// long long is a bit funky: on some platforms, its alignment is 4 bytes but its size is
// 8 bytes. In that case, atomics may or may not be lockfree based on their address.
static_assert(alignof(long long) == sizeof(long long) ? LockFreeStatusInfo<long long>::status_known : true, "");

// Those should always be lock free: hardcode some expected values to make sure our tests are actually
// testing something meaningful.
static_assert(LockFreeStatusInfo<char>::value == LockFreeStatus::always, "");
static_assert(LockFreeStatusInfo<short>::value == LockFreeStatus::always, "");
static_assert(LockFreeStatusInfo<int>::value == LockFreeStatus::always, "");
#endif

// These macros are somewhat suprising to use, since they take the values 0, 1, or 2.
// To make the tests clearer, get rid of them in preference of LockFreeStatusInfo.
#undef TEST_ATOMIC_CHAR_LOCK_FREE
#undef TEST_ATOMIC_SHORT_LOCK_FREE
#undef TEST_ATOMIC_INT_LOCK_FREE
#undef TEST_ATOMIC_LONG_LOCK_FREE
#undef TEST_ATOMIC_LLONG_LOCK_FREE
#undef TEST_ATOMIC_POINTER_LOCK_FREE

#ifdef TEST_COMPILER_CLANG
# pragma clang diagnostic pop
#endif

struct UserAtomicType {
int i;

Expand Down
20 changes: 20 additions & 0 deletions libcxx/utils/libcxx/test/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,26 @@ def _mingwSupportsModules(cfg):
""",
),
),
# Check for a Windows UCRT bug (not fixed upstream yet).
# With UCRT, printf("%a", 0.0) produces "0x0.0000000000000p+0",
# while other C runtimes produce just "0x0p+0".
# https://developercommunity.visualstudio.com/t/Printf-formatting-of-float-as-hex-prints/1660844
Feature(
name="win32-broken-printf-a-precision",
when=lambda cfg: "_WIN32" in compilerMacros(cfg)
and not programSucceeds(
cfg,
"""
#include <stdio.h>
#include <string.h>
int main(int, char**) {
char buf[100];
snprintf(buf, sizeof(buf), "%a", 0.0);
return strcmp(buf, "0x0p+0");
}
""",
),
),
# Check for Glibc < 2.27, where the ru_RU.UTF-8 locale had
# mon_decimal_point == ".", which our tests don't handle.
Feature(
Expand Down
2 changes: 2 additions & 0 deletions libcxxabi/test/test_demangle.pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
// 80-bit format, and this demangling test is failing on it.
// XFAIL: LIBCXX-ANDROID-FIXME && target={{i686|x86_64}}-{{.+}}-android{{.*}}

// XFAIL: win32-broken-printf-a-precision

#include "support/timer.h"
#include <algorithm>
#include <cassert>
Expand Down
2 changes: 1 addition & 1 deletion lld/ELF/InputSection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1107,7 +1107,7 @@ void InputSectionBase::relocate(uint8_t *buf, uint8_t *bufEnd) {
auto *sec = cast<InputSection>(this);
// For a relocatable link, also call relocateNonAlloc() to rewrite applicable
// locations with tombstone values.
invokeOnRelocs(*sec, sec->relocateNonAlloc, buf);
invokeOnRelocs(*sec, sec->relocateNonAlloc<ELFT>, buf);
}

// For each function-defining prologue, find any calls to __morestack,
Expand Down
6 changes: 3 additions & 3 deletions lld/ELF/InputSection.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ template <class ELFT> struct RelsOrRelas {
{ \
const RelsOrRelas<ELFT> rs = (sec).template relsOrRelas<ELFT>(); \
if (rs.areRelocsCrel()) \
f<ELFT>(__VA_ARGS__, rs.crels); \
f(__VA_ARGS__, rs.crels); \
else if (rs.areRelocsRel()) \
f<ELFT>(__VA_ARGS__, rs.rels); \
f(__VA_ARGS__, rs.rels); \
else \
f<ELFT>(__VA_ARGS__, rs.relas); \
f(__VA_ARGS__, rs.relas); \
}

// This is the base class of all sections that lld handles. Some are sections in
Expand Down
2 changes: 1 addition & 1 deletion lld/ELF/Relocations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2424,7 +2424,7 @@ template <class ELFT> void elf::checkNoCrossRefs() {
if (!isd)
continue;
parallelForEach(isd->sections, [&](InputSection *sec) {
invokeOnRelocs(*sec, scanCrossRefs, noxref, osec, sec);
invokeOnRelocs(*sec, scanCrossRefs<ELFT>, noxref, osec, sec);
});
}
}
Expand Down
207 changes: 83 additions & 124 deletions lld/ELF/ScriptLexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,24 @@ using namespace llvm;
using namespace lld;
using namespace lld::elf;

ScriptLexer::ScriptLexer(MemoryBufferRef mb) : curBuf(mb), mbs(1, mb) {}

// Returns a whole line containing the current token.
StringRef ScriptLexer::getLine() {
StringRef s = getCurrentMB().getBuffer();
StringRef tok = tokens[pos - 1];

size_t pos = s.rfind('\n', tok.data() - s.data());
size_t pos = s.rfind('\n', prevTok.data() - s.data());
if (pos != StringRef::npos)
s = s.substr(pos + 1);
return s.substr(0, s.find_first_of("\r\n"));
}

// Returns 1-based line number of the current token.
size_t ScriptLexer::getLineNumber() {
if (pos == 0)
if (prevTok.empty())
return 1;
StringRef s = getCurrentMB().getBuffer();
StringRef tok = tokens[pos - 1];
const size_t tokOffset = tok.data() - s.data();
const size_t tokOffset = prevTok.data() - s.data();

// For the first token, or when going backwards, start from the beginning of
// the buffer. If this token is after the previous token, start from the
Expand All @@ -76,40 +76,41 @@ size_t ScriptLexer::getLineNumber() {

// Returns 0-based column number of the current token.
size_t ScriptLexer::getColumnNumber() {
StringRef tok = tokens[pos - 1];
return tok.data() - getLine().data();
return prevTok.data() - getLine().data();
}

std::string ScriptLexer::getCurrentLocation() {
std::string filename = std::string(getCurrentMB().getBufferIdentifier());
return (filename + ":" + Twine(getLineNumber())).str();
}

ScriptLexer::ScriptLexer(MemoryBufferRef mb) { tokenize(mb); }

// We don't want to record cascading errors. Keep only the first one.
void ScriptLexer::setError(const Twine &msg) {
if (errorCount())
return;

std::string s = (getCurrentLocation() + ": " + msg).str();
if (pos)
if (prevTok.size())
s += "\n>>> " + getLine().str() + "\n>>> " +
std::string(getColumnNumber(), ' ') + "^";
error(s);
}

// Split S into linker script tokens.
void ScriptLexer::tokenize(MemoryBufferRef mb) {
std::vector<StringRef> vec;
mbs.push_back(mb);
StringRef s = mb.getBuffer();
StringRef begin = s;

void ScriptLexer::lex() {
for (;;) {
StringRef &s = curBuf.s;
s = skipSpace(s);
if (s.empty())
break;
if (s.empty()) {
// If this buffer is from an INCLUDE command, switch to the "return
// value"; otherwise, mark EOF.
if (buffers.empty()) {
eof = true;
return;
}
curBuf = buffers.pop_back_val();
continue;
}
curTokState = inExpr;

// Quoted token. Note that double-quote characters are parts of a token
// because, in a glob match context, only unquoted tokens are interpreted
Expand All @@ -118,45 +119,53 @@ void ScriptLexer::tokenize(MemoryBufferRef mb) {
if (s.starts_with("\"")) {
size_t e = s.find("\"", 1);
if (e == StringRef::npos) {
StringRef filename = mb.getBufferIdentifier();
size_t lineno = begin.substr(0, s.data() - begin.data()).count('\n');
error(filename + ":" + Twine(lineno + 1) + ": unclosed quote");
size_t lineno =
StringRef(curBuf.begin, s.data() - curBuf.begin).count('\n');
error(curBuf.filename + ":" + Twine(lineno + 1) + ": unclosed quote");
return;
}

vec.push_back(s.take_front(e + 1));
curTok = s.take_front(e + 1);
s = s.substr(e + 1);
continue;
return;
}

// Some operators form separate tokens.
if (s.starts_with("<<=") || s.starts_with(">>=")) {
vec.push_back(s.substr(0, 3));
curTok = s.substr(0, 3);
s = s.substr(3);
continue;
return;
}
if (s.size() > 1 && ((s[1] == '=' && strchr("*/+-<>&^|", s[0])) ||
(s[0] == s[1] && strchr("<>&|", s[0])))) {
vec.push_back(s.substr(0, 2));
if (s.size() > 1 && (s[1] == '=' && strchr("+-*/!&^|", s[0]))) {
curTok = s.substr(0, 2);
s = s.substr(2);
continue;
return;
}

// Unquoted token. This is more relaxed than tokens in C-like language,
// so that you can write "file-name.cpp" as one bare token, for example.
size_t pos = s.find_first_not_of(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
"0123456789_.$/\\~=+[]*?-!^:");
// Unquoted token. The non-expression token is more relaxed than tokens in
// C-like languages, so that you can write "file-name.cpp" as one bare
// token.
size_t pos;
if (inExpr) {
pos = s.find_first_not_of(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
"0123456789_.$");
if (pos == 0 && s.size() >= 2 &&
((s[0] == s[1] && strchr("<>&|", s[0])) ||
is_contained({"==", "!=", "<=", ">=", "<<", ">>"}, s.substr(0, 2))))
pos = 2;
} else {
pos = s.find_first_not_of(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
"0123456789_.$/\\~=+[]*?-!^:");
}

// A character that cannot start a word (which is usually a
// punctuation) forms a single character token.
if (pos == 0)
pos = 1;
vec.push_back(s.substr(0, pos));
curTok = s.substr(0, pos);
s = s.substr(pos);
break;
}

tokens.insert(tokens.begin() + pos, vec.begin(), vec.end());
}

// Skip leading whitespace characters or comments.
Expand Down Expand Up @@ -185,93 +194,30 @@ StringRef ScriptLexer::skipSpace(StringRef s) {
}
}

// An erroneous token is handled as if it were the last token before EOF.
bool ScriptLexer::atEOF() { return errorCount() || tokens.size() == pos; }

// Split a given string as an expression.
// This function returns "3", "*" and "5" for "3*5" for example.
static std::vector<StringRef> tokenizeExpr(StringRef s) {
StringRef ops = "!~*/+-<>?^:="; // List of operators

// Quoted strings are literal strings, so we don't want to split it.
if (s.starts_with("\""))
return {s};

// Split S with operators as separators.
std::vector<StringRef> ret;
while (!s.empty()) {
size_t e = s.find_first_of(ops);

// No need to split if there is no operator.
if (e == StringRef::npos) {
ret.push_back(s);
break;
}

// Get a token before the operator.
if (e != 0)
ret.push_back(s.substr(0, e));

// Get the operator as a token.
// Keep !=, ==, >=, <=, << and >> operators as a single tokens.
if (s.substr(e).starts_with("!=") || s.substr(e).starts_with("==") ||
s.substr(e).starts_with(">=") || s.substr(e).starts_with("<=") ||
s.substr(e).starts_with("<<") || s.substr(e).starts_with(">>")) {
ret.push_back(s.substr(e, 2));
s = s.substr(e + 2);
} else {
ret.push_back(s.substr(e, 1));
s = s.substr(e + 1);
}
}
return ret;
}

// In contexts where expressions are expected, the lexer should apply
// different tokenization rules than the default one. By default,
// arithmetic operator characters are regular characters, but in the
// expression context, they should be independent tokens.
//
// For example, "foo*3" should be tokenized to "foo", "*" and "3" only
// in the expression context.
//
// This function may split the current token into multiple tokens.
void ScriptLexer::maybeSplitExpr() {
if (!inExpr || errorCount() || atEOF())
return;

std::vector<StringRef> v = tokenizeExpr(tokens[pos]);
if (v.size() == 1)
return;
tokens.erase(tokens.begin() + pos);
tokens.insert(tokens.begin() + pos, v.begin(), v.end());
}
// Used to determine whether to stop parsing. Treat errors like EOF.
bool ScriptLexer::atEOF() { return eof || errorCount(); }

StringRef ScriptLexer::next() {
maybeSplitExpr();

if (errorCount())
return "";
if (atEOF()) {
setError("unexpected EOF");
return "";
}
return tokens[pos++];
prevTok = peek();
return std::exchange(curTok, StringRef(curBuf.s.data(), 0));
}

StringRef ScriptLexer::peek() {
StringRef tok = next();
if (errorCount())
return "";
pos = pos - 1;
return tok;
// curTok is invalid if curTokState and inExpr mismatch.
if (curTok.size() && curTokState != inExpr) {
curBuf.s = StringRef(curTok.data(), curBuf.s.end() - curTok.data());
curTok = {};
}
if (curTok.empty())
lex();
return curTok;
}

bool ScriptLexer::consume(StringRef tok) {
if (next() == tok)
return true;
--pos;
return false;
if (peek() != tok)
return false;
next();
return true;
}

void ScriptLexer::skip() { (void)next(); }
Expand All @@ -280,8 +226,23 @@ void ScriptLexer::expect(StringRef expect) {
if (errorCount())
return;
StringRef tok = next();
if (tok != expect)
setError(expect + " expected, but got " + tok);
if (tok != expect) {
if (atEOF())
setError("unexpected EOF");
else
setError(expect + " expected, but got " + tok);
}
}

ScriptLexer::Token ScriptLexer::till(StringRef tok) {
StringRef str = next();
if (str == tok)
return {};
if (!atEOF())
return {str};
prevTok = {};
setError("unexpected EOF");
return {};
}

// Returns true if S encloses T.
Expand All @@ -292,10 +253,8 @@ static bool encloses(StringRef s, StringRef t) {
MemoryBufferRef ScriptLexer::getCurrentMB() {
// Find input buffer containing the current token.
assert(!mbs.empty());
if (pos == 0)
return mbs.back();
for (MemoryBufferRef mb : mbs)
if (encloses(mb.getBuffer(), tokens[pos - 1]))
if (encloses(mb.getBuffer(), curBuf.s))
return mb;
llvm_unreachable("getCurrentMB: failed to find a token");
}
37 changes: 33 additions & 4 deletions lld/ELF/ScriptLexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,67 @@
#define LLD_ELF_SCRIPT_LEXER_H

#include "lld/Common/LLVM.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/MemoryBufferRef.h"
#include <vector>

namespace lld::elf {

class ScriptLexer {
protected:
struct Buffer {
// The remaining content to parse and the filename.
StringRef s, filename;
const char *begin = nullptr;
Buffer() = default;
Buffer(MemoryBufferRef mb)
: s(mb.getBuffer()), filename(mb.getBufferIdentifier()),
begin(mb.getBufferStart()) {}
};
// The current buffer and parent buffers due to INCLUDE.
Buffer curBuf;
SmallVector<Buffer, 0> buffers;

struct Token {
StringRef str;
explicit operator bool() const { return !str.empty(); }
operator StringRef() const { return str; }
};

// The token before the last next().
StringRef prevTok;
// Rules for what is a token are different when we are in an expression.
// curTok holds the cached return value of peek() and is invalid when the
// expression state changes.
StringRef curTok;
// The inExpr state when curTok is cached.
bool curTokState = false;
bool eof = false;

public:
explicit ScriptLexer(MemoryBufferRef mb);

void setError(const Twine &msg);
void tokenize(MemoryBufferRef mb);
void lex();
StringRef skipSpace(StringRef s);
bool atEOF();
StringRef next();
StringRef peek();
void skip();
bool consume(StringRef tok);
void expect(StringRef expect);
Token till(StringRef tok);
std::string getCurrentLocation();
MemoryBufferRef getCurrentMB();

std::vector<MemoryBufferRef> mbs;
std::vector<StringRef> tokens;
bool inExpr = false;
size_t pos = 0;

size_t lastLineNumber = 0;
size_t lastLineNumberOffset = 0;

private:
void maybeSplitExpr();
StringRef getLine();
size_t getLineNumber();
size_t getColumnNumber();
Expand Down
105 changes: 58 additions & 47 deletions lld/ELF/ScriptParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,9 @@ void ScriptParser::readDynamicList() {
std::tie(locals, globals) = readSymbols();
expect(";");

if (!atEOF()) {
setError("EOF expected, but got " + next());
StringRef tok = peek();
if (tok.size()) {
setError("EOF expected, but got " + tok);
return;
}
if (!locals.empty()) {
Expand All @@ -215,8 +216,9 @@ void ScriptParser::readDynamicList() {

void ScriptParser::readVersionScript() {
readVersionScriptCommand();
if (!atEOF())
setError("EOF expected, but got " + next());
StringRef tok = peek();
if (tok.size())
setError("EOF expected, but got " + tok);
}

void ScriptParser::readVersionScriptCommand() {
Expand All @@ -225,7 +227,9 @@ void ScriptParser::readVersionScriptCommand() {
return;
}

while (!atEOF() && !errorCount() && peek() != "}") {
if (atEOF())
setError("unexpected EOF");
while (peek() != "}" && !atEOF()) {
StringRef verStr = next();
if (verStr == "{") {
setError("anonymous version definition is used in "
Expand All @@ -246,6 +250,8 @@ void ScriptParser::readVersion() {
void ScriptParser::readLinkerScript() {
while (!atEOF()) {
StringRef tok = next();
if (atEOF())
break;
if (tok == ";")
continue;

Expand Down Expand Up @@ -307,8 +313,8 @@ void ScriptParser::readDefsym(StringRef name) {
void ScriptParser::readNoCrossRefs(bool to) {
expect("(");
NoCrossRefCommand cmd{{}, to};
while (!errorCount() && !consume(")"))
cmd.outputSections.push_back(unquote(next()));
while (auto tok = till(")"))
cmd.outputSections.push_back(unquote(tok));
if (cmd.outputSections.size() < 2)
warn(getCurrentLocation() + ": ignored with fewer than 2 output sections");
else
Expand Down Expand Up @@ -368,8 +374,8 @@ void ScriptParser::readAsNeeded() {
expect("(");
bool orig = config->asNeeded;
config->asNeeded = true;
while (!errorCount() && !consume(")"))
addFile(unquote(next()));
while (auto tok = till(")"))
addFile(unquote(tok));
config->asNeeded = orig;
}

Expand All @@ -384,8 +390,8 @@ void ScriptParser::readEntry() {

void ScriptParser::readExtern() {
expect("(");
while (!errorCount() && !consume(")"))
config->undefined.push_back(unquote(next()));
while (auto tok = till(")"))
config->undefined.push_back(unquote(tok));
}

void ScriptParser::readGroup() {
Expand All @@ -406,20 +412,23 @@ void ScriptParser::readInclude() {
}

if (std::optional<std::string> path = searchScript(tok)) {
if (std::optional<MemoryBufferRef> mb = readFile(*path))
tokenize(*mb);
if (std::optional<MemoryBufferRef> mb = readFile(*path)) {
buffers.push_back(curBuf);
curBuf = Buffer(*mb);
mbs.push_back(*mb);
}
return;
}
setError("cannot find linker script " + tok);
}

void ScriptParser::readInput() {
expect("(");
while (!errorCount() && !consume(")")) {
if (consume("AS_NEEDED"))
while (auto tok = till(")")) {
if (tok == "AS_NEEDED")
readAsNeeded();
else
addFile(unquote(next()));
addFile(unquote(tok));
}
}

Expand All @@ -435,8 +444,8 @@ void ScriptParser::readOutput() {
void ScriptParser::readOutputArch() {
// OUTPUT_ARCH is ignored for now.
expect("(");
while (!errorCount() && !consume(")"))
skip();
while (next() != ")" && !atEOF())
;
}

static std::pair<ELFKind, uint16_t> parseBfdName(StringRef s) {
Expand Down Expand Up @@ -514,10 +523,9 @@ void ScriptParser::readOutputFormat() {

void ScriptParser::readPhdrs() {
expect("{");

while (!errorCount() && !consume("}")) {
while (auto tok = till("}")) {
PhdrsCommand cmd;
cmd.name = next();
cmd.name = tok;
cmd.type = readPhdrType();

while (!errorCount() && !consume(";")) {
Expand Down Expand Up @@ -613,15 +621,14 @@ SmallVector<SectionCommand *, 0> ScriptParser::readOverlay() {

void ScriptParser::readOverwriteSections() {
expect("{");
while (!errorCount() && !consume("}"))
script->overwriteSections.push_back(readOutputSectionDescription(next()));
while (auto tok = till("}"))
script->overwriteSections.push_back(readOutputSectionDescription(tok));
}

void ScriptParser::readSections() {
expect("{");
SmallVector<SectionCommand *, 0> v;
while (!errorCount() && !consume("}")) {
StringRef tok = next();
while (auto tok = till("}")) {
if (tok == "OVERLAY") {
for (SectionCommand *cmd : readOverlay())
v.push_back(cmd);
Expand Down Expand Up @@ -701,9 +708,8 @@ static int precedence(StringRef op) {

StringMatcher ScriptParser::readFilePatterns() {
StringMatcher Matcher;

while (!errorCount() && !consume(")"))
Matcher.addPattern(SingleStringMatcher(next()));
while (auto tok = till(")"))
Matcher.addPattern(SingleStringMatcher(tok));
return Matcher;
}

Expand Down Expand Up @@ -790,7 +796,7 @@ ScriptParser::readInputSectionRules(StringRef filePattern, uint64_t withFlags,
make<InputSectionDescription>(filePattern, withFlags, withoutFlags);
expect("(");

while (!errorCount() && !consume(")")) {
while (peek() != ")" && !atEOF()) {
SortSectionPolicy outer = readSortKind();
SortSectionPolicy inner = SortSectionPolicy::Default;
SmallVector<SectionPattern, 0> v;
Expand All @@ -816,6 +822,7 @@ ScriptParser::readInputSectionRules(StringRef filePattern, uint64_t withFlags,

std::move(v.begin(), v.end(), std::back_inserter(cmd->sectionPatterns));
}
expect(")");
return cmd;
}

Expand Down Expand Up @@ -990,8 +997,7 @@ OutputDesc *ScriptParser::readOutputSectionDescription(StringRef outSec) {
osec->constraint = ConstraintKind::ReadWrite;
expect("{");

while (!errorCount() && !consume("}")) {
StringRef tok = next();
while (auto tok = till("}")) {
if (tok == ";") {
// Empty commands are allowed. Do nothing here.
} else if (SymbolAssignment *assign = readAssignment(tok)) {
Expand Down Expand Up @@ -1098,12 +1104,23 @@ SymbolAssignment *ScriptParser::readProvideHidden(bool provide, bool hidden) {
return cmd;
}

// Replace whitespace sequence (including \n) with one single space. The output
// is used by -Map.
static void squeezeSpaces(std::string &str) {
char prev = '\0';
auto it = str.begin();
for (char c : str)
if (!isSpace(c) || (c = ' ') != prev)
*it++ = prev = c;
str.erase(it, str.end());
}

SymbolAssignment *ScriptParser::readAssignment(StringRef tok) {
// Assert expression returns Dot, so this is equal to ".=."
if (tok == "ASSERT")
return make<SymbolAssignment>(".", readAssert(), 0, getCurrentLocation());

size_t oldPos = pos;
const char *oldS = prevTok.data();
SymbolAssignment *cmd = nullptr;
bool savedSeenRelroEnd = script->seenRelroEnd;
const StringRef op = peek();
Expand All @@ -1127,9 +1144,8 @@ SymbolAssignment *ScriptParser::readAssignment(StringRef tok) {

if (cmd) {
cmd->dataSegmentRelroEnd = !savedSeenRelroEnd && script->seenRelroEnd;
cmd->commandString =
tok.str() + " " +
llvm::join(tokens.begin() + oldPos, tokens.begin() + pos, " ");
cmd->commandString = StringRef(oldS, curTok.data() - oldS).str();
squeezeSpaces(cmd->commandString);
expect(";");
}
return cmd;
Expand Down Expand Up @@ -1333,12 +1349,11 @@ ByteCommand *ScriptParser::readByteCommand(StringRef tok) {
if (size == -1)
return nullptr;

size_t oldPos = pos;
const char *oldS = prevTok.data();
Expr e = readParenExpr();
std::string commandString =
tok.str() + " " +
llvm::join(tokens.begin() + oldPos, tokens.begin() + pos, " ");
return make<ByteCommand>(e, size, commandString);
std::string commandString = StringRef(oldS, curBuf.s.data() - oldS).str();
squeezeSpaces(commandString);
return make<ByteCommand>(e, size, std::move(commandString));
}

static std::optional<uint64_t> parseFlag(StringRef tok) {
Expand Down Expand Up @@ -1753,16 +1768,13 @@ SmallVector<SymbolVersion, 0> ScriptParser::readVersionExtern() {
expect("{");

SmallVector<SymbolVersion, 0> ret;
while (!errorCount() && peek() != "}") {
StringRef tok = next();
while (auto tok = till("}")) {
ret.push_back(
{unquote(tok), isCXX, !tok.starts_with("\"") && hasWildcard(tok)});
{unquote(tok), isCXX, !tok.str.starts_with("\"") && hasWildcard(tok)});
if (consume("}"))
return ret;
expect(";");
}

expect("}");
return ret;
}

Expand All @@ -1782,8 +1794,7 @@ Expr ScriptParser::readMemoryAssignment(StringRef s1, StringRef s2,
// MEMORY { name [(attr)] : ORIGIN = origin, LENGTH = len ... }
void ScriptParser::readMemory() {
expect("{");
while (!errorCount() && !consume("}")) {
StringRef tok = next();
while (auto tok = till("}")) {
if (tok == "INCLUDE") {
readInclude();
continue;
Expand Down
Loading