Skip to content

Commit f5716f6

Browse files
committed
Add preserve_most to most fastMalloc APIs on ARM64
https://bugs.webkit.org/show_bug.cgi?id=320661 rdar://183640618 Reviewed by Mark Lam, Yusuke Suzuki, and Marcus Plutowski. This patch adds Clang's preserve_most attribute to most fastMalloc APIs. The preserve_most attribute on ARM64 turns `x9-x15` into callee saves. See: https://clang.llvm.org/docs/AttributeReference.html#preserve-most For functions that are leaf functions this can be beneficial as the caller has more scratch registers to work with that they don't have to save. AI helped me gather the following analysis of a production build. Counting every stp/str that writes a callee-saved register to the stack. ``` ┌───────────┬─────────┬──────────┬─────────┬─────────┬────────┐ │ framework │ bucket │ baseline │ patched │ delta │ % │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ JSC │ x19-x28 │ 163,270 │ 153,920 │ -9,350 │ -5.73% │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ x9-x15 │ 23,459 │ 24,647 │ +1,188 │ +5.06% │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ d8-d15 │ 3,290 │ 3,290 │ 0 │ — │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ fp/lr │ 49,488 │ 49,507 │ +19 │ +0.04% │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ total │ 239,507 │ 231,364 │ -8,143 │ -3.40% │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ WebCore │ x19-x28 │ 412,378 │ 395,350 │ -17,028 │ -4.13% │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ x9-x15 │ 28,562 │ 30,434 │ +1,872 │ +6.55% │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ d8-d15 │ 16,426 │ 16,427 │ +1 │ — │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ fp/lr │ 178,373 │ 178,362 │ -11 │ -0.01% │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ total │ 635,739 │ 620,573 │ -15,166 │ -2.39% │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ WebKit │ x19-x28 │ 209,816 │ 203,697 │ -6,119 │ -2.92% │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ x9-x15 │ 7,672 │ 8,360 │ +688 │ +8.97% │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ d8-d15 │ 5,472 │ 5,472 │ 0 │ — │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ fp/lr │ 117,244 │ 117,242 │ -2 │ — │ ├───────────┼─────────┼──────────┼─────────┼─────────┼────────┤ │ │ total │ 340,204 │ 334,771 │ -5,433 │ -1.60% │ └───────────┴─────────┴──────────┴─────────┴─────────┴────────┘ ``` Combined: 1,215,450 → 1,186,708, a net -28,742 (-2.36%). Text size shrinks in step: -15,142 / -17,215 / -6,438 instructions. I only applied preserve_most to ARM64 since on X86 it leaves only one scratch free, which doesn't seem worth it. Additionally, there seems to be a "bug" in LLVM's hot-cold outlining where the cold function doesn't inherit the hot function's calling convention. I filed rdar://183555125 to track this. That said, LTO seems to undo this problem (even without PGO data) so it's still worth enabling when LTO is on. No new tests, no observable behavior change. Canonical link: https://commits.webkit.org/318270@main
1 parent 88d15b8 commit f5716f6

21 files changed

Lines changed: 108 additions & 45 deletions

Configurations/CommonBase.xcconfig

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,17 @@ WK_ENGINEERING_BUILD_Debug = 1;
5555
WK_ENGINEERING_BUILD_Release = 1;
5656
WK_ENGINEERING_BUILD_Production = 0;
5757

58-
WK_GCC_PREPROCESSOR_DEFINITIONS = $(WK_GCC_PREPROCESSOR_DEFINITIONS_$(USE_INTERNAL_SDK)) ENGINEERING_BUILD=$(WK_ENGINEERING_BUILD);
58+
WK_GCC_PREPROCESSOR_DEFINITIONS = $(WK_GCC_PREPROCESSOR_DEFINITIONS_$(USE_INTERNAL_SDK)) ENGINEERING_BUILD=$(WK_ENGINEERING_BUILD) $(WK_PRESERVE_MOST_DEFINES);
5959
WK_GCC_PREPROCESSOR_DEFINITIONS_YES = $(WK_OS_UNFAIR_LOCK_INLINE_CFLAGS) __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES=0;
6060
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) $(WK_GCC_PREPROCESSOR_DEFINITIONS);
6161

62+
// PRESERVE_MOST is only profitable under LTO: without it, hot/cold splitting gives the extracted
63+
// cold region the default calling convention, so the preserve_most parent saves X9-X15 on its hot
64+
// path. See rdar://183555125.
65+
WK_PRESERVE_MOST_DEFINES = $(WK_PRESERVE_MOST_DEFINES_$(LLVM_LTO));
66+
WK_PRESERVE_MOST_DEFINES_YES = HAVE_PRESERVE_MOST=1;
67+
WK_PRESERVE_MOST_DEFINES_YES_THIN = HAVE_PRESERVE_MOST=1;
68+
6269
WK_COMMON_OTHER_CFLAGS = $(WK_AVAILABILITY_OVERLAY_FLAGS) $(WK_SANITIZER_OTHER_CFLAGS) $(WK_COMMON_OTHER_CFLAGS_PGO_$(WK_OR_$(WK_ENABLE_PGO_USE)_$(ENABLE_LLVM_PROFILE_GENERATION)));
6370
// Work around rdar://141760829 until the fix is available on all clang-1700
6471
// based toolchains.

Source/JavaScriptCore/API/glib/JSCValue.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ GBytes* jsc_value_to_string_as_bytes(JSCValue* value)
458458
}
459459

460460
// Ignore the null character added by JSStringGetUTF8CString.
461-
return g_bytes_new_with_free_func(string, stringSize - 1, fastFree, string);
461+
return g_bytes_new_with_free_func(string, stringSize - 1, fastFreeCallback, string);
462462
}
463463

464464
/**

Source/WTF/wtf/Compiler.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,23 @@
305305
#define RETURNS_NONNULL __attribute__((returns_nonnull))
306306
#endif
307307

308+
/* PRESERVE_MOST */
309+
310+
// Shrinks the register set a caller must spill around a call.
311+
// See: https://clang.llvm.org/docs/AttributeReference.html#preserve-most
312+
//
313+
// Requires LTO, so the build defines HAVE_PRESERVE_MOST only when LTO is on. Without LTO,
314+
// LLVM's hot/cold splitting extracts cold regions into separate functions that get the default
315+
// calling convention, and the preserve_most parent then has to save X9-X15 for them in its entry
316+
// block (on the hot path), which costs more than the attribute saves. See rdar://183555125.
317+
#if !defined(PRESERVE_MOST)
318+
#if defined(HAVE_PRESERVE_MOST) && HAVE_PRESERVE_MOST && defined(__aarch64__)
319+
#define PRESERVE_MOST __attribute__((preserve_most))
320+
#else
321+
#define PRESERVE_MOST
322+
#endif
323+
#endif
324+
308325
/* OBJC_CLASS */
309326

310327
#if !defined(OBJC_CLASS) && defined(__OBJC__)

Source/WTF/wtf/FastMalloc.cpp

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
#include <bmalloc/bmalloc.h>
3030
#include <string.h>
31+
#include <string_view>
3132
#include <wtf/Atomics.h>
3233
#include <wtf/CheckedArithmetic.h>
3334
#include <wtf/PageBlock.h>
@@ -59,6 +60,11 @@
5960

6061
#endif
6162

63+
#if defined(PAS_PRESERVE_MOST)
64+
static_assert(std::string_view { _STRINGIFY(PRESERVE_MOST) } == std::string_view { _STRINGIFY(PAS_PRESERVE_MOST) },
65+
"PRESERVE_MOST and PAS_PRESERVE_MOST disagree; this is catastrophic for performance");
66+
#endif
67+
6268
namespace WTF {
6369

6470
#if !defined(NDEBUG)
@@ -330,7 +336,7 @@ bool isFastMallocEnabled()
330336
return bmalloc::api::isEnabled();
331337
}
332338

333-
void* fastMalloc(size_t size)
339+
PRESERVE_MOST void* fastMalloc(size_t size)
334340
{
335341
ASSERT_IS_WITHIN_LIMIT(size);
336342
assertMallocRestrictionForCurrentThreadScope();
@@ -390,7 +396,7 @@ void* fastRealloc(void* object, size_t size)
390396
return result;
391397
}
392398

393-
void fastFree(void* object)
399+
PRESERVE_MOST void fastFree(void* object)
394400
{
395401
bmalloc::api::free(object);
396402
#if ENABLE(MALLOC_HEAP_BREAKDOWN) && TRACK_MALLOC_CALLSTACK
@@ -399,6 +405,11 @@ void fastFree(void* object)
399405
#endif
400406
}
401407

408+
void fastFreeCallback(void* object)
409+
{
410+
fastFree(object);
411+
}
412+
402413
size_t fastMallocSize(const void* p)
403414
{
404415
#if BENABLE(MALLOC_SIZE)
@@ -421,7 +432,7 @@ size_t fastMallocGoodSize(size_t size)
421432
#endif
422433
}
423434

