04ba20aa98
Implements a backend targeting RV32GC and RV64GC, based on Linux standardizing around GC. The assembler is written to make it easy to disable usage of C, but because the sizes of some instruction sequences are compile-time constants, an additional build configuration would need to be defined to make use of it. The assembler and disassembler cover every RV32/64GC instruction. The simulator covers all instructions except accessing CSRs and the floating point state accessible through such, include accrued exceptions and dynamic rounding mode. Quirks: - RISC-V is a compare-and-branch architecture, but some existing "architecture-independent" parts of the Dart compiler assume a condition code architecture. To avoid rewriting these parts, we use a peephole in the assembler to map to compare-and-branch. See Assembler::BranchIf. Luckily nothing depended on taking multiple branches on the same condition code set. - There are no hardware overflow checks, so we must use Hacker's Delight style software checks. Often these are very cheap: if the sign of one operand is known, a single branch is needed. - The ranges of RISC-V branches and jumps are such that we use 3 levels of generation for forward branches, instead of the 2 levels of near and far branches used on ARM[64]. Nearly all code is handled by the first two levels with 20-bits of range, with enormous regex matchers triggering the third level that uses aupic+jalr to get 32-bits of range. - For PC-relative calls in AOT, we always generate auipc+jalr pairs with 32-bits of range, so we never generate trampolines. - Only a subset of registers are available in some compressed instructions, so we assign the most popular uses to these registers. In particular, THR, TMP[2], CODE and PP. This has the effect of assigning CODE and PP to volatile registers in the C calling convention, whereas they are assigned preserved registers on the other architectures. As on ARM64, PP is untagged; this is so short indices can be accessed with a compressed instruction. - There are no push or pop instructions, so combining pushes and pops is preferred so we can update SP once. - The C calling convention has a strongly aligned stack, but unlike on ARM64 we don't need to use an alternate stack pointer. The author ensured language was added to the RISC-V psABI making the OS responsible for realigning the stack pointer for signal handlers, allowing Dart to leave the stack pointer misaligned from the C calling convention's point of view until a foreign call. - We don't bother with the link register tracking done on ARM[64]. Instead we make use of an alternate link register to avoid inline spilling in the write barrier. Unimplemented: - non-trivial FFI cases - Compressed pointers - No intention to implement. - Unboxed SIMD - We might make use of the V extension registers when the V extension is ratified. - BigInt intrinsics TEST=existing tests for IL level, new tests for assembler/disassembler/simulator Bug: https://github.com/dart-lang/sdk/issues/38587 Bug: https://github.com/dart-lang/sdk/issues/48164 Change-Id: I991d1df4be5bf55efec5371b767b332d37dfa3e0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/217289 Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com> Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
195 lines
6.3 KiB
C++
195 lines
6.3 KiB
C++
// Copyright (c) 2012, 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_JIT_COMPILER_H_
|
|
#define RUNTIME_VM_COMPILER_JIT_COMPILER_H_
|
|
|
|
#include "vm/allocation.h"
|
|
#include "vm/compiler/api/deopt_id.h"
|
|
#include "vm/growable_array.h"
|
|
#include "vm/runtime_entry.h"
|
|
#include "vm/thread_pool.h"
|
|
|
|
namespace dart {
|
|
|
|
// Forward declarations.
|
|
class BackgroundCompilationQueue;
|
|
class Class;
|
|
class Code;
|
|
class CompilationWorkQueue;
|
|
class FlowGraph;
|
|
class Function;
|
|
class IndirectGotoInstr;
|
|
class Library;
|
|
class ParsedFunction;
|
|
class QueueElement;
|
|
class Script;
|
|
class SequenceNode;
|
|
|
|
class CompilationPipeline : public ZoneAllocated {
|
|
public:
|
|
static CompilationPipeline* New(Zone* zone, const Function& function);
|
|
|
|
virtual void ParseFunction(ParsedFunction* parsed_function) = 0;
|
|
virtual FlowGraph* BuildFlowGraph(
|
|
Zone* zone,
|
|
ParsedFunction* parsed_function,
|
|
ZoneGrowableArray<const ICData*>* ic_data_array,
|
|
intptr_t osr_id,
|
|
bool optimized) = 0;
|
|
virtual ~CompilationPipeline() {}
|
|
};
|
|
|
|
class DartCompilationPipeline : public CompilationPipeline {
|
|
public:
|
|
void ParseFunction(ParsedFunction* parsed_function) override;
|
|
|
|
FlowGraph* BuildFlowGraph(Zone* zone,
|
|
ParsedFunction* parsed_function,
|
|
ZoneGrowableArray<const ICData*>* ic_data_array,
|
|
intptr_t osr_id,
|
|
bool optimized) override;
|
|
};
|
|
|
|
class IrregexpCompilationPipeline : public CompilationPipeline {
|
|
public:
|
|
IrregexpCompilationPipeline() : backtrack_goto_(NULL) {}
|
|
|
|
void ParseFunction(ParsedFunction* parsed_function) override;
|
|
|
|
FlowGraph* BuildFlowGraph(Zone* zone,
|
|
ParsedFunction* parsed_function,
|
|
ZoneGrowableArray<const ICData*>* ic_data_array,
|
|
intptr_t osr_id,
|
|
bool optimized) override;
|
|
|
|
private:
|
|
IndirectGotoInstr* backtrack_goto_;
|
|
};
|
|
|
|
class Compiler : public AllStatic {
|
|
public:
|
|
static const intptr_t kNoOSRDeoptId = DeoptId::kNone;
|
|
|
|
static bool IsBackgroundCompilation();
|
|
// The result for a function may change if debugging gets turned on/off.
|
|
static bool CanOptimizeFunction(Thread* thread, const Function& function);
|
|
|
|
#if !defined(PRODUCT)
|
|
// Whether it's possible for unoptimized code to optimize immediately on entry
|
|
// (can happen with random or very low optimization counter thresholds)
|
|
static bool CanOptimizeImmediately() {
|
|
return FLAG_optimization_counter_threshold < 2 ||
|
|
FLAG_randomize_optimization_counter;
|
|
}
|
|
#endif
|
|
|
|
// Generates code for given function without optimization and sets its code
|
|
// field.
|
|
//
|
|
// Returns the raw code object if compilation succeeds. Otherwise returns an
|
|
// ErrorPtr. Also installs the generated code on the function.
|
|
static ObjectPtr CompileFunction(Thread* thread, const Function& function);
|
|
|
|
// Generates unoptimized code if not present, current code is unchanged.
|
|
static ErrorPtr EnsureUnoptimizedCode(Thread* thread,
|
|
const Function& function);
|
|
|
|
// Generates optimized code for function.
|
|
//
|
|
// Returns the code object if compilation succeeds. Returns an Error if
|
|
// there is a compilation error. If optimization fails, but there is no
|
|
// error, returns null. Any generated code is installed unless we are in
|
|
// OSR mode.
|
|
static ObjectPtr CompileOptimizedFunction(Thread* thread,
|
|
const Function& function,
|
|
intptr_t osr_id = kNoOSRDeoptId);
|
|
|
|
// Generates local var descriptors and sets it in 'code'. Do not call if the
|
|
// local var descriptor already exists.
|
|
static void ComputeLocalVarDescriptors(const Code& code);
|
|
|
|
// Eagerly compiles all functions in a class.
|
|
//
|
|
// Returns Error::null() if there is no compilation error.
|
|
static ErrorPtr CompileAllFunctions(const Class& cls);
|
|
|
|
// Notify the compiler that background (optimized) compilation has failed
|
|
// because the mutator thread changed the state (e.g., deoptimization,
|
|
// deferred loading). The background compilation may retry to compile
|
|
// the same function later.
|
|
static void AbortBackgroundCompilation(intptr_t deopt_id, const char* msg);
|
|
};
|
|
|
|
// Class to run optimizing compilation in a background thread.
|
|
// Current implementation: one task per isolate, it dies with the owning
|
|
// isolate.
|
|
// No OSR compilation in the background compiler.
|
|
class BackgroundCompiler {
|
|
public:
|
|
explicit BackgroundCompiler(IsolateGroup* isolate_group);
|
|
virtual ~BackgroundCompiler();
|
|
|
|
static void Stop(IsolateGroup* isolate_group) {
|
|
isolate_group->background_compiler()->Stop();
|
|
}
|
|
|
|
// Enqueues a function to be compiled in the background.
|
|
//
|
|
// Return `true` if successful.
|
|
bool EnqueueCompilation(const Function& function);
|
|
|
|
void VisitPointers(ObjectPointerVisitor* visitor);
|
|
|
|
BackgroundCompilationQueue* function_queue() const { return function_queue_; }
|
|
bool is_running() const { return running_; }
|
|
|
|
void Run();
|
|
|
|
private:
|
|
friend class NoBackgroundCompilerScope;
|
|
|
|
void Stop();
|
|
void StopLocked(Thread* thread, SafepointMonitorLocker* done_locker);
|
|
void Enable();
|
|
void Disable();
|
|
bool IsRunning() { return !done_; }
|
|
|
|
IsolateGroup* isolate_group_;
|
|
|
|
Monitor monitor_; // Controls access to the queue and running state.
|
|
BackgroundCompilationQueue* function_queue_;
|
|
bool running_; // While true, will try to read queue and compile.
|
|
bool done_; // True if the thread is done.
|
|
int16_t disabled_depth_;
|
|
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(BackgroundCompiler);
|
|
};
|
|
|
|
class NoBackgroundCompilerScope : public StackResource {
|
|
public:
|
|
explicit NoBackgroundCompilerScope(Thread* thread)
|
|
: StackResource(thread), isolate_group_(thread->isolate_group()) {
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
UNREACHABLE();
|
|
#else
|
|
isolate_group_->background_compiler()->Disable();
|
|
#endif
|
|
}
|
|
~NoBackgroundCompilerScope() {
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
UNREACHABLE();
|
|
#else
|
|
isolate_group_->background_compiler()->Enable();
|
|
#endif
|
|
}
|
|
|
|
private:
|
|
IsolateGroup* isolate_group_;
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_VM_COMPILER_JIT_COMPILER_H_
|