blob: 84d7acfc0508eba6b1de49e7a2af84041e0242f6 [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.
#ifndef RUNTIME_VM_COMPILER_BACKEND_IL_TEST_HELPER_H_
#define RUNTIME_VM_COMPILER_BACKEND_IL_TEST_HELPER_H_
#include <vector>
#include "include/dart_api.h"
#include "platform/allocation.h"
#include "vm/compiler/backend/il.h"
#include "vm/compiler/compiler_pass.h"
#include "vm/compiler/compiler_state.h"
#include "vm/unit_test.h"
// The helpers in this file make it easier to write C++ unit tests which assert
// that Dart code gets turned into certain IR.
//
// Here is an example on how to use it:
//
// ISOLATE_UNIT_TEST_CASE(MyIRTest) {
// const char* script = R"(
// void foo() { ... }
// void main() { foo(); }
// )";
//
// // Load the script and exercise the code once.
// const auto& lib = Library::Handle(LoadTestScript(script);
//
// // Cause the code to be exercised once (to populate ICData).
// Invoke(lib, "main");
//
// // Look up the function.
// const auto& function = Function::Handle(GetFunction(lib, "foo"));
//
// // Run the JIT compilation pipeline with two passes.
// TestPipeline pipeline(function);
// FlowGraph* graph = pipeline.RunJITPasses("ComputeSSA,TypePropagation");
//
// ...
// }
//
namespace dart {
class FlowGraph;
class Function;
class Library;
class RawFunction;
class RawLibrary;
RawLibrary* LoadTestScript(const char* script,
Dart_NativeEntryResolver resolver = nullptr,
const char* lib_uri = RESOLVED_USER_TEST_URI);
RawFunction* GetFunction(const Library& lib, const char* name);
void Invoke(const Library& lib, const char* name);
class TestPipeline : public ValueObject {
public:
explicit TestPipeline(const Function& function,
CompilerPass::PipelineMode mode)
: function_(function),
thread_(Thread::Current()),
compiler_state_(thread_),
mode_(mode) {}
~TestPipeline() { delete pass_state_; }
// As a side-effect this will populate
// - [ic_data_array_]
// - [parsed_function_]
// - [pass_state_]
// - [flow_graph_]
FlowGraph* RunPasses(std::initializer_list<CompilerPass::Id> passes);
void CompileGraphAndAttachFunction();
private:
const Function& function_;
Thread* thread_;
CompilerState compiler_state_;
CompilerPass::PipelineMode mode_;
ZoneGrowableArray<const ICData*>* ic_data_array_ = nullptr;
ParsedFunction* parsed_function_ = nullptr;
CompilerPassState* pass_state_ = nullptr;
FlowGraph* flow_graph_ = nullptr;
};
// Match opcodes used for [ILMatcher], see below.
enum MatchOpCode {
// Emit a match and match-and-move code for every instruction.
#define DEFINE_MATCH_OPCODES(Instruction, _) \
kMatch##Instruction, kMatchAndMove##Instruction, \
kMatchAndMoveOptional##Instruction,
FOR_EACH_INSTRUCTION(DEFINE_MATCH_OPCODES)
#undef DEFINE_MATCH_OPCODES
// Matches a branch and moves left.
kMatchAndMoveBranchTrue,
// Matches a branch and moves right.
kMatchAndMoveBranchFalse,
// Moves forward across any instruction.
kMoveAny,
// Moves over all parallel moves.
kMoveParallelMoves,
// Moves forward until the next match code matches.
kMoveGlob,
};
// Match codes used for [ILMatcher], see below.
class MatchCode {
public:
MatchCode(MatchOpCode opcode) // NOLINT
: opcode_(opcode), capture_(nullptr) {}
MatchCode(MatchOpCode opcode, Instruction** capture)
: opcode_(opcode), capture_(capture) {}
#define DEFINE_TYPED_CONSTRUCTOR(Type, ignored) \
MatchCode(MatchOpCode opcode, Type##Instr** capture) \
: opcode_(opcode), capture_(reinterpret_cast<Instruction**>(capture)) { \
RELEASE_ASSERT(opcode == kMatch##Type || opcode == kMatchAndMove##Type); \
}
FOR_EACH_INSTRUCTION(DEFINE_TYPED_CONSTRUCTOR)
#undef DEFINE_TYPED_CONSTRUCTOR
MatchOpCode opcode() { return opcode_; }
private:
friend class ILMatcher;
MatchOpCode opcode_;
Instruction** capture_;
};
// Used for matching a sequence of IL instructions including capturing support.
//
// Example:
//
// TargetEntryInstr* entry = ....;
// BranchInstr* branch = nullptr;
//
// ILMatcher matcher(flow_graph, entry);
// if (matcher.TryMatch({ kMoveGlob, {kMatchBranch, &branch}, })) {
// EXPECT(branch->operation_cid() == kMintCid);
// ...
// }
//
// This match will start at [entry], follow any number instructions (including
// [GotoInstr]s until a [BranchInstr] is found).
//
// If the match was successful, this returns `true` and updates the current
// value for the cursor.
class ILMatcher : public ValueObject {
public:
ILMatcher(FlowGraph* flow_graph, Instruction* cursor, bool trace = true)
: flow_graph_(flow_graph),
cursor_(cursor),
// clang-format off
#if !defined(PRODUCT)
trace_(trace) {}
#else
trace_(false) {}
#endif
// clang-format on
Instruction* value() { return cursor_; }
// From the current [value] according to match_codes.
//
// Returns `true` if the match was successful and cursor has been updated,
// otherwise returns `false`.
bool TryMatch(std::initializer_list<MatchCode> match_codes);
private:
Instruction* MatchInternal(std::vector<MatchCode> match_codes,
size_t i,
Instruction* cursor);
const char* MatchOpCodeToCString(MatchOpCode code);
FlowGraph* flow_graph_;
Instruction* cursor_;
bool trace_;
};
} // namespace dart
#endif // RUNTIME_VM_COMPILER_BACKEND_IL_TEST_HELPER_H_