Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  invoke.S

  Sprache: Sparc
 

// 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'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 be only object refs, then the managed ABI is very simple:
//     just place all arguments in the native arg registers using LWU. Call the quick code.
// (2) The next method might have an arg/return shape that can avoid the shorty, or at least avoid
//     most complications of the managed ABI arg setup.
// (2.1) If the next method has 0 args, then peek ahead in dex: if no scalar return, then call the
//       quick code. (Even when the next opcode is move-result-object, nterp will expect the
//       reference at a0, matching where the managed ABI leaves it after the call.)
// (2.2) If the next method has 0 args and scalar return, or has 1 arg, then obtain the shorty.
// (2.2.1) Post-shorty: if 0 args, call the quick code. (After the call, a returned float must be
//         copied from fa0 into a0.)
// (2.2.2) Post-shorty: check the arg's shorty type. If 'L', we must load it with LWU. Otherwise, we
//         load it with LW and store a copy into FA0 (to avoid another branch). 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.

//
// invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB
// Format 35c: A|G|op BBBB F|E|D|C
//

// 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=""):
   EXPORT_PC
   FETCH s7, count=2                // s7 := F|E|D|C or CCCC (range)
%  fetch_from_thread_cache("a0"/*slow path*/"2f", "t0", "t1")
                                    // a0 := method idx of resolved virtual method
1:
%  fetch_receiver(reg="a1", vreg="s7", range=range)
                                    // a1 := fp[C] (this)
   // Note: null case handled by SEGV handler.
   lwu t0, MIRROR_OBJECT_CLASS_OFFSET(a1)
                                    // t0 := klass object (32-bit addr)
   UNPOISON_HEAP_REF t0
   // Entry address = entry's byte offset in vtable + vtable's byte offset in klass object.
   sh3add a0, a0, t0                // a0 := entry's byte offset
   ld a0, MIRROR_CLASS_VTABLE_OFFSET_64(a0)
                                    // a0 := ArtMethod*
   tail NterpInvokeVirtual${range}  // args a0, a1, s7
2:
%  resolve_method_into_a0()
   j 1b


// 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=""):
   EXPORT_PC
   FETCH s7, count=2              // s7 := F|E|D|C or CCCC (range)
%  fetch_from_thread_cache("a0"/*slow path*/"2f", "t0", "t1")
                                  // a0 := ArtMethod*
1:
%  fetch_receiver(reg="a1", vreg="s7", range=range)
                                  // a1 := fp[C] (this)
   beqz a1, 3f                    // throw if null
   tail NterpInvokeSuper${range}  // args a0, a1, s7
2:
%  resolve_method_into_a0()
   j 1b
3:
   tail common_errNullObject


// 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=""):
   EXPORT_PC
   FETCH s7, count=2               // s7 := F|E|D|C or CCCC (range)
%  fetch_from_thread_cache("a0"/*slow path*/"2f", "t0", "t1")
                                   // a0 := ArtMethod*, never String.<init>
1:
%  fetch_receiver(reg="a1", vreg="s7", range=range)
                                   // a1 := fp[C] (this)
   beqz a1, 3f                     // throw if null
   tail NterpInvokeDirect${range}  // args a0, a1, s7
2:
%  resolve_method_into_a0()        #  a0 := ArtMethod* or String.<init>
   and t0, a0, 0x1                 // t0 := string-init bit
   beqz t0, 1b                     // not string init
   and a0, a0, ~0x1                // clear string-init bit
   tail NterpInvokeStringInit${range}  // args a0, s7
3:
   tail common_errNullObject


// 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
   // TODO: Unnecessary if A=0, and unnecessary if nterp-to-nterp.
   FETCH s7, count=2               // s7 := F|E|D|C or CCCC (range)
%  fetch_from_thread_cache("a0"/*slow path*/"1f", "t0", "t1")
                                   // a0 := ArtMethod*
   tail NterpInvokeStatic${range}  // arg a0, s7
1:
%  resolve_method_into_a0()
   tail NterpInvokeStatic${range}  // arg a0, s7


// 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=""):
   EXPORT_PC
   FETCH s7, count=2               // s7 := F|E|D|C or CCCC (range)
   // T0 is eventually used to carry the "hidden argument" in the managed ABI.
   // This handler is tight on space, so we cache this arg in A0 and move it to T0 later.
   // Here, A0 is one of
   // (1) ArtMethod*
   // (2) ArtMethod* with LSB #1 set (default method)
   // (3) method index << 16 with LSB #0 set (j.l.Object method)
%  fetch_from_thread_cache("a0"/*slow path*/"5f", "t0", "t1")
1:
%  fetch_receiver(reg="a1", vreg="s7", range=range)
                          // a1 := fp[C] (this)
   // Note: null case handled by SEGV handler.
   lwu t0, MIRROR_OBJECT_CLASS_OFFSET(a1)
                          // t0 := klass object (32-bit addr)
   UNPOISON_HEAP_REF t0
   slliw t1, a0, 30       // test LSB #0 and #1
   bltz t1, 3f            // LSB #1 is set; handle default method
   bgtz t1, 4f            // LSB #0 is set; handle object method
   // no signal bits; it is a clean ArtMethod*
   lhu t1, ART_METHOD_IMT_INDEX_OFFSET(a0)
                          // t1 := idx into interface method table (16-bit value)
2:
   ld t0, MIRROR_CLASS_IMT_PTR_OFFSET_64(t0)
                          // t0 := base address of imt
   sh3add t0, t1, t0      // t0 := entry's address in imt
   ld a2, (t0)            // a2 := ArtMethod*
   tail NterpInvokeInterface${range}  // a0 (hidden arg), a1 (this), a2 (ArtMethod*), s7 (vregs)
3:
   andi a0, a0, ~2        // a0 := default ArtMethod*, LSB #1 cleared
   lhu t1, ART_METHOD_METHOD_INDEX_OFFSET(a0)
                          // t1 := method_index_ (16-bit value)
   // Default methods have a contract with art::IMTable.
   andi t1, t1, ART_METHOD_IMT_MASK
                          // t1 := idx into interface method table
   j 2b
4:
   // Interface methods on j.l.Object have a contract with NterpGetMethod.
   srliw t1, a0, 16       // t3 := method index
   sh3add t0, t1, t0      // t0 := entry's byte offset, before vtable offset adjustment
   ld a0, MIRROR_CLASS_VTABLE_OFFSET_64(t0)
   tail NterpInvokeDirect${range}  // args a0, a1, s7
5:
%  resolve_method_into_a0()
   j 1b


//
// 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():
%   op_invoke_super(range="Range")


// 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")


// 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")


// 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():
%   op_invoke_interface(range="Range")


// 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=""):
   EXPORT_PC
   FETCH s7, count=2  // s7 := F|E|D|C or CCCC (range)
   // No need to fetch the target method; the runtime handles it.
%  fetch_receiver(reg="s8", vreg="s7", range=range)
   beqz s8, 1f        // throw if null

   ld a0, (sp)        // a0 := caller ArtMethod*
   mv a1, xPC
   call NterpGetShortyFromInvokePolymorphic  // args a0, a1
   mv a1, s8
   tail NterpInvokePolymorphic${range}  // args a0 (shorty), a1 (this), s7 (vregs)
1:
   tail common_errNullObject


// 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")


// 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=""):
   EXPORT_PC
   ld a0, (sp)  // a0 := caller ArtMethod*
   mv a1, xPC
   call NterpGetShortyFromInvokeCustom  // args a0, a1
   mv s7, a0    // s7 := shorty
   FETCH a0, 1  // a0 := BBBB
   FETCH s8, 2  // s8 := F|E|D|C or CCCC (range)
   tail NterpInvokeCustom${range}  // args a0 (BBBB), s7 (shorty), s8 (vregs)


// 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
   mv a3, xFP
   call nterp_get_method


%def fetch_receiver(reg="", vreg="", range=""):
%  if range == 'Range':
     GET_VREG_OBJECT $reg, $vreg           // reg := refs[CCCC]
%  else:
     andi $reg, $vreg, 0xF                 // reg := C
     GET_VREG_OBJECT $reg, $reg            // reg := refs[C]


//
// These asm blocks are positioned in main.S for visibility to stack walking.
//


// NterpInvokeVirtual
// a0: ArtMethod*
// a1: this
// s7: vreg ids F|E|D|C
%def nterp_invoke_virtual():
%  nterp_invoke_direct(uniq="invoke_virtual")


// NterpInvokeSuper
// a0: ArtMethod*
// a1: this
// s7: vreg ids F|E|D|C
%def nterp_invoke_super():
%  nterp_invoke_direct(uniq="invoke_super")


