// 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 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 #1set (default method)
// (3) method index << 16 with LSB #0set (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
// 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")
// 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*
// 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")
// 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
// 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 lo32-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
.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
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)
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.