[packages/ldc] - rel 3, sync with git for llvm 22 compatibility

baggins baggins at pld-linux.org
Sat Apr 18 22:39:25 CEST 2026


commit 0cb953558f29825500e177b8a703b59ff88ff4a5
Author: Jan Rękorajski <baggins at pld-linux.org>
Date:   Sun Apr 19 00:38:49 2026 +0200

    - rel 3, sync with git for llvm 22 compatibility

 git.patch | 10578 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 ldc.spec  |     6 +-
 2 files changed, 10582 insertions(+), 2 deletions(-)
---
diff --git a/ldc.spec b/ldc.spec
index 3c63c10..9b1bc61 100644
--- a/ldc.spec
+++ b/ldc.spec
@@ -6,7 +6,7 @@
 %bcond_without	geany		# geany autocompletion support
 %bcond_with	jit		# dynamic compilation support (JIT) (LLVM 18/19 only)
 
-%define		rel		2
+%define		rel		3
 
 %define	bootstrap_version 1.42.0
 Summary:	LLVM D Compiler
@@ -26,6 +26,7 @@ Source3:	macros.%{name}
 Patch0:		%{name}-include-path.patch
 Patch1:		%{name}-no-default-rpath.patch
 Patch2:		%{name}-dmd.patch
+Patch3:		git.patch
 URL:		https://github.com/ldc-developers/ldc
 # for llvm < 16
 #BuildRequires:	SPIRV-LLVM-Translator-devel
@@ -40,7 +41,7 @@ BuildRequires:	libconfig-devel
 BuildRequires:	libedit-devel
 BuildRequires:	libstdc++-devel
 BuildRequires:	llvm-devel >= 15.0
-BuildRequires:	llvm-devel < 22
+BuildRequires:	llvm-devel < 23
 BuildRequires:	rpm-build >= 4.6
 BuildRequires:	rpmbuild(macros) >= 2.047
 BuildRequires:	tar >= 1:1.22
@@ -127,6 +128,7 @@ Obsługa automatycznego dopełniania dla biblioteki Phobos w IDE geany.
 %patch -P0 -p1
 %patch -P1 -p1
 %patch -P2 -p1
+%patch -P3 -p1
 
 %if %{with geany}
 # temp geany config directory for allow geany to generate tags
