Description
In Ladybird, all iterators for our AK containers inherit from AK::SimpleIterator: https://github.com/LadybirdBrowser/ladybird/blob/3b04c983f14a3838870f202fe38fe081cb0771b4/AK/Iterator.h#L16
When I tried to use myVec.forEach { elem in ... } in my new code, I ran into two issues:
-
In code that was importing both AK and another module that uses AK, the compiler informed me that the forEach method on my container did not exist.
-
In code that only imports AK (a test file), the compiler confirmed that the exact type used in the other module is indeed a CxxConvertibleToContainer type, but failed with a bogus linker error.
This issue contains a repro for the second issue, since I suspect they are linked.
Reproduction
git clone https://github.com/LadybirdBrowser/ladybird.git
// install dependencies from https://github.com/LadybirdBrowser/ladybird/blob/master/Documentation/BuildInstructionsLadybird.md#build-prerequisites
// apply patch below
cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_SWIFT=ON --preset default
ninja -C release TestAKBindings
./Build/release/bin/TestAKBindings
Apply this patch:
diff --git a/AK/Vector.h b/AK/Vector.h
index 41ac977fa13..5cbf6071544 100644
--- a/AK/Vector.h
+++ b/AK/Vector.h
@@ -857,6 +857,9 @@ private:
template<class... Args>
Vector(Args... args) -> Vector<CommonType<Args...>>;
+using U32Vector = Vector<u32>;
+using U32Vector2Capacity = Vector<u32, 2>;
+
}
#if USING_AK_GLOBALLY
diff --git a/Tests/AK/TestAKBindings.swift b/Tests/AK/TestAKBindings.swift
index 37211c021d4..22c0dc26d4b 100644
--- a/Tests/AK/TestAKBindings.swift
+++ b/Tests/AK/TestAKBindings.swift
@@ -14,6 +14,12 @@ private func isCxxSequenceType<T: ~Copyable>(_ type: borrowing T.Type) -> Bool {
return CxxSequenceMarker<T>.self is ConformanceMarker.Type
}
+enum CxxConvertibleToCollectionMarker<T: ~Copyable> {}
+extension CxxConvertibleToCollectionMarker: ConformanceMarker where T: CxxConvertibleToCollection {}
+private func isCxxConvertibleToCollectionType<T: ~Copyable>(_ type: borrowing T.Type) -> Bool {
+ return CxxConvertibleToCollectionMarker<T>.self is ConformanceMarker.Type
+}
+
class StandardError: TextOutputStream {
func write(_ string: Swift.String) {
try! FileHandle.standardError.write(contentsOf: Data(string.utf8))
@@ -26,7 +32,7 @@ struct TestAKBindings {
var standardError = StandardError()
print("Testing CxxSequence types...", to: &standardError)
- precondition(isCxxSequenceType(AK.StringView.self))
+ //precondition(isCxxSequenceType(AK.StringView.self))
precondition(isCxxSequenceType(AK.Bytes.self))
precondition(isCxxSequenceType(AK.ReadonlyBytes.self))
precondition(isCxxSequenceType(AK.Utf16Data.self))
@@ -35,14 +41,33 @@ struct TestAKBindings {
precondition(!isCxxSequenceType(AK.String.self))
precondition(!isCxxSequenceType(AK.Error.self))
+ precondition(isCxxSequenceType(AK.U32Vector.self))
+ precondition(isCxxSequenceType(AK.U32Vector2Capacity.self))
print("CxxSequence types pass", to: &standardError)
}
+ static func testCollectionTypesAreBound() {
+ var standardError = StandardError()
+ print ("Testing CxxConvertibleToCollection types...", to: &standardError)
+
+ precondition(isCxxConvertibleToCollectionType(AK.U32Vector.self))
+ precondition(isCxxConvertibleToCollectionType(AK.U32Vector2Capacity.self))
+
+ let u32Vector = AK.U32Vector()
+ u32Vector.forEach { _ in }
+
+ let u32Vector2Capacity = AK.U32Vector2Capacity()
+ u32Vector2Capacity.forEach { _ in }
+
+ print("CxxConvertibleToCollection types pass", to: &standardError)
+ }
+
static func main() {
var standardError = StandardError()
print("Starting test suite...", to: &standardError)
testSequenceTypesAreBound()
+ testCollectionTypesAreBound()
print("All tests pass", to: &standardError)
}
Result:
ninja: Entering directory `Build/release/'
[0/2] Re-checking globbed directories...
[1/2] Linking Swift executable bin/TestAKBindings
FAILED: bin/TestAKBindings
: && /home/andrew/.local/bin/swiftc -j 32 -num-threads 32 -emit-executable -o bin/TestAKBindings -O -g Lagom/Tests/AK/CMakeFiles/TestAKBindings.dir/TestAKBindings.swift.o /home/andrew/.local/share/swiftly/toolchains/main-snapshot-2024-10-30/usr/lib/swift/linux/x86_64/swiftrt.o -use-ld=lld -L /home/andrew/ladybird-org/ladybird-browser/Build/release/lib -L /home/andrew/.local/share/swiftly/toolchains/main-snapshot-2024-10-30/usr/lib/swift/linux -L /home/andrew/.local/share/swiftly/toolchains/main-snapshot-2024-10-30/usr/lib/swift/linux/x86_64 -Xlinker -rpath -Xlinker /home/andrew/ladybird-org/ladybird-browser/Build/release/vcpkg_installed/x64-linux/lib:/home/andrew/.local/share/swiftly/toolchains/main-snapshot-2024-10-30/usr/lib/swift/linux:/home/andrew/.local/share/swiftly/toolchains/main-snapshot-2024-10-30/usr/lib/swift/linux/x86_64:/home/andrew/ladybird-org/ladybird-browser/Build/release/lib lib/liblagom-ak.so.0.0.0 && :
error: link command failed with exit code 1 (use -v to see invocation)
ld.lld: error: undefined symbol: AK::SimpleIterator<AK::Vector<unsigned int, 0ul> const, unsigned int const>::operator==(AK::SimpleIterator<AK::Vector<unsigned int, 0ul> const, unsigned int const> const&) const
>>> referenced by TestAKBindings.swift.o
>>> Lagom/Tests/AK/CMakeFiles/TestAKBindings.dir/TestAKBindings.swift.o:($sSo2AKO0095SimpleIteratorVectorCUnsignedInt_CUnsignedLong_0_constCUnsignedInt_const_mrDAEawaAJfxaDAjgaCqAaVSQSCSQ2eeoiySbx_xtFZTW)
>>> referenced by <compiler-generated>:0 (/<compiler-generated>:0)
>>> Lagom/Tests/AK/CMakeFiles/TestAKBindings.dir/TestAKBindings.swift.o:($sSTsE21_copySequenceContents12initializing8IteratorQz_SitSry7ElementQzG_tFSo2AKO0047VectorCUnsignedInt_CUnsignedLong_0_sfBJmlmajtndV_Tgq5)
>>> referenced by <stdin>:0
>>> Lagom/Tests/AK/CMakeFiles/TestAKBindings.dir/TestAKBindings.swift.o:($s14TestAKBindingsAAV27testCollectionTypesAreBoundyyFZTf4d_n)
>>> referenced 1 more times
ld.lld: error: undefined symbol: AK::SimpleIterator<AK::Vector<unsigned int, 2ul> const, unsigned int const>::operator==(AK::SimpleIterator<AK::Vector<unsigned int, 2ul> const, unsigned int const> const&) const
>>> referenced by TestAKBindings.swift.o
>>> Lagom/Tests/AK/CMakeFiles/TestAKBindings.dir/TestAKBindings.swift.o:($sSo2AKO0095SimpleIteratorVectorCUnsignedInt_CUnsignedLong_2_constCUnsignedInt_const_mrDAEawaAJfxaDAjgaCqAaVSQSCSQ2eeoiySbx_xtFZTW)
>>> referenced by <compiler-generated>:0 (/<compiler-generated>:0)
>>> Lagom/Tests/AK/CMakeFiles/TestAKBindings.dir/TestAKBindings.swift.o:($sSTsE21_copySequenceContents12initializing8IteratorQz_SitSry7ElementQzG_tFSo2AKO0047VectorCUnsignedInt_CUnsignedLong_2_sfBJmlmajtndV_Tgq5)
>>> referenced by <stdin>:0
>>> Lagom/Tests/AK/CMakeFiles/TestAKBindings.dir/TestAKBindings.swift.o:($s14TestAKBindingsAAV27testCollectionTypesAreBoundyyFZTf4d_n)
>>> referenced 1 more times
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ninja: build stopped: subcommand failed.
Expected behavior
The test should compile, link, and run without issue.
Environment
$ swiftc -version
Swift version 6.1-dev (LLVM 9d655fdc6103926, Swift 0d16cbf)
Target: x86_64-unknown-linux-gnu
Distro: Ubuntu 24.04
uname -a: Linux Andrew-Workstation 6.8.0-48-generic #48-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 27 14:04:52 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
Additional information
operator== on the iterator type is defined as
constexpr bool operator==(SimpleIterator const& other) const = default;
constexpr auto operator<=>(SimpleIterator const& other) const = default;
Removing constexpr has no effect.
Adding a translation unit to force generation of the operator== (see details pane below) adds the operator== as LOCAL HIDDEN symbols. Clang seems to expect each TU to generate these symbols if it needs them.
Patch to liblagom-ak.so to contain the missing symbols
diff --git a/AK/AK+Swift.cpp b/AK/AK+Swift.cpp
new file mode 100644
index 00000000000..f9599211337
--- /dev/null
+++ b/AK/AK+Swift.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/Format.h>
+#include <AK/Vector.h>
+
+#pragma clang optimize off
+
+namespace AK {
+
+void __swift_use_vector();
+
+[[gnu::used, clang::optnone]] void __swift_use_vector() {
+ auto x = Vector<u32> {};
+ x.append(1);
+
+ for (auto i : x) {
+ dbgln("{}", i);
+ }
+
+ auto x2 = Vector<u32, 2> {};
+ x2.append(1);
+ x2.append(2);
+ for (auto i : x2) {
+ dbgln("{}", i);
+ }
+}
+
+}
diff --git a/AK/CMakeLists.txt b/AK/CMakeLists.txt
index 3fbdc65b90a..21335e70efa 100644
--- a/AK/CMakeLists.txt
+++ b/AK/CMakeLists.txt
@@ -80,7 +80,7 @@ if (ENABLE_SWIFT)
"${CMAKE_CURRENT_BINARY_DIR}/Backtrace.h"
"${CMAKE_CURRENT_BINARY_DIR}/Debug.h"
)
- target_sources(AK PRIVATE AK+Swift.swift)
+ target_sources(AK PRIVATE AK+Swift.swift AK+Swift.cpp)
add_swift_target_properties(AK)
endif()
diff --git a/AK/Vector.h b/AK/Vector.h
index 41ac977fa13..5cbf6071544 100644
--- a/AK/Vector.h
+++ b/AK/Vector.h
@@ -857,6 +857,9 @@ private:
template<class... Args>
Vector(Args... args) -> Vector<CommonType<Args...>>;
+using U32Vector = Vector<u32>;
+using U32Vector2Capacity = Vector<u32, 2>;
+
}
#if USING_AK_GLOBALLY
diff --git a/Tests/AK/TestAKBindings.swift b/Tests/AK/TestAKBindings.swift
index 37211c021d4..22c0dc26d4b 100644
--- a/Tests/AK/TestAKBindings.swift
+++ b/Tests/AK/TestAKBindings.swift
@@ -14,6 +14,12 @@ private func isCxxSequenceType<T: ~Copyable>(_ type: borrowing T.Type) -> Bool {
return CxxSequenceMarker<T>.self is ConformanceMarker.Type
}
+enum CxxConvertibleToCollectionMarker<T: ~Copyable> {}
+extension CxxConvertibleToCollectionMarker: ConformanceMarker where T: CxxConvertibleToCollection {}
+private func isCxxConvertibleToCollectionType<T: ~Copyable>(_ type: borrowing T.Type) -> Bool {
+ return CxxConvertibleToCollectionMarker<T>.self is ConformanceMarker.Type
+}
+
class StandardError: TextOutputStream {
func write(_ string: Swift.String) {
try! FileHandle.standardError.write(contentsOf: Data(string.utf8))
@@ -26,7 +32,7 @@ struct TestAKBindings {
var standardError = StandardError()
print("Testing CxxSequence types...", to: &standardError)
- precondition(isCxxSequenceType(AK.StringView.self))
+ //precondition(isCxxSequenceType(AK.StringView.self))
precondition(isCxxSequenceType(AK.Bytes.self))
precondition(isCxxSequenceType(AK.ReadonlyBytes.self))
precondition(isCxxSequenceType(AK.Utf16Data.self))
@@ -35,14 +41,33 @@ struct TestAKBindings {
precondition(!isCxxSequenceType(AK.String.self))
precondition(!isCxxSequenceType(AK.Error.self))
+ precondition(isCxxSequenceType(AK.U32Vector.self))
+ precondition(isCxxSequenceType(AK.U32Vector2Capacity.self))
print("CxxSequence types pass", to: &standardError)
}
+ static func testCollectionTypesAreBound() {
+ var standardError = StandardError()
+ print ("Testing CxxConvertibleToCollection types...", to: &standardError)
+
+ precondition(isCxxConvertibleToCollectionType(AK.U32Vector.self))
+ precondition(isCxxConvertibleToCollectionType(AK.U32Vector2Capacity.self))
+
+ let u32Vector = AK.U32Vector()
+ u32Vector.forEach { _ in }
+
+ let u32Vector2Capacity = AK.U32Vector2Capacity()
+ u32Vector2Capacity.forEach { _ in }
+
+ print("CxxConvertibleToCollection types pass", to: &standardError)
+ }
+
static func main() {
var standardError = StandardError()
print("Starting test suite...", to: &standardError)
testSequenceTypesAreBound()
+ testCollectionTypesAreBound()
print("All tests pass", to: &standardError)
}
The symbols appear like so, with that patch applied:
readelf -Ws Build/release/lib/liblagom-ak.so.0.0.0 | llvm-cxxfilt-18 | grep "operator==(AK::SimpleIterator"
1253: 0000000000064c70 21 FUNC LOCAL HIDDEN 27 AK::SimpleIterator<AK::Vector<unsigned int, 0ul>, unsigned int>::operator==(AK::SimpleIterator<AK::Vector<unsigned int, 0ul>, unsigned int> const&) const
1259: 0000000000064d50 21 FUNC LOCAL HIDDEN 27 AK::SimpleIterator<AK::Vector<unsigned int, 2ul>, unsigned int>::operator==(AK::SimpleIterator<AK::Vector<unsigned int, 2ul>, unsigned int> const&) const
Description
In Ladybird, all iterators for our AK containers inherit from AK::SimpleIterator: https://github.com/LadybirdBrowser/ladybird/blob/3b04c983f14a3838870f202fe38fe081cb0771b4/AK/Iterator.h#L16
When I tried to use
myVec.forEach { elem in ... }in my new code, I ran into two issues:In code that was importing both AK and another module that uses AK, the compiler informed me that the forEach method on my container did not exist.
In code that only imports AK (a test file), the compiler confirmed that the exact type used in the other module is indeed a CxxConvertibleToContainer type, but failed with a bogus linker error.
This issue contains a repro for the second issue, since I suspect they are linked.
Reproduction
Apply this patch:
Result:
Expected behavior
The test should compile, link, and run without issue.
Environment
$ swiftc -version
Swift version 6.1-dev (LLVM 9d655fdc6103926, Swift 0d16cbf)
Target: x86_64-unknown-linux-gnu
Distro: Ubuntu 24.04
uname -a: Linux Andrew-Workstation 6.8.0-48-generic #48-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 27 14:04:52 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
Additional information
operator== on the iterator type is defined as
Removing constexpr has no effect.
Adding a translation unit to force generation of the operator== (see details pane below) adds the operator== as LOCAL HIDDEN symbols. Clang seems to expect each TU to generate these symbols if it needs them.
Patch to liblagom-ak.so to contain the missing symbols
The symbols appear like so, with that patch applied: