summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/interpreter/mterp/riscv64/invoke.S594
-rw-r--r--runtime/interpreter/mterp/riscv64/main.S39
-rw-r--r--runtime/nterp_helpers.cc1
3 files changed, 598 insertions, 36 deletions
diff --git a/runtime/interpreter/mterp/riscv64/invoke.S b/runtime/interpreter/mterp/riscv64/invoke.S
index e1c03ab01b..f3a3e624a8 100644
--- a/runtime/interpreter/mterp/riscv64/invoke.S
+++ b/runtime/interpreter/mterp/riscv64/invoke.S
@@ -1,59 +1,580 @@
-%def op_invoke_custom():
- unimp
+// Theory of operation. These invoke-X opcodes bounce to code labels in main.S which attempt a
+// variety of fast paths; the full asm doesn't fit in the per-opcode handler's size limit.
+//
+// Calling convention. There are three argument transfer types.
+// (A) Managed ABI -> Nterp. The ExecuteNterpImpl handles this case. We set up a fresh nterp frame
+// and move arguments from machine arg registers (and sometimes stack) into the frame.
+// (B) Nterp -> Nterp. An invoke op's fast path handles this case. If we can stay in nterp, then
+// we set up a fresh nterp frame, and copy the register slots from caller to callee.
+// (C) Nterp -> Managed ABI. Invoke op's remaining cases. To leave nterp, we read out arguments from
+// the caller's nterp frame and place them into machine arg registers (and sometimes stack).
+// Doing so requires obtaining and deciphering the method's shorty for arg type, width, and
+// order info.
+//
+// Fast path structure.
+// (0) If the next method's "quick code" is nterp, then set up a fresh nterp frame and perform a
+// vreg->vreg transfer. Jump to handler for the next method's first opcode.
+// - The following paths leave nterp. -
+// (1) If the next method is guaranteed to avoid floats, doubles, and longs, then the managed ABI is
+// very simple: just place all arguments in the native arg registers. We don't need to know the
+// precise types or widths, just the order matters. Call the quick code.
+// (2) If the next method has 0 or 1 argument, then the managed ABI is mildly overloaded by
+// pessimistically placing a singleton 32-bit arg in both a0 and fa0; we don't have to know if
+// the argument is an int or float. We might be able to avoid the shorty ...
+// (2.1) If this 0/1 arg method isn't returning a scalar value, it's returning void or an object. A
+// return will come back in a0, so we definitely don't need the shorty. Call the quick code.
+// (2.2) If this 0/1 arg method returns a scalar value, that return might be a float or double! We
+// must obtain the shorty to know for sure, so that a returned float is copied from fa0 into
+// a0, as expected by Nterp. But we can skip the argument setup for the managed ABI. Call the
+// quick code.
+// - The fully pessimistic case. -
+// (3) The next method has 2+ arguments with a mix of float/double/long, OR it is polymorphic OR
+// custom. Obtain the shorty and perform the full setup for managed ABI. Polymorphic and custom
+// invokes are specially shunted to the runtime. Otherwise we call the quick code.
+//
+// Code organization. These functions are organized in a three tier structure to aid readability.
+// (P) The "front end" is an opcode handler, such as op_invoke_virtual(). They are defined in
+// invoke.S. Since all the invoke code cannot fit in the allotted handler region, every invoke
+// handler has code extending into a "back end".
+// (Q) The opcode handler calls a "back end" label that is located in main.S. The code for that
+// label is defined in invoke.S. As a convention, the label in main.S is NterpInvokeVirtual. The
+// code in invoke.S is nterp_invoke_virtual().
+// (R) For the Nterp to Nterp fast path case, the back end calls a label located in main.S, the code
+// for which is defined in invoke.S. As a convention, the label in main.S is
+// NterpToNterpInstance, and the code in invoke.S is nterp_to_nterp_instance().
+// Helpers for each tier are placed just after the functions of each tier.
-%def op_invoke_custom_range():
- unimp
+//
+// invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB
+// Format 35c: A|G|op BBBB F|E|D|C
+//
-%def invoke_direct_or_super(helper="", range="", is_super=""):
- unimp
+// invoke-virtual {vC, vD, vE, vF, vG}, meth@BBBB
+// Format 35c: A|G|6e BBBB F|E|D|C
+//
+// Note: invoke-virtual is used to invoke a normal virtual method (a method that is not private,
+// static, or final, and is also not a constructor).
+%def op_invoke_virtual(range=""):
+ unimp
-%def op_invoke_direct():
- unimp
-%def op_invoke_direct_range():
- unimp
+// invoke-super {vC, vD, vE, vF, vG}, meth@BBBB
+// Format 35c: A|G|6f BBBB F|E|D|C
+//
+// Note: When the method_id references a method of a non-interface class, invoke-super is used to
+// invoke the closest superclass's virtual method (as opposed to the one with the same method_id in
+// the calling class).
+// Note: In Dex files version 037 or later, if the method_id refers to an interface method,
+// invoke-super is used to invoke the most specific, non-overridden version of that method defined
+// on that interface. The same method restrictions hold as for invoke-virtual. In Dex files prior to
+// version 037, having an interface method_id is illegal and undefined.
+%def op_invoke_super(range=""):
+ unimp
+
+
+// invoke-direct {vC, vD, vE, vF, vG}, meth@BBBB
+// Format 35c: A|G|70 BBBB F|E|D|C
+//
+// Note: invoke-direct is used to invoke a non-static direct method (that is, an instance method
+// that is by its nature non-overridable, namely either a private instance method or a constructor).
+//
+// For additional context on string init, see b/28555675. The object reference is replaced after
+// the string factory call, so we disable thread-caching the resolution of string init, and skip
+// fast paths out to managed ABI calls.
+%def op_invoke_direct(range=""):
+ unimp
+
-%def op_invoke_super():
- unimp
+// invoke-static {vC, vD, vE, vF, vG}, meth@BBBB
+// Format 35c: A|G|71 BBBB F|E|D|C
+//
+// Note: invoke-static is used to invoke a static method (which is always considered a direct
+// method).
+%def op_invoke_static(range=""):
+ EXPORT_PC
+ FETCH_FROM_THREAD_CACHE a0, /*slow path*/1f, t0, t1
+ // a0 := ArtMethod*
+ tail NterpInvokeStatic${range} // arg a0
+1:
+% resolve_method_into_a0()
+ tail NterpInvokeStatic${range} // arg a0
+
+
+// invoke-interface {vC, vD, vE, vF, vG}, meth@BBBB
+// Format 35c: A|G|72 BBBB F|E|D|C
+//
+// Note: invoke-interface is used to invoke an interface method, that is, on an object whose
+// concrete class isn't known, using a method_id that refers to an interface.
+%def op_invoke_interface(range=""):
+ unimp
+
+
+//
+// invoke-kind/range {vCCCC .. vNNNN}, meth@BBBB
+// Format 3rc: AA|op BBBB CCCC
+// where NNNN = CCCC + AA - 1, that is A determines the count 0..255, and C determines the first
+// register.
+//
+// invoke-virtual/range {vCCCC .. vNNNN}, meth@BBBB
+// Format 3rc: AA|74 BBBB CCCC
+//
+// Note: invoke-virtual/range is used to invoke a normal virtual method (a method that is not
+// private, static, or final, and is also not a constructor).
+%def op_invoke_virtual_range():
+% op_invoke_virtual(range="Range")
+
+
+// invoke-super/range {vCCCC .. vNNNN}, meth@BBBB
+// Format 3rc: AA|75 BBBB CCCC
+//
+// Note: When the method_id references a method of a non-interface class, invoke-super/range is used
+// to invoke the closest superclass's virtual method (as opposed to the one with the same method_id
+// in the calling class).
+// Note: In Dex files version 037 or later, if the method_id refers to an interface method,
+// invoke-super/range is used to invoke the most specific, non-overridden version of that method
+// defined on that interface. In Dex files prior to version 037, having an interface method_id is
+// illegal and undefined.
%def op_invoke_super_range():
- unimp
+% op_invoke_super(range="Range")
-%def op_invoke_polymorphic():
- unimp
-%def op_invoke_polymorphic_range():
- unimp
+// invoke-direct/range {vCCCC .. vNNNN}, meth@BBBB
+// Format 3rc: AA|76 BBBB CCCC
+//
+// Note: invoke-direct/range is used to invoke a non-static direct method (that is, an instance
+// method that is by its nature non-overridable, namely either a private instance method or a
+// constructor).
+%def op_invoke_direct_range():
+% op_invoke_direct(range="Range")
-%def invoke_interface(range=""):
- unimp
-%def op_invoke_interface_slow_path():
- unimp
+// invoke-static/range {vCCCC .. vNNNN}, meth@BBBB
+// Format 3rc: AA|77 BBBB CCCC
+//
+// Note: invoke-static/range is used to invoke a static method (which is always considered a direct
+// method).
+%def op_invoke_static_range():
+% op_invoke_static(range="Range")
-%def op_invoke_interface():
- unimp
+// invoke-interface/range {vCCCC .. vNNNN}, meth@BBBB
+// Format 3rc: AA|78 BBBB CCCC
+//
+// Note: invoke-interface/range is used to invoke an interface method, that is, on an object whose
+// concrete class isn't known, using a method_id that refers to an interface.
%def op_invoke_interface_range():
- unimp
+% op_invoke_interface(range="Range")
-%def invoke_static(helper=""):
- unimp
-%def op_invoke_static():
- unimp
+// invoke-polymorphic {vC, vD, vE, vF, vG}, meth@BBBB, proto@HHHH
+// Format 45cc: A|G|fa BBBB F|E|D|C HHHH
+//
+// Note: Invoke the indicated signature polymorphic method. The result (if any) may be stored with
+// an appropriate move-result* variant as the immediately subsequent instruction.
+//
+// The method reference must be to a signature polymorphic method, such as
+// java.lang.invoke.MethodHandle.invoke or java.lang.invoke.MethodHandle.invokeExact.
+//
+// The receiver must be an object supporting the signature polymorphic method being invoked.
+//
+// The prototype reference describes the argument types provided and the expected return type.
+//
+// The invoke-polymorphic bytecode may raise exceptions when it executes. The exceptions are
+// described in the API documentation for the signature polymorphic method being invoked.
+//
+// Present in Dex files from version 038 onwards.
+%def op_invoke_polymorphic(range=""):
+ unimp
-%def op_invoke_static_range():
- unimp
-%def invoke_virtual(helper="", range=""):
- unimp
+// invoke-polymorphic/range {vCCCC .. vNNNN}, meth@BBBB, proto@HHHH
+// Format 4rcc: AA|fb BBBB CCCC HHHH
+// where NNNN = CCCC + AA - 1, that is A determines the count 0..255, and C determines the first
+// register.
+//
+// Note: Invoke the indicated method handle. See the invoke-polymorphic description above for
+// details.
+//
+// Present in Dex files from version 038 onwards.
+%def op_invoke_polymorphic_range():
+% op_invoke_polymorphic(range="Range")
-%def op_invoke_virtual():
- unimp
-%def op_invoke_virtual_range():
- unimp
+// invoke-custom {vC, vD, vE, vF, vG}, call_site@BBBB
+// Format 35c: A|G|fc BBBB F|E|D|C
+//
+// Note: Resolves and invokes the indicated call site. The result from the invocation (if any) may
+// be stored with an appropriate move-result* variant as the immediately subsequent instruction.
+//
+// This instruction executes in two phases: call site resolution and call site invocation.
+//
+// Call site resolution checks whether the indicated call site has an associated
+// java.lang.invoke.CallSite instance. If not, the bootstrap linker method for the indicated call
+// site is invoked using arguments present in the DEX file (see call_site_item). The bootstrap
+// linker method returns a java.lang.invoke.CallSite instance that will then be associated with the
+// indicated call site if no association exists. Another thread may have already made the
+// association first, and if so execution of the instruction continues with the first associated
+// java.lang.invoke.CallSite instance.
+//
+// Call site invocation is made on the java.lang.invoke.MethodHandle target of the resolved
+// java.lang.invoke.CallSite instance. The target is invoked as if executing invoke-polymorphic
+// (described above) using the method handle and arguments to the invoke-custom instruction as the
+// arguments to an exact method handle invocation.
+//
+// Exceptions raised by the bootstrap linker method are wrapped in a java.lang.BootstrapMethodError.
+// A BootstrapMethodError is also raised if:
+// - the bootstrap linker method fails to return a java.lang.invoke.CallSite instance.
+// - the returned java.lang.invoke.CallSite has a null method handle target.
+// - the method handle target is not of the requested type.
+//
+// Present in Dex files from version 038 onwards.
+%def op_invoke_custom(range=""):
+ unimp
+
+
+// invoke-custom/range {vCCCC .. vNNNN}, call_site@BBBB
+// Format 3rc: AA|fd BBBB CCCC
+// where NNNN = CCCC + AA - 1, that is A determines the count 0..255, and C determines the first
+// register.
+//
+// Note: Resolve and invoke a call site. See the invoke-custom description above for details.
+//
+// Present in Dex files from version 038 onwards.
+%def op_invoke_custom_range():
+% op_invoke_custom(range="Range")
+
+
+// handler helpers
+
+%def resolve_method_into_a0():
+ mv a0, xSELF
+ ld a1, (sp) // We can't always rely on a0 = ArtMethod*.
+ mv a2, xPC
+ call nterp_get_method
+
+
+//
+// These asm blocks are positioned in main.S for visibility to stack walking.
+//
+
+// NterpInvokeVirtual
+// a0: ArtMethod*
+// a1: this
+%def nterp_invoke_virtual():
+ unimp
+
+
+// NterpInvokeSuper
+// a0: ArtMethod*
+// a1: this
+%def nterp_invoke_super():
+ unimp
+
+
+// NterpInvokeDirect
+// a0: ArtMethod*
+// a1: this
+%def nterp_invoke_direct(uniq="invoke_direct"):
+ unimp
+
+
+// NterpInvokeStringInit
+// a0: ArtMethod*
+// a1: this
+%def nterp_invoke_string_init(uniq="invoke_string_init"):
+ unimp
+
+
+// NterpInvokeStatic
+// a0: ArtMethod*
+%def nterp_invoke_static(uniq="invoke_static"):
+ ld s7, ART_METHOD_QUICK_CODE_OFFSET_64(a0)
+ // s7 := quick code
+ srliw t0, xINST, 12 // t0 := A
+% try_simple_args_static(ins="t0", z0="t1", z1="t2", skip=f".L{uniq}_01", uniq=uniq)
+ // a1, a2, a3, a4, a5 := C, D, E, F, G
+ jalr s7 // args a0 - a5
+ j .L${uniq}_next_op
+
+.L${uniq}_01:
+ mv s8, zero // initialize shorty reg
+% try_01_args_static(ins="t0", z0="t1", z1="t2", skip=f".L{uniq}_slow", call=f".L{uniq}_01_call", uniq=uniq)
+ // Return value expected. Get shorty, stash in callee-save to be available on return.
+ // When getting shorty, stash this fast path's arg registers then restore.
+ // Unconditionally stores a1/fa0, even if extra arg not found.
+ fmv.s fs0, fa0
+ mv s10, a1
+% get_shorty_preserve_a0(shorty="s8", y0="s9")
+ mv a1, s10
+ fmv.s fa0, fs0
+.L${uniq}_01_call:
+ jalr s7 // args a0, and maybe a1, fa0
+ beqz s8, .L${uniq}_next_op // no shorty, no return value
+% maybe_float_returned(shorty="s8", z0="t0", z1="t1", uniq=f"{uniq}_0")
+ // a0 := fa0 if float return
+ j .L${uniq}_next_op
+
+.L${uniq}_slow:
+% get_shorty_preserve_a0(shorty="s8", y0="s9")
+% slow_setup_args(shorty="s8", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4", arg_start="0", uniq=uniq)
+ jalr s7 // args in a0-a5, fa0-fa4
+% maybe_float_returned(shorty="s8", z0="t0", z1="t1", uniq=f"{uniq}_1")
+ // a0 := fa0 if float return
+.L${uniq}_next_op:
+ FETCH_ADVANCE_INST 3
+ GET_INST_OPCODE t0
+ GOTO_OPCODE t0
+
+
+// NterpInvokeInterface
+// a0: ArtMethod*
+// a1: this
+// a2: the target interface method
+// - ignored in nterp-to-nterp transfer
+// - side-loaded into T0 as a "hidden argument" in managed ABI transfer
+%def nterp_invoke_interface(uniq="invoke_interface"):
+ unimp
+
+
+// NterpInvokePolymorphic
+%def nterp_invoke_polymorphic(uniq="invoke_polymorphic"):
+ unimp
+
+
+// NterpInvokeCustom
+%def nterp_invoke_custom(uniq="invoke_custom"):
+ unimp
+
+
+// NterpInvokeVirtualRange
+%def nterp_invoke_virtual_range():
+% nterp_invoke_direct_range(uniq="invoke_virtual_range")
+
+
+// NterpInvokeSuperRange
+%def nterp_invoke_super_range():
+% nterp_invoke_direct_range(uniq="invoke_super_range")
+
+
+// NterpInvokeDirectRange
+%def nterp_invoke_direct_range(uniq="invoke_direct_range"):
+ unimp
+
+
+// NterpInvokeStringInitRange
+%def nterp_invoke_string_init_range(uniq="invoke_string_init_range"):
+ unimp
+
+
+// NterpInvokeStaticRange
+%def nterp_invoke_static_range(uniq="invoke_static_range"):
+ unimp
+
+
+// NterpInvokeInterfaceRange
+// a0: ArtMethod*
+// a1: this
+// a2: the target interface method
+// - ignored in nterp-to-nterp transfer
+// - side-loaded into T0 as a "hidden argument" in managed ABI transfer
+%def nterp_invoke_interface_range(uniq="invoke_interface_range"):
+ unimp
+
+
+// NterpInvokePolymorphicRange
+%def nterp_invoke_polymorphic_range(uniq="invoke_polymorphic_range"):
+ unimp
+
+
+// NterpInvokeCustomRange
+%def nterp_invoke_custom_range(uniq="invoke_custom_range"):
+ unimp
+
+
+// fast path and slow path helpers
+
+// Static variant.
+%def try_simple_args_static(ins="", z0="", z1="", skip="", uniq=""):
+ lwu $z0, ART_METHOD_ACCESS_FLAGS_OFFSET(a0)
+ bexti $z0, $z0, ART_METHOD_NTERP_INVOKE_FAST_PATH_FLAG_BIT
+ beqz $z0, $skip
+ beqz $ins, .L${uniq}_simple_done // A = 0: no further args.
+ FETCH $z1, count=2 // z1 := F|E|D|C
+ li $z0, 2
+ blt $ins, $z0, .L${uniq}_simple_1 // A = 1
+ beq $ins, $z0, .L${uniq}_simple_2 // A = 2
+ li $z0, 4
+ blt $ins, $z0, .L${uniq}_simple_3 // A = 3
+ beq $ins, $z0, .L${uniq}_simple_4 // A = 4
+ // A = 5
+ srliw $z0, xINST, 8 // z0 := A|G
+ andi $z0, $z0, 0xF // z0 := G
+ GET_VREG a5, $z0
+.L${uniq}_simple_4:
+ srliw $z0, $z1, 12 // z0 := F
+ GET_VREG a4, $z0
+.L${uniq}_simple_3:
+ srliw $z0, $z1, 8 // z0 := F|E
+ andi $z0, $z0, 0xF // z0 := E
+ GET_VREG a3, $z0
+.L${uniq}_simple_2:
+ srliw $z0, $z1, 4 // z0 := F|E|D
+ andi $z0, $z0, 0xF // z0 := D
+ GET_VREG a2, $z0
+.L${uniq}_simple_1:
+ andi $z0, $z1, 0xF // z0 := C
+ GET_VREG a1, $z0
+.L${uniq}_simple_done:
+
+
+// Static variant.
+%def try_01_args_static(ins="", z0="", z1="", skip="", call="", uniq=""):
+ beqz $ins, .L${uniq}_01_peek_next // A = 0
+ li $z0, 1 // z0 := imm 1
+ bgt $ins, $z0, $skip // A >= 2
+ // A = 1
+ FETCH $z1, count=2, width=8, byte=0
+ // z1 := D|C
+ andi $z1, $z1, 0xF // z1 := C
+ GET_VREG a1, $z1
+ fmv.w.x fa0, a1
+.L${uniq}_01_peek_next:
+% try_01_args_peek_next(z0=z0) # z0 is zero if invoke has return value
+ bnez $z0, $call
+
+
+%def try_01_args_peek_next(z0=""):
+ FETCH $z0, count=3, width=8, byte=0
+ // z0 := next op
+ bclri $z0, $z0, 0 // clear bit #0
+ addi $z0, $z0, -0x0A // z0 := zero if op is 0x0A or 0x0B
+
+
+// The invoked method might return in FA0, via managed ABI.
+// The next opcode, MOVE-RESULT{-WIDE}, expects the value in A0.
+%def maybe_float_returned(shorty="", z0="", z1="", uniq=""):
+ lb $z0, ($shorty) // z0 := first byte of shorty; type of return
+ li $z1, 'F' //
+ beq $z0, $z1, .L${uniq}_float_return_move
+ li $z1, 'D' //
+ bne $z0, $z1, .L${uniq}_float_return_done
+.L${uniq}_float_return_move:
+ // If fa0 carries a 32-bit float, the hi bits of fa0 will contain all 1's (NaN boxing).
+ // The use of fmv.x.d will transfer those hi bits into a0, and that's okay, because the next
+ // opcode, move-result, will only read the lo 32-bits of a0 - the box bits are correctly ignored.
+ // If fa0 carries a 64-bit float, then fmv.x.d works as expected.
+ fmv.x.d a0, fa0
+.L${uniq}_float_return_done:
+
+
+// Static variant.
+%def get_shorty_preserve_a0(shorty="", y0=""):
+ mv $y0, a0
+ call NterpGetShorty // arg a0
+ mv $shorty, a0
+ mv a0, $y0
+
+
+// Hardcoded
+// - a0: ArtMethod*
+// - a1: this
+%def slow_setup_args(shorty="", z0="", z1="", z2="", z3="", z4="", arg_start="1", uniq=""):
+ srliw $z0, xINST, 12 // z0 := A
+ li $z1, 5
+ FETCH $z2, count=2 // z2 := F|E|D|C
+ blt $z0, $z1, .L${uniq}_slow_gpr
+ // A = 5: need vreg G
+ srliw $z1, xINST, 8 // z1 := A|G
+ andi $z1, $z1, 0xF // z1 := G
+ slliw $z1, $z1, 16 // z1 := G0000
+ add $z2, $z1, $z2 // z2 := G|F|E|D|C
+
+.L${uniq}_slow_gpr:
+ addi $z0, $shorty, 1 // z0 := first arg of shorty
+ srliw $z1, $z2, 4*$arg_start // z1 := (instance) F|E|D or G|F|E|D, (static) F|E|D|C or G|F|E|D|C
+ // linear scan through shorty: extract non-float vregs
+% if arg_start == "0": # static can place vC into a1; instance already loaded "this" into a1.
+% load_vreg_in_gpr(gpr="a1", shorty=z0, vregs=z1, z0=z3, z1=z4, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_0")
+% load_vreg_in_gpr(gpr="a2", shorty=z0, vregs=z1, z0=z3, z1=z4, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_1")
+% load_vreg_in_gpr(gpr="a3", shorty=z0, vregs=z1, z0=z3, z1=z4, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_2")
+% load_vreg_in_gpr(gpr="a4", shorty=z0, vregs=z1, z0=z3, z1=z4, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_3")
+% load_vreg_in_gpr(gpr="a5", shorty=z0, vregs=z1, z0=z3, z1=z4, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_4")
+
+.L${uniq}_slow_fpr:
+ addi $z0, $shorty, 1 // z0 := first arg of shorty
+ srliw $z1, $z2, 4*$arg_start // z1 := (instance) F|E|D or G|F|E|D, (static) F|E|D|C or G|F|E|D|C
+ // linear scan through shorty: extract float/double vregs
+% load_vreg_in_fpr(fpr="fa0", shorty=z0, vregs=z1, z0=z3, z1=z4, done=f".L{uniq}_slow_done", uniq=f"{uniq}_0")
+% load_vreg_in_fpr(fpr="fa1", shorty=z0, vregs=z1, z0=z3, z1=z4, done=f".L{uniq}_slow_done", uniq=f"{uniq}_1")
+% load_vreg_in_fpr(fpr="fa2", shorty=z0, vregs=z1, z0=z3, z1=z4, done=f".L{uniq}_slow_done", uniq=f"{uniq}_2")
+% load_vreg_in_fpr(fpr="fa3", shorty=z0, vregs=z1, z0=z3, z1=z4, done=f".L{uniq}_slow_done", uniq=f"{uniq}_3")
+% if arg_start == "0": # static can place G into fa4; instance has only 4 args.
+% load_vreg_in_fpr(fpr="fa4", shorty=z0, vregs=z1, z0=z3, z1=z4, done=f".L{uniq}_slow_done", uniq=f"{uniq}_4")
+%#:
+.L${uniq}_slow_done:
+
+
+// Iterate through 4-bit vreg ids in the "vregs" register, load a non-FP value
+// into one argument register.
+%def load_vreg_in_gpr(gpr="", shorty="", vregs="", z0="", z1="", done="", uniq=""):
+.L${uniq}_gpr_find:
+ lb $z0, ($shorty) // z0 := next shorty arg spec
+ addi $shorty, $shorty, 1 // increment char ptr
+ beqz $z0, $done // z0 == \0
+ li $z1, 'F' // float
+ beq $z0, $z1, .L${uniq}_gpr_skip_4_bytes
+ li $z1, 'D' // double
+ beq $z0, $z1, .L${uniq}_gpr_skip_8_bytes
+
+ li $z1, 'J' // long
+ andi $gpr, $vregs, 0xF // gpr := vreg id
+ beq $z0, $z1, .L${uniq}_gpr_load_8_bytes
+ GET_VREG $gpr, $gpr // gpr := 32-bit load
+ srliw $vregs, $vregs, 4 // shift out the processed arg, one vreg
+ j .L${uniq}_gpr_set // and exit
+.L${uniq}_gpr_load_8_bytes:
+ GET_VREG_WIDE $gpr, $gpr // gpr := 64-bit load
+ srliw $vregs, $vregs, 8 // shift out the processed arg, a vreg pair
+ j .L${uniq}_gpr_set // and exit
+
+.L${uniq}_gpr_skip_8_bytes:
+ srliw $vregs, $vregs, 4 // shift out a skipped arg
+.L${uniq}_gpr_skip_4_bytes:
+ srliw $vregs, $vregs, 4 // shift out a skipped arg
+ j .L${uniq}_gpr_find
+.L${uniq}_gpr_set:
+
+
+// Iterate through 4-bit vreg ids in the "vregs" register, load a float or double
+// value into one floating point argument register.
+%def load_vreg_in_fpr(fpr="", shorty="", vregs="", z0="", z1="", done="", uniq=""):
+.L${uniq}_fpr_find:
+ lb $z0, ($shorty) // z0 := next shorty arg spec
+ addi $shorty, $shorty, 1 // increment char ptr
+ beqz $z0, $done // z0 == \0
+ li $z1, 'F' // float
+ beq $z0, $z1, .L${uniq}_fpr_load_4_bytes
+ li $z1, 'D' // double
+ beq $z0, $z1, .L${uniq}_fpr_load_8_bytes
+
+ li $z1, 'J' // long
+ srliw $vregs, $vregs, 4 // shift out a skipped arg, one vreg
+ bne $z0, $z1, .L${uniq}_fpr_find
+ srliw $vregs, $vregs, 4 // shift out one more skipped arg, for J
+ j .L${uniq}_fpr_find
+
+.L${uniq}_fpr_load_4_bytes:
+ andi $z1, $vregs, 0xF
+ GET_VREG_FLOAT $fpr, $z1
+ srliw $vregs, $vregs, 4 // shift out the processed arg, one vreg
+ j .L${uniq}_fpr_set
+.L${uniq}_fpr_load_8_bytes:
+ andi $z1, $vregs, 0xF
+ GET_VREG_DOUBLE $fpr, $z1
+ srliw $vregs, $vregs, 8 // shift out the processed arg, a vreg pair
+.L${uniq}_fpr_set:
+
// See runtime/nterp_helpers.cc for a diagram of the setup.
@@ -285,3 +806,4 @@
addi $outs, $outs, 8
addi $fp, $fp, 8
j .Lentry_fstack
+
diff --git a/runtime/interpreter/mterp/riscv64/main.S b/runtime/interpreter/mterp/riscv64/main.S
index fd5b9b28b8..5f328c0943 100644
--- a/runtime/interpreter/mterp/riscv64/main.S
+++ b/runtime/interpreter/mterp/riscv64/main.S
@@ -568,6 +568,44 @@ common_errDivideByZero:
// CALL preserves RA for stack walking.
call art_quick_throw_div_zero
+common_errNullObject:
+ EXPORT_PC
+ // CALL preserves RA for stack walking.
+ call art_quick_throw_null_pointer_exception
+
+NterpInvokeVirtual:
+% nterp_invoke_virtual()
+NterpInvokeSuper:
+% nterp_invoke_super()
+NterpInvokeDirect:
+% nterp_invoke_direct()
+NterpInvokeStringInit:
+% nterp_invoke_string_init()
+NterpInvokeStatic:
+% nterp_invoke_static()
+NterpInvokeInterface:
+% nterp_invoke_interface()
+NterpInvokePolymorphic:
+% nterp_invoke_polymorphic()
+NterpInvokeCustom:
+% nterp_invoke_custom()
+NterpInvokeVirtualRange:
+% nterp_invoke_virtual_range()
+NterpInvokeSuperRange:
+% nterp_invoke_super_range()
+NterpInvokeDirectRange:
+% nterp_invoke_direct_range()
+NterpInvokeStringInitRange:
+% nterp_invoke_string_init_range()
+NterpInvokeStaticRange:
+% nterp_invoke_static_range()
+NterpInvokeInterfaceRange:
+% nterp_invoke_interface_range()
+NterpInvokePolymorphicRange:
+% nterp_invoke_polymorphic_range()
+NterpInvokeCustomRange:
+% nterp_invoke_custom_range()
+
// This is the logical end of ExecuteNterpImpl, where the frame info applies.
.cfi_endproc
@@ -583,6 +621,7 @@ EndExecuteNterpImpl:
// Entrypoints into runtime.
NTERP_TRAMPOLINE nterp_get_class, NterpGetClass
+NTERP_TRAMPOLINE nterp_get_method, NterpGetMethod
NTERP_TRAMPOLINE nterp_get_static_field, NterpGetStaticField
NTERP_TRAMPOLINE nterp_hot_method, NterpHotMethod
diff --git a/runtime/nterp_helpers.cc b/runtime/nterp_helpers.cc
index 546b1762b4..8f8cf6f042 100644
--- a/runtime/nterp_helpers.cc
+++ b/runtime/nterp_helpers.cc
@@ -274,6 +274,7 @@ bool CanMethodUseNterp(ArtMethod* method, InstructionSet isa) {
case Instruction::CONST_WIDE_HIGH16:
case Instruction::SPUT:
case Instruction::SPUT_OBJECT:
+ case Instruction::INVOKE_STATIC:
case Instruction::NEG_INT:
case Instruction::NOT_INT:
case Instruction::NEG_LONG: