https://github.com/bader updated 
https://github.com/llvm/llvm-project/pull/202829

>From ef16010d9e601f41b6eaa4cd595784d6798f6fc9 Mon Sep 17 00:00:00 2001
From: Alexey Bader <[email protected]>
Date: Tue, 9 Jun 2026 10:07:46 -0700
Subject: [PATCH 1/2] [clang-sycl-linker] Add static archive (.a) support

Add support for static archives of LLVM bitcode files to clang-sycl-linker.
The archive member-selection engine (a symbol-driven fixed-point lazy
extraction loop) is simplified to bitcode-only handling.

clang-sycl-linker gains -l/--library, --whole-archive/--no-whole-archive, and
-u/--undefined options. The previous --bc-library option is removed in favor of
the standard -l mechanism. Inputs (positional files and -l libraries) are now
resolved to in-memory buffers; archive members are pulled in lazily only when
they resolve undefined symbols, and bitcode is loaded with parseBitcodeFile.

Co-Authored-By: Claude
---
 clang/docs/ClangSYCLLinker.rst                |  71 ++-
 .../clang-sycl-linker/archive-extras.ll       | 106 ++++
 .../OffloadTools/clang-sycl-linker/basic.ll   | 105 ++--
 .../OffloadTools/clang-sycl-linker/link.ll    | 133 ++++-
 .../clang-sycl-linker/split-mode.ll           |   6 +-
 .../OffloadTools/clang-sycl-linker/triple.ll  |   2 +
 .../clang-sycl-linker/weak-symbols.ll         | 117 +++++
 clang/tools/clang-sycl-linker/CMakeLists.txt  |   1 +
 .../clang-sycl-linker/ClangSYCLLinker.cpp     | 469 +++++++++++++-----
 clang/tools/clang-sycl-linker/SYCLLinkOpts.td |  21 +-
 10 files changed, 863 insertions(+), 168 deletions(-)
 create mode 100644 clang/test/OffloadTools/clang-sycl-linker/archive-extras.ll
 create mode 100644 clang/test/OffloadTools/clang-sycl-linker/weak-symbols.ll

diff --git a/clang/docs/ClangSYCLLinker.rst b/clang/docs/ClangSYCLLinker.rst
index c28c9fefaace3..2906bf2d9f488 100644
--- a/clang/docs/ClangSYCLLinker.rst
+++ b/clang/docs/ClangSYCLLinker.rst
@@ -50,7 +50,10 @@ be passed down to downstream AOT compilation tools like 
'ocloc' and 'opencl-aot'
     -help-hidden                  Display all available options
     -help                         Display available options (--help-hidden for 
more)
     -L <dir>                      Add <dir> to the library search path
-    --bc-library <name>           Add LLVM bitcode library <name> (with 
extension) to the link. A relative <name> is resolved against -L paths; an 
absolute path is taken as-is.
+    -l <libname>                  Search for library <libname>
+    --whole-archive               Include all archive members in the link
+    --no-whole-archive            Only include archive members that resolve 
undefined symbols (default)
+    -u <symbol>                   Force undefined symbol during linking
     --module-split-mode=<mode>    Module split mode: 'source' (default), 
'kernel', or 'none'
     --ocloc-options=<value>       Options passed to ocloc for Intel GPU AOT 
compilation
     --opencl-aot-options=<value>  Options passed to opencl-aot for Intel CPU 
AOT compilation
@@ -61,8 +64,49 @@ be passed down to downstream AOT compilation tools like 
'ocloc' and 'opencl-aot'
     -v                            Print verbose information
     -spirv-dump-device-code=<dir> Directory to dump SPIR-V IR code into
 
-Example
-=======
+Library Linking
+===============
+
+Device bitcode libraries can be packaged into archive libraries (``.a`` files)
+using ``llvm-ar`` and linked using the ``-l`` option:
+
+.. code-block:: console
+
+  llvm-ar rc libdevice.a func1.bc func2.bc func3.bc
+  clang-sycl-linker input.bc -l device -L /path/to/libs
+
+The linker supports standard archive library search semantics:
+
+* ``-l <name>`` searches for ``lib<name>.a`` in the directories specified by 
``-L``
+* ``-l :<exact-name>`` searches for the exact filename in the ``-L`` paths
+* Absolute paths can be passed as positional arguments: ``clang-sycl-linker 
input.bc /path/to/libdevice.a``
+
+By default, archive linking is **lazy** - only archive members (individual 
``.bc`` files)
+that resolve undefined symbols are extracted and linked. This happens at file
+granularity: if any symbol in a ``.bc`` file is needed, all symbols in that 
file
+are included. The linker uses a symbol-driven fixed-point algorithm: it
+repeatedly scans archives to extract members that resolve currently undefined
+symbols until no more extractions occur.
+
+To force extraction of all archive members regardless of symbol resolution, use
+``--whole-archive``:
+
+.. code-block:: console
+
+  clang-sycl-linker input.bc --whole-archive -l device --no-whole-archive -l 
other
+
+The ``-u <symbol>`` option can be used to force a symbol to be undefined, which
+can trigger extraction of archive members that define that symbol:
+
+.. code-block:: console
+
+  clang-sycl-linker input.bc -u my_init_function -l device
+
+Examples
+========
+
+Basic Usage
+-----------
 
 This tool is intended to be invoked when targeting any of the target offloading
 toolchains. When the --sycl-link option is passed to the clang driver, the
@@ -74,3 +118,24 @@ generate the final executable.
 .. code-block:: console
 
   clang-sycl-linker --triple spirv64 --arch bmg_g21 input.bc
+
+Linking with Device Libraries
+------------------------------
+
+To link device bitcode libraries, first package them into archive files:
+
+.. code-block:: console
+
+  # Create device library archives
+  llvm-ar rc libmath.a sin.bc cos.bc tan.bc
+  llvm-ar rc libutils.a helper1.bc helper2.bc
+
+  # Link with lazy loading (only needed members extracted)
+  clang-sycl-linker --triple spirv64 kernel.bc -l math -l utils -L 
/path/to/libs -o kernel.spv
+
+  # Force all members to be included from libmath.a
+  clang-sycl-linker --triple spirv64 kernel.bc --whole-archive -l math 
--no-whole-archive -l utils -L /path/to/libs -o kernel.spv
+
+  # Use exact archive filename or absolute path
+  clang-sycl-linker --triple spirv64 kernel.bc -l :libmath.a -L /path/to/libs 
-o kernel.spv
+  clang-sycl-linker --triple spirv64 kernel.bc /absolute/path/libmath.a -o 
kernel.spv
diff --git a/clang/test/OffloadTools/clang-sycl-linker/archive-extras.ll 
b/clang/test/OffloadTools/clang-sycl-linker/archive-extras.ll
new file mode 100644
index 0000000000000..bd26ddc3994f2
--- /dev/null
+++ b/clang/test/OffloadTools/clang-sycl-linker/archive-extras.ll
@@ -0,0 +1,106 @@
+; Additional archive-handling edge cases for clang-sycl-linker.
+;
+; REQUIRES: spirv-registered-target
+;
+; RUN: rm -rf %t && split-file %s %t
+; RUN: llvm-as %t/main.ll -o %t/main.bc
+; RUN: llvm-as %t/dup.ll -o %t/dup.bc
+; RUN: llvm-as %t/incl.ll -o %t/incl.bc
+; RUN: llvm-as %t/extra.ll -o %t/extra.bc
+; RUN: llvm-as %t/otherarch.ll -o %t/otherarch.bc
+; RUN: llvm-ar rc %t/libdup.a %t/dup.bc
+; RUN: llvm-ar rc %t/libincl.a %t/incl.bc
+; RUN: llvm-ar rc %t/libextra.a %t/extra.bc
+;
+; A multiply-defined symbol inside an archive member is harmless while the
+; member stays lazy: main.bc already defines bar_func1, so dup.bc is never
+; extracted and there is no multiply-defined error.
+; RUN: clang-sycl-linker %t/main.bc -l dup -L %t --dry-run -o /dev/null 
--print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-LAZY-OK
+; CHECK-LAZY-OK: define {{.*}}bar_func1{{.*}}
+; CHECK-LAZY-OK-NOT: error:
+;
+; Forcing the same member in with --whole-archive extracts dup.bc and the
+; conflicting definition now triggers a multiply-defined error.
+; RUN: not clang-sycl-linker %t/main.bc --whole-archive -l dup -L %t --dry-run 
-o /dev/null 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-WHOLE-CONFLICT
+; CHECK-WHOLE-CONFLICT: error: Linking globals named {{.*}}bar_func1{{.*}} 
symbol multiply defined!
+;
+; --no-whole-archive after --whole-archive restores lazy behavior for a later
+; -l: libincl is whole-archived (inclFunc is pulled in unconditionally), while
+; libextra is lazy and nothing references extraFunc, so it is not pulled.
+; RUN: clang-sycl-linker %t/main.bc --whole-archive -l incl --no-whole-archive 
-l extra -L %t --dry-run -o /dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-NO-WHOLE
+; CHECK-NO-WHOLE: define {{.*}}bar_func1{{.*}}
+; CHECK-NO-WHOLE: define {{.*}}inclFunc{{.*}}
+; CHECK-NO-WHOLE-NOT: define {{.*}}extraFunc{{.*}}
+;
+; -L search must skip a directory whose name matches the requested library and
+; fall through to a later -L path that holds the real archive. Here %t/dir1
+; contains a *directory* named libincl.a, while %t (the second -L) has the real
+; archive; the real one must be found rather than erroring on the directory.
+; RUN: mkdir -p %t/dir1/libincl.a
+; RUN: clang-sycl-linker %t/main.bc --whole-archive -l incl -L %t/dir1 -L %t 
--dry-run -o /dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-DIR-FALLTHROUGH
+; CHECK-DIR-FALLTHROUGH: define {{.*}}inclFunc{{.*}}
+;
+; A whole-archive member built for a different target triple is silently 
skipped
+; rather than producing a "conflicting target triples" error. otherarch.bc has
+; triple spirv32; main.bc is spirv64, so otherarch's member is dropped while
+; inclFunc (spirv64) is still linked.
+; RUN: llvm-ar rc %t/libother.a %t/incl.bc %t/otherarch.bc
+; RUN: clang-sycl-linker %t/main.bc --whole-archive -l other -L %t --dry-run 
-o /dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-TRIPLE-SKIP
+; CHECK-TRIPLE-SKIP: define {{.*}}inclFunc{{.*}}
+; CHECK-TRIPLE-SKIP-NOT: define {{.*}}otherArchFunc{{.*}}
+; CHECK-TRIPLE-SKIP-NOT: error:
+
+;--- main.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @bar_func1(i32 %a, i32 %b) {
+entry:
+  %res = add nsw i32 %b, %a
+  ret i32 %res
+}
+
+;--- dup.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @bar_func1(i32 %a, i32 %b) {
+entry:
+  %mul = shl nsw i32 %a, 1
+  %res = add nsw i32 %mul, %b
+  ret i32 %res
+}
+
+;--- incl.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @inclFunc(i32 %a) {
+entry:
+  %res = add nsw i32 %a, 7
+  ret i32 %res
+}
+
+;--- extra.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @extraFunc(i32 %a) {
+entry:
+  %res = mul nsw i32 %a, 3
+  ret i32 %res
+}
+
+;--- otherarch.ll
+target triple = "spirv32"
+
+define spir_func i32 @otherArchFunc(i32 %a) {
+entry:
+  %res = sub nsw i32 %a, 1
+  ret i32 %res
+}
diff --git a/clang/test/OffloadTools/clang-sycl-linker/basic.ll 
b/clang/test/OffloadTools/clang-sycl-linker/basic.ll
index e906d23b90be9..564ef6a67f3e9 100644
--- a/clang/test/OffloadTools/clang-sycl-linker/basic.ll
+++ b/clang/test/OffloadTools/clang-sycl-linker/basic.ll
@@ -22,76 +22,97 @@
 ;
 ; Test non-existent input file
 ; RUN: not clang-sycl-linker %t-missing.bc -o %t.out 2>&1 | FileCheck %s 
--check-prefix=MISSING
-; MISSING: input file '{{.*}}-missing.bc' does not exist
+; MISSING: input file not found: '{{.*}}-missing.bc'
 ;
 ; Test the dry run of a simple case to link two input files.
 ; Test that IMG_SPIRV image kind is set for non-AOT compilation.
 ; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none %t/input1.bc 
%t/input2.bc -o %t/spirv.out 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=SIMPLE-FO
-; SIMPLE-FO:      link: inputs: {{.*}}.bc, {{.*}}.bc  libfiles:  output: 
[[LLVMLINKOUT:.*]].bc
+; SIMPLE-FO:      link: inputs: {{.*}}.bc, {{.*}}.bc output: 
[[LLVMLINKOUT:.*]].bc
 ; SIMPLE-FO-NEXT: LLVM backend: input: [[LLVMLINKOUT]].bc, output: {{.*}}_0.spv
 ; SIMPLE-FO-NEXT: sycl-bundle: image kind: spv, triple: spirv64, arch: {{$}}
 ; SIMPLE-FO-NOT:  {{.+}}
 ;
-; Test the dry run of a simple case with device library files specified.
+; Test the dry run of a simple case with device library archive specified 
using --whole-archive.
 ; RUN: mkdir -p %t/libs
-; RUN: touch %t/libs/lib1.bc
-; RUN: touch %t/libs/lib2.bc
-; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none %t/input1.bc 
%t/input2.bc --library-path=%t/libs --bc-library lib1.bc --bc-library lib2.bc 
-o a.spv 2>&1 \
+; RUN: llvm-as %t/lib1.ll -o %t/libs/lib1.bc
+; RUN: llvm-as %t/lib2.ll -o %t/libs/lib2.bc
+; RUN: llvm-ar rc %t/libs/libdevice.a %t/libs/lib1.bc %t/libs/lib2.bc
+; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none %t/input1.bc 
%t/input2.bc --library-path=%t/libs --whole-archive -l device -o /dev/null 2>&1 
\
 ; RUN:   | FileCheck %s --check-prefix=DEVLIBS
-; DEVLIBS:      link: inputs: {{.*}}.bc  libfiles: {{.*}}lib1.bc, 
{{.*}}lib2.bc  output: [[LLVMLINKOUT:.*]].bc
-; DEVLIBS-NEXT: LLVM backend: input: [[LLVMLINKOUT]].bc, output: a_0.spv
+; DEVLIBS:      link: inputs: {{.*}}.bc, {{.*}}.bc, 
{{.*}}libdevice.a(lib1.bc), {{.*}}libdevice.a(lib2.bc) output: 
[[LLVMLINKOUT:.*]].bc
+; DEVLIBS-NEXT: LLVM backend: input: [[LLVMLINKOUT]].bc, output: 
/dev/null_0.spv
 ; DEVLIBS-NEXT: sycl-bundle: image kind: spv, triple: spirv64, arch: {{$}}
 ; DEVLIBS-NOT:  {{.+}}
 ;
-; Test -L short form (joined) and --bc-library= joined form.
-; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none %t/input1.bc 
-L%t/libs --bc-library=lib1.bc -o a.spv 2>&1 \
+; Test -L short form (joined) and -l with archive using --whole-archive.
+; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none %t/input1.bc 
-L%t/libs --whole-archive -l device -o /dev/null 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=DEVLIBS-SHORT
-; DEVLIBS-SHORT: link: inputs: {{.*}}.bc  libfiles: {{.*}}libs{{[/\\]}}lib1.bc 
 output: {{.*}}.bc
+; DEVLIBS-SHORT: link: inputs: {{.*}}.bc, {{.*}}libdevice.a(lib1.bc), 
{{.*}}libdevice.a(lib2.bc) output: {{.*}}.bc
 ;
-; Test that search continues past the first -L when the library is not found 
there. lib1.bc exists only in %t/libs (the second -L).
+; Test that search continues past the first -L when the library is not found 
there. libdevice.a exists only in %t/libs (the second -L).
 ; RUN: mkdir -p %t/empty
-; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none %t/input1.bc -L 
%t/empty -L %t/libs --bc-library lib1.bc -o a.spv 2>&1 \
+; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none %t/input1.bc -L 
%t/empty -L %t/libs --whole-archive -l device -o /dev/null 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=DEVLIBS-FALLTHROUGH
-; DEVLIBS-FALLTHROUGH: link: inputs: {{.*}}.bc  libfiles: 
{{.*}}libs{{[/\\]}}lib1.bc  output: {{.*}}.bc
-;
-; Test that -L paths are searched in order: when the same name exists in 
multiple -L dirs, the first one wins.
-; RUN: mkdir -p %t/libs2
-; RUN: touch %t/libs/shadow.bc %t/libs2/shadow.bc
-; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none %t/input1.bc -L 
%t/libs2 -L %t/libs --bc-library shadow.bc -o a.spv 2>&1 \
-; RUN:   | FileCheck %s --check-prefix=DEVLIBS-ORDER
-; DEVLIBS-ORDER: link: inputs: {{.*}}.bc  libfiles: 
{{.*}}libs2{{[/\\]}}shadow.bc  output: {{.*}}.bc
+; DEVLIBS-FALLTHROUGH: link: inputs: {{.*}}.bc, {{.*}}libdevice.a(lib1.bc), 
{{.*}}libdevice.a(lib2.bc) output: {{.*}}.bc
 ;
 ; Test a simple case with a random file (not bitcode) as input.
 ; RUN: touch %t/dummy.o
-; RUN: not clang-sycl-linker %t/dummy.o -o a.spv 2>&1 \
+; RUN: not clang-sycl-linker %t/dummy.o -o /dev/null 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=FILETYPEERROR
-; FILETYPEERROR: unsupported file type
+; FILETYPEERROR: unsupported file type: '{{.*}}dummy.o'
+;
+; Test that unsupported file type error includes buffer identifier when found 
inside an archive.
+; Create an archive containing an unsupported file (text file instead of 
bitcode).
+; RUN: echo "not bitcode" > %t/invalid.txt
+; RUN: llvm-ar rc %t/libinvalid.a %t/invalid.txt
+; RUN: not clang-sycl-linker --dry-run %t/input1.bc -L %t --whole-archive -l 
invalid -o /dev/null 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=ARCHIVE-INVALID-MEMBER
+; ARCHIVE-INVALID-MEMBER: unsupported file type: 
'{{.*}}libinvalid.a(invalid.txt)'
+;
+; Test mixed archive: valid bitcode member + invalid member.
+; The error should clearly identify which member is invalid.
+; RUN: llvm-ar rc %t/libmixed.a %t/libs/lib1.bc %t/invalid.txt
+; RUN: not clang-sycl-linker --dry-run %t/input1.bc -L %t --whole-archive -l 
mixed -o /dev/null 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=ARCHIVE-MIXED-INVALID
+; ARCHIVE-MIXED-INVALID: unsupported file type: '{{.*}}libmixed.a(invalid.txt)'
 ;
 ; Test to see if device library related errors are emitted.
-; RUN: not clang-sycl-linker --dry-run %t/input1.bc %t/input2.bc 
--library-path=%t/libs --bc-library lib1.bc --bc-library lib2.bc --bc-library 
lib3.bc -o a.spv 2>&1 \
+; RUN: not clang-sycl-linker --dry-run %t/input1.bc %t/input2.bc 
--library-path=%t/libs -l device -l nonexistent -o /dev/null 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=DEVLIBSERR
-; DEVLIBSERR: '{{.*}}lib3.bc' library file not found
+; DEVLIBSERR: unable to find library -lnonexistent
 ;
-; Test that there is no implicit CWD search: a bare bitcode name without any -L
+; Test that there is no implicit CWD search: a bare library name without any -L
 ; must fail to resolve, even if a same-named file exists in the CWD.
-; RUN: cd %t && not clang-sycl-linker --dry-run input1.bc --bc-library 
input1.bc -o a.spv 2>&1 \
+; RUN: cd %t && not clang-sycl-linker --dry-run input1.bc -l mixed -o 
/dev/null 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=NO-CWD-SEARCH
-; NO-CWD-SEARCH: 'input1.bc' library file not found
+; NO-CWD-SEARCH: unable to find library -lmixed
 ;
 ; Test that a directory matching the requested name is not accepted as a 
