f1ba8ceacf
Fix GCC TSAN build. Cleanup sanitizer includes. TEST=ci Change-Id: Ib68bbfa701b4309c03514ed689391051094208e4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452226 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Alexander Aprelev <aam@google.com>
524 lines
19 KiB
C++
524 lines
19 KiB
C++
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include <vector>
|
|
|
|
#include "platform/thread_sanitizer.h"
|
|
#include "vm/compiler/backend/il.h"
|
|
#include "vm/compiler/backend/il_printer.h"
|
|
#include "vm/compiler/backend/il_test_helper.h"
|
|
#include "vm/compiler/call_specializer.h"
|
|
#include "vm/compiler/compiler_pass.h"
|
|
#include "vm/object.h"
|
|
#include "vm/unit_test.h"
|
|
|
|
namespace dart {
|
|
|
|
#ifdef DART_TARGET_OS_WINDOWS
|
|
const char* pointer_prefix = "0x";
|
|
#else
|
|
const char* pointer_prefix = "";
|
|
#endif
|
|
|
|
static constexpr intptr_t kMemoryTestLength = 1024;
|
|
static constexpr uint8_t kUnInitialized = 0xFE;
|
|
|
|
static classid_t TypedDataCidForElementSize(intptr_t elem_size) {
|
|
switch (elem_size) {
|
|
case 1:
|
|
return kTypedDataUint8ArrayCid;
|
|
case 2:
|
|
return kTypedDataUint16ArrayCid;
|
|
case 4:
|
|
return kTypedDataUint32ArrayCid;
|
|
case 8:
|
|
return kTypedDataUint64ArrayCid;
|
|
case 16:
|
|
return kTypedDataInt32x4ArrayCid;
|
|
default:
|
|
break;
|
|
}
|
|
UNIMPLEMENTED();
|
|
}
|
|
|
|
static inline intptr_t ExpectedValue(intptr_t i) {
|
|
return 1 + i % 100;
|
|
}
|
|
|
|
static void InitializeMemory(uint8_t* input, uint8_t* output) {
|
|
const bool use_same_buffer = input == output;
|
|
for (intptr_t i = 0; i < kMemoryTestLength; i++) {
|
|
input[i] = ExpectedValue(i); // Initialized.
|
|
if (!use_same_buffer) {
|
|
output[i] = kUnInitialized; // Empty.
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
ASSERT(Utils::IsPowerOfTwo(kMemoryTestLength));
|
|
expect.LessThan<intptr_t>(0, elem_size);
|
|
if (!Utils::IsPowerOfTwo(elem_size)) {
|
|
expect.Fail("Expected %" Pd " to be a power of two", elem_size);
|
|
}
|
|
expect.LessEqual<intptr_t>(0, length);
|
|
expect.LessEqual<intptr_t>(0, dest_start);
|
|
expect.LessEqual<intptr_t>(dest_start + length,
|
|
kMemoryTestLength / elem_size);
|
|
expect.LessEqual<intptr_t>(0, src_start);
|
|
expect.LessEqual<intptr_t>(src_start + length, kMemoryTestLength / elem_size);
|
|
const bool use_same_buffer = input == output;
|
|
const intptr_t dest_start_in_bytes = dest_start * elem_size;
|
|
const intptr_t dest_end_in_bytes = dest_start_in_bytes + length * elem_size;
|
|
const intptr_t index_diff = dest_start_in_bytes - src_start * elem_size;
|
|
for (intptr_t i = 0; i < kMemoryTestLength; i++) {
|
|
if (!use_same_buffer) {
|
|
const intptr_t expected = ExpectedValue(i);
|
|
const intptr_t got = input[i];
|
|
if (expected != got) {
|
|
expect.Fail("Unexpected change to input buffer at index %" Pd
|
|
", expected %" Pd ", got %" Pd "",
|
|
i, expected, got);
|
|
}
|
|
}
|
|
const intptr_t unchanged =
|
|
use_same_buffer ? ExpectedValue(i) : kUnInitialized;
|
|
const intptr_t got = output[i];
|
|
if (dest_start_in_bytes <= i && i < dest_end_in_bytes) {
|
|
// Copied.
|
|
const intptr_t expected = ExpectedValue(i - index_diff);
|
|
if (expected != got) {
|
|
if (got == unchanged) {
|
|
expect.Fail("No change to output buffer at index %" Pd
|
|
", expected %" Pd ", got %" Pd "",
|
|
i, expected, got);
|
|
} else {
|
|
expect.Fail("Incorrect change to output buffer at index %" Pd
|
|
", expected %" Pd ", got %" Pd "",
|
|
i, expected, got);
|
|
}
|
|
}
|
|
} else {
|
|
// Untouched.
|
|
if (got != unchanged) {
|
|
expect.Fail("Unexpected change to input buffer at index %" Pd
|
|
", expected %" Pd ", got %" Pd "",
|
|
i, unchanged, got);
|
|
}
|
|
}
|
|
}
|
|
return expect.failed();
|
|
}
|
|
|
|
#define CHECK_DEFAULT_MEMORY(in, out) \
|
|
do { \
|
|
if (CheckMemory(dart::Expect(__FILE__, __LINE__), in, out, 0, 0, 0, 1)) { \
|
|
return; \
|
|
} \
|
|
} while (false)
|
|
#define CHECK_MEMORY(in, out, start, skip, len, size) \
|
|
do { \
|
|
if (CheckMemory(dart::Expect(__FILE__, __LINE__), in, out, start, skip, \
|
|
len, size)) { \
|
|
return; \
|
|
} \
|
|
} while (false)
|
|
|
|
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) {
|
|
OS::Print("==================================================\n");
|
|
OS::Print("RunMemoryCopyInstrTest src_start %" Pd " dest_start %" Pd
|
|
" length "
|
|
"%" Pd "%s elem_size %" Pd "\n",
|
|
src_start, dest_start, length, unboxed_inputs ? " (unboxed)" : "",
|
|
elem_size);
|
|
OS::Print("==================================================\n");
|
|
classid_t cid = TypedDataCidForElementSize(elem_size);
|
|
|
|
uint8_t* ptr = reinterpret_cast<uint8_t*>(malloc(kMemoryTestLength));
|
|
uint8_t* ptr2 = use_same_buffer
|
|
? ptr
|
|
: reinterpret_cast<uint8_t*>(malloc(kMemoryTestLength));
|
|
InitializeMemory(ptr, ptr2);
|
|
|
|
OS::Print("&ptr %p &ptr2 %p\n", ptr, ptr2);
|
|
|
|
// clang-format off
|
|
CStringUniquePtr kScript(OS::SCreate(nullptr, R"(
|
|
import 'dart:ffi';
|
|
|
|
@pragma("vm:entry-point", "call")
|
|
void copyConst() {
|
|
final pointer = Pointer<Uint8>.fromAddress(%s%p);
|
|
final pointer2 = Pointer<Uint8>.fromAddress(%s%p);
|
|
noop();
|
|
}
|
|
|
|
@pragma("vm:entry-point", "call")
|
|
void callNonConstCopy() {
|
|
final pointer = Pointer<Uint8>.fromAddress(%s%p);
|
|
final pointer2 = Pointer<Uint8>.fromAddress(%s%p);
|
|
final src_start = %)" Pd R"(;
|
|
final dest_start = %)" Pd R"(;
|
|
final length = %)" Pd R"(;
|
|
copyNonConst(
|
|
pointer, pointer2, src_start, dest_start, length);
|
|
}
|
|
|
|
void noop() {}
|
|
|
|
void copyNonConst(Pointer<Uint8> ptr1,
|
|
Pointer<Uint8> ptr2,
|
|
int src_start,
|
|
int dest_start,
|
|
int length) {}
|
|
)", pointer_prefix, ptr, pointer_prefix, ptr2,
|
|
pointer_prefix, ptr, pointer_prefix, ptr2,
|
|
src_start, dest_start, length));
|
|
// clang-format on
|
|
|
|
const auto& root_library = Library::Handle(LoadTestScript(kScript.get()));
|
|
|
|
// Test the MemoryCopy instruction when the inputs are constants.
|
|
{
|
|
Invoke(root_library, "copyConst");
|
|
// Running this should be a no-op on the memory.
|
|
CHECK_DEFAULT_MEMORY(ptr, ptr2);
|
|
|
|
const auto& const_copy =
|
|
Function::Handle(GetFunction(root_library, "copyConst"));
|
|
|
|
TestPipeline pipeline(const_copy, CompilerPass::kJIT);
|
|
FlowGraph* flow_graph = pipeline.RunPasses({
|
|
CompilerPass::kComputeSSA,
|
|
});
|
|
|
|
StaticCallInstr* pointer = nullptr;
|
|
StaticCallInstr* pointer2 = nullptr;
|
|
StaticCallInstr* another_function_call = nullptr;
|
|
{
|
|
ILMatcher cursor(flow_graph, flow_graph->graph_entry()->normal_entry());
|
|
|
|
EXPECT(cursor.TryMatch({
|
|
kMoveGlob,
|
|
kMatchAndMoveDebugStepCheck,
|
|
kMatchAndMoveDebugStepCheck,
|
|
kMatchAndMoveRecordCoverage,
|
|
{kMatchAndMoveStaticCall, &pointer},
|
|
kMatchAndMoveDebugStepCheck,
|
|
kMatchAndMoveRecordCoverage,
|
|
{kMatchAndMoveStaticCall, &pointer2},
|
|
kMatchAndMoveRecordCoverage,
|
|
{kMatchAndMoveStaticCall, &another_function_call},
|
|
}));
|
|
}
|
|
|
|
Zone* const zone = Thread::Current()->zone();
|
|
auto const rep = unboxed_inputs ? kUnboxedIntPtr : kTagged;
|
|
|
|
auto* const src_start_constant_instr = flow_graph->GetConstant(
|
|
Integer::ZoneHandle(zone, Integer::New(src_start, Heap::kOld)), rep);
|
|
|
|
auto* const dest_start_constant_instr = flow_graph->GetConstant(
|
|
Integer::ZoneHandle(zone, Integer::New(dest_start, Heap::kOld)), rep);
|
|
|
|
auto* const length_constant_instr = flow_graph->GetConstant(
|
|
Integer::ZoneHandle(zone, Integer::New(length, Heap::kOld)), rep);
|
|
|
|
auto* const memory_copy_instr = new (zone) MemoryCopyInstr(
|
|
new (zone) Value(pointer), /*src_cid=*/cid, new (zone) Value(pointer2),
|
|
/*dest_cid=*/cid, new (zone) Value(src_start_constant_instr),
|
|
new (zone) Value(dest_start_constant_instr),
|
|
new (zone) Value(length_constant_instr), unboxed_inputs,
|
|
/*can_overlap=*/use_same_buffer);
|
|
flow_graph->InsertBefore(another_function_call, memory_copy_instr, nullptr,
|
|
FlowGraph::kEffect);
|
|
|
|
another_function_call->RemoveFromGraph();
|
|
|
|
{
|
|
// Check we constructed the right graph.
|
|
ILMatcher cursor(flow_graph, flow_graph->graph_entry()->normal_entry());
|
|
EXPECT(cursor.TryMatch({
|
|
kMoveGlob,
|
|
kMatchAndMoveDebugStepCheck,
|
|
kMatchAndMoveDebugStepCheck,
|
|
kMatchAndMoveRecordCoverage,
|
|
kMatchAndMoveStaticCall,
|
|
kMatchAndMoveDebugStepCheck,
|
|
kMatchAndMoveRecordCoverage,
|
|
kMatchAndMoveStaticCall,
|
|
kMatchAndMoveRecordCoverage,
|
|
kMatchAndMoveMemoryCopy,
|
|
}));
|
|
}
|
|
|
|
{
|
|
#if !defined(PRODUCT) && !defined(USING_THREAD_SANITIZER)
|
|
SetFlagScope<bool> sfs(&FLAG_disassemble_optimized, true);
|
|
#endif
|
|
|
|
pipeline.RunForcedOptimizedAfterSSAPasses();
|
|
pipeline.CompileGraphAndAttachFunction();
|
|
}
|
|
|
|
{
|
|
// Check that the memory copy has constant inputs after optimization.
|
|
ILMatcher cursor(flow_graph, flow_graph->graph_entry()->normal_entry());
|
|
MemoryCopyInstr* memory_copy;
|
|
EXPECT(cursor.TryMatch({
|
|
kMoveGlob,
|
|
{kMatchAndMoveMemoryCopy, &memory_copy},
|
|
}));
|
|
EXPECT_EQ(kUntagged, memory_copy->src()->definition()->representation());
|
|
EXPECT_EQ(kUntagged, memory_copy->dest()->definition()->representation());
|
|
EXPECT(memory_copy->src_start()->BindsToConstant());
|
|
EXPECT(memory_copy->dest_start()->BindsToConstant());
|
|
EXPECT(memory_copy->length()->BindsToConstant());
|
|
}
|
|
|
|
// Run the mem copy.
|
|
Invoke(root_library, "copyConst");
|
|
}
|
|
|
|
CHECK_MEMORY(ptr, ptr2, dest_start, src_start, length, elem_size);
|
|
// Reinitialize the memory for the non-constant MemoryCopy version.
|
|
InitializeMemory(ptr, ptr2);
|
|
|
|
// Test the MemoryCopy instruction when the inputs are not constants.
|
|
{
|
|
Invoke(root_library, "callNonConstCopy");
|
|
// Running this should be a no-op on the memory.
|
|
CHECK_DEFAULT_MEMORY(ptr, ptr2);
|
|
|
|
const auto& copy_non_const =
|
|
Function::Handle(GetFunction(root_library, "copyNonConst"));
|
|
|
|
TestPipeline pipeline(copy_non_const, CompilerPass::kJIT);
|
|
FlowGraph* flow_graph = pipeline.RunPasses({
|
|
CompilerPass::kComputeSSA,
|
|
});
|
|
|
|
auto* const entry_instr = flow_graph->graph_entry()->normal_entry();
|
|
auto* const initial_defs = entry_instr->initial_definitions();
|
|
EXPECT(initial_defs != nullptr);
|
|
EXPECT_EQ(5, initial_defs->length());
|
|
|
|
auto* const param_ptr = initial_defs->At(0)->AsParameter();
|
|
EXPECT(param_ptr != nullptr);
|
|
auto* const param_ptr2 = initial_defs->At(1)->AsParameter();
|
|
EXPECT(param_ptr2 != nullptr);
|
|
auto* const param_src_start = initial_defs->At(2)->AsParameter();
|
|
EXPECT(param_src_start != nullptr);
|
|
auto* const param_dest_start = initial_defs->At(3)->AsParameter();
|
|
EXPECT(param_dest_start != nullptr);
|
|
auto* const param_length = initial_defs->At(4)->AsParameter();
|
|
EXPECT(param_length != nullptr);
|
|
|
|
DartReturnInstr* return_instr;
|
|
{
|
|
ILMatcher cursor(flow_graph, entry_instr);
|
|
|
|
EXPECT(cursor.TryMatch({
|
|
kMoveGlob,
|
|
{kMatchDartReturn, &return_instr},
|
|
}));
|
|
}
|
|
|
|
Zone* const zone = Thread::Current()->zone();
|
|
|
|
Definition* src_start_def = param_src_start;
|
|
Definition* dest_start_def = param_dest_start;
|
|
Definition* length_def = param_length;
|
|
if (unboxed_inputs) {
|
|
// Manually add the unbox instruction ourselves instead of leaving it
|
|
// up to the SelectDefinitions pass.
|
|
length_def = UnboxInstr::Create(
|
|
kUnboxedWord, new (zone) Value(param_length), DeoptId::kNone,
|
|
UnboxInstr::ValueMode::kHasValidType);
|
|
flow_graph->InsertBefore(return_instr, length_def, nullptr,
|
|
FlowGraph::kValue);
|
|
dest_start_def = UnboxInstr::Create(
|
|
kUnboxedWord, new (zone) Value(param_dest_start), DeoptId::kNone,
|
|
UnboxInstr::ValueMode::kHasValidType);
|
|
flow_graph->InsertBefore(length_def, dest_start_def, nullptr,
|
|
FlowGraph::kValue);
|
|
src_start_def = UnboxInstr::Create(
|
|
kUnboxedWord, new (zone) Value(param_src_start), DeoptId::kNone,
|
|
UnboxInstr::ValueMode::kHasValidType);
|
|
flow_graph->InsertBefore(dest_start_def, src_start_def, nullptr,
|
|
FlowGraph::kValue);
|
|
}
|
|
|
|
auto* const memory_copy_instr = new (zone) MemoryCopyInstr(
|
|
new (zone) Value(param_ptr), /*src_cid=*/cid,
|
|
new (zone) Value(param_ptr2), /*dest_cid=*/cid,
|
|
new (zone) Value(src_start_def), new (zone) Value(dest_start_def),
|
|
new (zone) Value(length_def),
|
|
|
|
unboxed_inputs, /*can_overlap=*/use_same_buffer);
|
|
flow_graph->InsertBefore(return_instr, memory_copy_instr, nullptr,
|
|
FlowGraph::kEffect);
|
|
|
|
{
|
|
// Check we constructed the right graph.
|
|
ILMatcher cursor(flow_graph, flow_graph->graph_entry()->normal_entry());
|
|
if (unboxed_inputs) {
|
|
EXPECT(cursor.TryMatch({
|
|
kMoveGlob,
|
|
kMatchAndMoveUnbox,
|
|
kMatchAndMoveUnbox,
|
|
kMatchAndMoveUnbox,
|
|
kMatchAndMoveMemoryCopy,
|
|
kMatchDartReturn,
|
|
}));
|
|
} else {
|
|
EXPECT(cursor.TryMatch({
|
|
kMoveGlob,
|
|
kMatchAndMoveMemoryCopy,
|
|
kMatchDartReturn,
|
|
}));
|
|
}
|
|
}
|
|
|
|
{
|
|
#if !defined(PRODUCT) && !defined(USING_THREAD_SANITIZER)
|
|
SetFlagScope<bool> sfs(&FLAG_disassemble_optimized, true);
|
|
#endif
|
|
|
|
pipeline.RunForcedOptimizedAfterSSAPasses();
|
|
pipeline.CompileGraphAndAttachFunction();
|
|
}
|
|
|
|
{
|
|
// Check that the memory copy has non-constant inputs after optimization.
|
|
ILMatcher cursor(flow_graph, flow_graph->graph_entry()->normal_entry());
|
|
MemoryCopyInstr* memory_copy;
|
|
EXPECT(cursor.TryMatch({
|
|
kMoveGlob,
|
|
{kMatchAndMoveMemoryCopy, &memory_copy},
|
|
}));
|
|
EXPECT_EQ(kUntagged, memory_copy->src()->definition()->representation());
|
|
EXPECT_EQ(kUntagged, memory_copy->dest()->definition()->representation());
|
|
EXPECT(!memory_copy->src_start()->BindsToConstant());
|
|
EXPECT(!memory_copy->dest_start()->BindsToConstant());
|
|
EXPECT(!memory_copy->length()->BindsToConstant());
|
|
}
|
|
|
|
// Run the mem copy.
|
|
Invoke(root_library, "callNonConstCopy");
|
|
}
|
|
|
|
CHECK_MEMORY(ptr, ptr2, dest_start, src_start, length, elem_size);
|
|
free(ptr);
|
|
if (!use_same_buffer) {
|
|
free(ptr2);
|
|
}
|
|
}
|
|
|
|
#define MEMORY_COPY_TEST_BOXED(src_start, dest_start, length, elem_size) \
|
|
ISOLATE_UNIT_TEST_CASE( \
|
|
IRTest_MemoryCopy_##src_start##_##dest_start##_##length##_##elem_size) { \
|
|
RunMemoryCopyInstrTest(src_start, dest_start, length, elem_size, false, \
|
|
false); \
|
|
}
|
|
|
|
#define MEMORY_COPY_TEST_UNBOXED(src_start, dest_start, length, el_si) \
|
|
ISOLATE_UNIT_TEST_CASE( \
|
|
IRTest_MemoryCopy_##src_start##_##dest_start##_##length##_##el_si##_u) { \
|
|
RunMemoryCopyInstrTest(src_start, dest_start, length, el_si, true, false); \
|
|
}
|
|
|
|
#define MEMORY_MOVE_TEST_BOXED(src_start, dest_start, length, elem_size) \
|
|
ISOLATE_UNIT_TEST_CASE( \
|
|
IRTest_MemoryMove_##src_start##_##dest_start##_##length##_##elem_size) { \
|
|
RunMemoryCopyInstrTest(src_start, dest_start, length, elem_size, false, \
|
|
true); \
|
|
}
|
|
|
|
#define MEMORY_MOVE_TEST_UNBOXED(src_start, dest_start, length, el_si) \
|
|
ISOLATE_UNIT_TEST_CASE( \
|
|
IRTest_MemoryMove_##src_start##_##dest_start##_##length##_##el_si##_u) { \
|
|
RunMemoryCopyInstrTest(src_start, dest_start, length, el_si, true, true); \
|
|
}
|
|
|
|
#define MEMORY_COPY_TEST(src_start, dest_start, length, elem_size) \
|
|
MEMORY_COPY_TEST_BOXED(src_start, dest_start, length, elem_size) \
|
|
MEMORY_COPY_TEST_UNBOXED(src_start, dest_start, length, elem_size)
|
|
|
|
#define MEMORY_MOVE_TEST(src_start, dest_start, length, elem_size) \
|
|
MEMORY_MOVE_TEST_BOXED(src_start, dest_start, length, elem_size) \
|
|
MEMORY_MOVE_TEST_UNBOXED(src_start, dest_start, length, elem_size)
|
|
|
|
#define MEMORY_TEST(src_start, dest_start, length, elem_size) \
|
|
MEMORY_MOVE_TEST(src_start, dest_start, length, elem_size) \
|
|
MEMORY_COPY_TEST(src_start, dest_start, length, elem_size)
|
|
|
|
// No offset, varying length.
|
|
MEMORY_TEST(0, 0, 1, 1)
|
|
MEMORY_TEST(0, 0, 2, 1)
|
|
MEMORY_TEST(0, 0, 3, 1)
|
|
MEMORY_TEST(0, 0, 4, 1)
|
|
MEMORY_TEST(0, 0, 5, 1)
|
|
MEMORY_TEST(0, 0, 6, 1)
|
|
MEMORY_TEST(0, 0, 7, 1)
|
|
MEMORY_TEST(0, 0, 8, 1)
|
|
MEMORY_TEST(0, 0, 16, 1)
|
|
|
|
// Offsets.
|
|
MEMORY_TEST(2, 2, 1, 1)
|
|
MEMORY_TEST(2, 17, 3, 1)
|
|
MEMORY_TEST(20, 5, 17, 1)
|
|
|
|
// Other element sizes.
|
|
MEMORY_TEST(0, 0, 1, 2)
|
|
MEMORY_TEST(0, 0, 1, 4)
|
|
MEMORY_TEST(0, 0, 1, 8)
|
|
MEMORY_TEST(0, 0, 2, 2)
|
|
MEMORY_TEST(0, 0, 2, 4)
|
|
MEMORY_TEST(0, 0, 2, 8)
|
|
MEMORY_TEST(0, 0, 4, 2)
|
|
MEMORY_TEST(0, 0, 4, 4)
|
|
MEMORY_TEST(0, 0, 4, 8)
|
|
MEMORY_TEST(0, 0, 8, 2)
|
|
MEMORY_TEST(0, 0, 8, 4)
|
|
MEMORY_TEST(0, 0, 8, 8)
|
|
MEMORY_TEST(0, 0, 2, 16)
|
|
MEMORY_TEST(0, 0, 4, 16)
|
|
MEMORY_TEST(0, 0, 8, 16)
|
|
|
|
// Other element sizes with offsets.
|
|
MEMORY_TEST(1, 1, 2, 2)
|
|
MEMORY_TEST(0, 1, 4, 2)
|
|
MEMORY_TEST(1, 2, 3, 2)
|
|
MEMORY_TEST(2, 1, 3, 2)
|
|
MEMORY_TEST(123, 2, 4, 4)
|
|
MEMORY_TEST(2, 123, 4, 4)
|
|
MEMORY_TEST(24, 23, 8, 4)
|
|
MEMORY_TEST(23, 24, 8, 4)
|
|
MEMORY_TEST(5, 72, 1, 8)
|
|
MEMORY_TEST(12, 13, 3, 8)
|
|
MEMORY_TEST(15, 12, 8, 8)
|
|
|
|
MEMORY_TEST(13, 14, 15, 16)
|
|
MEMORY_TEST(14, 13, 15, 16)
|
|
|
|
// Size promotions with offsets.
|
|
MEMORY_TEST(2, 2, 8, 1) // promoted to 2.
|
|
MEMORY_TEST(4, 4, 8, 1) // promoted to 4.
|
|
MEMORY_TEST(8, 8, 8, 1) // promoted to 8.
|
|
MEMORY_TEST(16, 16, 16, 1) // promoted to 16 on ARM64.
|
|
|
|
} // namespace dart
|