diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserSwiftDescriptorFinder.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserSwiftDescriptorFinder.cpp index da9666bed58d8..1f7b177bd6393 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserSwiftDescriptorFinder.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserSwiftDescriptorFinder.cpp @@ -30,6 +30,7 @@ #include "lldb/Utility/Flags.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" +#include "llvm/Support/MathExtras.h" using namespace lldb; using namespace lldb_private; @@ -593,12 +594,16 @@ getDWARFBuiltinTypeDescriptor(TypeSystemSwiftTypeRef &swift_typesystem, if (is_enum && byte_size == 0) return nullptr; - auto alignment = die.GetAttributeValueAsUnsigned(llvm::dwarf::DW_AT_alignment, - byte_size ? byte_size : 8); + // The compiler only emits the alignment if it's not the default. For an enum, + // leave it as 0, which type lowering understands as "no known alignment" and + // recomputes from the payloads. + std::optional alignment_attr = + die.GetAttributeValueAsOptionalUnsigned(llvm::dwarf::DW_AT_alignment); + uint64_t alignment = + alignment_attr.value_or(is_enum ? 0 : (byte_size ? byte_size : 8)); - // TODO: this seems simple to calculate but maybe we should encode the stride - // in DWARF? That's what reflection metadata does. - unsigned stride = ((byte_size + alignment - 1) & ~(alignment - 1)); + // If the alignment is unknown, so is the stride. + unsigned stride = alignment ? llvm::alignTo(byte_size, alignment) : 0; auto num_extra_inhabitants = die.GetAttributeValueAsUnsigned( llvm::dwarf::DW_AT_LLVM_num_extra_inhabitants, 0); diff --git a/lldb/test/API/lang/swift/embedded/multipayload_enum_alignment/Makefile b/lldb/test/API/lang/swift/embedded/multipayload_enum_alignment/Makefile new file mode 100644 index 0000000000000..2a69023633b34 --- /dev/null +++ b/lldb/test/API/lang/swift/embedded/multipayload_enum_alignment/Makefile @@ -0,0 +1,3 @@ +SWIFT_SOURCES := main.swift + +include Makefile.rules diff --git a/lldb/test/API/lang/swift/embedded/multipayload_enum_alignment/TestSwiftEmbeddedMultiPayloadEnumAlignment.py b/lldb/test/API/lang/swift/embedded/multipayload_enum_alignment/TestSwiftEmbeddedMultiPayloadEnumAlignment.py new file mode 100644 index 0000000000000..6ebd2dbc5b92f --- /dev/null +++ b/lldb/test/API/lang/swift/embedded/multipayload_enum_alignment/TestSwiftEmbeddedMultiPayloadEnumAlignment.py @@ -0,0 +1,77 @@ +""" +Test that a multi-payload enum's alignment is not fabricated from its +DW_AT_byte_size in embedded Swift. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +import lldbsuite.test.lldbutil as lldbutil + + +class TestSwiftEmbeddedMultiPayloadEnumAlignment(TestBase): + def setup_test(self): + self.build() + self.runCmd("setting set symbols.swift-enable-ast-context false") + _, _, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec("main.swift") + ) + return thread.GetSelectedFrame() + + @skipUnlessDarwin + @skipUnlessEmbeddedSwift + # The expected sizes and offsets assume Int64 is 8-byte aligned, which holds + # on arm64 (including arm64_32) and x86_64, but not on every 32-bit ABI. + @skipIf(archs=no_match(["arm64", "x86_64"])) + @swiftTest + def test_enum_stride(self): + frame = self.setup_test() + + wide_enum = frame.FindVariable("w").GetType() + self.assertIn("WideEnum", wide_enum.GetName()) + self.assertEqual(wide_enum.GetByteSize(), 34) + + pairs = frame.FindVariable("pairs") + self.assertTrue(pairs.IsValid(), "pairs is valid") + self.assertEqual(pairs.GetType().GetByteSize(), 74) + self.assertEqual(pairs.GetNumChildren(), 2) + + base = pairs.GetLoadAddress() + self.assertNotEqual(base, lldb.LLDB_INVALID_ADDRESS) + self.assertEqual(pairs.GetChildAtIndex(0).GetLoadAddress() - base, 0) + self.assertEqual( + pairs.GetChildAtIndex(1).GetLoadAddress() - base, + 40, + "WideEnum's stride is alignUp(34, 8) = 40, not alignUp(34, 34) = 66", + ) + + self.expect( + "frame variable pairs", + substrs=["first", "pair", "0 = 7", "1 = 8", "second", "0 = 9", "1 = 10"], + ) + + @skipUnlessDarwin + @skipUnlessEmbeddedSwift + @skipIf(archs=no_match(["arm64", "x86_64"])) + @swiftTest + def test_enum_alignment(self): + frame = self.setup_test() + + prefixed = frame.FindVariable("prefixed") + self.assertTrue(prefixed.IsValid(), "prefixed is valid") + self.assertEqual(prefixed.GetType().GetByteSize(), 42) + + base = prefixed.GetLoadAddress() + self.assertNotEqual(base, lldb.LLDB_INVALID_ADDRESS) + payload = prefixed.GetChildMemberWithName("payload") + self.assertTrue(payload.IsValid(), "payload is valid") + self.assertEqual( + payload.GetLoadAddress() - base, + 8, + "WideEnum's alignment is 8 (the max of the payload alignments), not 34", + ) + + self.expect( + "frame variable prefixed", + substrs=["tag = 3", "payload", "pair", "0 = 11", "1 = 12"], + ) diff --git a/lldb/test/API/lang/swift/embedded/multipayload_enum_alignment/main.swift b/lldb/test/API/lang/swift/embedded/multipayload_enum_alignment/main.swift new file mode 100644 index 0000000000000..b1cbf7c2503d8 --- /dev/null +++ b/lldb/test/API/lang/swift/embedded/multipayload_enum_alignment/main.swift @@ -0,0 +1,58 @@ +// Every size and offset this test checks is expressed in explicitly sized types +// (Int64/UInt8/Int8), so none of them depend on the pointer width. They do +// depend on Int64 being 8-byte aligned, which the test gates on. + +// A 33-byte payload: four Int64s (alignment 8) plus a trailing UInt8. Its +// DW_AT_byte_size (33) is deliberately not a power of two and much larger than +// its real alignment (8). +struct Wide { + var a: Int64 = 1 + var b: Int64 = 2 + var c: Int64 = 3 + var d: Int64 = 4 + var e: UInt8 = 5 +} + +// A multi-payload enum: emitted as a DW_TAG_structure_type whose first child is +// a DW_TAG_variant_part, with DW_AT_byte_size 34 and *no* DW_AT_alignment. Its +// authoritative layout is alignment 8 (the max of its payload alignments) and +// stride alignUp(34, 8) = 40. +enum WideEnum { + case wide(Wide) + case pair(Int64, Int64) + case none +} + +// Two enums back to back: the offset of `second`, and the whole struct's size, +// are a direct readout of the enum's stride. Correct: 40 + 34 = 74. +struct EnumPair { + var first: WideEnum + var second: WideEnum +} + +// A one-byte prefix followed by an enum: the offset of `payload` is a readout +// of the enum's alignment. Correct: the payload lands at alignUp(1, 8) = 8, so +// the struct's size is 8 + 34 = 42. A fabricated alignment of 34 rounded 1 up +// to 2 instead (the mask-based round-up is only valid for powers of two). +struct PrefixedEnum { + var tag: Int8 + var payload: WideEnum +} + +@inline(never) +func blackHole(_ x: Int64) {} + +func f() { + let w = WideEnum.wide(Wide()) + let pairs = EnumPair(first: .pair(7, 8), second: .pair(9, 10)) + let prefixed = PrefixedEnum(tag: 3, payload: .pair(11, 12)) + + if case .pair(let x, _) = pairs.second { blackHole(x) } + if case .pair(let y, _) = prefixed.payload { blackHole(y) } + if case .wide(let z) = w { blackHole(z.a) } + + let s = StaticString("break here") + print(s) // break here +} + +f()