library:
-; %t/libs is a directory created above; resolving --bc-library libs against -L 
%t
-; would otherwise pick it up and fail later with a confusing bitcode-reader 
error.
-; RUN: not clang-sycl-linker --dry-run %t/input1.bc -L %t --bc-library libs -o 
a.spv 2>&1 \
+; %t/libs is a directory created above; resolving -l:libs against -L %t skips 
the
+; directory during the search, so the library is reported as not found (and a
+; real archive of the same name in a later -L path could still be picked up).
+; RUN: not clang-sycl-linker --dry-run %t/input1.bc -L %t -l :libs -o 
/dev/null 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=NO-DIR-AS-LIB
-; NO-DIR-AS-LIB: 'libs' library file not found
+; NO-DIR-AS-LIB: unable to find library -l:libs
+;
+; Test that providing only an empty archive results in an error.
+; RUN: llvm-ar rc %t/empty.a
+; RUN: not clang-sycl-linker --dry-run --whole-archive %t/empty.a -o /dev/null 
2>&1 \
+; RUN:   | FileCheck %s --check-prefix=NO-RESOLVED-INPUT
+; NO-RESOLVED-INPUT: no input files could be resolved
+;
+; Test that providing only a lazy archive with no extracted members results in 
an error.
+; RUN: not clang-sycl-linker --dry-run %t/libs/libdevice.a -o /dev/null 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=NO-RESOLVED-LAZY
+; NO-RESOLVED-LAZY: no input files could be resolved
 ;
 ; Test AOT compilation for an Intel GPU.
 ; Test that IMG_Object image kind is set for AOT compilation (Intel GPU).
 ; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none -arch=bmg_g21 
%t/input1.bc %t/input2.bc -o %t/aot-gpu.out 2>&1 \
 ; RUN:     --ocloc-options="-a -b" \
 ; RUN:   | FileCheck %s --check-prefix=AOT-INTEL-GPU
-; AOT-INTEL-GPU:      link: inputs: {{.*}}.bc, {{.*}}.bc libfiles: output: 
[[LLVMLINKOUT:.*]].bc
+; AOT-INTEL-GPU:      link: inputs: {{.*}}.bc, {{.*}}.bc output: 
[[LLVMLINKOUT:.*]].bc
 ; AOT-INTEL-GPU-NEXT: LLVM backend: input: [[LLVMLINKOUT]].bc, output: 
[[SPIRVTRANSLATIONOUT:.*]]_0.spv
 ; AOT-INTEL-GPU-NEXT: "{{.*}}ocloc{{.*}}" {{.*}}-device bmg_g21 -a -b 
{{.*}}-output [[SPIRVTRANSLATIONOUT]]_0.out -file [[SPIRVTRANSLATIONOUT]]_0.spv
 ; AOT-INTEL-GPU-NEXT: sycl-bundle: image kind: o, triple: spirv64, arch: 
bmg_g21
@@ -102,7 +123,7 @@
 ; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none 
-arch=graniterapids %t/input1.bc %t/input2.bc -o %t/aot-cpu.out 2>&1 \
 ; RUN:     --opencl-aot-options="-a -b" \
 ; RUN:   | FileCheck %s --check-prefix=AOT-INTEL-CPU
-; AOT-INTEL-CPU:      link: inputs: {{.*}}.bc, {{.*}}.bc libfiles: output: 
[[LLVMLINKOUT:.*]].bc
+; AOT-INTEL-CPU:      link: inputs: {{.*}}.bc, {{.*}}.bc output: 
[[LLVMLINKOUT:.*]].bc
 ; AOT-INTEL-CPU-NEXT: LLVM backend: input: [[LLVMLINKOUT]].bc, output: 
[[SPIRVTRANSLATIONOUT:.*]]_0.spv
 ; AOT-INTEL-CPU-NEXT: "{{.*}}opencl-aot{{.*}}" {{.*}}--device=cpu -a -b 
{{.*}}-o [[SPIRVTRANSLATIONOUT]]_0.out [[SPIRVTRANSLATIONOUT]]_0.spv
 ; AOT-INTEL-CPU-NEXT: sycl-bundle: image kind: o, triple: spirv64, arch: 
graniterapids
@@ -164,3 +185,19 @@ target triple = "spirv64"
 define spir_func i32 @helper() {
   ret i32 0
 }
+
+;--- lib1.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @lib1_func() {
+  ret i32 1
+}
+
+;--- lib2.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @lib2_func() {
+  ret i32 2
+}
diff --git a/clang/test/OffloadTools/clang-sycl-linker/link.ll 
b/clang/test/OffloadTools/clang-sycl-linker/link.ll
index 4114f0a3f3fb1..31d2c9d2344e2 100644
--- a/clang/test/OffloadTools/clang-sycl-linker/link.ll
+++ b/clang/test/OffloadTools/clang-sycl-linker/link.ll
@@ -7,9 +7,13 @@
 ; RUN: llvm-as %t/bar.ll -o %t/bar.bc
 ; RUN: llvm-as %t/baz.ll -o %t/baz.bc
 ; RUN: llvm-as %t/libfoo.ll -o %t/libfoo.bc
+; RUN: llvm-as %t/addFive.ll -o %t/addFive.bc
+; RUN: llvm-as %t/unusedFunc.ll -o %t/unusedFunc.bc
+; RUN: llvm-ar rc %t/libfoo.a %t/libfoo.bc
+; RUN: llvm-ar rc %t/libdevice.a %t/addFive.bc %t/unusedFunc.bc
 ;
 ; Test linking two input files.
-; RUN: clang-sycl-linker %t/foo.bc %t/bar.bc --dry-run -o a.spv 
--print-linked-module 2>&1 \
+; RUN: clang-sycl-linker %t/foo.bc %t/bar.bc --dry-run -o /dev/null 
--print-linked-module 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=CHECK-SIMPLE
 ; CHECK-SIMPLE: define {{.*}}foo_func1{{.*}}
 ; CHECK-SIMPLE: define {{.*}}foo_func2{{.*}}
@@ -18,22 +22,115 @@
 ; CHECK-SIMPLE-NOT: define {{.*}}unusedFunc{{.*}}
 ;
 ; Test that multiply defined symbols are reported as errors.
-; RUN: not clang-sycl-linker %t/bar.bc %t/baz.bc --dry-run -o a.spv 2>&1 \
+; RUN: not clang-sycl-linker %t/bar.bc %t/baz.bc --dry-run -o /dev/null 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=CHECK-MULTIPLE-DEFS
 ; CHECK-MULTIPLE-DEFS: error: Linking globals named {{.*}}bar_func1{{.*}} 
symbol multiply defined!
 ;
-; Test linking with a BC library file resolved through -L.
-; RUN: clang-sycl-linker %t/foo.bc %t/bar.bc --bc-library libfoo.bc -L %t 
--dry-run -o a.spv --print-linked-module 2>&1 \
+; Test lazy linking with an archive library: only needed members are extracted.
+; foo.bc references addFive, so addFive.bc is extracted from libdevice.a.
+; unusedFunc.bc is not needed, so it should NOT be extracted.
+; RUN: clang-sycl-linker %t/foo.bc %t/bar.bc -l device -L %t --dry-run -o 
/dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-LAZY-LINK
+; CHECK-LAZY-LINK: define {{.*}}foo_func1{{.*}}
+; CHECK-LAZY-LINK: define {{.*}}foo_func2{{.*}}
+; CHECK-LAZY-LINK: define {{.*}}bar_func1{{.*}}
+; CHECK-LAZY-LINK: define {{.*}}addFive{{.*}}
+; CHECK-LAZY-LINK-NOT: define {{.*}}unusedFunc{{.*}}
+;
+; Test linking with an archive library file using -l:libname.a syntax.
+; Archive linking extracts members at file granularity, so all functions in 
libfoo.bc are included.
+; RUN: clang-sycl-linker %t/foo.bc %t/bar.bc -l :libfoo.a -L %t --dry-run -o 
/dev/null --print-linked-module 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=CHECK-DEVICE-LIB
 ; CHECK-DEVICE-LIB: define {{.*}}foo_func1{{.*}}
 ; CHECK-DEVICE-LIB: define {{.*}}foo_func2{{.*}}
 ; CHECK-DEVICE-LIB: define {{.*}}bar_func1{{.*}}
 ; CHECK-DEVICE-LIB: define {{.*}}addFive{{.*}}
-; CHECK-DEVICE-LIB-NOT: define {{.*}}unusedFunc{{.*}}
+; CHECK-DEVICE-LIB: define {{.*}}unusedFunc{{.*}}
 ;
