1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <algorithm>
6 #include <cstddef>
7 #include <cstdlib>
8 #include <cstring>
9
10 #include "build/build_config.h"
11 #include "build/rust/std/alias.h"
12 #include "build/rust/std/immediate_crash.h"
13
14 #if BUILDFLAG(IS_ANDROID)
15 #include <malloc.h>
16 #endif
17
18 // When linking a final binary, rustc has to pick between either:
19 // * The default Rust allocator
20 // * Any #[global_allocator] defined in *any rlib in its dependency tree*
21 // (https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/global-allocators.html)
22 //
23 // In this latter case, this fact will be recorded in some of the metadata
24 // within the .rlib file. (An .rlib file is just a .a file, but does have
25 // additional metadata for use by rustc. This is, as far as I know, the only
26 // such metadata we would ideally care about.)
27 //
28 // In all the linked rlibs,
29 // * If 0 crates define a #[global_allocator], rustc uses its default allocator
30 // * If 1 crate defines a #[global_allocator], rustc uses that
31 // * If >1 crates define a #[global_allocator], rustc bombs out.
32 //
33 // Because rustc does these checks, it doesn't just have the __rust_alloc
34 // symbols defined anywhere (neither in the stdlib nor in any of these
35 // crates which have a #[global_allocator] defined.)
36 //
37 // Instead:
38 // Rust's final linking stage invokes dynamic LLVM codegen to create symbols
39 // for the basic heap allocation operations. It literally creates a
40 // __rust_alloc symbol at link time. Unless any crate has specified a
41 // #[global_allocator], it simply calls from __rust_alloc into
42 // __rdl_alloc, which is the default Rust allocator. The same applies to a
43 // few other symbols.
44 //
45 // We're not (always) using rustc for final linking. For cases where we're not
46 // Rustc as the final linker, we'll define those symbols here instead.
47 //
48 // The Rust stdlib on Windows uses GetProcessHeap() which will bypass
49 // PartitionAlloc, so we do not forward these functions back to the stdlib.
50 // Instead, we pass them to PartitionAlloc, while replicating functionality from
51 // the unix stdlib to allow them to provide their increased functionality on top
52 // of the system functions.
53 //
54 // In future, we may build a crate with a #[global_allocator] and
55 // redirect these symbols back to Rust in order to use to that crate instead.
56 //
57 // Instead of going through system functions like malloc() we may want to call
58 // into PA directly if we wished for Rust allocations to be in a different
59 // partition, or similar, in the future.
60 //
61 // They're weak symbols, because this file will sometimes end up in targets
62 // which are linked by rustc, and thus we would otherwise get duplicate
63 // definitions. The following definitions will therefore only end up being
64 // used in targets which are linked by our C++ toolchain.
65
66 extern "C" {
67
68 #ifdef COMPONENT_BUILD
69 #if BUILDFLAG(IS_WIN)
70 #define REMAP_ALLOC_ATTRIBUTES __declspec(dllexport) __attribute__((weak))
71 #else
72 #define REMAP_ALLOC_ATTRIBUTES \
73 __attribute__((visibility("default"))) __attribute__((weak))
74 #endif
75 #else
76 #define REMAP_ALLOC_ATTRIBUTES __attribute__((weak))
77 #endif // COMPONENT_BUILD
78
79 // This must exist as the stdlib depends on it to prove that we know the
80 // alloc shims below are unstable. In the future we may be required to replace
81 // them with a #[global_allocator] crate (see file comment above for more).
82 //
83 // Marked as weak as when Rust drives linking it includes this symbol itself,
84 // and we don't want a collision due to C++ being in the same link target, where
85 // C++ causes us to explicitly link in the stdlib and this symbol here.
86 [[maybe_unused]] __attribute__((
87 weak)) unsigned char __rust_no_alloc_shim_is_unstable;
88
__rust_alloc(size_t size,size_t align)89 REMAP_ALLOC_ATTRIBUTES void* __rust_alloc(size_t size, size_t align) {
90 // This mirrors kMaxSupportedAlignment from
91 // base/allocator/partition_allocator/src/partition_alloc/partition_alloc_constants.h.
92 // ParitionAlloc will crash if given an alignment larger than this.
93 constexpr size_t max_align = (1 << 21) / 2;
94 if (align > max_align) {
95 return nullptr;
96 }
97
98 if (align <= alignof(std::max_align_t)) {
99 return malloc(size);
100 } else {
101 // Note: PartitionAlloc by default will route aligned allocations back to
102 // malloc() (the fast path) if they are for a small enough alignment. So we
103 // just unconditionally use aligned allocation functions here.
104 // https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:base/allocator/partition_allocator/src/partition_alloc/shim/allocator_shim_default_dispatch_to_partition_alloc.cc;l=219-226;drc=31d99ff4aa0cc0b75063325ff243e911516a5a6a
105
106 #if defined(COMPILER_MSVC)
107 return _aligned_malloc(size, align);
108 #elif BUILDFLAG(IS_ANDROID)
109 // Android has no posix_memalign() exposed:
110 // https://source.chromium.org/chromium/chromium/src/+/main:base/memory/aligned_memory.cc;l=24-30;drc=e4622aaeccea84652488d1822c28c78b7115684f
111 return memalign(align, size);
112 #else
113 // The `align` from Rust is always a power of 2:
114 // https://doc.rust-lang.org/std/alloc/struct.Layout.html#method.from_size_align.
115 //
116 // We get here only if align > alignof(max_align_t), which guarantees that
117 // the alignment is both a power of 2 and even, which is required by
118 // posix_memalign().
119 //
120 // The PartitionAlloc impl requires that the alignment is at least the same
121 // as pointer-alignment. std::max_align_t is at least pointer-aligned as
122 // well, so we satisfy that.
123 void* p;
124 auto ret = posix_memalign(&p, align, size);
125 return ret == 0 ? p : nullptr;
126 #endif
127 }
128 }
129
__rust_dealloc(void * p,size_t size,size_t align)130 REMAP_ALLOC_ATTRIBUTES void __rust_dealloc(void* p, size_t size, size_t align) {
131 #if defined(COMPILER_MSVC)
132 if (align <= alignof(std::max_align_t)) {
133 free(p);
134 } else {
135 _aligned_free(p);
136 }
137 #else
138 free(p);
139 #endif
140 }
141
__rust_realloc(void * p,size_t old_size,size_t align,size_t new_size)142 REMAP_ALLOC_ATTRIBUTES void* __rust_realloc(void* p,
143 size_t old_size,
144 size_t align,
145 size_t new_size) {
146 if (align <= alignof(std::max_align_t)) {
147 return realloc(p, new_size);
148 } else {
149 void* out = __rust_alloc(align, new_size);
150 memcpy(out, p, std::min(old_size, new_size));
151 return out;
152 }
153 }
154
__rust_alloc_zeroed(size_t size,size_t align)155 REMAP_ALLOC_ATTRIBUTES void* __rust_alloc_zeroed(size_t size, size_t align) {
156 if (align <= alignof(std::max_align_t)) {
157 return calloc(size, 1);
158 } else {
159 void* p = __rust_alloc(size, align);
160 memset(p, 0, size);
161 return p;
162 }
163 }
164
__rust_alloc_error_handler(size_t size,size_t align)165 REMAP_ALLOC_ATTRIBUTES void __rust_alloc_error_handler(size_t size,
166 size_t align) {
167 NO_CODE_FOLDING();
168 IMMEDIATE_CRASH();
169 }
170
171 REMAP_ALLOC_ATTRIBUTES extern const unsigned char
172 __rust_alloc_error_handler_should_panic = 0;
173
174 } // extern "C"
175