| // Copyright (c) 2017, 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 "vm/compiler/frontend/prologue_builder.h" |
| |
| #include "vm/compiler/backend/il.h" |
| #include "vm/compiler/backend/il_printer.h" |
| #include "vm/compiler/frontend/base_flow_graph_builder.h" |
| #include "vm/compiler/jit/compiler.h" |
| #include "vm/kernel_loader.h" |
| #include "vm/longjump.h" |
| #include "vm/object_store.h" |
| #include "vm/report.h" |
| #include "vm/resolver.h" |
| #include "vm/stack_frame.h" |
| |
| #if !defined(DART_PRECOMPILED_RUNTIME) |
| namespace dart { |
| namespace kernel { |
| |
| #define Z (zone_) |
| |
| // Returns static type of the parameter if it can be trusted (was type checked |
| // by caller) and dynamic otherwise. |
| static CompileType ParameterType(LocalVariable* param) { |
| return param->was_type_checked_by_caller() |
| ? CompileType::FromAbstractType(param->type()) |
| : CompileType::Dynamic(); |
| } |
| |
| bool PrologueBuilder::PrologueSkippableOnUncheckedEntry( |
| const Function& function) { |
| return !function.HasOptionalParameters() && |
| !function.IsNonImplicitClosureFunction() && !function.IsGeneric(); |
| } |
| |
| bool PrologueBuilder::HasEmptyPrologue(const Function& function) { |
| return !function.HasOptionalParameters() && !function.IsGeneric() && |
| !function.CanReceiveDynamicInvocation(); |
| } |
| |
| BlockEntryInstr* PrologueBuilder::BuildPrologue(BlockEntryInstr* entry, |
| PrologueInfo* prologue_info) { |
| // We always have to build the graph, but we only link it sometimes. |
| const bool link = !is_inlining_ && !compiling_for_osr_; |
| |
| const intptr_t previous_block_id = last_used_block_id_; |
| |
| const bool load_optional_arguments = function_.HasOptionalParameters(); |
| const bool expect_type_args = function_.IsGeneric(); |
| const bool check_arguments = function_.CanReceiveDynamicInvocation(); |
| |
| Fragment prologue = Fragment(entry); |
| JoinEntryInstr* nsm = NULL; |
| if (load_optional_arguments || check_arguments || expect_type_args) { |
| nsm = BuildThrowNoSuchMethod(); |
| } |
| if (check_arguments) { |
| Fragment f = BuildTypeArgumentsLengthCheck(nsm, expect_type_args); |
| if (link) prologue += f; |
| } |
| if (load_optional_arguments) { |
| Fragment f = BuildOptionalParameterHandling( |
| nsm, parsed_function_->expression_temp_var()); |
| if (link) prologue += f; |
| } else if (check_arguments) { |
| Fragment f = BuildFixedParameterLengthChecks(nsm); |
| if (link) prologue += f; |
| } |
| if (function_.IsClosureFunction()) { |
| Fragment f = BuildClosureContextHandling(); |
| if (!compiling_for_osr_) prologue += f; |
| } |
| if (expect_type_args) { |
| Fragment f = BuildTypeArgumentsHandling(nsm); |
| if (link) prologue += f; |
| } |
| |
| const bool is_empty_prologue = prologue.entry == prologue.current; |
| |
| // Always do this to preserve deoptid numbering. |
| JoinEntryInstr* normal_code = BuildJoinEntry(); |
| Fragment jump_to_normal_code = Goto(normal_code); |
| |
| if (is_empty_prologue) { |
| *prologue_info = PrologueInfo(-1, -1); |
| return entry; |
| } else { |
| prologue += jump_to_normal_code; |
| *prologue_info = |
| PrologueInfo(previous_block_id, normal_code->block_id() - 1); |
| return normal_code; |
| } |
| } |
| |
| Fragment PrologueBuilder::BuildTypeArgumentsLengthCheck(JoinEntryInstr* nsm, |
| bool expect_type_args) { |
| Fragment check_type_args; |
| JoinEntryInstr* done = BuildJoinEntry(); |
| |
| // Type args are always optional, so length can always be zero. |
| // If expect_type_args, a non-zero length must match the declaration length. |
| TargetEntryInstr *then, *fail; |
| check_type_args += LoadArgDescriptor(); |
| check_type_args += LoadNativeField(Slot::ArgumentsDescriptor_type_args_len()); |
| if (expect_type_args) { |
| JoinEntryInstr* join2 = BuildJoinEntry(); |
| |
| LocalVariable* len = MakeTemporary(); |
| |
| TargetEntryInstr* otherwise; |
| check_type_args += LoadLocal(len); |
| check_type_args += IntConstant(0); |
| check_type_args += BranchIfEqual(&then, &otherwise); |
| |
| TargetEntryInstr* then2; |
| Fragment check_len(otherwise); |
| check_len += LoadLocal(len); |
| check_len += IntConstant(function_.NumTypeParameters()); |
| check_len += BranchIfEqual(&then2, &fail); |
| |
| Fragment(then) + Goto(join2); |
| Fragment(then2) + Goto(join2); |
| |
| Fragment(join2) + Drop() + Goto(done); |
| Fragment(fail) + Goto(nsm); |
| } else { |
| check_type_args += IntConstant(0); |
| check_type_args += BranchIfEqual(&then, &fail); |
| Fragment(then) + Goto(done); |
| Fragment(fail) + Goto(nsm); |
| } |
| |
| return Fragment(check_type_args.entry, done); |
| } |
| |
| Fragment PrologueBuilder::BuildOptionalParameterHandling( |
| JoinEntryInstr* nsm, |
| LocalVariable* temp_var) { |
| Fragment copy_args_prologue; |
| const int num_fixed_params = function_.num_fixed_parameters(); |
| const int num_opt_pos_params = function_.NumOptionalPositionalParameters(); |
| const int num_opt_named_params = function_.NumOptionalNamedParameters(); |
| const int num_params = |
| num_fixed_params + num_opt_pos_params + num_opt_named_params; |
| ASSERT(function_.NumParameters() == num_params); |
| |
| // Check that min_num_pos_args <= num_pos_args <= max_num_pos_args, |
| // where num_pos_args is the number of positional arguments passed in. |
| const int min_num_pos_args = num_fixed_params; |
| const int max_num_pos_args = num_fixed_params + num_opt_pos_params; |
| |
| copy_args_prologue += LoadArgDescriptor(); |
| copy_args_prologue += |
| LoadNativeField(Slot::ArgumentsDescriptor_positional_count()); |
| LocalVariable* positional_count_var = MakeTemporary(); |
| |
| copy_args_prologue += LoadArgDescriptor(); |
| copy_args_prologue += LoadNativeField(Slot::ArgumentsDescriptor_count()); |
| LocalVariable* count_var = MakeTemporary(); |
| |
| // Ensure the caller provided at least [min_num_pos_args] arguments. |
| copy_args_prologue += IntConstant(min_num_pos_args); |
| copy_args_prologue += LoadLocal(positional_count_var); |
| copy_args_prologue += SmiRelationalOp(Token::kLTE); |
| TargetEntryInstr *success1, *fail1; |
| copy_args_prologue += BranchIfTrue(&success1, &fail1); |
| copy_args_prologue = Fragment(copy_args_prologue.entry, success1); |
| |
| // Ensure the caller provided at most [max_num_pos_args] arguments. |
| copy_args_prologue += LoadLocal(positional_count_var); |
| copy_args_prologue += IntConstant(max_num_pos_args); |
| copy_args_prologue += SmiRelationalOp(Token::kLTE); |
| TargetEntryInstr *success2, *fail2; |
| copy_args_prologue += BranchIfTrue(&success2, &fail2); |
| copy_args_prologue = Fragment(copy_args_prologue.entry, success2); |
| |
| // Link up the argument check failing code. |
| Fragment(fail1) + Goto(nsm); |
| Fragment(fail2) + Goto(nsm); |
| |
| copy_args_prologue += LoadLocal(count_var); |
| copy_args_prologue += IntConstant(min_num_pos_args); |
| copy_args_prologue += SmiBinaryOp(Token::kSUB, /* truncate= */ true); |
| LocalVariable* optional_count_var = MakeTemporary(); |
| |
| // Copy mandatory parameters down. |
| intptr_t param = 0; |
| for (; param < num_fixed_params; ++param) { |
| copy_args_prologue += LoadLocal(optional_count_var); |
| copy_args_prologue += LoadFpRelativeSlot( |
| compiler::target::kWordSize * |
| (compiler::target::frame_layout.param_end_from_fp + |
| num_fixed_params - param), |
| ParameterType(ParameterVariable(param))); |
| copy_args_prologue += |
| StoreLocalRaw(TokenPosition::kNoSource, ParameterVariable(param)); |
| copy_args_prologue += Drop(); |
| } |
| |
| // Copy optional parameters down. |
| if (num_opt_pos_params > 0) { |
| JoinEntryInstr* next_missing = NULL; |
| for (intptr_t opt_param = 1; param < num_params; ++param, ++opt_param) { |
| TargetEntryInstr *supplied, *missing; |
| copy_args_prologue += IntConstant(opt_param); |
| copy_args_prologue += LoadLocal(optional_count_var); |
| copy_args_prologue += SmiRelationalOp(Token::kLTE); |
| copy_args_prologue += BranchIfTrue(&supplied, &missing); |
| |
| Fragment good(supplied); |
| good += LoadLocal(optional_count_var); |
| good += LoadFpRelativeSlot( |
| compiler::target::kWordSize * |
| (compiler::target::frame_layout.param_end_from_fp + |
| num_fixed_params - param), |
| ParameterType(ParameterVariable(param))); |
| good += StoreLocalRaw(TokenPosition::kNoSource, ParameterVariable(param)); |
| good += Drop(); |
| |
| Fragment not_good(missing); |
| if (next_missing != NULL) { |
| not_good += Goto(next_missing); |
| not_good.current = next_missing; |
| } |
| next_missing = BuildJoinEntry(); |
| not_good += Constant(DefaultParameterValueAt(opt_param - 1)); |
| not_good += |
| StoreLocalRaw(TokenPosition::kNoSource, ParameterVariable(param)); |
| not_good += Drop(); |
| not_good += Goto(next_missing); |
| |
| copy_args_prologue.current = good.current; |
| } |
| copy_args_prologue += Goto(next_missing /* join good/not_good flows */); |
| copy_args_prologue.current = next_missing; |
| |
| // If there are more arguments from the caller we haven't processed, go |
| // NSM. |
| TargetEntryInstr *done, *unknown_named_arg_passed; |
| copy_args_prologue += LoadLocal(positional_count_var); |
| copy_args_prologue += LoadLocal(count_var); |
| copy_args_prologue += BranchIfEqual(&done, &unknown_named_arg_passed); |
| copy_args_prologue.current = done; |
| { |
| Fragment f(unknown_named_arg_passed); |
| f += Goto(nsm); |
| } |
| } else { |
| ASSERT(num_opt_named_params > 0); |
| |
| const intptr_t first_name_offset = |
| compiler::target::ArgumentsDescriptor::first_named_entry_offset() - |
| compiler::target::Array::data_offset(); |
| |
| // Start by alphabetically sorting the names of the optional parameters. |
| int* opt_param_position = Z->Alloc<int>(num_opt_named_params); |
| SortOptionalNamedParametersInto(opt_param_position, num_fixed_params, |
| num_params); |
| |
| ASSERT(temp_var != nullptr); |
| LocalVariable* optional_count_vars_processed = temp_var; |
| copy_args_prologue += IntConstant(0); |
| copy_args_prologue += |
| StoreLocalRaw(TokenPosition::kNoSource, optional_count_vars_processed); |
| copy_args_prologue += Drop(); |
| |
| for (intptr_t i = 0; param < num_params; ++param, ++i) { |
| JoinEntryInstr* join = BuildJoinEntry(); |
| |
| copy_args_prologue += IntConstant( |
| compiler::target::ArgumentsDescriptor::named_entry_size() / |
| compiler::target::kWordSize); |
| copy_args_prologue += LoadLocal(optional_count_vars_processed); |
| copy_args_prologue += SmiBinaryOp(Token::kMUL, /* truncate= */ true); |
| LocalVariable* tuple_diff = MakeTemporary(); |
| |
| // name = arg_desc[names_offset + arg_desc_name_index + nameOffset] |
| copy_args_prologue += LoadArgDescriptor(); |
| copy_args_prologue += |
| IntConstant((first_name_offset + |
| compiler::target::ArgumentsDescriptor::name_offset()) / |
| compiler::target::kWordSize); |
| copy_args_prologue += LoadLocal(tuple_diff); |
| copy_args_prologue += SmiBinaryOp(Token::kADD, /* truncate= */ true); |
| copy_args_prologue += |
| LoadIndexed(/* index_scale = */ compiler::target::kWordSize); |
| |
| // first name in sorted list of all names |
| const String& param_name = String::ZoneHandle( |
| Z, function_.ParameterNameAt(opt_param_position[i])); |
| ASSERT(param_name.IsSymbol()); |
| copy_args_prologue += Constant(param_name); |
| |
| // Compare the two names: Note that the ArgumentDescriptor array always |
| // terminates with a "null" name (i.e. kNullCid), which will prevent us |
| // from running out-of-bounds. |
| TargetEntryInstr *supplied, *missing; |
| copy_args_prologue += BranchIfStrictEqual(&supplied, &missing); |
| |
| // Let's load position from arg descriptor (to see which parameter is the |
| // name) and move kEntrySize forward in ArgDescriptopr names array. |
| Fragment good(supplied); |
| |
| { |
| // fp[target::frame_layout.param_end_from_fp + (count_var - pos)] |
| good += LoadLocal(count_var); |
| { |
| // pos = arg_desc[names_offset + arg_desc_name_index + positionOffset] |
| good += LoadArgDescriptor(); |
| good += IntConstant( |
| (first_name_offset + |
| compiler::target::ArgumentsDescriptor::position_offset()) / |
| compiler::target::kWordSize); |
| good += LoadLocal(tuple_diff); |
| good += SmiBinaryOp(Token::kADD, /* truncate= */ true); |
| good += LoadIndexed(/* index_scale = */ compiler::target::kWordSize); |
| } |
| good += SmiBinaryOp(Token::kSUB, /* truncate= */ true); |
| good += LoadFpRelativeSlot( |
| compiler::target::kWordSize * |
| compiler::target::frame_layout.param_end_from_fp, |
| ParameterType(ParameterVariable(opt_param_position[i]))); |
| |
| // Copy down. |
| good += StoreLocalRaw(TokenPosition::kNoSource, |
| ParameterVariable(opt_param_position[i])); |
| good += Drop(); |
| |
| // Increase processed optional variable count. |
| good += LoadLocal(optional_count_vars_processed); |
| good += IntConstant(1); |
| good += SmiBinaryOp(Token::kADD, /* truncate= */ true); |
| good += StoreLocalRaw(TokenPosition::kNoSource, |
| optional_count_vars_processed); |
| good += Drop(); |
| |
| good += Goto(join); |
| } |
| |
| // We had no match, let's just load the default constant. |
| Fragment not_good(missing); |
| { |
| not_good += Constant( |
| DefaultParameterValueAt(opt_param_position[i] - num_fixed_params)); |
| |
| // Copy down with default value. |
| not_good += StoreLocalRaw(TokenPosition::kNoSource, |
| ParameterVariable(opt_param_position[i])); |
| not_good += Drop(); |
| not_good += Goto(join); |
| } |
| |
| copy_args_prologue.current = join; |
| copy_args_prologue += Drop(); // tuple_diff |
| } |
| |
| // If there are more arguments from the caller we haven't processed, go |
| // NSM. |
| TargetEntryInstr *done, *unknown_named_arg_passed; |
| copy_args_prologue += LoadLocal(optional_count_var); |
| copy_args_prologue += LoadLocal(optional_count_vars_processed); |
| copy_args_prologue += BranchIfEqual(&done, &unknown_named_arg_passed); |
| copy_args_prologue.current = done; |
| |
| { |
| Fragment f(unknown_named_arg_passed); |
| f += Goto(nsm); |
| } |
| } |
| |
| copy_args_prologue += Drop(); // optional_count_var |
| copy_args_prologue += Drop(); // count_var |
| copy_args_prologue += Drop(); // positional_count_var |
| |
| return copy_args_prologue; |
| } |
| |
| Fragment PrologueBuilder::BuildFixedParameterLengthChecks(JoinEntryInstr* nsm) { |
| Fragment check_args; |
| JoinEntryInstr* done = BuildJoinEntry(); |
| |
| check_args += LoadArgDescriptor(); |
| check_args += LoadNativeField(Slot::ArgumentsDescriptor_count()); |
| LocalVariable* count = MakeTemporary(); |
| |
| TargetEntryInstr *then, *fail; |
| check_args += LoadLocal(count); |
| check_args += IntConstant(function_.num_fixed_parameters()); |
| check_args += BranchIfEqual(&then, &fail); |
| |
| TargetEntryInstr *then2, *fail2; |
| Fragment check_len(then); |
| check_len += LoadArgDescriptor(); |
| check_len += LoadNativeField(Slot::ArgumentsDescriptor_positional_count()); |
| check_len += BranchIfEqual(&then2, &fail2); |
| |
| Fragment(fail) + Goto(nsm); |
| Fragment(fail2) + Goto(nsm); |
| Fragment(then2) + Goto(done); |
| |
| return Fragment(check_args.entry, done); |
| } |
| |
| Fragment PrologueBuilder::BuildClosureContextHandling() { |
| LocalVariable* closure_parameter = parsed_function_->ParameterVariable(0); |
| LocalVariable* context = parsed_function_->current_context_var(); |
| |
| // Load closure.context & store it into the context variable. |
| // (both load/store happen on the copyied-down places). |
| Fragment populate_context; |
| populate_context += LoadLocal(closure_parameter); |
| populate_context += LoadNativeField(Slot::Closure_context()); |
| populate_context += StoreLocal(TokenPosition::kNoSource, context); |
| populate_context += Drop(); |
| return populate_context; |
| } |
| |
| Fragment PrologueBuilder::BuildTypeArgumentsHandling(JoinEntryInstr* nsm) { |
| LocalVariable* type_args_var = parsed_function_->RawTypeArgumentsVariable(); |
| |
| Fragment handling; |
| |
| Fragment store_type_args; |
| store_type_args += LoadArgDescriptor(); |
| store_type_args += LoadNativeField(Slot::ArgumentsDescriptor_count()); |
| store_type_args += LoadFpRelativeSlot( |
| compiler::target::kWordSize * |
| (1 + compiler::target::frame_layout.param_end_from_fp), |
| CompileType::CreateNullable(/*is_nullable=*/true, kTypeArgumentsCid)); |
| store_type_args += StoreLocal(TokenPosition::kNoSource, type_args_var); |
| store_type_args += Drop(); |
| |
| Fragment store_null; |
| store_null += NullConstant(); |
| store_null += StoreLocal(TokenPosition::kNoSource, type_args_var); |
| store_null += Drop(); |
| |
| handling += TestTypeArgsLen(store_null, store_type_args, 0); |
| |
| if (parsed_function_->function().IsClosureFunction()) { |
| LocalVariable* closure = parsed_function_->ParameterVariable(0); |
| |
| // Currently, delayed type arguments can only be introduced through type |
| // inference in the FE. So if they are present, we can assume they are |
| // correct in number and bound. |
| Fragment use_delayed_type_args; |
| use_delayed_type_args += LoadLocal(closure); |
| use_delayed_type_args += |
| LoadNativeField(Slot::Closure_delayed_type_arguments()); |
| use_delayed_type_args += |
| StoreLocal(TokenPosition::kNoSource, type_args_var); |
| use_delayed_type_args += Drop(); |
| |
| handling += TestDelayedTypeArgs( |
| closure, |
| /*present=*/TestTypeArgsLen(use_delayed_type_args, Goto(nsm), 0), |
| /*absent=*/Fragment()); |
| } |
| |
| return handling; |
| } |
| |
| void PrologueBuilder::SortOptionalNamedParametersInto(int* opt_param_position, |
| int num_fixed_params, |
| int num_params) { |
| String& name = String::Handle(Z); |
| String& name_i = String::Handle(Z); |
| for (int pos = num_fixed_params; pos < num_params; pos++) { |
| name = function_.ParameterNameAt(pos); |
| int i = pos - num_fixed_params; |
| while (--i >= 0) { |
| name_i = function_.ParameterNameAt(opt_param_position[i]); |
| const intptr_t result = name.CompareTo(name_i); |
| ASSERT(result != 0); |
| if (result > 0) break; |
| opt_param_position[i + 1] = opt_param_position[i]; |
| } |
| opt_param_position[i + 1] = pos; |
| } |
| } |
| |
| } // namespace kernel |
| } // namespace dart |
| |
| #endif // !defined(DART_PRECOMPILED_RUNTIME) |