Index: runtime/vm/code_generator_x64.cc |
=================================================================== |
--- runtime/vm/code_generator_x64.cc (revision 0) |
+++ runtime/vm/code_generator_x64.cc (revision 0) |
@@ -0,0 +1,2797 @@ |
+// Copyright (c) 2011, 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/globals.h" // Needed here to get TARGET_ARCH_X64. |
+#if defined(TARGET_ARCH_X64) |
+ |
+#include "vm/code_generator.h" |
+ |
+#include "lib/error.h" |
+#include "vm/ast_printer.h" |
+#include "vm/class_finalizer.h" |
+#include "vm/dart_entry.h" |
+#include "vm/debugger.h" |
+#include "vm/ic_data.h" |
+#include "vm/longjump.h" |
+#include "vm/object.h" |
+#include "vm/object_store.h" |
+#include "vm/parser.h" |
+#include "vm/resolver.h" |
+#include "vm/stub_code.h" |
+ |
+namespace dart { |
+ |
+DEFINE_FLAG(bool, print_ast, false, "Print abstract syntax tree."); |
+DEFINE_FLAG(bool, print_scopes, false, "Print scopes of local variables."); |
+DEFINE_FLAG(bool, trace_functions, false, "Trace entry of each function."); |
+DEFINE_FLAG(int, optimization_invocation_threshold, 1000, |
+ "number of invocations before a function is optimized, -1 means never."); |
+DECLARE_FLAG(bool, enable_type_checks); |
+DECLARE_FLAG(bool, report_invocation_count); |
+DECLARE_FLAG(bool, trace_compiler); |
+ |
+#define __ assembler_-> |
+ |
+ |
+// TODO(regis): CodeGeneratorState, CodeGenerator::DescriptorList, and |
+// CodeGenerator::HandlerList can probably be moved to code_generator.cc, since |
+// they seem to be architecture independent. |
+ |
+ |
+CodeGeneratorState::CodeGeneratorState(CodeGenerator* codegen) |
+ : StackResource(Isolate::Current()), |
+ codegen_(codegen), |
+ parent_(codegen->state()) { |
+ if (parent_ != NULL) { |
+ root_node_ = parent_->root_node_; |
+ loop_level_ = parent_->loop_level_; |
+ context_level_ = parent_->context_level_; |
+ current_try_index_ = parent_->current_try_index_; |
+ } else { |
+ root_node_ = NULL; |
+ loop_level_ = 0; |
+ context_level_ = 0; |
+ current_try_index_ = CatchClauseNode::kInvalidTryIndex; |
+ } |
+ codegen_->set_state(this); |
+} |
+ |
+ |
+CodeGeneratorState::~CodeGeneratorState() { |
+ codegen_->set_state(parent_); |
+} |
+ |
+ |
+class CodeGenerator::DescriptorList : public ZoneAllocated { |
+ public: |
+ struct PcDesc { |
+ intptr_t pc_offset; // PC offset value of the descriptor. |
+ PcDescriptors::Kind kind; // Descriptor kind (kDeopt, kOther). |
+ intptr_t node_id; // AST node id. |
+ intptr_t token_index; // Token position in source of PC. |
+ intptr_t try_index; // Try block index of PC. |
+ }; |
+ |
+ DescriptorList() : list_() { |
+ } |
+ ~DescriptorList() { } |
+ |
+ intptr_t Length() const { |
+ return list_.length(); |
+ } |
+ |
+ intptr_t PcOffset(int index) const { |
+ return list_[index].pc_offset; |
+ } |
+ PcDescriptors::Kind Kind(int index) const { |
+ return list_[index].kind; |
+ } |
+ intptr_t NodeId(int index) const { |
+ return list_[index].node_id; |
+ } |
+ intptr_t TokenIndex(int index) const { |
+ return list_[index].token_index; |
+ } |
+ intptr_t TryIndex(int index) const { |
+ return list_[index].try_index; |
+ } |
+ |
+ void AddDescriptor(PcDescriptors::Kind kind, |
+ intptr_t pc_offset, |
+ intptr_t node_id, |
+ intptr_t token_index, |
+ intptr_t try_index) { |
+ struct PcDesc data; |
+ data.pc_offset = pc_offset; |
+ data.kind = kind; |
+ data.node_id = node_id; |
+ data.token_index = token_index; |
+ data.try_index = try_index; |
+ list_.Add(data); |
+ } |
+ |
+ RawPcDescriptors* FinalizePcDescriptors(uword entry_point) { |
+ intptr_t num_descriptors = Length(); |
+ const PcDescriptors& descriptors = |
+ PcDescriptors::Handle(PcDescriptors::New(num_descriptors)); |
+ for (intptr_t i = 0; i < num_descriptors; i++) { |
+ descriptors.AddDescriptor(i, |
+ (entry_point + PcOffset(i)), |
+ Kind(i), |
+ NodeId(i), |
+ TokenIndex(i), |
+ TryIndex(i)); |
+ } |
+ return descriptors.raw(); |
+ } |
+ |
+ private: |
+ GrowableArray<struct PcDesc> list_; |
+ DISALLOW_COPY_AND_ASSIGN(DescriptorList); |
+}; |
+ |
+ |
+class CodeGenerator::HandlerList : public ZoneAllocated { |
+ public: |
+ struct HandlerDesc { |
+ intptr_t try_index; // Try block index handled by the handler. |
+ intptr_t pc_offset; // Handler PC offset value. |
+ }; |
+ |
+ HandlerList() : list_() { |
+ } |
+ ~HandlerList() { } |
+ |
+ intptr_t Length() const { |
+ return list_.length(); |
+ } |
+ |
+ intptr_t TryIndex(int index) const { |
+ return list_[index].try_index; |
+ } |
+ intptr_t PcOffset(int index) const { |
+ return list_[index].pc_offset; |
+ } |
+ void SetPcOffset(int index, intptr_t handler_pc) { |
+ list_[index].pc_offset = handler_pc; |
+ } |
+ |
+ void AddHandler(intptr_t try_index, intptr_t pc_offset) { |
+ struct HandlerDesc data; |
+ data.try_index = try_index; |
+ data.pc_offset = pc_offset; |
+ list_.Add(data); |
+ } |
+ |
+ RawExceptionHandlers* FinalizeExceptionHandlers(uword entry_point) { |
+ intptr_t num_handlers = Length(); |
+ const ExceptionHandlers& handlers = |
+ ExceptionHandlers::Handle(ExceptionHandlers::New(num_handlers)); |
+ for (intptr_t i = 0; i < num_handlers; i++) { |
+ handlers.SetHandlerEntry(i, TryIndex(i), (entry_point + PcOffset(i))); |
+ } |
+ return handlers.raw(); |
+ } |
+ |
+ private: |
+ GrowableArray<struct HandlerDesc> list_; |
+ DISALLOW_COPY_AND_ASSIGN(HandlerList); |
+}; |
+ |
+ |
+CodeGenerator::CodeGenerator(Assembler* assembler, |
+ const ParsedFunction& parsed_function) |
+ : assembler_(assembler), |
+ parsed_function_(parsed_function), |
+ locals_space_size_(-1), |
+ state_(NULL), |
+ pc_descriptors_list_(NULL), |
+ exception_handlers_list_(NULL), |
+ try_index_(CatchClauseNode::kInvalidTryIndex) { |
+ ASSERT(assembler_ != NULL); |
+ ASSERT(parsed_function.node_sequence() != NULL); |
+ pc_descriptors_list_ = new CodeGenerator::DescriptorList(); |
+ exception_handlers_list_ = new CodeGenerator::HandlerList(); |
+} |
+ |
+ |
+bool CodeGenerator::IsResultNeeded(AstNode* node) const { |
+ return !state()->IsRootNode(node); |
+} |
+ |
+ |
+// NOTE: First 13 bytes of the code may be patched with a jump instruction. Do |
+// not emit any objects in the first 13 bytes. |
+void CodeGenerator::GenerateCode() { |
+ CodeGeneratorState codegen_state(this); |
+ if (FLAG_print_scopes && FLAG_print_ast) { |
+ // Print the function scope before code generation. |
+ AstPrinter::PrintFunctionScope(parsed_function_); |
+ } |
+ if (FLAG_print_ast) { |
+ // Print the function ast before code generation. |
+ AstPrinter::PrintFunctionNodes(parsed_function_); |
+ } |
+ if (FLAG_trace_functions) { |
+ // Preserve RBX (ic-data array or object) and R10 (arguments descriptor). |
+ __ nop(8); |
+ __ pushq(RBX); |
+ __ pushq(R10); |
+ const Function& function = |
+ Function::ZoneHandle(parsed_function_.function().raw()); |
+ __ LoadObject(RAX, function); |
+ __ pushq(RAX); |
+ GenerateCallRuntime(AstNode::kNoId, |
+ 0, |
+ kTraceFunctionEntryRuntimeEntry); |
+ __ popq(RAX); |
+ __ popq(R10); |
+ __ popq(RBX); |
+ } |
+ |
+ const bool code_generation_finished = TryIntrinsify(); |
+ // In some cases intrinsifier can generate all code and no AST based |
+ // code generation is needed. In some cases slow-paths (e.g., overflows) are |
+ // implemented by the AST based code generation and 'code_generation_finished' |
+ // is false. |
+ if (!code_generation_finished) { |
+ GeneratePreEntryCode(); |
+ GenerateEntryCode(); |
+ if (FLAG_print_scopes) { |
+ // Print the function scope (again) after generating the prologue in order |
+ // to see annotations such as allocation indices of locals. |
+ if (FLAG_print_ast) { |
+ // Second printing. |
+ OS::Print("Annotated "); |
+ } |
+ AstPrinter::PrintFunctionScope(parsed_function_); |
+ } |
+ parsed_function_.node_sequence()->Visit(this); |
+ } |
+ // End of code. |
+ __ int3(); |
+ GenerateDeferredCode(); |
+ |
+ // Emit function patching code. This will be swapped with the first 13 bytes |
+ // at entry point. |
+ pc_descriptors_list_->AddDescriptor(PcDescriptors::kPatchCode, |
+ assembler_->CodeSize(), |
+ AstNode::kNoId, |
+ 0, |
+ -1); |
+ __ jmp(&StubCode::FixCallersTargetLabel()); |
+} |
+ |
+ |
+void CodeGenerator::GenerateDeferredCode() { |
+} |
+ |
+ |
+// Pre entry code is called before the frame has been constructed: |
+// - check for stack overflow. |
+// - optionally count function invocations. |
+// - optionally trigger optimizing compiler if invocation threshold has been |
+// reached. |
+// Note that first 13 bytes may be patched with a jump. |
+// TODO(srdjan): Add check that no object is inlined in the first |
+// 13 bytes (length of a jump instruction). |
+void CodeGenerator::GeneratePreEntryCode() { |
+ // Do not optimize if: |
+ // - we count invocations. |
+ // - optimization disabled via negative 'optimization_invocation_threshold; |
+ // - function is marked as non-optimizable. |
+ // - type checks are enabled. |
+ const bool may_optimize = |
+ !FLAG_report_invocation_count && |
+ (FLAG_optimization_invocation_threshold >= 0) && |
+ !Isolate::Current()->debugger()->IsActive() && |
+ parsed_function_.function().is_optimizable(); |
+ // Count invocation and check. |
+ if (FLAG_report_invocation_count || may_optimize) { |
+ // TODO(turnidge): It would be nice to remove this nop. Right now |
+ // we need it to make sure the function is still patchable. |
+ __ nop(8); |
+ __ nop(5); |
+ const Function& function = |
+ Function::ZoneHandle(parsed_function_.function().raw()); |
+ __ LoadObject(RAX, function); |
+ __ movq(R8, FieldAddress(RAX, Function::invocation_counter_offset())); |
+ __ incq(R8); |
+ if (may_optimize) { |
+ __ cmpq(R8, Immediate(FLAG_optimization_invocation_threshold)); |
+ __ j(GREATER, &StubCode::OptimizeInvokedFunctionLabel()); |
+ } |
+ __ movq(FieldAddress(RAX, Function::invocation_counter_offset()), R8); |
+ } |
+} |
+ |
+ |
+// Verify assumptions (in debug mode only). |
+// - No two deopt descriptors have the same node id (deoptimization). |
+// - No two ic-call descriptors have the same node id (type feedback). |
+// - No two descriptors of same kind have the same PC. |
+// A function without unique ids is marked as non-optimizable (e.g., because of |
+// finally blocks). |
+static void VerifyPcDescriptors(const PcDescriptors& descriptors, |
+ bool check_ids) { |
+#if defined(DEBUG) |
+ // TODO(srdjan): Implement a more efficient way to check, currently drop |
+ // the check for too large number of descriptors. |
+ if (descriptors.Length() > 3000) { |
+ if (FLAG_trace_compiler) { |
+ OS::Print("Not checking pc decriptors, length %d\n", |
+ descriptors.Length()); |
+ } |
+ return; |
+ } |
+ for (intptr_t i = 0; i < descriptors.Length(); i++) { |
+ uword pc = descriptors.PC(i); |
+ PcDescriptors::Kind kind = descriptors.DescriptorKind(i); |
+ // 'node_id' is set for kDeopt and kIcCall and must be unique for one kind. |
+ intptr_t node_id = AstNode::kNoId; |
+ if (check_ids) { |
+ if ((descriptors.DescriptorKind(i) == PcDescriptors::kDeopt) || |
+ (descriptors.DescriptorKind(i) == PcDescriptors::kIcCall)) { |
+ node_id = descriptors.NodeId(i); |
+ } |
+ } |
+ for (intptr_t k = i + 1; k < descriptors.Length(); k++) { |
+ if (kind == descriptors.DescriptorKind(k)) { |
+ if (node_id != AstNode::kNoId) { |
+ ASSERT(descriptors.NodeId(k) != node_id); |
+ } |
+ ASSERT(pc != descriptors.PC(k)); |
+ } |
+ } |
+ } |
+#endif // DEBUG |
+} |
+ |
+ |
+void CodeGenerator::FinalizePcDescriptors(const Code& code) { |
+ ASSERT(pc_descriptors_list_ != NULL); |
+ const PcDescriptors& descriptors = PcDescriptors::Handle( |
+ pc_descriptors_list_->FinalizePcDescriptors(code.EntryPoint())); |
+ VerifyPcDescriptors( |
+ descriptors, parsed_function_.function().is_optimizable()); |
+ code.set_pc_descriptors(descriptors); |
+} |
+ |
+ |
+void CodeGenerator::FinalizeExceptionHandlers(const Code& code) { |
+ ASSERT(exception_handlers_list_ != NULL); |
+ const ExceptionHandlers& handlers = ExceptionHandlers::Handle( |
+ exception_handlers_list_->FinalizeExceptionHandlers(code.EntryPoint())); |
+ code.set_exception_handlers(handlers); |
+} |
+ |
+ |
+void CodeGenerator::GenerateLoadVariable(Register dst, |
+ const LocalVariable& variable) { |
+ if (variable.is_captured()) { |
+ // The variable lives in the context. |
+ int delta = state()->context_level() - variable.owner()->context_level(); |
+ ASSERT(delta >= 0); |
+ Register base = CTX; |
+ while (delta-- > 0) { |
+ __ movq(dst, FieldAddress(base, Context::parent_offset())); |
+ base = dst; |
+ } |
+ __ movq(dst, |
+ FieldAddress(base, Context::variable_offset(variable.index()))); |
+ } else { |
+ // The variable lives in the current stack frame. |
+ __ movq(dst, Address(RBP, variable.index() * kWordSize)); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::GenerateStoreVariable(const LocalVariable& variable, |
+ Register src, |
+ Register scratch) { |
+ if (variable.is_captured()) { |
+ // The variable lives in the context. |
+ int delta = state()->context_level() - variable.owner()->context_level(); |
+ ASSERT(delta >= 0); |
+ Register base = CTX; |
+ while (delta-- > 0) { |
+ __ movq(scratch, FieldAddress(base, Context::parent_offset())); |
+ base = scratch; |
+ } |
+ __ movq(FieldAddress(base, Context::variable_offset(variable.index())), |
+ src); |
+ } else { |
+ // The variable lives in the current stack frame. |
+ __ movq(Address(RBP, variable.index() * kWordSize), src); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::GeneratePushVariable(const LocalVariable& variable, |
+ Register scratch) { |
+ if (variable.is_captured()) { |
+ // The variable lives in the context. |
+ int delta = state()->context_level() - variable.owner()->context_level(); |
+ ASSERT(delta >= 0); |
+ Register base = CTX; |
+ while (delta-- > 0) { |
+ __ movq(scratch, FieldAddress(base, Context::parent_offset())); |
+ base = scratch; |
+ } |
+ __ pushq(FieldAddress(base, Context::variable_offset(variable.index()))); |
+ } else { |
+ // The variable lives in the current stack frame. |
+ __ pushq(Address(RBP, variable.index() * kWordSize)); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::GenerateInstanceCall( |
+ intptr_t node_id, |
+ intptr_t token_index, |
+ const String& function_name, |
+ int num_arguments, |
+ const Array& optional_arguments_names, |
+ intptr_t num_args_checked) { |
+ ASSERT(num_args_checked > 0); // At least receiver check is necessary. |
+ // Set up the function name and number of arguments (including the receiver) |
+ // to the InstanceCall stub which will resolve the correct entrypoint for |
+ // the operator and call it. |
+ ICData ic_data(function_name, num_args_checked); |
+ __ LoadObject(RBX, Array::ZoneHandle(ic_data.data())); |
+ __ LoadObject(R10, ArgumentsDescriptor(num_arguments, |
+ optional_arguments_names)); |
+ uword label_address = 0; |
+ switch (num_args_checked) { |
+ case 1: |
+ label_address = StubCode::OneArgCheckInlineCacheEntryPoint(); |
+ break; |
+ case 2: |
+ label_address = StubCode::TwoArgsCheckInlineCacheEntryPoint(); |
+ break; |
+ default: |
+ UNIMPLEMENTED(); |
+ } |
+ ExternalLabel target_label("InlineCache", label_address); |
+ |
+ __ call(&target_label); |
+ AddCurrentDescriptor(PcDescriptors::kIcCall, |
+ node_id, |
+ token_index); |
+ __ addq(RSP, Immediate(num_arguments * kWordSize)); |
+} |
+ |
+ |
+// Call to generate entry code: |
+// - compute frame size and setup frame. |
+// - allocate local variables on stack. |
+// - optionally check if number of arguments match. |
+// - initialize all non-argument locals to null. |
+// |
+// Input parameters: |
+// RSP : points to return address. |
+// RSP + 8 : address of last argument (arg n-1). |
+// RSP + 8*n : address of first argument (arg 0). |
+// R10 : arguments descriptor array. |
+void CodeGenerator::GenerateEntryCode() { |
+ const Immediate raw_null = |
+ Immediate(reinterpret_cast<intptr_t>(Object::null())); |
+ const Function& function = parsed_function_.function(); |
+ LocalScope* scope = parsed_function_.node_sequence()->scope(); |
+ const int num_fixed_params = function.num_fixed_parameters(); |
+ const int num_opt_params = function.num_optional_parameters(); |
+ const int num_params = num_fixed_params + num_opt_params; |
+ int first_param_index; |
+ int first_local_index; |
+ int num_copied_params; |
+ // Assign indices to parameters and locals. |
+ if (num_params == num_fixed_params) { |
+ // No need to copy incoming arguments. |
+ // The body of the function will access parameter i at fp[1 + num_fixed - i] |
+ // and local variable j at fp[-1 - j]. |
+ first_param_index = 1 + num_params; |
+ first_local_index = -1; |
+ num_copied_params = 0; |
+ } else { |
+ // The body of the function will access copied parameter i at fp[-1 - i] |
+ // and local j at fp[-1 - num_params - j]. |
+ first_param_index = -1; |
+ first_local_index = -1 - num_params; |
+ num_copied_params = num_params; |
+ ASSERT(num_copied_params > 0); |
+ } |
+ |
+ // Allocate parameters and local variables, either in the local frame or in |
+ // the context(s). |
+ LocalScope* context_owner = NULL; // No context needed so far. |
+ int first_free_frame_index = |
+ scope->AllocateVariables(first_param_index, |
+ num_params, |
+ first_local_index, |
+ scope, // Initial loop owner. |
+ &context_owner); |
+ // Frame indices are relative to the frame pointer and are decreasing. |
+ ASSERT(first_free_frame_index <= first_local_index); |
+ const int num_locals = first_local_index - first_free_frame_index; |
+ |
+ // Reserve local space for copied incoming and default arguments and locals. |
+ // TODO(regis): We may give up reserving space on stack for args/locals |
+ // because pushes of initial values may be more effective than moves. |
+ set_locals_space_size((num_copied_params + num_locals) * kWordSize); |
+ __ EnterFrame(locals_space_size()); |
+ |
+ // We check the number of passed arguments when we have to copy them due to |
+ // the presence of optional named parameters. |
+ // No such checking code is generated if only fixed parameters are declared, |
+ // unless we are debug mode or unless we are compiling a closure. |
+ if (num_copied_params == 0) { |
+#if defined(DEBUG) |
+ const bool check_arguments = true; // Always check arguments in debug mode. |
+#else |
+ // The number of arguments passed to closure functions must always be |
+ // checked here, because no resolving stub (normally responsible for the |
+ // check) is involved in closure calls. |
+ const bool check_arguments = function.IsClosureFunction(); |
+#endif |
+ if (check_arguments) { |
+ // Check that num_fixed <= argc <= num_params. |
+ Label argc_in_range; |
+ // Total number of args is the first Smi in args descriptor array (R10). |
+ __ movq(RAX, FieldAddress(R10, Array::data_offset())); |
+ if (num_opt_params == 0) { |
+ __ cmpq(RAX, Immediate(Smi::RawValue(num_fixed_params))); |
+ __ j(EQUAL, &argc_in_range, Assembler::kNearJump); |
+ } else { |
+ __ subq(RAX, Immediate(Smi::RawValue(num_fixed_params))); |
+ __ cmpq(RAX, Immediate(Smi::RawValue(num_opt_params))); |
+ __ j(BELOW_EQUAL, &argc_in_range, Assembler::kNearJump); |
+ } |
+ if (function.IsClosureFunction()) { |
+ GenerateCallRuntime(AstNode::kNoId, |
+ function.token_index(), |
+ kClosureArgumentMismatchRuntimeEntry); |
+ } else { |
+ __ Stop("Wrong number of arguments"); |
+ } |
+ __ Bind(&argc_in_range); |
+ } |
+ } else { |
+ ASSERT(first_param_index == -1); |
+ // Copy positional arguments. |
+ // Check that no fewer than num_fixed_params positional arguments are passed |
+ // in and that no more than num_params arguments are passed in. |
+ // Passed argument i at fp[1 + argc - i] copied to fp[-1 - i]. |
+ |
+ // Total number of args is the first Smi in args descriptor array (R10). |
+ __ movq(RBX, FieldAddress(R10, Array::data_offset())); |
+ // Check that num_args <= num_params. |
+ Label wrong_num_arguments; |
+ __ cmpq(RBX, Immediate(Smi::RawValue(num_params))); |
+ __ j(GREATER, &wrong_num_arguments); |
+ // Number of positional args is the second Smi in descriptor array (R10). |
+ __ movq(RCX, FieldAddress(R10, Array::data_offset() + (1 * kWordSize))); |
+ // Check that num_pos_args >= num_fixed_params. |
+ __ cmpq(RCX, Immediate(Smi::RawValue(num_fixed_params))); |
+ __ j(LESS, &wrong_num_arguments); |
+ // Since RBX and RCX are Smi, use TIMES_4 instead of TIMES_8. |
+ // Let RBX point to the last passed positional argument, i.e. to |
+ // fp[1 + num_args - (num_pos_args - 1)]. |
+ __ subq(RBX, RCX); |
+ __ leaq(RBX, Address(RBP, RBX, TIMES_4, 2 * kWordSize)); |
+ // Let RDI point to the last copied positional argument, i.e. to |
+ // fp[-1 - (num_pos_args - 1)]. |
+ __ SmiUntag(RCX); |
+ __ movq(RAX, RCX); |
+ __ negq(RAX); |
+ __ leaq(RDI, Address(RBP, RAX, TIMES_8, 0)); |
+ Label loop, loop_condition; |
+ __ jmp(&loop_condition, Assembler::kNearJump); |
+ // We do not use the final allocation index of the variable here, i.e. |
+ // scope->VariableAt(i)->index(), because captured variables still need |
+ // to be copied to the context that is not yet allocated. |
+ const Address argument_addr(RBX, RCX, TIMES_8, 0); |
+ const Address copy_addr(RDI, RCX, TIMES_8, 0); |
+ __ Bind(&loop); |
+ __ movq(RAX, argument_addr); |
+ __ movq(copy_addr, RAX); |
+ __ Bind(&loop_condition); |
+ __ decq(RCX); |
+ __ j(POSITIVE, &loop, Assembler::kNearJump); |
+ |
+ // Copy or initialize optional named arguments. |
+ ASSERT(num_opt_params > 0); // Or we would not have to copy arguments. |
+ // Start by alphabetically sorting the names of the optional parameters. |
+ LocalVariable** opt_param = new LocalVariable*[num_opt_params]; |
+ int* opt_param_position = new int[num_opt_params]; |
+ for (int pos = num_fixed_params; pos < num_params; pos++) { |
+ LocalVariable* parameter = scope->VariableAt(pos); |
+ const String& opt_param_name = parameter->name(); |
+ int i = pos - num_fixed_params; |
+ while (--i >= 0) { |
+ LocalVariable* param_i = opt_param[i]; |
+ const intptr_t result = opt_param_name.CompareTo(param_i->name()); |
+ ASSERT(result != 0); |
+ if (result > 0) break; |
+ opt_param[i + 1] = opt_param[i]; |
+ opt_param_position[i + 1] = opt_param_position[i]; |
+ } |
+ opt_param[i + 1] = parameter; |
+ opt_param_position[i + 1] = pos; |
+ } |
+ // Generate code handling each optional parameter in alphabetical order. |
+ // Total number of args is the first Smi in args descriptor array (R10). |
+ __ movq(RBX, FieldAddress(R10, Array::data_offset())); |
+ // Number of positional args is the second Smi in descriptor array (R10). |
+ __ movq(RCX, FieldAddress(R10, Array::data_offset() + (1 * kWordSize))); |
+ __ SmiUntag(RCX); |
+ // Let RBX point to the first passed argument, i.e. to fp[1 + argc - 0]. |
+ __ leaq(RBX, Address(RBP, RBX, TIMES_4, kWordSize)); // RBX is Smi. |
+ // Let EDI point to the name/pos pair of the first named argument. |
+ __ leaq(RDI, FieldAddress(R10, Array::data_offset() + (2 * kWordSize))); |
+ for (int i = 0; i < num_opt_params; i++) { |
+ // Handle this optional parameter only if k or fewer positional arguments |
+ // have been passed, where k is the position of this optional parameter in |
+ // the formal parameter list. |
+ Label load_default_value, assign_optional_parameter, next_parameter; |
+ const int param_pos = opt_param_position[i]; |
+ __ cmpq(RCX, Immediate(param_pos)); |
+ __ j(GREATER, &next_parameter, Assembler::kNearJump); |
+ // Check if this named parameter was passed in. |
+ __ movq(RAX, Address(RDI, 0)); // Load RAX with the name of the argument. |
+ __ CompareObject(RAX, opt_param[i]->name()); |
+ __ j(NOT_EQUAL, &load_default_value, Assembler::kNearJump); |
+ // Load RAX with passed-in argument at provided arg_pos, i.e. at |
+ // fp[1 + argc - arg_pos]. |
+ __ movq(RAX, Address(RDI, kWordSize)); // RAX is arg_pos as Smi. |
+ __ addq(RDI, Immediate(2 * kWordSize)); // Point to next name/pos pair. |
+ __ negq(RAX); |
+ Address argument_addr(RBX, RAX, TIMES_4, 0); // RAX is a negative Smi. |
+ __ movq(RAX, argument_addr); |
+ __ jmp(&assign_optional_parameter, Assembler::kNearJump); |
+ __ Bind(&load_default_value); |
+ // Load RAX with default argument at pos. |
+ const Object& value = Object::ZoneHandle( |
+ parsed_function_.default_parameter_values().At( |
+ param_pos - num_fixed_params)); |
+ __ LoadObject(RAX, value); |
+ __ Bind(&assign_optional_parameter); |
+ // Assign RAX to fp[-1 - param_pos]. |
+ // We do not use the final allocation index of the variable here, i.e. |
+ // scope->VariableAt(i)->index(), because captured variables still need |
+ // to be copied to the context that is not yet allocated. |
+ const Address param_addr(RBP, (-1 - param_pos) * kWordSize); |
+ __ movq(param_addr, RAX); |
+ __ Bind(&next_parameter); |
+ } |
+ delete[] opt_param; |
+ delete[] opt_param_position; |
+ // Check that RDI now points to the null terminator in the array descriptor. |
+ Label all_arguments_processed; |
+ __ cmpq(Address(RDI, 0), raw_null); |
+ __ j(EQUAL, &all_arguments_processed, Assembler::kNearJump); |
+ |
+ __ Bind(&wrong_num_arguments); |
+ if (function.IsClosureFunction()) { |
+ GenerateCallRuntime(AstNode::kNoId, |
+ function.token_index(), |
+ kClosureArgumentMismatchRuntimeEntry); |
+ } else { |
+ // Invoke noSuchMethod function. |
+ ICData ic_data(String::Handle(function.name()), 1); |
+ __ LoadObject(RBX, Array::ZoneHandle(ic_data.data())); |
+ // RBP : points to previous frame pointer. |
+ // RBP + 8 : points to return address. |
+ // RBP + 16 : address of last argument (arg n-1). |
+ // RSP + 16 + 8*(n-1) : address of first argument (arg 0). |
+ // RBX : ic-data array. |
+ // R10 : arguments descriptor array. |
+ __ call(&StubCode::CallNoSuchMethodFunctionLabel()); |
+ } |
+ |
+ if (FLAG_trace_functions) { |
+ __ pushq(RAX); // Preserve result. |
+ __ PushObject(function); |
+ GenerateCallRuntime(AstNode::kNoId, |
+ 0, |
+ kTraceFunctionExitRuntimeEntry); |
+ __ popq(RAX); // Remove argument. |
+ __ popq(RAX); // Restore result. |
+ } |
+ __ LeaveFrame(); |
+ __ ret(); |
+ |
+ __ Bind(&all_arguments_processed); |
+ // Nullify originally passed arguments only after they have been copied and |
+ // checked, otherwise noSuchMethod would not see their original values. |
+ // This step can be skipped in case we decide that formal parameters are |
+ // implicitly final, since garbage collecting the unmodified value is not |
+ // an issue anymore. |
+ |
+ // R10 : arguments descriptor array. |
+ // Total number of args is the first Smi in args descriptor array (R10). |
+ __ movq(RCX, FieldAddress(R10, Array::data_offset())); |
+ __ SmiUntag(RCX); |
+ Label null_args_loop, null_args_loop_condition; |
+ __ jmp(&null_args_loop_condition, Assembler::kNearJump); |
+ const Address original_argument_addr(RBP, RCX, TIMES_8, 2 * kWordSize); |
+ __ Bind(&null_args_loop); |
+ __ movq(original_argument_addr, raw_null); |
+ __ Bind(&null_args_loop_condition); |
+ __ decq(RCX); |
+ __ j(POSITIVE, &null_args_loop, Assembler::kNearJump); |
+ } |
+ |
+ // Initialize locals. |
+ // TODO(regis): For now, always unroll the init loop. Decide later above |
+ // which threshold to implement a loop. |
+ // Consider emitting pushes instead of moves. |
+ for (int index = first_local_index; index > first_free_frame_index; index--) { |
+ if (index == first_local_index) { |
+ __ movq(RAX, raw_null); |
+ } |
+ __ movq(Address(RBP, index * kWordSize), RAX); |
+ } |
+ |
+ // Generate stack overflow check. |
+ __ movq(TMP, Immediate(Isolate::Current()->stack_limit_address())); |
+ __ cmpq(RSP, Address(TMP, 0)); |
+ Label no_stack_overflow; |
+ __ j(ABOVE, &no_stack_overflow); |
+ GenerateCallRuntime(AstNode::kNoId, |
+ function.token_index(), |
+ kStackOverflowRuntimeEntry); |
+ __ Bind(&no_stack_overflow); |
+} |
+ |
+ |
+void CodeGenerator::GenerateReturnEpilog() { |
+ // Unchain the context(s) up to context level 0. |
+ int context_level = state()->context_level(); |
+ ASSERT(context_level >= 0); |
+ while (context_level-- > 0) { |
+ __ movq(CTX, FieldAddress(CTX, Context::parent_offset())); |
+ } |
+#ifdef DEBUG |
+ // Check that the entry stack size matches the exit stack size. |
+ __ movq(R10, RBP); |
+ __ subq(R10, RSP); |
+ ASSERT(locals_space_size() >= 0); |
+ __ cmpq(R10, Immediate(locals_space_size())); |
+ Label wrong_stack; |
+ __ j(NOT_EQUAL, &wrong_stack, Assembler::kNearJump); |
+#endif // DEBUG. |
+ |
+ if (FLAG_trace_functions) { |
+ __ pushq(RAX); // Preserve result. |
+ const Function& function = |
+ Function::ZoneHandle(parsed_function_.function().raw()); |
+ __ LoadObject(RBX, function); |
+ __ pushq(RBX); |
+ GenerateCallRuntime(AstNode::kNoId, |
+ 0, |
+ kTraceFunctionExitRuntimeEntry); |
+ __ popq(RAX); // Remove argument. |
+ __ popq(RAX); // Restore result. |
+ } |
+ __ LeaveFrame(); |
+ __ ret(); |
+ |
+#ifdef DEBUG |
+ __ Bind(&wrong_stack); |
+ __ Stop("Exit stack size does not match the entry stack size."); |
+#endif // DEBUG. |
+} |
+ |
+ |
+void CodeGenerator::VisitReturnNode(ReturnNode* node) { |
+ ASSERT(!IsResultNeeded(node)); |
+ ASSERT(node->value() != NULL); |
+ |
+ if (!node->value()->IsLiteralNode()) { |
+ node->value()->Visit(this); |
+ // The result of the return value is now on top of the stack. |
+ } |
+ |
+ // Generate inlined code for all finally blocks as we are about to transfer |
+ // control out of the 'try' blocks if any. |
+ for (intptr_t i = 0; i < node->inlined_finally_list_length(); i++) { |
+ node->InlinedFinallyNodeAt(i)->Visit(this); |
+ } |
+ |
+ if (node->value()->IsLiteralNode()) { |
+ // Load literal value into RAX. |
+ const Object& literal = node->value()->AsLiteralNode()->literal(); |
+ if (literal.IsSmi()) { |
+ __ movq(RAX, Immediate(reinterpret_cast<int64_t>(literal.raw()))); |
+ } else { |
+ __ LoadObject(RAX, literal); |
+ } |
+ } else { |
+ // Pop the previously evaluated result value into RAX. |
+ __ popq(RAX); |
+ } |
+ |
+ // Generate type check. |
+ if (FLAG_enable_type_checks) { |
+ const RawFunction::Kind kind = parsed_function().function().kind(); |
+ // Implicit getters do not need a type check at return. |
+ if ((kind != RawFunction::kImplicitGetter) && |
+ (kind != RawFunction::kConstImplicitGetter)) { |
+ GenerateAssertAssignable( |
+ node->id(), |
+ node->value()->token_index(), |
+ AbstractType::ZoneHandle(parsed_function().function().result_type()), |
+ String::ZoneHandle(String::NewSymbol("function result"))); |
+ } |
+ } |
+ GenerateReturnEpilog(); |
+} |
+ |
+ |
+void CodeGenerator::VisitLiteralNode(LiteralNode* node) { |
+ if (!IsResultNeeded(node)) return; |
+ __ PushObject(node->literal()); |
+} |
+ |
+ |
+void CodeGenerator::VisitTypeNode(TypeNode* node) { |
+ // Type nodes are handled specially by the code generator. |
+ UNREACHABLE(); |
+} |
+ |
+ |
+void CodeGenerator::VisitAssignableNode(AssignableNode* node) { |
+ ASSERT(FLAG_enable_type_checks); |
+ node->expr()->Visit(this); |
+ __ popq(RAX); |
+ GenerateAssertAssignable(node->id(), |
+ node->token_index(), |
+ node->type(), |
+ node->dst_name()); |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitClosureNode(ClosureNode* node) { |
+ const Function& function = node->function(); |
+ if (function.IsNonImplicitClosureFunction()) { |
+ const int current_context_level = state()->context_level(); |
+ const ContextScope& context_scope = ContextScope::ZoneHandle( |
+ node->scope()->PreserveOuterScope(current_context_level)); |
+ ASSERT(!function.HasCode()); |
+ ASSERT(function.context_scope() == ContextScope::null()); |
+ function.set_context_scope(context_scope); |
+ } else { |
+ ASSERT(function.context_scope() != ContextScope::null()); |
+ if (function.IsImplicitInstanceClosureFunction()) { |
+ node->receiver()->Visit(this); |
+ } |
+ } |
+ // The function type of a closure may have type arguments. In that case, pass |
+ // the type arguments of the instantiator. |
+ const Class& cls = Class::Handle(function.signature_class()); |
+ ASSERT(!cls.IsNull()); |
+ const bool requires_type_arguments = cls.HasTypeArguments(); |
+ if (requires_type_arguments) { |
+ ASSERT(!function.IsImplicitStaticClosureFunction()); |
+ GenerateInstantiatorTypeArguments(node->token_index()); |
+ } |
+ const Code& stub = Code::Handle( |
+ StubCode::GetAllocationStubForClosure(function)); |
+ const ExternalLabel label(function.ToCString(), stub.EntryPoint()); |
+ GenerateCall(node->token_index(), &label); |
+ if (requires_type_arguments) { |
+ __ popq(RCX); // Pop type arguments. |
+ } |
+ if (function.IsImplicitInstanceClosureFunction()) { |
+ __ popq(RCX); // Pop receiver. |
+ } |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitPrimaryNode(PrimaryNode* node) { |
+ // PrimaryNodes are temporary during parsing. |
+ ErrorMsg(node->token_index(), |
+ "Unexpected primary node: %s", node->primary().ToCString()); |
+} |
+ |
+ |
+void CodeGenerator::VisitCloneContextNode(CloneContextNode *node) { |
+ const Context& result = Context::ZoneHandle(); |
+ __ PushObject(result); |
+ __ pushq(CTX); |
+ GenerateCallRuntime(node->id(), |
+ node->token_index(), kCloneContextRuntimeEntry); |
+ __ popq(RAX); |
+ __ popq(CTX); // result: cloned context. Set as current context. |
+} |
+ |
+ |
+void CodeGenerator::VisitSequenceNode(SequenceNode* node_sequence) { |
+ CodeGeneratorState codegen_state(this); |
+ LocalScope* scope = node_sequence->scope(); |
+ const intptr_t num_context_variables = |
+ (scope != NULL) ? scope->num_context_variables() : 0; |
+ if (num_context_variables > 0) { |
+ // The loop local scope declares variables that are captured. |
+ // Allocate and chain a new context. |
+ __ movq(R10, Immediate(num_context_variables)); |
+ const ExternalLabel label("alloc_context", |
+ StubCode::AllocateContextEntryPoint()); |
+ GenerateCall(node_sequence->token_index(), &label); |
+ |
+ // Chain the new context in RAX to its parent in CTX. |
+ __ movq(FieldAddress(RAX, Context::parent_offset()), CTX); |
+ // Set new context as current context. |
+ __ movq(CTX, RAX); |
+ state()->set_context_level(scope->context_level()); |
+ |
+ // If this node_sequence is the body of the function being compiled, copy |
+ // the captured parameters from the frame into the context. |
+ if (node_sequence == parsed_function_.node_sequence()) { |
+ ASSERT(scope->context_level() == 1); |
+ const Immediate raw_null = |
+ Immediate(reinterpret_cast<intptr_t>(Object::null())); |
+ const Function& function = parsed_function_.function(); |
+ const int num_params = function.NumberOfParameters(); |
+ int param_frame_index = |
+ (num_params == function.num_fixed_parameters()) ? 1 + num_params : -1; |
+ for (int pos = 0; pos < num_params; param_frame_index--, pos++) { |
+ LocalVariable* parameter = scope->VariableAt(pos); |
+ ASSERT(parameter->owner() == scope); |
+ if (parameter->is_captured()) { |
+ // Copy parameter from local frame to current context. |
+ const Address local_addr(RBP, param_frame_index * kWordSize); |
+ __ movq(RAX, local_addr); |
+ GenerateStoreVariable(*parameter, RAX, R10); |
+ // Write NULL to the source location to detect buggy accesses and |
+ // allow GC of passed value if it gets overwritten by a new value in |
+ // the function. |
+ __ movq(local_addr, raw_null); |
+ } |
+ } |
+ } |
+ } |
+ // If this node_sequence is the body of the function being compiled, generate |
+ // code checking the type of the actual arguments. |
+ if (FLAG_enable_type_checks && |
+ (node_sequence == parsed_function_.node_sequence())) { |
+ GenerateArgumentTypeChecks(); |
+ } |
+ for (int i = 0; i < node_sequence->length(); i++) { |
+ AstNode* child_node = node_sequence->NodeAt(i); |
+ state()->set_root_node(child_node); |
+ child_node->Visit(this); |
+ } |
+ if (node_sequence->label() != NULL) { |
+ __ Bind(node_sequence->label()->break_label()); |
+ } |
+ if (num_context_variables > 0) { |
+ // Unchain the previously allocated context. |
+ __ movq(CTX, FieldAddress(CTX, Context::parent_offset())); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitArgumentListNode(ArgumentListNode* arguments) { |
+ for (int i = 0; i < arguments->length(); i++) { |
+ AstNode* argument = arguments->NodeAt(i); |
+ argument->Visit(this); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitArrayNode(ArrayNode* node) { |
+ // Evaluate the array elements. |
+ for (int i = 0; i < node->length(); i++) { |
+ AstNode* element = node->ElementAt(i); |
+ element->Visit(this); |
+ } |
+ |
+ // Allocate the array. |
+ // R10 : Array length as Smi. |
+ // RBX : element type for the array. |
+ __ movq(R10, Immediate(Smi::RawValue(node->length()))); |
+ const AbstractTypeArguments& element_type = node->type_arguments(); |
+ ASSERT(element_type.IsNull() || element_type.IsInstantiated()); |
+ __ LoadObject(RBX, element_type); |
+ GenerateCall(node->token_index(), &StubCode::AllocateArrayLabel()); |
+ |
+ // Pop the element values from the stack into the array. |
+ __ leaq(RCX, FieldAddress(RAX, Array::data_offset())); |
+ for (int i = node->length() - 1; i >= 0; i--) { |
+ __ popq(Address(RCX, i * kWordSize)); |
+ } |
+ |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitLoadLocalNode(LoadLocalNode* node) { |
+ // Load the value of the local variable and push it onto the expression stack. |
+ if (IsResultNeeded(node)) { |
+ GeneratePushVariable(node->local(), RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitStoreLocalNode(StoreLocalNode* node) { |
+ node->value()->Visit(this); |
+ __ popq(RAX); |
+ if (FLAG_enable_type_checks) { |
+ GenerateAssertAssignable(node->id(), |
+ node->value()->token_index(), |
+ node->local().type(), |
+ node->local().name()); |
+ } |
+ GenerateStoreVariable(node->local(), RAX, R10); |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitLoadInstanceFieldNode(LoadInstanceFieldNode* node) { |
+ node->instance()->Visit(this); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ __ popq(RAX); // Instance. |
+ __ movq(RAX, FieldAddress(RAX, node->field().Offset())); |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitStoreInstanceFieldNode(StoreInstanceFieldNode* node) { |
+ node->instance()->Visit(this); |
+ node->value()->Visit(this); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ __ popq(RAX); // Value. |
+ if (FLAG_enable_type_checks) { |
+ GenerateAssertAssignable(node->id(), |
+ node->value()->token_index(), |
+ AbstractType::ZoneHandle(node->field().type()), |
+ String::ZoneHandle(node->field().name())); |
+ } |
+ __ popq(R10); // Instance. |
+ __ StoreIntoObject(R10, FieldAddress(R10, node->field().Offset()), RAX); |
+ if (IsResultNeeded(node)) { |
+ // The result is the input value. |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+// Expects array and index on stack and returns result in RAX. |
+void CodeGenerator::GenerateLoadIndexed(intptr_t node_id, |
+ intptr_t token_index) { |
+ // Invoke the [] operator on the receiver object with the index as argument. |
+ const String& operator_name = |
+ String::ZoneHandle(String::NewSymbol(Token::Str(Token::kINDEX))); |
+ const int kNumArguments = 2; // Receiver and index. |
+ const Array& kNoArgumentNames = Array::Handle(); |
+ const int kNumArgumentsChecked = 1; |
+ GenerateInstanceCall(node_id, |
+ token_index, |
+ operator_name, |
+ kNumArguments, |
+ kNoArgumentNames, |
+ kNumArgumentsChecked); |
+} |
+ |
+ |
+void CodeGenerator::VisitLoadIndexedNode(LoadIndexedNode* node) { |
+ node->array()->Visit(this); |
+ // Now compute the index. |
+ node->index_expr()->Visit(this); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ GenerateLoadIndexed(node->id(), node->token_index()); |
+ // Result is in RAX. |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+// Expected arguments. |
+// TOS(0): value. |
+// TOS(1): index. |
+// TOS(2): array. |
+void CodeGenerator::GenerateStoreIndexed(intptr_t node_id, |
+ intptr_t token_index, |
+ bool preserve_value) { |
+ // It is not necessary to generate a type test of the assigned value here, |
+ // because the []= operator will check the type of its incoming arguments. |
+ if (preserve_value) { |
+ __ popq(RAX); |
+ __ popq(RDX); |
+ __ popq(RCX); |
+ __ pushq(RAX); // Preserve stored value. |
+ __ pushq(RCX); // Restore arguments. |
+ __ pushq(RDX); |
+ __ pushq(RAX); |
+ } |
+ // Invoke the []= operator on the receiver object with index and |
+ // value as arguments. |
+ const String& operator_name = |
+ String::ZoneHandle(String::NewSymbol(Token::Str(Token::kASSIGN_INDEX))); |
+ const int kNumArguments = 3; // Receiver, index and value. |
+ const Array& kNoArgumentNames = Array::Handle(); |
+ const int kNumArgumentsChecked = 1; |
+ GenerateInstanceCall(node_id, |
+ token_index, |
+ operator_name, |
+ kNumArguments, |
+ kNoArgumentNames, |
+ kNumArgumentsChecked); |
+} |
+ |
+ |
+void CodeGenerator::VisitStoreIndexedNode(StoreIndexedNode* node) { |
+ // Compute the receiver object and pass as first argument to call. |
+ node->array()->Visit(this); |
+ // Now compute the index. |
+ node->index_expr()->Visit(this); |
+ // Finally compute the value to assign. |
+ node->value()->Visit(this); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ GenerateStoreIndexed(node->id(), node->token_index(), IsResultNeeded(node)); |
+} |
+ |
+ |
+void CodeGenerator::VisitLoadStaticFieldNode(LoadStaticFieldNode* node) { |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ __ LoadObject(RDX, node->field()); |
+ __ movq(RAX, FieldAddress(RDX, Field::value_offset())); |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitStoreStaticFieldNode(StoreStaticFieldNode* node) { |
+ node->value()->Visit(this); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ __ popq(RAX); // Value. |
+ if (FLAG_enable_type_checks) { |
+ GenerateAssertAssignable(node->id(), |
+ node->value()->token_index(), |
+ AbstractType::ZoneHandle(node->field().type()), |
+ String::ZoneHandle(node->field().name())); |
+ } |
+ __ LoadObject(RDX, node->field()); |
+ __ StoreIntoObject(RDX, FieldAddress(RDX, Field::value_offset()), RAX); |
+ if (IsResultNeeded(node)) { |
+ // The result is the input value. |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::GenerateLogicalNotOp(UnaryOpNode* node) { |
+ // Generate false if operand is true, otherwise generate true. |
+ const Bool& bool_true = Bool::ZoneHandle(Bool::True()); |
+ const Bool& bool_false = Bool::ZoneHandle(Bool::False()); |
+ node->operand()->Visit(this); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ Label done; |
+ GenerateConditionTypeCheck(node->id(), node->operand()->token_index()); |
+ __ popq(RDX); |
+ __ LoadObject(RAX, bool_true); |
+ __ cmpq(RAX, RDX); |
+ __ j(NOT_EQUAL, &done, Assembler::kNearJump); |
+ __ LoadObject(RAX, bool_false); |
+ __ Bind(&done); |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitUnaryOpNode(UnaryOpNode* node) { |
+ if (node->kind() == Token::kNOT) { |
+ // "!" cannot be overloaded, therefore inline it. |
+ GenerateLogicalNotOp(node); |
+ return; |
+ } |
+ node->operand()->Visit(this); |
+ if (node->kind() == Token::kADD) { |
+ // Unary operator '+' does not exist, it's a NOP, skip it. |
+ if (!IsResultNeeded(node)) { |
+ __ popq(RAX); |
+ } |
+ return; |
+ } |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ String& operator_name = String::ZoneHandle(); |
+ if (node->kind() == Token::kSUB) { |
+ operator_name = String::NewSymbol(Token::Str(Token::kNEGATE)); |
+ } else { |
+ operator_name = String::NewSymbol(node->Name()); |
+ } |
+ const int kNumberOfArguments = 1; |
+ const Array& kNoArgumentNames = Array::Handle(); |
+ const int kNumArgumentsChecked = 1; |
+ GenerateInstanceCall(node->id(), |
+ node->token_index(), |
+ operator_name, |
+ kNumberOfArguments, |
+ kNoArgumentNames, |
+ kNumArgumentsChecked); |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitIncrOpLocalNode(IncrOpLocalNode* node) { |
+ ASSERT((node->kind() == Token::kINCR) || (node->kind() == Token::kDECR)); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ GenerateLoadVariable(RAX, node->local()); |
+ if (!node->prefix() && IsResultNeeded(node)) { |
+ // Preserve as result. |
+ __ pushq(RAX); |
+ } |
+ const Immediate value = Immediate(reinterpret_cast<int64_t>(Smi::New(1))); |
+ const char* operator_name = (node->kind() == Token::kINCR) ? "+" : "-"; |
+ __ pushq(RAX); |
+ __ pushq(value); |
+ GenerateBinaryOperatorCall(node->id(), node->token_index(), operator_name); |
+ // result is in RAX. |
+ if (FLAG_enable_type_checks) { |
+ GenerateAssertAssignable(node->id(), |
+ node->token_index(), |
+ node->local().type(), |
+ node->local().name()); |
+ } |
+ GenerateStoreVariable(node->local(), RAX, RDX); |
+ if (node->prefix() && IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitIncrOpInstanceFieldNode( |
+ IncrOpInstanceFieldNode* node) { |
+ ASSERT((node->kind() == Token::kINCR) || (node->kind() == Token::kDECR)); |
+ node->receiver()->Visit(this); |
+ __ pushq(Address(RSP, 0)); // Duplicate receiver (preserve for setter). |
+ MarkDeoptPoint(node->getter_id(), node->token_index()); |
+ GenerateInstanceGetterCall(node->getter_id(), |
+ node->token_index(), |
+ node->field_name()); |
+ // result is in RAX. |
+ __ popq(RDX); // Get receiver. |
+ if (!node->prefix() && IsResultNeeded(node)) { |
+ // Preserve as result. |
+ __ pushq(RAX); // Preserve value as result. |
+ } |
+ const Immediate one_value = Immediate(reinterpret_cast<int64_t>(Smi::New(1))); |
+ const char* operator_name = (node->kind() == Token::kINCR) ? "+" : "-"; |
+ // RAX: Value. |
+ // RDX: Receiver. |
+ __ pushq(RDX); // Preserve receiver. |
+ __ pushq(RAX); // Left operand. |
+ __ pushq(one_value); // Right operand. |
+ GenerateBinaryOperatorCall(node->operator_id(), |
+ node->token_index(), |
+ operator_name); |
+ __ popq(RDX); // Restore receiver. |
+ if (IsResultNeeded(node) && node->prefix()) { |
+ // Value stored into field is the result. |
+ __ pushq(RAX); |
+ } |
+ __ pushq(RDX); // Receiver. |
+ __ pushq(RAX); // Value. |
+ // It is not necessary to generate a type test of the assigned value here, |
+ // because the setter will check the type of its incoming arguments. |
+ GenerateInstanceSetterCall(node->setter_id(), |
+ node->token_index(), |
+ node->field_name()); |
+} |
+ |
+ |
+void CodeGenerator::VisitIncrOpStaticFieldNode(IncrOpStaticFieldNode* node) { |
+ ASSERT((node->kind() == Token::kINCR) || (node->kind() == Token::kDECR)); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ if (node->field().IsNull()) { |
+ GenerateStaticGetterCall(node->token_index(), |
+ node->field_class(), |
+ node->field_name()); |
+ } else { |
+ __ LoadObject(RDX, node->field()); |
+ __ movq(RAX, FieldAddress(RDX, Field::value_offset())); |
+ } |
+ // Value in RAX. |
+ if (!node->prefix() && IsResultNeeded(node)) { |
+ // Preserve as result. |
+ __ pushq(RAX); |
+ } |
+ const Immediate value = Immediate(reinterpret_cast<int64_t>(Smi::New(1))); |
+ const char* operator_name = (node->kind() == Token::kINCR) ? "+" : "-"; |
+ __ pushq(RAX); // Left operand. |
+ __ pushq(value); // Right operand. |
+ GenerateBinaryOperatorCall(node->id(), node->token_index(), operator_name); |
+ // result is in RAX. |
+ if (node->prefix() && IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+ if (node->field().IsNull()) { |
+ __ pushq(RAX); |
+ // It is not necessary to generate a type test of the assigned value here, |
+ // because the setter will check the type of its incoming arguments. |
+ GenerateStaticSetterCall(node->token_index(), |
+ node->field_class(), |
+ node->field_name()); |
+ } else { |
+ if (FLAG_enable_type_checks) { |
+ GenerateAssertAssignable(node->id(), |
+ node->token_index(), |
+ AbstractType::ZoneHandle(node->field().type()), |
+ String::ZoneHandle(node->field().name())); |
+ } |
+ __ LoadObject(RDX, node->field()); |
+ __ StoreIntoObject(RDX, FieldAddress(RDX, Field::value_offset()), RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitIncrOpIndexedNode(IncrOpIndexedNode* node) { |
+ ASSERT((node->kind() == Token::kINCR) || (node->kind() == Token::kDECR)); |
+ node->array()->Visit(this); |
+ node->index()->Visit(this); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ // Preserve array and index for GenerateStoreIndex. |
+ __ pushq(Address(RSP, kWordSize)); // Copy array. |
+ __ pushq(Address(RSP, kWordSize)); // Copy index. |
+ GenerateLoadIndexed(node->load_id(), node->token_index()); |
+ // Result is in RAX. |
+ if (!node->prefix() && IsResultNeeded(node)) { |
+ // Preserve RAX as result. |
+ __ popq(RDX); // Preserved index -> RDX. |
+ __ popq(RCX); // Preserved array -> RCX. |
+ __ pushq(RAX); // Preserve original value from indexed load. |
+ __ pushq(RCX); // Array. |
+ __ pushq(RDX); // Index. |
+ } |
+ const Immediate value = Immediate(reinterpret_cast<int64_t>(Smi::New(1))); |
+ const char* operator_name = (node->kind() == Token::kINCR) ? "+" : "-"; |
+ __ pushq(RAX); // Left operand. |
+ __ pushq(value); // Right operand. |
+ GenerateBinaryOperatorCall(node->operator_id(), |
+ node->token_index(), |
+ operator_name); |
+ __ pushq(RAX); |
+ // TOS(0): value, TOS(1): index, TOS(2): array. |
+ GenerateStoreIndexed(node->store_id(), |
+ node->token_index(), |
+ node->prefix() && IsResultNeeded(node)); |
+} |
+ |
+ |
+static const Class* CoreClass(const char* c_name) { |
+ const String& class_name = String::Handle(String::NewSymbol(c_name)); |
+ const Class& cls = Class::ZoneHandle(Library::Handle( |
+ Library::CoreImplLibrary()).LookupClass(class_name)); |
+ ASSERT(!cls.IsNull()); |
+ return &cls; |
+} |
+ |
+ |
+// Optimize instanceof type test by adding inlined tests for: |
+// - NULL -> return false. |
+// - Smi -> compile time subtype check (only if dst class is not parameterized). |
+// - Class equality (only if class is not parameterized). |
+// Inputs: |
+// - RAX: object. |
+// Destroys RCX. |
+// Returns: |
+// - true or false on stack. |
+void CodeGenerator::GenerateInstanceOf(intptr_t node_id, |
+ intptr_t token_index, |
+ const AbstractType& type, |
+ bool negate_result) { |
+ ASSERT(type.IsFinalized()); |
+ const Bool& bool_true = Bool::ZoneHandle(Bool::True()); |
+ const Bool& bool_false = Bool::ZoneHandle(Bool::False()); |
+ |
+ // All instances are of a subtype of the Object type. |
+ const Type& object_type = |
+ Type::Handle(Isolate::Current()->object_store()->object_type()); |
+ if (type.IsInstantiated() && object_type.IsSubtypeOf(type)) { |
+ __ PushObject(negate_result ? bool_false : bool_true); |
+ return; |
+ } |
+ |
+ const Immediate raw_null = |
+ Immediate(reinterpret_cast<intptr_t>(Object::null())); |
+ Label done; |
+ // If type is instantiated and non-parameterized, we can inline code |
+ // checking whether the tested instance is a Smi. |
+ if (type.IsInstantiated()) { |
+ // A null object is only an instance of Object and Dynamic, which has |
+ // already been checked above (if the type is instantiated). So we can |
+ // return false here if the instance is null (and if the type is |
+ // instantiated). |
+ // We can only inline this null check if the type is instantiated at compile |
+ // time, since an uninstantiated type at compile time could be Object or |
+ // Dynamic at run time. |
+ Label non_null; |
+ __ cmpq(RAX, raw_null); |
+ __ j(NOT_EQUAL, &non_null, Assembler::kNearJump); |
+ __ PushObject(negate_result ? bool_true : bool_false); |
+ __ jmp(&done, Assembler::kNearJump); |
+ |
+ __ Bind(&non_null); |
+ |
+ const Class& type_class = Class::ZoneHandle(type.type_class()); |
+ const bool requires_type_arguments = type_class.HasTypeArguments(); |
+ // A Smi object cannot be the instance of a parameterized class. |
+ // A class equality check is only applicable with a dst type of a |
+ // non-parameterized class or with a raw dst type of a parameterized class. |
+ if (requires_type_arguments) { |
+ const AbstractTypeArguments& type_arguments = |
+ AbstractTypeArguments::Handle(type.arguments()); |
+ const bool is_raw_type = type_arguments.IsNull() || |
+ type_arguments.IsDynamicTypes(type_arguments.Length()); |
+ Label runtime_call; |
+ __ testq(RAX, Immediate(kSmiTagMask)); |
+ __ j(ZERO, &runtime_call, Assembler::kNearJump); |
+ // Object not Smi. |
+ if (is_raw_type) { |
+ if (type.IsListInterface()) { |
+ Label push_result; |
+ // TODO(srdjan) also accept List<Object>. |
+ __ movq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ __ CompareObject(RCX, *CoreClass("ObjectArray")); |
+ __ j(EQUAL, &push_result, Assembler::kNearJump); |
+ __ CompareObject(RCX, *CoreClass("GrowableObjectArray")); |
+ __ j(NOT_EQUAL, &runtime_call, Assembler::kNearJump); |
+ __ Bind(&push_result); |
+ __ PushObject(negate_result ? bool_false : bool_true); |
+ __ jmp(&done, Assembler::kNearJump); |
+ } else if (!type_class.is_interface()) { |
+ __ movq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ __ CompareObject(RCX, type_class); |
+ __ j(NOT_EQUAL, &runtime_call, Assembler::kNearJump); |
+ __ PushObject(negate_result ? bool_false : bool_true); |
+ __ jmp(&done, Assembler::kNearJump); |
+ } |
+ } |
+ __ Bind(&runtime_call); |
+ // Fall through to runtime call. |
+ } else { |
+ Label compare_classes; |
+ __ testq(RAX, Immediate(kSmiTagMask)); |
+ __ j(NOT_ZERO, &compare_classes, Assembler::kNearJump); |
+ // Object is Smi. |
+ const Class& smi_class = Class::Handle(Smi::Class()); |
+ // TODO(regis): We should introduce a SmiType. |
+ if (smi_class.IsSubtypeOf(TypeArguments::Handle(), |
+ type_class, |
+ TypeArguments::Handle())) { |
+ __ PushObject(negate_result ? bool_false : bool_true); |
+ } else { |
+ __ PushObject(negate_result ? bool_true : bool_false); |
+ } |
+ __ jmp(&done, Assembler::kNearJump); |
+ |
+ // Compare if the classes are equal. |
+ __ Bind(&compare_classes); |
+ const Class* compare_class = NULL; |
+ if (type.IsStringInterface()) { |
+ compare_class = &Class::ZoneHandle( |
+ Isolate::Current()->object_store()->one_byte_string_class()); |
+ } else if (type.IsBoolInterface()) { |
+ compare_class = &Class::ZoneHandle( |
+ Isolate::Current()->object_store()->bool_class()); |
+ } else if (!type_class.is_interface()) { |
+ compare_class = &type_class; |
+ } |
+ if (compare_class != NULL) { |
+ Label runtime_call; |
+ __ movq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ __ CompareObject(RCX, *compare_class); |
+ __ j(NOT_EQUAL, &runtime_call, Assembler::kNearJump); |
+ __ PushObject(negate_result ? bool_false : bool_true); |
+ __ jmp(&done, Assembler::kNearJump); |
+ __ Bind(&runtime_call); |
+ } |
+ } |
+ } |
+ const Object& result = Object::ZoneHandle(); |
+ __ PushObject(result); // Make room for the result of the runtime call. |
+ __ pushq(RAX); // Push the instance. |
+ __ PushObject(type); // Push the type. |
+ if (!type.IsInstantiated()) { |
+ GenerateInstantiatorTypeArguments(token_index); |
+ } else { |
+ __ pushq(raw_null); // Null instantiator. |
+ } |
+ GenerateCallRuntime(node_id, token_index, kInstanceofRuntimeEntry); |
+ // Pop the two parameters supplied to the runtime entry. The result of the |
+ // instanceof runtime call will be left as the result of the operation. |
+ __ addq(RSP, Immediate(3 * kWordSize)); |
+ if (negate_result) { |
+ Label negate_done; |
+ __ popq(RDX); |
+ __ LoadObject(RAX, bool_true); |
+ __ cmpq(RDX, RAX); |
+ __ j(NOT_EQUAL, &negate_done, Assembler::kNearJump); |
+ __ LoadObject(RAX, bool_false); |
+ __ Bind(&negate_done); |
+ __ pushq(RAX); |
+ } |
+ __ Bind(&done); |
+} |
+ |
+ |
+// Jumps to label if RCX equals the given class. |
+// Inputs: |
+// - RCX: tested class. |
+void CodeGenerator::TestClassAndJump(const Class& cls, Label* label) { |
+ __ CompareObject(RCX, cls); |
+ __ j(EQUAL, label); |
+} |
+ |
+ |
+// Optimize assignable type check by adding inlined tests for: |
+// - NULL -> return NULL. |
+// - Smi -> compile time subtype check (only if dst class is not parameterized). |
+// - Class equality (only if class is not parameterized). |
+// Inputs: |
+// - RAX: object. |
+// Destroys RCX and RDX. |
+// Returns: |
+// - object in RAX for successful assignable check (or throws TypeError). |
+void CodeGenerator::GenerateAssertAssignable(intptr_t node_id, |
+ intptr_t token_index, |
+ const AbstractType& dst_type, |
+ const String& dst_name) { |
+ ASSERT(FLAG_enable_type_checks); |
+ ASSERT(token_index >= 0); |
+ ASSERT(!dst_type.IsNull()); |
+ ASSERT(dst_type.IsFinalized()); |
+ |
+ // Any expression is assignable to the Dynamic type and to the Object type. |
+ // Skip the test. |
+ if (dst_type.IsDynamicType() || dst_type.IsObjectType()) { |
+ return; |
+ } |
+ |
+ // It is a compile-time error to explicitly return a value (including null) |
+ // from a void function. However, functions that do not explicitly return a |
+ // value, implicitly return null. This includes void functions. Therefore, we |
+ // skip the type test here and trust the parser to only return null in void |
+ // function. |
+ if (dst_type.IsVoidType()) { |
+ return; |
+ } |
+ |
+ // A NULL object is always assignable and is returned as result. |
+ const Immediate raw_null = |
+ Immediate(reinterpret_cast<intptr_t>(Object::null())); |
+ Label done, runtime_call; |
+ __ cmpq(RAX, raw_null); |
+ __ j(EQUAL, &done); |
+ |
+ // If dst_type is instantiated and non-parameterized, we can inline code |
+ // checking whether the assigned instance is a Smi. |
+ if (dst_type.IsInstantiated()) { |
+ const Class& dst_type_class = Class::ZoneHandle(dst_type.type_class()); |
+ const bool dst_class_has_type_arguments = dst_type_class.HasTypeArguments(); |
+ // A Smi object cannot be the instance of a parameterized class. |
+ // A class equality check is only applicable with a dst type of a |
+ // non-parameterized class or with a raw dst type of a parameterized class. |
+ if (dst_class_has_type_arguments) { |
+ const AbstractTypeArguments& dst_type_arguments = |
+ AbstractTypeArguments::Handle(dst_type.arguments()); |
+ const bool is_raw_dst_type = dst_type_arguments.IsNull() || |
+ dst_type_arguments.IsDynamicTypes(dst_type_arguments.Length()); |
+ if (is_raw_dst_type) { |
+ // Dynamic type argument, check only classes. |
+ if (dst_type.IsListInterface()) { |
+ // TODO(srdjan) also accept List<Object>. |
+ __ testq(RAX, Immediate(kSmiTagMask)); |
+ __ j(ZERO, &runtime_call); |
+ __ movq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ TestClassAndJump(*CoreClass("ObjectArray"), &done); |
+ TestClassAndJump(*CoreClass("GrowableObjectArray"), &done); |
+ } else if (!dst_type_class.is_interface()) { |
+ __ testq(RAX, Immediate(kSmiTagMask)); |
+ __ j(ZERO, &runtime_call); |
+ __ movq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ TestClassAndJump(dst_type_class, &done); |
+ } |
+ // Fall through to runtime class. |
+ } |
+ } else { // dst_type has NO type arguments. |
+ Label compare_classes; |
+ __ testq(RAX, Immediate(kSmiTagMask)); |
+ __ j(NOT_ZERO, &compare_classes); |
+ // Object is Smi. |
+ const Class& smi_class = Class::Handle(Smi::Class()); |
+ // TODO(regis): We should introduce a SmiType. |
+ if (smi_class.IsSubtypeOf(TypeArguments::Handle(), |
+ dst_type_class, |
+ TypeArguments::Handle())) { |
+ // Successful assignable type check: return object in RAX. |
+ __ jmp(&done); |
+ } else { |
+ // Failed assignable type check: call runtime to throw TypeError. |
+ __ jmp(&runtime_call); |
+ } |
+ // Compare if the classes are equal. |
+ __ Bind(&compare_classes); |
+ // If dst_type is an interface, we can skip the class equality check, |
+ // because instances cannot be of an interface type. |
+ if (!dst_type_class.is_interface()) { |
+ __ movq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ TestClassAndJump(dst_type_class, &done); |
+ } else { |
+ // However, for specific core library interfaces, we can check for |
+ // specific core library classes. |
+ if (dst_type.IsBoolInterface()) { |
+ __ movq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ const Class& bool_class = Class::ZoneHandle( |
+ Isolate::Current()->object_store()->bool_class()); |
+ TestClassAndJump(bool_class, &done); |
+ } else if (dst_type.IsSubtypeOf( |
+ Type::Handle(Type::NumberInterface()))) { |
+ __ movq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ if (dst_type.IsIntInterface() || dst_type.IsNumberInterface()) { |
+ // We already checked for Smi above. |
+ const Class& mint_class = Class::ZoneHandle( |
+ Isolate::Current()->object_store()->mint_class()); |
+ TestClassAndJump(mint_class, &done); |
+ const Class& bigint_class = Class::ZoneHandle( |
+ Isolate::Current()->object_store()->bigint_class()); |
+ TestClassAndJump(bigint_class, &done); |
+ } |
+ if (dst_type.IsDoubleInterface() || dst_type.IsNumberInterface()) { |
+ const Class& double_class = Class::ZoneHandle( |
+ Isolate::Current()->object_store()->double_class()); |
+ TestClassAndJump(double_class, &done); |
+ } |
+ } else if (dst_type.IsStringInterface()) { |
+ __ movq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ const Class& one_byte_string_class = Class::ZoneHandle( |
+ Isolate::Current()->object_store()->one_byte_string_class()); |
+ TestClassAndJump(one_byte_string_class, &done); |
+ const Class& two_byte_string_class = Class::ZoneHandle( |
+ Isolate::Current()->object_store()->two_byte_string_class()); |
+ TestClassAndJump(two_byte_string_class, &done); |
+ const Class& four_byte_string_class = Class::ZoneHandle( |
+ Isolate::Current()->object_store()->four_byte_string_class()); |
+ TestClassAndJump(four_byte_string_class, &done); |
+ } else if (dst_type.IsFunctionInterface()) { |
+ __ movq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ __ movq(RCX, FieldAddress(RCX, Class::signature_function_offset())); |
+ __ cmpq(RCX, raw_null); |
+ __ j(NOT_EQUAL, &done); |
+ } |
+ } |
+ } |
+ } |
+ __ Bind(&runtime_call); |
+ const Object& result = Object::ZoneHandle(); |
+ __ PushObject(result); // Make room for the result of the runtime call. |
+ const Immediate location = |
+ Immediate(reinterpret_cast<int64_t>(Smi::New(token_index))); |
+ __ pushq(location); // Push the source location. |
+ __ pushq(RAX); // Push the source object. |
+ __ PushObject(dst_type); // Push the type of the destination. |
+ if (!dst_type.IsInstantiated()) { |
+ GenerateInstantiatorTypeArguments(token_index); |
+ } else { |
+ __ pushq(raw_null); // Null instantiator. |
+ } |
+ __ PushObject(dst_name); // Push the name of the destination. |
+ GenerateCallRuntime(node_id, token_index, kTypeCheckRuntimeEntry); |
+ // Pop the parameters supplied to the runtime entry. The result of the |
+ // type check runtime call is the checked value. |
+ __ addq(RSP, Immediate(5 * kWordSize)); |
+ __ popq(RAX); |
+ |
+ __ Bind(&done); |
+} |
+ |
+ |
+void CodeGenerator::GenerateArgumentTypeChecks() { |
+ const Function& function = parsed_function_.function(); |
+ LocalScope* scope = parsed_function_.node_sequence()->scope(); |
+ const int num_fixed_params = function.num_fixed_parameters(); |
+ const int num_opt_params = function.num_optional_parameters(); |
+ ASSERT(num_fixed_params + num_opt_params <= scope->num_variables()); |
+ for (int i = 0; i < num_fixed_params + num_opt_params; i++) { |
+ LocalVariable* parameter = scope->VariableAt(i); |
+ GenerateLoadVariable(RAX, *parameter); |
+ GenerateAssertAssignable(AstNode::kNoId, |
+ parameter->token_index(), |
+ parameter->type(), |
+ parameter->name()); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::GenerateConditionTypeCheck(intptr_t node_id, |
+ intptr_t token_index) { |
+ if (!FLAG_enable_type_checks) { |
+ return; |
+ } |
+ |
+ // Check that the type of the object on the stack is allowed in conditional |
+ // context. |
+ // Call the runtime if the object is null or not of type bool. |
+ const Immediate raw_null = |
+ Immediate(reinterpret_cast<intptr_t>(Object::null())); |
+ Label runtime_call, done; |
+ __ movq(RAX, Address(RSP, 0)); |
+ __ cmpq(RAX, raw_null); |
+ __ j(EQUAL, &runtime_call, Assembler::kNearJump); |
+ __ testq(RAX, Immediate(kSmiTagMask)); |
+ __ j(ZERO, &runtime_call, Assembler::kNearJump); // Call runtime for Smi. |
+ // This check should pass if the receiver's class implements the interface |
+ // 'bool'. Check only class 'Bool' since it is the only legal implementation |
+ // of the interface 'bool'. |
+ const Class& bool_class = |
+ Class::ZoneHandle(Isolate::Current()->object_store()->bool_class()); |
+ __ movq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ __ CompareObject(RCX, bool_class); |
+ __ j(EQUAL, &done, Assembler::kNearJump); |
+ |
+ __ Bind(&runtime_call); |
+ const Object& result = Object::ZoneHandle(); |
+ __ PushObject(result); // Make room for the result of the runtime call. |
+ const Immediate location = |
+ Immediate(reinterpret_cast<int64_t>(Smi::New(token_index))); |
+ __ pushq(location); // Push the source location. |
+ __ pushq(RAX); // Push the source object. |
+ GenerateCallRuntime(node_id, token_index, kConditionTypeErrorRuntimeEntry); |
+ // Pop the parameters supplied to the runtime entry. The result of the |
+ // type check runtime call is the checked value. |
+ __ addq(RSP, Immediate(3 * kWordSize)); |
+ |
+ __ Bind(&done); |
+} |
+ |
+ |
+void CodeGenerator::VisitComparisonNode(ComparisonNode* node) { |
+ const Bool& bool_true = Bool::ZoneHandle(Bool::True()); |
+ const Bool& bool_false = Bool::ZoneHandle(Bool::False()); |
+ node->left()->Visit(this); |
+ |
+ // The instanceof operator needs special handling. |
+ if (Token::IsInstanceofOperator(node->kind())) { |
+ __ popq(RAX); // Left operand. |
+ ASSERT(node->right()->IsTypeNode()); |
+ GenerateInstanceOf(node->id(), |
+ node->token_index(), |
+ node->right()->AsTypeNode()->type(), |
+ (node->kind() == Token::kISNOT)); |
+ if (!IsResultNeeded(node)) { |
+ __ popq(RAX); // Pop the result of the instanceof operation. |
+ } |
+ return; |
+ } |
+ |
+ node->right()->Visit(this); |
+ // Both left and right values on stack. |
+ |
+ // '===' and '!==' are not overloadable. |
+ if ((node->kind() == Token::kEQ_STRICT) || |
+ (node->kind() == Token::kNE_STRICT)) { |
+ __ popq(RDX); // Right operand. |
+ __ popq(RAX); // Left operand. |
+ if (!IsResultNeeded(node)) { |
+ return; |
+ } |
+ Label load_true, done; |
+ __ cmpq(RAX, RDX); |
+ if (node->kind() == Token::kEQ_STRICT) { |
+ __ j(EQUAL, &load_true, Assembler::kNearJump); |
+ } else { |
+ __ j(NOT_EQUAL, &load_true, Assembler::kNearJump); |
+ } |
+ __ LoadObject(RAX, bool_false); |
+ __ jmp(&done, Assembler::kNearJump); |
+ __ Bind(&load_true); |
+ __ LoadObject(RAX, bool_true); |
+ __ Bind(&done); |
+ // Result is in RAX. |
+ __ pushq(RAX); |
+ return; |
+ } |
+ |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ |
+ // '!=' not overloadable, always implements negation of '=='. |
+ // Call operator for '=='. |
+ if ((node->kind() == Token::kEQ) || (node->kind() == Token::kNE)) { |
+ // Null is a special receiver with a special type and frequently used on |
+ // operators "==" and "!=". Emit inlined code for null so that it does not |
+ // pollute type information at call site. |
+ Label null_done; |
+ { |
+ const Immediate raw_null = |
+ Immediate(reinterpret_cast<intptr_t>(Object::null())); |
+ Label non_null_compare, load_true; |
+ // Check if left argument is null. |
+ __ cmpq(Address(RSP, 1 * kWordSize), raw_null); |
+ __ j(NOT_EQUAL, &non_null_compare, Assembler::kNearJump); |
+ // Comparison with NULL is "===". |
+ // Load/remove arguments. |
+ __ popq(RDX); |
+ __ popq(RAX); |
+ __ cmpq(RAX, RDX); |
+ if (node->kind() == Token::kEQ) { |
+ __ j(EQUAL, &load_true, Assembler::kNearJump); |
+ } else { |
+ __ j(NOT_EQUAL, &load_true, Assembler::kNearJump); |
+ } |
+ __ LoadObject(RAX, bool_false); |
+ __ jmp(&null_done, Assembler::kNearJump); |
+ __ Bind(&load_true); |
+ __ LoadObject(RAX, bool_true); |
+ __ jmp(&null_done, Assembler::kNearJump); |
+ __ Bind(&non_null_compare); |
+ } |
+ // Do '==' first then negate if necessary, |
+ const String& operator_name = String::ZoneHandle(String::NewSymbol("==")); |
+ const int kNumberOfArguments = 2; |
+ const Array& kNoArgumentNames = Array::Handle(); |
+ const int kNumArgumentsChecked = 1; |
+ GenerateInstanceCall(node->id(), |
+ node->token_index(), |
+ operator_name, |
+ kNumberOfArguments, |
+ kNoArgumentNames, |
+ kNumArgumentsChecked); |
+ |
+ // Result is in RAX. No need to negate if result is not needed. |
+ if ((node->kind() == Token::kNE) && IsResultNeeded(node)) { |
+ // Negate result. |
+ Label load_true, done; |
+ __ LoadObject(RDX, bool_false); |
+ __ cmpq(RAX, RDX); |
+ __ j(EQUAL, &load_true, Assembler::kNearJump); |
+ __ movq(RAX, RDX); // false. |
+ __ jmp(&done, Assembler::kNearJump); |
+ __ Bind(&load_true); |
+ __ LoadObject(RAX, bool_true); |
+ __ Bind(&done); |
+ } |
+ __ Bind(&null_done); |
+ // Result is in RAX. |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+ return; |
+ } |
+ |
+ // Call operator. |
+ GenerateBinaryOperatorCall(node->id(), node->token_index(), node->Name()); |
+ // Result is in RAX. |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::CountBackwardLoop() { |
+ Label done; |
+ const Function& function = |
+ Function::ZoneHandle(parsed_function_.function().raw()); |
+ __ LoadObject(RAX, function); |
+ __ movq(RBX, FieldAddress(RAX, Function::invocation_counter_offset())); |
+ __ incq(RBX); |
+ if (!FLAG_report_invocation_count) { |
+ // Prevent overflow. |
+ __ cmpq(RBX, Immediate(FLAG_optimization_invocation_threshold)); |
+ __ j(GREATER, &done); |
+ } |
+ __ movq(FieldAddress(RAX, Function::invocation_counter_offset()), RBX); |
+ __ Bind(&done); |
+} |
+ |
+ |
+void CodeGenerator::VisitWhileNode(WhileNode* node) { |
+ const Bool& bool_true = Bool::ZoneHandle(Bool::True()); |
+ SourceLabel* label = node->label(); |
+ __ Bind(label->continue_label()); |
+ node->condition()->Visit(this); |
+ GenerateConditionTypeCheck(node->id(), node->condition()->token_index()); |
+ __ popq(RAX); |
+ __ LoadObject(RDX, bool_true); |
+ __ cmpq(RAX, RDX); |
+ __ j(NOT_EQUAL, label->break_label()); |
+ node->body()->Visit(this); |
+ CountBackwardLoop(); |
+ __ jmp(label->continue_label()); |
+ __ Bind(label->break_label()); |
+} |
+ |
+ |
+void CodeGenerator::VisitDoWhileNode(DoWhileNode* node) { |
+ const Bool& bool_true = Bool::ZoneHandle(Bool::True()); |
+ SourceLabel* label = node->label(); |
+ Label loop; |
+ __ Bind(&loop); |
+ node->body()->Visit(this); |
+ CountBackwardLoop(); |
+ __ Bind(label->continue_label()); |
+ node->condition()->Visit(this); |
+ GenerateConditionTypeCheck(node->id(), node->condition()->token_index()); |
+ __ popq(RAX); |
+ __ LoadObject(RDX, bool_true); |
+ __ cmpq(RAX, RDX); |
+ __ j(EQUAL, &loop); |
+ __ Bind(label->break_label()); |
+} |
+ |
+ |
+void CodeGenerator::VisitForNode(ForNode* node) { |
+ const Bool& bool_true = Bool::ZoneHandle(Bool::True()); |
+ node->initializer()->Visit(this); |
+ SourceLabel* label = node->label(); |
+ Label loop; |
+ __ Bind(&loop); |
+ if (node->condition() != NULL) { |
+ node->condition()->Visit(this); |
+ GenerateConditionTypeCheck(node->id(), node->condition()->token_index()); |
+ __ popq(RAX); |
+ __ LoadObject(RDX, bool_true); |
+ __ cmpq(RAX, RDX); |
+ __ j(NOT_EQUAL, label->break_label()); |
+ } |
+ node->body()->Visit(this); |
+ CountBackwardLoop(); |
+ __ Bind(label->continue_label()); |
+ node->increment()->Visit(this); |
+ __ jmp(&loop); |
+ __ Bind(label->break_label()); |
+} |
+ |
+ |
+void CodeGenerator::VisitJumpNode(JumpNode* node) { |
+ SourceLabel* label = node->label(); |
+ |
+ // Generate inlined code for all finally blocks as we may transfer |
+ // control out of the 'try' blocks if any. |
+ for (intptr_t i = 0; i < node->inlined_finally_list_length(); i++) { |
+ node->InlinedFinallyNodeAt(i)->Visit(this); |
+ } |
+ |
+ // Unchain the context(s) up to the outer context level of the scope which |
+ // contains the destination label. |
+ ASSERT(label->owner() != NULL); |
+ LocalScope* outer_context_owner = label->owner()->parent(); |
+ ASSERT(outer_context_owner != NULL); |
+ int target_context_level = 0; |
+ if (outer_context_owner->HasContextLevel()) { |
+ target_context_level = outer_context_owner->context_level(); |
+ ASSERT(target_context_level >= 0); |
+ int context_level = state()->context_level(); |
+ ASSERT(context_level >= target_context_level); |
+ while (context_level-- > target_context_level) { |
+ __ movq(CTX, FieldAddress(CTX, Context::parent_offset())); |
+ } |
+ } |
+ |
+ if (node->kind() == Token::kBREAK) { |
+ __ jmp(label->break_label()); |
+ } else { |
+ __ jmp(label->continue_label()); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitConditionalExprNode(ConditionalExprNode* node) { |
+ const Bool& bool_true = Bool::ZoneHandle(Bool::True()); |
+ Label false_label, done; |
+ node->condition()->Visit(this); |
+ GenerateConditionTypeCheck(node->id(), node->condition()->token_index()); |
+ __ popq(RAX); |
+ __ LoadObject(RDX, bool_true); |
+ __ cmpq(RAX, RDX); |
+ __ j(NOT_EQUAL, &false_label); |
+ node->true_expr()->Visit(this); |
+ __ jmp(&done); |
+ __ Bind(&false_label); |
+ node->false_expr()->Visit(this); |
+ __ Bind(&done); |
+ if (!IsResultNeeded(node)) { |
+ __ popq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitSwitchNode(SwitchNode *node) { |
+ SourceLabel* label = node->label(); |
+ node->body()->Visit(this); |
+ __ Bind(label->break_label()); |
+} |
+ |
+ |
+void CodeGenerator::VisitCaseNode(CaseNode* node) { |
+ const Bool& bool_true = Bool::ZoneHandle(Bool::True()); |
+ Label case_statements, end_case; |
+ |
+ for (int i = 0; i < node->case_expressions()->length(); i++) { |
+ // Load case expression onto stack. |
+ AstNode* case_expr = node->case_expressions()->NodeAt(i); |
+ case_expr->Visit(this); |
+ __ popq(RAX); |
+ __ CompareObject(RAX, bool_true); |
+ // Jump to case clause code if case expression equals switch expression |
+ __ j(EQUAL, &case_statements); |
+ } |
+ // If this case clause contains the default label, fall through to |
+ // case clause code, else skip this clause. |
+ if (!node->contains_default()) { |
+ __ jmp(&end_case); |
+ } |
+ |
+ // If there is a label associated with this case clause, bind it. |
+ if (node->label() != NULL) { |
+ __ Bind(node->label()->continue_label()); |
+ } |
+ |
+ // Generate code for case clause statements. The parser guarantees that |
+ // the code contains a jump, so we should never fall through the end |
+ // of the statements. |
+ __ Bind(&case_statements); |
+ node->statements()->Visit(this); |
+ __ Bind(&end_case); |
+} |
+ |
+ |
+void CodeGenerator::VisitIfNode(IfNode* node) { |
+ const Bool& bool_true = Bool::ZoneHandle(Bool::True()); |
+ Label false_label; |
+ node->condition()->Visit(this); |
+ GenerateConditionTypeCheck(node->id(), node->condition()->token_index()); |
+ __ popq(RAX); |
+ __ LoadObject(RDX, bool_true); |
+ __ cmpq(RAX, RDX); |
+ __ j(NOT_EQUAL, &false_label); |
+ node->true_branch()->Visit(this); |
+ if (node->false_branch() != NULL) { |
+ Label done; |
+ __ jmp(&done); |
+ __ Bind(&false_label); |
+ node->false_branch()->Visit(this); |
+ __ Bind(&done); |
+ } else { |
+ __ Bind(&false_label); |
+ } |
+} |
+ |
+ |
+// Operators '&&' and '||' are not overloadabled, inline them. |
+void CodeGenerator::GenerateLogicalAndOrOp(BinaryOpNode* node) { |
+ // Generate true if (left == true) op (right == true), otherwise generate |
+ // false, with op being either || or &&. |
+ const Bool& bool_true = Bool::ZoneHandle(Bool::True()); |
+ const Bool& bool_false = Bool::ZoneHandle(Bool::False()); |
+ Label load_false, done; |
+ node->left()->Visit(this); |
+ GenerateConditionTypeCheck(node->id(), node->left()->token_index()); |
+ __ popq(RAX); |
+ __ LoadObject(RDX, bool_true); |
+ __ cmpq(RAX, RDX); |
+ if (node->kind() == Token::kAND) { |
+ __ j(NOT_EQUAL, &load_false); |
+ } else { |
+ ASSERT(node->kind() == Token::kOR); |
+ __ j(EQUAL, &done); |
+ } |
+ node->right()->Visit(this); |
+ GenerateConditionTypeCheck(node->id(), node->right()->token_index()); |
+ __ popq(RAX); |
+ __ LoadObject(RDX, bool_true); |
+ __ cmpq(RAX, RDX); |
+ __ j(EQUAL, &done); |
+ __ Bind(&load_false); |
+ __ LoadObject(RAX, bool_false); |
+ __ Bind(&done); |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+// Expect receiver(left operand) and right operand on stack. |
+// Return result in RAX. |
+void CodeGenerator::GenerateBinaryOperatorCall(intptr_t node_id, |
+ intptr_t token_index, |
+ const char* name) { |
+ const String& operator_name = String::ZoneHandle(String::NewSymbol(name)); |
+ const int kNumberOfArguments = 2; |
+ const Array& kNoArgumentNames = Array::Handle(); |
+ const int kNumArgumentsChecked = 2; |
+ GenerateInstanceCall(node_id, |
+ token_index, |
+ operator_name, |
+ kNumberOfArguments, |
+ kNoArgumentNames, |
+ kNumArgumentsChecked); |
+} |
+ |
+ |
+void CodeGenerator::VisitBinaryOpNode(BinaryOpNode* node) { |
+ if ((node->kind() == Token::kAND) || (node->kind() == Token::kOR)) { |
+ // Operators "&&" and "||" cannot be overloaded, therefore inline them |
+ // instead of calling the operator. |
+ GenerateLogicalAndOrOp(node); |
+ return; |
+ } |
+ node->left()->Visit(this); |
+ node->right()->Visit(this); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ GenerateBinaryOperatorCall(node->id(), node->token_index(), node->Name()); |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitStringConcatNode(StringConcatNode* node) { |
+ const String& cls_name = String::Handle(String::NewSymbol("StringBase")); |
+ const Library& core_lib = Library::Handle( |
+ Isolate::Current()->object_store()->core_library()); |
+ const Class& cls = Class::Handle(core_lib.LookupClass(cls_name)); |
+ ASSERT(!cls.IsNull()); |
+ const String& func_name = String::Handle(String::NewSymbol("_interpolate")); |
+ const int number_of_parameters = 1; |
+ const Function& interpol_func = Function::ZoneHandle( |
+ Resolver::ResolveStatic(cls, func_name, |
+ number_of_parameters, |
+ Array::Handle(), |
+ Resolver::kIsQualified)); |
+ ASSERT(!interpol_func.IsNull()); |
+ |
+ // First try to concatenate and canonicalize the values at compile time. |
+ bool compile_time_interpolation = true; |
+ Array& literals = Array::Handle(Array::New(node->values()->length())); |
+ for (int i = 0; i < node->values()->length(); i++) { |
+ if (node->values()->ElementAt(i)->IsLiteralNode()) { |
+ LiteralNode* lit = node->values()->ElementAt(i)->AsLiteralNode(); |
+ literals.SetAt(i, lit->literal()); |
+ } else { |
+ compile_time_interpolation = false; |
+ break; |
+ } |
+ } |
+ if (compile_time_interpolation) { |
+ if (!IsResultNeeded(node)) { |
+ return; |
+ } |
+ // Build argument array to pass to the interpolation function. |
+ GrowableArray<const Object*> interpolate_arg; |
+ interpolate_arg.Add(&literals); |
+ const Array& kNoArgumentNames = Array::Handle(); |
+ // Call the interpolation function. |
+ String& concatenated = String::ZoneHandle(); |
+ concatenated ^= DartEntry::InvokeStatic(interpol_func, |
+ interpolate_arg, |
+ kNoArgumentNames); |
+ if (concatenated.IsUnhandledException()) { |
+ ErrorMsg(node->token_index(), |
+ "Exception thrown in CodeGenerator::VisitStringConcatNode"); |
+ } |
+ ASSERT(!concatenated.IsNull()); |
+ concatenated = String::NewSymbol(concatenated); |
+ |
+ __ LoadObject(RAX, concatenated); |
+ __ pushq(RAX); |
+ return; |
+ } |
+ |
+ // Could not concatenate at compile time, generate a call to |
+ // interpolation function. |
+ ArgumentListNode* interpol_arg = new ArgumentListNode(node->token_index()); |
+ interpol_arg->Add(node->values()); |
+ node->values()->Visit(this); |
+ __ LoadObject(RBX, interpol_func); |
+ __ LoadObject(R10, ArgumentsDescriptor(interpol_arg->length(), |
+ interpol_arg->names())); |
+ GenerateCall(node->token_index(), &StubCode::CallStaticFunctionLabel()); |
+ __ addq(RSP, Immediate(interpol_arg->length() * kWordSize)); |
+ // Result is in RAX. |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitInstanceCallNode(InstanceCallNode* node) { |
+ const int number_of_arguments = node->arguments()->length() + 1; |
+ // Compute the receiver object and pass it as first argument to call. |
+ node->receiver()->Visit(this); |
+ // Now compute rest of the arguments to the call. |
+ node->arguments()->Visit(this); |
+ // Some method may be inlined using type feedback, therefore this may be a |
+ // deoptimization point. |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ const int kNumArgumentsChecked = 1; |
+ GenerateInstanceCall(node->id(), |
+ node->token_index(), |
+ node->function_name(), |
+ number_of_arguments, |
+ node->arguments()->names(), |
+ kNumArgumentsChecked); |
+ // Result is in RAX. |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitStaticCallNode(StaticCallNode* node) { |
+ node->arguments()->Visit(this); |
+ __ LoadObject(RBX, node->function()); |
+ __ LoadObject(R10, ArgumentsDescriptor(node->arguments()->length(), |
+ node->arguments()->names())); |
+ GenerateCall(node->token_index(), &StubCode::CallStaticFunctionLabel()); |
+ __ addq(RSP, Immediate(node->arguments()->length() * kWordSize)); |
+ // Result is in RAX. |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitClosureCallNode(ClosureCallNode* node) { |
+ // The spec states that the closure is evaluated before the arguments. |
+ // Preserve the current context, since it will be overridden by the closure |
+ // context during the call. |
+ __ pushq(CTX); |
+ // Compute the closure object and pass it as first argument to the stub. |
+ node->closure()->Visit(this); |
+ // Now compute the arguments to the call. |
+ node->arguments()->Visit(this); |
+ // Set up the number of arguments (excluding the closure) to the ClosureCall |
+ // stub which will setup the closure context and jump to the entrypoint of the |
+ // closure function (the function will be compiled if it has not already been |
+ // compiled). |
+ // NOTE: The stub accesses the closure before the parameter list. |
+ __ LoadObject(R10, ArgumentsDescriptor(node->arguments()->length(), |
+ node->arguments()->names())); |
+ GenerateCall(node->token_index(), &StubCode::CallClosureFunctionLabel()); |
+ __ addq(RSP, Immediate((node->arguments()->length() + 1) * kWordSize)); |
+ // Restore the context. |
+ __ popq(CTX); |
+ // Result is in RAX. |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+// Pushes the type arguments of the instantiator on the stack. |
+void CodeGenerator::GenerateInstantiatorTypeArguments(intptr_t token_index) { |
+ Class& instantiator_class = Class::Handle(); |
+ Function& outer_function = |
+ Function::Handle(parsed_function().function().raw()); |
+ while (outer_function.IsLocalFunction()) { |
+ outer_function = outer_function.parent_function(); |
+ } |
+ // TODO(regis): Remove support for type parameters on factories. |
+ if (outer_function.IsFactory() && |
+ (outer_function.signature_class() != Class::null())) { |
+ instantiator_class = outer_function.signature_class(); |
+ } else { |
+ instantiator_class = outer_function.owner(); |
+ } |
+ if (instantiator_class.NumTypeParameters() == 0) { |
+ // The type arguments are compile time constants. |
+ AbstractTypeArguments& type_arguments = AbstractTypeArguments::ZoneHandle(); |
+ // TODO(regis): Temporary type should be allocated in new gen heap. |
+ Type& type = Type::Handle( |
+ Type::NewParameterizedType(instantiator_class, type_arguments)); |
+ String& errmsg = String::Handle(); |
+ type ^= ClassFinalizer::FinalizeAndCanonicalizeType(instantiator_class, |
+ type, |
+ &errmsg); |
+ if (!errmsg.IsNull()) { |
+ ErrorMsg(token_index, errmsg.ToCString()); |
+ } |
+ type_arguments = type.arguments(); |
+ __ PushObject(type_arguments); |
+ } else { |
+ ASSERT(parsed_function().instantiator() != NULL); |
+ parsed_function().instantiator()->Visit(this); |
+ if (!outer_function.IsFactory()) { |
+ __ popq(RAX); // Pop instantiator. |
+ // The instantiator is the receiver of the caller, which is not a factory. |
+ // The receiver cannot be null; extract its AbstractTypeArguments object. |
+ // Note that in the factory case, the instantiator is the first parameter |
+ // of the factory, i.e. already an AbstractTypeArguments object. |
+ intptr_t type_arguments_instance_field_offset = |
+ instantiator_class.type_arguments_instance_field_offset(); |
+ ASSERT(type_arguments_instance_field_offset != Class::kNoTypeArguments); |
+ __ movq(RAX, FieldAddress(RAX, type_arguments_instance_field_offset)); |
+ __ pushq(RAX); |
+ } |
+ } |
+} |
+ |
+ |
+// Pushes the type arguments on the stack in preparation of a constructor or |
+// factory call. |
+// For a factory call, instantiates (possibly requiring an additional run time |
+// call) and pushes the type argument vector that will be passed as implicit |
+// first parameter to the factory. |
+// For a constructor call allocating an object of a parameterized class, pushes |
+// the type arguments and the type arguments of the instantiator, without ever |
+// generating an additional run time call. |
+// Does nothing for a constructor call allocating an object of a non |
+// parameterized class. |
+// Note that a class without proper type parameters may still be parameterized, |
+// e.g. class A extends Array<int>. |
+void CodeGenerator::GenerateTypeArguments(ConstructorCallNode* node, |
+ bool requires_type_arguments) { |
+ const Immediate raw_null = |
+ Immediate(reinterpret_cast<intptr_t>(Object::null())); |
+ // Instantiate the type arguments if necessary. |
+ if (node->type_arguments().IsNull() || |
+ node->type_arguments().IsInstantiated()) { |
+ if (requires_type_arguments) { |
+ // A factory requires the type arguments as first parameter. |
+ __ PushObject(node->type_arguments()); |
+ if (!node->constructor().IsFactory()) { |
+ // The allocator additionally requires the instantiator type arguments. |
+ __ pushq(raw_null); // Null instantiator. |
+ } |
+ } |
+ } else { |
+ // The type arguments are uninstantiated. |
+ ASSERT(requires_type_arguments); |
+ GenerateInstantiatorTypeArguments(node->token_index()); |
+ __ popq(RAX); // Pop instantiator. |
+ // RAX is the instantiator AbstractTypeArguments object (or null). |
+ // If RAX is null, no need to instantiate the type arguments, use null, and |
+ // allocate an object of a raw type. |
+ Label type_arguments_instantiated, type_arguments_uninstantiated; |
+ __ cmpq(RAX, raw_null); |
+ __ j(EQUAL, &type_arguments_instantiated, Assembler::kNearJump); |
+ |
+ // Instantiate non-null type arguments. |
+ if (node->type_arguments().IsUninstantiatedIdentity()) { |
+ // Check if the instantiator type argument vector is a TypeArguments of a |
+ // matching length and, if so, use it as the instantiated type_arguments. |
+ __ LoadObject(RCX, Class::ZoneHandle(Object::type_arguments_class())); |
+ __ cmpq(RCX, FieldAddress(RAX, Object::class_offset())); |
+ __ j(NOT_EQUAL, &type_arguments_uninstantiated, Assembler::kNearJump); |
+ Immediate arguments_length = Immediate(reinterpret_cast<int64_t>( |
+ Smi::New(node->type_arguments().Length()))); |
+ __ cmpq(FieldAddress(RAX, TypeArguments::length_offset()), |
+ arguments_length); |
+ __ j(EQUAL, &type_arguments_instantiated, Assembler::kNearJump); |
+ } |
+ __ Bind(&type_arguments_uninstantiated); |
+ if (node->constructor().IsFactory()) { |
+ // A runtime call to instantiate the type arguments is required before |
+ // calling the factory. |
+ const Object& result = Object::ZoneHandle(); |
+ __ PushObject(result); // Make room for the result of the runtime call. |
+ __ PushObject(node->type_arguments()); |
+ __ pushq(RAX); // Push instantiator type arguments. |
+ GenerateCallRuntime(node->id(), |
+ node->token_index(), |
+ kInstantiateTypeArgumentsRuntimeEntry); |
+ __ popq(RAX); // Pop instantiator type arguments. |
+ __ popq(RAX); // Pop uninstantiated type arguments. |
+ __ popq(RAX); // Pop instantiated type arguments. |
+ __ Bind(&type_arguments_instantiated); |
+ __ pushq(RAX); // Instantiated type arguments. |
+ } else { |
+ // In the non-factory case, we rely on the allocation stub to |
+ // instantiate the type arguments. |
+ __ PushObject(node->type_arguments()); |
+ __ pushq(RAX); // Instantiator type arguments. |
+ Label type_arguments_pushed; |
+ __ jmp(&type_arguments_pushed, Assembler::kNearJump); |
+ |
+ __ Bind(&type_arguments_instantiated); |
+ __ pushq(RAX); // Instantiated type arguments. |
+ __ pushq(raw_null); // Null instantiator. |
+ __ Bind(&type_arguments_pushed); |
+ } |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitConstructorCallNode(ConstructorCallNode* node) { |
+ if (node->constructor().IsFactory()) { |
+ const bool requires_type_arguments = true; // Always first arg to factory. |
+ GenerateTypeArguments(node, requires_type_arguments); |
+ // The top of stack is an instantiated AbstractTypeArguments object |
+ // (or null). |
+ int num_args = node->arguments()->length() + 1; // +1 to include type args. |
+ node->arguments()->Visit(this); |
+ // Call the factory. |
+ __ LoadObject(RBX, node->constructor()); |
+ __ LoadObject(R10, ArgumentsDescriptor(num_args, |
+ node->arguments()->names())); |
+ GenerateCall(node->token_index(), &StubCode::CallStaticFunctionLabel()); |
+ // Factory constructor returns object in RAX. |
+ __ addq(RSP, Immediate(num_args * kWordSize)); |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+ return; |
+ } |
+ |
+ const Class& cls = Class::ZoneHandle(node->constructor().owner()); |
+ const bool requires_type_arguments = cls.HasTypeArguments(); |
+ GenerateTypeArguments(node, requires_type_arguments); |
+ |
+ // If cls is parameterized, the type arguments and the instantiator's |
+ // type arguments are on the stack. |
+ const Code& stub = Code::Handle(StubCode::GetAllocationStubForClass(cls)); |
+ const ExternalLabel label(cls.ToCString(), stub.EntryPoint()); |
+ GenerateCall(node->token_index(), &label); |
+ if (requires_type_arguments) { |
+ __ popq(RCX); // Pop type arguments. |
+ __ popq(RCX); // Pop instantiator type arguments. |
+ } |
+ |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); // Set up return value from allocate. |
+ } |
+ |
+ // First argument(this) for constructor call which follows. |
+ __ pushq(RAX); |
+ // Second argument is the implicit construction phase parameter. |
+ // Run both the constructor initializer list and the constructor body. |
+ __ PushObject(Smi::ZoneHandle(Smi::New(Function::kCtorPhaseAll))); |
+ |
+ |
+ // Now setup rest of the arguments for the constructor call. |
+ node->arguments()->Visit(this); |
+ |
+ // Call the constructor. |
+ // +2 to include implicit receiver and phase arguments. |
+ int num_args = node->arguments()->length() + 2; |
+ __ LoadObject(RBX, node->constructor()); |
+ __ LoadObject(R10, ArgumentsDescriptor(num_args, node->arguments()->names())); |
+ GenerateCall(node->token_index(), &StubCode::CallStaticFunctionLabel()); |
+ // Constructors do not return any value. |
+ |
+ // Pop out all the other arguments on the stack. |
+ __ addq(RSP, Immediate(num_args * kWordSize)); |
+} |
+ |
+ |
+// Expects receiver on stack, returns result in RAX.. |
+void CodeGenerator::GenerateInstanceGetterCall(intptr_t node_id, |
+ intptr_t token_index, |
+ const String& field_name) { |
+ const String& getter_name = String::ZoneHandle(Field::GetterName(field_name)); |
+ const int kNumberOfArguments = 1; |
+ const Array& kNoArgumentNames = Array::Handle(); |
+ const int kNumArgumentsChecked = 1; |
+ GenerateInstanceCall(node_id, |
+ token_index, |
+ getter_name, |
+ kNumberOfArguments, |
+ kNoArgumentNames, |
+ kNumArgumentsChecked); |
+} |
+ |
+ |
+// Call to the instance getter. |
+void CodeGenerator::VisitInstanceGetterNode(InstanceGetterNode* node) { |
+ node->receiver()->Visit(this); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ GenerateInstanceGetterCall(node->id(), |
+ node->token_index(), |
+ node->field_name()); |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+// Expects receiver and value on stack. |
+void CodeGenerator::GenerateInstanceSetterCall(intptr_t node_id, |
+ intptr_t token_index, |
+ const String& field_name) { |
+ const String& setter_name = String::ZoneHandle(Field::SetterName(field_name)); |
+ const int kNumberOfArguments = 2; // receiver + value. |
+ const Array& kNoArgumentNames = Array::Handle(); |
+ const int kNumArgumentsChecked = 1; |
+ GenerateInstanceCall(node_id, |
+ token_index, |
+ setter_name, |
+ kNumberOfArguments, |
+ kNoArgumentNames, |
+ kNumArgumentsChecked); |
+} |
+ |
+ |
+// The call to the instance setter implements the assignment to a field. |
+// The result of the assignment to a field is the value being stored. |
+void CodeGenerator::VisitInstanceSetterNode(InstanceSetterNode* node) { |
+ // Compute the receiver object and pass it as first argument to call. |
+ node->receiver()->Visit(this); |
+ node->value()->Visit(this); |
+ MarkDeoptPoint(node->id(), node->token_index()); |
+ if (IsResultNeeded(node)) { |
+ __ popq(RAX); // value. |
+ __ popq(RDX); // receiver. |
+ __ pushq(RAX); // Preserve value. |
+ __ pushq(RDX); // arg0: receiver. |
+ __ pushq(RAX); // arg1: value. |
+ } |
+ // It is not necessary to generate a type test of the assigned value here, |
+ // because the setter will check the type of its incoming arguments. |
+ GenerateInstanceSetterCall(node->id(), |
+ node->token_index(), |
+ node->field_name()); |
+} |
+ |
+ |
+// Return result in RAX. |
+void CodeGenerator::GenerateStaticGetterCall(intptr_t token_index, |
+ const Class& field_class, |
+ const String& field_name) { |
+ const String& getter_name = String::Handle(Field::GetterName(field_name)); |
+ const Function& function = |
+ Function::ZoneHandle(field_class.LookupStaticFunction(getter_name)); |
+ if (function.IsNull()) { |
+ ErrorMsg(token_index, "Static getter does not exist: %s", |
+ getter_name.ToCString()); |
+ } |
+ __ LoadObject(RBX, function); |
+ const int kNumberOfArguments = 0; |
+ const Array& kNoArgumentNames = Array::Handle(); |
+ __ LoadObject(R10, ArgumentsDescriptor(kNumberOfArguments, kNoArgumentNames)); |
+ GenerateCall(token_index, &StubCode::CallStaticFunctionLabel()); |
+ // No arguments were pushed, hence nothing to pop. |
+} |
+ |
+ |
+// Call to static getter. |
+void CodeGenerator::VisitStaticGetterNode(StaticGetterNode* node) { |
+ GenerateStaticGetterCall(node->token_index(), |
+ node->cls(), |
+ node->field_name()); |
+ // Result is in RAX. |
+ if (IsResultNeeded(node)) { |
+ __ pushq(RAX); |
+ } |
+} |
+ |
+ |
+// Expects value on stack. |
+void CodeGenerator::GenerateStaticSetterCall(intptr_t token_index, |
+ const Class& field_class, |
+ const String& field_name) { |
+ const String& setter_name = String::Handle(Field::SetterName(field_name)); |
+ const Function& function = |
+ Function::ZoneHandle(field_class.LookupStaticFunction(setter_name)); |
+ __ LoadObject(RBX, function); |
+ const int kNumberOfArguments = 1; // value. |
+ const Array& kNoArgumentNames = Array::Handle(); |
+ __ LoadObject(R10, ArgumentsDescriptor(kNumberOfArguments, kNoArgumentNames)); |
+ GenerateCall(token_index, &StubCode::CallStaticFunctionLabel()); |
+ __ addq(RSP, Immediate(kNumberOfArguments * kWordSize)); |
+} |
+ |
+ |
+// The call to static setter implements assignment to a static field. |
+// The result of the assignment is the value being stored. |
+void CodeGenerator::VisitStaticSetterNode(StaticSetterNode* node) { |
+ node->value()->Visit(this); |
+ if (IsResultNeeded(node)) { |
+ // Preserve the original value when returning from setter. |
+ __ movq(RAX, Address(RSP, 0)); |
+ __ pushq(RAX); // arg0: value. |
+ } |
+ // It is not necessary to generate a type test of the assigned value here, |
+ // because the setter will check the type of its incoming arguments. |
+ GenerateStaticSetterCall(node->token_index(), |
+ node->cls(), |
+ node->field_name()); |
+} |
+ |
+ |
+void CodeGenerator::VisitNativeBodyNode(NativeBodyNode* node) { |
+ // Push the result place holder initialized to NULL. |
+ __ PushObject(Object::ZoneHandle()); |
+ // Pass a pointer to the first argument in RAX. |
+ if (!node->has_optional_parameters()) { |
+ __ leaq(RAX, Address(RBP, (1 + node->argument_count()) * kWordSize)); |
+ } else { |
+ __ leaq(RAX, Address(RBP, -1 * kWordSize)); |
+ } |
+ __ movq(RBX, Immediate(reinterpret_cast<uword>(node->native_c_function()))); |
+ __ movq(R10, Immediate(node->argument_count())); |
+ GenerateCall(node->token_index(), &StubCode::CallNativeCFunctionLabel()); |
+ // Result is on the stack. |
+ if (!IsResultNeeded(node)) { |
+ __ popq(RAX); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitCatchClauseNode(CatchClauseNode* node) { |
+ // NOTE: The implicit variables ':saved_context', ':exception_var' |
+ // and ':stacktrace_var' can never be captured variables. |
+ // Restore CTX from local variable ':saved_context'. |
+ GenerateLoadVariable(CTX, node->context_var()); |
+ |
+ // Restore RSP from RBP as we are coming from a throw and the code for |
+ // popping arguments has not been run. |
+ ASSERT(locals_space_size() >= 0); |
+ __ movq(RSP, RBP); |
+ __ subq(RSP, Immediate(locals_space_size())); |
+ |
+ // The JumpToExceptionHandler trampoline code sets up |
+ // - the exception object in RAX (kExceptionObjectReg) |
+ // - the stacktrace object in register RDX (kStackTraceObjectReg) |
+ // We now setup the exception object and the trace object |
+ // so that the handler code has access to these objects. |
+ GenerateStoreVariable(node->exception_var(), |
+ kExceptionObjectReg, |
+ kNoRegister); |
+ GenerateStoreVariable(node->stacktrace_var(), |
+ kStackTraceObjectReg, |
+ kNoRegister); |
+ |
+ // Now generate code for the catch handler block. |
+ node->VisitChildren(this); |
+} |
+ |
+ |
+void CodeGenerator::VisitTryCatchNode(TryCatchNode* node) { |
+ CodeGeneratorState codegen_state(this); |
+ int outer_try_index = state()->try_index(); |
+ // We are about to generate code for a new try block, generate an |
+ // unique 'try index' for this block and set that try index in |
+ // the code generator state. |
+ int try_index = generate_next_try_index(); |
+ state()->set_try_index(try_index); |
+ exception_handlers_list_->AddHandler(try_index, -1); |
+ |
+ // Preserve CTX into local variable '%saved_context'. |
+ GenerateStoreVariable(node->context_var(), CTX, kNoRegister); |
+ |
+ node->try_block()->Visit(this); |
+ |
+ // We are done generating code for the try block. |
+ ASSERT(state()->try_index() > CatchClauseNode::kInvalidTryIndex); |
+ ASSERT(try_index == state()->try_index()); |
+ state()->set_try_index(outer_try_index); |
+ |
+ CatchClauseNode* catch_block = node->catch_block(); |
+ if (catch_block != NULL) { |
+ // Jump over the catch handler block, when exceptions are thrown we |
+ // will end up at the next instruction. |
+ __ jmp(node->end_catch_label()->continue_label()); |
+ |
+ // Set the corresponding try index for this catch block so |
+ // that we can set the appropriate handler pc when we generate |
+ // code for this catch block. |
+ catch_block->set_try_index(try_index); |
+ |
+ // Set the handler pc for this try index in the exception handler |
+ // table. |
+ exception_handlers_list_->SetPcOffset(try_index, assembler_->CodeSize()); |
+ |
+ // Generate code for the catch block. |
+ catch_block->Visit(this); |
+ |
+ // Bind the end of catch blocks label here. |
+ __ Bind(node->end_catch_label()->continue_label()); |
+ } |
+ |
+ // Generate code for the finally block if one exists. |
+ if (node->finally_block() != NULL) { |
+ node->finally_block()->Visit(this); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::VisitThrowNode(ThrowNode* node) { |
+ const Object& result = Object::ZoneHandle(); |
+ node->exception()->Visit(this); |
+ __ popq(RAX); // Exception object is now in RAX. |
+ if (node->stacktrace() != NULL) { |
+ __ PushObject(result); // Make room for the result of the runtime call. |
+ __ pushq(RAX); // Push the exception object. |
+ node->stacktrace()->Visit(this); |
+ GenerateCallRuntime(node->id(), node->token_index(), kReThrowRuntimeEntry); |
+ } else { |
+ __ PushObject(result); // Make room for the result of the runtime call. |
+ __ pushq(RAX); // Push the exception object. |
+ GenerateCallRuntime(node->id(), node->token_index(), kThrowRuntimeEntry); |
+ } |
+ // We should never return here. |
+ __ int3(); |
+} |
+ |
+ |
+void CodeGenerator::VisitInlinedFinallyNode(InlinedFinallyNode* node) { |
+ int try_index = state()->try_index(); |
+ if (try_index >= 0) { |
+ // We are about to generate code for an inlined finally block. Exceptions |
+ // thrown in this block of code should be treated as though they are |
+ // thrown not from the current try block but the outer try block if any. |
+ // the code generator state. |
+ state()->set_try_index((try_index - 1)); |
+ } |
+ |
+ // Restore CTX from local variable ':saved_context'. |
+ GenerateLoadVariable(CTX, node->context_var()); |
+ node->finally_block()->Visit(this); |
+ |
+ if (try_index >= 0) { |
+ state()->set_try_index(try_index); |
+ } |
+} |
+ |
+ |
+void CodeGenerator::GenerateCall(intptr_t token_index, |
+ const ExternalLabel* ext_label) { |
+ __ call(ext_label); |
+ AddCurrentDescriptor(PcDescriptors::kOther, AstNode::kNoId, token_index); |
+} |
+ |
+ |
+void CodeGenerator::GenerateCallRuntime(intptr_t node_id, |
+ intptr_t token_index, |
+ const RuntimeEntry& entry) { |
+ __ CallRuntimeFromDart(entry); |
+ AddCurrentDescriptor(PcDescriptors::kOther, node_id, token_index); |
+} |
+ |
+ |
+void CodeGenerator::MarkDeoptPoint(intptr_t node_id, |
+ intptr_t token_index) { |
+ ASSERT(node_id != AstNode::kNoId); |
+ AddCurrentDescriptor(PcDescriptors::kDeopt, node_id, token_index); |
+} |
+ |
+ |
+// Uses current pc position and try-index. |
+void CodeGenerator::AddCurrentDescriptor(PcDescriptors::Kind kind, |
+ intptr_t node_id, |
+ intptr_t token_index) { |
+ pc_descriptors_list_->AddDescriptor(kind, |
+ assembler_->CodeSize(), |
+ node_id, |
+ token_index, |
+ state()->try_index()); |
+} |
+ |
+ |
+void CodeGenerator::ErrorMsg(intptr_t token_index, const char* format, ...) { |
+ const intptr_t kMessageBufferSize = 512; |
+ char message_buffer[kMessageBufferSize]; |
+ va_list args; |
+ va_start(args, format); |
+ const Class& cls = Class::Handle(parsed_function_.function().owner()); |
+ const Script& script = Script::Handle(cls.script()); |
+ Parser::FormatMessage(script, token_index, "Error", |
+ message_buffer, kMessageBufferSize, |
+ format, args); |
+ va_end(args); |
+ Isolate::Current()->long_jump_base()->Jump(1, message_buffer); |
+ UNREACHABLE(); |
+} |
+ |
+} // namespace dart |
+ |
+#endif // defined TARGET_ARCH_X64 |