424-
void* fastAlignedMalloc(size_t alignment, size_t size)
435+
PRESERVE_MOST void* fastAlignedMalloc(size_t alignment, size_t size)
425436
{
426437
ASSERT_IS_WITHIN_LIMIT(size);
427438
assertMallocRestrictionForCurrentThreadScope();
@@ -434,7 +445,7 @@ void* fastAlignedMalloc(size_t alignment, size_t size)
434445
return result;
435446
}
436447

437-
void* tryFastAlignedMalloc(size_t alignment, size_t size)
448+
PRESERVE_MOST void* tryFastAlignedMalloc(size_t alignment, size_t size)
438449
{
439450
FAIL_IF_EXCEEDS_LIMIT(size);
440451
assertMallocRestrictionForCurrentThreadScope();
@@ -447,7 +458,7 @@ void* tryFastAlignedMalloc(size_t alignment, size_t size)
447458
return result;
448459
}
449460

450-
TryMallocReturnValue tryFastMalloc(size_t size)
461+
PRESERVE_MOST TryMallocReturnValue tryFastMalloc(size_t size)
451462
{
452463
FAIL_IF_EXCEEDS_LIMIT(size);
453464
assertMallocRestrictionForCurrentThreadScope();
@@ -475,7 +486,7 @@ TryMallocReturnValue tryFastRealloc(void* object, size_t newSize)
475486
return result;
476487
}
477488

478-
void* fastCompactMalloc(size_t size)
489+
PRESERVE_MOST void* fastCompactMalloc(size_t size)
479490
{
480491
ASSERT_IS_WITHIN_LIMIT(size);
481492
assertMallocRestrictionForCurrentThreadScope();
@@ -533,7 +544,7 @@ void* fastCompactRealloc(void* object, size_t size)
533544
return result;
534545
}
535546

536-
void* fastCompactAlignedMalloc(size_t alignment, size_t size)
547+
PRESERVE_MOST void* fastCompactAlignedMalloc(size_t alignment, size_t size)
537548
{
538549
ASSERT_IS_WITHIN_LIMIT(size);
539550
assertMallocRestrictionForCurrentThreadScope();
@@ -546,7 +557,7 @@ void* fastCompactAlignedMalloc(size_t alignment, size_t size)
546557
return result;
547558
}
548559

549-
void* tryFastCompactAlignedMalloc(size_t alignment, size_t size)
560+
PRESERVE_MOST void* tryFastCompactAlignedMalloc(size_t alignment, size_t size)
550561
{
551562
FAIL_IF_EXCEEDS_LIMIT(size);
552563
assertMallocRestrictionForCurrentThreadScope();
@@ -559,7 +570,7 @@ void* tryFastCompactAlignedMalloc(size_t alignment, size_t size)
559570
return result;
560571
}
561572