diff --git a/git.patch b/git.patch
new file mode 100644
index 0000000..5675698
--- /dev/null
+++ b/git.patch
@@ -0,0 +1,10578 @@
+diff --git a/CHANGELOG.md b/CHANGELOG.md
+index 647eb518f6..d9f19defa1 100644
+--- a/CHANGELOG.md
++++ b/CHANGELOG.md
+@@ -1,8 +1,13 @@
+ # LDC master
+ 
+ #### Big news
++- Support for [LLVM 22](https://releases.llvm.org/22.1.0/docs/ReleaseNotes.html). The prebuilt packages use v22.1.2. (#5097, #5102)
++- Minimum LLVM version raised to 18. (#5094)
++- DMD-style inline assembly: `asm { naked; }` is now much less of a special case wrt. codegen - such naked functions are now emitted as if `asm { naked; }` was replaced with the `@naked` function UDA. IR-wise, they are not emitted as module-level asm blobs anymore, but regular IR functions. This should lift a few former `asm { naked; }`-specific restrictions and fixed at least one LTO issue. (#5041)
++- Predefined version `LDC_LLVM_*` now only contains the LLVM major version, i.e., former `version (LDC_LLVM_1801)` with LLVM v18.1 is now `version (LDC_LLVM_18)`. Use `ldc.intrinsics.LLVM_version` for backwards compatibility if really needed. (#5109)
+ 
+ #### Platform support
++- Supports LLVM 18 - 22.
+ 
+ #### Bug fixes
+ 
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 81ca3effa7..83584dcae8 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -29,14 +29,13 @@ include(BuildDExecutable)
+ # Locate LLVM.
+ #
+ 
+-find_package(LLVM 15.0 REQUIRED
++find_package(LLVM 18.0 REQUIRED
+     all-targets analysis asmparser asmprinter bitreader bitwriter codegen core
+     debuginfodwarf debuginfomsf debuginfopdb demangle
+     instcombine ipo instrumentation irreader libdriver linker lto mc
+     mcdisassembler mcparser objcarcopts object option profiledata scalaropts
+     selectiondag support tablegen target transformutils vectorize
+     windowsdriver windowsmanifest symbolize ${EXTRA_LLVM_MODULES})
+-math(EXPR LDC_LLVM_VER ${LLVM_VERSION_MAJOR}*100+${LLVM_VERSION_MINOR})
+ message(STATUS "Using LLVM Version ${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}")
+ # Remove LLVMTableGen library from list of libraries
+ string(REGEX MATCH "[^;]*LLVMTableGen[^;]*" LLVM_TABLEGEN_LIBRARY "${LLVM_LIBRARIES}")
+@@ -49,7 +48,7 @@ endforeach()
+ 
+ # Set MLIR support variables if it is found.
+ # FIXME: LLVM 14+ (`mlir::OwningModuleRef` replacement)
+-if(NOT LDC_WITH_MLIR STREQUAL "OFF" AND LDC_LLVM_VER LESS 1400)
++if(NOT LDC_WITH_MLIR STREQUAL "OFF" AND LLVM_VERSION_MAJOR LESS 14)
+     include(FindMLIR)
+     if(MLIR_FOUND)
+         message(STATUS "-- Building LDC with MLIR support (${MLIR_ROOT_DIR})")
+@@ -61,46 +60,6 @@ if(NOT LDC_WITH_MLIR STREQUAL "OFF" AND LDC_LLVM_VER LESS 1400)
+     endif()
+ endif()
+ 
+-# Check and adapt for LLVMSPIRVLib (Khronos SPIRV-LLVM-Translator)
+-set(LLVM_SPIRV_FOUND OFF)
+-if (LDC_LLVM_VER LESS 1600)
+-if(MSVC)
+-    if(EXISTS "${LLVM_LIBRARY_DIRS}/LLVMSPIRVLib.lib")
+-        set(LLVM_SPIRV_FOUND ON)
+-        set(LLVM_LIBRARIES "${LLVM_LIBRARY_DIRS}/LLVMSPIRVLib.lib" ${LLVM_LIBRARIES})
+-    endif()
+-else()
+-    if((EXISTS "${LLVM_LIBRARY_DIRS}/libLLVMSPIRVLib.a") OR
+-       (EXISTS "${LLVM_LIBRARY_DIRS}/libLLVMSPIRVLib.so") OR
+-       (EXISTS "${LLVM_LIBRARY_DIRS}/libLLVMSPIRVLib.dylib"))
+-        set(LLVM_SPIRV_FOUND ON)
+-        set(LLVM_LIBRARIES -lLLVMSPIRVLib ${LLVM_LIBRARIES})
+-    endif()
+-endif()
+-if(NOT LLVM_SPIRV_FOUND)
+-    find_package(PkgConfig)
+-    if(PkgConfig_FOUND)
+-        if(MSVC)
+-            # make pkg-config use -LC:\path\to\build\LLVMSPIRVLib.lib not -L-lLLVMSPIRVLib
+-            set(PKG_CONFIG_EXECUTABLE "${PKG_CONFIG_EXECUTABLE} --msvc-syntax")
+-        endif()
+-        pkg_check_modules(LLVM_SPIRV LLVMSPIRVLib)
+-        if(LLVM_SPIRV_FOUND)
+-            set(LLVM_SPIRV_FOUND ON) # translate 1 to ON
+-            include_directories(${LLVM_SPIRV_INCLUDE_DIRS})
+-        else()
+-            set(LLVM_SPIRV_FOUND OFF)
+-        endif()
+-    endif()
+-endif()
+-if(LLVM_SPIRV_FOUND)
+-    message(STATUS "-- Building LDC with SPIR-V support")
+-    add_definitions("-DLDC_LLVM_SUPPORTED_TARGET_SPIRV=1")
+-else()
+-    message(STATUS "-- Building LDC without SPIR-V support: not found")
+-endif()
+-endif()
+-
+ #
+ # Get info about used Linux distribution.
+ #
+@@ -137,6 +96,9 @@ set(ALTERNATIVE_MALLOC_O   ""     CACHE STRING "If specified, adds ALTERNATIVE_M
+ # Most linux distributions have a policy of not bundling dependencies like zlib
+ set(PHOBOS_SYSTEM_ZLIB OFF CACHE BOOL "Use system zlib instead of Phobos' vendored version")
+ 
++# Force use of softfloat for compiler
++option(LDC_FORCE_SOFTFLOAT_REAL "Force LDC to be built with 80-bit real in software" OFF)
++
+ if(D_VERSION EQUAL 1)
+     message(FATAL_ERROR "D version 1 is no longer supported.
+ Please consider using D version 2 or checkout the 'd1' git branch for the last version supporting D version 1.")
+@@ -175,6 +137,7 @@ if(NOT MSVC_IDE)
+ endif()
+ 
+ if(MSVC)
++    set(LDC_FORCE_SOFTFLOAT_REAL ON)
+     if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+         message(STATUS "Let D host compiler output 64-bit object files")
+         append("-m64" DFLAGS_BASE)
+@@ -206,6 +169,11 @@ if(MSVC)
+     endforeach()
+ endif()
+ 
++if (LDC_FORCE_SOFTFLOAT_REAL)
++    append("-version=LDC_real_softfloat" DFLAGS_LDC)
++    append("-DLDC_real_softfloat" LDC_CXXFLAGS)
++endif()
++
+ # Use separate compiler flags for the frontend and for the LDC-specific parts,
+ # as enabling warnings on the DMD frontend only leads to a lot of clutter in
+ # the output (LLVM_CXXFLAGS sometimes already includes -Wall).
+@@ -435,10 +403,8 @@ include(HandleLTOPGOBuildOptions)
+ set(LDC_DYNAMIC_COMPILE "AUTO" CACHE STRING "Support dynamic compilation (ON|OFF). Enabled by default; not supported for LLVM < 18.")
+ option(LDC_DYNAMIC_COMPILE_USE_CUSTOM_PASSES "Use custom LDC passes in jit" ON)
+ if(LDC_DYNAMIC_COMPILE STREQUAL "AUTO")
+-    if(LDC_LLVM_VER LESS 1800)
+-        set(LDC_DYNAMIC_COMPILE OFF)
+     # FIXME: dynamic-compile regressions with LLVM 20
+-    elseif(LDC_LLVM_VER LESS 2000)
++    if(LLVM_VERSION_MAJOR LESS 20)
+         set(LDC_DYNAMIC_COMPILE ON)
+     else()
+         set(LDC_DYNAMIC_COMPILE OFF)
+@@ -475,13 +441,11 @@ append("-J${PROJECT_SOURCE_DIR}/dmd/res" DFLAGS_LDC)
+ 
+ append("-version=IN_LLVM" DFLAGS_LDC)
+ append("-DIN_LLVM" LDC_CXXFLAGS)
+-append("-DOPAQUE_VTBLS" LDC_CXXFLAGS)
+ # Predefine LDC_INSTALL_PREFIX as raw string literal, requiring shell + CMake escaping.
+ # E.g., for CMAKE_INSTALL_PREFIX=`C:\dir with space`:
+ #   g++ "-DLDC_INSTALL_PREFIX=R\"(C:\dir with space)\"" ...
+ #   => LDC_INSTALL_PREFIX defined as `R"(C:\dir with space)"`
+ append("\"-DLDC_INSTALL_PREFIX=R\\\"(${CMAKE_INSTALL_PREFIX})\\\"\"" LDC_CXXFLAGS)
+-append("-DLDC_LLVM_VER=${LDC_LLVM_VER}" LDC_CXXFLAGS)
+ append("\"-DLDC_LIBDIR_SUFFIX=R\\\"(${LIB_SUFFIX})\\\"\"" LDC_CXXFLAGS)
+ append("-DLDC_HOST_${D_COMPILER_ID}=1" LDC_CXXFLAGS)
+ append("-DLDC_HOST_FE_VER=${D_COMPILER_FE_VERSION}" LDC_CXXFLAGS)
+@@ -580,9 +544,6 @@ if(WIN32)
+ elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+     target_link_libraries(${LDC_LIB} dl)
+ endif()
+-if(LLVM_SPIRV_FOUND)
+-    target_link_libraries(${LDC_LIB} ${LLVM_SPIRV_LIBRARIES} ${LLVM_SPIRV_LDFLAGS})
+-endif()
+ 
+ set(LDC_EXE_FULL ${PROJECT_BINARY_DIR}/bin/${LDC_EXE_NAME}${CMAKE_EXECUTABLE_SUFFIX})
+ set(LDMD_EXE_FULL ${PROJECT_BINARY_DIR}/bin/${LDMD_EXE_NAME}${CMAKE_EXECUTABLE_SUFFIX})
+@@ -601,19 +562,17 @@ if(MSVC)
+     endif()
+ endif()
+ if(LDC_WITH_LLD)
+-    if(MSVC)
+-        list(APPEND LDC_LINKERFLAG_LIST LLVMSymbolize.lib)
+-    else()
+-        set(LDC_LINKERFLAG_LIST -lLLVMSymbolize ${LDC_LINKERFLAG_LIST})
++    if(LLVM_VERSION_MAJOR GREATER 21)
++        if(MSVC)
++            list(APPEND LDC_LINKERFLAG_LIST LLVMDTLTO.lib)
++        else()
++            set(LDC_LINKERFLAG_LIST -lLLVMDTLTO ${LDC_LINKERFLAG_LIST})
++        endif()
+     endif()
+     if(MSVC)
+-        list(APPEND LDC_LINKERFLAG_LIST lldMinGW.lib lldCOFF.lib lldELF.lib lldMachO.lib lldWasm.lib lldCommon.lib)
++        list(APPEND LDC_LINKERFLAG_LIST lldMinGW.lib lldCOFF.lib lldELF.lib lldMachO.lib lldWasm.lib lldCommon.lib LLVMSymbolize.lib)
+     else()
+-        set(LDC_LINKERFLAG_LIST -llldMinGW -llldCOFF -llldELF -llldMachO -llldWasm -llldCommon ${LDC_LINKERFLAG_LIST})
+-    endif()
+-    if(APPLE)
+-        # LLD 13.0.0 on Mac needs libxar
+-        list(APPEND LDC_LINKERFLAG_LIST -lxar)
++        set(LDC_LINKERFLAG_LIST -llldMinGW -llldCOFF -llldELF -llldMachO -llldWasm -llldCommon -lLLVMSymbolize ${LDC_LINKERFLAG_LIST})
+     endif()
+     if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
+         # FreeBSD LLVM port links to zstd, but does not convey this information via CMake
+@@ -799,11 +758,7 @@ endif()
+ # another directory.
+ set(COMPILER_RT_BASE_DIR "${LLVM_LIBRARY_DIRS}/clang" CACHE PATH "Base path of compiler-rt libraries. If they in are /usr/lib/clang/17/lib/linux/libclang_rt* you should set this value to /usr/lib/clang")
+ set(COMPILER_RT_LIBDIR "${COMPILER_RT_BASE_DIR}")
+-if(LDC_LLVM_VER LESS 1600)
+-    set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/${LLVM_VERSION_BASE_STRING}")
+-else()
+-    set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/${LLVM_VERSION_MAJOR}")
+-endif()
++set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/${LLVM_VERSION_MAJOR}")
+ set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/lib")
+ if(APPLE)
+     set(COMPILER_RT_LIBDIR "${COMPILER_RT_LIBDIR}/darwin")
+@@ -881,7 +836,7 @@ if (LDC_INSTALL_LLVM_RUNTIME_LIBS)
+         if(CMAKE_SIZEOF_VOID_P EQUAL 4)
+             set(compilerrt_arch_suffix "i386")
+         endif()
+-        if(LDC_LLVM_VER LESS 2000)
++        if(LLVM_VERSION_MAJOR LESS 20)
+             copy_compilerrt_lib("clang_rt.asan-${compilerrt_arch_suffix}.lib" "ldc_rt.asan.lib" FALSE)
+             copy_compilerrt_lib("clang_rt.lsan-${compilerrt_arch_suffix}.lib" "ldc_rt.lsan.lib" FALSE)
+         else()
+@@ -956,12 +911,6 @@ add_subdirectory(tools)
+ 
+ set(switches)
+ 
+-# LLVM 16: Disable function specializations by default.
+-# They cause miscompiles of e.g. the frontend for some targets (macOS x86_64 and Windows x64).
+-if(LDC_LLVM_VER GREATER 1599 AND LDC_LLVM_VER LESS 1700)
+-    list(APPEND switches "-func-specialization-size-threshold=1000000000")
+-endif()
+-
+ list(APPEND switches "-defaultlib=phobos2-ldc,druntime-ldc")
+ 
+ makeConfSection(NAME "30-compiler" SECTION "default"
+diff --git a/README.md b/README.md
+index 9d8b8e568b..5076bf12f7 100644
+--- a/README.md
++++ b/README.md
+@@ -75,7 +75,7 @@ libraries is available on the project wiki for
+ [Windows](https://wiki.dlang.org/Building_and_hacking_LDC_on_Windows_using_MSVC).
+ 
+ If you have a working C++/D build environment, CMake, and a recent LLVM
+-version (≥ 15) available, there should be no big surprises. Do not
++version (≥ 18) available, there should be no big surprises. Do not
+ forget to make sure the Phobos submodule is up to date:
+ 
+     $ cd ldc
+diff --git a/cmake/Modules/FindLLVM.cmake b/cmake/Modules/FindLLVM.cmake
+index 75fa1eee29..018011e8d0 100644
+--- a/cmake/Modules/FindLLVM.cmake
++++ b/cmake/Modules/FindLLVM.cmake
+@@ -36,9 +36,6 @@ set(llvm_config_names llvm-config-21.1 llvm-config211 llvm-config-21
+                       llvm-config-20.1 llvm-config201 llvm-config-20
+                       llvm-config-19.1 llvm-config191 llvm-config-19
+                       llvm-config-18.1 llvm-config181 llvm-config-18
+-                      llvm-config-17.0 llvm-config170 llvm-config-17
+-                      llvm-config-16.0 llvm-config160 llvm-config-16
+-                      llvm-config-15.0 llvm-config150 llvm-config-15
+                       llvm-config)
+ find_program(LLVM_CONFIG
+     NAMES ${llvm_config_names}
+@@ -51,14 +48,10 @@ if(APPLE)
+         NAMES ${llvm_config_names}
+         PATHS /opt/local/libexec/llvm-21/bin
+               /opt/local/libexec/llvm-20/bin /opt/local/libexec/llvm-19/bin
+-              /opt/local/libexec/llvm-18/bin /opt/local/libexec/llvm-17/bin
+-              /opt/local/libexec/llvm-16/bin /opt/local/libexec/llvm-15/bin
+-              /opt/local/libexec/llvm/bin
++              /opt/local/libexec/llvm-18/bin /opt/local/libexec/llvm/bin
+               /usr/local/opt/llvm at 21/bin
+               /usr/local/opt/llvm at 20/bin /usr/local/opt/llvm at 19/bin
+-              /usr/local/opt/llvm at 18/bin /usr/local/opt/llvm at 17/bin
+-              /usr/local/opt/llvm at 16/bin /usr/local/opt/llvm at 15/bin
+-              /usr/local/opt/llvm/bin
++              /usr/local/opt/llvm at 18/bin /usr/local/opt/llvm/bin
+         NO_DEFAULT_PATH)
+ endif()
+ 
+diff --git a/dmd/declaration.h b/dmd/declaration.h
+index 7533348374..5c4562aab9 100644
+--- a/dmd/declaration.h
++++ b/dmd/declaration.h
+@@ -581,6 +581,9 @@ public:
+     VarDeclaration *v_argptr;           // '_argptr' variable
+     VarDeclarations *parameters;        // Array of VarDeclaration's for parameters
+     DsymbolTable *labtab;               // statement label symbol table
++#if IN_LLVM
++    Identifiers *asmLabels;             // identifiers of labels defined in DMD-style inline assembly
++#endif
+     Dsymbol *overnext;                  // next in overload list
+     FuncDeclaration *overnext0;         // next in overload list (only used during IFTI)
+     Loc endloc;                         // location of closing curly bracket
+diff --git a/dmd/func.d b/dmd/func.d
+index 6709e32ebb..e39e233eae 100644
+--- a/dmd/func.d
++++ b/dmd/func.d
+@@ -256,6 +256,10 @@ version (IN_LLVM)
+     VarDeclaration v_argptr;            /// '_argptr' variable
+     VarDeclarations* parameters;        /// Array of VarDeclaration's for parameters
+     DsymbolTable labtab;                /// statement label symbol table
++version (IN_LLVM)
++{
++    Identifiers* asmLabels;             /// identifiers of labels defined in DMD-style inline assembly
++}
+     Dsymbol overnext;                   /// next in overload list
+     FuncDeclaration overnext0;          /// next in overload list (only used during IFTI)
+     Loc endloc;                         /// location of closing curly bracket
+diff --git a/dmd/globals.h b/dmd/globals.h
+index 9d4bf185a5..bad8e9c410 100644
+--- a/dmd/globals.h
++++ b/dmd/globals.h
+@@ -17,11 +17,7 @@
+ #include "compiler.h"
+ 
+ #if IN_LLVM
+-#if LDC_LLVM_VER < 1700
+-#include "llvm/ADT/Triple.h"
+-#else
+ #include "llvm/TargetParser/Triple.h"
+-#endif
+ 
+ enum OUTPUTFLAG
+ {
+diff --git a/dmd/root/ctfloat.d b/dmd/root/ctfloat.d
+index 2996d7cd60..2ca9e20527 100644
+--- a/dmd/root/ctfloat.d
++++ b/dmd/root/ctfloat.d
+@@ -47,7 +47,8 @@ extern (C++) struct CTFloat
+     version (GNU)
+         enum yl2x_supported = false;
+     else
+-        enum yl2x_supported = __traits(compiles, core.math.yl2x(1.0L, 2.0L));
++        enum yl2x_supported = is(real_t == real) &&
++                              __traits(compiles, core.math.yl2x(1.0L, 2.0L));
+     enum yl2xp1_supported = yl2x_supported;
+ 
+     static void yl2x(const real_t* x, const real_t* y, real_t* res) // IN_LLVM: impure because of log2
+diff --git a/dmd/root/longdouble.d b/dmd/root/longdouble.d
+index 0bbd27a39e..9da7f09e06 100644
+--- a/dmd/root/longdouble.d
++++ b/dmd/root/longdouble.d
+@@ -11,8 +11,14 @@
+ 
+ module dmd.root.longdouble;
+ 
+-version (CRuntime_Microsoft)
++version (LDC_real_softfloat)
+ {
++    version = EnableSoftfloat;
++    alias longdouble = longdouble_soft;
++}
++else version (CRuntime_Microsoft)
++{
++    version = EnableSoftfloat;
+     static if (real.sizeof > 8)
+         alias longdouble = real;
+     else
+@@ -23,7 +29,7 @@ else
+ 
+ // longdouble_soft needed when building the backend with
+ // Visual C or the frontend with LDC on Windows
+-version (CRuntime_Microsoft):
++version (EnableSoftfloat):
+ extern (C++):
+ nothrow:
+ @nogc:
+@@ -71,7 +77,7 @@ bool initFPU()
+     return true;
+ }
+ 
+-version(unittest) version(CRuntime_Microsoft)
++version(unittest)
+ extern(D) shared static this()
+ {
+     initFPU(); // otherwise not guaranteed to be run before pure unittest below
+diff --git a/dmd/root/longdouble.h b/dmd/root/longdouble.h
+index 150b9f55e1..251be1d0f3 100644
+--- a/dmd/root/longdouble.h
++++ b/dmd/root/longdouble.h
+@@ -11,7 +11,13 @@
+ 
+ #pragma once
+ 
+-#if !_MSC_VER // has native 10 byte doubles
++#if defined(LDC_real_softfloat)
++#define USE_LONGDOUBLE_SOFTFLOAT 1
++#elif defined(_MSC_VER)
++#define USE_LONGDOUBLE_SOFTFLOAT 1
++#endif
++
++#if !USE_LONGDOUBLE_SOFTFLOAT // has native 10 byte doubles
+ #include <stdio.h>
+ typedef long double longdouble;
+ typedef volatile long double volatile_longdouble;
+diff --git a/dmd/statementsem.d b/dmd/statementsem.d
+index 987979bb88..a5be77972f 100644
+--- a/dmd/statementsem.d
++++ b/dmd/statementsem.d
+@@ -3695,6 +3695,13 @@ version (IN_LLVM)
+                 if (auto ls = s.isLabelStatement())
+                 {
+                     sc.func.searchLabel(ls.ident, ls.loc);
++
++version (IN_LLVM)
++{
++                    if (!sc.func.asmLabels)
++                        sc.func.asmLabels = new Identifiers();
++                    sc.func.asmLabels.push(ls.ident);
++}
+                 }
+             }
+         }
+diff --git a/driver/archiver.cpp b/driver/archiver.cpp
+index 14c91d810c..15725a947d 100644
+--- a/driver/archiver.cpp
++++ b/driver/archiver.cpp
+@@ -14,13 +14,8 @@
+ #include "driver/cl_options.h"
+ #include "driver/tool.h"
+ #include "gen/logger.h"
+-#if LDC_LLVM_VER < 1700
+-#include "llvm/ADT/Triple.h"
+-#include "llvm/Support/Host.h"
+-#else
+ #include "llvm/TargetParser/Host.h"
+ #include "llvm/TargetParser/Triple.h"
+-#endif
+ #include "llvm/Object/Archive.h"
+ #include "llvm/Object/ArchiveWriter.h"
+ #include "llvm/Object/MachO.h"
+@@ -45,11 +40,7 @@ namespace llvm_ar {
+ StringRef ArchiveName;
+ std::vector<const char *> Members;
+ 
+-#if LDC_LLVM_VER < 1800
+-bool Symtab = true;
+-#else
+ llvm::SymtabWritingMode Symtab = llvm::SymtabWritingMode::NormalSymtab;
+-#endif
+ bool Deterministic = true;
+ bool Thin = false;
+ 
+diff --git a/driver/args.cpp b/driver/args.cpp
+index 2485dab7dd..2ce4210a12 100644
+--- a/driver/args.cpp
++++ b/driver/args.cpp
+@@ -170,11 +170,7 @@ int executeAndWait(std::vector<const char *> fullArgs,
+   }
+ 
+   const std::vector<llvm::StringRef> argv = toRefsVector(fullArgs);
+-#if LDC_LLVM_VER < 1600
+-  auto envVars = llvm::None;
+-#else
+   auto envVars = std::nullopt;
+-#endif
+ 
+   return llvm::sys::ExecuteAndWait(argv[0], argv, envVars, {}, 0, 0, errorMsg);
+ }
+diff --git a/driver/cache.cpp b/driver/cache.cpp
+index 9e9278847d..2556ef2422 100644
+--- a/driver/cache.cpp
++++ b/driver/cache.cpp
+@@ -309,30 +309,14 @@ void outputIR2ObjRelevantCmdlineArgs(llvm::raw_ostream &hash_os) {
+   hash_os << opts::getFeaturesStr();
+   hash_os << opts::floatABI;
+   const auto relocModel = opts::getRelocModel();
+-#if LDC_LLVM_VER >= 1600
+   if (relocModel.has_value())
+     hash_os << relocModel.value();
+-#else
+-  if (relocModel.hasValue())
+-    hash_os << relocModel.getValue();
+-#endif
+   const auto codeModel = opts::getCodeModel();
+-#if LDC_LLVM_VER >= 1600
+   if (codeModel.has_value())
+     hash_os << codeModel.value();
+-#else
+-  if (codeModel.hasValue())
+-    hash_os << codeModel.getValue();
+-#endif
+-
+   const auto framePointerUsage = opts::framePointerUsage();
+-#if LDC_LLVM_VER >= 1600
+   if (framePointerUsage.has_value())
+     hash_os << static_cast<int>(framePointerUsage.value());
+-#else
+-  if (framePointerUsage.hasValue())
+-    hash_os << static_cast<int>(framePointerUsage.getValue());
+-#endif
+ }
+ 
+ // Output to `hash_os` all environment flags that influence object code output
+diff --git a/driver/cl_options-llvm.cpp b/driver/cl_options-llvm.cpp
+index 036e0bfa4f..168fab6adb 100644
+--- a/driver/cl_options-llvm.cpp
++++ b/driver/cl_options-llvm.cpp
+@@ -10,12 +10,8 @@
+ #include "driver/cl_options-llvm.h"
+ 
+ #if LDC_WITH_LLD
+-#if LDC_LLVM_VER < 1700
+-#include "llvm/ADT/Triple.h"
+-#else
+ #include "llvm/TargetParser/Triple.h"
+ #endif
+-#endif
+ 
+ // Pull in command-line options and helper functions from special LLVM header
+ // shared by multiple LLVM tools.
+@@ -35,17 +31,17 @@ std::string getArchStr() {
+   return codegen::getMArch();
+ }
+ 
+-Optional<Reloc::Model> getRelocModel() {
++std::optional<Reloc::Model> getRelocModel() {
+   return codegen::getExplicitRelocModel();
+ }
+ 
+-Optional<CodeModel::Model> getCodeModel() {
++std::optional<CodeModel::Model> getCodeModel() {
+   return codegen::getExplicitCodeModel();
+ }
+ 
+ using FPK = llvm::FramePointerKind;
+ 
+-llvm::Optional<FPK> framePointerUsage() {
++std::optional<FPK> framePointerUsage() {
+   // Defaults to `FP::None`; no way to check if set explicitly by user except
+   // indirectly via setFunctionAttributes()...
+   return codegen::getFramePointerUsage();
+@@ -89,11 +85,11 @@ TargetOptions initTargetOptionsFromCodeGenFlags() {
+   return ::opts::InitTargetOptionsFromCodeGenFlags(llvm::Triple());
+ }
+ 
+-Optional<Reloc::Model> getRelocModelFromCMModel() {
++std::optional<Reloc::Model> getRelocModelFromCMModel() {
+   return ::opts::getRelocModel();
+ }
+ 
+-Optional<CodeModel::Model> getCodeModelFromCMModel() {
++std::optional<CodeModel::Model> getCodeModelFromCMModel() {
+   return ::opts::getCodeModel();
+ }
+ 
+diff --git a/driver/cl_options-llvm.h b/driver/cl_options-llvm.h
+index 2f39b2401a..69b54359dc 100644
+--- a/driver/cl_options-llvm.h
++++ b/driver/cl_options-llvm.h
+@@ -9,17 +9,10 @@
+ 
+ #pragma once
+ 
+-#if LDC_LLVM_VER < 1700
+-#include "llvm/ADT/Optional.h"
+-#else
+-#include <optional>
+-namespace llvm {
+-template <typename T> using Optional = std::optional<T>;
+-}
+-#endif
+ #include "llvm/Support/CommandLine.h"
+ #include "llvm/Support/CodeGen.h"
+ #include "llvm/Target/TargetOptions.h"
++#include <optional>
+ 
+ namespace llvm {
+ class Function;
+@@ -29,9 +22,9 @@ class Triple;
+ namespace opts {
+ 
+ std::string getArchStr();
+-llvm::Optional<llvm::Reloc::Model> getRelocModel();
+-llvm::Optional<llvm::CodeModel::Model> getCodeModel();
+-llvm::Optional<llvm::FramePointerKind> framePointerUsage();
++std::optional<llvm::Reloc::Model> getRelocModel();
++std::optional<llvm::CodeModel::Model> getCodeModel();
++std::optional<llvm::FramePointerKind> framePointerUsage();
+ 
+ bool disableRedZone();
+ bool printTargetFeaturesHelp();
+diff --git a/driver/cl_options.cpp b/driver/cl_options.cpp
+index 5b155a6797..c101cf199c 100644
+--- a/driver/cl_options.cpp
++++ b/driver/cl_options.cpp
+@@ -20,6 +20,12 @@ namespace opts {
+ // This vector is filled by parseCommandLine in main.cpp.
+ llvm::SmallVector<const char *, 32> allArguments;
+ 
++cl::opt<bool> noBuiltIn(
++    "fno-builtin",
++    cl::desc("Disable implicit builtin knowledge of functions"),
++    cl::ZeroOrMore
++);
++
+ cl::OptionCategory linkingCategory("Linking options");
+ 
+ // Positional options first, in order:
+@@ -577,7 +583,7 @@ llvm::FastMathFlags defaultFMF;
+ void setDefaultMathOptions(llvm::TargetOptions &targetOptions) {
+   if (fFastMath) {
+     defaultFMF.setFast();
+-#if LDC_LLVM_VER < 2200
++#if LLVM_VERSION_MAJOR < 22
+     targetOptions.UnsafeFPMath = true;
+ #endif
+   }
+@@ -788,12 +794,15 @@ static cl::extrahelp
+ /// The clashing LLVM options are suffixed with "llvm-" and hidden from the
+ /// -help output.
+ void createClashingOptions() {
+-  llvm::StringMap<cl::Option *> &map = cl::getRegisteredOptions();
+-
++  auto &map = cl::getRegisteredOptions();
+   auto renameAndHide = [&map](const char *from, const char *to) {
+     auto i = map.find(from);
+     if (i != map.end()) {
++#if LLVM_VERSION_MAJOR >= 22
++      cl::Option *opt = i->second;
++#else
+       cl::Option *opt = i->getValue();
++#endif
+       map.erase(i);
+       if (to) {
+         opt->setArgStr(to);
+@@ -847,7 +856,8 @@ void hideLLVMOptions() {
+       "amdgpu-enable-global-sgpr-addr", "amdgpu-enable-merge-m0",
+       "amdgpu-enable-power-sched", "amdgpu-igrouplp",
+       "amdgpu-indirect-call-specialization-threshold",
+-      "amdgpu-kernarg-preload-count", "amdgpu-module-splitting-max-depth",
++      "amdgpu-kernarg-preload", "amdgpu-kernarg-preload-count",
++      "amdgpu-module-splitting-max-depth",
+       "amdgpu-promote-alloca-to-vector-limit",
+       "amdgpu-promote-alloca-to-vector-max-regs",
+       "amdgpu-promote-alloca-to-vector-vgpr-ratio",
+@@ -857,9 +867,11 @@ void hideLLVMOptions() {
+       "argext-abi-check", "arm-add-build-attributes",
+       "arm-implicit-it", "asm-instrumentation", "asm-show-inst",
+       "atomic-counter-update-promoted", "atomic-first-counter",
+-      "basic-block-address-map", "basic-block-sections",
++      "basic-block-address-map", "basic-block-section-match-infer",
++      "basic-block-sections",
+       "basicblock-sections", "bounds-checking-single-trap",
+-      "bounds-checking-unique-traps", "bpf-stack-size", "cfg-hide-cold-paths",
++      "bounds-checking-unique-traps", "bpf-stack-size",
++      "call-graph-section", "cfg-hide-cold-paths",
+       "cfg-hide-deoptimize-paths", "cfg-hide-unreachable-paths",
+       "check-functions-filter", "code-model", "conditional-counter-update",
+       "cost-kind", "cppfname", "cppfor", "cppgen", "crel",
+@@ -867,18 +879,21 @@ void hideLLVMOptions() {
+       "cvp-dont-process-adds", "debug-counter", "debug-entry-values",
+       "debugger-tune", "debugify-atoms", "debugify-func-limit",
+       "debugify-level", "debugify-quiet", "debug-info-correlate",
+-      "denormal-fp-math", "denormal-fp-math-f32",
++      "denormal-fp-math", "denormal-fp-math-f32", "devirtualize-speculatively",
+       "disable-auto-upgrade-debug-info", "disable-debug-info-verifier",
+       "disable-i2p-p2i-opt",
+       "disable-objc-arc-checkforcfghazards", "disable-promote-alloca-to-lds",
+-      "disable-promote-alloca-to-vector", "disable-slp-vectorization",
++      "disable-promote-alloca-to-vector", "disable-qfp-opt",
++      "disable-qfp-opt-mul", "disable-slp-vectorization",
+       "disable-spill-fusing",
+-      "do-counter-promotion", "dot-cfg-mssa", "dwarf64", "emit-call-site-info",
++      "do-counter-promotion", "dot-cfg-mssa", "dwarf64",
++      "elide-all-zero-branch-weights", "emit-bb-hash", "emit-call-site-info",
+       "emit-compact-unwind-non-canonical", "emit-dwarf-unwind",
+       "emit-gnuas-syntax-on-zos", "emscripten-cxx-exceptions-allowed",
+       "emscripten-cxx-exceptions-whitelist",
+       "emulated-tls", "enable-approx-func-fp-math", "enable-correct-eh-support",
+       "enable-cse-in-irtranslator", "enable-cse-in-legalizer",
++      "enable-devirtualize-speculatively",
+       "enable-emscripten-cxx-exceptions", "enable-emscripten-sjlj",
+       "enable-fp-mad", "enable-gvn-hoist", "enable-gvn-memdep",
+       "enable-gvn-memoryssa",
+@@ -889,7 +904,8 @@ void hideLLVMOptions() {
+       "enable-no-nans-fp-math", "enable-no-signed-zeros-fp-math",
+       "enable-no-trapping-fp-math", "enable-objc-arc-annotations",
+       "enable-objc-arc-opts", "enable-pgo-force-function-attrs",
+-      "enable-pie", "enable-scoped-noalias",
++      "enable-pie", "enable-poison-reuse-guard",
++      "enable-scoped-noalias",
+       "enable-split-backedge-in-load-pre", "enable-split-loopiv-heuristic",
+       "enable-tbaa", "enable-tlsdesc", "enable-unsafe-fp-math",
+       "enable-vtable-profile-use", "enable-vtable-value-profiling",
+@@ -901,8 +917,8 @@ void hideLLVMOptions() {
+       "force-dwarf-frame-section", "force-opaque-pointers",
+       "force-tail-folding-style",
+       "fs-profile-debug-bw-threshold", "fs-profile-debug-prob-diff-threshold",
+-      "generate-merged-base-profiles",
+-      "gpsize", "hash-based-counter-split", "hexagon-add-build-attributes",
++      "generate-merged-base-profiles", "gpsize", "gsframe",
++      "hash-based-counter-split", "hexagon-add-build-attributes",
+       "hexagon-rdf-limit", "hot-cold-split", "hwasan-percentile-cutoff-hot",
+       "hwasan-random-rate", "ignore-xcoff-visibility",
+       "imp-null-check-page-size", "imp-null-max-insts-to-consider",
+@@ -914,7 +930,8 @@ void hideLLVMOptions() {
+       "instcombine-negator-max-depth", "instcombine-unsafe-select-transform",
+       "instrprof-atomic-counter-update-all", "internalize-public-api-file",
+       "internalize-public-api-list", "intrinsic-cost-strategy",
+-      "ir2vec-arg-weight", "ir2vec-opc-weight", "ir2vec-type-weight",
++      "ir2vec-arg-weight", "ir2vec-kind", "ir2vec-opc-weight",
++      "ir2vec-type-weight",
+       "ir2vec-vocab-path", "iterative-counter-promotion",
+       "join-liveintervals", "jump-table-type", "large-data-threshold",
+       "limit-float-precision", "lint-abort-on-error", "loongarch-use-aa",
+@@ -927,7 +944,10 @@ void hideLLVMOptions() {
+       "memop-size-large", "memop-size-range", "merror-missing-parenthesis",
+       "merror-noncontigious-register", "mfuture-regs", "mhvx",
+       "mips-compact-branches", "mips16-constant-islands", "mips16-hard-float",
+-      "mir-strip-debugify-only", "misexpect-tolerance", "mlsm", "mno-compound",
++      "mir-strip-debugify-only",
++      "mir2vec-common-operand-weight", "mir2vec-kind", "mir2vec-opc-weight",
++      "mir2vec-reg-operand-weight", "mir2vec-vocab-path",
++      "misexpect-tolerance", "mlsm", "mno-compound",
+       "mno-fixup", "mno-ldc1-sdc1", "mno-pairing",
+       "ms-secure-hotpatch-functions-file", "ms-secure-hotpatch-functions-list",
+       "mwarn-missing-parenthesis",
+@@ -943,10 +963,15 @@ void hideLLVMOptions() {
+       "polly-dump-after", "polly-dump-after-file", "polly-dump-before",
+       "polly-dump-before-file", "pre-RA-sched", "print-after-all",
+       "print-before-all", "print-machineinstrs", "print-module-scope",
+-      "print-pipeline-passes", "profile-correlate",
++      "print-pipeline-passes",
++      "profcheck-annotate-select", "profcheck-default-function-entry-count",
++      "profcheck-default-select-false-weight",
++      "profcheck-default-select-true-weight", "profcheck-weights-for-test",
++      "profile-correlate",
+       "profile-estimator-loop-weight", "profile-estimator-loop-weight",
+       "profile-file", "profile-info-file", "profile-verifier-noassert",
+-      "promote-alloca-vector-loop-user-weight", "pseudo-probe-for-profiling",
++      "promote-alloca-vector-loop-user-weight", "propeller-infer-threshold",
++      "pseudo-probe-for-profiling",
+       "r600-ir-structurize", "rdf-dump", "rdf-limit", "recip", "regalloc",
+       "relax-elf-relocations", "remarks-section", "rewrite-map-file",
+       "riscv-add-build-attributes", "riscv-use-aa", "rng-seed",
+@@ -961,11 +986,12 @@ void hideLLVMOptions() {
+       "skip-ret-exit-block",
+       "speculative-counter-promotion-max-exiting",
+       "speculative-counter-promotion-to-loop", "spiller", "spirv-debug",
++      "spirv-emit-op-names",
+       "spirv-erase-cl-md", "spirv-ext", "spirv-lower-const-expr",
+       "spirv-mem2reg",
+       "spirv-no-deref-attr", "spirv-text", "spirv-verify-regularize-passes",
+-      "split-machine-functions", "spv-dump-deps",
+-      "spv-emit-nonsemantic-debug-info",
++      "split-machine-functions", "spv-allow-unknown-intrinsics",
++      "spv-dump-deps", "spv-emit-nonsemantic-debug-info",
+       "spv-lower-saddwithoverflow-validate", "spvbool-validate",
+       "spvmemmove-validate", "stack-alignment", "stack-protector-guard",
+       "stack-protector-guard-offset", "stack-protector-guard-reg",
+@@ -1016,7 +1042,8 @@ void hideLLVMOptions() {
+                                                "trap-func",
+                                                "W"};
+ 
+-  llvm::StringMap<cl::Option *> &map = cl::getRegisteredOptions();
++  auto &map = cl::getRegisteredOptions();
++
+   for (const auto name : hiddenOptions) {
+     // Check if option exists first for resilience against LLVM changes
+     // between versions.
+diff --git a/driver/cl_options.h b/driver/cl_options.h
+index 964874bcce..481b83adcc 100644
+--- a/driver/cl_options.h
++++ b/driver/cl_options.h
+@@ -41,6 +41,7 @@ extern cl::OptionCategory linkingCategory;
+    egrep -e '^(cl::|#if|#e)' gen/cl_options.cpp \
+     | sed -re 's/^(cl::.*)\(.*$/    extern \1;/'
+  */
++extern cl::opt<bool> noBuiltIn;
+ extern cl::list<std::string> fileList;
+ extern cl::list<std::string> runargs;
+ extern cl::opt<bool> invokedByLDMD;
+diff --git a/driver/cl_options_instrumentation.cpp b/driver/cl_options_instrumentation.cpp
+index 325f71383d..c8c0a07a77 100644
+--- a/driver/cl_options_instrumentation.cpp
++++ b/driver/cl_options_instrumentation.cpp
+@@ -18,11 +18,7 @@
+ #include "dmd/errors.h"
+ #include "dmd/globals.h"
+ #include "gen/to_string.h"
+-#if LDC_LLVM_VER < 1700
+-#include "llvm/ADT/Triple.h"
+-#else
+ #include "llvm/TargetParser/Triple.h"
+-#endif
+ 
+ namespace {
+ namespace cl = llvm::cl;
+diff --git a/driver/cl_options_sanitizers.h b/driver/cl_options_sanitizers.h
+index e1eaa3c934..423322e7fd 100644
+--- a/driver/cl_options_sanitizers.h
++++ b/driver/cl_options_sanitizers.h
+@@ -14,7 +14,7 @@
+ #pragma once
+ 
+ #include "driver/cl_helpers.h"
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+ #include "llvm/Transforms/Utils/Instrumentation.h"
+ #else
+ #include "llvm/Transforms/Instrumentation.h"
+diff --git a/driver/codegenerator.cpp b/driver/codegenerator.cpp
+index 04fdeee4a6..6ec8044eb4 100644
+--- a/driver/codegenerator.cpp
++++ b/driver/codegenerator.cpp
+@@ -37,7 +37,7 @@
+ #include "mlir/IR/MLIRContext.h"
+ #endif
+ 
+-#if LDC_LLVM_VER < 2200
++#if LLVM_VERSION_MAJOR < 22
+ namespace llvm {
+   using LLVMRemarkFileHandle = std::unique_ptr<llvm::ToolOutputFile>;
+ }
+@@ -218,7 +218,7 @@ void CodeGenerator::prepareLLModule(Module *m) {
+   // name, as it should not collide with a symbol name used somewhere in the
+   // module.
+   ir_ = new IRState(m->srcfile.toChars(), context_);
+-#if LDC_LLVM_VER >= 2100
++#if LLVM_VERSION_MAJOR >= 21
+   ir_->module.setTargetTriple(*global.params.targetTriple);
+ #else
+   ir_->module.setTargetTriple(global.params.targetTriple->str());
+diff --git a/driver/cpreprocessor.cpp b/driver/cpreprocessor.cpp
+index fff0e5d223..d4b579d99e 100644
+--- a/driver/cpreprocessor.cpp
++++ b/driver/cpreprocessor.cpp
+@@ -99,7 +99,6 @@ FileName runCPreprocessor(FileName csrcfile, Loc loc, OutBuffer &defines) {
+       // propagate the target to the preprocessor
+       args.push_back("--target=" + triple.getTriple());
+ 
+-#if LDC_LLVM_VER >= 1800 // getAllProcessorFeatures was introduced in this version
+       // propagate all enabled/disabled features to the preprocessor
+       const auto &subTarget = gTargetMachine->getMCSubtargetInfo();
+       const auto &featureBits = subTarget->getFeatureBits();
+@@ -114,7 +113,6 @@ FileName runCPreprocessor(FileName csrcfile, Loc loc, OutBuffer &defines) {
+         args.push_back(featureString.str().str());
+         featureString.clear();
+       }
+-#endif
+ 
+       // print macro definitions (clang-cl doesn't support /PD - use clang's
+       // -dD)
+diff --git a/driver/linker-gcc.cpp b/driver/linker-gcc.cpp
+index d3ad82b346..93cb97a492 100644
+--- a/driver/linker-gcc.cpp
++++ b/driver/linker-gcc.cpp
+@@ -27,14 +27,12 @@
+ 
+ #if LDC_WITH_LLD
+ #include "lld/Common/Driver.h"
+-#if LDC_LLVM_VER >= 1700
+ LLD_HAS_DRIVER(coff)
+ LLD_HAS_DRIVER(elf)
+ LLD_HAS_DRIVER(mingw)
+ LLD_HAS_DRIVER(macho)
+ LLD_HAS_DRIVER(wasm)
+ #endif
+-#endif
+ 
+ //////////////////////////////////////////////////////////////////////////////
+ 
+@@ -161,11 +159,6 @@ void ArgsBuilder::addLTOGoldPluginFlags(bool requirePlugin) {
+     addLdFlag("-plugin-opt=-function-sections");
+   if (TO.DataSections)
+     addLdFlag("-plugin-opt=-data-sections");
+-
+-#if LDC_LLVM_VER >= 1600 && LDC_LLVM_VER < 1700
+-  // LLVM 16: disable function specializations by default
+-  addLdFlag("-plugin-opt=-func-specialization-size-threshold=1000000000");
+-#endif
+ }
+ 
+ // Returns an empty string when libLTO.dylib was not specified nor found.
+@@ -196,11 +189,6 @@ void ArgsBuilder::addDarwinLTOFlags() {
+   std::string dylibPath = getLTOdylibPath();
+   if (!dylibPath.empty()) {
+     addLdFlag("-lto_library", dylibPath);
+-
+-#if LDC_LLVM_VER >= 1600 && LDC_LLVM_VER < 1700
+-    // LLVM 16: disable function specializations by default
+-    addLdFlag("-mllvm", "-func-specialization-size-threshold=1000000000");
+-#endif
+   }
+ }
+ 
+@@ -624,20 +612,11 @@ void ArgsBuilder::build(llvm::StringRef outputPath,
+   }
+ 
+   const auto explicitPlatformLibs = getExplicitPlatformLibs();
+-#if LDC_LLVM_VER >= 1600
+   if (explicitPlatformLibs.has_value()) {
+     for (const auto &name : explicitPlatformLibs.value()) {
+       args.push_back("-l" + name);
+     }
+-  }
+-#else
+-  if (explicitPlatformLibs.hasValue()) {
+-    for (const auto &name : explicitPlatformLibs.getValue()) {
+-      args.push_back("-l" + name);
+-    }
+-  }
+-#endif
+-  else {
++  } else {
+     addDefaultPlatformLibs();
+   }
+ 
+@@ -656,10 +635,6 @@ void ArgsBuilder::addLinker() {
+ //////////////////////////////////////////////////////////////////////////////
+ 
+ void ArgsBuilder::addUserSwitches() {
+-#if LDC_LLVM_VER >= 1800
+-  #define startswith starts_with
+-#endif
+-
+   // additional linker and cc switches (preserve order across both lists)
+   for (unsigned ilink = 0, icc = 0;;) {
+     unsigned linkpos = ilink < opts::linkerSwitches.size()
+@@ -676,9 +651,9 @@ void ArgsBuilder::addUserSwitches() {
+       // Options starting with `-Wl,`, -shared or -static are not handled by
+       // the linker and must be passed to the driver.
+       auto str = llvm::StringRef(p);
+-      if (!(str.startswith("-l") || str.startswith("-L") ||
+-            str.startswith("-Wl,") || str.startswith("-shared") ||
+-            str.startswith("-static"))) {
++      if (!(str.starts_with("-l") || str.starts_with("-L") ||
++            str.starts_with("-Wl,") || str.starts_with("-shared") ||
++            str.starts_with("-static"))) {
+         args.push_back("-Xlinker");
+       }
+       args.push_back(p);
+@@ -688,10 +663,6 @@ void ArgsBuilder::addUserSwitches() {
+       break;
+     }
+   }
+-
+-#if LDC_LLVM_VER >= 1800
+-  #undef startswith
+-#endif
+ }
+ 
+ //////////////////////////////////////////////////////////////////////////////
+diff --git a/driver/linker-msvc.cpp b/driver/linker-msvc.cpp
+index ad86ebabdf..931ccc3bb5 100644
+--- a/driver/linker-msvc.cpp
++++ b/driver/linker-msvc.cpp
+@@ -22,10 +22,8 @@
+ 
+ #if LDC_WITH_LLD
+ #include "lld/Common/Driver.h"
+-#if LDC_LLVM_VER >= 1700
+ LLD_HAS_DRIVER(coff)
+ #endif
+-#endif
+ 
+ //////////////////////////////////////////////////////////////////////////////
+ 
+@@ -39,22 +37,15 @@ void addMscrtLibs(bool useInternalToolchain, std::vector<std::string> &args) {
+   // We need the vcruntime lib for druntime's exception handling (ldc.eh_msvc).
+   // Pick one of the 4 variants matching the selected main UCRT lib.
+ 
+-#if LDC_LLVM_VER >= 1700
+-#define contains_lower contains_insensitive
+-#define endswith_lower ends_with_insensitive
+-#else
+-#define contains_lower contains_insensitive
+-#define endswith_lower endswith_insensitive
+-#endif
+   if (useInternalToolchain) {
+-    assert(mscrtlibName.contains_lower("vcruntime"));
++    assert(mscrtlibName.contains_insensitive("vcruntime"));
+     return;
+   }
+ 
+-  const bool isStatic = mscrtlibName.contains_lower("libcmt");
++  const bool isStatic = mscrtlibName.contains_insensitive("libcmt");
+ 
+-  const bool isDebug =
+-      mscrtlibName.endswith_lower("d") || mscrtlibName.endswith_lower("d.lib");
++  const bool isDebug = mscrtlibName.ends_with_insensitive("d") ||
++                       mscrtlibName.ends_with_insensitive("d.lib");
+ 
+   const llvm::StringRef prefix = isStatic ? "lib" : "";
+   const llvm::StringRef suffix = isDebug ? "d" : "";
+@@ -77,9 +68,9 @@ void addSanitizerLibs(bool useInternalToolchain,
+                       std::vector<std::string> &args) {
+   if (opts::isSanitizerEnabled(opts::AddressSanitizer)) {
+     args.push_back("ldc_rt.asan.lib");
+-#if LDC_LLVM_VER >= 2000 // extra library since LLVM 20
++#if LLVM_VERSION_MAJOR >= 20 // extra library since LLVM 20
+     const bool linkStaticCRT =
+-        getMscrtLibName(&useInternalToolchain).contains_lower("libcmt");
++        getMscrtLibName(&useInternalToolchain).contains_insensitive("libcmt");
+     args.push_back((llvm::Twine("ldc_rt.asan_") +
+                     (linkStaticCRT ? "static" : "dynamic") +
+                     "_runtime_thunk.lib")
+@@ -108,7 +99,8 @@ int linkObjToBinaryMSVC(llvm::StringRef outputPath,
+ 
+   const bool forceMSVC = env::has(L"LDC_VSDIR_FORCE");
+   const bool useInternalToolchain =
+-      (!forceMSVC && getExplicitMscrtLibName().contains_lower("vcruntime")) ||
++      (!forceMSVC &&
++       getExplicitMscrtLibName().contains_insensitive("vcruntime")) ||
+       !msvcEnv.setup();
+ 
+   if (forceMSVC && useInternalToolchain) {
+@@ -243,22 +235,12 @@ int linkObjToBinaryMSVC(llvm::StringRef outputPath,
+   }
+ 
+   auto explicitPlatformLibs = getExplicitPlatformLibs();
+-#if LDC_LLVM_VER >= 1600
+   if (explicitPlatformLibs.has_value()) {
+     for (auto &lib : explicitPlatformLibs.value()) {
+       args.push_back(llvm::sys::path::has_extension(lib) ? std::move(lib)
+                                                          : lib + ".lib");
+     }
+-  }
+-#else
+-  if (explicitPlatformLibs.hasValue()) {
+-    for (auto &lib : explicitPlatformLibs.getValue()) {
+-      args.push_back(llvm::sys::path::has_extension(lib) ? std::move(lib)
+-                                                         : lib + ".lib");
+-    }
+-  }
+-#endif
+-  else {
++  } else {
+     // default platform libs
+     // TODO check which libaries are necessary
+     args.insert(args.end(),
+diff --git a/driver/linker.cpp b/driver/linker.cpp
+index 65e0a099e0..44495896ee 100644
+--- a/driver/linker.cpp
++++ b/driver/linker.cpp
+@@ -192,14 +192,10 @@ static std::vector<std::string> getDefaultLibNames() {
+ 
+ //////////////////////////////////////////////////////////////////////////////
+ 
+-llvm::Optional<std::vector<std::string>> getExplicitPlatformLibs() {
++std::optional<std::vector<std::string>> getExplicitPlatformLibs() {
+   if (platformLib.getNumOccurrences() > 0)
+     return parseLibNames(platformLib);
+-#if LDC_LLVM_VER < 1600
+-  return llvm::None;
+-#else
+   return std::nullopt;
+-#endif
+ }
+ 
+ //////////////////////////////////////////////////////////////////////////////
+diff --git a/driver/linker.h b/driver/linker.h
+index 28362d8652..4f584f8359 100644
+--- a/driver/linker.h
++++ b/driver/linker.h
+@@ -42,11 +42,7 @@ bool linkAgainstSharedDefaultLibs();
+ /**
+  * Returns the -platformlib library names, if specified.
+  */
+-#if LDC_LLVM_VER < 1600
+-llvm::Optional<std::vector<std::string>> getExplicitPlatformLibs();
+-#else
+ std::optional<std::vector<std::string>> getExplicitPlatformLibs();
+-#endif
+ 
+ /**
+  * Returns the value of -mscrtlib.
+diff --git a/driver/main.cpp b/driver/main.cpp
+index e1619dc0cb..df3637f55c 100644
+--- a/driver/main.cpp
++++ b/driver/main.cpp
+@@ -57,11 +57,7 @@
+ #include "llvm/LinkAllIR.h"
+ #include "llvm/LinkAllPasses.h"
+ #include "llvm/Support/FileSystem.h"
+-#if LDC_LLVM_VER >= 1700
+ #include "llvm/TargetParser/Host.h"
+-#else
+-#include "llvm/Support/Host.h"
+-#endif
+ #include "llvm/Support/ManagedStatic.h"
+ #include "llvm/Support/Path.h"
+ #include "llvm/Support/StringSaver.h"
+@@ -189,17 +185,13 @@ void tryParse(const llvm::SmallVectorImpl<const char *> &args, size_t i,
+ }
+ 
+ bool tryParseLowmem(const llvm::SmallVectorImpl<const char *> &args) {
+-#if LDC_LLVM_VER >= 1800
+-  #define startswith starts_with
+-#endif
+-
+   bool lowmem = false;
+   for (size_t i = 1; i < args.size(); ++i) {
+     if (args::isRunArg(args[i]))
+       break;
+ 
+     llvm::StringRef arg = args[i];
+-    if (arg.startswith("-lowmem") || arg.startswith("--lowmem")) {
++    if (arg.starts_with("-lowmem") || arg.starts_with("--lowmem")) {
+       auto remainder = arg.substr(arg[1] == '-' ? 8 : 7);
+       if (remainder.empty()) {
+         lowmem = true;
+@@ -210,10 +202,6 @@ bool tryParseLowmem(const llvm::SmallVectorImpl<const char *> &args) {
+     }
+   }
+   return lowmem;
+-
+-#if LDC_LLVM_VER >= 1800
+-  #undef startswith
+-#endif
+ }
+ 
+ const char *
+@@ -322,7 +310,11 @@ void parseCommandLine(Strings &sourceFiles) {
+     if (auto target = lookupTarget("", triple, errMsg)) {
+       llvm::errs() << "Targeting " << target->getName() << ". ";
+       // this prints the available CPUs and features of the target to stderr...
++#if LLVM_VERSION_MAJOR >= 22
++      target->createMCSubtargetInfo(triple, "help", "");
++#else
+       target->createMCSubtargetInfo(cfg_triple, "help", "");
++#endif
+     } else {
+       error(Loc(), "%s", errMsg.c_str());
+       fatal();
+@@ -574,13 +566,6 @@ void parseCommandLine(Strings &sourceFiles) {
+ 
+   global.params.dihdr.fullOutput = opts::hdrKeepAllBodies;
+   global.params.disableRedZone = opts::disableRedZone();
+-
+-  // enforce opaque IR pointers
+-#if LDC_LLVM_VER >= 1700
+-  // supports opaque IR pointers only
+-#else
+-  getGlobalContext().setOpaquePointers(true);
+-#endif
+ }
+ 
+ void initializePasses() {
+@@ -590,28 +575,13 @@ void initializePasses() {
+   initializeCore(Registry);
+   initializeTransformUtils(Registry);
+   initializeScalarOpts(Registry);
+-#if LDC_LLVM_VER < 1600
+-  initializeObjCARCOpts(Registry);
+-#endif
+   initializeVectorization(Registry);
+   initializeInstCombine(Registry);
+-#if LDC_LLVM_VER < 1600
+-  initializeAggressiveInstCombine(Registry);
+-#endif
+   initializeIPO(Registry);
+-#if LDC_LLVM_VER < 1600
+-  initializeInstrumentation(Registry);
+-#endif
+   initializeAnalysis(Registry);
+   initializeCodeGen(Registry);
+   initializeGlobalISel(Registry);
+   initializeTarget(Registry);
+-
+-#if LDC_LLVM_VER < 1700
+-// Initialize passes not included above
+-  initializeRewriteSymbolsLegacyPassPass(Registry);
+-  initializeSjLjEHPreparePass(Registry);
+-#endif
+ }
+ 
+ /// Register the MIPS ABI.
+@@ -774,7 +744,6 @@ void registerPredefinedTargetVersions() {
+   case llvm::Triple::wasm64:
+     VersionCondition::addPredefinedGlobalIdent("WebAssembly");
+     break;
+-#if LDC_LLVM_VER >= 1600
+   case llvm::Triple::loongarch32:
+     VersionCondition::addPredefinedGlobalIdent("LoongArch32");
+     registerPredefinedFloatABI("LoongArch_SoftFloat", "LoongArch_HardFloat");
+@@ -786,7 +755,6 @@ void registerPredefinedTargetVersions() {
+   case llvm::Triple::xtensa:
+     VersionCondition::addPredefinedGlobalIdent("Xtensa");
+     break;
+-#endif // LDC_LLVM_VER >= 1600
+   default:
+     warning(Loc(), "unknown target CPU architecture: %s",
+             triple.getArchName().str().c_str());
+@@ -1064,7 +1032,7 @@ void registerPredefinedVersions() {
+ // Expose LLVM version to runtime
+ #define STR(x) #x
+ #define XSTR(x) STR(x)
+-  VersionCondition::addPredefinedGlobalIdent("LDC_LLVM_" XSTR(LDC_LLVM_VER));
++  VersionCondition::addPredefinedGlobalIdent("LDC_LLVM_" XSTR(LLVM_VERSION_MAJOR));
+ #undef XSTR
+ #undef STR
+ }
+@@ -1181,15 +1149,9 @@ int cppmain() {
+   }
+ 
+   auto relocModel = getRelocModel();
+-#if LDC_LLVM_VER >= 1600
+   if (global.params.dll && !relocModel.has_value()) {
+     relocModel = llvm::Reloc::PIC_;
+   }
+-#else
+-  if (global.params.dll && !relocModel.hasValue()) {
+-    relocModel = llvm::Reloc::PIC_;
+-  }
+-#endif
+ 
+   fixupTripleEnv(mTargetTriple);
+ 
+diff --git a/driver/plugins.cpp b/driver/plugins.cpp
+index 1356b4c5fd..b6baa8dfba 100644
+--- a/driver/plugins.cpp
++++ b/driver/plugins.cpp
+@@ -25,7 +25,11 @@
+ #include "llvm/Support/CommandLine.h"
+ #include "llvm/Support/DynamicLibrary.h"
+ #include "llvm/ADT/SmallVector.h"
++#if LLVM_VERSION_MAJOR >= 22
++#include "llvm/Plugins/PassPlugin.h"
++#else
+ #include "llvm/Passes/PassPlugin.h"
++#endif
+ #include "llvm/Support/Error.h"
+ 
+ #include "driver/cl_options.h"
+diff --git a/driver/targetmachine.cpp b/driver/targetmachine.cpp
+index 0afc279bcc..6f85efd660 100644
+--- a/driver/targetmachine.cpp
++++ b/driver/targetmachine.cpp
+@@ -21,21 +21,12 @@
+ #include "gen/logger.h"
+ #include "llvm/ADT/StringExtras.h"
+ #include "llvm/ADT/StringSwitch.h"
+-#if LDC_LLVM_VER < 1700
+-#include "llvm/ADT/Triple.h"
+-#include "llvm/MC/SubtargetFeature.h"
+-#include "llvm/Support/Host.h"
+-#include "llvm/Support/TargetParser.h"
+-#include "llvm/Support/AArch64TargetParser.h"
+-#include "llvm/Support/ARMTargetParser.h"
+-#else
+ #include "llvm/TargetParser/AArch64TargetParser.h"
+ #include "llvm/TargetParser/ARMTargetParser.h"
+ #include "llvm/TargetParser/Host.h"
+ #include "llvm/TargetParser/SubtargetFeature.h"
+ #include "llvm/TargetParser/TargetParser.h"
+ #include "llvm/TargetParser/Triple.h"
+-#endif
+ #include "llvm/IR/Module.h"
+ #include "llvm/MC/MCObjectFileInfo.h"
+ #include "llvm/Support/CommandLine.h"
+@@ -46,10 +37,6 @@
+ 
+ #include "gen/optimizer.h"
+ 
+-#if LDC_LLVM_VER >= 1800
+-#define startswith starts_with
+-#endif
+-
+ #ifdef LDC_LLVM_SUPPORTS_MACHO_DWARF_LINE_AS_REGULAR_SECTION
+ // LDC-LLVM >= 20:
+ // On Darwin, emit __debug_line section in __DWARF segment as regular
+@@ -87,65 +74,63 @@ const char *getABI(const llvm::Triple &triple, const llvm::SmallVectorImpl<llvm:
+     switch (triple.getArch()) {
+     case llvm::Triple::arm:
+     case llvm::Triple::armeb:
+-      if (ABIName.startswith("aapcs"))
++      if (ABIName.starts_with("aapcs"))
+         return "aapcs";
+-      if (ABIName.startswith("eabi"))
++      if (ABIName.starts_with("eabi"))
+         return "apcs";
+       break;
+     case llvm::Triple::mips:
+     case llvm::Triple::mipsel:
+     case llvm::Triple::mips64:
+     case llvm::Triple::mips64el:
+-      if (ABIName.startswith("o32"))
++      if (ABIName.starts_with("o32"))
+         return "o32";
+-      if (ABIName.startswith("n32"))
++      if (ABIName.starts_with("n32"))
+         return "n32";
+-      if (ABIName.startswith("n64"))
++      if (ABIName.starts_with("n64"))
+         return "n64";
+-      if (ABIName.startswith("eabi"))
++      if (ABIName.starts_with("eabi"))
+         return "eabi";
+       break;
+     case llvm::Triple::ppc64:
+     case llvm::Triple::ppc64le:
+-      if (ABIName.startswith("elfv1"))
++      if (ABIName.starts_with("elfv1"))
+         return "elfv1";
+-      if (ABIName.startswith("elfv2"))
++      if (ABIName.starts_with("elfv2"))
+         return "elfv2";
+       break;
+     case llvm::Triple::riscv64:
+-      if (ABIName.startswith("lp64f"))
++      if (ABIName.starts_with("lp64f"))
+         return "lp64f";
+-      if (ABIName.startswith("lp64d"))
++      if (ABIName.starts_with("lp64d"))
+         return "lp64d";
+-      if (ABIName.startswith("lp64"))
++      if (ABIName.starts_with("lp64"))
+         return "lp64";
+       break;
+     case llvm::Triple::riscv32:
+-      if (ABIName.startswith("ilp32f"))
++      if (ABIName.starts_with("ilp32f"))
+         return "ilp32f";
+-      if (ABIName.startswith("ilp32d"))
++      if (ABIName.starts_with("ilp32d"))
+         return "ilp32d";
+-      if (ABIName.startswith("ilp32"))
++      if (ABIName.starts_with("ilp32"))
+         return "ilp32";
+       break;
+-#if LDC_LLVM_VER >= 1600
+     case llvm::Triple::loongarch32:
+-      if (ABIName.startswith("ilp32s"))
++      if (ABIName.starts_with("ilp32s"))
+         return "ilp32s";
+-      if (ABIName.startswith("ilp32f"))
++      if (ABIName.starts_with("ilp32f"))
+         return "ilp32f";
+-      if (ABIName.startswith("ilp32d"))
++      if (ABIName.starts_with("ilp32d"))
+         return "ilp32d";
+       break;
+     case llvm::Triple::loongarch64:
+-      if (ABIName.startswith("lp64f"))
++      if (ABIName.starts_with("lp64f"))
+         return "lp64f";
+-      if (ABIName.startswith("lp64d"))
++      if (ABIName.starts_with("lp64d"))
+         return "lp64d";
+-      if (ABIName.startswith("lp64s"))
++      if (ABIName.starts_with("lp64s"))
+         return "lp64s";
+       break;
+-#endif // LDC_LLVM_VER >= 1600
+     default:
+       break;
+     }
+@@ -169,7 +154,6 @@ const char *getABI(const llvm::Triple &triple, const llvm::SmallVectorImpl<llvm:
+     return "lp64";
+   case llvm::Triple::riscv32:
+     return "ilp32";
+-#if LDC_LLVM_VER >= 1600
+   case llvm::Triple::loongarch32:
+     if (isFeatureEnabled(features, "d"))
+       return "ilp32d";
+@@ -182,7 +166,6 @@ const char *getABI(const llvm::Triple &triple, const llvm::SmallVectorImpl<llvm:
+     if (isFeatureEnabled(features, "f"))
+       return "lp64f";
+     return "lp64d";
+-#endif // LDC_LLVM_VER >= 1600
+   default:
+     return "";
+   }
+@@ -222,22 +205,22 @@ static std::string getX86TargetCPU(const llvm::Triple &triple) {
+   if (triple.isArch64Bit()) {
+     return "x86-64";
+   }
+-  if (triple.getOSName().startswith("haiku")) {
++  if (triple.getOSName().starts_with("haiku")) {
+     return "i586";
+   }
+-  if (triple.getOSName().startswith("openbsd")) {
++  if (triple.getOSName().starts_with("openbsd")) {
+     return "i486";
+   }
+-  if (triple.getOSName().startswith("freebsd")) {
++  if (triple.getOSName().starts_with("freebsd")) {
+     return "i486";
+   }
+-  if (triple.getOSName().startswith("netbsd")) {
++  if (triple.getOSName().starts_with("netbsd")) {
+     return "i486";
+   }
+-  if (triple.getOSName().startswith("openbsd")) {
++  if (triple.getOSName().starts_with("openbsd")) {
+     return "i486";
+   }
+-  if (triple.getOSName().startswith("dragonfly")) {
++  if (triple.getOSName().starts_with("dragonfly")) {
+     return "i486";
+   }
+ 
+@@ -263,12 +246,6 @@ static std::string getARMTargetCPU(const llvm::Triple &triple) {
+ }
+ 
+ static std::string getAArch64TargetCPU(const llvm::Triple &triple) {
+-#if LDC_LLVM_VER < 1600
+-  auto defaultCPU = llvm::AArch64::getDefaultCPU(triple.getArchName());
+-  if (!defaultCPU.empty())
+-    return std::string(defaultCPU);
+-#endif
+-
+   return "generic";
+ }
+ 
+@@ -309,18 +286,32 @@ static std::string getTargetCPU(const llvm::Triple &triple) {
+     return getRiscv32TargetCPU(triple);
+   case llvm::Triple::riscv64:
+     return getRiscv64TargetCPU(triple);
+-#if LDC_LLVM_VER >= 1600
+   case llvm::Triple::loongarch32:
+     return getLoongArch32TargetCPU(triple);
+   case llvm::Triple::loongarch64:
+     return getLoongArch64TargetCPU(triple);
+-#endif // LDC_LLVM_VER >= 1600
+   }
+ }
+ 
+ static const char *getLLVMArchSuffixForARM(llvm::StringRef CPU) {
+   return llvm::StringSwitch<const char *>(CPU)
+       .Case("strongarm", "v4")
++#if LLVM_VERSION_MAJOR >= 22
++      .Cases({"arm7tdmi", "arm7tdmi-s", "arm710t"}, "v4t")
++      .Cases({"arm720t", "arm9", "arm9tdmi"}, "v4t")
++      .Cases({"arm920", "arm920t", "arm922t"}, "v4t")
++      .Cases({"arm940t", "ep9312"}, "v4t")
++      .Cases({"arm10tdmi", "arm1020t"}, "v5")
++      .Cases({"arm9e", "arm926ej-s", "arm946e-s"}, "v5e")
++      .Cases({"arm966e-s", "arm968e-s", "arm10e"}, "v5e")
++      .Cases({"arm1020e", "arm1022e", "xscale", "iwmmxt"}, "v5e")
++      .Cases({"arm1136j-s", "arm1136jf-s", "arm1176jz-s"}, "v6")
++      .Cases({"arm1176jzf-s", "mpcorenovfp", "mpcore"}, "v6")
++      .Cases({"arm1156t2-s", "arm1156t2f-s"}, "v6t2")
++      .Cases({"cortex-a5", "cortex-a7", "cortex-a8"}, "v7")
++      .Cases({"cortex-a9", "cortex-a12", "cortex-a15"}, "v7")
++      .Cases({"cortex-r4", "cortex-r5"}, "v7r")
++#else
+       .Cases("arm7tdmi", "arm7tdmi-s", "arm710t", "v4t")
+       .Cases("arm720t", "arm9", "arm9tdmi", "v4t")
+       .Cases("arm920", "arm920t", "arm922t", "v4t")
+@@ -335,6 +326,7 @@ static const char *getLLVMArchSuffixForARM(llvm::StringRef CPU) {
+       .Cases("cortex-a5", "cortex-a7", "cortex-a8", "v7")
+       .Cases("cortex-a9", "cortex-a12", "cortex-a15", "v7")
+       .Cases("cortex-r4", "cortex-r5", "v7r")
++#endif
+       .Case("cortex-m0", "v6m")
+       .Case("cortex-m3", "v7m")
+       .Case("cortex-m4", "v7em")
+@@ -349,8 +341,8 @@ static FloatABI::Type getARMFloatABI(const llvm::Triple &triple,
+                                      const char *llvmArchSuffix) {
+   if (triple.isOSDarwin()) {
+     // Darwin defaults to "softfp" for v6 and v7.
+-    if (llvm::StringRef(llvmArchSuffix).startswith("v6") ||
+-        llvm::StringRef(llvmArchSuffix).startswith("v7")) {
++    if (llvm::StringRef(llvmArchSuffix).starts_with("v6") ||
++        llvm::StringRef(llvmArchSuffix).starts_with("v7")) {
+       return FloatABI::SoftFP;
+     }
+     return FloatABI::Soft;
+@@ -361,9 +353,9 @@ static FloatABI::Type getARMFloatABI(const llvm::Triple &triple,
+     return FloatABI::Soft;
+   }
+ 
+-  if (triple.getVendorName().startswith("hardfloat"))
++  if (triple.getVendorName().starts_with("hardfloat"))
+     return FloatABI::Hard;
+-  if (triple.getVendorName().startswith("softfloat"))
++  if (triple.getVendorName().starts_with("softfloat"))
+     return FloatABI::SoftFP;
+ 
+   switch (triple.getEnvironment()) {
+@@ -375,7 +367,7 @@ static FloatABI::Type getARMFloatABI(const llvm::Triple &triple,
+     // EABI is always AAPCS, and if it was not marked 'hard', it's softfp
+     return FloatABI::SoftFP;
+   case llvm::Triple::Android: {
+-    if (llvm::StringRef(llvmArchSuffix).startswith("v7")) {
++    if (llvm::StringRef(llvmArchSuffix).starts_with("v7")) {
+       return FloatABI::SoftFP;
+     }
+     return FloatABI::Soft;
+@@ -425,7 +417,11 @@ const llvm::Target *lookupTarget(const std::string &arch, llvm::Triple &triple,
+     }
+   } else {
+     std::string tempError;
++#if LLVM_VERSION_MAJOR >= 22
++    target = llvm::TargetRegistry::lookupTarget(triple, tempError);
++#else
+     target = llvm::TargetRegistry::lookupTarget(triple.getTriple(), tempError);
++#endif
+     if (!target) {
+       errorMsg = "unable to get target for '" + triple.getTriple() +
+                  "', see -version and -mtriple.";
+@@ -440,8 +436,8 @@ createTargetMachine(const std::string targetTriple, const std::string arch,
+                     std::string cpu, const std::string featuresString,
+                     const ExplicitBitness::Type bitness,
+                     FloatABI::Type &floatABI,
+-                    llvm::Optional<llvm::Reloc::Model> relocModel,
+-                    llvm::Optional<llvm::CodeModel::Model> codeModel,
++                    std::optional<llvm::Reloc::Model> relocModel,
++                    std::optional<llvm::CodeModel::Model> codeModel,
+                     const llvm::CodeGenOptLevel codeGenOptLevel,
+                     const bool noLinkerStripDead) {
+   // Determine target triple. If the user didn't explicitly specify one, use
+@@ -543,19 +539,12 @@ createTargetMachine(const std::string targetTriple, const std::string arch,
+ 
+   // For LoongArch 64-bit target default to la64 if nothing has been selected
+   // All current LoongArch targets have 64-bit floating point registers.
+-#if LDC_LLVM_VER >= 1600
+   if (triple.getArch() == llvm::Triple::loongarch64 && features.empty()) {
+     features = {"+d"};
+   }
+-#endif
+ 
+   // Handle cases where LLVM picks wrong default relocModel
+-#if LDC_LLVM_VER >= 1600
+-  if (relocModel.has_value()) {}
+-#else
+-  if (relocModel.hasValue()) {}
+-#endif
+-  else {
++  if (!relocModel.has_value()) {
+     if (triple.isOSDarwin()) {
+       // Darwin defaults to PIC (and as of 10.7.5/LLVM 3.1-3.3, TLS use leads
+       // to crashes for non-PIC code). LLVM doesn't handle this.
+@@ -636,11 +625,6 @@ createTargetMachine(const std::string targetTriple, const std::string arch,
+   // EmuTLS).
+   if (triple.getEnvironment() == llvm::Triple::Android) {
+     targetOptions.EmulatedTLS = false;
+-#if LDC_LLVM_VER < 1700
+-    // Removed in this commit:
+-    // https://github.com/llvm/llvm-project/commit/0d333bf0e3aa37e2e6ae211e3aa80631c3e01b85
+-    targetOptions.ExplicitEmulatedTLS = true;
+-#endif
+   }
+ 
+   const std::string finalFeaturesString =
+@@ -653,7 +637,7 @@ createTargetMachine(const std::string targetTriple, const std::string arch,
+   }
+ 
+   return target->createTargetMachine(
+-#if LDC_LLVM_VER >= 2100
++#if LLVM_VERSION_MAJOR >= 21
+                                      triple,
+ #else
+                                      triple.str(),
+diff --git a/driver/targetmachine.h b/driver/targetmachine.h
+index 3bfa567e4f..e0c21ee140 100644
+--- a/driver/targetmachine.h
++++ b/driver/targetmachine.h
+@@ -14,24 +14,12 @@
+ 
+ #pragma once
+ 
+-#if LDC_LLVM_VER < 1700
+-#include "llvm/ADT/Optional.h"
+-#else
+-#include <optional>
+-namespace llvm {
+-template <typename T> using Optional = std::optional<T>;
+-}
+-#endif
+ #include "llvm/ADT/SmallVector.h"
+ #include "llvm/ADT/StringRef.h"
+ #include "llvm/Support/CodeGen.h"
++#include <optional>
+ #include <string>
+ #include <vector>
+-namespace llvm {
+-#if LDC_LLVM_VER < 1800
+-using CodeGenOptLevel = llvm::CodeGenOpt::Level;
+-#endif
+-}
+ 
+ namespace ExplicitBitness {
+ enum Type { None, M32, M64 };
+@@ -69,8 +57,8 @@ llvm::TargetMachine *
+ createTargetMachine(std::string targetTriple, std::string arch, std::string cpu,
+                     std::string featuresString, ExplicitBitness::Type bitness,
+                     FloatABI::Type &floatABI,
+-                    llvm::Optional<llvm::Reloc::Model> relocModel,
+-                    llvm::Optional<llvm::CodeModel::Model> codeModel,
++                    std::optional<llvm::Reloc::Model> relocModel,
++                    std::optional<llvm::CodeModel::Model> codeModel,
+                     llvm::CodeGenOptLevel codeGenOptLevel,
+                     bool noLinkerStripDead);
+ 
+diff --git a/driver/toobj.cpp b/driver/toobj.cpp
+index f9789452f4..211d8d88f4 100644
+--- a/driver/toobj.cpp
++++ b/driver/toobj.cpp
+@@ -37,34 +37,14 @@
+ #include "llvm/CodeGen/TargetSubtargetInfo.h"
+ #include "llvm/Transforms/Utils/Cloning.h"
+ #include "llvm/IR/Module.h"
+-#ifdef LDC_LLVM_SUPPORTED_TARGET_SPIRV
+-#if LDC_LLVM_VER < 1600
+-#include "LLVMSPIRVLib/LLVMSPIRVLib.h"
+-#endif
+-#endif
+ #include <cstddef>
+ #include <fstream>
+ 
+-using CodeGenFileType = llvm::CodeGenFileType;
+-
+-#if LDC_LLVM_VER >= 1800
+-constexpr llvm::CodeGenFileType CGFT_AssemblyFile = CodeGenFileType::AssemblyFile;
+-constexpr llvm::CodeGenFileType CGFT_ObjectFile = CodeGenFileType::ObjectFile;
+-#endif
+-
+-#if LDC_LLVM_VER < 1700
+-static llvm::cl::opt<bool>
+-    NoIntegratedAssembler("no-integrated-as", llvm::cl::ZeroOrMore,
+-                          llvm::cl::Hidden,
+-                          llvm::cl::desc("Disable integrated assembler"));
+-#else
+ namespace llvm {
+ namespace codegen {
+ bool getDisableIntegratedAS();
+ }
+ }
+-#define NoIntegratedAssembler llvm::codegen::getDisableIntegratedAS()
+-#endif
+ 
+ namespace {
+ 
+@@ -81,21 +61,13 @@ void runDLLImportRelocationPass(llvm::TargetMachine &Target, llvm::Module &m) {
+ // based on llc code, University of Illinois Open Source License
+ void codegenModule(llvm::TargetMachine &Target, llvm::Module &m,
+                    const char *filename,
+-                   CodeGenFileType fileType) {
++                   llvm::CodeGenFileType fileType) {
+   using namespace llvm;
+ 
+   const ComputeBackend::Type cb = getComputeTargetType(&m);
+ 
+   if (cb == ComputeBackend::SPIRV) {
+-#ifdef LDC_LLVM_SUPPORTED_TARGET_SPIRV
+-#if LDC_LLVM_VER < 1600
+-    IF_LOG Logger::println("running createSPIRVWriterPass()");
+-    std::ofstream out(filename, std::ofstream::binary);
+-    llvm::createSPIRVWriterPass(out)->runOnModule(m);
+-    IF_LOG Logger::println("Success.");
+-    return;
+-#endif
+-#else
++#ifndef LDC_LLVM_SUPPORTED_TARGET_SPIRV
+     error(Loc(), "Trying to target SPIRV, but LDC is not built to do so!");
+     return;
+ #endif
+@@ -132,11 +104,8 @@ void codegenModule(llvm::TargetMachine &Target, llvm::Module &m,
+           nullptr,  // DWO output file
+           // Always generate assembly for ptx as it is an assembly format
+           // The PTX backend fails if we pass anything else.
+-          (cb == ComputeBackend::NVPTX) ? CGFT_AssemblyFile : fileType
+-#if LDC_LLVM_VER < 1700
+-          , codeGenOptLevel()
+-#endif
+-      )) {
++          (cb == ComputeBackend::NVPTX) ? CodeGenFileType::AssemblyFile
++                                        : fileType)) {
+     llvm_unreachable("no support for asm output");
+   }
+ 
+@@ -300,14 +269,14 @@ public:
+ void writeObjectFile(llvm::Module *m, const char *filename) {
+   IF_LOG Logger::println("Writing object file to: %s", filename);
+   codegenModule(*gTargetMachine, *m, filename,
+-                CGFT_ObjectFile);
++                llvm::CodeGenFileType::ObjectFile);
+ }
+ 
+ bool shouldAssembleExternally() {
+   // There is no integrated assembler on AIX because XCOFF is not supported.
+   // Starting with LLVM 3.5 the integrated assembler can be used with MinGW.
+   return global.params.output_o &&
+-         (NoIntegratedAssembler ||
++         (llvm::codegen::getDisableIntegratedAS() ||
+           global.params.targetTriple->getOS() == llvm::Triple::AIX);
+ }
+ 
+@@ -479,10 +448,10 @@ void writeModule(llvm::Module *m, const char *filename) {
+       // to avoid running 'addPassesToEmitFile' passes twice on same module
+       auto clonedModule = llvm::CloneModule(*m);
+       codegenModule(*gTargetMachine, *clonedModule, spath.c_str(),
+-                    CGFT_AssemblyFile);
++                    llvm::CodeGenFileType::AssemblyFile);
+     } else {
+       codegenModule(*gTargetMachine, *m, spath.c_str(),
+-                    CGFT_AssemblyFile);
++                    llvm::CodeGenFileType::AssemblyFile);
+     }
+ 
+     if (assembleExternally) {
+diff --git a/driver/tool.cpp b/driver/tool.cpp
+index e64ec96cbd..31ff31cf83 100644
+--- a/driver/tool.cpp
++++ b/driver/tool.cpp
+@@ -20,12 +20,7 @@
+ #include "llvm/Support/MemoryBuffer.h"
+ #include "llvm/Support/Path.h"
+ #include "llvm/Target/TargetMachine.h"
+-
+-#if LDC_LLVM_VER >= 1600
+ #include "llvm/TargetParser/Host.h"
+-#else
+-#include "llvm/Support/Host.h"
+-#endif
+ 
+ #ifdef _WIN32
+ #include <Windows.h>
+@@ -214,12 +209,10 @@ void appendTargetArgsForGcc(std::vector<std::string> &args) {
+   case Triple::nvptx64:
+     args.push_back(triple.isArch64Bit() ? "-m64" : "-m32");
+     return;
+-#if LDC_LLVM_VER >= 1600
+   // LoongArch does not use -m32/-m64 and uses -mabi=.
+   case Triple::loongarch64:
+     args.emplace_back(triple.isArch64Bit() ? "-mabi=lp64d" : "-mabi=ilp32d");
+     return;
+-#endif // LDC_LLVM_VER >= 1600
+   // MIPS does not have -m32/-m64 but requires -mabi=.
+   case Triple::mips64:
+   case Triple::mips64el:
+diff --git a/gen/abi/abi.cpp b/gen/abi/abi.cpp
+index eab1ca5ca1..5e5a1baa20 100644
+--- a/gen/abi/abi.cpp
++++ b/gen/abi/abi.cpp
+@@ -158,11 +158,7 @@ llvm::CallingConv::ID TargetABI::callingConv(FuncDeclaration *fdecl) {
+ void TargetABI::setUnwindTableKind(llvm::Function *fn) {
+   llvm::UWTableKind kind = defaultUnwindTableKind();
+   if (kind != llvm::UWTableKind::None) {
+-#if LDC_LLVM_VER >= 1600
+     fn->setUWTableKind(kind);
+-#else
+-    fn->setUWTableKind(kind);
+-#endif
+   }
+ }
+ 
+@@ -285,10 +281,8 @@ TargetABI *TargetABI::getTarget() {
+   case llvm::Triple::thumb:
+   case llvm::Triple::thumbeb:
+     return getArmTargetABI();
+-#if LDC_LLVM_VER >= 1600
+   case llvm::Triple::loongarch64:
+     return getLoongArch64TargetABI();
+-#endif // LDC_LLVM_VER >= 1600
+   case llvm::Triple::wasm32:
+   case llvm::Triple::wasm64:
+     return getWasmTargetABI();
+diff --git a/gen/abi/generic.h b/gen/abi/generic.h
+index 6458d28b8b..4def40c790 100644
+--- a/gen/abi/generic.h
++++ b/gen/abi/generic.h
+@@ -260,7 +260,7 @@ struct IndirectByvalRewrite : ABIRewrite {
+     auto &attrs = arg.attrs;
+     attrs.clear();
+     attrs.addAttribute(LLAttribute::NoAlias);
+-#if LDC_LLVM_VER >= 2100
++#if LLVM_VERSION_MAJOR >= 21
+     attrs.addCapturesAttr(llvm::CaptureInfo::none());
+ #else
+     attrs.addAttribute(LLAttribute::NoCapture);
+diff --git a/gen/abi/nvptx.cpp b/gen/abi/nvptx.cpp
+index cd81b6261f..31cf01d946 100644
+--- a/gen/abi/nvptx.cpp
++++ b/gen/abi/nvptx.cpp
+@@ -38,7 +38,7 @@ struct NVPTXTargetABI : TargetABI {
+       return;
+ 
+     Type *ty = arg.type->toBasetype();
+-    llvm::Optional<DcomputePointer> ptr;
++    std::optional<DcomputePointer> ptr;
+     if (ty->ty == TY::Tstruct &&
+         (ptr = toDcomputePointer(static_cast<TypeStruct *>(ty)->sym))) {
+       pointerRewite.applyTo(arg);
+diff --git a/gen/abi/spirv.cpp b/gen/abi/spirv.cpp
+index 60a3eada37..df8a487acc 100644
+--- a/gen/abi/spirv.cpp
++++ b/gen/abi/spirv.cpp
+@@ -44,7 +44,7 @@ struct SPIRVTargetABI : TargetABI {
+       return;
+ 
+     Type *ty = arg.type->toBasetype();
+-    llvm::Optional<DcomputePointer> ptr;
++    std::optional<DcomputePointer> ptr;
+     if (ty->ty == TY::Tstruct &&
+         (ptr = toDcomputePointer(static_cast<TypeStruct *>(ty)->sym))) {
+       pointerRewite.applyTo(arg);
+diff --git a/gen/arrays.cpp b/gen/arrays.cpp
+index 8179679d10..a27c4338e6 100644
+--- a/gen/arrays.cpp
++++ b/gen/arrays.cpp
+@@ -533,11 +533,7 @@ llvm::Constant *arrayLiteralToConst(IRState *p, ArrayLiteralExp *ale) {
+     // extend i1 to i8
+     if (val->getType()->isIntegerTy(1)) {
+       LLType *I8PtrTy = LLType::getInt8Ty(p->context());
+-#if LDC_LLVM_VER < 1800
+-      val = llvm::ConstantExpr::getZExt(val, I8PtrTy);
+-#else
+       val = llvm::ConstantFoldCastOperand(llvm::Instruction::ZExt, val, I8PtrTy, *gDataLayout);
+-#endif
+     }
+     if (!elementType) {
+       elementType = val->getType();
+diff --git a/gen/asm-gcc.cpp b/gen/asm-gcc.cpp
+index 9df3c55c2e..60026c6f98 100644
+--- a/gen/asm-gcc.cpp
++++ b/gen/asm-gcc.cpp
+@@ -63,10 +63,6 @@ class ConstraintsBuilder {
+   // Appends a constraint string expression with an optional prefix.
+   // Returns true if the string describes an indirect operand.
+   bool append(Expression *e, char prefix = 0) {
+-#if LDC_LLVM_VER >= 1800
+-    #define startswith starts_with
+-#endif
+-
+     auto se = e->isStringExp();
+     assert(se);
+     llvm::StringRef code = peekString(se);
+@@ -81,10 +77,10 @@ class ConstraintsBuilder {
+ 
+     // commit any modifier and strip from `code`
+     bool isIndirect = false;
+-    if (code.startswith("&")) { // early clobber
++    if (code.starts_with("&")) { // early clobber
+       str << '&';
+       code = code.substr(1);
+-    } else if (code.startswith("*")) { // indirect in/output
++    } else if (code.starts_with("*")) { // indirect in/output
+       isIndirect = true; // delay the commit
+       code = code.substr(1);
+     }
+@@ -97,10 +93,6 @@ class ConstraintsBuilder {
+     str << ',';
+ 
+     return isIndirect;
+-
+-#if LDC_LLVM_VER >= 1800
+-    #undef startswith
+-#endif
+   }
+ 
+   // Might set `isIndirect` to true (but never resets to false).
+diff --git a/gen/asm-x86.h b/gen/asm-x86.h
+index eecd1a8b65..2104b4155c 100644
+--- a/gen/asm-x86.h
++++ b/gen/asm-x86.h
+@@ -2340,57 +2340,15 @@ struct AsmProcessor {
+                   AsmCode *asmcode, AsmArgMode mode = Mode_Input) {
+     using namespace dmd;
+ 
+-    if (sc->func->isNaked()) {
+-      switch (type) {
+-      case Arg_Integer:
+-        if (e->type->isUnsigned()) {
+-          insnTemplate << "$" << e->toUInteger();
+-        } else {
++    if (type == Arg_Integer) {
++      if (e->type->isUnsigned()) {
++        insnTemplate << "$$" << e->toUInteger();
++      } else {
+ #ifndef ASM_X86_64
+-          insnTemplate << "$" << static_cast<sinteger_t>(e->toInteger());
++        insnTemplate << "$$" << static_cast<sinteger_t>(e->toInteger());
+ #else
+-          insnTemplate << "$" << e->toInteger();
++        insnTemplate << "$$" << e->toInteger();
+ #endif
+-        }
+-        break;
+-
+-      case Arg_Pointer:
+-        error(stmt->loc, "unsupported pointer reference to `%s` in naked asm",
+-                    e->toChars());
+-        break;
+-
+-      case Arg_Memory:
+-        // Peel off one layer of explicitly taking the address, if present.
+-        if (auto ae = e->isAddrExp()) {
+-          e = ae->e1;
+-        }
+-
+-        if (auto v = e->isVarExp()) {
+-          if (VarDeclaration *vd = v->var->isVarDeclaration()) {
+-            if (!vd->isDataseg()) {
+-              error(stmt->loc, "only global variables can be referenced by "
+-                          "identifier in naked asm");
+-              break;
+-            }
+-
+-            // print out the mangle
+-            if (prependExtraUnderscore(vd->resolvedLinkage())) {
+-              insnTemplate << "_";
+-            }
+-            OutBuffer buf;
+-            mangleToBuffer(vd, buf);
+-            insnTemplate << buf.peekChars();
+-            getIrGlobal(vd, true)->nakedUse = true;
+-            break;
+-          }
+-        }
+-        error(stmt->loc, "unsupported memory reference to `%s` in naked asm",
+-                    e->toChars());
+-        break;
+-
+-      default:
+-        llvm_unreachable("Unsupported argument in asm.");
+-        break;
+       }
+     } else {
+       insnTemplate << fmt << "<<" << (mode == Mode_Input ? "in" : "out")
+@@ -2401,13 +2359,6 @@ struct AsmProcessor {
+   void addOperand2(const char *fmtpre, const char *fmtpost, AsmArgType type,
+                    Expression *e, AsmCode *asmcode,
+                    AsmArgMode mode = Mode_Input) {
+-    if (sc->func->isNaked()) {
+-      // taken from above
+-      error(stmt->loc, "only global variables can be referenced by identifier in "
+-                  "naked asm");
+-      return;
+-    }
+-
+     insnTemplate << fmtpre << "<<" << (mode == Mode_Input ? "in" : "out")
+                  << ">>" << fmtpost;
+     asmcode->args.push_back(AsmArg(type, e, mode));
+@@ -3078,11 +3029,9 @@ struct AsmProcessor {
+               // If we subtract 8 from our const-displacement, then we can use the `H` modifier to ensure
+               // that we always end up with a valid syntax for a memory operand with an offset.
+               // So, we do just that when we have const-displacement in play.
+-              // (Only for non-naked asm, as this isn't an issue for naked asm.)
+               //
+               // See also: https://lists.llvm.org/pipermail/llvm-dev/2017-August/116244.html
+-              const auto forceLeadingDisplacement = hasConstDisplacement && !sc->func->isNaked();
+-              if (forceLeadingDisplacement) {
++              if (hasConstDisplacement) {
+                 // Subtract 8 from our const-displacement, and prepare to add the 8 from the `H` modifier.
+                 insnTemplate << "-8+";
+               }
+@@ -3092,14 +3041,11 @@ struct AsmProcessor {
+                 use_star = false;
+               }
+ 
+-              if (!sc->func->isNaked()) // no addrexp in naked asm please :)
+-              {
+-                Type *tt = pointerTo(e->type);
+-                e = createAddrExp(Loc(), e);
+-                e->type = tt;
+-              }
++              Type *tt = pointerTo(e->type);
++              e = createAddrExp(Loc(), e);
++              e->type = tt;
+ 
+-              if (forceLeadingDisplacement) {
++              if (hasConstDisplacement) {
+                 // We have a const-displacement in play, so we add the `H` modifier, as described earlier.
+                 insnTemplate << "${" << "<<" << (mode == Mode_Input ? "in" : "out")
+                              << asmcode->args.size() << ">>" << ":H}";
+diff --git a/gen/asmstmt.cpp b/gen/asmstmt.cpp
+index 12d364d012..b5c868a844 100644
+--- a/gen/asmstmt.cpp
++++ b/gen/asmstmt.cpp
+@@ -532,9 +532,14 @@ void CompoundAsmStatement_toIR(CompoundAsmStatement *stmt, IRState *p) {
+       }
+       Identifier *const ident = targetLabel->ident;
+ 
+-      // if internal, no special handling is necessary, skip
+-      if (llvm::any_of(asmblock->internalLabels,
+-                       [ident](Identifier *i) { return i->equals(ident); })) {
++      // if the label is defined in inline asm, we can jump to it directly
++      if (fd->isNaked() // in naked DMD-style inline-asm functions, all D labels
++                        // become assembly labels too (and the extra forwarding
++                        // code would mess with the stack due to an i32 alloca)
++          || (fd->asmLabels &&
++              llvm::any_of(*fd->asmLabels, [ident](Identifier *i) {
++                return i->equals(ident);
++              }))) {
+         continue;
+       }
+ 
+@@ -784,21 +789,3 @@ void CompoundAsmStatement_toIR(CompoundAsmStatement *stmt, IRState *p) {
+     p->ir->SetInsertPoint(bb);
+   }
+ }
+-
+-//////////////////////////////////////////////////////////////////////////////
+-
+-void AsmStatement_toNakedIR(InlineAsmStatement *stmt, IRState *irs) {
+-  IF_LOG Logger::println("InlineAsmStatement::toNakedIR(): %s",
+-                         stmt->loc.toChars());
+-  LOG_SCOPE;
+-
+-  // is there code?
+-  if (!stmt->asmcode) {
+-    return;
+-  }
+-  AsmCode *code = static_cast<AsmCode *>(stmt->asmcode);
+-
+-  // build asm stmt
+-  replace_func_name(irs, code->insnTemplate);
+-  irs->nakedAsm << "\t" << code->insnTemplate << std::endl;
+-}
+diff --git a/gen/ctfloat.cpp b/gen/ctfloat.cpp
+index ef033c018d..9ce8ad72d7 100644
+--- a/gen/ctfloat.cpp
++++ b/gen/ctfloat.cpp
+@@ -83,7 +83,11 @@ void CTFloat::toAPFloat(const real_t src, APFloat &dst) {
+   u.fp = src;
+ 
+   const unsigned sizeInBits = APFloat::getSizeInBits(*apSemantics);
++#if LLVM_VERSION_MAJOR >= 22
++  const APInt bits = APInt(sizeInBits, u.bits);
++#else
+   const APInt bits = APInt(sizeInBits, numUint64Parts, u.bits);
++#endif
+ 
+   dst = APFloat(*apSemantics, bits);
+ }
+diff --git a/gen/dcompute/druntime.cpp b/gen/dcompute/druntime.cpp
+index 0f2c79bd4c..829185481c 100644
+--- a/gen/dcompute/druntime.cpp
++++ b/gen/dcompute/druntime.cpp
+@@ -43,17 +43,13 @@ bool isFromLDC_OpenCL(Dsymbol *sym) {
+   return isFromLDC_Mod(sym,Id::opencl);
+ }
+ 
+-llvm::Optional<DcomputePointer> toDcomputePointer(StructDeclaration *sd) {
++std::optional<DcomputePointer> toDcomputePointer(StructDeclaration *sd) {
+   if (sd->ident != Id::dcPointer || !isFromLDC_DCompute(sd)) {
+-#if LDC_LLVM_VER < 1600
+-    return llvm::Optional<DcomputePointer>(llvm::None);
+-#else
+     return std::optional<DcomputePointer>(std::nullopt);
+-#endif
+   }
+ 
+   TemplateInstance *ti = sd->isInstantiated();
+   int addrspace = isExpression((*ti->tiargs)[0])->toInteger();
+   Type *type = isType((*ti->tiargs)[1]);
+-  return llvm::Optional<DcomputePointer>(DcomputePointer(addrspace, type));
++  return std::optional<DcomputePointer>(DcomputePointer(addrspace, type));
+ }
+diff --git a/gen/dcompute/druntime.h b/gen/dcompute/druntime.h
+index 6ff3b2c8a8..673eb24951 100644
+--- a/gen/dcompute/druntime.h
++++ b/gen/dcompute/druntime.h
+@@ -18,14 +18,7 @@
+ #include "gen/irstate.h"
+ #include "gen/llvm.h"
+ #include "gen/tollvm.h"
+-#if LDC_LLVM_VER < 1700
+-#include "llvm/ADT/Optional.h"
+-#else
+ #include <optional>
+-namespace llvm {
+-template <typename T> using Optional = std::optional<T>;
+-}
+-#endif
+ 
+ class Dsymbol;
+ class Type;
+@@ -45,4 +38,4 @@ struct DcomputePointer {
+     return LLPointerType::get(getGlobalContext(), as);
+   }
+ };
+-llvm::Optional<DcomputePointer> toDcomputePointer(StructDeclaration *sd);
++std::optional<DcomputePointer> toDcomputePointer(StructDeclaration *sd);
+diff --git a/gen/dcompute/targetCUDA.cpp b/gen/dcompute/targetCUDA.cpp
+index ffd2711acc..9861cb2764 100644
+--- a/gen/dcompute/targetCUDA.cpp
++++ b/gen/dcompute/targetCUDA.cpp
+@@ -43,7 +43,7 @@ public:
+         llvm::Reloc::Static, llvm::CodeModel::Medium, codeGenOptLevel(), false);
+ 
+     _ir = new IRState("dcomputeTargetCUDA", ctx);
+-#if LDC_LLVM_VER >= 2100
++#if LLVM_VERSION_MAJOR >= 21
+     _ir->module.setTargetTriple(llvm::Triple(tripleString));
+ #else
+     _ir->module.setTargetTriple(tripleString);
+diff --git a/gen/dcompute/targetOCL.cpp b/gen/dcompute/targetOCL.cpp
+index b93850c95e..e0fa37310e 100644
+--- a/gen/dcompute/targetOCL.cpp
++++ b/gen/dcompute/targetOCL.cpp
+@@ -24,7 +24,7 @@
+ #include <string>
+ 
+ // from SPIRVInternal.h
+-#if LDC_LLVM_VER < 1900
++#if LLVM_VERSION_MAJOR < 19
+ #  define SPIR_TARGETTRIPLE32 "spir-unknown-unknown"
+ #  define SPIR_TARGETTRIPLE64 "spir64-unknown-unknown"
+ #  define SPIR_DATALAYOUT32                                                    \
+@@ -42,7 +42,7 @@
+ #else // LLVM 19+
+ #  define SPIR_TARGETTRIPLE32 "spirv-unknown-unknown"
+ #  define SPIR_TARGETTRIPLE64 "spirv64-unknown-unknown"
+-#  if LDC_LLVM_VER < 2000
++#  if LLVM_VERSION_MAJOR < 20
+ #    define SPIR_DATALAYOUT32                                                  \
+        "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64"                          \
+        "-v96:128-v192:256-v256:256-v512:512-v1024:1024-G1"
+@@ -75,14 +75,13 @@ public:
+     _ir = new IRState("dcomputeTargetOCL", ctx);
+     std::string targTripleStr = is64 ? SPIR_TARGETTRIPLE64
+                                      : SPIR_TARGETTRIPLE32;
+-#if LDC_LLVM_VER >= 2100
++#if LLVM_VERSION_MAJOR >= 21
+     llvm::Triple targTriple = llvm::Triple(targTripleStr);
+ #else
+     std::string targTriple = targTripleStr;
+ #endif
+     _ir->module.setTargetTriple(targTriple);
+ 
+-#if LDC_LLVM_VER >= 1600
+     auto floatABI = ::FloatABI::Hard;
+     targetMachine = createTargetMachine(
+             targTripleStr,
+@@ -90,7 +89,6 @@ public:
+             "", {},
+             is64 ? ExplicitBitness::M64 : ExplicitBitness::M32, floatABI,
+             llvm::Reloc::Static, llvm::CodeModel::Medium, codeGenOptLevel(), false);
+-#endif
+     _ir->module.setDataLayout(is64 ? SPIR_DATALAYOUT64 : SPIR_DATALAYOUT32);
+     _ir->dcomputetarget = this;
+   }
+@@ -200,7 +198,7 @@ public:
+   void decodeTypes(std::array<llvm::SmallVector<llvm::Metadata *, 8>,count_KernArgMD>& attrs,
+                    VarDeclaration *v)
+   {
+-    llvm::Optional<DcomputePointer> ptr;
++    std::optional<DcomputePointer> ptr;
+     std::string typeQuals;
+     std::string baseTyName;
+     std::string tyName;
+diff --git a/gen/declarations.cpp b/gen/declarations.cpp
+index bdadc84c61..0e43a20ff6 100644
+--- a/gen/declarations.cpp
++++ b/gen/declarations.cpp
+@@ -412,24 +412,19 @@ public:
+   void visit(PragmaDeclaration *decl) override {
+     const auto &triple = *global.params.targetTriple;
+ 
+-#if LDC_LLVM_VER >= 1800
+-    #define endswith ends_with
+-    #define startswith starts_with
+-#endif
+-
+     if (decl->ident == Id::lib) {
+       assert(!irs->dcomputetarget);
+       llvm::StringRef name = getPragmaStringArg(decl);
+ 
+       if (triple.isWindowsGNUEnvironment()) {
+-        if (name.endswith(".lib")) {
++        if (name.ends_with(".lib")) {
+           // On MinGW, strip the .lib suffix, if any, to improve compatibility
+           // with code written for DMD (we pass the name to GCC via -l, just as
+           // on Posix).
+           name = name.drop_back(4);
+         }
+ 
+-        if (name.startswith("shell32")) {
++        if (name.starts_with("shell32")) {
+           // Another DMD compatibility kludge: Ignore
+           // pragma(lib, "shell32.lib"), it is implicitly provided by
+           // MinGW.
+@@ -438,10 +433,10 @@ public:
+       }
+ 
+       if (triple.isWindowsMSVCEnvironment()) {
+-        if (name.endswith(".a")) {
++        if (name.ends_with(".a")) {
+           name = name.drop_back(2);
+         }
+-        if (name.endswith(".lib")) {
++        if (name.ends_with(".lib")) {
+           name = name.drop_back(4);
+         }
+ 
+@@ -450,7 +445,7 @@ public:
+         std::string arg = ("/DEFAULTLIB:\"" + name + "\"").str();
+         gIR->addLinkerOption(llvm::StringRef(arg));
+       } else {
+-        const bool isStaticLib = name.endswith(".a");
++        const bool isStaticLib = name.ends_with(".a");
+         const size_t nameLen = name.size();
+ 
+         char *arg = nullptr;
+@@ -490,11 +485,6 @@ public:
+       }
+     }
+     visit(static_cast<AttribDeclaration *>(decl));
+-
+-#if LDC_LLVM_VER >= 1800
+-    #undef endswith
+-    #undef startswith
+-#endif
+   }
+ 
+   //////////////////////////////////////////////////////////////////////////
+diff --git a/gen/dibuilder.cpp b/gen/dibuilder.cpp
+index 21b176ef00..df1328ecdb 100644
+--- a/gen/dibuilder.cpp
++++ b/gen/dibuilder.cpp
+@@ -47,13 +47,6 @@ using namespace dmd;
+ namespace cl = llvm::cl;
+ using LLMetadata = llvm::Metadata;
+ 
+-#if LDC_LLVM_VER >= 1600
+-namespace llvm {
+-  template <typename T> using Optional = std::optional<T>;
+-  inline constexpr std::nullopt_t None = std::nullopt;
+-}
+-#endif
+-
+ static cl::opt<cl::boolOrDefault> emitColumnInfo(
+     "gcolumn-info", cl::ZeroOrMore, cl::Hidden,
+     cl::desc("Include column numbers in line debug infos. Defaults to "
+@@ -426,7 +419,7 @@ DIType DIBuilder::CreateEnumType(TypeEnum *type) {
+ DIType DIBuilder::CreatePointerType(TypePointer *type) {
+   // TODO: The addressspace is important for dcompute targets. See e.g.
+   // https://www.mail-archive.com/dwarf-discuss@lists.dwarfstd.org/msg00326.html
+-  const llvm::Optional<unsigned> DWARFAddressSpace = llvm::None;
++  const std::optional<unsigned> DWARFAddressSpace;
+ 
+   const auto name = processDIName(type->toPrettyChars(true));
+ 
+@@ -672,10 +665,7 @@ DIType DIBuilder::CreateCompositeType(Type *t) {
+     ret = DBuilder.createClassType(
+         scope, name, file, lineNum, sizeInBits, alignmentInBits,
+         classOffsetInBits, DIFlags::FlagZero, derivedFrom, elemsArray,
+-#if LDC_LLVM_VER >= 1800
+-        runtimeLang,
+-#endif
+-        vtableHolder, templateParams, uniqueIdentifier);
++        runtimeLang, vtableHolder, templateParams, uniqueIdentifier);
+   } else {
+     ret = DBuilder.createStructType(scope, name, file, lineNum, sizeInBits,
+                                     alignmentInBits, DIFlags::FlagZero,
+@@ -799,7 +789,7 @@ DISubroutineType DIBuilder::CreateFunctionType(Type *type,
+       DIType ditype = DBuilder.createReferenceType(
+           llvm::dwarf::DW_TAG_pointer_type, pointeeType, target.ptrsize * 8);
+       ditype = DBuilder.createObjectPointerType(ditype
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+       , /* Implicit */ true
+ #endif
+       );
+@@ -819,11 +809,7 @@ DISubroutineType DIBuilder::CreateFunctionType(Type *type,
+ }
+ 
+ DISubroutineType DIBuilder::CreateEmptyFunctionType() {
+-#if LDC_LLVM_VER >= 2100 
+   auto paramsArray = DBuilder.getOrCreateTypeArray({});
+-#else
+-  auto paramsArray = DBuilder.getOrCreateTypeArray(llvm::None);
+-#endif
+   return DBuilder.createSubroutineType(paramsArray);
+ }
+ 
+@@ -869,7 +855,7 @@ DIType DIBuilder::CreateTypeDescription(Type *t, bool voidToUbyte) {
+     // display null as void*
+     return DBuilder.createPointerType(
+         CreateTypeDescription(Type::tvoid), target.ptrsize * 8, 0,
+-        /* DWARFAddressSpace */ llvm::None, "typeof(null)");
++        /* DWARFAddressSpace */ std::nullopt, "typeof(null)");
+   }
+   if (auto te = t->isTypeEnum())
+     return CreateEnumType(te);
+@@ -892,7 +878,7 @@ DIType DIBuilder::CreateTypeDescription(Type *t, bool voidToUbyte) {
+     const auto name =
+         (tc->sym->toPrettyChars(true) + llvm::StringRef("*")).str();
+     return DBuilder.createPointerType(aggregateDIType, target.ptrsize * 8, 0,
+-                                      llvm::None, processDIName(name));
++                                      std::nullopt, processDIName(name));
+   }
+   if (auto tf = t->isTypeFunction())
+     return CreateFunctionType(tf);
+@@ -1229,7 +1215,7 @@ void DIBuilder::EmitValue(llvm::Value *val, VarDeclaration *vd) {
+   auto instr = DBuilder.insertDbgValueIntrinsic(
+       val, debugVariable, DBuilder.createExpression(),
+       IR->ir->getCurrentDebugLocation(), IR->scopebb());
+-#if LDC_LLVM_VER >= 1900
++#if LLVM_VERSION_MAJOR >= 19
+   llvm::cast<llvm::DbgRecord *>
+ #endif
+   (instr)->setDebugLoc(IR->ir->getCurrentDebugLocation());
+diff --git a/gen/functions.cpp b/gen/functions.cpp
+index 3c3e6d3d35..b1e106f6bc 100644
+--- a/gen/functions.cpp
++++ b/gen/functions.cpp
+@@ -773,10 +773,8 @@ static LinkageWithCOMDAT lowerFuncLinkage(FuncDeclaration *fdecl) {
+     return LinkageWithCOMDAT(LLGlobalValue::ExternalLinkage, false);
+   }
+ 
+-  // A body-less declaration always needs to be marked as external in LLVM
+-  // (also e.g. naked template functions which would otherwise be weak_odr,
+-  // but where the definition is in module-level inline asm).
+-  if (!fdecl->fbody || fdecl->isNaked()) {
++  // A body-less declaration always needs to be marked as external in LLVM.
++  if (!fdecl->fbody) {
+     return LinkageWithCOMDAT(LLGlobalValue::ExternalLinkage, false);
+   }
+ 
+@@ -910,10 +908,6 @@ bool eraseDummyAfterReturnBB(llvm::BasicBlock *bb) {
+  * to be found.
+  */
+ void emulateWeakAnyLinkageForMSVC(IrFunction *irFunc, LINK linkage) {
+-#if LDC_LLVM_VER >= 1800
+-  #define startswith starts_with
+-#endif
+-
+   LLFunction *func = irFunc->getLLVMFunc();
+ 
+   const bool isWin32 = global.params.targetTriple->isArch32Bit();
+@@ -930,18 +924,18 @@ void emulateWeakAnyLinkageForMSVC(IrFunction *irFunc, LINK linkage) {
+ 
+   std::string finalWeakMangle = finalMangle.str();
+   if (linkage == LINK::cpp) {
+-    assert(finalMangle.startswith("?"));
++    assert(finalMangle.starts_with("?"));
+     // prepend `__weak_` to first identifier
+-    size_t offset = finalMangle.startswith("??$") ? 3 : 1;
++    size_t offset = finalMangle.starts_with("??$") ? 3 : 1;
+     finalWeakMangle.insert(offset, "__weak_");
+   } else if (linkage == LINK::d) {
+     const size_t offset = isWin32 ? 1 : 0;
+-    assert(finalMangle.substr(offset).startswith("_D"));
++    assert(finalMangle.substr(offset).starts_with("_D"));
+     // prepend a `__weak` package
+     finalWeakMangle.insert(offset + 2, "6__weak");
+   } else {
+     // prepend `__weak_`
+-    const size_t offset = isWin32 && finalMangle.startswith("_") ? 1 : 0;
++    const size_t offset = isWin32 && finalMangle.starts_with("_") ? 1 : 0;
+     finalWeakMangle.insert(offset, "__weak_");
+   }
+ 
+@@ -965,10 +959,6 @@ void emulateWeakAnyLinkageForMSVC(IrFunction *irFunc, LINK linkage) {
+   // declaration
+   irFunc->setLLVMFunc(newFunc);
+   func->replaceNonMetadataUsesWith(newFunc);
+-
+-#if LDC_LLVM_VER >= 1800
+-  #undef startswith
+-#endif
+ }
+ 
+ } // anonymous namespace
+@@ -1127,12 +1117,6 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
+     gIR->funcGenStates.pop_back();
+   };
+ 
+-  // if this function is naked, we take over right away! no standard processing!
+-  if (fd->isNaked()) {
+-    DtoDefineNakedFunction(fd);
+-    return;
+-  }
+-
+   SCOPE_EXIT {
+     if (irFunc->isDynamicCompiled()) {
+       defineDynamicCompiledFunction(gIR, irFunc);
+@@ -1172,6 +1156,10 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
+       func->addFnAttr(LLAttribute::SanitizeAddress);
+     }
+ 
++    if (opts::isSanitizerEnabled(opts::FuzzSanitizer)) {
++      func->addFnAttr(LLAttribute::OptForFuzzing);
++    }
++    
+     if (opts::isSanitizerEnabled(opts::MemorySanitizer & noSanitizeMask)) {
+       func->addFnAttr(LLAttribute::SanitizeMemory);
+     }
+@@ -1190,7 +1178,9 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
+   if (opts::isUsingSampleBasedPGOProfile()) {
+     func->addFnAttr("use-sample-profile");
+   }
+-
++  if (opts::noBuiltIn) {
++    func->addFnAttr("no-builtins");
++  }
+   if (fd->hasInlineAsm()) {
+     // disable frame-pointer-elimination for functions with DMD-style inline asm
+     func->addFnAttr("frame-pointer", "all");
+@@ -1210,18 +1200,13 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
+   gIR->ir->setFastMathFlags(irFunc->FMF);
+   gIR->DBuilder.EmitFuncStart(fd);
+ 
+-  // @naked: emit body and return, no prologue/epilogue
+-  if (func->hasFnAttribute(llvm::Attribute::Naked)) {
+-    Statement_toIR(fd->fbody, gIR);
+-    const bool wasDummy = eraseDummyAfterReturnBB(gIR->scopebb());
+-    if (!wasDummy && !gIR->scopereturned()) {
+-      // this is what clang does to prevent LLVM complaining about
+-      // non-terminated function
+-      gIR->ir->CreateUnreachable();
+-    }
+-    return;
++  if (fd->isNaked()) { // DMD-style `asm { naked; }`
++    func->addFnAttr(llvm::Attribute::Naked);
+   }
+ 
++  // @naked: emit body and return, no prologue/epilogue
++  const bool isNaked = func->hasFnAttribute(llvm::Attribute::Naked);
++
+   // create temporary allocas block
+   funcGen.allocasBlock =
+       llvm::BasicBlock::Create(gIR->context(), "allocas", func);
+@@ -1229,12 +1214,12 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
+   emitInstrumentationFnEnter(fd);
+ 
+   if (global.params.trace && fd->emitInstrumentation && !fd->isCMain() &&
+-      !fd->isNaked()) {
++      !isNaked) {
+     emitDMDStyleFunctionTrace(*gIR, fd, funcGen);
+   }
+ 
+   // give the 'this' parameter (an lvalue) storage and debug info
+-  if (irFty.arg_this) {
++  if (!isNaked && irFty.arg_this) {
+     LLValue *thisvar = irFunc->thisArg;
+     assert(thisvar);
+ 
+@@ -1256,7 +1241,7 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
+   }
+ 
+   // define all explicit parameters
+-  if (fd->parameters)
++  if (!isNaked && fd->parameters)
+     defineParameters(irFty, *fd->parameters);
+ 
+   // Initialize PGO state for this function
+@@ -1322,7 +1307,10 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
+ 
+     // pass the previous block into this block
+     gIR->DBuilder.EmitStopPoint(fd->endloc);
+-    if (func->getReturnType() == LLType::getVoidTy(gIR->context())) {
++
++    if (isNaked) {
++      gIR->ir->CreateUnreachable();
++    } else if (func->getReturnType() == LLType::getVoidTy(gIR->context())) {
+       gIR->ir->CreateRetVoid();
+     } else if (isAnyMainFunction(fd)) {
+       gIR->ir->CreateRet(LLConstant::getNullValue(func->getReturnType()));
+@@ -1335,12 +1323,7 @@ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally) {
+   }
+ 
+   // move allocas from temporary block to the start of the function
+-#if LDC_LLVM_VER >= 1600
+   beginbb->splice(beginbb->begin(), funcGen.allocasBlock);
+-#else
+-  beginbb->getInstList().splice(beginbb->begin(),
+-                                funcGen.allocasBlock->getInstList());
+-#endif
+   funcGen.allocasBlock->eraseFromParent();
+   funcGen.allocasBlock = nullptr;
+ 
+diff --git a/gen/functions.h b/gen/functions.h
+index 5dcfa7032d..609da95620 100644
+--- a/gen/functions.h
++++ b/gen/functions.h
+@@ -40,7 +40,6 @@ void DtoResolveFunction(FuncDeclaration *fdecl);
+ void DtoDeclareFunction(FuncDeclaration *fdecl);
+ void DtoDefineFunction(FuncDeclaration *fd, bool linkageAvailableExternally = false);
+ 
+-void DtoDefineNakedFunction(FuncDeclaration *fd);
+ void emitABIReturnAsmStmt(IRAsmBlock *asmblock, Loc loc,
+                           FuncDeclaration *fdecl);
+ 
+diff --git a/gen/irstate.cpp b/gen/irstate.cpp
+index 6834025129..364e825763 100644
+--- a/gen/irstate.cpp
++++ b/gen/irstate.cpp
+@@ -45,14 +45,14 @@ IrFunction *IRState::func() { return &funcGen().irFunc; }
+ 
+ llvm::Function *IRState::topfunc() { return func()->getLLVMFunc(); }
+ 
+-#if LDC_LLVM_VER >= 1900
++#if LLVM_VERSION_MAJOR >= 19
+ llvm::BasicBlock::iterator IRState::nextAllocaPos() {
+ #else
+ llvm::BasicBlock *IRState::nextAllocaPos() {
+ #endif
+   return funcGen()
+       .allocasBlock
+-#if LDC_LLVM_VER >= 1900
++#if LLVM_VERSION_MAJOR >= 19
+       ->end()
+ #endif
+       ;
+@@ -203,9 +203,7 @@ LLGlobalVariable *
+ getCachedStringLiteralImpl(llvm::Module &module,
+                            llvm::StringMap<LLGlobalVariable *> &cache,
+                            llvm::StringRef key,
+-#if LDC_LLVM_VER >= 1700
+                            std::optional< unsigned > addrspace,
+-#endif
+                            F initFactory) {
+   auto iter = cache.find(key);
+   if (iter != cache.end()) {
+@@ -214,14 +212,9 @@ getCachedStringLiteralImpl(llvm::Module &module,
+ 
+   LLConstant *constant = initFactory();
+ 
+-  auto gvar =
+-      new LLGlobalVariable(module, constant->getType(), true,
+-                           LLGlobalValue::PrivateLinkage, constant,
+-                           ".str", nullptr, llvm::GlobalValue::NotThreadLocal
+-#if LDC_LLVM_VER >= 1700
+-                           , addrspace
+-#endif
+-                           );
++  auto gvar = new LLGlobalVariable(
++      module, constant->getType(), true, LLGlobalValue::PrivateLinkage,
++      constant, ".str", nullptr, llvm::GlobalValue::NotThreadLocal, addrspace);
+   gvar->setUnnamedAddr(LLGlobalValue::UnnamedAddr::Global);
+ 
+   cache[key] = gvar;
+@@ -261,30 +254,18 @@ LLGlobalVariable *IRState::getCachedStringLiteral(StringExp *se) {
+   const llvm::StringRef key(reinterpret_cast<const char *>(keyData.ptr),
+                             keyData.length);
+ 
+-  return getCachedStringLiteralImpl(module, *cache, key,
+-#if LDC_LLVM_VER >= 1700
+-                                    std::nullopt,
+-#endif
+-                                    [se]() {
++  return getCachedStringLiteralImpl(module, *cache, key, std::nullopt, [se]() {
+     // null-terminate
+     return buildStringLiteralConstant(se, se->len + 1);
+   });
+ }
+ 
+-LLGlobalVariable *IRState::getCachedStringLiteral(llvm::StringRef s
+-#if LDC_LLVM_VER >= 1700
+-                                                  ,std::optional< unsigned > addrspace
+-#endif
+-                                                  ) {
+-  return getCachedStringLiteralImpl(module,
+-                                    cachedStringLiterals,
+-                                    s,
+-#if LDC_LLVM_VER >= 1700
+-                                    addrspace,
+-#endif
+-                                    [&]() {
+-    return llvm::ConstantDataArray::getString(context(), s, true);
+-  });
++LLGlobalVariable *
++IRState::getCachedStringLiteral(llvm::StringRef s,
++                                std::optional<unsigned> addrspace) {
++  return getCachedStringLiteralImpl(
++      module, cachedStringLiterals, s, addrspace,
++      [&]() { return llvm::ConstantDataArray::getString(context(), s, true); });
+ }
+ 
+ ////////////////////////////////////////////////////////////////////////////////
+diff --git a/gen/irstate.h b/gen/irstate.h
+index 25e963dce9..0e8983a9f7 100644
+--- a/gen/irstate.h
++++ b/gen/irstate.h
+@@ -24,10 +24,10 @@
+ #include "llvm/ProfileData/InstrProfReader.h"
+ #include <deque>
+ #include <memory>
++#include <optional>
+ #include <set>
+ #include <sstream>
+ #include <vector>
+-#include <optional>
+ 
+ namespace llvm {
+ class LLVMContext;
+@@ -94,9 +94,6 @@ struct IRAsmBlock {
+   std::set<std::string> clobs;
+   size_t outputcount;
+ 
+-  // stores the labels within the asm block
+-  std::vector<Identifier *> internalLabels;
+-
+   CompoundAsmStatement *asmBlock;
+   LLType *retty;
+   unsigned retn;
+@@ -156,7 +153,7 @@ public:
+   IrFunction *func();
+   llvm::Function *topfunc();
+ 
+-#if LDC_LLVM_VER >= 1900
++#if LLVM_VERSION_MAJOR >= 19
+   llvm::BasicBlock::iterator nextAllocaPos();
+ #else
+   llvm::BasicBlock *nextAllocaPos();
+@@ -220,7 +217,6 @@ public:
+ 
+   // for inline asm
+   IRAsmBlock *asmBlock = nullptr;
+-  std::ostringstream nakedAsm;
+ 
+   // Globals to pin in the llvm.used array to make sure they are not
+   // eliminated.
+@@ -254,11 +250,9 @@ public:
+   // calls with a StringExp with matching data will return the same variable.
+   // Exception: ulong[]-typed hex strings (not null-terminated either).
+   llvm::GlobalVariable *getCachedStringLiteral(StringExp *se);
+-  llvm::GlobalVariable *getCachedStringLiteral(llvm::StringRef s
+-#if LDC_LLVM_VER >= 1700
+-                                               ,std::optional< unsigned > = std::nullopt
+-#endif
+-                                               );
++  llvm::GlobalVariable *
++  getCachedStringLiteral(llvm::StringRef s,
++                         std::optional<unsigned> = std::nullopt);
+ 
+   // List of functions with cpu or features attributes overriden by user
+   std::vector<IrFunction *> targetCpuOrFeaturesOverridden;
+diff --git a/gen/llvm.h b/gen/llvm.h
+index 45f051aa93..0070894b8b 100644
+--- a/gen/llvm.h
++++ b/gen/llvm.h
+@@ -35,10 +35,10 @@ using llvm::APFloat;
+ using llvm::APInt;
+ using llvm::IRBuilder;
+ 
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+ #define GET_INTRINSIC_DECL(_X, _TY)                                              \
+   (llvm::Intrinsic::getOrInsertDeclaration(&gIR->module, llvm::Intrinsic::_X, _TY))
+-#elif LDC_LLVM_VER >= 1900
++#elif LLVM_VERSION_MAJOR >= 19
+ #define GET_INTRINSIC_DECL(_X, _TY)                                              \
+   (llvm::Intrinsic::getDeclaration(&gIR->module, llvm::Intrinsic::_X, _TY))
+ #else
+diff --git a/gen/llvmhelpers.cpp b/gen/llvmhelpers.cpp
+index 0771ab54ca..83d72dcc55 100644
+--- a/gen/llvmhelpers.cpp
++++ b/gen/llvmhelpers.cpp
+@@ -1132,11 +1132,7 @@ LLConstant *DtoConstExpInit(Loc loc, Type *targetType, Expression *exp) {
+   // extend i1 to i8
+   if (llType->isIntegerTy(1)) {
+     llType = LLType::getInt8Ty(gIR->context());
+-#if LDC_LLVM_VER < 1800
+-    val = llvm::ConstantExpr::getZExt(val, llType);
+-#else
+     val = llvm::ConstantFoldCastOperand(llvm::Instruction::ZExt, val, llType, *gDataLayout);
+-#endif
+   }
+ 
+   if (llType == targetLLType)
+@@ -1196,11 +1192,7 @@ LLConstant *DtoConstExpInit(Loc loc, Type *targetType, Expression *exp) {
+            "On initializer integer type mismatch, the target should be wider "
+            "than the source.");
+ 
+-#if LDC_LLVM_VER < 1800
+-    return llvm::ConstantExpr::getZExtOrBitCast(val, target);
+-#else
+     return llvm::ConstantFoldCastOperand(llvm::Instruction::ZExt, val, target, *gDataLayout);
+-#endif
+   }
+ 
+   Logger::println("Unhandled type mismatch, giving up.");
+diff --git a/gen/modules.cpp b/gen/modules.cpp
+index 569859e468..41fcc777b3 100644
+--- a/gen/modules.cpp
++++ b/gen/modules.cpp
+@@ -50,9 +50,7 @@
+ #include "llvm/Support/FileSystem.h"
+ #include "llvm/Support/Path.h"
+ #include "llvm/Transforms/Utils/ModuleUtils.h"
+-#if LDC_LLVM_VER >= 1700
+ #include "llvm/Support/VirtualFileSystem.h"
+-#endif
+ 
+ #if _AIX || __sun
+ #include <alloca.h>
+@@ -340,13 +338,8 @@ void loadInstrProfileData(IRState *irs) {
+     IF_LOG Logger::println("Read profile data from %s",
+                            global.params.datafileInstrProf);
+ 
+-    auto readerOrErr =
+-        llvm::IndexedInstrProfReader::create(global.params.datafileInstrProf
+-#if LDC_LLVM_VER >= 1700
+-                                             ,
+-                                             *llvm::vfs::getRealFileSystem()
+-#endif
+-        );
++    auto readerOrErr = llvm::IndexedInstrProfReader::create(
++        global.params.datafileInstrProf, *llvm::vfs::getRealFileSystem());
+     if (auto E = readerOrErr.takeError()) {
+       handleAllErrors(std::move(E), [&](const llvm::ErrorInfoBase &EI) {
+         error(irs->dmodule->loc, "Could not read profile file '%s': %s",
+diff --git a/gen/ms-cxx-helper.cpp b/gen/ms-cxx-helper.cpp
+index d17cd3c677..815eb5d058 100644
+--- a/gen/ms-cxx-helper.cpp
++++ b/gen/ms-cxx-helper.cpp
+@@ -113,11 +113,7 @@ void cloneBlocks(const std::vector<llvm::BasicBlock *> &srcblocks,
+       if (!newInst)
+         newInst = Inst->clone();
+ 
+-#if LDC_LLVM_VER < 1600
+-      nbb->getInstList().push_back(newInst);
+-#else
+       newInst->insertInto(nbb, nbb->end());
+-#endif
+ 
+       VMap[Inst] = newInst; // Add instruction map to value.
+       if (unwindTo)
+diff --git a/gen/naked.cpp b/gen/naked.cpp
+index 5e5b3ea653..e63ed83d55 100644
+--- a/gen/naked.cpp
++++ b/gen/naked.cpp
+@@ -27,228 +27,6 @@
+ 
+ using namespace dmd;
+ 
+-////////////////////////////////////////////////////////////////////////////////
+-// FIXME: Integrate these functions
+-void AsmStatement_toNakedIR(InlineAsmStatement *stmt, IRState *irs);
+-
+-////////////////////////////////////////////////////////////////////////////////
+-
+-class ToNakedIRVisitor : public Visitor {
+-  IRState *irs;
+-
+-public:
+-  explicit ToNakedIRVisitor(IRState *irs) : irs(irs) {}
+-
+-  //////////////////////////////////////////////////////////////////////////
+-
+-  // Import all functions from class Visitor
+-  using Visitor::visit;
+-
+-  //////////////////////////////////////////////////////////////////////////
+-
+-  void visit(Statement *stmt) override {
+-    error(stmt->loc, "Statement not allowed in naked function");
+-  }
+-
+-  //////////////////////////////////////////////////////////////////////////
+-
+-  void visit(InlineAsmStatement *stmt) override {
+-    AsmStatement_toNakedIR(stmt, irs);
+-  }
+-
+-  //////////////////////////////////////////////////////////////////////////
+-
+-  void visit(CompoundStatement *stmt) override {
+-    IF_LOG Logger::println("CompoundStatement::toNakedIR(): %s",
+-                           stmt->loc.toChars());
+-    LOG_SCOPE;
+-
+-    if (stmt->statements) {
+-      for (auto s : *stmt->statements) {
+-        if (s) {
+-          s->accept(this);
+-        }
+-      }
+-    }
+-  }
+-
+-  //////////////////////////////////////////////////////////////////////////
+-
+-  void visit(ExpStatement *stmt) override {
+-    IF_LOG Logger::println("ExpStatement::toNakedIR(): %s",
+-                           stmt->loc.toChars());
+-    LOG_SCOPE;
+-
+-    // This happens only if there is a ; at the end:
+-    // asm { naked; ... };
+-    // Is this a legal AST?
+-    if (!stmt->exp) {
+-      return;
+-    }
+-
+-    // only expstmt supported in declarations
+-    if (!stmt->exp || stmt->exp->op != EXP::declaration) {
+-      visit(static_cast<Statement *>(stmt));
+-      return;
+-    }
+-
+-    DeclarationExp *d = static_cast<DeclarationExp *>(stmt->exp);
+-    VarDeclaration *vd = d->declaration->isVarDeclaration();
+-    FuncDeclaration *fd = d->declaration->isFuncDeclaration();
+-    EnumDeclaration *ed = d->declaration->isEnumDeclaration();
+-
+-    // and only static variable/function declaration
+-    // no locals or nested stuffies!
+-    if (!vd && !fd && !ed) {
+-      visit(static_cast<Statement *>(stmt));
+-      return;
+-    }
+-    if (vd && !(vd->storage_class & (STCstatic | STCmanifest))) {
+-      error(vd->loc, "non-static variable `%s` not allowed in naked function",
+-            vd->toChars());
+-      return;
+-    }
+-    if (fd && !fd->isStatic()) {
+-      error(fd->loc,
+-            "non-static nested function `%s` not allowed in naked function",
+-            fd->toChars());
+-      return;
+-    }
+-    // enum decls should always be safe
+-
+-    // make sure the symbols gets processed
+-    // TODO: codegen() here is likely incorrect
+-    Declaration_codegen(d->declaration, irs);
+-  }
+-
+-  //////////////////////////////////////////////////////////////////////////
+-
+-  void visit(LabelStatement *stmt) override {
+-    IF_LOG Logger::println("LabelStatement::toNakedIR(): %s",
+-                           stmt->loc.toChars());
+-    LOG_SCOPE;
+-
+-    printLabelName(irs->nakedAsm, mangleExact(irs->func()->decl),
+-                   stmt->ident->toChars());
+-    irs->nakedAsm << ":";
+-
+-    if (stmt->statement) {
+-      stmt->statement->accept(this);
+-    }
+-  }
+-};
+-
+-////////////////////////////////////////////////////////////////////////////////
+-
+-void DtoDefineNakedFunction(FuncDeclaration *fd) {
+-  IF_LOG Logger::println("DtoDefineNakedFunction(%s)", mangleExact(fd));
+-  LOG_SCOPE;
+-
+-  // we need to do special processing on the body, since we only want
+-  // to allow actual inline asm blocks to reach the final asm output
+-
+-  std::ostringstream &asmstr = gIR->nakedAsm;
+-
+-  // build function header
+-
+-  // FIXME: could we perhaps use llvm asmwriter to give us these details ?
+-
+-  const char *mangle = mangleExact(fd);
+-  std::string fullmangle; // buffer only
+-
+-  const auto &triple = *global.params.targetTriple;
+-  bool const isWin = triple.isOSWindows();
+-  bool const isDarwin = triple.isOSDarwin();
+-
+-  // osx is different
+-  // also mangling has an extra underscore prefixed
+-  if (isDarwin) {
+-    fullmangle += '_';
+-    fullmangle += mangle;
+-    mangle = fullmangle.c_str();
+-
+-    asmstr << "\t.section\t__TEXT,__text,regular,pure_instructions"
+-           << std::endl;
+-    asmstr << "\t.globl\t" << mangle << std::endl;
+-    if (fd->isInstantiated()) {
+-      asmstr << "\t.weak_definition\t" << mangle << std::endl;
+-    }
+-    asmstr << "\t.p2align\t4, 0x90" << std::endl;
+-    asmstr << mangle << ":" << std::endl;
+-  }
+-  // Windows is different
+-  else if (isWin) {
+-    // mangled names starting with '?' (MSVC++ symbols) apparently need quoting
+-    if (mangle[0] == '?') {
+-      fullmangle += '"';
+-      fullmangle += mangle;
+-      fullmangle += '"';
+-      mangle = fullmangle.c_str();
+-    } else if (triple.isArch32Bit()) {
+-      // prepend extra underscore for Windows x86
+-      fullmangle += '_';
+-      fullmangle += mangle;
+-      mangle = fullmangle.c_str();
+-    }
+-
+-    asmstr << "\t.def\t" << mangle << ";" << std::endl;
+-    // hard code these two numbers for now since gas ignores .scl and llvm
+-    // is defaulting to .type 32 for everything I have seen
+-    asmstr << "\t.scl\t2;" << std::endl;
+-    asmstr << "\t.type\t32;" << std::endl;
+-    asmstr << "\t.endef" << std::endl;
+-
+-    if (fd->isInstantiated()) {
+-      asmstr << "\t.section\t.text,\"xr\",discard," << mangle << std::endl;
+-    } else {
+-      asmstr << "\t.text" << std::endl;
+-    }
+-    asmstr << "\t.globl\t" << mangle << std::endl;
+-    asmstr << "\t.p2align\t4, 0x90" << std::endl;
+-    asmstr << mangle << ":" << std::endl;
+-  } else {
+-    if (fd->isInstantiated()) {
+-      asmstr << "\t.section\t.text." << mangle << ",\"axG\", at progbits,"
+-             << mangle << ",comdat" << std::endl;
+-      asmstr << "\t.weak\t" << mangle << std::endl;
+-    } else {
+-      asmstr << "\t.text" << std::endl;
+-      asmstr << "\t.globl\t" << mangle << std::endl;
+-    }
+-    asmstr << "\t.p2align\t4, 0x90" << std::endl;
+-    asmstr << "\t.type\t" << mangle << ", at function" << std::endl;
+-    asmstr << mangle << ":" << std::endl;
+-  }
+-
+-  // emit body
+-  ToNakedIRVisitor v(gIR);
+-  fd->fbody->accept(&v);
+-
+-  // We could have generated new errors in toNakedIR(), but we are in codegen
+-  // already so we have to abort here.
+-  if (global.errors) {
+-    fatal();
+-  }
+-
+-  // emit size after body
+-  // llvm does this on linux, but not on osx or Win
+-  if (!(isWin || isDarwin)) {
+-    asmstr << "\t.size\t" << mangle << ", .-" << mangle << std::endl
+-           << std::endl;
+-  }
+-
+-  gIR->module.appendModuleInlineAsm(asmstr.str());
+-  asmstr.str("");
+-
+-  if (global.params.dllexport ||
+-      (global.params.targetTriple->isOSWindows() && fd->isExport())) {
+-    // Embed a linker switch telling the MS linker to export the naked function.
+-    // This mimics the effect of the dllexport attribute for regular functions.
+-    const auto linkerSwitch = std::string("/EXPORT:") + mangle;
+-    gIR->addLinkerOption(llvm::StringRef(linkerSwitch));
+-  }
+-}
+-
+ ////////////////////////////////////////////////////////////////////////////////
+ 
+ void emitABIReturnAsmStmt(IRAsmBlock *asmblock, Loc loc,
+@@ -436,7 +214,7 @@ DValue *DtoInlineAsmExpr(Loc loc, FuncDeclaration *fd,
+   LLSmallVector<LLValue *, 8> operands;
+   LLSmallVector<LLType *, 8> indirectTypes;
+   operands.reserve(n);
+-    
++
+   Type *returnType = fd->type->nextOf();
+   const size_t cisize = constraintInfo.size();
+   const size_t minRequired = n + (returnType->ty == TY::Tvoid ? 0 : 1);
+diff --git a/gen/optimizer.cpp b/gen/optimizer.cpp
+index 7029959452..e418774715 100644
+--- a/gen/optimizer.cpp
++++ b/gen/optimizer.cpp
+@@ -36,11 +36,7 @@
+ #include "driver/targetmachine.h"
+ #endif
+ 
+-#if LDC_LLVM_VER < 1700
+-#include "llvm/ADT/Triple.h"
+-#else
+ #include "llvm/TargetParser/Triple.h"
+-#endif
+ #include "llvm/Analysis/InlineCost.h"
+ #include "llvm/Analysis/TargetLibraryInfo.h"
+ #include "llvm/Analysis/TargetTransformInfo.h"
+@@ -52,15 +48,12 @@
+ #include "llvm/LinkAllPasses.h"
+ #include "llvm/Support/CommandLine.h"
+ #include "llvm/Target/TargetMachine.h"
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+ #include "llvm/Transforms/Utils/Instrumentation.h"
+ #else
+ #include "llvm/Transforms/Instrumentation.h"
+ #endif
+ #include "llvm/Transforms/IPO.h"
+-#if LDC_LLVM_VER < 1700
+-#include "llvm/Transforms/IPO/PassManagerBuilder.h"
+-#endif
+ #include "llvm/Transforms/Instrumentation/MemorySanitizer.h"
+ #include "llvm/Transforms/Instrumentation/ThreadSanitizer.h"
+ #include "llvm/Transforms/Instrumentation/AddressSanitizer.h"
+@@ -212,7 +205,7 @@ static OptimizationLevel getOptimizationLevel(){
+ #ifndef IN_JITRT
+ static void addAddressSanitizerPasses(ModulePassManager &mpm,
+                                       OptimizationLevel level
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+                                       ,
+                                       ThinOrFullLTOPhase
+ #endif
+@@ -223,11 +216,7 @@ static void addAddressSanitizerPasses(ModulePassManager &mpm,
+   aso.UseAfterScope = true;
+   aso.UseAfterReturn = opts::fSanitizeAddressUseAfterReturn;
+ 
+-#if LDC_LLVM_VER >= 1600
+   mpm.addPass(AddressSanitizerPass(aso));
+-#else
+-  mpm.addPass(ModuleAddressSanitizerPass(aso));
+-#endif
+ }
+ 
+ static void addMemorySanitizerPass(ModulePassManager &mpm,
+@@ -236,13 +225,8 @@ static void addMemorySanitizerPass(ModulePassManager &mpm,
+   int trackOrigins = fSanitizeMemoryTrackOrigins;
+   bool recover = opts::isSanitizerRecoveryEnabled(opts::MemorySanitizer);
+   bool kernel = false;
+-#if LDC_LLVM_VER >= 1600
+   mpm.addPass(MemorySanitizerPass(
+       MemorySanitizerOptions{trackOrigins, recover, kernel}));
+-#else
+-  fpm.addPass(MemorySanitizerPass(
+-      MemorySanitizerOptions{trackOrigins, recover, kernel}));
+-#endif
+ 
+   // MemorySanitizer inserts complex instrumentation that mostly follows
+   // the logic of the original code, but operates on "shadow" values.
+@@ -260,7 +244,7 @@ static void addMemorySanitizerPass(ModulePassManager &mpm,
+ }
+ static void addThreadSanitizerPass(ModulePassManager &mpm,
+                                    OptimizationLevel level
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+                                    ,
+                                    ThinOrFullLTOPhase
+ #endif
+@@ -271,18 +255,13 @@ static void addThreadSanitizerPass(ModulePassManager &mpm,
+ 
+ static void addSanitizerCoveragePass(ModulePassManager &mpm,
+                                      OptimizationLevel level
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+                                      ,
+                                      ThinOrFullLTOPhase
+ #endif
+ ) {
+-#if LDC_LLVM_VER >= 1600
+   mpm.addPass(SanitizerCoveragePass(
+       opts::getSanitizerCoverageOptions()));
+-#else
+-  mpm.addPass(ModuleSanitizerCoveragePass(
+-      opts::getSanitizerCoverageOptions()));
+-#endif
+ }
+ // Adds PGO instrumentation generation and use passes.
+ static void addPGOPasses(ModulePassManager &mpm, OptimizationLevel level) {
+@@ -291,13 +270,7 @@ static void addPGOPasses(ModulePassManager &mpm, OptimizationLevel level) {
+     options.NoRedZone = global.params.disableRedZone;
+     if (global.params.datafileInstrProf)
+       options.InstrProfileOutput = global.params.datafileInstrProf;
+-    mpm.addPass(
+-#if LDC_LLVM_VER < 1800
+-      InstrProfiling(options)
+-#else
+-      InstrProfilingLoweringPass(options)
+-#endif // LDC_LLVM_VER < 1800
+-    );
++    mpm.addPass(InstrProfilingLoweringPass(options));
+   } else if (opts::isUsingASTBasedPGOProfile()) {
+     // We are generating code with PGO profile information available.
+     // Do indirect call promotion from -O1
+@@ -310,7 +283,7 @@ static void addPGOPasses(ModulePassManager &mpm, OptimizationLevel level) {
+ 
+ static void addStripExternalsPass(ModulePassManager &mpm,
+                                   OptimizationLevel level
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+                                   ,
+                                   ThinOrFullLTOPhase
+ #endif
+@@ -327,7 +300,7 @@ static void addStripExternalsPass(ModulePassManager &mpm,
+ 
+ static void addSimplifyDRuntimeCallsPass(ModulePassManager &mpm,
+                                          OptimizationLevel level
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+                                          ,
+                                          ThinOrFullLTOPhase
+ #endif
+@@ -342,7 +315,7 @@ static void addSimplifyDRuntimeCallsPass(ModulePassManager &mpm,
+ 
+ static void addGarbageCollect2StackPass(ModulePassManager &mpm,
+                                         OptimizationLevel level
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+                                         ,
+                                         ThinOrFullLTOPhase
+ #endif
+@@ -356,58 +329,48 @@ static void addGarbageCollect2StackPass(ModulePassManager &mpm,
+ }
+ 
+ #ifndef IN_JITRT
+-static llvm::Optional<PGOOptions> getPGOOptions() {
++static std::optional<PGOOptions> getPGOOptions() {
+   // FIXME: Do we have these anywhere?
+   bool debugInfoForProfiling = false;
+   bool pseudoProbeForProfiling = false;
+   if (opts::isInstrumentingForIRBasedPGO()) {
+     return PGOOptions(
+         global.params.datafileInstrProf, "", "",
+-#if LDC_LLVM_VER >= 1700
+         "" /*MemoryProfileUsePath*/,
+-#if LDC_LLVM_VER < 2200
++#if LLVM_VERSION_MAJOR < 22
+         llvm::vfs::getRealFileSystem(),
+-#endif
+ #endif
+         PGOOptions::PGOAction::IRInstr, PGOOptions::CSPGOAction::NoCSAction,
+-#if LDC_LLVM_VER >= 1900
++#if LLVM_VERSION_MAJOR >= 19
+         PGOOptions::ColdFuncOpt::Default,
+ #endif
+         debugInfoForProfiling, pseudoProbeForProfiling);
+   } else if (opts::isUsingIRBasedPGOProfile()) {
+     return PGOOptions(
+         global.params.datafileInstrProf, "", "",
+-#if LDC_LLVM_VER >= 1700
+         "" /*MemoryProfileUsePath*/,
+-#if LDC_LLVM_VER < 2200
++#if LLVM_VERSION_MAJOR < 22
+         llvm::vfs::getRealFileSystem(),
+-#endif
+ #endif
+         PGOOptions::PGOAction::IRUse, PGOOptions::CSPGOAction::NoCSAction,
+-#if LDC_LLVM_VER >= 1900
++#if LLVM_VERSION_MAJOR >= 19
+         PGOOptions::ColdFuncOpt::Default,
+ #endif
+         debugInfoForProfiling, pseudoProbeForProfiling);
+   } else if (opts::isUsingSampleBasedPGOProfile()) {
+     return PGOOptions(
+         global.params.datafileInstrProf, "", "",
+-#if LDC_LLVM_VER >= 1700
+         "" /*MemoryProfileUsePath*/,
+-#if LDC_LLVM_VER < 2200
++#if LLVM_VERSION_MAJOR < 22
+         llvm::vfs::getRealFileSystem(),
+-#endif
+ #endif
+         PGOOptions::PGOAction::SampleUse, PGOOptions::CSPGOAction::NoCSAction,
+-#if LDC_LLVM_VER >= 1900
++#if LLVM_VERSION_MAJOR >= 19
+         PGOOptions::ColdFuncOpt::Default,
+ #endif
+         debugInfoForProfiling, pseudoProbeForProfiling);
+   }
+-#if LDC_LLVM_VER < 1600
+-  return None;
+-#else
+   return std::nullopt;
+-#endif
+ }
+ #endif // !IN_JITRT
+ 
+@@ -472,17 +435,9 @@ void runOptimizationPasses(llvm::Module *M, llvm::TargetMachine *TM) {
+   bool debugLogging = false;
+   ppo.Indent = false;
+   ppo.SkipAnalyses = false;
+-#if LDC_LLVM_VER < 1600
+-  StandardInstrumentations si(debugLogging, /*VerifyEach=*/false, ppo);
+-#else
+   StandardInstrumentations si(M->getContext(), debugLogging, /*VerifyEach=*/false, ppo);
+-#endif
+ 
+-#if LDC_LLVM_VER < 1700
+-  si.registerCallbacks(pic, &fam);
+-#else
+   si.registerCallbacks(pic, &mam);
+-#endif
+ 
+   PassBuilder pb(TM, getPipelineTuningOptions(optLevelVal, sizeLevelVal),
+ #ifdef IN_JITRT
+@@ -566,14 +521,14 @@ void runOptimizationPasses(llvm::Module *M, llvm::TargetMachine *TM) {
+ 
+   if (optLevelVal == 0) {
+ #ifdef IN_JITRT
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+     const ThinOrFullLTOPhase ltoPrelink = ThinOrFullLTOPhase::None;
+ #else
+     const bool ltoPrelink = false;
+-#endif // LDC_LLVM_VER >= 2000
++#endif
+     mpm = pb.buildO0DefaultPipeline(level, ltoPrelink);
+-#else
+-#if LDC_LLVM_VER >= 2000
++#else // !IN_JITRT
++#if LLVM_VERSION_MAJOR >= 20
+     const ThinOrFullLTOPhase ltoPrelink =
+         opts::isUsingLTO()
+             ? (opts::isUsingThinLTO() ? ThinOrFullLTOPhase::ThinLTOPreLink
+@@ -581,15 +536,13 @@ void runOptimizationPasses(llvm::Module *M, llvm::TargetMachine *TM) {
+             : ThinOrFullLTOPhase::None;
+ #else
+     const bool ltoPrelink = opts::isUsingLTO();
+-#endif // LDC_LLVM_VER >= 2000
++#endif
+     mpm = pb.buildO0DefaultPipeline(level, ltoPrelink);
+-#if LDC_LLVM_VER >= 1700
+   } else if (opts::ltoFatObjects && opts::isUsingLTO()) {
+     mpm = pb.buildFatLTODefaultPipeline(level,
+                                         opts::isUsingThinLTO(),
+                                         opts::isUsingThinLTO()
+     );
+-#endif
+   } else if (opts::isUsingThinLTO()) {
+     mpm = pb.buildThinLTOPreLinkDefaultPipeline(level);
+   } else if (opts::isUsingLTO()) {
+diff --git a/gen/optimizer.h b/gen/optimizer.h
+index 8ba3521a28..688b654069 100644
+--- a/gen/optimizer.h
++++ b/gen/optimizer.h
+@@ -19,12 +19,6 @@
+ 
+ #include "llvm/Support/CommandLine.h"
+ 
+-namespace llvm {
+-#if LDC_LLVM_VER < 1800
+-using CodeGenOptLevel = llvm::CodeGenOpt::Level;
+-#endif
+-}
+-
+ namespace llvm {
+ class raw_ostream;
+ }
+diff --git a/gen/passes/DLLImportRelocation.cpp b/gen/passes/DLLImportRelocation.cpp
+index d2dc6c8369..288f71cbfd 100644
+--- a/gen/passes/DLLImportRelocation.cpp
++++ b/gen/passes/DLLImportRelocation.cpp
+@@ -272,11 +272,7 @@ bool DLLImportRelocation::run(Module &m) {
+   Impl impl(m);
+   bool hasChanged = false;
+ 
+-#if LDC_LLVM_VER >= 1700
+   for (GlobalVariable &global : m.globals()) {
+-#else
+-  for (GlobalVariable &global : m.getGlobalList()) {
+-#endif
+     // TODO: thread-local globals would need to be initialized in a separate TLS
+     // ctor
+     if (!global.hasInitializer() || global.isThreadLocal())
+diff --git a/gen/passes/GarbageCollect2Stack.cpp b/gen/passes/GarbageCollect2Stack.cpp
+index 4c9b82c0c8..a788789c86 100644
+--- a/gen/passes/GarbageCollect2Stack.cpp
++++ b/gen/passes/GarbageCollect2Stack.cpp
+@@ -24,7 +24,7 @@
+ #include "llvm/ADT/Statistic.h"
+ #include "llvm/ADT/StringMap.h"
+ #include "llvm/ADT/StringSwitch.h"
+-#if LDC_LLVM_VER >= 2100
++#if LLVM_VERSION_MAJOR >= 21
+ #include "llvm/IR/AbstractCallSite.h"
+ #endif
+ #include "llvm/IR/Constants.h"
+@@ -100,7 +100,7 @@ Value* FunctionInfo::promote(CallBase *CB, IRBuilder<> &B, const G2StackAnalysis
+   // FIXME: set alignment on alloca?
+   return new AllocaInst(
+       Ty, BB.getModule()->getDataLayout().getAllocaAddrSpace(), ".nongc_mem",
+-#if LDC_LLVM_VER >= 1900
++#if LLVM_VERSION_MAJOR >= 19
+       BB.begin()
+ #else
+       &(*BB.begin())
+@@ -352,7 +352,7 @@ static void RemoveCall(CallBase *CB, const G2StackAnalysis &A) {
+   // the dominator tree here.
+   if (auto Invoke = dyn_cast<InvokeInst>(static_cast<Instruction *>(CB))) {
+     BranchInst::Create(Invoke->getNormalDest(),
+-#if LDC_LLVM_VER >= 1900
++#if LLVM_VERSION_MAJOR >= 19
+                        Invoke->getIterator()
+ #else
+                        Invoke
+@@ -363,7 +363,7 @@ static void RemoveCall(CallBase *CB, const G2StackAnalysis &A) {
+ 
+   // Remove the runtime call.
+   if (A.CGNode) {
+-#if LDC_LLVM_VER >= 2100
++#if LLVM_VERSION_MAJOR >= 21
+     //FIXME: Look into using `LazyCallGraph` and the new pass manager
+     for (auto I = A.CGNode->begin(); ; I++) {
+       assert(I != A.CGNode->end() && "Cannot find callsite to remove!");
+@@ -755,7 +755,7 @@ bool isSafeToStackAllocate(BasicBlock::iterator Alloc, Value *V,
+       auto B = CB->arg_begin(), E = CB->arg_end();
+       for (auto A = B; A != E; ++A) {
+         if (A->get() == V) {
+-#if LDC_LLVM_VER >= 2100
++#if LLVM_VERSION_MAJOR >= 21
+           if (!(CB->paramHasAttr(A - B, llvm::Attribute::AttrKind::Captures) &&
+                 capturesNothing(
+                     CB->getParamAttr(A - B, llvm::Attribute::AttrKind::Captures)
+diff --git a/gen/passes/SimplifyDRuntimeCalls.cpp b/gen/passes/SimplifyDRuntimeCalls.cpp
+index 9dd672f20b..fd698cfeb9 100644
+--- a/gen/passes/SimplifyDRuntimeCalls.cpp
++++ b/gen/passes/SimplifyDRuntimeCalls.cpp
+@@ -160,7 +160,7 @@ Value *ArraySliceCopyOpt::CallOptimizer(Function *Callee, CallInst *CI,
+     Sz = (Int->getValue() * ElemSz->getValue()).getZExtValue();
+   }
+ 
+-#if LDC_LLVM_VER >= 2100
++#if LLVM_VERSION_MAJOR >= 21
+   llvm::LocationSize Sz2 =
+       (Sz == llvm::MemoryLocation::UnknownSize)
+           ? llvm::LocationSize::beforeOrAfterPointer()
+diff --git a/gen/pgo_ASTbased.cpp b/gen/pgo_ASTbased.cpp
+index 723cabdd95..c1937b5539 100644
+--- a/gen/pgo_ASTbased.cpp
++++ b/gen/pgo_ASTbased.cpp
+@@ -38,13 +38,17 @@ llvm::cl::opt<bool, false, opts::FlagParser<bool>> enablePGOIndirectCalls(
+     "pgo-indirect-calls", llvm::cl::ZeroOrMore, llvm::cl::Hidden,
+     llvm::cl::desc("(*) Enable PGO of indirect calls"),
+     llvm::cl::init(true));
+-}
+ 
+-#if LDC_LLVM_VER >= 1800
+-namespace llvm::support {
+-  const auto little = llvm::endianness::little;
+-}
++uint64_t inLittleEndian(uint64_t x) {
++#if LLVM_VERSION_MAJOR >= 22
++  return llvm::support::endian::byte_swap<uint64_t>(x,
++                                                    llvm::endianness::little);
++#else
++  return llvm::support::endian::byte_swap<uint64_t, llvm::endianness::little>(
++      x);
+ #endif
++}
++} // anonymous namespace
+ 
+ /// \brief Stable hasher for PGO region counters.
+ ///
+@@ -121,8 +125,7 @@ public:
+ 
+     // Pass through MD5 if enough work has built up.
+     if (Count && Count % NumTypesPerWord == 0) {
+-      using namespace llvm::support;
+-      uint64_t Swapped = endian::byte_swap<uint64_t, little>(Working);
++      uint64_t Swapped = inLittleEndian(Working);
+       MD5.update(llvm::ArrayRef<uint8_t>((uint8_t *)&Swapped, sizeof(Swapped)));
+       Working = 0;
+     }
+@@ -142,8 +145,7 @@ public:
+ 
+     // Check for remaining work in Working.
+     if (Working) {
+-      using namespace llvm::support;
+-      uint64_t Swapped = endian::byte_swap<uint64_t, little>(Working);
++      uint64_t Swapped = inLittleEndian(Working);
+       MD5.update(llvm::ArrayRef<uint8_t>((uint8_t *)&Swapped, sizeof(Swapped)));
+     }
+ 
+@@ -924,11 +926,7 @@ void CodeGenPGO::loadRegionCounts(llvm::IndexedInstrProfReader *PGOReader,
+   auto EC = RecordExpected.takeError();
+ 
+   if (EC) {
+-#if LDC_LLVM_VER >= 1700
+     auto IPE = std::get<0>(llvm::InstrProfError::take(std::move(EC)));
+-#else
+-    auto IPE = llvm::InstrProfError::take(std::move(EC));
+-#endif
+     if (IPE == llvm::instrprof_error::unknown_function) {
+       IF_LOG Logger::println("No profile data for function: %s",
+                              FuncName.c_str());
+diff --git a/gen/runtime.cpp b/gen/runtime.cpp
+index 5d3cbb6ebf..7ac04957e1 100644
+--- a/gen/runtime.cpp
++++ b/gen/runtime.cpp
+@@ -32,9 +32,7 @@
+ #include "ir/irtypefunction.h"
+ #include "llvm/Bitcode/BitcodeWriter.h"
+ #include "llvm/IR/Attributes.h"
+-#if LDC_LLVM_VER >= 1600
+ #include "llvm/Support/ModRef.h"
+-#endif
+ #include "llvm/IR/Module.h"
+ #include "llvm/Support/CommandLine.h"
+ #include "llvm/Support/MemoryBuffer.h"
+@@ -511,15 +509,9 @@ static void buildRuntimeModule() {
+   // `readonly`
+   {
+     auto addReadOnly = [&](AttrSet& a) {
+-#if LDC_LLVM_VER >= 1600
+       a = a.merge(AttrSet(llvm::AttributeList().addFnAttribute(
+                           context, llvm::Attribute::getWithMemoryEffects(
+                             context, llvm::MemoryEffects::readOnly()))));
+-#else
+-      llvm::AttrBuilder ab(context);
+-      ab.addAttribute(llvm::Attribute::ReadOnly);
+-      a = a.addToFunction(ab);
+-#endif
+     };
+     addReadOnly(Attr_ReadOnly);
+     addReadOnly(Attr_ReadOnly_NoUnwind);
+@@ -542,7 +534,7 @@ static void buildRuntimeModule() {
+   {
+     auto addCapturesNone = [&](int extra, AttrSet& a) {
+       llvm::AttrBuilder ab(context);
+-#if LDC_LLVM_VER >= 2100
++#if LLVM_VERSION_MAJOR >= 21
+       ab.addCapturesAttr(llvm::CaptureInfo::none());
+ #else
+       ab.addAttribute(llvm::Attribute::NoCapture);
+diff --git a/gen/semantic-dcompute.cpp b/gen/semantic-dcompute.cpp
+index 5fe5f64e16..1cf3fb4843 100644
+--- a/gen/semantic-dcompute.cpp
++++ b/gen/semantic-dcompute.cpp
+@@ -92,6 +92,9 @@ struct DComputeSemanticAnalyser : public StoppableVisitor {
+       return;
+     }
+ 
++    if (!decl->type)
++      return;
++
+     if (decl->type->ty == TY::Taarray) {
+       error(decl->loc, "associative arrays not allowed in `@compute` code");
+       stop = true;
+diff --git a/gen/statements.cpp b/gen/statements.cpp
+index 1bdd3cef30..004fba0656 100644
+--- a/gen/statements.cpp
++++ b/gen/statements.cpp
+@@ -431,7 +431,11 @@ public:
+     if (llvm::ConstantInt *const_val = llvm::dyn_cast<llvm::ConstantInt>(cond_val)) {
+       Statement *executed = stmt->ifbody;
+       Statement *skipped = stmt->elsebody;
++#if LLVM_VERSION_MAJOR >= 21
++      if (const_val->isZero()) {
++#else
+       if (const_val->isZeroValue()) {
++#endif
+         std::swap(executed, skipped);
+       }
+       if (!containsLabel(skipped)) {
+@@ -441,7 +445,11 @@ public:
+           irs->DBuilder.EmitBlockStart(executed->loc);
+         }
+         // True condition, the branch is taken so emit counter increment.
++#if LLVM_VERSION_MAJOR >= 21
++        if (!const_val->isZero()) {
++#else
+         if (!const_val->isZeroValue()) {
++#endif
+           PGO.emitCounterIncrement(stmt);
+         }
+         if (executed) {
+@@ -1542,17 +1550,21 @@ public:
+     auto &PGO = irs->funcGen().pgo;
+     PGO.setCurrentStmt(stmt);
+ 
++    auto fd = irs->func()->decl;
++
++    const auto getFullAsmLabelString = [stmt, fd]() {
++      std::stringstream s;
++      printLabelName(s, mangleExact(fd), stmt->ident->toChars());
++      s << ":";
++      return s.str();
++    };
++
+     // if it's an inline asm label, we don't create a basicblock, just emit it
+     // in the asm
+     if (irs->asmBlock) {
+       auto a = new IRAsmStmt;
+-      std::stringstream label;
+-      printLabelName(label, mangleExact(irs->func()->decl),
+-                     stmt->ident->toChars());
+-      label << ":";
+-      a->code = label.str();
++      a->code = getFullAsmLabelString();
+       irs->asmBlock->s.push_back(a);
+-      irs->asmBlock->internalLabels.push_back(stmt->ident);
+ 
+       // disable inlining
+       irs->func()->setNeverInline();
+@@ -1566,6 +1578,13 @@ public:
+       }
+ 
+       irs->ir->SetInsertPoint(labelBB);
++
++      // in a naked DMD-style inline-asm function, create an assembly label too
++      // (so that inline-asm can jump to it directly)
++      if (fd->isNaked()) {
++        DtoInlineAsmExpr(stmt->loc, getFullAsmLabelString(), "", {}, {},
++                         irs->ir->getVoidTy());
++      }
+     }
+ 
+     PGO.emitCounterIncrement(stmt);
+diff --git a/gen/target.cpp b/gen/target.cpp
+index 7f77923a39..16622a9922 100644
+--- a/gen/target.cpp
++++ b/gen/target.cpp
+@@ -83,10 +83,8 @@ llvm::Type *getRealType(const llvm::Triple &triple) {
+ 
+   case Triple::riscv32:
+   case Triple::riscv64:
+-#if LDC_LLVM_VER >= 1600
+   case Triple::loongarch32:
+   case Triple::loongarch64:
+-#endif // LDC_LLVM_VER >= 1600
+     return LLType::getFP128Ty(ctx);
+ 
+   case Triple::wasm32:
+diff --git a/gen/tocall.cpp b/gen/tocall.cpp
+index edc5ef837c..c1b0bc1b3c 100644
+--- a/gen/tocall.cpp
++++ b/gen/tocall.cpp
+@@ -1045,7 +1045,7 @@ DValue *DtoCallFunction(Loc loc, Type *resulttype, DValue *fnval,
+     if (cf->isIntrinsic()) { // override intrinsic attrs
+       attrlist =
+           llvm::Intrinsic::getAttributes(gIR->context(), cf->getIntrinsicID()
+-#if LDC_LLVM_VER >= 2100
++#if LLVM_VERSION_MAJOR >= 21
+                                          ,cf->getFunctionType()
+ #endif
+                                          );
+diff --git a/gen/toconstelem.cpp b/gen/toconstelem.cpp
+index c32f6f4a7b..9ded6dddc0 100644
+--- a/gen/toconstelem.cpp
++++ b/gen/toconstelem.cpp
+@@ -522,11 +522,7 @@ public:
+             // extend i1 to i8
+             if (c->getType()->isIntegerTy(1)) {
+               LLType *I8PtrTy = LLType::getInt8Ty(p->context());
+-#if LDC_LLVM_VER < 1800
+-              c = llvm::ConstantExpr::getZExt(c, I8PtrTy);
+-#else
+               c = llvm::ConstantFoldCastOperand(llvm::Instruction::ZExt, c, I8PtrTy, *gDataLayout);
+-#endif
+             }
+             varInits[e->sd->fields[i]] = c;
+           }
+diff --git a/gen/toir.cpp b/gen/toir.cpp
+index 0a22d1b0d5..e746c6bbcd 100644
+--- a/gen/toir.cpp
++++ b/gen/toir.cpp
+@@ -1016,11 +1016,7 @@ public:
+   }
+ 
+   static llvm::PointerType * getWithSamePointeeType(llvm::PointerType *p, unsigned addressSpace) {
+-#if LDC_LLVM_VER >= 1700
+     return llvm::PointerType::get(p->getContext(), addressSpace);
+-#else
+-    return llvm::PointerType::getWithSamePointeeType(p, addressSpace);
+-#endif
+   }
+ 
+   //////////////////////////////////////////////////////////////////////////////
+diff --git a/gen/trycatchfinally.cpp b/gen/trycatchfinally.cpp
+index b486228874..510e0c0ac6 100644
+--- a/gen/trycatchfinally.cpp
++++ b/gen/trycatchfinally.cpp
+@@ -323,7 +323,7 @@ CleanupScope::CleanupScope(llvm::BasicBlock *beginBlock,
+ }
+ 
+ namespace {
+-#if LDC_LLVM_VER >= 1900
++#if LLVM_VERSION_MAJOR >= 19
+ llvm::BasicBlock::iterator getTerminatorPos(llvm::BasicBlock *bb) {
+   auto it = bb->rbegin().getReverse();
+   assert(it->isTerminator());
+diff --git a/gen/uda.cpp b/gen/uda.cpp
+index a89c2c1e75..63f84e5699 100644
+--- a/gen/uda.cpp
++++ b/gen/uda.cpp
+@@ -210,11 +210,7 @@ void applyAttrAllocSize(StructLiteralExp *sle, IrFunction *irFunc) {
+   if (numArgIdx >= 0) {
+     builder.addAllocSizeAttr(llvmSizeIdx, llvmNumIdx);
+   } else {
+-#if LDC_LLVM_VER < 1600
+-    builder.addAllocSizeAttr(llvmSizeIdx, llvm::Optional<unsigned>());
+-#else
+     builder.addAllocSizeAttr(llvmSizeIdx, std::optional<unsigned>());
+-#endif
+   }
+ 
+   llvm::Function *func = irFunc->getLLVMFunc();
+@@ -307,10 +303,6 @@ void applyAttrTarget(StructLiteralExp *sle, llvm::Function *func,
+   // The current implementation here does not do any checking of the specified
+   // string and simply passes all to llvm.
+ 
+-#if LDC_LLVM_VER >= 1800
+-  #define startswith starts_with
+-#endif
+-
+   checkStructElems(sle, {Type::tstring});
+   llvm::StringRef targetspec = getFirstElemString(sle);
+ 
+@@ -338,20 +330,20 @@ void applyAttrTarget(StructLiteralExp *sle, llvm::Function *func,
+     if (s.empty())
+       continue;
+ 
+-    if (s.startswith("arch=")) {
++    if (s.starts_with("arch=")) {
+       // TODO: be smarter than overwriting the previous arch= setting
+       CPU = s.drop_front(5);
+       continue;
+     }
+-    if (s.startswith("tune=")) {
++    if (s.starts_with("tune=")) {
+       // clang 3.8 ignores tune= too
+       continue;
+     }
+-    if (s.startswith("fpmath=")) {
++    if (s.starts_with("fpmath=")) {
+       // TODO: implementation; clang 3.8 ignores fpmath= too
+       continue;
+     }
+-    if (s.startswith("no-")) {
++    if (s.starts_with("no-")) {
+       std::string f = (std::string("-") + s.drop_front(3)).str();
+       features.emplace_back(std::move(f));
+       continue;
+@@ -370,10 +362,6 @@ void applyAttrTarget(StructLiteralExp *sle, llvm::Function *func,
+                     llvm::join(features.begin(), features.end(), ","));
+     irFunc->targetFeaturesOverridden = true;
+   }
+-
+-#if LDC_LLVM_VER >= 1800
+-  #undef startswith
+-#endif
+ }
+ 
+ void applyAttrAssumeUsed(IRState &irs, StructLiteralExp *sle,
+@@ -438,11 +426,7 @@ bool parseCallingConvention(llvm::StringRef name,
+           .Case("intel_ocl_bicc", llvm::CallingConv::Intel_OCL_BI)
+           .Case("x86_64_sysvcc", llvm::CallingConv::X86_64_SysV)
+           .Case("win64cc", llvm::CallingConv::Win64)
+-#if LDC_LLVM_VER >= 1800
+           .Case("webkit_jscc", llvm::CallingConv::WASM_EmscriptenInvoke)
+-#else
+-          .Case("webkit_jscc", llvm::CallingConv::WebKit_JS)
+-#endif
+           .Case("anyregcc", llvm::CallingConv::AnyReg)
+           .Case("preserve_mostcc", llvm::CallingConv::PreserveMost)
+           .Case("preserve_allcc", llvm::CallingConv::PreserveAll)
+@@ -450,13 +434,8 @@ bool parseCallingConvention(llvm::StringRef name,
+           .Case("swiftcc", llvm::CallingConv::Swift)
+           .Case("swifttailcc", llvm::CallingConv::SwiftTail)
+           .Case("x86_intrcc", llvm::CallingConv::X86_INTR)
+-#if LDC_LLVM_VER >= 1700
+           .Case("hhvmcc", llvm::CallingConv::DUMMY_HHVM)
+           .Case("hhvm_ccc", llvm::CallingConv::DUMMY_HHVM_C)
+-#else
+-          .Case("hhvmcc", llvm::CallingConv::HHVM)
+-          .Case("hhvm_ccc", llvm::CallingConv::HHVM_C)
+-#endif
+           .Case("cxx_fast_tlscc", llvm::CallingConv::CXX_FAST_TLS)
+           .Case("amdgpu_vs", llvm::CallingConv::AMDGPU_VS)
+           .Case("amdgpu_gfx", llvm::CallingConv::AMDGPU_Gfx)
+diff --git a/gen/variable_lifetime.cpp b/gen/variable_lifetime.cpp
+index ed0b772dbf..717760881e 100644
+--- a/gen/variable_lifetime.cpp
++++ b/gen/variable_lifetime.cpp
+@@ -53,7 +53,7 @@ void LocalVariableLifetimeAnnotator::addLocalVariable(llvm::AllocaInst *address,
+ 
+   // Emit lifetime start
+   irs.CreateCallOrInvoke(getLLVMLifetimeStartFn(),
+-#if LDC_LLVM_VER >= 2200
++#if LLVM_VERSION_MAJOR >= 22
+                          {address},
+ #else
+                          {size, address},
+@@ -67,7 +67,7 @@ void LocalVariableLifetimeAnnotator::popScope() {
+     return;
+ 
+   for (const auto &var : scopes.back().variables) {
+-#if LDC_LLVM_VER < 2200
++#if LLVM_VERSION_MAJOR < 22
+     auto size = var.first;
+ #endif
+     auto address = var.second;
+@@ -75,7 +75,7 @@ void LocalVariableLifetimeAnnotator::popScope() {
+     assert(address);
+ 
+     irs.CreateCallOrInvoke(getLLVMLifetimeEndFn(),
+-#if LDC_LLVM_VER >= 2200
++#if LLVM_VERSION_MAJOR >= 22
+                            {address},
+ #else
+                            {size, address},
+@@ -92,7 +92,7 @@ llvm::Function *LocalVariableLifetimeAnnotator::getLLVMLifetimeStartFn() {
+     return lifetimeStartFunction;
+ 
+   lifetimeStartFunction = llvm::Intrinsic::
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+     getOrInsertDeclaration(
+ #else
+     getDeclaration(
+@@ -108,7 +108,7 @@ llvm::Function *LocalVariableLifetimeAnnotator::getLLVMLifetimeEndFn() {
+     return lifetimeEndFunction;
+ 
+   lifetimeEndFunction = llvm::Intrinsic::
+-#if LDC_LLVM_VER >= 2000
++#if LLVM_VERSION_MAJOR >= 20
+     getOrInsertDeclaration(
+ #else
+     getDeclaration(
+diff --git a/ir/iraggr.cpp b/ir/iraggr.cpp
+index 28d2ddddd2..94275dafce 100644
+--- a/ir/iraggr.cpp
++++ b/ir/iraggr.cpp
+@@ -63,10 +63,6 @@ bool IrAggr::useDLLImport() const {
+ //////////////////////////////////////////////////////////////////////////////
+ 
+ LLGlobalVariable *IrAggr::getInitSymbol(bool define) {
+-#if LDC_LLVM_VER >= 1800
+-  #define startswith starts_with
+-#endif
+-
+   if (!init) {
+     const auto irMangle = getIRMangledInitSymbolName(aggrdecl);
+ 
+@@ -74,7 +70,7 @@ LLGlobalVariable *IrAggr::getInitSymbol(bool define) {
+     // special.
+     auto cd = aggrdecl->isClassDeclaration();
+     const bool isBuiltinTypeInfo =
+-        cd && llvm::StringRef(cd->ident->toChars()).startswith("TypeInfo_");
++        cd && llvm::StringRef(cd->ident->toChars()).starts_with("TypeInfo_");
+ 
+     // Only declare the symbol if it isn't yet, otherwise the init symbol of
+     // built-in TypeInfos may clash with an existing base-typed forward
+@@ -108,10 +104,6 @@ LLGlobalVariable *IrAggr::getInitSymbol(bool define) {
+   }
+ 
+   return init;
+-
+-#if LDC_LLVM_VER >= 1800
+-  #undef startswith
+-#endif
+ }
+ 
+ //////////////////////////////////////////////////////////////////////////////
+diff --git a/ir/irtypestruct.cpp b/ir/irtypestruct.cpp
+index cac45683e0..644c196021 100644
+--- a/ir/irtypestruct.cpp
++++ b/ir/irtypestruct.cpp
+@@ -72,7 +72,7 @@ IrTypeStruct *IrTypeStruct::get(StructDeclaration *sd) {
+ 
+   // For ldc.dcomptetypes.Pointer!(uint n,T),
+   // emit { T addrspace(gIR->dcomputetarget->mapping[n])* }
+-  llvm::Optional<DcomputePointer> p;
++  std::optional<DcomputePointer> p;
+ 
+   if (gIR->dcomputetarget && (p = toDcomputePointer(sd))) {
+     // Translate the virtual dcompute address space into the real one for
+diff --git a/runtime/druntime/src/__importc_builtins.di b/runtime/druntime/src/__importc_builtins.di
+index 476852cd82..3c870c1842 100644
+--- a/runtime/druntime/src/__importc_builtins.di
++++ b/runtime/druntime/src/__importc_builtins.di
+@@ -12,6 +12,10 @@
+ 
+ module __builtins;
+ 
++import core.stdc.config : c_long, c_ulong;
++import core.checkedint : adds, subs, muls;
++
++
+ /* gcc relies on internal __builtin_xxxx functions and templates to
+  * accomplish <stdarg.h>. D does the same thing with templates in core.stdc.stdarg.
+  * Here, we redirect the gcc builtin declarations to the equivalent
+@@ -101,6 +105,11 @@ version (DigitalMars)
+     alias __builtin_fabsf = imported!"core.stdc.math".fabsf;
+     alias __builtin_fabsl = imported!"core.stdc.math".fabsl;
+ 
++    alias __builtin_memcmp = imported!"core.stdc.string".memcmp;
++    alias __builtin_memcpy = imported!"core.stdc.string".memcpy;
++    alias __builtin_memmove = imported!"core.stdc.string".memmove;
++    alias __builtin_memset = imported!"core.stdc.string".memset;
++
+     ushort __builtin_bswap16()(ushort value)
+     {
+         return cast(ushort) (((value >> 8) & 0xFF) | ((value << 8) & 0xFF00U));
+@@ -161,6 +170,11 @@ else version (LDC)
+     alias __builtin_fabsf = imported!"ldc.intrinsics".llvm_fabs!float;
+     alias __builtin_fabsl = imported!"ldc.intrinsics".llvm_fabs!real;
+ 
++    alias __builtin_memcmp  = imported!"core.stdc.string".memcmp;
++    alias __builtin_memcpy  = imported!"ldc.intrinsics".llvm_memcpy!size_t;
++    alias __builtin_memmove = imported!"ldc.intrinsics".llvm_memmove!size_t;
++    alias __builtin_memset  = imported!"ldc.intrinsics".llvm_memset!size_t;
++
+     alias __builtin_bswap16 = imported!"ldc.intrinsics".llvm_bswap!ushort;
+     alias __builtin_bswap32 = imported!"ldc.intrinsics".llvm_bswap!uint;
+     alias __builtin_bswap64 = imported!"ldc.intrinsics".llvm_bswap!ulong;
+@@ -172,7 +186,8 @@ else version (LDC)
+ 
+     alias __uint128_t = imported!"core.int128".Cent;
+ 
+-    alias __builtin_alloca = imported!"core.stdc.stdlib".alloca;
++    pragma(LDC_alloca)
++    extern(C) void* __builtin_alloca(size_t size) nothrow @nogc pure;
+ 
+     // gcc builtins:
+ 
+@@ -200,3 +215,233 @@ version (CRuntime_Glibc) version (AArch64)
+     alias __Float32x4_t = __vector(float[4]);
+     alias __Float64x2_t = __vector(double[2]);
+ }
++
++// https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html
++
++private bool overflowOp(alias op, T1, T2, T3)(T1 a, T2 b, ref T3 res)
++{
++    bool overflow = false;
++    res = op(a, b, overflow);
++    return overflow;
++}
++
++private T builtin_opc(alias op, T)(T a, T b, T carry_in, ref T carry_out)
++{
++    carry_out = op(a, b, a) | op(a, carry_in, a);
++    return a;
++}
++
++pragma(inline, true)
++{
++    bool __builtin_add_overflow(T1, T2, T3)(T1 a, T2 b, T3* res) => overflowOp!(adds, T1, T2, T3)(a, b, *res);
++    bool __builtin_sub_overflow(T1, T2, T3)(T1 a, T2 b, T3* res) => overflowOp!(subs, T1, T2, T3)(a, b, *res);
++    bool __builtin_mul_overflow(T1, T2, T3)(T1 a, T2 b, T3* res) => overflowOp!(muls, T1, T2, T3)(a, b, *res);
++    bool __builtin_add_overflow_p(T1, T2, T3)(T1 a, T2 b, T3 c)  => overflowOp!(adds, T1, T2, T3)(a, b, res);
++    bool __builtin_sub_overflow_p(T1, T2, T3)(T1 a, T2 b, T3 c)  => overflowOp!(subs, T1, T2, T3)(a, b, res);
++    bool __builtin_mul_overflow_p(T1, T2, T3)(T1 a, T2 b, T3 c)  => overflowOp!(muls, T1, T2, T3)(a, b, res);
++    bool __builtin_sadd_overflow  ()(int     a, int     b, int*     res) => overflowOp!(adds, int    , int    , int    )(a, b, *res);
++    bool __builtin_saddl_overflow ()(c_long  a, c_long  b, c_long*  res) => overflowOp!(adds, c_long , c_long , c_long )(a, b, *res);
++    bool __builtin_saddll_overflow()(long    a, long    b, long*    res) => overflowOp!(adds, long   , long   , long   )(a, b, *res);
++    bool __builtin_uadd_overflow  ()(uint    a, uint    b, uint*    res) => overflowOp!(adds, uint   , uint   , uint   )(a, b, *res);
++    bool __builtin_uaddl_overflow ()(c_ulong a, c_ulong b, c_ulong* res) => overflowOp!(adds, c_ulong, c_ulong, c_ulong)(a, b, *res);
++    bool __builtin_uaddll_overflow()(ulong   a, ulong   b, ulong*   res) => overflowOp!(adds, ulong  , ulong  , ulong  )(a, b, *res);
++    bool __builtin_ssub_overflow  ()(int     a, int     b, int*     res) => overflowOp!(subs, int    , int    , int    )(a, b, *res);
++    bool __builtin_ssubl_overflow ()(c_long  a, c_long  b, c_long*  res) => overflowOp!(subs, c_long , c_long , c_long )(a, b, *res);
++    bool __builtin_ssubll_overflow()(long    a, long    b, long*    res) => overflowOp!(subs, long   , long   , long   )(a, b, *res);
++    bool __builtin_usub_overflow  ()(uint    a, uint    b, uint*    res) => overflowOp!(subs, uint   , uint   , uint   )(a, b, *res);
++    bool __builtin_usubl_overflow ()(c_ulong a, c_ulong b, c_ulong* res) => overflowOp!(subs, c_ulong, c_ulong, c_ulong)(a, b, *res);
++    bool __builtin_usubll_overflow()(ulong   a, ulong   b, ulong*   res) => overflowOp!(subs, ulong  , ulong  , ulong  )(a, b, *res);
++    bool __builtin_smul_overflow  ()(int     a, int     b, int*     res) => overflowOp!(muls, int    , int    , int    )(a, b, *res);
++    bool __builtin_smull_overflow ()(c_long  a, c_long  b, c_long*  res) => overflowOp!(muls, c_long , c_long , c_long )(a, b, *res);
++    bool __builtin_smulll_overflow()(long    a, long    b, long*    res) => overflowOp!(muls, long   , long   , long   )(a, b, *res);
++    bool __builtin_umul_overflow  ()(uint    a, uint    b, uint*    res) => overflowOp!(muls, uint   , uint   , uint   )(a, b, *res);
++    bool __builtin_umull_overflow ()(c_ulong a, c_ulong b, c_ulong* res) => overflowOp!(muls, c_ulong, c_ulong, c_ulong)(a, b, *res);
++    bool __builtin_umulll_overflow()(ulong   a, ulong   b, ulong*   res) => overflowOp!(muls, ulong  , ulong  , ulong  )(a, b, *res);
++
++    uint  __builtin_addc  ()(uint  a, uint  b, uint  carry_in, uint*  carry_out) => builtin_opc!(adds, uint )(a, b, carry_in, *carry_out);
++    ulong __builtin_addcl ()(ulong a, ulong b, uint  carry_in, ulong* carry_out) => builtin_opc!(adds, ulong)(a, b, carry_in, *carry_out);
++    ulong __builtin_addcll()(ulong a, ulong b, ulong carry_in, ulong* carry_out) => builtin_opc!(adds, ulong)(a, b, carry_in, *carry_out);
++    uint  __builtin_subc  ()(uint  a, uint  b, uint  carry_in, uint*  carry_out) => builtin_opc!(subs, uint )(a, b, carry_in, *carry_out);
++    ulong __builtin_subcl ()(ulong a, ulong b, uint  carry_in, ulong* carry_out) => builtin_opc!(subs, ulong)(a, b, carry_in, *carry_out);
++    ulong __builtin_subcll()(ulong a, ulong b, ulong carry_in, ulong* carry_out) => builtin_opc!(subs, ulong)(a, b, carry_in, *carry_out);
++}
++
++unittest {
++    int r1;
++    assert(__builtin_sadd_overflow(2147483647, 1, &r1) == true);
++    assert(__builtin_sadd_overflow(1, 1, &r1) == false);
++
++    assert(__builtin_ssub_overflow(-2147483648, 1, &r1) == true);
++    assert(__builtin_ssub_overflow(5, 3, &r1) == false);
++
++    assert(__builtin_smul_overflow(2000000000, 2, &r1) == true);
++    assert(__builtin_smul_overflow(10, 20, &r1) == false);
++
++    uint ur;
++    assert(__builtin_uadd_overflow(0xFFFFFFFFu, 1u, &ur) == true);
++    assert(__builtin_uadd_overflow(10u, 20u, &ur) == false);
++
++    assert(__builtin_usub_overflow(0u, 1u, &ur) == true);
++    assert(__builtin_usub_overflow(20u, 10u, &ur) == false);
++
++    assert(__builtin_umul_overflow(0xFFFFFFFFu, 2u, &ur) == true);
++    assert(__builtin_umul_overflow(10u, 20u, &ur) == false);
++
++    uint carry;
++    uint rr = __builtin_addc(1u, 1u, 0u, &carry);
++    assert(rr == 2);
++    assert(carry == 0);
++
++    rr = __builtin_addc(0xFFFFFFFFu, 1u, 0u, &carry);
++    assert(carry == 1);
++
++    rr = __builtin_subc(1u, 1u, 0u, &carry);
++    assert(rr == 0);
++    assert(carry == 0);
++
++    rr = __builtin_subc(0u, 1u, 0u, &carry);
++    assert(carry == 1);
++}
++
++private U signbit(T, U)(T x)
++{
++    T arg = x;
++    return cast(U)arg >> (T.sizeof * 8 - 1);
++}
++
++pragma(inline, true)
++{
++    // https://gcc.gnu.org/onlinedocs/gcc/Bit-Operation-Builtins.html
++    import core.bitop : popcnt, bsr, bsf, rol, ror;
++    private int clz(T)(T x) => bsr(x) ^ ((int.sizeof * 8)-1);
++
++    int __builtin_clz()(uint x)          => clz!uint(x);
++    int __builtin_clzl()(c_ulong x)      => clz!c_ulong(x);
++    int __builtin_clzll()(ulong x)       => clz!ulong(x);
++    int __builtin_clzg(T)(T arg)         => clz(arg);
++
++    int __builtin_ctz()(uint x)          => bsf(x);
++    int __builtin_ctzl()(c_ulong x)      => bsf(x);
++    int __builtin_ctzll()(ulong x)       => bsf(x);
++    int __builtin_ctzg(T)(T arg)         => bsf(arg);
++
++    int __builtin_clrsb()(int x)         => signbit!(int, uint)(x) ? clz!uint(~x) - 1 : clz!uint(x) -1;
++    int __builtin_clrsbl()(c_long x)     => signbit!(c_long, c_ulong)(x) ? clz!c_ulong(~x) - 1 : clz!c_ulong(x) -1;
++    int __builtin_clrsbll()(long x)      => signbit!(long, ulong)(x) ? clz!ulong(~x) - 1 : clz!ulong(x) -1;
++    int __builtin_clrsbg(T, U)(T arg)    => signbit!(T, U)(arg) ? clz!U(~x) - 1 : clz!U(x) -1;
++
++    int __builtin_ffs()(int x)           => x ? bsf(x) + 1 : 0;
++    int __builtin_ffsl()(c_long x)       => x ? bsf(x) + 1 : 0;
++    int __builtin_ffsll()(long x)        => x ? bsf(x) + 1 : 0;
++    int __builtin_ffsg(T)(T arg)         => arg ? bsf(arg) + 1 : 0;
++
++    int __builtin_popcount()(uint x)     => popcnt(x);
++    int __builtin_popcountl()(c_ulong x) => popcnt(x);
++    int __builtin_popcountll()(ulong x)  => popcnt(x);
++    int __builtin_popcountg(T)(T arg)    => popcnt(arg);
++
++    int __builtin_parity()(uint x)      => popcnt(x) % 2;
++    int __builtin_parityl()(c_ulong)    => popcnt(x) % 2;
++    int __builtin_parityll()(ulong)     => popcnt(x) % 2;
++    int __builtin_parityg(T)(T arg)     => popcnt(arg) % 2;
++
++    T __builtin_stdc_bit_ceil(T)(T arg)  => arg <= 1 ? T(1) : T(2) << (T.sizeof * 8 - 1 - clz(arg - 1));
++    T __builtin_stdc_bit_floor(T)(T arg) => arg == 0 ? T(0) : T(1) << (T.sizeof * 8 - 1 - clz(arg));
++    uint __builtin_stdc_bit_width(T)(T arg) => T.sizeof * 8 - clz(arg);
++    uint __builtin_stdc_count_ones (T)(T arg) => popcnt(arg);
++    uint __builtin_stdc_count_zeros(T)(T arg) => popcnt(cast(T) ~arg);
++    uint __builtin_stdc_first_leading_one  (T)(T arg) => clz( arg) + 1U;
++    uint __builtin_stdc_first_leading_zero (T)(T arg) => clz(~arg) + 1U;
++    uint __builtin_stdc_first_trailing_one (T)(T arg) => ctz( arg) + 1U;
++    uint __builtin_stdc_first_trailing_zero(T)(T arg) => ctz(~arg) + 1U;
++    uint __builtin_stdc_has_single_bit(T)(T arg) => popcnt(arg) == 1;
++    T1 __builtin_stdc_rotate_left (T1, T2)(T1 arg1, T2 arg2) => roL(arg1, arg2);
++    T1 __builtin_stdc_rotate_right(T1, T2)(T1 arg1, T2 arg2) => ror(arg1, arg2);
++}
++
++unittest
<Skipped 6647 lines>
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/packages/ldc.git/commitdiff/0cb953558f29825500e177b8a703b59ff88ff4a5



More information about the pld-cvs-commit mailing list