Skip to content

Commit cedfa64

Browse files
authored
Merge pull request #21683 from atrick/5.0-fix-align-mask
Force manual allocation (via Unsafe*Pointer) to use >= 16 alignment.
2 parents b97ac0d + 4dd1a11 commit cedfa64

File tree

7 files changed

+164
-11
lines changed

7 files changed

+164
-11
lines changed

include/swift/AST/Builtins.def

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,9 +423,29 @@ BUILTIN_MISC_OPERATION(IsSameMetatype, "is_same_metatype", "n", Special)
423423
BUILTIN_MISC_OPERATION(Alignof, "alignof", "n", Special)
424424

425425
/// AllocRaw has type (Int, Int) -> Builtin.RawPointer
426+
///
427+
/// Parameters: object size, object alignment.
428+
///
429+
/// This alignment is not a mask; the compiler decrements by one to provide
430+
/// a mask to the runtime.
431+
///
432+
/// If alignment == 0, then the runtime will use "aligned" allocation,
433+
/// and the memory will be aligned to _swift_MinAllocationAlignment.
426434
BUILTIN_MISC_OPERATION(AllocRaw, "allocRaw", "", Special)
427435

428436
/// DeallocRaw has type (Builtin.RawPointer, Int, Int) -> ()
437+
///
438+
/// Parameters: object address, object size, object alignment.
439+
///
440+
/// This alignment is not a mask; the compiler decrements by one to provide
441+
/// a mask to the runtime.
442+
///
443+
/// If alignment == 0, then the runtime will use the "aligned" deallocation
444+
/// path, which assumes that "aligned" allocation was used.
445+
///
446+
/// Note that the alignment value provided to `deallocRaw` must be identical to
447+
/// the alignment value provided to `allocRaw` when the memory at this address
448+
/// was allocated.
429449
BUILTIN_MISC_OPERATION(DeallocRaw, "deallocRaw", "", Special)
430450

431451
/// Fence has type () -> ().

stdlib/public/SwiftShims/RuntimeShims.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,26 @@ int _swift_stdlib_putc_stderr(int C);
5656
SWIFT_RUNTIME_STDLIB_API
5757
__swift_size_t _swift_stdlib_getHardwareConcurrency();
5858

59+
/// Manually allocated memory is at least 16-byte aligned in Swift.
60+
///
61+
/// When swift_slowAlloc is called with "default" alignment (alignMask ==
62+
/// ~(size_t(0))), it will execute the "aligned allocation path" (AlignedAlloc)
63+
/// using this value for the alignment.
64+
///
65+
/// This is done so users do not need to specify the allocation alignment when
66+
/// manually deallocating memory via Unsafe[Raw][Buffer]Pointer. Any
67+
/// user-specified alignment less than or equal to _swift_MinAllocationAlignment
68+
/// results in a runtime request for "default" alignment. This guarantees that
69+
/// manual allocation always uses an "aligned" runtime allocation. If an
70+
/// allocation is "aligned" then it must be freed using an "aligned"
71+
/// deallocation. The converse must also hold. Since manual Unsafe*Pointer
72+
/// deallocation is always "aligned", the user never needs to specify alignment
73+
/// during deallocation.
74+
///
75+
/// This value is inlined (and constant propagated) in user code. On Windows,
76+
/// the Swift runtime and user binaries need to agree on this value.
77+
#define _swift_MinAllocationAlignment (__swift_size_t) 16
78+
5979
#ifdef __cplusplus
6080
}} // extern "C", namespace swift
6181
#endif

stdlib/public/core/Builtin.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,6 @@ public func _unsafeUncheckedDowncast<T : AnyObject>(_ x: AnyObject, to type: T.T
242242
return Builtin.castReference(x)
243243
}
244244

245-
import SwiftShims
246-
247245
@inlinable
248246
@inline(__always)
249247
public func _getUnsafePointerToStoredProperties(_ x: AnyObject)
@@ -255,6 +253,18 @@ public func _getUnsafePointerToStoredProperties(_ x: AnyObject)
255253
storedPropertyOffset
256254
}
257255

256+
/// Get the minimum alignment for manually allocated memory.
257+
///
258+
/// Memory allocated via UnsafeMutable[Raw][Buffer]Pointer must never pass
259+
/// an alignment less than this value to Builtin.allocRaw. This
260+
/// ensures that the memory can be deallocated without specifying the
261+
/// alignment.
262+
@inlinable
263+
@inline(__always)
264+
internal func _minAllocationAlignment() -> Int {
265+
return _swift_MinAllocationAlignment
266+
}
267+
258268
//===----------------------------------------------------------------------===//
259269
// Branch hints
260270
//===----------------------------------------------------------------------===//

