Flutter Engine
The Flutter Engine
memory_copy_test.cc
Go to the documentation of this file.
1// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
5#include <vector>
6
12#include "vm/object.h"
13#include "vm/unit_test.h"
14
15namespace dart {
16
17#ifdef DART_TARGET_OS_WINDOWS
18const char* pointer_prefix = "0x";
19#else
20const char* pointer_prefix = "";
21#endif
22
23static constexpr intptr_t kMemoryTestLength = 1024;
24static constexpr uint8_t kUnInitialized = 0xFE;
25
26static classid_t TypedDataCidForElementSize(intptr_t elem_size) {
27 switch (elem_size) {
28 case 1:
29 return kTypedDataUint8ArrayCid;
30 case 2:
31 return kTypedDataUint16ArrayCid;
32 case 4:
33 return kTypedDataUint32ArrayCid;
34 case 8:
35 return kTypedDataUint64ArrayCid;
36 case 16:
37 return kTypedDataInt32x4ArrayCid;
38 default:
39 break;
40 }
42}
43
44static inline intptr_t ExpectedValue(intptr_t i) {
45 return 1 + i % 100;
46}
47
48static void InitializeMemory(uint8_t* input, uint8_t* output) {
49 const bool use_same_buffer = input == output;
50 for (intptr_t i = 0; i < kMemoryTestLength; i++) {
51 input[i] = ExpectedValue(i); // Initialized.
52 if (!use_same_buffer) {
53 output[i] = kUnInitialized; // Empty.
54 }
55 }
56}
57
58static bool CheckMemory(Expect expect,
59 const uint8_t* input,
60 const uint8_t* output,
61 intptr_t dest_start,
62 intptr_t src_start,
63 intptr_t length,
64 intptr_t elem_size) {
66 expect.LessThan<intptr_t>(0, elem_size);
67 if (!Utils::IsPowerOfTwo(elem_size)) {
68 expect.Fail("Expected %" Pd " to be a power of two", elem_size);
69 }
70 expect.LessEqual<intptr_t>(0, length);
71 expect.LessEqual<intptr_t>(0, dest_start);
72 expect.LessEqual<intptr_t>(dest_start + length,
73 kMemoryTestLength / elem_size);
74 expect.LessEqual<intptr_t>(0, src_start);
75 expect.LessEqual<intptr_t>(src_start + length, kMemoryTestLength / elem_size);
76 const bool use_same_buffer = input == output;
77 const intptr_t dest_start_in_bytes = dest_start * elem_size;
78 const intptr_t dest_end_in_bytes = dest_start_in_bytes + length * elem_size;
79 const intptr_t index_diff = dest_start_in_bytes - src_start * elem_size;
80 for (intptr_t i = 0; i < kMemoryTestLength; i++) {
81 if (!use_same_buffer) {
82 const intptr_t expected = ExpectedValue(i);
83 const intptr_t got = input[i];
84 if (expected != got) {
85 expect.Fail("Unexpected change to input buffer at index %" Pd
86 ", expected %" Pd ", got %" Pd "",
87 i, expected, got);
88 }
89 }
90 const intptr_t unchanged =
91 use_same_buffer ? ExpectedValue(i) : kUnInitialized;
92 const intptr_t got = output[i];
93 if (dest_start_in_bytes <= i && i < dest_end_in_bytes) {
94 // Copied.
95 const intptr_t expected = ExpectedValue(i - index_diff);
96 if (expected != got) {
97 if (got == unchanged) {
98 expect.Fail("No change to output buffer at index %" Pd
99 ", expected %" Pd ", got %" Pd "",
100 i, expected, got);
101 } else {
102 expect.Fail("Incorrect change to output buffer at index %" Pd
103 ", expected %" Pd ", got %" Pd "",
104 i, expected, got);
105 }
106 }
107 } else {
108 // Untouched.
109 if (got != unchanged) {
110 expect.Fail("Unexpected change to input buffer at index %" Pd
111 ", expected %" Pd ", got %" Pd "",
112 i, unchanged, got);
113 }
114 }
115 }
116 return expect.failed();
117}
118
119#define CHECK_DEFAULT_MEMORY(in, out) \
120 do { \
121 if (CheckMemory(dart::Expect(__FILE__, __LINE__), in, out, 0, 0, 0, 1)) { \
122 return; \
123 } \
124 } while (false)
125#define CHECK_MEMORY(in, out, start, skip, len, size) \
126 do { \
127 if (CheckMemory(dart::Expect(__FILE__, __LINE__), in, out, start, skip, \
128 len, size)) { \
129 return; \
130 } \
131 } while (false)
132
133static void RunMemoryCopyInstrTest(intptr_t src_start,
134 intptr_t dest_start,
135 intptr_t length,
136 intptr_t elem_size,
137 bool unboxed_inputs,
138 bool use_same_buffer) {
139 OS::Print("==================================================\n");
140 OS::Print("RunMemoryCopyInstrTest src_start %" Pd " dest_start %" Pd
141 " length "
142 "%" Pd "%s elem_size %" Pd "\n",
143 src_start, dest_start, length, unboxed_inputs ? " (unboxed)" : "",
144 elem_size);
145 OS::Print("==================================================\n");
147
148 uint8_t* ptr = reinterpret_cast<uint8_t*>(malloc(kMemoryTestLength));
149 uint8_t* ptr2 = use_same_buffer
150 ? ptr
151 : reinterpret_cast<uint8_t*>(malloc(kMemoryTestLength));
152 InitializeMemory(ptr, ptr2);
153
154 OS::Print("&ptr %p &ptr2 %p\n", ptr, ptr2);
155
156 // clang-format off
157 CStringUniquePtr kScript(OS::SCreate(nullptr, R"(
158 import 'dart:ffi';
159
160 void copyConst() {
161 final pointer = Pointer<Uint8>.fromAddress(%s%p);
162 final pointer2 = Pointer<Uint8>.fromAddress(%s%p);
163 noop();
164 }
165
166 void callNonConstCopy() {
167 final pointer = Pointer<Uint8>.fromAddress(%s%p);
168 final pointer2 = Pointer<Uint8>.fromAddress(%s%p);
169 final src_start = %)" Pd R"(;
170 final dest_start = %)" Pd R"(;
171 final length = %)" Pd R"(;
172 copyNonConst(
173 pointer, pointer2, src_start, dest_start, length);
174 }
175
176 void noop() {}
177
178 void copyNonConst(Pointer<Uint8> ptr1,
179 Pointer<Uint8> ptr2,
180 int src_start,
181 int dest_start,
182 int length) {}
183 )", pointer_prefix, ptr, pointer_prefix, ptr2,
184 pointer_prefix, ptr, pointer_prefix, ptr2,
185 src_start, dest_start, length));
186 // clang-format on
187
188 const auto& root_library = Library::Handle(LoadTestScript(kScript.get()));
189
190 // Test the MemoryCopy instruction when the inputs are constants.
191 {
192 Invoke(root_library, "copyConst");
193 // Running this should be a no-op on the memory.
194 CHECK_DEFAULT_MEMORY(ptr, ptr2);
195
196 const auto& const_copy =
197 Function::Handle(GetFunction(root_library, "copyConst"));
198
199 TestPipeline pipeline(const_copy, CompilerPass::kJIT);
200 FlowGraph* flow_graph = pipeline.RunPasses({
201 CompilerPass::kComputeSSA,
202 });
203
204 StaticCallInstr* pointer = nullptr;
205 StaticCallInstr* pointer2 = nullptr;
206 StaticCallInstr* another_function_call = nullptr;
207 {
208 ILMatcher cursor(flow_graph, flow_graph->graph_entry()->normal_entry());
209
210 EXPECT(cursor.TryMatch({
211 kMoveGlob,
212 kMatchAndMoveRecordCoverage,
213 {kMatchAndMoveStaticCall, &pointer},
214 kMatchAndMoveRecordCoverage,
215 {kMatchAndMoveStaticCall, &pointer2},
216 kMatchAndMoveRecordCoverage,
217 {kMatchAndMoveStaticCall, &another_function_call},
218 }));
219 }
220
221 Zone* const zone = Thread::Current()->zone();
222 auto const rep = unboxed_inputs ? kUnboxedIntPtr : kTagged;
223
224 auto* const src_start_constant_instr = flow_graph->GetConstant(
225 Integer::ZoneHandle(zone, Integer::New(src_start, Heap::kOld)), rep);
226
227 auto* const dest_start_constant_instr = flow_graph->GetConstant(
228 Integer::ZoneHandle(zone, Integer::New(dest_start, Heap::kOld)), rep);
229
230 auto* const length_constant_instr = flow_graph->GetConstant(
232
233 auto* const memory_copy_instr = new (zone) MemoryCopyInstr(
234 new (zone) Value(pointer), /*src_cid=*/cid, new (zone) Value(pointer2),
235 /*dest_cid=*/cid, new (zone) Value(src_start_constant_instr),
236 new (zone) Value(dest_start_constant_instr),
237 new (zone) Value(length_constant_instr), unboxed_inputs,
238 /*can_overlap=*/use_same_buffer);
239 flow_graph->InsertBefore(another_function_call, memory_copy_instr, nullptr,
241
242 another_function_call->RemoveFromGraph();
243
244 {
245 // Check we constructed the right graph.
246 ILMatcher cursor(flow_graph, flow_graph->graph_entry()->normal_entry());
247 EXPECT(cursor.TryMatch({
248 kMoveGlob,
249 kMatchAndMoveRecordCoverage,
250 kMatchAndMoveStaticCall,
251 kMatchAndMoveRecordCoverage,
252 kMatchAndMoveStaticCall,
253 kMatchAndMoveRecordCoverage,
254 kMatchAndMoveMemoryCopy,
255 }));
256 }
257
258 {
259#if !defined(PRODUCT) && !defined(USING_THREAD_SANITIZER)
260 SetFlagScope<bool> sfs(&FLAG_disassemble_optimized, true);
261#endif
262
263 pipeline.RunForcedOptimizedAfterSSAPasses();
264 pipeline.CompileGraphAndAttachFunction();
265 }
266
267 {
268 // Check that the memory copy has constant inputs after optimization.
269 ILMatcher cursor(flow_graph, flow_graph->graph_entry()->normal_entry());
270 MemoryCopyInstr* memory_copy;
271 EXPECT(cursor.TryMatch({
272 kMoveGlob,
273 {kMatchAndMoveMemoryCopy, &memory_copy},
274 }));
275 EXPECT_EQ(kUntagged, memory_copy->src()->definition()->representation());
276 EXPECT_EQ(kUntagged, memory_copy->dest()->definition()->representation());
277 EXPECT(memory_copy->src_start()->BindsToConstant());
278 EXPECT(memory_copy->dest_start()->BindsToConstant());
279 EXPECT(memory_copy->length()->BindsToConstant());
280 }
281
282 // Run the mem copy.
283 Invoke(root_library, "copyConst");
284 }
285
286 CHECK_MEMORY(ptr, ptr2, dest_start, src_start, length, elem_size);
287 // Reinitialize the memory for the non-constant MemoryCopy version.
288 InitializeMemory(ptr, ptr2);
289
290 // Test the MemoryCopy instruction when the inputs are not constants.
291 {
292 Invoke(root_library, "callNonConstCopy");
293 // Running this should be a no-op on the memory.
294 CHECK_DEFAULT_MEMORY(ptr, ptr2);
295
296 const auto& copy_non_const =
297 Function::Handle(GetFunction(root_library, "copyNonConst"));
298
299 TestPipeline pipeline(copy_non_const, CompilerPass::kJIT);
300 FlowGraph* flow_graph = pipeline.RunPasses({
301 CompilerPass::kComputeSSA,
302 });
303
304 auto* const entry_instr = flow_graph->graph_entry()->normal_entry();
305 auto* const initial_defs = entry_instr->initial_definitions();
306 EXPECT(initial_defs != nullptr);
307 EXPECT_EQ(5, initial_defs->length());
308
309 auto* const param_ptr = initial_defs->At(0)->AsParameter();
310 EXPECT(param_ptr != nullptr);
311 auto* const param_ptr2 = initial_defs->At(1)->AsParameter();
312 EXPECT(param_ptr2 != nullptr);
313 auto* const param_src_start = initial_defs->At(2)->AsParameter();
314 EXPECT(param_src_start != nullptr);
315 auto* const param_dest_start = initial_defs->At(3)->AsParameter();
316 EXPECT(param_dest_start != nullptr);
317 auto* const param_length = initial_defs->At(4)->AsParameter();
318 EXPECT(param_length != nullptr);
319
320 DartReturnInstr* return_instr;
321 {
322 ILMatcher cursor(flow_graph, entry_instr);
323
324 EXPECT(cursor.TryMatch({
325 kMoveGlob,
326 {kMatchDartReturn, &return_instr},
327 }));
328 }
329
330 Zone* const zone = Thread::Current()->zone();
331
332 Definition* src_start_def = param_src_start;
333 Definition* dest_start_def = param_dest_start;
334 Definition* length_def = param_length;
335 if (unboxed_inputs) {
336 // Manually add the unbox instruction ourselves instead of leaving it
337 // up to the SelectDefinitions pass.
338 length_def =
339 UnboxInstr::Create(kUnboxedWord, new (zone) Value(param_length),
340 DeoptId::kNone, Instruction::kNotSpeculative);
341 flow_graph->InsertBefore(return_instr, length_def, nullptr,
342 FlowGraph::kValue);
343 dest_start_def =
344 UnboxInstr::Create(kUnboxedWord, new (zone) Value(param_dest_start),
345 DeoptId::kNone, Instruction::kNotSpeculative);
346 flow_graph->InsertBefore(length_def, dest_start_def, nullptr,
347 FlowGraph::kValue);
348 src_start_def =
349 UnboxInstr::Create(kUnboxedWord, new (zone) Value(param_src_start),
350 DeoptId::kNone, Instruction::kNotSpeculative);
351 flow_graph->InsertBefore(dest_start_def, src_start_def, nullptr,
352 FlowGraph::kValue);
353 }
354
355 auto* const memory_copy_instr = new (zone) MemoryCopyInstr(
356 new (zone) Value(param_ptr), /*src_cid=*/cid,
357 new (zone) Value(param_ptr2), /*dest_cid=*/cid,
358 new (zone) Value(src_start_def), new (zone) Value(dest_start_def),
359 new (zone) Value(length_def),
360
361 unboxed_inputs, /*can_overlap=*/use_same_buffer);
362 flow_graph->InsertBefore(return_instr, memory_copy_instr, nullptr,
363 FlowGraph::kEffect);
364
365 {
366 // Check we constructed the right graph.
367 ILMatcher cursor(flow_graph, flow_graph->graph_entry()->normal_entry());
368 if (unboxed_inputs) {
369 EXPECT(cursor.TryMatch({
370 kMoveGlob,
371 kMatchAndMoveUnbox,
372 kMatchAndMoveUnbox,
373 kMatchAndMoveUnbox,
374 kMatchAndMoveMemoryCopy,
375 kMatchDartReturn,
376 }));
377 } else {
378 EXPECT(cursor.TryMatch({
379 kMoveGlob,
380 kMatchAndMoveMemoryCopy,
381 kMatchDartReturn,
382 }));
383 }
384 }
385
386 {
387#if !defined(PRODUCT) && !defined(USING_THREAD_SANITIZER)
388 SetFlagScope<bool> sfs(&FLAG_disassemble_optimized, true);
389#endif
390
391 pipeline.RunForcedOptimizedAfterSSAPasses();
392 pipeline.CompileGraphAndAttachFunction();
393 }
394
395 {
396 // Check that the memory copy has non-constant inputs after optimization.
397 ILMatcher cursor(flow_graph, flow_graph->graph_entry()->normal_entry());
398 MemoryCopyInstr* memory_copy;
399 EXPECT(cursor.TryMatch({
400 kMoveGlob,
401 {kMatchAndMoveMemoryCopy, &memory_copy},
402 }));
403 EXPECT_EQ(kUntagged, memory_copy->src()->definition()->representation());
404 EXPECT_EQ(kUntagged, memory_copy->dest()->definition()->representation());
405 EXPECT(!memory_copy->src_start()->BindsToConstant());
406 EXPECT(!memory_copy->dest_start()->BindsToConstant());
407 EXPECT(!memory_copy->length()->BindsToConstant());
408 }
409
410 // Run the mem copy.
411 Invoke(root_library, "callNonConstCopy");
412 }
413
414 CHECK_MEMORY(ptr, ptr2, dest_start, src_start, length, elem_size);
415 free(ptr);
416 if (!use_same_buffer) {
417 free(ptr2);
418 }
419}
420
421#define MEMORY_COPY_TEST_BOXED(src_start, dest_start, length, elem_size) \
422 ISOLATE_UNIT_TEST_CASE( \
423 IRTest_MemoryCopy_##src_start##_##dest_start##_##length##_##elem_size) { \
424 RunMemoryCopyInstrTest(src_start, dest_start, length, elem_size, false, \
425 false); \
426 }
427
428#define MEMORY_COPY_TEST_UNBOXED(src_start, dest_start, length, el_si) \
429 ISOLATE_UNIT_TEST_CASE( \
430 IRTest_MemoryCopy_##src_start##_##dest_start##_##length##_##el_si##_u) { \
431 RunMemoryCopyInstrTest(src_start, dest_start, length, el_si, true, false); \
432 }
433
434#define MEMORY_MOVE_TEST_BOXED(src_start, dest_start, length, elem_size) \
435 ISOLATE_UNIT_TEST_CASE( \
436 IRTest_MemoryMove_##src_start##_##dest_start##_##length##_##elem_size) { \
437 RunMemoryCopyInstrTest(src_start, dest_start, length, elem_size, false, \
438 true); \
439 }
440
441#define MEMORY_MOVE_TEST_UNBOXED(src_start, dest_start, length, el_si) \
442 ISOLATE_UNIT_TEST_CASE( \
443 IRTest_MemoryMove_##src_start##_##dest_start##_##length##_##el_si##_u) { \
444 RunMemoryCopyInstrTest(src_start, dest_start, length, el_si, true, true); \
445 }
446
447#define MEMORY_COPY_TEST(src_start, dest_start, length, elem_size) \
448 MEMORY_COPY_TEST_BOXED(src_start, dest_start, length, elem_size) \
449 MEMORY_COPY_TEST_UNBOXED(src_start, dest_start, length, elem_size)
450
451#define MEMORY_MOVE_TEST(src_start, dest_start, length, elem_size) \
452 MEMORY_MOVE_TEST_BOXED(src_start, dest_start, length, elem_size) \
453 MEMORY_MOVE_TEST_UNBOXED(src_start, dest_start, length, elem_size)
454
455#define MEMORY_TEST(src_start, dest_start, length, elem_size) \
456 MEMORY_MOVE_TEST(src_start, dest_start, length, elem_size) \
457 MEMORY_COPY_TEST(src_start, dest_start, length, elem_size)
458
459// No offset, varying length.
460MEMORY_TEST(0, 0, 1, 1)
461MEMORY_TEST(0, 0, 2, 1)
462MEMORY_TEST(0, 0, 3, 1)
463MEMORY_TEST(0, 0, 4, 1)
464MEMORY_TEST(0, 0, 5, 1)
465MEMORY_TEST(0, 0, 6, 1)
466MEMORY_TEST(0, 0, 7, 1)
467MEMORY_TEST(0, 0, 8, 1)
468MEMORY_TEST(0, 0, 16, 1)
469
470// Offsets.
471MEMORY_TEST(2, 2, 1, 1)
472MEMORY_TEST(2, 17, 3, 1)
473MEMORY_TEST(20, 5, 17, 1)
474
475// Other element sizes.
476MEMORY_TEST(0, 0, 1, 2)
477MEMORY_TEST(0, 0, 1, 4)
478MEMORY_TEST(0, 0, 1, 8)
479MEMORY_TEST(0, 0, 2, 2)
480MEMORY_TEST(0, 0, 2, 4)
481MEMORY_TEST(0, 0, 2, 8)
482MEMORY_TEST(0, 0, 4, 2)
483MEMORY_TEST(0, 0, 4, 4)
484MEMORY_TEST(0, 0, 4, 8)
485MEMORY_TEST(0, 0, 8, 2)
486MEMORY_TEST(0, 0, 8, 4)
487MEMORY_TEST(0, 0, 8, 8)
488MEMORY_TEST(0, 0, 2, 16)
489MEMORY_TEST(0, 0, 4, 16)
490MEMORY_TEST(0, 0, 8, 16)
491
492// Other element sizes with offsets.
493MEMORY_TEST(1, 1, 2, 2)
494MEMORY_TEST(0, 1, 4, 2)
495MEMORY_TEST(1, 2, 3, 2)
496MEMORY_TEST(2, 1, 3, 2)
497MEMORY_TEST(123, 2, 4, 4)
498MEMORY_TEST(2, 123, 4, 4)
499MEMORY_TEST(24, 23, 8, 4)
500MEMORY_TEST(23, 24, 8, 4)
501MEMORY_TEST(5, 72, 1, 8)
502MEMORY_TEST(12, 13, 3, 8)
503MEMORY_TEST(15, 12, 8, 8)
504
505MEMORY_TEST(13, 14, 15, 16)
506MEMORY_TEST(14, 13, 15, 16)
507
508// Size promotions with offsets.
509MEMORY_TEST(2, 2, 8, 1) // promoted to 2.
510MEMORY_TEST(4, 4, 8, 1) // promoted to 4.
511MEMORY_TEST(8, 8, 8, 1) // promoted to 8.
512MEMORY_TEST(16, 16, 16, 1) // promoted to 16 on ARM64.
513
514} // namespace dart
static sk_sp< Effect > Create()
Definition: RefCntTest.cpp:117
#define EXPECT(type, expectedAlignment, expectedSize)
void Fail(const char *format,...) const PRINTF_ATTRIBUTE(2
void static bool failed()
Definition: assert.h:94
GraphEntryInstr * graph_entry() const
Definition: flow_graph.h:268
FunctionEntryInstr * normal_entry() const
Definition: il.h:2001
@ kOld
Definition: heap.h:39
bool TryMatch(std::initializer_list< MatchCode > match_codes, MatchOpCode insert_before=kInvalidMatchOpCode)
void InsertBefore(Instruction *next)
Definition: il.h:1280
static IntegerPtr New(const String &str, Heap::Space space=Heap::kNew)
Definition: object.cc:22984
static void Print(const char *format,...) PRINTF_ATTRIBUTE(1
static char * SCreate(Zone *zone, const char *format,...) PRINTF_ATTRIBUTE(2
static Object & Handle()
Definition: object.h:407
static Object & ZoneHandle()
Definition: object.h:419
FlowGraph * RunPasses(std::initializer_list< CompilerPass::Id > passes)
Zone * zone() const
Definition: thread_state.h:37
static Thread * Current()
Definition: thread.h:362
static constexpr bool IsPowerOfTwo(T x)
Definition: utils.h:76
Definition: il.h:75
#define UNIMPLEMENTED
#define ASSERT(E)
size_t length
#define CHECK_DEFAULT_MEMORY(in, out)
#define MEMORY_TEST(src_start, dest_start, length, elem_size)
#define CHECK_MEMORY(in, out, start, skip, len, size)
Definition: dart_vm.cc:33
LibraryPtr LoadTestScript(const char *script, Dart_NativeEntryResolver resolver, const char *lib_uri)
static void InitializeMemory(uint8_t *input, uint8_t *output)
void * malloc(size_t size)
Definition: allocation.cc:19
int32_t classid_t
Definition: globals.h:524
static classid_t TypedDataCidForElementSize(intptr_t elem_size)
static constexpr uint8_t kUnInitialized
ObjectPtr Invoke(const Library &lib, const char *name)
static constexpr intptr_t kMemoryTestLength
FunctionPtr GetFunction(const Library &lib, const char *name)
static bool CheckMemory(Expect expect, const uint8_t *input, const uint8_t *output, intptr_t dest_start, intptr_t src_start, intptr_t length, intptr_t elem_size)
const char * pointer_prefix
const intptr_t cid
static intptr_t ExpectedValue(intptr_t i)
static constexpr Representation kUnboxedIntPtr
Definition: locations.h:176
static constexpr Representation kUnboxedWord
Definition: locations.h:164
static void RunMemoryCopyInstrTest(intptr_t src_start, intptr_t dest_start, intptr_t length, intptr_t elem_size, bool unboxed_inputs, bool use_same_buffer)
@ kNone
Definition: layer.h:53
#define Pd
Definition: globals.h:408