blob: 9b8891ece73d6d1e4eefc68066ae6a56f0cf1a4b [file] [log] [blame]
// Copyright (c) 2019, 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 "vm/closure_functions_cache.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 {
#if defined(DART_PRECOMPILER)
// This test asserts that we are inlining accesses to typed data interfaces
// (e.g. Uint8List) if there are no instantiated 3rd party classes.
ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_Inlining) {
const char* kScript =
R"(
import 'dart:typed_data';
foo(Uint8List list, int from) {
if (from >= list.length) {
return list[from];
}
}
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
const auto& function = Function::Handle(GetFunction(root_library, "foo"));
TestPipeline pipeline(function, CompilerPass::kAOT);
FlowGraph* flow_graph = pipeline.RunPasses({});
auto entry = flow_graph->graph_entry()->normal_entry();
EXPECT(entry != nullptr);
CheckNullInstr* check_null = nullptr;
LoadFieldInstr* load_field = nullptr;
GenericCheckBoundInstr* bounds_check = nullptr;
Instruction* load_untagged = nullptr;
LoadIndexedInstr* load_indexed = nullptr;
ILMatcher cursor(flow_graph, entry);
if (IsolateGroup::Current()->null_safety()) {
RELEASE_ASSERT(cursor.TryMatch({
kMoveGlob,
{kMatchAndMoveLoadField, &load_field},
kMoveGlob,
kMatchAndMoveBranchTrue,
kMoveGlob,
{kMatchAndMoveGenericCheckBound, &bounds_check},
{kMatchAndMoveLoadUntagged, &load_untagged},
kMoveParallelMoves,
{kMatchAndMoveLoadIndexed, &load_indexed},
kMoveGlob,
kMatchReturn,
}));
} else {
RELEASE_ASSERT(cursor.TryMatch({
kMoveGlob,
{kMatchAndMoveCheckNull, &check_null},
{kMatchAndMoveLoadField, &load_field},
kMoveGlob,
kMatchAndMoveBranchTrue,
kMoveGlob,
{kMatchAndMoveGenericCheckBound, &bounds_check},
{kMatchAndMoveLoadUntagged, &load_untagged},
kMoveParallelMoves,
{kMatchAndMoveLoadIndexed, &load_indexed},
kMoveGlob,
kMatchReturn,
}));
}
EXPECT(load_field->InputAt(0)->definition()->IsParameter());
EXPECT(bounds_check->length()
->definition()
->OriginalDefinitionIgnoreBoxingAndConstraints() == load_field);
EXPECT(load_untagged->InputAt(0)->definition()->IsParameter());
EXPECT(load_indexed->InputAt(0)->definition() == load_untagged);
}
// This test asserts that we are inlining get:length, [] and []= for all typed
// data interfaces. It also ensures that the asserted IR actually works by
// exercising it.
ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_FunctionalGetSet) {
const char* kTemplate =
R"(
import 'dart:typed_data';
void reverse%s(%s list) {
final length = list.length;
final halfLength = length >> 1;
for (int i = 0; i < halfLength; ++i) {
final tmp = list[length-i-1];
list[length-i-1] = list[i];
list[i] = tmp;
}
}
)";
char script_buffer[1024];
char uri_buffer[1024];
char function_name[1024];
auto& lib = Library::Handle();
auto& function = Function::Handle();
auto check_il = [&](const char* name) {
// Fill in the template with the [name].
Utils::SNPrint(script_buffer, sizeof(script_buffer), kTemplate, name, name);
Utils::SNPrint(uri_buffer, sizeof(uri_buffer), "file:///reverse-%s.dart",
name);
Utils::SNPrint(function_name, sizeof(function_name), "reverse%s", name);
// Create a new library, load the function and compile it using our AOT
// pipeline.
lib = LoadTestScript(script_buffer, nullptr, uri_buffer);
function = GetFunction(lib, function_name);
TestPipeline pipeline(function, CompilerPass::kAOT);
FlowGraph* flow_graph = pipeline.RunPasses({});
auto entry = flow_graph->graph_entry()->normal_entry();
// Ensure the IL matches what we expect.
ILMatcher cursor(flow_graph, entry);
if (IsolateGroup::Current()->null_safety()) {
EXPECT(cursor.TryMatch({
// Before loop
kMoveGlob,
kMatchAndMoveLoadField,
kMoveGlob,
kMatchAndMoveBranchTrue,
// Loop
kMoveGlob,
// Load 1
kMatchAndMoveGenericCheckBound,
kMoveGlob,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveLoadIndexed,
kMoveGlob,
// Load 2
kMatchAndMoveGenericCheckBound,
kMoveGlob,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveLoadIndexed,
kMoveGlob,
// Store 1
kMoveParallelMoves,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveStoreIndexed,
kMoveGlob,
// Store 2
kMoveParallelMoves,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveStoreIndexed,
kMoveGlob,
// Exit the loop.
kMatchAndMoveBranchFalse,
kMoveGlob,
kMatchReturn,
}));
} else {
EXPECT(cursor.TryMatch({
// Before loop
kMoveGlob,
kMatchAndMoveCheckNull,
kMatchAndMoveLoadField,
kMoveGlob,
kMatchAndMoveBranchTrue,
// Loop
kMoveGlob,
// Load 1
kMatchAndMoveGenericCheckBound,
kMoveGlob,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveLoadIndexed,
kMoveGlob,
// Load 2
kMatchAndMoveGenericCheckBound,
kMoveGlob,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveLoadIndexed,
kMoveGlob,
// Store 1
kMoveParallelMoves,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveStoreIndexed,
kMoveGlob,
// Store 2
kMoveParallelMoves,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveStoreIndexed,
kMoveGlob,
// Exit the loop.
kMatchAndMoveBranchFalse,
kMoveGlob,
kMatchReturn,
}));
}
};
check_il("Uint8List");
check_il("Int8List");
check_il("Uint8ClampedList");
check_il("Int16List");
check_il("Uint16List");
check_il("Int32List");
check_il("Uint32List");
check_il("Int64List");
check_il("Uint64List");
check_il("Float32List");
check_il("Float64List");
}
// This test asserts that we get errors if receiver, index or value are null.
ISOLATE_UNIT_TEST_CASE(IRTest_TypedDataAOT_FunctionalIndexError) {
const char* kTemplate =
R"(
import 'dart:typed_data';
void set%s(%s list, int index, %s value) {
list[index] = value;
}
)";
char script_buffer[1024];
char uri_buffer[1024];
char function_name[1024];
auto& lib = Library::Handle();
auto& function = Function::Handle();
auto& arguments = Array::Handle();
auto& result = Object::Handle();
const intptr_t kIndex = 1;
const intptr_t kLastStage = 3;
auto run_test = [&](const char* name, const char* type,
const TypedDataBase& data, const Object& value,
int stage) {
// Fill in the template with the [name].
Utils::SNPrint(script_buffer, sizeof(script_buffer), kTemplate, name, name,
type);
Utils::SNPrint(uri_buffer, sizeof(uri_buffer), "file:///set-%s.dart", name);
Utils::SNPrint(function_name, sizeof(function_name), "set%s", name);
// Create a new library, load the function and compile it using our AOT
// pipeline.
lib = LoadTestScript(script_buffer, nullptr, uri_buffer);
function = GetFunction(lib, function_name);
TestPipeline pipeline(function, CompilerPass::kAOT);
FlowGraph* flow_graph = pipeline.RunPasses({});
auto entry = flow_graph->graph_entry()->normal_entry();
// Ensure the IL matches what we expect.
ILMatcher cursor(flow_graph, entry, /*trace=*/true);
if (IsolateGroup::Current()->null_safety()) {
EXPECT(cursor.TryMatch({
// LoadField length
kMoveGlob,
kMatchAndMoveLoadField,
// Bounds check
kMoveGlob,
kMatchAndMoveGenericCheckBound,
// Store value.
kMoveGlob,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveOptionalUnbox,
kMoveParallelMoves,
kMatchAndMoveStoreIndexed,
// Return
kMoveGlob,
kMatchReturn,
}));
} else {
EXPECT(cursor.TryMatch({
// Receiver null check
kMoveGlob,
kMatchAndMoveCheckNull,
// Index null check
kMoveGlob,
kMatchAndMoveCheckNull,
// Value null check
kMoveGlob,
kMatchAndMoveCheckNull,
// LoadField length
kMoveGlob,
kMatchAndMoveLoadField,
// Bounds check
kMoveGlob,
kMatchAndMoveGenericCheckBound,
// Store value.
kMoveGlob,
kMatchAndMoveLoadUntagged,
kMoveParallelMoves,
kMatchAndMoveOptionalUnbox,
kMoveParallelMoves,
kMatchAndMoveStoreIndexed,
// Return
kMoveGlob,
kMatchReturn,
}));
}
// Compile the graph and attach the code.
pipeline.CompileGraphAndAttachFunction();
arguments = Array::New(3);
arguments.SetAt(0, stage == 0 ? Object::null_object() : data);
arguments.SetAt(
1, stage == 1 ? Object::null_object() : Smi::Handle(Smi::New(kIndex)));
arguments.SetAt(2, stage == 2 ? Object::null_object() : value);
result = DartEntry::InvokeFunction(function, arguments);
// Ensure we didn't deoptimize to unoptimized code.
EXPECT(function.unoptimized_code() == Code::null());
if (stage == kLastStage) {
// The last stage must be successful
EXPECT(result.IsNull());
} else {
// Ensure we get an error.
EXPECT(result.IsUnhandledException());
result = UnhandledException::Cast(result).exception();
}
};
const auto& uint8_list =
TypedData::Handle(TypedData::New(kTypedDataUint8ArrayCid, 16));
const auto& uint8c_list =
TypedData::Handle(TypedData::New(kTypedDataUint8ClampedArrayCid, 16));
const auto& int16_list =
TypedData::Handle(TypedData::New(kTypedDataInt16ArrayCid, 16));
const auto& uint16_list =
TypedData::Handle(TypedData::New(kTypedDataUint16ArrayCid, 16));
const auto& int32_list =
TypedData::Handle(TypedData::New(kTypedDataInt32ArrayCid, 16));
const auto& uint32_list =
TypedData::Handle(TypedData::New(kTypedDataUint32ArrayCid, 16));
const auto& int64_list =
TypedData::Handle(TypedData::New(kTypedDataInt64ArrayCid, 16));
const auto& uint64_list =
TypedData::Handle(TypedData::New(kTypedDataUint64ArrayCid, 16));
const auto& float32_list =
TypedData::Handle(TypedData::New(kTypedDataFloat32ArrayCid, 16));
const auto& float64_list =
TypedData::Handle(TypedData::New(kTypedDataFloat64ArrayCid, 16));
const auto& int8_list =
TypedData::Handle(TypedData::New(kTypedDataInt8ArrayCid, 16));
const auto& int_value = Integer::Handle(Integer::New(42));
const auto& float_value = Double::Handle(Double::New(4.2));
// With null safety nulls cannot be passed as non-nullable arguments, so
// skip all error stages and only run the last stage.
const intptr_t first_stage =
IsolateGroup::Current()->null_safety() ? kLastStage : 0;
for (intptr_t stage = first_stage; stage <= kLastStage; ++stage) {
run_test("Uint8List", "int", int8_list, int_value, stage);
run_test("Int8List", "int", uint8_list, int_value, stage);
run_test("Uint8ClampedList", "int", uint8c_list, int_value, stage);
run_test("Int16List", "int", int16_list, int_value, stage);
run_test("Uint16List", "int", uint16_list, int_value, stage);
run_test("Int32List", "int", int32_list, int_value, stage);
run_test("Uint32List", "int", uint32_list, int_value, stage);
run_test("Int64List", "int", int64_list, int_value, stage);
run_test("Uint64List", "int", uint64_list, int_value, stage);
run_test("Float32List", "double", float32_list, float_value, stage);
run_test("Float64List", "double", float64_list, float_value, stage);
}
}
#endif // defined(DART_PRECOMPILER)
} // namespace dart