[vm] Add pragma vm:unsafe:no-bounds-checks

This pragma instructs compiler to remove all bounds checks from the
annotated function. This can be helpful when tuning performance of
hot tight loops where compiler is unable to eliminate bounds check
itself.

For very tight loops I have measured 25-50% overhead from bounds
checks which I think comes from some combination of general code
quality issues due to fixed input registers and increased branch
density.

In future it could be possible to teach our range analysis to
eliminate bounds checks when loop bound is itself bounded by
array length, but for now we can resort to this pragma for
extremely hot library code.

Issue https://github.com/dart-lang/sdk/issues/55522

TEST=vm/cc/BoundsCheckElimination_Pragma
R=alexmarkov@google.com

Change-Id: Ia7b1e88a16a2b45fa8593a227a4985568892b29c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/364500
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
This commit is contained in:
Vyacheslav Egorov
2024-04-25 14:26:55 +00:00
committed by Commit Queue
parent 350954ae9d
commit b0535bddd6
6 changed files with 49 additions and 3 deletions
+1
View File
@@ -31,6 +31,7 @@ understands potential repercussions.
| Pragma | Meaning |
| --- | --- |
| `vm:unsafe:no-interrupts` | Removes all `CheckStackOverflow` instructions from the optimized version of the marked function, which disables stack overflow checking and interruption within that function. This pragma exists mainly for performance evaluation and should not be used in a general-purpose code, because VM relies on these checks for OOB message delivery and GC scheduling. |
| `vm:unsafe:no-bounds-checks` | Removes all array bounds checks from the optimized version of the marked function in AOT mode. This pragma exists for optimizing throughput of extremely tight loops. |
## Pragmas for internal use
+12 -1
View File
@@ -40,6 +40,14 @@ static bool ShouldReorderBlocks(const Function& function,
FLAG_reorder_basic_blocks && !function.IsFfiCallbackTrampoline();
}
static bool IsMarkedWithNoBoundsChecks(const Function& function) {
Object& options = Object::Handle();
return Library::FindPragma(dart::Thread::Current(),
/*only_core=*/false, function,
Symbols::vm_unsafe_no_bounds_checks(),
/*multiple=*/false, &options);
}
FlowGraph::FlowGraph(const ParsedFunction& parsed_function,
GraphEntryInstr* graph_entry,
intptr_t max_block_id,
@@ -70,7 +78,10 @@ FlowGraph::FlowGraph(const ParsedFunction& parsed_function,
loop_invariant_loads_(nullptr),
captured_parameters_(new(zone()) BitVector(zone(), variable_count())),
inlining_id_(-1),
should_print_(false) {
should_print_(false),
should_remove_all_bounds_checks_(
CompilerState::Current().is_aot() &&
IsMarkedWithNoBoundsChecks(parsed_function.function())) {
should_print_ = FlowGraphPrinter::ShouldPrint(parsed_function.function(),
&compiler_pass_filters_);
ComputeLocationsOfFixedParameters(
+5
View File
@@ -509,6 +509,10 @@ class FlowGraph : public ZoneAllocated {
bool should_reorder_blocks() const { return should_reorder_blocks_; }
bool should_remove_all_bounds_checks() const {
return should_remove_all_bounds_checks_;
}
//
// High-level utilities.
//
@@ -735,6 +739,7 @@ class FlowGraph : public ZoneAllocated {
intptr_t inlining_id_;
bool should_print_;
const bool should_remove_all_bounds_checks_;
uint8_t* compiler_pass_filters_ = nullptr;
intptr_t max_argument_slot_count_ = -1;
+3 -1
View File
@@ -6771,7 +6771,9 @@ bool CheckArrayBoundInstr::IsFixedLengthArrayType(intptr_t cid) {
}
Definition* CheckBoundBaseInstr::Canonicalize(FlowGraph* flow_graph) {
return IsRedundant() ? index()->definition() : this;
return (flow_graph->should_remove_all_bounds_checks() || IsRedundant())
? index()->definition()
: this;
}
intptr_t CheckArrayBoundInstr::LengthOffsetFor(intptr_t class_id) {
@@ -1603,6 +1603,32 @@ ISOLATE_UNIT_TEST_CASE(CheckStackOverflowElimination_NoInterruptsPragma) {
}
}
ISOLATE_UNIT_TEST_CASE(BoundsCheckElimination_Pragma) {
const char* kScript = R"(
import 'dart:typed_data';
@pragma('vm:unsafe:no-bounds-checks')
int test(Uint8List list) {
int result = 0;
for (int i = 0; i < 10; i++) {
result = list[i];
}
return result;
}
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
const auto& function = Function::Handle(GetFunction(root_library, "test"));
TestPipeline pipeline(function, CompilerPass::kAOT);
auto flow_graph = pipeline.RunPasses({});
for (auto block : flow_graph->postorder()) {
for (auto instr : block->instructions()) {
EXPECT_PROPERTY(instr, !it.IsCheckBoundBase());
}
}
}
// This test checks that CSE unwraps redefinitions when comparing all
// instructions except loads, which are handled specially.
ISOLATE_UNIT_TEST_CASE(CSE_Redefinitions) {
+2 -1
View File
@@ -565,7 +565,8 @@ class ObjectPointerVisitor;
V(vm_testing_print_flow_graph, "vm:testing:print-flow-graph") \
V(vm_trace_entrypoints, "vm:testing.unsafe.trace-entrypoints-fn") \
V(vm_unsafe_no_interrupts, "vm:unsafe:no-interrupts") \
V(vm_align_loops, "vm:align-loops")
V(vm_align_loops, "vm:align-loops") \
V(vm_unsafe_no_bounds_checks, "vm:unsafe:no-bounds-checks")
// Contains a list of frequently used strings in a canonicalized form. This
// list is kept in the vm_isolate in order to share the copy across isolates