-; Test that an absolute path to --bc-library is taken as-is, with no -L 
required.
-; RUN: clang-sycl-linker %t/foo.bc %t/bar.bc --bc-library %t/libfoo.bc 
--dry-run -o a.spv --print-linked-module 2>&1 \
-; RUN:   | FileCheck %s --check-prefix=CHECK-DEVICE-LIB
+; Test that an absolute path as a positional argument still performs lazy 
member extraction.
+; libdevice.a has two members (addFive.bc and unusedFunc.bc).
+; Since foo.bc needs addFive, only addFive.bc member is extracted; 
unusedFunc.bc is not.
+; RUN: clang-sycl-linker %t/foo.bc %t/bar.bc %t/libdevice.a --dry-run -o 
/dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-DEVICE-LIB-POS
+; CHECK-DEVICE-LIB-POS: define {{.*}}foo_func1{{.*}}
+; CHECK-DEVICE-LIB-POS: define {{.*}}foo_func2{{.*}}
+; CHECK-DEVICE-LIB-POS: define {{.*}}bar_func1{{.*}}
+; CHECK-DEVICE-LIB-POS: define {{.*}}addFive{{.*}}
+; CHECK-DEVICE-LIB-POS-NOT: define {{.*}}unusedFunc{{.*}}
+;
+; Test that -L paths are searched in order: when the same name exists in 
multiple -L dirs, the first one wins.
+; RUN: mkdir -p %t/libs1 %t/libs2
+; RUN: rm -f %t/libs1/libshadow.a %t/libs2/libshadow.a
+; RUN: llvm-ar rc %t/libs1/libshadow.a %t/addFive.bc
+; RUN: llvm-ar rc %t/libs2/libshadow.a %t/unusedFunc.bc
+; RUN: clang-sycl-linker %t/foo.bc -L %t/libs2 -L %t/libs1 --whole-archive -l 
shadow --dry-run -o /dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-LIB-ORDER
+; CHECK-LIB-ORDER: define {{.*}}unusedFunc
+; CHECK-LIB-ORDER-NOT: define {{.*}}addFive
+;
+; Test that -u forces extraction of an otherwise-unreferenced archive member.
+; Without -u, unusedFunc is not extracted. With -u unusedFunc, it is pulled in.
+; RUN: clang-sycl-linker %t/bar.bc %t/libdevice.a --dry-run -o /dev/null 
--print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-NO-FORCE-UNDEF
+; CHECK-NO-FORCE-UNDEF: define {{.*}}bar_func1{{.*}}
+; CHECK-NO-FORCE-UNDEF-NOT: define {{.*}}unusedFunc{{.*}}
+; CHECK-NO-FORCE-UNDEF-NOT: define {{.*}}addFive{{.*}}
+;
+; RUN: clang-sycl-linker %t/bar.bc %t/libdevice.a -u unusedFunc --dry-run -o 
/dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-FORCE-UNDEF
+; CHECK-FORCE-UNDEF: define {{.*}}bar_func1{{.*}}
+; CHECK-FORCE-UNDEF: define {{.*}}unusedFunc{{.*}}
+; CHECK-FORCE-UNDEF-NOT: define {{.*}}addFive{{.*}}
+;
+; Test that multiple -u flags work correctly and extract all specified members.
+; RUN: clang-sycl-linker %t/bar.bc %t/libdevice.a -u unusedFunc -u addFive 
--dry-run -o /dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-MULTI-UNDEF
+; CHECK-MULTI-UNDEF: define {{.*}}bar_func1{{.*}}
+; CHECK-MULTI-UNDEF: define {{.*}}addFive{{.*}}
+; CHECK-MULTI-UNDEF: define {{.*}}unusedFunc{{.*}}
+;
+; Test that -u works correctly with -l library syntax (not just positional 
archives).
+; RUN: clang-sycl-linker %t/bar.bc -l device -L %t -u unusedFunc --dry-run -o 
/dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-UNDEF-WITH-L
+; CHECK-UNDEF-WITH-L: define {{.*}}bar_func1{{.*}}
+; CHECK-UNDEF-WITH-L: define {{.*}}unusedFunc{{.*}}
+; CHECK-UNDEF-WITH-L-NOT: define {{.*}}addFive{{.*}}
+;
+; Test that -u combined with actual references works correctly (both should be 
extracted).
+; foo.bc references addFive, and -u forces unusedFunc.
+; RUN: clang-sycl-linker %t/foo.bc %t/bar.bc %t/libdevice.a -u unusedFunc 
--dry-run -o /dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-UNDEF-PLUS-REF
+; CHECK-UNDEF-PLUS-REF: define {{.*}}foo_func1{{.*}}
+; CHECK-UNDEF-PLUS-REF: define {{.*}}bar_func1{{.*}}
+; CHECK-UNDEF-PLUS-REF: define {{.*}}addFive{{.*}}
+; CHECK-UNDEF-PLUS-REF: define {{.*}}unusedFunc{{.*}}
+;
+; Regression test: -u symbol should remain undefined until resolved by archive 
member.
+; This test verifies the fix for the bug where forced-undefined entries were 
overwritten
+; before ResolvesReference was computed, making -u ineffective.
+; RUN: clang-sycl-linker %t/bar.bc -u addFive %t/libdevice.a --dry-run -o 
/dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-UNDEF-REMAINS
+; CHECK-UNDEF-REMAINS: define {{.*}}bar_func1{{.*}}
+; CHECK-UNDEF-REMAINS: define {{.*}}addFive{{.*}}
+; CHECK-UNDEF-REMAINS-NOT: define {{.*}}unusedFunc{{.*}}
+;
+; Test -u with archive processed BEFORE the symbol table has been populated by 
regular inputs.
+; This specifically tests that the forced-undefined placeholder survives 
initial processing.
+; RUN: clang-sycl-linker -u addFive %t/libdevice.a %t/bar.bc --dry-run -o 
/dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-UNDEF-FIRST
+; CHECK-UNDEF-FIRST: define {{.*}}addFive{{.*}}
+; CHECK-UNDEF-FIRST: define {{.*}}bar_func1{{.*}}
+; CHECK-UNDEF-FIRST-NOT: define {{.*}}unusedFunc{{.*}}
+;
+; Test that -l with an absolute path works correctly (standard linker 
behavior).
+; An absolute path given to -l should be used directly without searching -L 
directories.
+; RUN: clang-sycl-linker %t/foo.bc %t/bar.bc -l %t/libdevice.a --dry-run -o 
/dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-ABSOLUTE-PATH-L
+; CHECK-ABSOLUTE-PATH-L: define {{.*}}foo_func1{{.*}}
+; CHECK-ABSOLUTE-PATH-L: define {{.*}}foo_func2{{.*}}
+; CHECK-ABSOLUTE-PATH-L: define {{.*}}bar_func1{{.*}}
+; CHECK-ABSOLUTE-PATH-L: define {{.*}}addFive{{.*}}
+; CHECK-ABSOLUTE-PATH-L-NOT: define {{.*}}unusedFunc{{.*}}
 
 ;--- foo.ll
 target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
@@ -93,3 +190,23 @@ entry:
   %res = mul nsw i32 %a, 5
   ret i32 %res
 }
+
+;--- addFive.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @addFive(i32 %a) {
+entry:
+  %res = add nsw i32 %a, 5
+  ret i32 %res
+}
+
+;--- unusedFunc.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @unusedFunc(i32 %a) {
+entry:
+  %res = mul nsw i32 %a, 5
+  ret i32 %res
+}
diff --git a/clang/test/OffloadTools/clang-sycl-linker/split-mode.ll 
b/clang/test/OffloadTools/clang-sycl-linker/split-mode.ll
index d10dbacf259fe..2def1e6d4d066 100644
--- a/clang/test/OffloadTools/clang-sycl-linker/split-mode.ll
+++ b/clang/test/OffloadTools/clang-sycl-linker/split-mode.ll
@@ -12,7 +12,7 @@
 ; Test the split mode ("none"): kernels from different TUs are not split into 
separate images.
 ; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none %t.bc -o 
%t-none.out 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=SPLIT-NONE
-; SPLIT-NONE:      link: inputs: {{.*}}.bc  libfiles:  output: 
[[LLVMLINKOUT:.*]].bc
+; SPLIT-NONE:      link: inputs: {{.*}}.bc output: [[LLVMLINKOUT:.*]].bc
 ; SPLIT-NONE-NEXT: LLVM backend: input: [[LLVMLINKOUT]].bc, output: 
{{.*}}_0.spv
 ; SPLIT-NONE-NEXT: sycl-bundle: image kind: spv, triple: spirv64, arch: {{$}}
 ; SPLIT-NONE-NOT:  {{.+}}
@@ -20,7 +20,7 @@
 ; Test the split mode ("kernel"): each SPIR_KERNEL function produces its own 
device image.
 ; RUN: clang-sycl-linker --dry-run -v --module-split-mode=kernel %t.bc -o 
%t-split-kernel.out 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=SPLIT-KERNEL
-; SPLIT-KERNEL:      link: inputs: {{.*}}.bc  libfiles:  output: 
[[LLVMLINKOUT:.*]].bc
+; SPLIT-KERNEL:      link: inputs: {{.*}}.bc output: [[LLVMLINKOUT:.*]].bc
 ; SPLIT-KERNEL-NEXT: sycl-module-split: input: [[LLVMLINKOUT]].bc, mode: kernel
 ; SPLIT-KERNEL-NEXT: [[SPLIT0:.*]].bc [kernel_c ]
 ; SPLIT-KERNEL-NEXT: [[SPLIT1:.*]].bc [kernel_b ]
@@ -43,7 +43,7 @@
 ; Test per-TU split ('source' explicitly provided)
 ; RUN: clang-sycl-linker --dry-run -v --module-split-mode=source %t.bc -o 
%t-src.out 2>&1 \
 ; RUN:   | FileCheck %s --check-prefix=SPLIT-SRC
-; SPLIT-SRC:      link: inputs: {{.*}}.bc  libfiles:  output: 
[[LLVMLINKOUT:.*]].bc
+; SPLIT-SRC:      link: inputs: {{.*}}.bc output: [[LLVMLINKOUT:.*]].bc
 ; SPLIT-SRC-NEXT: sycl-module-split: input: [[LLVMLINKOUT]].bc, mode: source
 ; SPLIT-SRC-NEXT: [[S0:.*]].bc [kernel_b kernel_c ]
 ; SPLIT-SRC-NEXT: [[S1:.*]].bc [kernel_a ]
diff --git a/clang/test/OffloadTools/clang-sycl-linker/triple.ll 
b/clang/test/OffloadTools/clang-sycl-linker/triple.ll
index c0e35b8fc9d36..f122194d9d9b2 100644
--- a/clang/test/OffloadTools/clang-sycl-linker/triple.ll
+++ b/clang/test/OffloadTools/clang-sycl-linker/triple.ll
@@ -63,6 +63,8 @@ define spir_kernel void @kernel_c() #0 {
 attributes #0 = { "sycl-module-id"="TU3.cpp" }
 
 ;--- no-triple.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+
 define spir_kernel void @kernel_d() #0 {
   ret void
 }