562-
TryMallocReturnValue tryFastCompactMalloc(size_t size)
573+
PRESERVE_MOST TryMallocReturnValue tryFastCompactMalloc(size_t size)
563574
{
564575
FAIL_IF_EXCEEDS_LIMIT(size);
565576
assertMallocRestrictionForCurrentThreadScope();

Source/WTF/wtf/FastMalloc.h

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,38 +132,43 @@ WTF_EXPORT_PRIVATE void fastSetMaxSingleAllocationSize(size_t);
132132
WTF_EXPORT_PRIVATE bool isFastMallocEnabled();
133133

134134
// These functions call CRASH() if an allocation fails.
135-
WTF_EXPORT_PRIVATE void* fastMalloc(size_t) RETURNS_NONNULL;
135+
WTF_EXPORT_PRIVATE PRESERVE_MOST void* fastMalloc(size_t) RETURNS_NONNULL;
136136
WTF_EXPORT_PRIVATE void* fastZeroedMalloc(size_t) RETURNS_NONNULL;
137137
WTF_EXPORT_PRIVATE void* fastCalloc(size_t numElements, size_t elementSize) RETURNS_NONNULL;
138138
WTF_EXPORT_PRIVATE void* fastRealloc(void*, size_t) RETURNS_NONNULL;
139139
WTF_EXPORT_PRIVATE char* fastStrDup(const char*) RETURNS_NONNULL;
140140
WTF_EXPORT_PRIVATE void* fastMemDup(const void*, size_t);
141141

142-
WTF_EXPORT_PRIVATE TryMallocReturnValue tryFastMalloc(size_t);
142+
WTF_EXPORT_PRIVATE PRESERVE_MOST TryMallocReturnValue tryFastMalloc(size_t);
143143
WTF_EXPORT_PRIVATE TryMallocReturnValue tryFastZeroedMalloc(size_t);
144144
WTF_EXPORT_PRIVATE TryMallocReturnValue tryFastCalloc(size_t numElements, size_t elementSize);
145145
WTF_EXPORT_PRIVATE TryMallocReturnValue tryFastRealloc(void*, size_t);
146146

147-
WTF_EXPORT_PRIVATE void fastFree(void*);
147+
WTF_EXPORT_PRIVATE PRESERVE_MOST void fastFree(void*);
148148

149-
WTF_EXPORT_PRIVATE void* fastAlignedMalloc(size_t alignment, size_t) RETURNS_NONNULL;
150-
WTF_EXPORT_PRIVATE void* tryFastAlignedMalloc(size_t alignment, size_t);
149+
// fastFree's calling convention is not the default one, so its address cannot be handed to C APIs
150+
// that take a plain function pointer (GDestroyNotify, sqlite3_mem_methods, and friends). Pass this
151+
// instead at those call sites; taking the address of fastFree there is a compile error.
152+
WTF_EXPORT_PRIVATE void fastFreeCallback(void*);
153+
154+
WTF_EXPORT_PRIVATE PRESERVE_MOST void* fastAlignedMalloc(size_t alignment, size_t) RETURNS_NONNULL;
155+
WTF_EXPORT_PRIVATE PRESERVE_MOST void* tryFastAlignedMalloc(size_t alignment, size_t);
151156

152157
// These functions behave like their non-compact counterparts, but guarantee
153158
// that the pointer returned can be stored as a CompactPtr or PackedPtr.
154159

155-
WTF_EXPORT_PRIVATE void* fastCompactMalloc(size_t) RETURNS_NONNULL;
160+
WTF_EXPORT_PRIVATE PRESERVE_MOST void* fastCompactMalloc(size_t) RETURNS_NONNULL;
156161
WTF_EXPORT_PRIVATE void* fastCompactZeroedMalloc(size_t) RETURNS_NONNULL;
157162
WTF_EXPORT_PRIVATE void* fastCompactCalloc(size_t numElements, size_t elementSize) RETURNS_NONNULL;
158163
WTF_EXPORT_PRIVATE void* fastCompactRealloc(void*, size_t) RETURNS_NONNULL;
159-
WTF_EXPORT_PRIVATE TryMallocReturnValue tryFastCompactMalloc(size_t);
164+
WTF_EXPORT_PRIVATE PRESERVE_MOST TryMallocReturnValue tryFastCompactMalloc(size_t);
160165
WTF_EXPORT_PRIVATE TryMallocReturnValue tryFastCompactZeroedMalloc(size_t);
161166
WTF_EXPORT_PRIVATE TryMallocReturnValue tryFastCompactCalloc(size_t numElements, size_t elementSize);
162167
WTF_EXPORT_PRIVATE TryMallocReturnValue tryFastCompactRealloc(void*, size_t);
163168
WTF_EXPORT_PRIVATE char* fastCompactStrDup(const char*) RETURNS_NONNULL;
164169
WTF_EXPORT_PRIVATE void* fastCompactMemDup(const void*, size_t);
165-
WTF_EXPORT_PRIVATE void* fastCompactAlignedMalloc(size_t alignment, size_t) RETURNS_NONNULL;
166-
WTF_EXPORT_PRIVATE void* tryFastCompactAlignedMalloc(size_t alignment, size_t);
170+
WTF_EXPORT_PRIVATE PRESERVE_MOST void* fastCompactAlignedMalloc(size_t alignment, size_t) RETURNS_NONNULL;
171+
WTF_EXPORT_PRIVATE PRESERVE_MOST void* tryFastCompactAlignedMalloc(size_t alignment, size_t);
167172

168173
WTF_EXPORT_PRIVATE size_t fastMallocSize(const void*);
169174

@@ -433,6 +438,7 @@ using WTF::FastFree;
433438
using WTF::isFastMallocEnabled;
434439
using WTF::fastCalloc;
435440
using WTF::fastFree;
441+
using WTF::fastFreeCallback;
436442
using WTF::fastMalloc;
437443
using WTF::fastMallocGoodSize;
438444
using WTF::fastMallocSize;

Source/WebCore/platform/graphics/gstreamer/GStreamerCommon.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1274,7 +1274,7 @@ bool webkitGstSetElementStateSynchronously(GstElement* pipeline, GstState target
12741274

12751275
GstBuffer* /* (transfer full) */ gstBufferNewWrappedFast(void* data, size_t length)
12761276
{
1277-
return gst_buffer_new_wrapped_full(static_cast<GstMemoryFlags>(0), data, length, 0, length, data, fastFree);
1277+
return gst_buffer_new_wrapped_full(static_cast<GstMemoryFlags>(0), data, length, 0, length, data, fastFreeCallback);
12781278
}
12791279

12801280
GstElement* /* (transfer floating) */ makeGStreamerElement(CStringView factoryName, const String& name)

Source/WebCore/platform/sql/SQLiteDatabase.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ void SQLiteDatabase::useFastMalloc()
104104

105105
static sqlite3_mem_methods fastMallocMethods = {
106106
[](int n) { return fastMalloc(n); },
107-
fastFree,
107+
fastFreeCallback,
108108
[](void *p, int n) { return fastRealloc(p, n); },
109109
[](void *p) { return static_cast<int>(fastMallocSize(p)); },
110110
[](int n) { return static_cast<int>(fastMallocGoodSize(n)); },

Source/WebKit/NetworkProcess/cache/NetworkCacheDataGLib.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Data::Data(std::span<const uint8_t> data)
5050
auto span = MallocSpan<uint8_t>::malloc(data.size());
5151
memcpySpan(span.mutableSpan(), data);
5252
auto* copiedData = span.leakSpan().data();
53-
m_buffer = adoptGRef(g_bytes_new_with_free_func(copiedData, data.size(), fastFree, copiedData));
53+
m_buffer = adoptGRef(g_bytes_new_with_free_func(copiedData, data.size(), fastFreeCallback, copiedData));
5454
}
5555

5656
Data::Data(GRefPtr<GBytes>&& buffer, FileSystem::FileHandle&& fileHandle)
@@ -111,7 +111,7 @@ Data concatenate(const Data& a, const Data& b)
111111
memcpySpan(span.mutableSpan().last(b.size()), b.span());
112112
auto* data = span.leakSpan().data();
113113

114-
return { adoptGRef(g_bytes_new_with_free_func(data, size, fastFree, data)) };
114+
return { adoptGRef(g_bytes_new_with_free_func(data, size, fastFreeCallback, data)) };
115115
}
116116

117117
struct MapWrapper {

Source/WebKit/NetworkProcess/cache/NetworkCacheIOChannelGLib.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ void IOChannel::read(size_t offset, size_t size, Ref<WTF::WorkQueueBase>&& queue
9393

9494
size_t bufferSize = std::min<size_t>(size, fileSize - offset);
9595
uint8_t* bufferData = static_cast<uint8_t*>(fastMalloc(bufferSize));
96-
GRefPtr<GBytes> buffer = adoptGRef(g_bytes_new_with_free_func(bufferData, bufferSize, fastFree, bufferData));
96+
GRefPtr<GBytes> buffer = adoptGRef(g_bytes_new_with_free_func(bufferData, bufferSize, fastFreeCallback, bufferData));
9797
gsize bytesRead;
9898
if (g_input_stream_read_all(m_inputStream.get(), bufferData, bufferSize, &bytesRead, nullptr, nullptr)) {
9999
GRefPtr<GBytes> bytes = bufferSize == bytesRead ? buffer : adoptGRef(g_bytes_new_from_bytes(buffer.get(), 0, bytesRead));

Source/WebKit/UIProcess/API/glib/WebKitWebView.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5005,7 +5005,7 @@ void webkit_web_view_run_javascript_from_gresource(WebKitWebView* webView, const
50055005
}
50065006

50075007
GTask* task = g_task_new(webView, cancellable, callback, userData);
5008-
GRefPtr<GOutputStream> outputStream = adoptGRef(g_memory_output_stream_new(0, 0, fastRealloc, fastFree));
5008+
GRefPtr<GOutputStream> outputStream = adoptGRef(g_memory_output_stream_new(0, 0, fastRealloc, fastFreeCallback));
50095009
g_output_stream_splice_async(outputStream.get(), inputStream.get(),
50105010
static_cast<GOutputStreamSpliceFlags>(G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET),
50115011
G_PRIORITY_DEFAULT, cancellable, resourcesStreamReadCallback, task);
@@ -5195,7 +5195,7 @@ GInputStream* webkit_web_view_save_finish(WebKitWebView* webView, GAsyncResult*
51955195
ViewSaveAsyncData* data = static_cast<ViewSaveAsyncData*>(g_task_get_task_data(task));
51965196
auto bytes = data->webData->span();
51975197
if (!bytes.empty())
5198-
g_memory_input_stream_add_data(G_MEMORY_INPUT_STREAM(dataStream), fastMemDup(bytes.data(), bytes.size()), bytes.size(), fastFree);
5198+
g_memory_input_stream_add_data(G_MEMORY_INPUT_STREAM(dataStream), fastMemDup(bytes.data(), bytes.size()), bytes.size(), fastFreeCallback);
51995199

52005200
return dataStream;
52015201
#else

0 commit comments

Comments
 (0)