stdlib/public/core/UnsafeBufferPointer.swift.gyb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,22 @@ extension Unsafe${Mutable}BufferPointer {
567567
public static func allocate(capacity count: Int)
568568
-> UnsafeMutableBufferPointer<Element> {
569569
let size = MemoryLayout<Element>.stride * count
570-
let raw = Builtin.allocRaw(size._builtinWordValue, Builtin.alignof(Element.self))
570+
// For any alignment <= _minAllocationAlignment, force alignment = 0.
571+
// This forces the runtime's "aligned" allocation path so that
572+
// deallocation does not require the original alignment.
573+
//
574+
// The runtime guarantees:
575+
//
576+
// align == 0 || align > _minAllocationAlignment:
577+
// Runtime uses "aligned allocation".
578+
//
579+
// 0 < align <= _minAllocationAlignment:
580+
// Runtime may use either malloc or "aligned allocation".
581+
var align = Builtin.alignof(Element.self)
582+
if Int(align) <= _minAllocationAlignment() {
583+
align = (0)._builtinWordValue
584+
}
585+
let raw = Builtin.allocRaw(size._builtinWordValue, align)
571586
Builtin.bindMemory(raw, count._builtinWordValue, Element.self)
572587
return UnsafeMutableBufferPointer(
573588
start: UnsafeMutablePointer(raw), count: count)

stdlib/public/core/UnsafePointer.swift

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,11 @@ public struct UnsafePointer<Pointee>: _Pointer {
225225
/// block. The memory must not be initialized or `Pointee` must be a trivial type.
226226
@inlinable
227227
public func deallocate() {
228-
Builtin.deallocRaw(_rawValue, (-1)._builtinWordValue, (-1)._builtinWordValue)
228+
// Passing zero alignment to the runtime forces "aligned
229+
// deallocation". Since allocation via `UnsafeMutable[Raw][Buffer]Pointer`
230+
// always uses the "aligned allocation" path, this ensures that the
231+
// runtime's allocation and deallocation paths are compatible.
232+
Builtin.deallocRaw(_rawValue, (-1)._builtinWordValue, (0)._builtinWordValue)
229233
}
230234

231235
/// Accesses the instance referenced by this pointer.
@@ -568,8 +572,22 @@ public struct UnsafeMutablePointer<Pointee>: _Pointer {
568572
public static func allocate(capacity count: Int)
569573
-> UnsafeMutablePointer<Pointee> {
570574
let size = MemoryLayout<Pointee>.stride * count
571-
let rawPtr =
572-
Builtin.allocRaw(size._builtinWordValue, Builtin.alignof(Pointee.self))
575+
// For any alignment <= _minAllocationAlignment, force alignment = 0.
576+
// This forces the runtime's "aligned" allocation path so that
577+
// deallocation does not require the original alignment.
578+
//
579+
// The runtime guarantees:
580+
//
581+
// align == 0 || align > _minAllocationAlignment:
582+
// Runtime uses "aligned allocation".
583+
//
584+
// 0 < align <= _minAllocationAlignment:
585+
// Runtime may use either malloc or "aligned allocation".
586+
var align = Builtin.alignof(Pointee.self)
587+
if Int(align) <= _minAllocationAlignment() {
588+
align = (0)._builtinWordValue
589+
}
590+
let rawPtr = Builtin.allocRaw(size._builtinWordValue, align)
573591
Builtin.bindMemory(rawPtr, count._builtinWordValue, Pointee.self)
574592
return UnsafeMutablePointer(rawPtr)
575593
}
@@ -580,7 +598,11 @@ public struct UnsafeMutablePointer<Pointee>: _Pointer {
580598
/// block. The memory must not be initialized or `Pointee` must be a trivial type.
581599
@inlinable
582600
public func deallocate() {
583-
Builtin.deallocRaw(_rawValue, (-1)._builtinWordValue, (-1)._builtinWordValue)
601+
// Passing zero alignment to the runtime forces "aligned
602+
// deallocation". Since allocation via `UnsafeMutable[Raw][Buffer]Pointer`
603+
// always uses the "aligned allocation" path, this ensures that the
604+
// runtime's allocation and deallocation paths are compatible.
605+
Builtin.deallocRaw(_rawValue, (-1)._builtinWordValue, (0)._builtinWordValue)
584606
}
585607

586608
/// Accesses the instance referenced by this pointer.

stdlib/public/core/UnsafeRawPointer.swift

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,11 @@ public struct UnsafeRawPointer: _Pointer {
241241
/// trivial type.
242242
@inlinable
243243
public func deallocate() {
244-
Builtin.deallocRaw(_rawValue, (-1)._builtinWordValue, (-1)._builtinWordValue)
244+
// Passing zero alignment to the runtime forces "aligned
245+
// deallocation". Since allocation via `UnsafeMutable[Raw][Buffer]Pointer`
246+
// always uses the "aligned allocation" path, this ensures that the
247+
// runtime's allocation and deallocation paths are compatible.
248+
Builtin.deallocRaw(_rawValue, (-1)._builtinWordValue, (0)._builtinWordValue)
245249
}
246250

247251
/// Binds the memory to the specified type and returns a typed pointer to the
@@ -577,6 +581,21 @@ public struct UnsafeMutableRawPointer: _Pointer {
577581
public static func allocate(
578582
byteCount: Int, alignment: Int
579583
) -> UnsafeMutableRawPointer {
584+
// For any alignment <= _minAllocationAlignment, force alignment = 0.
585+
// This forces the runtime's "aligned" allocation path so that
586+
// deallocation does not require the original alignment.
587+
//
588+
// The runtime guarantees:
589+
//
590+
// align == 0 || align > _minAllocationAlignment:
591+
// Runtime uses "aligned allocation".
592+
//
593+
// 0 < align <= _minAllocationAlignment:
594+
// Runtime may use either malloc or "aligned allocation".
595+
var alignment = alignment
596+
if alignment <= _minAllocationAlignment() {
597+
alignment = 0
598+
}
580599
return UnsafeMutableRawPointer(Builtin.allocRaw(
581600
byteCount._builtinWordValue, alignment._builtinWordValue))
582601
}
@@ -587,7 +606,11 @@ public struct UnsafeMutableRawPointer: _Pointer {
587606
/// trivial type.
588607
@inlinable
589608
public func deallocate() {
590-
Builtin.deallocRaw(_rawValue, (-1)._builtinWordValue, (-1)._builtinWordValue)
609+
// Passing zero alignment to the runtime forces "aligned
610+
// deallocation". Since allocation via `UnsafeMutable[Raw][Buffer]Pointer`
611+
// always uses the "aligned allocation" path, this ensures that the
612+
// runtime's allocation and deallocation paths are compatible.
613+
Builtin.deallocRaw(_rawValue, (-1)._builtinWordValue, (0)._builtinWordValue)
591614
}
592615

593616
/// Binds the memory to the specified type and returns a typed pointer to the

stdlib/public/runtime/Heap.cpp

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "swift/Runtime/Heap.h"
1919
#include "Private.h"
2020
#include "swift/Runtime/Debug.h"
21+
#include "../SwiftShims/RuntimeShims.h"
2122
#include <algorithm>
2223
#include <stdlib.h>
2324

@@ -48,19 +49,61 @@ using namespace swift;
4849

4950
#endif
5051

52+
// This assert ensures that manually allocated memory always uses the
53+
// AlignedAlloc path. The stdlib will use "default" alignment for any user
54+
// requested alignment less than or equal to _swift_MinAllocationAlignment. The
55+
// runtime must ensure that any alignment > _swift_MinAllocationAlignment also
56+
// uses the "aligned" deallocation path.
57+
static_assert(_swift_MinAllocationAlignment > MALLOC_ALIGN_MASK,
58+
"Swift's default alignment must exceed platform malloc mask.");
5159

52-
60+
// When alignMask == ~(size_t(0)), allocation uses the "default"
61+
// _swift_MinAllocationAlignment. This is different than calling swift_slowAlloc
62+
// with `alignMask == _swift_MinAllocationAlignment - 1` because it forces
63+
// the use of AlignedAlloc. This allows manually allocated to memory to always
64+
// be deallocated with AlignedFree without knowledge of its original allocation
65+
// alignment.
66+
//
67+
// For alignMask > (_minAllocationAlignment-1)
68+
// i.e. alignment == 0 || alignment > _minAllocationAlignment:
69+
// The runtime must use AlignedAlloc, and the standard library must
70+
// deallocate using an alignment that meets the same condition.
71+
//
72+
// For alignMask <= (_minAllocationAlignment-1)
73+
// i.e. 0 < alignment <= _minAllocationAlignment:
74+
// The runtime may use either malloc or AlignedAlloc, and the standard library
75+
// must deallocate using an identical alignment.
5376
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
5477
void *p;
78+
// This check also forces "default" alignment to use AlignedAlloc.
5579
if (alignMask <= MALLOC_ALIGN_MASK) {
5680
p = malloc(size);
5781
} else {
58-
p = AlignedAlloc(size, alignMask + 1);
82+
size_t alignment = (alignMask == ~(size_t(0)))
83+
? _swift_MinAllocationAlignment
84+
: alignMask + 1;
85+
p = AlignedAlloc(size, alignment);
5986
}
6087
if (!p) swift::crash("Could not allocate memory.");
6188
return p;
6289
}
6390

91+
// Unknown alignment is specified by passing alignMask == ~(size_t(0)), forcing
92+
// the AlignedFree deallocation path for unknown alignment. The memory
93+
// deallocated with unknown alignment must have been allocated with either
94+
// "default" alignment, or alignment > _swift_MinAllocationAlignment, to
95+
// guarantee that it was allocated with AlignedAlloc.
96+
//
97+
// The standard library assumes the following behavior:
98+
//
99+
// For alignMask > (_minAllocationAlignment-1)
100+
// i.e. alignment == 0 || alignment > _minAllocationAlignment:
101+
// The runtime must use AlignedFree.
102+
//
103+
// For alignMask <= (_minAllocationAlignment-1)
104+
// i.e. 0 < alignment <= _minAllocationAlignment:
105+
// The runtime may use either `free` or AlignedFree as long as it is
106+
// consistent with allocation with the same alignment.
64107
void swift::swift_slowDealloc(void *ptr, size_t bytes, size_t alignMask) {
65108
if (alignMask <= MALLOC_ALIGN_MASK) {
66109
free(ptr);

0 commit comments

Comments
 (0)