diff --git a/clang/test/OffloadTools/clang-sycl-linker/weak-symbols.ll 
b/clang/test/OffloadTools/clang-sycl-linker/weak-symbols.ll
new file mode 100644
index 0000000000000..b14e1aeebf9a4
--- /dev/null
+++ b/clang/test/OffloadTools/clang-sycl-linker/weak-symbols.ll
@@ -0,0 +1,117 @@
+; Test weak symbol resolution semantics for clang-sycl-linker.
+;
+; REQUIRES: spirv-registered-target
+;
+; RUN: rm -rf %t && split-file %s %t
+; RUN: llvm-as %t/main.ll -o %t/main.bc
+; RUN: llvm-as %t/weak-archive.ll -o %t/weak-archive.bc
+; RUN: llvm-as %t/strong-archive.ll -o %t/strong-archive.bc
+; RUN: llvm-as %t/weak-main.ll -o %t/weak-main.bc
+; RUN: llvm-as %t/strong-main.ll -o %t/strong-main.bc
+; RUN: llvm-as %t/another-weak.ll -o %t/another-weak.bc
+; RUN: llvm-ar rc %t/libweak.a %t/weak-archive.bc
+; RUN: llvm-ar rc %t/libstrong.a %t/strong-archive.bc
+; RUN: llvm-ar rc %t/libmixed.a %t/weak-archive.bc %t/strong-archive.bc
+; RUN: llvm-ar rc %t/libanother.a %t/another-weak.bc
+;
+; Strong definition in main input takes precedence over weak in lazy archive.
+; The weak definition in libweak.a should NOT be extracted because main.bc 
already
+; defines commonFunc (strongly), so there's no undefined reference to resolve.
+; RUN: clang-sycl-linker %t/main.bc -l weak -L %t --dry-run -o /dev/null 
--print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-STRONG-WINS
+; CHECK-STRONG-WINS: define{{.*}}i32 @commonFunc{{.*}} {
+; CHECK-STRONG-WINS-NEXT: ret i32 42
+; CHECK-STRONG-WINS-NOT: ret i32 999
+;
+; Weak definition in main, strong in lazy archive.
+; When weak-main.bc references commonFunc weakly, the strong definition in
+; libstrong.a should be extracted and take precedence.
+; RUN: clang-sycl-linker %t/weak-main.bc -u commonFunc -l strong -L %t 
--dry-run -o /dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-STRONG-FROM-ARCHIVE
+; CHECK-STRONG-FROM-ARCHIVE: define{{.*}}i32 @commonFunc{{.*}} {
+; CHECK-STRONG-FROM-ARCHIVE-NEXT: ret i32 100
+; CHECK-STRONG-FROM-ARCHIVE-NOT: ret i32 999
+;
+; Two weak definitions from different lazy archives.
+; Both archives provide weak definitions. The first one encountered (by -L/-l 
order)
+; should be taken. Here libweak.a comes before libanother.a, so weak-archive's
+; version (ret 999) should be extracted when -u forces the symbol.
+; RUN: clang-sycl-linker %t/strong-main.bc -u commonFunc -l weak -l another -L 
%t --dry-run -o /dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-FIRST-WEAK-WINS
+; CHECK-FIRST-WEAK-WINS: define{{.*}}weak{{.*}}i32 @commonFunc{{.*}} {
+; CHECK-FIRST-WEAK-WINS-NEXT: ret i32 999
+; CHECK-FIRST-WEAK-WINS-NOT: ret i32 777
+;
+; Whole-archive with mixed weak and strong definitions in same archive.
+; When --whole-archive forces extraction of all members, the strong definition
+; should override the weak one. libmixed.a contains both weak-archive.bc and
+; strong-archive.bc; the strong one should win.
+; RUN: clang-sycl-linker %t/strong-main.bc --whole-archive -l mixed -L %t 
--dry-run -o /dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-WHOLE-STRONG-WINS
+; CHECK-WHOLE-STRONG-WINS: define{{.*}}i32 @commonFunc{{.*}} {
+; CHECK-WHOLE-STRONG-WINS-NEXT: ret i32 100
+; CHECK-WHOLE-STRONG-WINS-NOT: ret i32 999
+;
+; Strong definition in one input, weak in another non-archive input.
+; Both are non-lazy; the strong definition should be kept.
+; RUN: clang-sycl-linker %t/strong-main.bc %t/weak-main.bc --dry-run -o 
/dev/null --print-linked-module 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=CHECK-NON-LAZY-STRONG
+; CHECK-NON-LAZY-STRONG: define{{.*}}i32 @mainFunc{{.*}}
+; CHECK-NON-LAZY-STRONG: define{{.*}}i32 @commonFunc{{.*}}
+; CHECK-NON-LAZY-STRONG-NOT: define{{.*}}weak{{.*}}@commonFunc
+
+;--- main.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @mainFunc() {
+  ret i32 0
+}
+
+define spir_func i32 @commonFunc() {
+  ret i32 42
+}
+
+;--- weak-archive.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define weak spir_func i32 @commonFunc() {
+  ret i32 999
+}
+
+;--- strong-archive.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @commonFunc() {
+  ret i32 100
+}
+
+;--- weak-main.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define weak spir_func i32 @commonFunc() {
+  ret i32 999
+}
+
+define spir_func i32 @weakMainFunc() {
+  ret i32 1
+}
+
+;--- strong-main.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define spir_func i32 @mainFunc() {
+  ret i32 2
+}
+
+;--- another-weak.ll
+target datalayout = 
"e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64-G1"
+target triple = "spirv64"
+
+define weak spir_func i32 @commonFunc() {
+  ret i32 777
+}
diff --git a/clang/tools/clang-sycl-linker/CMakeLists.txt 
b/clang/tools/clang-sycl-linker/CMakeLists.txt
index a2104084dad87..84f24603ea87d 100644
--- a/clang/tools/clang-sycl-linker/CMakeLists.txt
+++ b/clang/tools/clang-sycl-linker/CMakeLists.txt
@@ -2,6 +2,7 @@ set(LLVM_LINK_COMPONENTS
   ${LLVM_TARGETS_TO_BUILD}
   Analysis
   BinaryFormat
+  BitReader
   BitWriter
   Core
   FrontendOffloading
diff --git a/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp 
b/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
index 8a563b69f6949..ce76f5841dd52 100644
--- a/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
+++ b/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
@@ -18,9 +18,12 @@
 #include "clang/Basic/OffloadArch.h"
 #include "clang/Basic/Version.h"
 
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringSwitch.h"
 #include "llvm/BinaryFormat/Magic.h"
+#include "llvm/Bitcode/BitcodeReader.h"
 #include "llvm/Bitcode/BitcodeWriter.h"
 #include "llvm/CodeGen/CommandFlags.h"
 #include "llvm/Frontend/Offloading/Utility.h"
@@ -30,8 +33,10 @@
 #include "llvm/LTO/LTO.h"
 #include "llvm/Linker/Linker.h"
 #include "llvm/MC/TargetRegistry.h"
+#include "llvm/Object/Archive.h"
 #include "llvm/Object/Binary.h"
 #include "llvm/Object/IRObjectFile.h"
+#include "llvm/Object/IRSymtab.h"
 #include "llvm/Object/OffloadBinary.h"
 #include "llvm/Option/ArgList.h"
 #include "llvm/Option/OptTable.h"
@@ -41,6 +46,7 @@
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/InitLLVM.h"
+#include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/Program.h"
 #include "llvm/Support/Signals.h"
@@ -187,84 +193,338 @@ static Error executeCommands(StringRef ExecutablePath,
   return Error::success();
 }
 
-static Expected<SmallVector<std::string>> getInput(const ArgList &Args) {
-  // Collect all input bitcode files to be passed to the linking stage.
-  SmallVector<std::string> BitcodeFiles;
-  auto Inputs = Args.filtered(OPT_INPUT);
-  if (Inputs.empty())
-    return createStringError("no input files provided");
-  for (const opt::Arg *Arg : Inputs) {
-    StringRef Filename = Arg->getValue();
-    if (!sys::fs::exists(Filename) || sys::fs::is_directory(Filename))
-      return createStringError("input file '" + Filename + "' does not exist");
-    file_magic Magic;
-    if (auto EC = identify_magic(Filename, Magic))
-      return createStringError("failed to open file '" + Filename + "'");
-    // TODO: Current use case involves LLVM IR bitcode files as input.
-    // This will be extended to support SPIR-V IR files.
-    if (Magic != file_magic::bitcode)
-      return createStringError("unsupported file type for '" + Filename + "'");
-    BitcodeFiles.push_back(std::string(Filename));
+namespace {
+/// A minimal symbol interface used to drive archive member extraction. Only 
the
+/// flags required by the symbol-resolution fixed-point loop are tracked.
+struct Symbol {
+  enum Flags {
+    None = 0,
+    Undefined = 1 << 0,
+    Weak = 1 << 1,
+  };
+
+  Symbol() : SymFlags(None) {}
+  Symbol(Symbol::Flags F) : SymFlags(F) {}
+  Symbol(const irsymtab::Reader::SymbolRef Sym) : SymFlags(0) {
+    if (Sym.isUndefined())
+      SymFlags |= Undefined;
+    if (Sym.isWeak())
+      SymFlags |= Weak;
   }
-  return BitcodeFiles;
-}
 
-/// Handle cases where input file is a LLVM IR bitcode file.
-/// When clang-sycl-linker is called via clang-linker-wrapper tool, input files
-/// are LLVM IR bitcode files.
-// TODO: Support SPIR-V IR files.
-static Expected<std::unique_ptr<Module>> getBitcodeModule(StringRef File,
-                                                          LLVMContext &C) {
-  SMDiagnostic Err;
+  bool isWeak() const { return SymFlags & Weak; }
+  bool isUndefined() const { return SymFlags & Undefined; }
 
-  auto M = getLazyIRFileModule(File, Err, C);
-  if (M)
-    return std::move(M);
-  return createStringError(Err.getMessage());
-}
+  uint32_t SymFlags;
+};
+
+/// Description of a single input (positional file or -l library).
+struct InputDesc {
+  enum class Kind { File, Library };
+
+  StringRef Value; // File path, or library name for -l (the value after -l).
+  Kind InputKind = Kind::File;
+  bool WholeArchive = false; // --whole-archive state in effect at this input.
+};
+
+/// An input buffer pending archive-member resolution, together with its parsed
+/// IR symbol table. The symbol table is parsed once and reused across all
+/// fixed-point passes so members are not re-parsed on every pass.
+struct PendingInput {
+  std::unique_ptr<MemoryBuffer> Buffer;
+  bool IsLazy = false;
+  bool FromArchive = false;
+  IRSymtabFile Symtab;
+};
+
+/// Resolved input buffers and their target triple.
+struct ResolvedInputs {
+  SmallVector<std::unique_ptr<MemoryBuffer>> Buffers;
+  llvm::Triple TargetTriple;
+  StringRef TripleSource; // Source of the triple (--triple= or filename)
+};
+} // namespace
 
 static std::optional<std::string> findFile(StringRef Dir, const Twine &Name) {
-  SmallString<128> Path(Dir);
-  llvm::sys::path::append(Path, Name);
+  SmallString<128> Path;
+  sys::path::append(Path, Dir, Name);
+  // Skip directories so a directory whose name matches the requested library
+  // does not stop the search; a later -L path may hold the real archive.
   if (sys::fs::exists(Path) && !sys::fs::is_directory(Path))
-    return std::string(Path);
+    return static_cast<std::string>(Path);
   return std::nullopt;
 }
 
 static std::optional<std::string>
-searchLibrary(StringRef Name, ArrayRef<StringRef> SearchPaths) {
-  // An absolute path is taken as-is; -L paths are only consulted for relative
-  // names.
-  if (sys::path::is_absolute(Name)) {
-    if (sys::fs::exists(Name) && !sys::fs::is_directory(Name))
-      return std::string(Name);
-    return std::nullopt;
-  }
+findFromSearchPaths(StringRef Name, ArrayRef<StringRef> SearchPaths) {
   for (StringRef Dir : SearchPaths)
     if (std::optional<std::string> File = findFile(Dir, Name))
       return File;
   return std::nullopt;
 }
 
-/// Gather all library files. The list of files and its location are passed 
from
-/// driver.
-static Expected<SmallVector<std::string>>
-getBCLibraryNames(const ArgList &Args) {
+/// Search for static libraries in the linker's library path given input like
+/// `-lfoo`, `-l:libfoo.a`, or `-l/absolute/path/to/lib.a`.
+static std::optional<std::string> searchLibrary(StringRef Input,
+                                                ArrayRef<StringRef> 
SearchPaths) {
+  // An absolute path is taken as-is; -L paths are only consulted for relative
+  // names.
+  if (sys::path::is_absolute(Input)) {
+    if (sys::fs::exists(Input) && !sys::fs::is_directory(Input))
+      return Input.str();
+    return std::nullopt;
+  }
+
+  if (Input.starts_with(":"))
+    return findFromSearchPaths(Input.drop_front(), SearchPaths);
+  SmallString<128> LibName("lib");
+  LibName += Input;
+  LibName += ".a";
+  return findFromSearchPaths(LibName, SearchPaths);
+}
+
+/// Scan a member's pre-parsed IR symbol table against \p SymTab and return 
true
+/// if the member should be extracted: it is non-lazy, or it defines a symbol
+/// that resolves a currently-undefined reference. Mirrors a linker's archive
+/// member selection.
+static bool scanSymbols(const IRSymtabFile &Symtab, StringMap<Symbol> &SymTab,
+                        bool IsLazy) {
+  bool Extracted = !IsLazy;
+  StringMap<Symbol> PendingSymbols;
+  for (unsigned I = 0; I != Symtab.Mods.size(); ++I) {
+    for (const auto &IRSym : Symtab.TheReader.module_symbols(I)) {
+      if (IRSym.isFormatSpecific() || !IRSym.isGlobal())
+        continue;
+
+      bool IsNewSymbol = IsLazy && !SymTab.count(IRSym.getName());
+      StringMap<Symbol> &Target = IsNewSymbol ? PendingSymbols : SymTab;
+      Symbol &OldSym = Target[IRSym.getName()];
+      Symbol Sym(IRSym);
+
+      if (OldSym.SymFlags == Symbol::None) {
+        OldSym = Sym;
+        if (!IsNewSymbol)
+          continue;
+      }
+
+      bool ResolvesReference =
+          !Sym.isUndefined() &&
+          (OldSym.isUndefined() || (OldSym.isWeak() && !Sym.isWeak())) &&
+          !(OldSym.isWeak() && OldSym.isUndefined() && IsLazy);
+      Extracted |= ResolvesReference;
+
+      if (ResolvesReference)
+        OldSym = Sym;
+    }
+  }
+  if (Extracted && IsLazy)
+    for (const auto &[Name, Sym] : PendingSymbols)
+      SymTab[Name] = Sym;
+  return Extracted;
+}
+
+/// Parse \p Buffer's IR symbol table and append it to \p Inputs. Errors if the
+/// buffer is not LLVM bitcode (the only member type the SYCL linker supports).
+static Error addBitcodeInput(SmallVector<PendingInput> &Inputs,
+                             std::unique_ptr<MemoryBuffer> Buffer, bool IsLazy,
+                             bool FromArchive) {
+  if (identify_magic(Buffer->getBuffer()) != file_magic::bitcode)
+    return createStringError("unsupported file type: '" +
+                             Buffer->getBufferIdentifier() + "'");
+  Expected<IRSymtabFile> SymtabOrErr = readIRSymtab(Buffer->getMemBufferRef());
+  if (!SymtabOrErr)
+    return SymtabOrErr.takeError();
+  Inputs.push_back(
+      {std::move(Buffer), IsLazy, FromArchive, std::move(*SymtabOrErr)});
+  return Error::success();
+}
+
+/// Resolve archive members from the given inputs using a symbol-driven
+/// fixed-point algorithm. For each input:
+/// - If it's a Library, search for lib<name>.a or :<name> in SearchPaths
+/// - If it's a File, use the path directly
+/// - Archives are expanded and members are lazily extracted based on symbol
+///   references unless WholeArchive is true
+/// - Non-archive bitcode inputs are always included
+///
+/// Returns the buffers to link, in extraction order, along with the resolved
+/// target triple. All returned buffers have compatible target triples;
+/// incompatible archive members are filtered during resolution.
+static Expected<ResolvedInputs>
+resolveArchiveMembers(ArrayRef<InputDesc> Order, ArrayRef<StringRef> 
SearchPaths,
+                      ArrayRef<StringRef> ForcedUndefs, StringRef 
TargetTripleArgValue) {
+  // Collect every candidate member, parsing each one's IR symbol table once.
+  SmallVector<PendingInput> Inputs;
+
+  for (const InputDesc &Desc : Order) {
+    std::optional<std::string> Filename;
+
+    if (Desc.InputKind == InputDesc::Kind::Library) {
+      Filename = searchLibrary(Desc.Value, SearchPaths);
+      if (!Filename)
+        return createStringError("unable to find library -l" + Desc.Value);
+    } else {
+      if (!sys::fs::exists(Desc.Value))
+        return createStringError("input file not found: '" + Desc.Value + "'");
+      if (sys::fs::is_directory(Desc.Value))
+        return createStringError("'" + Desc.Value + "': Is a directory");
+      Filename = Desc.Value.str();
+    }
+
+    auto BufferOrErr =
+        errorOrToExpected(MemoryBuffer::getFileOrSTDIN(*Filename));
+    if (!BufferOrErr)
+      return createFileError(*Filename, BufferOrErr.takeError());
+
+    MemoryBufferRef Buffer = (*BufferOrErr)->getMemBufferRef();
+    switch (identify_magic(Buffer.getBuffer())) {
+    case file_magic::bitcode:
+      if (Error Err = addBitcodeInput(Inputs, std::move(*BufferOrErr),
+                                      /*IsLazy=*/false, /*FromArchive=*/false))
+        return Err;
+      break;
+    case file_magic::archive: {
+      Expected<std::unique_ptr<object::Archive>> LibFile =
+          object::Archive::create(Buffer);
+      if (!LibFile)
+        return LibFile.takeError();
+      Error Err = Error::success();
+      for (auto Child : (*LibFile)->children(Err)) {
+        auto ChildBufferOrErr = Child.getMemoryBufferRef();
+        if (!ChildBufferOrErr)
+          return ChildBufferOrErr.takeError();
+        // Include archive name in buffer identifier for better diagnostics.
+        std::string BufferIdentifier =
+            (*Filename + "(" + ChildBufferOrErr->getBufferIdentifier() + ")")
+                .str();
+        std::unique_ptr<MemoryBuffer> ChildBuffer =
+            MemoryBuffer::getMemBufferCopy(ChildBufferOrErr->getBuffer(),
+                                           BufferIdentifier);
+        if (Error E = addBitcodeInput(Inputs, std::move(ChildBuffer),
+                                      !Desc.WholeArchive, 
/*FromArchive=*/true))
+          return E;
+      }
+      if (Err)
+        return Err;
+      break;
+    }
+    default:
+      return createStringError("unsupported file type: '" + *Filename + "'");
+    }
+  }
+
+  // Resolve the target triple: use --triple= if provided, otherwise infer from
+  // the first non-archive input with a non-empty triple.
+  llvm::Triple TargetTriple(TargetTripleArgValue);
+  StringRef TripleSource = TargetTriple.empty() ? "" : "--triple=";
+
+  if (TargetTriple.empty()) {
+    for (const PendingInput &In : Inputs) {
+      if (!In.FromArchive && In.Symtab.Mods.size() > 0) {
+        StringRef Triple = In.Symtab.TheReader.getTargetTriple();
+        if (!Triple.empty()) {
+          TargetTriple = llvm::Triple(Triple);
+          TripleSource = In.Buffer->getBufferIdentifier();
+          break;
+        }
+      }
+    }
+  }
+
+  // Seed symbol table with forced undefined symbols.
+  StringMap<Symbol> SymTab;
+  for (StringRef Sym : ForcedUndefs)
+    SymTab[Sym] = Symbol(Symbol::Undefined);
+
+  // Fixed-point loop to extract archive members. Each pass may resolve symbols
+  // that unlock further members; iterate until no new member is extracted.
+  SmallVector<std::unique_ptr<MemoryBuffer>> Resolved;
+  bool Extracted = true;
+  while (Extracted) {
+    Extracted = false;
+    for (PendingInput &In : Inputs) {
+      if (!In.Buffer)
+        continue;
+
+      // Filter archive members by target triple before symbol scanning.
+      // Members built for a different target are silently skipped, matching 
how
+      // a real linker treats device libraries built for other architectures.
+      if (In.FromArchive) {
+        StringRef MemberTriple = In.Symtab.TheReader.getTargetTriple();
+        if (!MemberTriple.empty() && MemberTriple != TargetTriple.str()) {
+          if (Verbose)
+            errs() << formatv(
+                "archive resolution: skipping {0}: triple {1} != {2}\n",
+                In.Buffer->getBufferIdentifier(), MemberTriple,
+                TargetTriple.str());
+          In.Buffer.reset();
+          continue;
+        }
+      }
+
+      if (!scanSymbols(In.Symtab, SymTab, In.IsLazy))
+        continue;
+      Extracted = true;
+      Resolved.push_back(std::move(In.Buffer));
+    }
+  }
+
+  return ResolvedInputs{std::move(Resolved), std::move(TargetTriple), 
TripleSource};
+}
+
+static Expected<ResolvedInputs>
+getInput(const ArgList &Args) {
+  // Build input descriptors for the archive resolver.
+  SmallVector<InputDesc> InputDescs;
+  bool WholeArchive = false;
+  for (const opt::Arg *Arg : Args.filtered(
+           OPT_INPUT, OPT_library, OPT_whole_archive, OPT_no_whole_archive)) {
+    if (Arg->getOption().matches(OPT_whole_archive) ||
+        Arg->getOption().matches(OPT_no_whole_archive)) {
+      WholeArchive = Arg->getOption().matches(OPT_whole_archive);
+      continue;
+    }
+
+    InputDesc Desc;
+    Desc.Value = Arg->getValue();
+    Desc.InputKind = Arg->getOption().matches(OPT_library)
+                         ? InputDesc::Kind::Library
+                         : InputDesc::Kind::File;
+    Desc.WholeArchive = WholeArchive;
+    InputDescs.push_back(Desc);
+  }
+
+  if (InputDescs.empty())
+    return createStringError("no input files provided");
+
+  // Gather search paths and forced undefined symbols.
   SmallVector<StringRef> LibraryPaths;
   for (const opt::Arg *Arg : Args.filtered(OPT_library_path))
     LibraryPaths.push_back(Arg->getValue());
 
-  SmallVector<std::string> LibraryFiles;
-  for (const opt::Arg *Arg : Args.filtered(OPT_bc_library)) {
-    std::optional<std::string> LibName =
-        searchLibrary(Arg->getValue(), LibraryPaths);
-    if (!LibName)
-      return createStringError("'" + Twine(Arg->getValue()) +
-                               "' library file not found");
-    LibraryFiles.push_back(std::move(*LibName));
-  }
+  // getAllArgValues returns a temporary vector; retain it so the StringRefs
+  // remain valid through the resolveArchiveMembers call.
+  std::vector<std::string> ForcedUndefStorage = Args.getAllArgValues(OPT_u);
+  SmallVector<StringRef> ForcedUndefs(ForcedUndefStorage.begin(),
+                                      ForcedUndefStorage.end());
 
-  return LibraryFiles;
+  // Get target triple from command line if specified.
+  StringRef TargetTripleStr = Args.getLastArgValue(OPT_triple_EQ);
+
+  Expected<ResolvedInputs> ResolvedOrErr =
+      resolveArchiveMembers(InputDescs, LibraryPaths, ForcedUndefs,
+                            TargetTripleStr);
+  if (!ResolvedOrErr)
+    return ResolvedOrErr.takeError();
+
+  if (ResolvedOrErr->Buffers.empty())
+    return createStringError("no input files could be resolved");
+
+  if (ResolvedOrErr->TargetTriple.empty())
+    return createStringError(
+        "target triple must be specified or inferable from inputs");
+
+  return std::move(*ResolvedOrErr);
 }
 
 namespace {
@@ -275,24 +535,18 @@ struct LinkResult {
 };
 } // namespace
 
-/// Following tasks are performed:
-/// 1. Resolve the target triple: use --triple= when given, otherwise take the
-/// first input that supplies a triple as canonical. Issue an error if any
-/// triple inputs disagree.
-/// 2. Link all input bitcode images into one image using the linkInModule API.
-/// 3. Gather all library bitcode images.
-/// 4. Link all the images gathered in Step 3 with the output of Step 2 using
-/// linkInModule API. LinkOnlyNeeded flag is used.
-static Expected<LinkResult> linkInputs(ArrayRef<std::string> InputFiles,
-                                       const ArgList &Args, LLVMContext &C) {
+/// Link all resolved input bitcode images into one module. All resolved inputs
+/// are guaranteed to have compatible target triples (incompatible archive
+/// members are filtered during archive resolution). Triple conflicts between
+/// regular (non-archive) inputs are hard errors caught during resolution.
+static Expected<LinkResult>
+linkInputs(ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs,
+           const llvm::Triple &TargetTriple,
+           StringRef TripleSource,
+           const ArgList &Args, LLVMContext &C) {
   llvm::TimeTraceScope TimeScope("Link code");
 
-  assert(InputFiles.size() && "No inputs to link");
-
-  // Get all library files.
-  Expected<SmallVector<std::string>> BCLibFiles = getBCLibraryNames(Args);
-  if (!BCLibFiles)
-    return BCLibFiles.takeError();
+  assert(Inputs.size() && "No inputs to link");
 
   // Create a new file to write the linked file to.
   auto BitcodeOutput =
@@ -301,56 +555,38 @@ static Expected<LinkResult> 
linkInputs(ArrayRef<std::string> InputFiles,
     return BitcodeOutput.takeError();
 
   if (Verbose) {
-    std::string Inputs = llvm::join(InputFiles.begin(), InputFiles.end(), ", 
");
-    std::string LibInputs =
-        llvm::join((*BCLibFiles).begin(), (*BCLibFiles).end(), ", ");
-    errs() << formatv("link: inputs: {0} libfiles: {1} output: {2}\n", Inputs,
-                      LibInputs, *BitcodeOutput);
+    std::string InputList = llvm::join(
+        llvm::map_range(Inputs,
+                        [](const auto &Buffer) {
+                          return Buffer->getBufferIdentifier();
+                        }),
+        ", ");
+    errs() << formatv("link: inputs: {0} output: {1}\n", InputList,
+                      *BitcodeOutput);
   }
 
-  // Link input files. Resolve the target triple.
-  llvm::Triple TargetTriple(Args.getLastArgValue(OPT_triple_EQ));
-  StringRef TripleSource = TargetTriple.empty() ? "" : "--triple=";
   auto LinkerOutput = std::make_unique<Module>("linker-output", C);
   Linker L(*LinkerOutput);
 
-  for (auto &File : InputFiles) {
-    auto ModOrErr = getBitcodeModule(File, C);
+  for (const auto &Buffer : Inputs) {
+    auto ModOrErr = parseBitcodeFile(Buffer->getMemBufferRef(), C);
     if (!ModOrErr)
       return ModOrErr.takeError();
 
     const llvm::Triple &T = (*ModOrErr)->getTargetTriple();
     if (!T.empty() && T != TargetTriple) {
-      if (TargetTriple.empty()) {
-        TargetTriple = T;
-        TripleSource = File;
-      } else {
-        return createStringError(
-            "conflicting target triples: '" + TargetTriple.str() + "' (from " +
-            TripleSource + ") vs '" + T.str() + "' (from " + File + ")");
-      }
+      // All incompatible archive members should have been filtered during
+      // resolution, so this is a conflict between regular inputs.
+      return createStringError(
+          "conflicting target triples: '" + TargetTriple.str() + "' (from " +
+          TripleSource + ") vs '" + T.str() + "' (from " +
+          Buffer->getBufferIdentifier() + ")");
     }
 
     if (L.linkInModule(std::move(*ModOrErr)))
       return createStringError("could not link IR");
   }
 
-  if (TargetTriple.empty())
-    return createStringError(
-        "target triple must be specified or inferable from inputs");
-
-  // Link in library files.
-  for (auto &File : *BCLibFiles) {
-    auto LibMod = getBitcodeModule(File, C);
-    if (!LibMod)
-      return LibMod.takeError();
-    if ((*LibMod)->getTargetTriple() == TargetTriple) {
-      unsigned Flags = Linker::Flags::LinkOnlyNeeded;
-      if (L.linkInModule(std::move(*LibMod), Flags))
-        return createStringError("could not link IR");
-    }
-  }
-
   // Dump linked output for testing.
   if (Args.hasArg(OPT_print_linked_module))
     outs() << *LinkerOutput;
@@ -693,13 +929,16 @@ static bool canSkipModuleSplit(IRSplitMode Mode, const 
Module &M,
 /// 4. Optionally run AOT compilation when targeting an Intel HW arch.
 /// 5. Pack the resulting images into a single OffloadBinary written to the
 ///    output file.
-static Error runSYCLLink(ArrayRef<std::string> Files, const ArgList &Args) {
+static Error runSYCLLink(ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs,
+                         const llvm::Triple &TargetTriple,
+                         StringRef TripleSource,
+                         const ArgList &Args) {
   llvm::TimeTraceScope TimeScope("SYCL linking");
 
   LLVMContext C;
 
   // Link all input bitcode files and library files.
-  Expected<LinkResult> LinkedOrErr = linkInputs(Files, Args, C);
+  Expected<LinkResult> LinkedOrErr = linkInputs(Inputs, TargetTriple, 
TripleSource, Args, C);
   if (!LinkedOrErr)
     return LinkedOrErr.takeError();
   LinkResult &Result = *LinkedOrErr;
@@ -849,10 +1088,10 @@ int main(int argc, char **argv) {
     reportError(createStringError("output file must be specified"));
   OutputFile = Args.getLastArgValue(OPT_o);
 
-  // Get the input files to pass to the linking stage.
-  auto FilesOrErr = getInput(Args);
-  if (!FilesOrErr)
-    reportError(FilesOrErr.takeError());
+  // Get the input buffers to pass to the linking stage.
+  auto ResolvedInputsOrErr = getInput(Args);
+  if (!ResolvedInputsOrErr)
+    reportError(ResolvedInputsOrErr.takeError());
 
   if (auto *A = Args.getLastArg(OPT_spirv_dump_device_code_EQ)) {
     StringRef V = A->getValue();
@@ -871,7 +1110,9 @@ int main(int argc, char **argv) {
   }
 
   // Run SYCL linking process on the generated inputs.
-  if (Error Err = runSYCLLink(*FilesOrErr, Args))
+  if (Error Err = runSYCLLink(ResolvedInputsOrErr->Buffers,
+                              ResolvedInputsOrErr->TargetTriple,
+                              ResolvedInputsOrErr->TripleSource, Args))
     reportError(std::move(Err));
 
   // Remove the temporary files created.
diff --git a/clang/tools/clang-sycl-linker/SYCLLinkOpts.td 
b/clang/tools/clang-sycl-linker/SYCLLinkOpts.td
index e00e63aa1767d..40f758cc7d837 100644
--- a/clang/tools/clang-sycl-linker/SYCLLinkOpts.td
+++ b/clang/tools/clang-sycl-linker/SYCLLinkOpts.td
@@ -24,12 +24,21 @@ def library_path_S : Separate<["--", "-"], "library-path">, 
Flags<[HelpHidden]>,
 def library_path_EQ : Joined<["--", "-"], "library-path=">, 
Flags<[HelpHidden]>,
   Alias<library_path>;
 
-def bc_library : Separate<["--", "-"], "bc-library">, MetaVarName<"<name>">,
-  HelpText<"Add LLVM bitcode library <name> (with extension) to the link. A "
-          "relative <name> is resolved against -L paths; an absolute path is "
-          "taken as-is.">;
-def bc_library_EQ : Joined<["--", "-"], "bc-library=">, Flags<[HelpHidden]>,
-  Alias<bc_library>;
+def library : JoinedOrSeparate<["-"], "l">, MetaVarName<"<libname>">,
+  HelpText<"Search for library <libname>">;
+def library_S : Separate<["--", "-"], "library">, Flags<[HelpHidden]>,
+  Alias<library>;
+def library_EQ : Joined<["--", "-"], "library=">, Flags<[HelpHidden]>,
+  Alias<library>;
+
+def whole_archive : Flag<["--", "-"], "whole-archive">,
+  HelpText<"Include all archive members in the link">;
+def no_whole_archive : Flag<["--", "-"], "no-whole-archive">,
+  HelpText<"Only include archive members that resolve undefined symbols 
(default)">;
+
+def u : JoinedOrSeparate<["-"], "u">, MetaVarName<"<symbol>">,
+  HelpText<"Force undefined symbol during linking">;
+def undefined : JoinedOrSeparate<["--"], "undefined">, Alias<u>;
 
 def arch_EQ : Joined<["--", "-"], "arch=">,
               Flags<[LinkerOnlyOption]>,

>From 7c75be53cb96294e2f280600d2e1c753e3f4df06 Mon Sep 17 00:00:00 2001
From: Alexey Bader <[email protected]>
Date: Tue, 9 Jun 2026 21:14:42 -0700
Subject: [PATCH 2/2] Fix formatting and basic.ll check on Windows.

---
 .../OffloadTools/clang-sycl-linker/basic.ll   |  2 +-
 .../clang-sycl-linker/ClangSYCLLinker.cpp     | 50 +++++++++----------
 2 files changed, 25 insertions(+), 27 deletions(-)

diff --git a/clang/test/OffloadTools/clang-sycl-linker/basic.ll 
b/clang/test/OffloadTools/clang-sycl-linker/basic.ll
index 564ef6a67f3e9..f3c11b170e02b 100644
--- a/clang/test/OffloadTools/clang-sycl-linker/basic.ll
+++ b/clang/test/OffloadTools/clang-sycl-linker/basic.ll
@@ -41,7 +41,7 @@
 ; RUN: clang-sycl-linker --dry-run -v --module-split-mode=none %t/input1.bc 
%t/input2.bc --library-path=%t/libs --whole-archive -l device -o /dev/null 2>&1 
\
 ; RUN:   | FileCheck %s --check-prefix=DEVLIBS
 ; DEVLIBS:      link: inputs: {{.*}}.bc, {{.*}}.bc, 
{{.*}}libdevice.a(lib1.bc), {{.*}}libdevice.a(lib2.bc) output: 
[[LLVMLINKOUT:.*]].bc
-; DEVLIBS-NEXT: LLVM backend: input: [[LLVMLINKOUT]].bc, output: 
/dev/null_0.spv
+; DEVLIBS-NEXT: LLVM backend: input: [[LLVMLINKOUT]].bc, output: {{.*}}_0.spv
 ; DEVLIBS-NEXT: sycl-bundle: image kind: spv, triple: spirv64, arch: {{$}}
 ; DEVLIBS-NOT:  {{.+}}
 ;
diff --git a/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp 
b/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
index ce76f5841dd52..49b0de703a87e 100644
--- a/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
+++ b/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp
@@ -265,8 +265,8 @@ findFromSearchPaths(StringRef Name, ArrayRef<StringRef> 
SearchPaths) {
 
 /// Search for static libraries in the linker's library path given input like
 /// `-lfoo`, `-l:libfoo.a`, or `-l/absolute/path/to/lib.a`.
-static std::optional<std::string> searchLibrary(StringRef Input,
-                                                ArrayRef<StringRef> 
SearchPaths) {
+static std::optional<std::string>
+searchLibrary(StringRef Input, ArrayRef<StringRef> SearchPaths) {
   // An absolute path is taken as-is; -L paths are only consulted for relative
   // names.
   if (sys::path::is_absolute(Input)) {
@@ -350,9 +350,9 @@ static Error addBitcodeInput(SmallVector<PendingInput> 
&Inputs,
 /// Returns the buffers to link, in extraction order, along with the resolved
 /// target triple. All returned buffers have compatible target triples;
 /// incompatible archive members are filtered during resolution.
-static Expected<ResolvedInputs>
-resolveArchiveMembers(ArrayRef<InputDesc> Order, ArrayRef<StringRef> 
SearchPaths,
-                      ArrayRef<StringRef> ForcedUndefs, StringRef 
TargetTripleArgValue) {
+static Expected<ResolvedInputs> resolveArchiveMembers(
+    ArrayRef<InputDesc> Order, ArrayRef<StringRef> SearchPaths,
+    ArrayRef<StringRef> ForcedUndefs, StringRef TargetTripleArgValue) {
   // Collect every candidate member, parsing each one's IR symbol table once.
   SmallVector<PendingInput> Inputs;
 
@@ -469,11 +469,11 @@ resolveArchiveMembers(ArrayRef<InputDesc> Order, 
ArrayRef<StringRef> SearchPaths
     }
   }
 
-  return ResolvedInputs{std::move(Resolved), std::move(TargetTriple), 
TripleSource};
+  return ResolvedInputs{std::move(Resolved), std::move(TargetTriple),
+                        TripleSource};
 }
 
-static Expected<ResolvedInputs>
-getInput(const ArgList &Args) {
+static Expected<ResolvedInputs> getInput(const ArgList &Args) {
   // Build input descriptors for the archive resolver.
   SmallVector<InputDesc> InputDescs;
   bool WholeArchive = false;
@@ -511,9 +511,8 @@ getInput(const ArgList &Args) {
   // Get target triple from command line if specified.
   StringRef TargetTripleStr = Args.getLastArgValue(OPT_triple_EQ);
 
-  Expected<ResolvedInputs> ResolvedOrErr =
-      resolveArchiveMembers(InputDescs, LibraryPaths, ForcedUndefs,
-                            TargetTripleStr);
+  Expected<ResolvedInputs> ResolvedOrErr = resolveArchiveMembers(
+      InputDescs, LibraryPaths, ForcedUndefs, TargetTripleStr);
   if (!ResolvedOrErr)
     return ResolvedOrErr.takeError();
 
@@ -541,8 +540,7 @@ struct LinkResult {
 /// regular (non-archive) inputs are hard errors caught during resolution.
 static Expected<LinkResult>
 linkInputs(ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs,
-           const llvm::Triple &TargetTriple,
-           StringRef TripleSource,
+           const llvm::Triple &TargetTriple, StringRef TripleSource,
            const ArgList &Args, LLVMContext &C) {
   llvm::TimeTraceScope TimeScope("Link code");
 
@@ -555,12 +553,12 @@ linkInputs(ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs,
     return BitcodeOutput.takeError();
 
   if (Verbose) {
-    std::string InputList = llvm::join(
-        llvm::map_range(Inputs,
-                        [](const auto &Buffer) {
-                          return Buffer->getBufferIdentifier();
-                        }),
-        ", ");
+    std::string InputList =
+        llvm::join(llvm::map_range(Inputs,
+                                   [](const auto &Buffer) {
+                                     return Buffer->getBufferIdentifier();
+                                   }),
+                   ", ");
     errs() << formatv("link: inputs: {0} output: {1}\n", InputList,
                       *BitcodeOutput);
   }
@@ -577,10 +575,10 @@ linkInputs(ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs,
     if (!T.empty() && T != TargetTriple) {
       // All incompatible archive members should have been filtered during
       // resolution, so this is a conflict between regular inputs.
-      return createStringError(
-          "conflicting target triples: '" + TargetTriple.str() + "' (from " +
-          TripleSource + ") vs '" + T.str() + "' (from " +
-          Buffer->getBufferIdentifier() + ")");
+      return createStringError("conflicting target triples: '" +
+                               TargetTriple.str() + "' (from " + TripleSource +
+                               ") vs '" + T.str() + "' (from " +
+                               Buffer->getBufferIdentifier() + ")");
     }
 
     if (L.linkInModule(std::move(*ModOrErr)))
@@ -931,14 +929,14 @@ static bool canSkipModuleSplit(IRSplitMode Mode, const 
Module &M,
 ///    output file.
 static Error runSYCLLink(ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs,
                          const llvm::Triple &TargetTriple,
-                         StringRef TripleSource,
-                         const ArgList &Args) {
+                         StringRef TripleSource, const ArgList &Args) {
   llvm::TimeTraceScope TimeScope("SYCL linking");
 
   LLVMContext C;
 
   // Link all input bitcode files and library files.
-  Expected<LinkResult> LinkedOrErr = linkInputs(Inputs, TargetTriple, 
TripleSource, Args, C);
+  Expected<LinkResult> LinkedOrErr =
+      linkInputs(Inputs, TargetTriple, TripleSource, Args, C);
   if (!LinkedOrErr)
     return LinkedOrErr.takeError();
   LinkResult &Result = *LinkedOrErr;

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to