%def invoke(helper="NterpUnimplemented"):
    call    SYMBOL($helper)

%def op_invoke_custom():
   EXPORT_PC
   movzwl 2(rPC), %eax // call_site index, first argument of runtime call.
   jmp NterpCommonInvokeCustom

%def op_invoke_custom_range():
   EXPORT_PC
   movzwl 2(rPC), %eax // call_site index, first argument of runtime call.
   jmp NterpCommonInvokeCustomRange

%def invoke_direct_or_super(helper="", range="", is_super=""):
   EXPORT_PC
   // Fast-path which gets the method from thread-local cache.
%  fetch_from_thread_cache("%eax", miss_label="2f")
1:
   // Load the first argument (the 'this' pointer).
   movzwl 4(rPC), %ecx // arguments
   .if !$range
   andl $$0xf, %ecx
   .endif
   movl (rFP, %ecx, 4), %ecx
   // NullPointerException check.
   testl %ecx, %ecx
   je common_errNullObject
   jmp $helper
2:
   movl rSELF:THREAD_SELF_OFFSET, ARG0
   movl 0(%esp), ARG1
   movl rPC, ARG2
   movl rFP, ARG3
   call nterp_get_method
   .if $is_super
   jmp 1b
   .else
   testl MACRO_LITERAL(1), %eax
   je 1b
   andl $$-2, %eax  // Remove the extra bit that marks it's a String.<init> method.
   .if $range
   jmp NterpHandleStringInitRange
   .else
   jmp NterpHandleStringInit
   .endif
   .endif

%def op_invoke_direct():
%  invoke_direct_or_super(helper="NterpCommonInvokeInstance", range="0", is_super="0")

%def op_invoke_direct_range():
%  invoke_direct_or_super(helper="NterpCommonInvokeInstanceRange", range="1", is_super="0")

%def op_invoke_polymorphic():
   EXPORT_PC
   // No need to fetch the target method.
   // Load the first argument (the 'this' pointer).
   movzwl 4(rPC), %ecx // arguments
   andl $$0xf, %ecx
   movl (rFP, %ecx, 4), %ecx
   // NullPointerException check.
   testl %ecx, %ecx
   je common_errNullObject
   jmp NterpCommonInvokePolymorphic

%def op_invoke_polymorphic_range():
   EXPORT_PC
   // No need to fetch the target method.
   // Load the first argument (the 'this' pointer).
   movzwl 4(rPC), %ecx // arguments
   movl (rFP, %ecx, 4), %ecx
   // NullPointerException check.
   testl %ecx, %ecx
   je common_errNullObject
   jmp NterpCommonInvokePolymorphicRange

%def invoke_interface(helper="", range=""):
%  slow_path = add_slow_path(op_invoke_interface_slow_path)
   EXPORT_PC
   // Fast-path which gets the interface method from thread-local cache.
%  fetch_from_thread_cache("%eax", miss_label=slow_path)
.L${opcode}_resume:
   // First argument is the 'this' pointer.
   movzwl 4(rPC), %ecx // arguments
   .if !$range
   andl $$0xf, %ecx
   .endif
   movl (rFP, %ecx, 4), %ecx
   movl MIRROR_OBJECT_CLASS_OFFSET(%ecx), %edx
   UNPOISON_HEAP_REF edx
   // Test the first two bits of the fetched ArtMethod:
   // - If the first bit is set, this is a method on j.l.Object
   // - If the second bit is set, this is a default method.
   testl $$3, %eax
   jne 2f
   // Save interface method as hidden argument.
   movd %eax, %xmm7
   movzw ART_METHOD_IMT_INDEX_OFFSET(%eax), %eax
1:
   movl MIRROR_CLASS_IMT_PTR_OFFSET_32(%edx), %edx
   movl (%edx, %eax, 4), %eax
   jmp $helper
2:
   testl $$1, %eax
   .if $range
   jne NterpHandleInvokeInterfaceOnObjectMethodRange
   .else
   jne NterpHandleInvokeInterfaceOnObjectMethod
   .endif
   // Default method
   andl $$-4, %eax
   // Save interface method as hidden argument.
   movd %eax, %xmm7
   movzw ART_METHOD_METHOD_INDEX_OFFSET(%eax), %eax
   andl $$ART_METHOD_IMT_MASK, %eax
   jmp 1b

%def op_invoke_interface_slow_path():
   movl rSELF:THREAD_SELF_OFFSET, ARG0
   movl 0(%esp), ARG1
   movl rPC, ARG2
   movl rFP, ARG3
   call nterp_get_method
   jmp .L${opcode}_resume

%def op_invoke_interface():
%  invoke_interface(helper="NterpCommonInvokeInterface", range="0")

%def op_invoke_interface_range():
%  invoke_interface(helper="NterpCommonInvokeInterfaceRange", range="1")

%def invoke_static(helper=""):
   EXPORT_PC
   // Fast-path which gets the method from thread-local cache.
%  fetch_from_thread_cache("%eax", miss_label="1f")
   jmp $helper
1:
   movl rSELF:THREAD_SELF_OFFSET, ARG0
   movl 0(%esp), ARG1
   movl rPC, ARG2
   movl rFP, ARG3
   call nterp_get_method
   jmp $helper

%def op_invoke_static():
%  invoke_static(helper="NterpCommonInvokeStatic")

%def op_invoke_static_range():
%  invoke_static(helper="NterpCommonInvokeStaticRange")

%def op_invoke_super():
%  invoke_direct_or_super(helper="NterpCommonInvokeInstance", range="0", is_super="1")

%def op_invoke_super_range():
%  invoke_direct_or_super(helper="NterpCommonInvokeInstanceRange", range="1", is_super="1")

%def invoke_virtual(helper="", range=""):
   EXPORT_PC
   // Fast-path which gets the method from thread-local cache.
%  fetch_from_thread_cache("%eax", miss_label="2f")
1:
   // First argument is the 'this' pointer.
   movzwl 4(rPC), %ecx // arguments
   .if !$range
   andl $$0xf, %ecx
   .endif
   movl (rFP, %ecx, 4), %ecx
   // Note: if ecx is null, this will be handled by our SIGSEGV handler.
   movl MIRROR_OBJECT_CLASS_OFFSET(%ecx), %edx
   UNPOISON_HEAP_REF edx
   movl MIRROR_CLASS_VTABLE_OFFSET_32(%edx, %eax, 4), %eax
   jmp $helper
2:
   movl rSELF:THREAD_SELF_OFFSET, ARG0
   movl 0(%esp), ARG1
   movl rPC, ARG2
   movl rFP, ARG3
   call nterp_get_method
   jmp 1b

%def op_invoke_virtual():
%  invoke_virtual(helper="NterpCommonInvokeInstance", range="0")

%def op_invoke_virtual_range():
%  invoke_virtual(helper="NterpCommonInvokeInstanceRange", range="1")