// NterpInvokeDirect
// a0: ArtMethod*
// a1: this
// s7: (regular) vreg ids F|E|D|C, (range) vreg id CCCC
%def nterp_invoke_direct(uniq="invoke_direct", range=""):
   ld s8, ART_METHOD_QUICK_CODE_OFFSET_64(a0)
                                 // s8 := quick code
%  try_nterp(quick="s8", z0="t0"skip=f".L{uniq}_simple")
   call NterpToNterpInstance${range}  // args a0, a1
   j .L${uniq}_next_op

.L${uniq}_simple:
%  if range == 'Range':
%    try_simple_args_range(vC="s7", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4"skip=f".L{uniq}_01", uniq=uniq)
%  else:
%    try_simple_args(v_fedc="s7", z0="t0", z1="t1"skip=f".L{uniq}_01", uniq=uniq)
   jalr s8                       // (regular) args a0 - a5, (range) args a0 - a7 and stack
   j .L${uniq}_next_op

.L${uniq}_01:
   mv s9, zero                   // initialize shorty reg
%  try_01_args(vreg="s7", shorty="s9", z0="t0", z1="t1", z2="t2", y0="s10", y1="s11", y2="s0"skip=f".L{uniq}_slow", call=f".L{uniq}_01_call", uniq=uniq, range=range)
                                 // if s9 := shorty, then maybe (a2, fa0) := fp[D] or fp[CCCC + 1]
.L${uniq}_01_call:
   jalr s8                       // args a0, a1, and maybe a2, fa0
   beqz s9, .L${uniq}_next_op    // no shorty, no scalar return
%  maybe_float_returned(shorty="s9", z0="t0", z1="t1", uniq=f"{uniq}_0")
                                 // a0 := fa0 if float return
   j .L${uniq}_next_op

.L${uniq}_slow:
%  get_shorty_save_a0_a1(shorty="s9", y0="s10", y1="s11")
%  if range == 'Range':
%    slow_setup_args_range(shorty="s9", vC="s7", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4", z5="t5", z6="t6", z7="s10", uniq=uniq)
%  else:
%    slow_setup_args(shorty="s9", vregs="s7", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4"z5="t5", z6="t6", uniq=uniq)
   jalr s8                       // args in a0-a5, fa0-fa4
%  maybe_float_returned(shorty="s9", 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


// NterpInvokeStringInit
// a0: ArtMethod*
// s7: (regular) vreg ids F|E|D|C, (range) vreg id CCCC
%def nterp_invoke_string_init(uniq="invoke_string_init", range=""):
   ld s8, ART_METHOD_QUICK_CODE_OFFSET_64(a0)
                        // s8 := quick code
%  try_nterp(quick="s8", z0="t0"skip=f".L{uniq}_slow")
   call NterpToNterpStringInit${range}  // arg a0
   j .L${uniq}_next_op

.L${uniq}_slow:
%  get_shorty_save_a0_a1(shorty="s9", y0="s10", y1="s11")
%  if range == 'Range':
%    slow_setup_args_string_init_range(shorty="s9", vC="s7", z0="t0", z1="t1", z2="t2", z3="t3", uniq=uniq)
%  else:
%    slow_setup_args_string_init(shorty="s9", v_fedc="s7", z0="t0", z1="t1", z2="t2", uniq=uniq)
   jalr s8              // args (regular) a0 - a5, (range) a0 - a5

.L${uniq}_next_op:
%  fetch_receiver(reg="t0", vreg="s7", range=range)
                        // t0 := fp[C] (this)
%  subst_vreg_references(old="t0", new="a0", z0="t1", z1="t2", z2="t3", uniq=uniq)
   FETCH_ADVANCE_INST 3
   GET_INST_OPCODE t0
   GOTO_OPCODE t0


// NterpInvokeStatic
// a0: ArtMethod*
// s7: (regular) vreg ids F|E|D|C, (range) vreg id CCCC
%def nterp_invoke_static(uniq="invoke_static", range=""):
   ld s8, ART_METHOD_QUICK_CODE_OFFSET_64(a0)
                               // s8 := quick code
%  try_nterp(quick="s8", z0="t0"skip=f".L{uniq}_simple")
   call NterpToNterpStatic${range}  // arg a0
   j .L${uniq}_next_op

.L${uniq}_simple:
%  if range == 'Range':
%    try_simple_args_range(vC="s7", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4", arg_start="0"skip=f".L{uniq}_01", uniq=uniq)
%  else:
%    try_simple_args(v_fedc="s7", z0="t0", z1="t1", arg_start="0"skip=f".L{uniq}_01", uniq=uniq)
   jalr s8                     // args (regular) a0 - a5, (range) a0 - a7 and maybe stack
   j .L${uniq}_next_op

.L${uniq}_01:
   mv s9, zero                 // initialize shorty reg
%  try_01_args_static(vreg="s7", shorty="s9", z0="t0", z1="t1", z2="t2", y0="s10", y1="s11"skip=f".L{uniq}_slow", call=f".L{uniq}_01_call", uniq=uniq, range=range)
                               // if s9 := shorty, then maybe (a2, fa0) := fp[C] or fp[CCCC]
.L${uniq}_01_call:
   jalr s8                     // args a0, and maybe a1, fa0
   beqz s9, .L${uniq}_next_op  // no shorty, no scalar return
%  maybe_float_returned(shorty="s9", z0="t0", z1="t1", uniq=f"{uniq}_0")
                               // a0 := fa0 if float return
   j .L${uniq}_next_op

.L${uniq}_slow:
%  get_shorty_save_a0(shorty="s9", y0="s10")
%  if range == 'Range':
%    slow_setup_args_range(shorty="s9", vC="s7", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4", z5="t5", z6="t6", z7="s10", arg_start="0", uniq=uniq)
%  else:
%    slow_setup_args(shorty="s9", vregs="s7", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4"z5="t5", z6="t6", arg_start="0", uniq=uniq)
   jalr s8                     // args (regular) a0 - a5 and fa0 - fa4, (range) a0 - a7 and fa0 - fa7 and maybe stack
%  maybe_float_returned(shorty="s9", 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: the target interface method
//     - ignored in nterp-to-nterp transfer
//     - preserved through shorty calls
//     - side-loaded in T0 as a "hidden argument" in managed ABI transfer
// a1: this
// a2: ArtMethod*
// s7: vreg ids F|E|D|C
%def nterp_invoke_interface(uniq="invoke_interface", range=""):
   // We immediately adjust the incoming arguments to suit the rest of the invoke.
   mv t0, a0                   // t0 := hidden arg, preserve until quick call
   mv a0, a2                   // a0 := ArtMethod*

   ld s8, ART_METHOD_QUICK_CODE_OFFSET_64(a0)
                               // s8 := quick code
%  try_nterp(quick="s8", z0="t1"skip=f".L{uniq}_simple")
   call NterpToNterpInstance${range}  // args a0, a1
   j .L${uniq}_next_op

.L${uniq}_simple:
%  if range == 'Range':
%    try_simple_args_range(vC="s7", z0="t1", z1="t2", z2="t3", z3="t4", z4="t5"skip=f".L{uniq}_01", uniq=uniq)
%  else:
%    try_simple_args(v_fedc="s7", z0="t1", z1="t2"skip=f".L{uniq}_01", uniq=uniq)
   jalr s8                     // args (regular) a0 - a5 and t0, (range) a0 - a7 and t0 and maybe stack
   j .L${uniq}_next_op

.L${uniq}_01:
   mv s9, zero                 // initialize shorty reg
%  try_01_args(vreg="s7", shorty="s9", z0="t1", z1="t2", z2="t3", y0="s10", y1="s11", y2="s0", interface=True, skip=f".L{uniq}_slow", call=f".L{uniq}_01_call", uniq=uniq, range=range)
                               // if s9 := shorty, then maybe (a2, fa0) := fp[D] or fp[CCCC + 1]
                               // (xINST clobbered, if taking this fast path)
.L${uniq}_01_call:
   jalr s8                     // args a0, a1, and t0, and maybe a2, fa0
   beqz s9, .L${uniq}_next_op  // no shorty, no scalar return
%  maybe_float_returned(shorty="s9", z0="t0", z1="t1", uniq=f"{uniq}_0")
                               // a0 := fa0 if float return
   j .L${uniq}_next_op

.L${uniq}_slow:
%  get_shorty_for_interface_save_a0_a1_t0(shorty="s9", y0="s10", y1="s11", y2="s0")
%  if range == 'Range':
%    slow_setup_args_range(shorty="s9", vC="s7", z0="s10", z1="t1", z2="t2", z3="t3", z4="t4", z5="t5", z6="t6", z7="s11", uniq=uniq)
%  else:
%    slow_setup_args(shorty="s9", vregs="s7", z0="s10", z1="t1", z2="t2", z3="t3", z4="t4", z5="t5", z6="t6", uniq=uniq)
   jalr s8                     // args (regular) a0 - a5, fa0 - fa4, t0, (range) a0 - a7, fa0 - fa7, t0
%  maybe_float_returned(shorty="s9", 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


// NterpInvokePolymorphic
// a0: shorty
// a1: receiver this
// s7: (regular) vreg ids F|E|D|C, (range) vreg id CCCC
%def nterp_invoke_polymorphic(uniq="invoke_polymorphic", range=""):
%  if range == "Range":
%    slow_setup_args_range(shorty="a0", vC="s7", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4", z5="t5", z6="t6", z7="s8", uniq=uniq)
%  else:
%    slow_setup_args(shorty="a0", vregs="s7", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4"z5="t5", z6="t6", uniq=uniq)
   // Managed ABI argument regs get spilled to stack and consumed by artInvokePolymorphic.
   call art_quick_invoke_polymorphic  // args a1 - a7, fa0 - fa7, and maybe stack
   // Note: If float return, artInvokePolymorphic will place the value in A0, as Nterp expects.
   FETCH_ADVANCE_INST 4
   GET_INST_OPCODE t0
   GOTO_OPCODE t0


// NterpInvokeCustom
// a0: BBBB
// s7: shorty
// s8: (regular) vreg ids F|E|D|C, (range) vreg id CCCC
%def nterp_invoke_custom(uniq="invoke_custom", range=""):
%  if range == "Range":
%    slow_setup_args_range(shorty="s7", vC="s8", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4", z5="t5", z6="t6", z7="s9", arg_start="0", uniq=uniq)
%  else:
%    slow_setup_args(shorty="s7", vregs="s8", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4"z5="t5", z6="t6", arg_start="0", uniq=uniq)
   // Managed ABI argument regs get spilled to stack and consumed by artInvokeCustom.
   call art_quick_invoke_custom  // args a0 - a7, fa0 - fa7, and maybe stack
   // Note: If float return, artInvokeCustom will place the value in A0, as Nterp expects.
   FETCH_ADVANCE_INST 3
   GET_INST_OPCODE t0
   GOTO_OPCODE t0


// NterpInvokeVirtualRange
// a0: ArtMethod*
// a1: this
// s7: vreg id CCCC
%def nterp_invoke_virtual_range():
%  nterp_invoke_direct(uniq="invoke_virtual_range", range="Range")


// NterpInvokeSuperRange
// a0: ArtMethod*
// a1: this
// s7: vreg id CCCC
%def nterp_invoke_super_range():
%  nterp_invoke_direct(uniq="invoke_super_range", range="Range")


// NterpInvokeDirectRange
// Hardcoded:
// a0: ArtMethod*
// a1: this
// s7: vreg id CCCC
%def nterp_invoke_direct_range():
%  nterp_invoke_direct(uniq="invoke_direct_range", range="Range")


// NterpInvokeStringInitRange
// a0: ArtMethod*
// s7: vreg id CCCC
%def nterp_invoke_string_init_range():
%  nterp_invoke_string_init(uniq="invoke_string_init_range", range="Range")


// NterpInvokeStaticRange
// a0: ArtMethod*
// s7: vreg id CCCC
%def nterp_invoke_static_range():
%  nterp_invoke_static(uniq="invoke_static_range", range="Range")


// NterpInvokeInterfaceRange
// a0: the target interface method
//     - ignored in nterp-to-nterp transfer
//     - preserved through shorty calls
//     - side-loaded in T0 as a "hidden argument" in managed ABI transfer
// a1: this
// a2: ArtMethod*
// s7: vreg id CCCC
%def nterp_invoke_interface_range():
%  nterp_invoke_interface(uniq="invoke_interface_range", range="Range")


// NterpInvokePolymorphicRange
%def nterp_invoke_polymorphic_range():
%  nterp_invoke_polymorphic(uniq="invoke_polymorphic_range", range="Range")


// NterpInvokeCustomRange
%def nterp_invoke_custom_range():
%  nterp_invoke_custom(uniq="invoke_custom_range", range="Range")


// fast path and slow path helpers


// Input
// - quick: quick code ptr
// Temporaries: z0
%def try_nterp(quick="", z0=""skip=""):
   lla $z0, ExecuteNterpImpl
   bne $z0, $quick, $skip


// Hardcoded
// - a0: ArtMethod*
// - xINST
// Input
// - v_fedc: vreg ids F|E|D|C
// Temporaries: z0, z1
%def try_simple_args(v_fedc="", z0="", z1="", arg_start="1"skip="", uniq=""):
   lwu $z0, ART_METHOD_ACCESS_FLAGS_OFFSET(a0)
   // The meaning of nterp-invoke-fast-path-flag for RISC-V diverges from other ISAs.
   BRANCH_IF_BIT_CLEAR $z0, $z0, ART_METHOD_NTERP_INVOKE_FAST_PATH_FLAG_BIT, $skip

   srliw $z0, xINST, 12              // z0 := A
%  if arg_start == "0":
     beqz $z0, .L${uniq}_simple_done  // A = 0: no further args.
   li $z1, 2
   blt $z0, $z1, .L${uniq}_simple_1  // A = 1
   beq $z0, $z1, .L${uniq}_simple_2  // A = 2
   li $z1, 4
   blt $z0, $z1, .L${uniq}_simple_3  // A = 3
   beq $z0, $z1, .L${uniq}_simple_4  // A = 4
   // A = 5
   srliw $z1, xINST, 8               // z1 := A|G
   andi $z1, $z1, 0xF                // z1 := G
   GET_VREG_OBJECT a5, $z1
.L${uniq}_simple_4:
   srliw $z1, $v_fedc, 12            // z1 := F
   GET_VREG_OBJECT a4, $z1
.L${uniq}_simple_3:
   srliw $z1, $v_fedc, 8             // z1 := F|E
   andi $z1, $z1, 0xF                // z1 := E
   GET_VREG_OBJECT a3, $z1
.L${uniq}_simple_2:
   srliw $z1, $v_fedc, 4             // z1 := F|E|D
   andi $z1, $z1, 0xF                // z1 := D
   GET_VREG_OBJECT a2, $z1
.L${uniq}_simple_1:
%  if arg_start == "0":
     andi $z1, $v_fedc, 0xF          // z1 := C
     GET_VREG_OBJECT a1, $z1
     // instance: a1 already set to "this"
.L${uniq}_simple_done:
%    pass

// Range variant.
%def try_simple_args_range(vC="", z0="", z1="", z2="", z3="", z4=""skip="", arg_start="1", uniq=""):
   lwu $z0, ART_METHOD_ACCESS_FLAGS_OFFSET(a0)
   // The meaning of nterp-invoke-fast-path-flag for RISC-V diverges from other ISAs.
   BRANCH_IF_BIT_CLEAR $z0, $z0, ART_METHOD_NTERP_INVOKE_FAST_PATH_FLAG_BIT, $skip

   srliw $z0, xINST, 8                 // z0 := AA
%  if arg_start == "0":  # static:
     beqz $z0, .L${uniq}_simple_done   // AA = 0: no further args.
     sh2add $z1, $vC, xFP              // z1 := &FP[CCCC]
     li $z2, 2
     blt $z0, $z2, .L${uniq}_simple_1  // AA = 1
%  else:  # instance:
     li $z2, 2
     blt $z0, $z2, .L${uniq}_simple_done  // AA = 1, and a1 already loaded.
     sh2add $z1, $vC, xFP               // z1 := &FP[CCCC]
   // Here: z0, z1, z2 same values for static vs instance.
   beq $z0, $z2, .L${uniq}_simple_2  // AA = 2
   li $z2, 4
   blt $z0, $z2, .L${uniq}_simple_3  // AA = 3
   beq $z0, $z2, .L${uniq}_simple_4  // AA = 4
   li $z2, 6
   blt $z0, $z2, .L${uniq}_simple_5  // AA = 5
   beq $z0, $z2, .L${uniq}_simple_6  // AA = 6
   li $z2, 7
   beq $z0, $z2, .L${uniq}_simple_7  // AA = 7

   // AA >= 8: store in stack. Load/store from FP[CCCC + 7] upwards.
   slli $z2, $z0, 63                 // z2 := negative if z0 bit #0 is set (odd)
   sh2add $z0, $z0, $z1              // z0 := loop guard at top of stack
   addi $z3, $z1, 7*4                // z3 := &FP[CCCC + 7]
   addi $z4, sp, __SIZEOF_POINTER__ + 7*4
                                     // z4 := &OUT[CCCC + 7]
   bltz $z2, .L${uniq}_simple_loop_wide
                                     // if AA odd, branch to wide-copy
   lwu $z2, ($z3)
   sw $z2, ($z4)
   addi $z3, $z3, 4
   addi $z4, $z4, 4

.L${uniq}_simple_loop_wide:
   // TODO: Consider ensuring 64-bit stores are aligned.
   beq $z3, $z0, .L${uniq}_simple_7
   ld $z2, ($z3)
   sd $z2, ($z4)
   addi $z3, $z3, 8
   addi $z4, $z4, 8
   j .L${uniq}_simple_loop_wide

   // Bottom 7 slots of OUT array never written; first args are passed with a1-a7.
.L${uniq}_simple_7:
   lwu a7, 6*4($z1)
.L${uniq}_simple_6:
   lwu a6, 5*4($z1)
.L${uniq}_simple_5:
   lwu a5, 4*4($z1)
.L${uniq}_simple_4:
   lwu a4, 3*4($z1)
.L${uniq}_simple_3:
   lwu a3, 2*4($z1)
.L${uniq}_simple_2:
   lwu a2, 1*4($z1)
.L${uniq}_simple_1:
%  if arg_start == "0":  # static:
     lwu a1, 0*4($z1)
%  pass
.L${uniq}_simple_done:
%  pass

// Check if a 0/1 arg invoke form is possible, set up a2 and fa0 if needed.
// If a return value expected, move possible float return to a0.
// Hardcoded: xINST, xPC, xFP, a0, a1, t0, fa0
// NOTE xINST clobbered if interface=True and we're taking the fast path.
// zN are temporaries, yN are callee-save
%def try_01_args(vreg="", shorty="", z0="", z1="", z2="", y0="", y1="", y2="", interface=False, skip="", call="", uniq="", range=""):
%  if range == 'Range':
     srliw $y0, xINST, 8   // y0 := AA
%  else:
     srliw $y0, xINST, 12  // y0 := A
   addi $y0, $y0, -2       // y0 := A - 2 or (range) AA - 2
   bgtz $y0, $skip         // 2+ args: slow path
   beqz $y0, .L${uniq}_01_shorty  // this and 1 arg: determine arg type with shorty
   // 0 args
%  try_01_args_peek_next(z0=z0)  # z0 is zero if invoke has scalar return
   bnez $z0, $call         // Non-scalar return, 0 args: make the call.
   // Scalar return, 0 args: determine return type with shorty

.L${uniq}_01_shorty:
   // Get shorty, stash in callee-save to be available on return.
   // When getting shorty, stash this fast path's A0 and A1, then restore.
%  if interface:
     // xINST is a regular callee save. Safe: orig xINST value unused before FETCH_ADVANCE_INST.
%    get_shorty_for_interface_save_a0_a1_t0(shorty=shorty, y0=y1, y1=y2, y2="xINST")
%  else:
%    get_shorty_save_a0_a1(shorty=shorty, y0=y1, y1=y2)
   // shorty assigned
   bltz $y0, $call         // Scalar return, 0 args: make the call.
   // ins = 2: this and 1 arg. Load arg type.
   lb $z0, 1($shorty)      // z0 := first arg
   li $z1, 'L'             // ref type
%  if range == 'Range':
     sh2add $z2, $vreg, xFP  // z2 := &fp[CCCC]
     lwu a2, 4($z2)        // a2 := fp[CCCC + 1], zext
%  else:
     srliw $z2, $vreg, 4   // z2 := F|E|D
     andi $z2, $z2, 0xF    // z2 := D
     sh2add $z2, $z2, xFP  // z2 := &fp[D]
     lwu a2, ($z2)         // a2 := fp[D], zext
   beq $z0, $z1, $call     // ref type: LWU into a2
   // non-'L' type
   fmv.w.x fa0, a2         // overload of managed ABI, for one arg
   sext.w a2, a2           // scalar type: LW into a2
   // immediately followed by call


// Static variant.
%def try_01_args_static(vreg="", shorty="", z0="", z1="", z2="", y0="", y1=""skip="", call="", uniq="", range=""):
%  if range == 'Range':
     srliw $y0, xINST, 8     // y0 := AA
%  else:
     srliw $y0, xINST, 12    // y0 := A
   addi $y0, $y0, -1         // y0 := A - 1 or (range) AA - 1
   bgtz $y0, $skip           // 2+ args: slow path
   beqz $y0, .L${uniq}_01_shorty  // 1 arg: determine arg type with shorty
   // 0 args
%  try_01_args_peek_next(z0=z0)  # z0 is zero if invoke has scalar return
   bnez $z0, $call           // Non-scalar return, 0 args: make the call.
   // Scalar return, 0 args: determine return type with shorty.

.L${uniq}_01_shorty:
   // Get shorty, stash in callee-save to be available on return.
   // When getting shorty, stash this fast path's A0 then restore.
%  get_shorty_save_a0(shorty=shorty, y0=y1)
   // shorty assigned
   bltz $y0, $call           // Scalar return, 0 args: make the call.
   // ins = 1: load arg type
   lb $z0, 1($shorty)        // z0 := first arg
   li $z1, 'L'               // ref type
%  if range == 'Range':
     sh2add $z2, $vreg, xFP  // z2 := &fp[CCCC]
%  else:
     andi $z2, $vreg, 0xF    // z2 := C
     sh2add $z2, $z2, xFP    // z2 := &fp[C]
   lwu a1, ($z2)             // a1 := fp[C] or (range) fp[CCCC], zext
   beq $z0, $z1, $call       // ref type: LWU into a1
   // non-'L' type
   fmv.w.x fa0, a1           // overload of managed ABI, for one arg
   sext.w a1, a1             // scalar type: LW into a1
   // immediately followed by call


%def try_01_args_peek_next(z0=""):
   FETCH $z0, count=3, width=8, byte=0
                                // z0 := next op
   andi $z0, $z0, ~1            // 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:
%  pass


// Hardcoded:
// - a0: ArtMethod*
// - a1: this
// Callee-saves: y0, y1
%def get_shorty_save_a0_a1(shorty="", y0="", y1=""):
   mv $y1, a1
   mv $y0, a0
   call NterpGetShorty  // arg a0
   mv $shorty, a0
   mv a0, $y0
   mv a1, $y1


// Static variant.
// Hardcoded:
// - a0: ArtMethod*
// Callee-saves: y0
%def get_shorty_save_a0(shorty="", y0=""):
   mv $y0, a0
   call NterpGetShorty  // arg a0
   mv $shorty, a0
   mv a0, $y0


// Interface variant.
// Hardcoded:
// - a0: ArtMethod*
// - a1: this
// - t0: "hidden argument"
// Callee-saves: y0, y1, y2
%def get_shorty_for_interface_save_a0_a1_t0(shorty="", y0="", y1="", y2=""):
   mv $y2, t0
   mv $y1, a1
   mv $y0, a0
   ld a0, (sp)            // a0 := caller ArtMethod*
   FETCH reg=a1, count=1  // a1 := BBBB method idx
   call NterpGetShortyFromMethodId
   mv $shorty, a0
   mv a0, $y0
   mv a1, $y1
   mv t0, $y2


// Hardcoded: xFP, xREFS
// Starting with vreg index 0, replace any old reference with new reference.
%def subst_vreg_references(old="", new="", z0="", z1="", z2="", uniq=""):
   mv $z0, xFP               // z0 := &fp[0]
   mv $z1, xREFS             // z1 := &refs[0]
.L${uniq}_subst_try:
   lwu $z2, ($z1)
   bne $z2, $old, .L${uniq}_subst_next
   sw $new, ($z0)
   sw $new, ($z1)
.L${uniq}_subst_next:
   addi $z0, $z0, 4
   addi $z1, $z1, 4
   bne $z1, xFP, .L${uniq}_subst_try


// Hardcoded
// - a0: ArtMethod*
// - a1: this
// Input
// - vregs: F|E|D|C from dex
%def slow_setup_args(shorty="", vregs="", z0="", z1="", z2="", z3="", z4="", z5="", z6="", arg_start="1", uniq=""):
   srliw $z0, xINST, 12     // z0 := A
   li $z1, 5
   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 $vregs, $z1, $vregs  // vregs := G|F|E|D|C

.L${uniq}_slow_gpr:
   addi $z0, $shorty, 1     // z0 := first arg of shorty
   srliw $z1, $vregs, 4*$arg_start
                            // z1 := (instance) F|E|D or G|F|E|D, (static) F|E|D|C or G|F|E|D|C
   li $z2, 'D'              // double
   li $z3, 'F'              // float
   li $z4, 'J'              // long
   li $z5, 'L'              // ref
   // 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, D=z2, F=z3, J=z4, L=z5, z0=z6, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_0")
%  load_vreg_in_gpr(gpr="a2", shorty=z0, vregs=z1, D=z2, F=z3, J=z4, L=z5, z0=z6, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_1")
%  load_vreg_in_gpr(gpr="a3", shorty=z0, vregs=z1, D=z2, F=z3, J=z4, L=z5, z0=z6, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_2")
%  load_vreg_in_gpr(gpr="a4", shorty=z0, vregs=z1, D=z2, F=z3, J=z4, L=z5, z0=z6, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_3")
%  load_vreg_in_gpr(gpr="a5", shorty=z0, vregs=z1, D=z2, F=z3, J=z4, L=z5, z0=z6, 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, $vregs, 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, D=z2, F=z3, J=z4, z0=z5, done=f".L{uniq}_slow_done", uniq=f"{uniq}_0")
%  load_vreg_in_fpr(fpr="fa1", shorty=z0, vregs=z1, D=z2, F=z3, J=z4, z0=z5, done=f".L{uniq}_slow_done", uniq=f"{uniq}_1")
%  load_vreg_in_fpr(fpr="fa2", shorty=z0, vregs=z1, D=z2, F=z3, J=z4, z0=z5, done=f".L{uniq}_slow_done", uniq=f"{uniq}_2")
%  load_vreg_in_fpr(fpr="fa3", shorty=z0, vregs=z1, D=z2, F=z3, J=z4, z0=z5, 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, D=z2, F=z3, J=z4, z0=z5, done=f".L{uniq}_slow_done", uniq=f"{uniq}_4")
%  pass
.L${uniq}_slow_done:
%  pass


// String-init variant: up to 4 args, no long/double/float args.
// Ref args ('L') loaded with LW *must* apply ZEXT.W to avoid subtle address bugs.
%def slow_setup_args_string_init(shorty="", v_fedc="", z0="", z1="", z2="", uniq=""):
   srliw $z0, xINST, 12            // z0 := A; possible values 1-5
   li $z1, 2
   blt $z0, $z1, .L${uniq}_slow_1  // A = 1
   li $z2, 'L'                     // z2 := ref type
   beq $z0, $z1, .L${uniq}_slow_2  // A = 2
   li $z1, 4
   blt $z0, $z1, .L${uniq}_slow_3  // A = 3
   beq $z0, $z1, .L${uniq}_slow_4  // A = 4

   // A = 5
   srliw $z0, xINST, 8             // z0 := A|G
   andi $z0, $z0, 0xF              // z0 := G
%  get_vreg("a4", z0)
   lb $z1, 4($shorty)              // shorty RDEFG
   bne $z1, $z2, .L${uniq}_slow_4
   zext.w a4, a4
.L${uniq}_slow_4:
   srliw $z1, $v_fedc, 12          // z1 := F
%  get_vreg("a3", z1)
   lb $z1, 3($shorty)              // shorty RDEF
   bne $z1, $z2, .L${uniq}_slow_3
   zext.w a3, a3
.L${uniq}_slow_3:
   srliw $z1, $v_fedc, 8           // z1 := F|E
   andi $z1, $z1, 0xF              // z1 := E
%  get_vreg("a2", z1)
   lb $z1, 2($shorty)              // shorty RDE
   bne $z1, $z2, .L${uniq}_slow_2
   zext.w a2, a2
.L${uniq}_slow_2:
   srliw $z1, $v_fedc, 4           // z1 := F|E|D
   andi $z1, $z1, 0xF              // z1 := D
%  get_vreg("a1", z1)
   lb $z1, 1($shorty)              // shorty RD
   bne $z1, $z2, .L${uniq}_slow_1
   zext.w a1, a1
.L${uniq}_slow_1:
   // "this" never read in string-init
%  pass


// Range and static-range variant.
// Hardcoded
// - (caller) xPC, xINST, xFP
// - (callee) sp
// Input
// - vC: CCCC from dex
%def slow_setup_args_range(shorty="", vC="", z0="", z1="", z2="", z3="", z4="", z5="", z6="", z7="", arg_start="1", uniq=""):
   addi $z0, $shorty, 1       // z0 := first arg of shorty
   addi $z1, $vC, $arg_start  // z1 := (instance) CCCC+1, (static) CCCC
   mv $z2, zero               // z2 := is_out_stack_needed false
   li $z3, 'D'                // double
   li $z4, 'F'                // float
   li $z5, 'J'                // long
   li $z6, 'L'                // ref

   // linear scan through shorty: extract non-float vregs
%  if arg_start == "0":  # static can place vCCCC into a1; instance already loaded "this" into a1.
%    load_vreg_in_gpr_range(gpr="a1", shorty=z0, idx=z1, D=z3, F=z4, J=z5, L=z6, z0=z7, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_1")
%  load_vreg_in_gpr_range(gpr="a2", shorty=z0, idx=z1, D=z3, F=z4, J=z5, L=z6, z0=z7, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_2")
%  load_vreg_in_gpr_range(gpr="a3", shorty=z0, idx=z1, D=z3, F=z4, J=z5, L=z6, z0=z7, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_3")
%  load_vreg_in_gpr_range(gpr="a4", shorty=z0, idx=z1, D=z3, F=z4, J=z5, L=z6, z0=z7, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_4")
%  load_vreg_in_gpr_range(gpr="a5", shorty=z0, idx=z1, D=z3, F=z4, J=z5, L=z6, z0=z7, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_5")
%  load_vreg_in_gpr_range(gpr="a6", shorty=z0, idx=z1, D=z3, F=z4, J=z5, L=z6, z0=z7, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_6")
%  load_vreg_in_gpr_range(gpr="a7", shorty=z0, idx=z1, D=z3, F=z4, J=z5, L=z6, z0=z7, done=f".L{uniq}_slow_fpr", uniq=f"{uniq}_7")
%  is_out_stack_needed(needed=z2, shorty=z0, D=z3, F=z4, z0=z1, uniq=uniq)

.L${uniq}_slow_fpr:
   addi $z0, $shorty, 1       // z0 := first arg of shorty
   addi $z1, $vC, $arg_start  // z1 := (instance) CCCC+1, (static) CCCC
   // linear scan through shorty: extract float/double vregs
%  load_vreg_in_fpr_range(fpr="fa0", shorty=z0, idx=z1, D=z3, F=z4, J=z5, z0=z6, done=f".L{uniq}_slow_stack", uniq=f"{uniq}_0")
%  load_vreg_in_fpr_range(fpr="fa1", shorty=z0, idx=z1, D=z3, F=z4, J=z5, z0=z6, done=f".L{uniq}_slow_stack", uniq=f"{uniq}_1")
%  load_vreg_in_fpr_range(fpr="fa2", shorty=z0, idx=z1, D=z3, F=z4, J=z5, z0=z6, done=f".L{uniq}_slow_stack", uniq=f"{uniq}_2")
%  load_vreg_in_fpr_range(fpr="fa3", shorty=z0, idx=z1, D=z3, F=z4, J=z5, z0=z6, done=f".L{uniq}_slow_stack", uniq=f"{uniq}_3")
%  load_vreg_in_fpr_range(fpr="fa4", shorty=z0, idx=z1, D=z3, F=z4, J=z5, z0=z6, done=f".L{uniq}_slow_stack", uniq=f"{uniq}_4")
%  load_vreg_in_fpr_range(fpr="fa5", shorty=z0, idx=z1, D=z3, F=z4, J=z5, z0=z6, done=f".L{uniq}_slow_stack", uniq=f"{uniq}_5")
%  load_vreg_in_fpr_range(fpr="fa6", shorty=z0, idx=z1, D=z3, F=z4, J=z5, z0=z6, done=f".L{uniq}_slow_stack", uniq=f"{uniq}_6")
%  load_vreg_in_fpr_range(fpr="fa7", shorty=z0, idx=z1, D=z3, F=z4, J=z5, z0=z6, done=f".L{uniq}_slow_stack", uniq=f"{uniq}_7")
%  is_out_stack_needed_float(needed=z2, shorty=z0, D=z3, F=z4, z0=z1, uniq=uniq)

.L${uniq}_slow_stack:
   beqz $z2, .L${uniq}_slow_done  // No stack needed, skip it. Otherwise copy-paste it all with LD/SD.
   addi $z0, sp, 8            // z0 := base addr of out array
   sh2add $z1, $vC, xFP       // z1 := base addr of FP[CCCC]
   srliw $z2, xINST, 8        // z2 := AA, vreg count
   sh2add $z2, $z2, $z1       // z2 := loop guard, addr of one slot past top of xFP array
%  copy_vregs_to_out(out=z0, fp=z1, fp_top=z2, z0=z3, uniq=uniq)
.L${uniq}_slow_done:
%  pass


// String-init variant: up to 4 args, no long/float/double args.
// Ref args ('L') loaded with LW *must* apply ZEXT.W to avoid subtle address bugs.
%def slow_setup_args_string_init_range(shorty="", vC="", z0="", z1="", z2="", z3="", uniq=""):
   srliw $z0, xINST, 8             // z0 := AA; possible values 1-5
   li $z1, 2
   blt $z0, $z1, .L${uniq}_slow_1  // A = 1
   sh2add $z2, $vC, xFP            // z2 := &fp[CCCC]
   li $z3, 'L'                     // z3 := ref type
   beq $z0, $z1, .L${uniq}_slow_2  // A = 2
   li $z1, 4
   blt $z0, $z1, .L${uniq}_slow_3  // A = 3
   beq $z0, $z1, .L${uniq}_slow_4  // A = 4
   // A = 5
   lw a4, 4*4($z2)
   lb $z1, 4($shorty)
   bne $z1, $z3, .L${uniq}_slow_4
   zext.w a4, a4
.L${uniq}_slow_4:
   lw a3, 3*4($z2)
   lb $z1, 3($shorty)
   bne $z1, $z3, .L${uniq}_slow_3
   zext.w a3, a3
.L${uniq}_slow_3:
   lw a2, 2*4($z2)
   lb $z1, 2($shorty)
   bne $z1, $z3, .L${uniq}_slow_2
   zext.w a2, a2
.L${uniq}_slow_2:
   lw a1, 1*4($z2)
   lb $z1, 1($shorty)
   bne $z1, $z3, .L${uniq}_slow_1
   zext.w a1, a1
.L${uniq}_slow_1:
   // "this" never read in string-init
%  pass


// 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="", D="", F="", J="", L="", z0="", 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
   beq $z0, $F, .L${uniq}_gpr_skip_4_bytes
   beq $z0, $D, .L${uniq}_gpr_skip_8_bytes

   andi $gpr, $vregs, 0xF    // gpr := vreg id
   beq $z0, $J, .L${uniq}_gpr_load_8_bytes
%  get_vreg(gpr, gpr)        #  gpr := 32-bit load
   bne $z0, $L, .L${uniq}_gpr_load_common
   zext.w $gpr, $gpr
.L${uniq}_gpr_load_common:
   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:
%  pass


// 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="", D="", F="", J="", z0="", 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
   beq $z0, $F, .L${uniq}_fpr_load_4_bytes
   beq $z0, $D, .L${uniq}_fpr_load_8_bytes

   srliw $vregs, $vregs, 4   // shift out a skipped arg, one vreg
   bne $z0, $J, .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 $z0, $vregs, 0xF
%  get_vreg_float(fpr, z0)
   srliw $vregs, $vregs, 4   // shift out the processed arg, one vreg
   j .L${uniq}_fpr_set
.L${uniq}_fpr_load_8_bytes:
   andi $z0, $vregs, 0xF
   GET_VREG_DOUBLE $fpr, $z0
   srliw $vregs, $vregs, 8   // shift out the processed arg, a vreg pair
.L${uniq}_fpr_set:
%  pass


// Range variant
%def load_vreg_in_gpr_range(gpr="", shorty="", idx="", D="", F="", J="", L="", z0="", done="", uniq=""):
.L${uniq}_gpr_range_find:
   lb $z0, ($shorty)           // z0 := next shorty arg
   addi $shorty, $shorty, 1    // increment char ptr
   beqz $z0, $done             // z0 == \0
   beq $z0, $F, .L${uniq}_gpr_range_skip_1_vreg
   beq $z0, $D, .L${uniq}_gpr_range_skip_2_vreg

   beq $z0, $J, .L${uniq}_gpr_range_load_2_vreg
%  get_vreg(gpr, idx)
   bne $z0, $L, .L${uniq}_gpr_range_load_common
   zext.w $gpr, $gpr
.L${uniq}_gpr_range_load_common:
   addi $idx, $idx, 1
   j .L${uniq}_gpr_range_done
.L${uniq}_gpr_range_load_2_vreg:
   GET_VREG_WIDE $gpr, $idx
   addi $idx, $idx, 2
   j .L${uniq}_gpr_range_done

.L${uniq}_gpr_range_skip_2_vreg:
   addi $idx, $idx, 1
.L${uniq}_gpr_range_skip_1_vreg:
   addi $idx, $idx, 1
   j .L${uniq}_gpr_range_find
.L${uniq}_gpr_range_done:
%  pass


// Range variant.
%def load_vreg_in_fpr_range(fpr="", shorty="", idx="", D="", F="", J="", z0="", done=""uniq=""):
.L${uniq}_fpr_range_find:
   lb $z0, ($shorty)         // z0 := next shorty arg
   addi $shorty, $shorty, 1  // increment char ptr
   beqz $z0, $done           // z0 == \0
   beq $z0, $F, .L${uniq}_fpr_range_load_4_bytes
   beq $z0, $D, .L${uniq}_fpr_range_load_8_bytes

   addi $idx, $idx, 1        // increment idx
   bne $z0, $J, .L${uniq}_fpr_range_find
   addi $idx, $idx, 1        // increment once more for J
   j .L${uniq}_fpr_range_find

.L${uniq}_fpr_range_load_4_bytes:
   mv $z0, $idx
%  get_vreg_float(fpr, z0)
   addi $idx, $idx, 1
   j .L${uniq}_fpr_range_set
.L${uniq}_fpr_range_load_8_bytes:
   mv $z0, $idx
   GET_VREG_DOUBLE $fpr, $z0
   addi $idx, $idx, 2
.L${uniq}_fpr_range_set:
%  pass


%def is_out_stack_needed(needed="", shorty="", D="", F="", z0="", uniq=""):
.L${uniq}_scan_arg:
   lb $z0, ($shorty)
   addi $shorty, $shorty, 1
   beqz $z0, .L${uniq}_scan_done
   beq $z0, $F, .L${uniq}_scan_arg
   beq $z0, $D, .L${uniq}_scan_arg
   li $needed, 1
.L${uniq}_scan_done:
%  pass


%def is_out_stack_needed_float(needed="", shorty="", D="", F="", z0="", uniq=""):
   bnez $needed, .L${uniq}_scan_float_done
.L${uniq}_scan_float_arg:
   lb $z0, ($shorty)
   addi $shorty, $shorty, 1
   beqz $z0, .L${uniq}_scan_float_done
   beq $z0, $F, .L${uniq}_scan_float_found
   beq $z0, $D, .L${uniq}_scan_float_found
   j .L${uniq}_scan_float_arg
.L${uniq}_scan_float_found:
   li $needed, 1
.L${uniq}_scan_float_done:
%  pass


%def copy_vregs_to_out(out="", fp="", fp_top="", z0="", uniq=""):
   sub $z0, $fp_top, $fp  // z0 := byte range
   BRANCH_IF_BIT_CLEAR $z0, $z0, 2, .L${uniq}_copy_wide
                          // branch if odd count of slots
   lwu $z0, ($fp)
   sw $z0, ($out)
   addi $fp, $fp, 4
   addi $out, $out, 4
.L${uniq}_copy_wide:
   beq $fp, $fp_top, .L${uniq}_copy_done
   ld $z0, ($fp)
   sd $z0, ($out)
   addi $fp, $fp, 8
   addi $out, $out, 8
   j .L${uniq}_copy_wide
.L${uniq}_copy_done:
%  pass


// NterpToNterpInstance
// a0: ArtMethod*
// a1: this
%def nterp_to_nterp_instance():
%  nterp_to_nterp(how_vC="in_a1", uniq="n2n_instance")


// NterpToNterpStringInit
// a0: ArtMethod*
%def nterp_to_nterp_string_init():
%  nterp_to_nterp(how_vC="skip", uniq="n2n_string_init")


// NterpToNterpStatic
// a0: ArtMethod*
%def nterp_to_nterp_static():
%  nterp_to_nterp(a1_instance=False, how_vC="load", uniq="n2n_static")


// NterpToNterpInstanceRange
%def nterp_to_nterp_instance_range():
%  nterp_to_nterp(how_vC="in_a1", uniq="n2n_instance_range", range="Range")


// NterpToNterpStringInitRange
%def nterp_to_nterp_string_init_range():
%  nterp_to_nterp(how_vC="skip", uniq="n2n_string_init_range", range="Range")


// NterpToNterpStaticRange
%def nterp_to_nterp_static_range():
%  nterp_to_nterp(a1_instance=False, how_vC="load", uniq="n2n_static_range", range="Range")


// helpers


%def nterp_to_nterp(a1_instance=True, how_vC="", uniq="", range=""):
   .cfi_startproc
%  setup_nterp_frame(cfi_refs="23", refs="s8", fp="s9", pc="s10", regs="s11", spills_sp="t0", z0="t1", z1="t2", z2="t3", z3="t4", uniq=uniq)
       // s8  := callee xREFS
       // s9  := callee xFP
       // s10 := callee xPC
       // s11 := fp/refs vreg count
       // t0  := post-spills pre-frame sp (unused here)
       // sp  := post-frame callee sp
%  if range == 'Range':
%    n2n_arg_move_range(refs="s8", fp="s9", regs="s11", vC="s7", z0="t0", z1="t1", z2="t2", z3="t3", z4="t4", z5="t5", a1_instance=a1_instance, how_vC=how_vC, uniq=uniq)
%  else:
%    n2n_arg_move(refs="s8", fp="s9", pc="s10", regs="s11", v_fedc="s7", z0="t0", z1="t1"z2="t2", z3="t3", a1_instance=a1_instance, how_vC=how_vC, uniq=uniq)
   mv xREFS, s8
   mv xFP, s9
   mv xPC, s10
   CFI_DEFINE_DEX_PC_WITH_OFFSET(/*tmpReg*/CFI_TMP, /*dexReg*/CFI_DEX, /*dexOffset*/0)

   START_EXECUTING_INSTRUCTIONS
   .cfi_endproc


// See runtime/nterp_helpers.cc for a diagram of the setup.
// Hardcoded
// - a0 - ArtMethod*
// Input
// - \cfi_refs: dwarf register number of \refs, for CFI
// - \uniq: string to ensure unique symbolic labels between instantiations
// Output
// - sp: adjusted downward for callee-saves and nterp frame
// - \refs: callee xREFS
// - \fp: callee xFP
// - \pc: callee xPC
// - \regs: register count in \refs
// - \ins: in count
// - \spills_sp: stack pointer after reg spills
%def setup_nterp_frame(cfi_refs="", refs="", fp="", pc="", regs="", ins="zero", spills_sp="", z0="", z1="", z2="", z3="", uniq=""):
   // Check guard page for stack overflow.
   li $z0, -STACK_OVERFLOW_RESERVED_BYTES
   add $z0, $z0, sp
   ld zero, ($z0)

   INCREASE_FRAME NTERP_SIZE_SAVE_CALLEE_SAVES
                        // sp := sp + callee-saves
   SETUP_NTERP_SAVE_CALLEE_SAVES

   ld $pc, ART_METHOD_DATA_OFFSET_64(a0)
   FETCH_CODE_ITEM_INFO code_item=$pc, regs=$regs, outs=$z0, ins=$ins
                        // pc   := callee dex array
                        // regs := vreg count for fp array and refs array
                        // z0   := vreg count for outs array
                        // ins  := vreg count for ins array

   // Compute required frame size: ((2 * \regs) + \z0) * 4 + 24
   // - The register array and reference array each have \regs number of slots.
   // - The out array has \z0 slots.
   // - Each register slot is 4 bytes.
   // - Additional 24 bytes for 3 fields: saved frame pointer, dex pc, and ArtMethod*.
   sh1add $z1, $regs, $z0
   slli $z1, $z1, 2
   addi $z1, $z1, 24    // z1 := frame size, without alignment padding

   // compute new stack pointer
   sub $z1, sp, $z1
   // 16-byte alignment.
   andi $z1, $z1, ~0xF  // z1 := new sp

   // Set \refs to base of reference array. Align to pointer size for the frame pointer and dex pc
   // pointer, below the reference array.
   sh2add $z0, $z0, $z1  // z0 := out array size in bytes
   addi $z0, $z0, 28     //     + 24 bytes for 3 fields, plus 4 for alignment slack.
   andi $refs, $z0, -__SIZEOF_POINTER__
                         // refs := refs array base

   // Set \fp to base of register array, above the reference array. This region is already aligned.
   sh2add $fp, $regs, $refs
                         // fp := fp array base

   // Set up the stack pointer.
   mv $spills_sp, sp     // spills_sp := old sp
   .cfi_def_cfa_register $spills_sp
   mv sp, $z1            // sp := new sp
   sd $spills_sp, -8($refs)
   // The CFA rule is now a dwarf expression, because the nterp frame offset for SP is a dynamic
   // value, and thus SP cannot help compute CFA. For the duration of the nterp frame, CFI
   // directives cannot adjust this CFA rule, but may still capture CFI for register spills as
   // "register + offset" with a dwarf expression.
   CFI_DEF_CFA_BREG_PLUS_UCONST $cfi_refs, -8, NTERP_SIZE_SAVE_CALLEE_SAVES

   // Put nulls in reference array.
   beqz $regs, .L${uniq}_ref_zero_done
   mv $z0, $refs         // z0 := address iterator
.L${uniq}_ref_zero:
   // Write in 8-byte increments, so fp[0] gets zero'ed too, if \regs is odd.
   sd zero, ($z0)
   addi $z0, $z0, 8
   bltu $z0, $fp, .L${uniq}_ref_zero
.L${uniq}_ref_zero_done:
   // Save the ArtMethod*.
   sd a0, (sp)


// Hardcoded
// - (caller) xINST, xFP, xREFS, xPC
// - a0: ArtMethod*
// - a1: this, for instance invoke
%def n2n_arg_move(refs="", fp="", regs="", pc="", v_fedc="", z0="", z1="", z2="", z3="", a1_instance=True, how_vC="", uniq=""):
   srliw $z0, xINST, 12       // z0 := A (arg count)

%  if not a1_instance:
     beqz $z0, .L${uniq}_arg_done
   // A >= 1, decide and branch
   li $z1, 2
   sub $z2, $regs, $z0        // z2 := regs - A; vC's index in fp
   sh2add $z3, $z2, $fp       // z3 := addr of fp[C]
   sh2add $z2, $z2, $refs     // z2 := addr of refs[C]
   blt $z0, $z1, .L${uniq}_arg_1
   beq $z0, $z1, .L${uniq}_arg_2
   li $z1, 4
   blt $z0, $z1, .L${uniq}_arg_3
   beq $z0, $z1, .L${uniq}_arg_4

   // A = 5
   srliw $z0, xINST, 8
   andi $z0, $z0, 0xF         // z0 := G
%  get_vreg(z1, z0)           #  z1 := xFP[G]
   sw $z1, (4*4)($z3)         // fp[G] := z1
   GET_VREG_OBJECT $z0, $z0   // z0 := xREFS[G]
   sw $z0, (4*4)($z2)         // refs[G] := z0
.L${uniq}_arg_4:
   srliw $z0, $v_fedc, 12     // z0 := F
%  get_vreg(z1, z0)           #  z1 := xFP[F]
   sw $z1, (3*4)($z3)         // fp[F] := z1
   GET_VREG_OBJECT $z0, $z0   // z0 := xREFS[F]
   sw $z0, (3*4)($z2)         // refs[F] := z0
.L${uniq}_arg_3:
   srliw $z0, $v_fedc, 8      // z0 := F|E
   andi $z0, $z0, 0xF         // z0 := E
%  get_vreg(z1, z0)           #  z1 := xFP[E]
   sw $z1, (2*4)($z3)         // fp[E] := z1
   GET_VREG_OBJECT $z0, $z0   // z0 := xREFS[E]
   sw $z0, (2*4)($z2)         // refs[E] := z0
.L${uniq}_arg_2:
   srliw $z0, $v_fedc, 4      // z0 := F|E|D
   andi $z0, $z0, 0xF         // z0 := D
%  get_vreg(z1, z0)           #  z1 := xFP[D]
   sw $z1, (1*4)($z3)         // fp[D] := z1
   GET_VREG_OBJECT $z0, $z0   // z0 := xREFS[D]
   sw $z0, (1*4)($z2)         // refs[D] := z0
.L${uniq}_arg_1:
%  if how_vC == "in_a1":
     // a1 = xFP[C] from earlier stage of instance invoke
     sw a1, (0*4)($z3)        // fp[C] := a1
     sw a1, (0*4)($z2)        // refs[C] := a1
%  elif how_vC == "skip":
     // string init doesn't read "this"
%    pass
%  elif how_vC == "load":
     // static method loads vC just like other vregs
     andi $z0, $v_fedc, 0xF   // z0 := C
%    get_vreg(z1, z0)         #  z1 := xFP[C]
     sw $z1, (0*4)($z3)       // fp[C] := z1
     GET_VREG_OBJECT $z0, $z0  // z0 := xREFS[C]
     sw $z0, (0*4)($z2)       // refs[C] := z0
%  pass
.L${uniq}_arg_done:
%  pass


%def n2n_arg_move_range(refs="", fp="", regs="", vC="", z0="", z1="", z2="", z3="", z4="", z5="", a1_instance=True, how_vC="", uniq=""):
   srliw $z0, xINST, 8     // z0 := AA (arg count)

%  if not a1_instance:
     beqz $z0, .L${uniq}_arg_range_done
   // AA >= 1, iterator setup
   sub $z4, $regs, $z0     // z4 := regs - AA; starting idx in fp and refs
   sh2add $z1, $vC, xREFS  // z1 := addr of xREFS[CCCC]
   sh2add $z2, $vC, xFP    // z2 := addr of xFP[CCCC]
   sh2add $z3, $z4, $refs  // z3 := addr of refs[z4]
   sh2add $z4, $z4, $fp    // z4 := addr of fp[z4]

   BRANCH_IF_BIT_CLEAR $z0, $z0, 0, .L${uniq}_arg_range_copy_wide
                           // branch if AA is even
   // AA is odd, transfer one slot. Apply some optimizations.
%  if how_vC == "in_a1":
     sw a1, ($z3)
     sw a1, ($z4)
%  elif how_vC == "skip":
     // string init doesn't read "this"
%    pass
%  elif how_vC == "load":
     lw $z0, ($z1)
     lw $z5, ($z2)
     sw $z0, ($z3)
     sw $z5, ($z4)
   addi $z1, $z1, 4
   addi $z2, $z2, 4
   addi $z3, $z3, 4
   addi $z4, $z4, 4
.L${uniq}_arg_range_copy_wide:
   // Even count of vreg slots, apply LD/SD.
   beq $z3, $fp, .L${uniq}_arg_range_done  // terminate loop if refs[regs] == fp[0]
   ld $z0, ($z1)
   ld $z5, ($z2)
   sd $z0, ($z3)
   sd $z5, ($z4)
   addi $z1, $z1, 8
   addi $z2, $z2, 8
   addi $z3, $z3, 8
   addi $z4, $z4, 8
   j .L${uniq}_arg_range_copy_wide
.L${uniq}_arg_range_done:
%  pass


//
// Nterp entry point helpers
//


// Hardcoded:
// - a0: ArtMethod*
%def setup_ref_args_and_go(fp="", refs="", refs_end="", spills_sp="", z0="", z1="", done=""):
   // Store managed-ABI register args into fp/refs arrays.
%  store_ref_to_vreg(gpr="a1", fp=fp, refs=refs, refs_end=refs_end, done=done)
%  store_ref_to_vreg(gpr="a2", fp=fp, refs=refs, refs_end=refs_end, done=done)
%  store_ref_to_vreg(gpr="a3", fp=fp, refs=refs, refs_end=refs_end, done=done)
%  store_ref_to_vreg(gpr="a4", fp=fp, refs=refs, refs_end=refs_end, done=done)
%  store_ref_to_vreg(gpr="a5", fp=fp, refs=refs, refs_end=refs_end, done=done)
%  store_ref_to_vreg(gpr="a6", fp=fp, refs=refs, refs_end=refs_end, done=done)
%  store_ref_to_vreg(gpr="a7", fp=fp, refs=refs, refs_end=refs_end, done=done)
   // We drained arg registers, so continue from caller's stack.
   // A ref arg is 4 bytes, so the continuation offset is well known.
   addi $z0, $spills_sp, (NTERP_SIZE_SAVE_CALLEE_SAVES + 8 + 7*4)
       // z0 := out array base addr + 7 vreg slots
.Lentry_ref_stack:
   lwu $z1, ($z0)
   sw $z1, ($fp)
   sw $z1, ($refs)
   addi $z0, $z0, 4
   addi $fp, $fp, 4
   addi $refs, $refs, 4
   bne $refs, $refs_end, .Lentry_ref_stack

   j $done


%def store_ref_to_vreg(gpr="", fp="", refs="", refs_end="", done=""):
   sw $gpr, ($fp)
   sw $gpr, ($refs)
   addi $fp, $fp, 4
   addi $refs, $refs, 4
   beq $refs, $refs_end, $done


// \fp and \refs are used as array base addrs, unmodified.
%def store_gpr_to_vreg(gpr="", offset="", shorty="", fp="", refs="", z0="", z1="", D=""F="", J="", L="", next=""):
.Lentry_arg_${gpr}:
   lb $z0, ($shorty)         // z0 := shorty type
   addi $shorty, $shorty, 1  // Increment char ptr.
   beqz $z0, $next           // z0 = \0: finished shorty pass
   beq $z0, $D, .Lentry_arg_skip_double_${gpr}
   beq $z0, $F, .Lentry_arg_skip_float_${gpr}

   add $z1, $offset, $fp
   beq $z0, $J, .Lentry_arg_long_${gpr}
   sw $gpr, ($z1)
   bne $z0, $L, .Lentry_arg_finish_${gpr}
   add $z1, $offset, $refs
   sw $gpr, ($z1)
   j .Lentry_arg_finish_${gpr}
.Lentry_arg_skip_double_${gpr}:
   addi $offset, $offset, 4
.Lentry_arg_skip_float_${gpr}:
   addi $offset, $offset, 4
   j .Lentry_arg_${gpr}
.Lentry_arg_long_${gpr}:
   sd $gpr, ($z1)
   addi $offset, $offset, 4
.Lentry_arg_finish_${gpr}:
   addi $offset, $offset, 4


// \fp is used as array base addr, unmodified.
%def store_fpr_to_vreg(fpr="", offset="", shorty="", fp="", z0="", z1="", D="", F="", J="", next=""):
.Lentry_farg_${fpr}:
   lb $z0, ($shorty)         // z0 := shorty type
   addi $shorty, $shorty, 1  // Increment char ptr.
   beqz $z0, $next           // z0 = \0: finished shorty pass
   beq $z0, $D, .Lentry_farg_double_${fpr}
   beq $z0, $F, .Lentry_farg_float_${fpr}
   addi $offset, $offset, 4
   bne $z0, $J, .Lentry_farg_${fpr}
   addi $offset, $offset, 4
   j .Lentry_farg_${fpr}

.Lentry_farg_float_${fpr}:
   add $z1, $offset, $fp
   fsw $fpr, ($z1)
   j .Lentry_farg_finish_${fpr}
.Lentry_farg_double_${fpr}:
   add $z1, $offset, $fp
   fsd $fpr, ($z1)
   addi $offset, $offset, 4
.Lentry_farg_finish_${fpr}:
   addi $offset, $offset, 4


// \outs, \fp, \refs are used as iterators, modified.
%def store_outs_to_vregs(outs="", shorty="", fp="", refs="", z0="", z1="", D="", F="", J="", L="", next=""):
.Lentry_stack:
   lb $z0, ($shorty)         // z0 := next shorty arg spec
   addi $shorty, $shorty, 1  // Increment char ptr.
   beqz $z0, $next           // z0 == \0
   beq $z0, $F, .Lentry_stack_next_4
   beq $z0, $D, .Lentry_stack_next_8
   beq $z0, $J, .Lentry_stack_long
   // 32-bit arg
   lwu $z1, ($outs)
   sw $z1, ($fp)
   bne $z0, $L, .Lentry_stack_next_4
   // and also a ref
   sw $z1, ($refs)
.Lentry_stack_next_4:
   addi $outs, $outs, 4
   addi $fp, $fp, 4
   addi $refs, $refs, 4
   j .Lentry_stack
.Lentry_stack_long:
   ld $z1, ($outs)
   sd $z1, ($fp)
.Lentry_stack_next_8:
   addi $outs, $outs, 8
   addi $fp, $fp, 8
   addi $refs, $refs, 8
   j .Lentry_stack


// \outs, \fp are used as iterators, modified.
%def store_float_outs_to_vregs(outs="", shorty="", fp="", z0="", D="", F="", J="", next=""):
.Lentry_fstack:
   lb $z0, ($shorty)         // z0 := next shorty arg spec
   addi $shorty, $shorty, 1  // Increment char ptr.
   beqz $z0, $next           // z0 == \0
   beq $z0, $F, .Lentry_fstack_float
   beq $z0, $D, .Lentry_fstack_double
   beq $z0, $J, .Lentry_fstack_next_8
   // 32-bit arg
   addi $outs, $outs, 4
   addi $fp, $fp, 4
   j .Lentry_fstack
.Lentry_fstack_float:
   lwu $z0, ($outs)
   sw $z0, ($fp)
   addi $outs, $outs, 4
   addi $fp, $fp, 4
   j .Lentry_fstack
.Lentry_fstack_double:
   ld $z0, ($outs)
   sd $z0, ($fp)
.Lentry_fstack_next_8:
   addi $outs, $outs, 8
   addi $fp, $fp, 8
   j .Lentry_fstack


Messung V0.5 in Prozent
C=96 H=95 G=95

¤ Dauer der Verarbeitung: 0.35 Sekunden  (vorverarbeitet am  2026-06-29) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik