diff options
author | 2023-12-19 18:23:56 -0800 | |
---|---|---|
committer | 2024-01-25 22:23:26 +0000 | |
commit | 360b1d08e4eaca3e15255265ab534844f2208d61 (patch) | |
tree | 248540b99685f61e07be4fc9d8108145d3270fdc | |
parent | 6f30991b16d1dcd386b6073a41a314a0120c7f48 (diff) |
riscv64: new-instance, check-cast, instance-of, new-array
(1) setup
lunch aosp_riscv64-trunk-userdebug
export ART_TEST_SSH_USER=ubuntu
export ART_TEST_SSH_HOST=localhost
export ART_TEST_SSH_PORT=10001
export ART_TEST_ON_VM=true
. art/tools/buildbot-utils.sh
art/tools/buildbot-build.sh --target
# Create, boot and configure the VM.
art/tools/buildbot-vm.sh create
art/tools/buildbot-vm.sh boot
art/tools/buildbot-vm.sh setup-ssh # password: 'ubuntu'
art/tools/buildbot-cleanup-device.sh
art/tools/buildbot-setup-device.sh
art/tools/buildbot-sync.sh
(2) test
art/test.py --target -r --no-prebuild --ndebug --64 -j 12 --cdex-none --interpreter
Test: Run these opcodes against all interpreter
tests on a Linux RISC-V VM.
Test: Cuttlefish boot
Bug: 283082047
Change-Id: I460dde59fe6e9ecdb33bd74186d01d5890da222c
-rw-r--r-- | runtime/arch/riscv64/quick_entrypoints_riscv64.S | 4 | ||||
-rw-r--r-- | runtime/interpreter/mterp/riscv64/array.S | 33 | ||||
-rw-r--r-- | runtime/interpreter/mterp/riscv64/main.S | 1 | ||||
-rw-r--r-- | runtime/interpreter/mterp/riscv64/object.S | 262 | ||||
-rw-r--r-- | runtime/nterp_helpers.cc | 4 |
5 files changed, 278 insertions, 26 deletions
diff --git a/runtime/arch/riscv64/quick_entrypoints_riscv64.S b/runtime/arch/riscv64/quick_entrypoints_riscv64.S index bf00278a26..141974e14a 100644 --- a/runtime/arch/riscv64/quick_entrypoints_riscv64.S +++ b/runtime/arch/riscv64/quick_entrypoints_riscv64.S @@ -1836,9 +1836,9 @@ END \name lwu \temp0, MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET(\temp0) srli \temp0, \temp0, PRIMITIVE_TYPE_SIZE_SHIFT_SHIFT // Component size shift is in high 16 // bits. - // xCount is holding a 32 bit value, + zext.w \temp1, \count // From \count we use a 32 bit value, // it can not overflow. - sll \temp1, \count, \temp0 // Calculate data size + sll \temp1, \temp1, \temp0 // Calculate data size // Add array data offset and alignment. addi \temp1, \temp1, (MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK) #if MIRROR_LONG_ARRAY_DATA_OFFSET != MIRROR_INT_ARRAY_DATA_OFFSET + 4 diff --git a/runtime/interpreter/mterp/riscv64/array.S b/runtime/interpreter/mterp/riscv64/array.S index f4d92f1c8b..769747efa9 100644 --- a/runtime/interpreter/mterp/riscv64/array.S +++ b/runtime/interpreter/mterp/riscv64/array.S @@ -20,7 +20,38 @@ // Format 22c: B|A|23 CCCC // Construct a new array of the indicated type and size. The type must be an array type. %def op_new_array(): - unimp + EXPORT_PC + srliw s8, xINST, 8 // s8 := B|A + srliw s7, xINST, 12 // s7 := B + andi s8, s8, 0xF // s8 := A + FETCH_FROM_THREAD_CACHE /*resolved klass*/a0, .L${opcode}_miss, t0, t1 + TEST_IF_MARKING t0, .L${opcode}_mark +.L${opcode}_resume: + +% get_vreg("a1", "s7") # a1 := fp[B] length + ld t0, THREAD_ALLOC_ARRAY_ENTRYPOINT_OFFSET(xSELF) + jalr t0 // args a0 (klass), a1 (length) + // return a0 := new-array + fence w, w // constructor fence; main.S has details + + SET_VREG_OBJECT a0, s8, z0=t0 // refs[A] := new-array + FETCH_ADVANCE_INST 2 + GET_INST_OPCODE t0 + GOTO_OPCODE t0 + +.L${opcode}_mark: + call art_quick_read_barrier_mark_reg10 // a0, klass + j .L${opcode}_resume + +.L${opcode}_miss: + EXPORT_PC + mv a0, xSELF + ld a1, (sp) // caller ArtMethod* + mv a2, xPC + call nterp_get_class + j .L${opcode}_resume + + // filled-new-array {vC, vD, vE, vF, vG}, type@BBBB diff --git a/runtime/interpreter/mterp/riscv64/main.S b/runtime/interpreter/mterp/riscv64/main.S index b799380a04..2490716f9a 100644 --- a/runtime/interpreter/mterp/riscv64/main.S +++ b/runtime/interpreter/mterp/riscv64/main.S @@ -626,6 +626,7 @@ NAME_END nterp_helper EndExecuteNterpImpl: // Entrypoints into runtime. +NTERP_TRAMPOLINE nterp_allocate_object, NterpAllocateObject NTERP_TRAMPOLINE nterp_filled_new_array, NterpFilledNewArray NTERP_TRAMPOLINE nterp_filled_new_array_range, NterpFilledNewArrayRange NTERP_TRAMPOLINE nterp_get_class, NterpGetClass diff --git a/runtime/interpreter/mterp/riscv64/object.S b/runtime/interpreter/mterp/riscv64/object.S index 7c8e10a6df..43bb31f1e9 100644 --- a/runtime/interpreter/mterp/riscv64/object.S +++ b/runtime/interpreter/mterp/riscv64/object.S @@ -1,17 +1,233 @@ +// check-cast vAA, type@BBBB +// Format 21c: AA|1f BBBB +// Throw a ClassCastException if the reference in the given register cannot be cast to the indicated +// type. %def op_check_cast(): - unimp + FETCH_FROM_THREAD_CACHE /*expected klass*/a1, .L${opcode}_miss, t0, t1 +.L${opcode}_miss_resume: + + srliw t0, xINST, 8 // t0 := AA +% get_vreg("a0", "t0", is_unsigned=True) # a0 := fp[AA], zext + beqz a0, .L${opcode}_next // null + lwu a2, MIRROR_OBJECT_CLASS_OFFSET(a0) // a2 := actual klass + UNPOISON_HEAP_REF a2 + // Fast path: compare without read barrier. + bne a1, a2, .L${opcode}_slow + +.L${opcode}_next: + FETCH_ADVANCE_INST 2 + GET_INST_OPCODE t0 + GOTO_OPCODE t0 + +.L${opcode}_miss: + EXPORT_PC + mv a0, xSELF + ld a1, (sp) // caller ArtMethod* + mv a2, xPC + call nterp_get_class + mv a1, a0 + j .L${opcode}_miss_resume -%def op_check_cast_slow_path(): - unimp +.L${opcode}_slow: + // A0 and A1 in position for quick call. +% slow_path = add_slow_path(op_check_cast_slow_path, "t0", "t1", "t2") + tail $slow_path // slow offset exceeds branch imm + // args a0, a1, a2 + + +// Checks cases for (1) interface, (2) array, and (3) super classes. +// Hardcoded: a0 (obj), a1 (expected klass), a2 (actual klass) +// +// Note. We don't do read barriers for simplicity. However, this means that fetched objects may be a +// from-space reference. That's OK as we only fetch constant information from the references. This +// also means that some of the comparisons below may lead to false negative due to stale data, so +// all negative cases must pass through the runtime, via potential read barrier. +%def op_check_cast_slow_path(z0, z1, z2): + // Interface check: cut to runtime. + lwu $z0, MIRROR_CLASS_ACCESS_FLAGS_OFFSET(a1) + BRANCH_IF_BIT_SET $z0, $z0, MIRROR_CLASS_IS_INTERFACE_FLAG_BIT, .L${opcode}_runtime + + // Array check handled below. + lwu $z0, MIRROR_CLASS_COMPONENT_TYPE_OFFSET(a1) + // Defer z0 unpoison to array path. + bnez $z0, .L${opcode}_array + + // Super check: find expected class, else cut to runtime. +.L${opcode}_super: + lwu a2, MIRROR_CLASS_SUPER_CLASS_OFFSET(a2) + UNPOISON_HEAP_REF a2 + beq a2, a1, .L${opcode}_slow_next + bnez a2, .L${opcode}_super + +.L${opcode}_runtime: + TEST_IF_MARKING $z0, .L${opcode}_mark +.L${opcode}_mark_resume: + EXPORT_PC + call art_quick_check_instance_of // args a0 (obj), a1 (expected klass) + + // Advancement logic replicated here for branch distance. +.L${opcode}_slow_next: + FETCH_ADVANCE_INST 2 + GET_INST_OPCODE $z0 + GOTO_OPCODE $z0 +.L${opcode}_mark: + call art_quick_read_barrier_mark_reg11 // a1, expected klass + j .L${opcode}_mark_resume + +.L${opcode}_array: + UNPOISON_HEAP_REF $z0 // z0 = expected.component + lwu $z1, MIRROR_CLASS_COMPONENT_TYPE_OFFSET(a2) // z1 := actual.component + beqz $z1, .L${opcode}_runtime // null: actual not an array + UNPOISON_HEAP_REF $z1 + lwu $z2, MIRROR_CLASS_SUPER_CLASS_OFFSET($z0) // z2 := expected.component.super + // z2 can skip unpoison for null check + bnez $z2, .L${opcode}_runtime // super type exists + // expected.component.super is null: expected is either Object[] or primitive array. + lhu $z2, MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET($z0) // z2 := expected.component.primitive + bnez $z2, .L${opcode}_runtime // expected's component is primitive + lwu $z2, MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET($z1) // z2 := actual.component.primitive + bnez $z2, .L${opcode}_runtime // actual's component is primitive + // Here, z0 is Object, and z1 is a subclass of Object. + j .L${opcode}_slow_next + + +// instance-of vA, vB, type@CCCC +// Format 22c: B|A|20 CCCC +// vA := 1 if vB instance-of CCCC, else 0 +// Store in the given destination register 1 if the indicated reference is an instance of the given +// type, or 0 if not. %def op_instance_of(): - unimp + srliw s7, xINST, 8 // s7 := B|A + srliw s8, xINST, 12 // s8 := B + andi s7, s7, 0xF // s7 := A, used in slow path + FETCH_FROM_THREAD_CACHE /*expected klass*/ a1, .L${opcode}_miss, t0, t1 +.L${opcode}_miss_resume: + +% get_vreg("a0", "s8", is_unsigned=True) # a0 := fp[B], zext + beqz a0, .L${opcode}_next // a0 = null = dst value "false" + lwu a2, MIRROR_OBJECT_CLASS_OFFSET(a0) // a2 := actual klass + UNPOISON_HEAP_REF a2 + // Fast path: compare without read barrier. + bne a1, a2, .L${opcode}_slow + + li a0, 1 // dst value "true" + +.L${opcode}_next: +% set_vreg("a0", "s7", z0="t1") # fp[A] := a0 + FETCH_ADVANCE_INST 2 + GET_INST_OPCODE t0 + GOTO_OPCODE t0 -%def op_instance_of_slow_path(): - unimp +.L${opcode}_miss: + EXPORT_PC + mv a0, xSELF + ld a1, (sp) // caller ArtMethod* + mv a2, xPC + call nterp_get_class + mv a1, a0 + j .L${opcode}_miss_resume +.L${opcode}_slow: + // A0 and A1 in position for quick call. +% slow_path = add_slow_path(op_instance_of_slow_path, "s7", "t0", "t1", "t2") + tail $slow_path // slow offset exceeds branch imm + // args a0, a1, a2 + + +// Checks cases for (1) interface, (2) array, and (3) super classes. +// Hardcoded: a0 (obj), a1 (expected klass), a2 (actual klass) +// +// Npte. If marking, don't bother with read barrier calls - cut to runtime. This arrangement allows +// the (non marking) super class fast path's negative case to skip the read barrier and runtime +// call, and correctly diagnose the situation with fp[A] := 0. +%def op_instance_of_slow_path(vA, z0, z1, z2): + TEST_IF_MARKING $z0, .L${opcode}_runtime_with_read_barrier + + // Interface check: cut to runtime. + lwu $z0, MIRROR_CLASS_ACCESS_FLAGS_OFFSET(a1) + BRANCH_IF_BIT_SET $z0, $z0, MIRROR_CLASS_IS_INTERFACE_FLAG_BIT, .L${opcode}_runtime + + // Array check handled below. + lwu $z0, MIRROR_CLASS_COMPONENT_TYPE_OFFSET(a1) + // Defer z0 unpoison to array path. + bnez $z0, .L${opcode}_array + + // Super check: find klass up the hierarchy. +.L${opcode}_super: + lwu a2, MIRROR_CLASS_SUPER_CLASS_OFFSET(a2) + UNPOISON_HEAP_REF a2 + beq a2, a1, .L${opcode}_super_exit + bnez a2, .L${opcode}_super +.L${opcode}_super_exit: + snez a0, a2 // a0 := 1 if (a1 = a2 != null), else 0 (because a2 = null) + +.L${opcode}_slow_next: +% set_vreg("a0", vA, z0=z0) # fp[A] := a0 + FETCH_ADVANCE_INST 2 + GET_INST_OPCODE $z0 + GOTO_OPCODE $z0 + +.L${opcode}_runtime_with_read_barrier: + call art_quick_read_barrier_mark_reg11 // a1, expected klass +.L${opcode}_runtime: + EXPORT_PC + call artInstanceOfFromCode // args a0 (obj), a1 (expected klass) + // return a0: 1 if true, else 0 + j .L${opcode}_slow_next + +.L${opcode}_array: + UNPOISON_HEAP_REF $z0 // z0 = expected.component + lwu $z1, MIRROR_CLASS_SUPER_CLASS_OFFSET($z0) // z1 := expected.component.super + // z1 can skip unpoison for null check + bnez $z1, .L${opcode}_runtime // super type exists + // Here, expected.component.super is null: expected is either Object[] or primitive array. + lwu a0, MIRROR_CLASS_COMPONENT_TYPE_OFFSET(a2) // a0 := actual.component + beqz a0, .L${opcode}_slow_next // actual not an array, a0 = null = dst value "false" + UNPOISON_HEAP_REF a0 + lhu $z1, MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET($z0) // z1 := expected.component.primitive + lhu $z2, MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET(a0) // z2 := actual.component.primitive + or a0, $z1, $z2 // a0 := 0 if z1 = z2 = 0, else non-zero (Primitive::Type enum) + seqz a0, a0 // a0 := 1 if both are class types, else 0 + // Here, when a0 = 1, expected.component is Object, and actual.component is a subclass of Object. + j .L${opcode}_slow_next + + +// new-instance vAA, type@BBBB +// Format 21c: AA|22 BBBB +// Construct a new instance of the indicated type, storing a reference to it in the destination. The +// type must refer to a non-array class. %def op_new_instance(): - unimp + EXPORT_PC + srliw s7, xINST, 8 // s7 := AA + FETCH_FROM_THREAD_CACHE /*resolved klass*/a0, .L${opcode}_miss, t0, t1 + TEST_IF_MARKING t0, .L${opcode}_mark +.L${opcode}_mark_resume: + + ld t0, THREAD_ALLOC_OBJECT_ENTRYPOINT_OFFSET(xSELF) + jalr t0 // arg a0 (klass) + // return a0 := new-obj + fence w, w // constructor fence; main.S has details + +.L${opcode}_miss_resume: + SET_VREG_OBJECT a0, s7, z0=t0 // refs[AA] := new-obj + FETCH_ADVANCE_INST 2 + GET_INST_OPCODE t0 + GOTO_OPCODE t0 + +.L${opcode}_mark: + call art_quick_read_barrier_mark_reg10 // a0, klass + j .L${opcode}_mark_resume + +.L${opcode}_miss: + EXPORT_PC + mv a0, xSELF + ld a1, (sp) // caller ArtMethod* + mv a2, xPC + call nterp_allocate_object + // return a0 := new-obj, plus cache entry is updated with the resolved klass + j .L${opcode}_miss_resume + // *** iget *** @@ -51,15 +267,15 @@ // Format 22c: B|A|52 CCCC // vA := vB.field %def op_iget(width=32, zext=False): - srliw s7, xINST, 12 // s7 := B srliw s8, xINST, 8 + srliw s7, xINST, 12 // s7 := B andi s8, s8, 0xF // s8 := A // Fast path: NterpGetInstanceFieldOffset's byte offset from thread-local cache. FETCH_FROM_THREAD_CACHE /*field_offset*/a0, .L${opcode}_slow, t1, t2 .L${opcode}_slow_resume: - GET_VREG_OBJECT t0, s7 // t0 := holder +% get_vreg("t0", "s7", is_unsigned=True) # t0 := holder beqz t0, .L${opcode}_null add t0, a0, t0 // t0 := field addr % load(dst="t1", src="t0", width=width, zext=zext) @@ -87,7 +303,7 @@ %def op_iget_volatile(width, zext, holder, dst, z0, z1): - GET_VREG_OBJECT $z0, $holder // z0 := holder +% get_vreg(z0, holder, is_unsigned=True) # z0 := holder beqz $z0, .L${opcode}_volatile_null sub $z0, $z0, a0 // z0 := field addr (holder - (-offset)) // Atomic load: "fence rw,rw ; LOAD ; fence r,rw" @@ -113,15 +329,15 @@ // iget-object vA, vB, field@CCCC // Format 22c: B|A|54 CCCC %def op_iget_object(): - srliw s7, xINST, 12 // s7 := B srliw s8, xINST, 8 + srliw s7, xINST, 12 // s7 := B andi s8, s8, 0xF // s8 := A // Fast path: NterpGetInstanceFieldOffset's byte offset from thread-local cache. FETCH_FROM_THREAD_CACHE /*field_offset*/a0, .L${opcode}_slow, t1, t2 .L${opcode}_slow_resume: - GET_VREG_OBJECT t0, s7 // t0 := holder +% get_vreg("t0", "s7", is_unsigned=True) # t0 := holder beqz t0, .L${opcode}_null add t0, a0, t0 // t0 := field addr lwu a0, (t0) // a0 := object @@ -159,7 +375,7 @@ tail .L${opcode}_slow_resume // resume offset exceeds branch imm .L${opcode}_volatile: - GET_VREG_OBJECT $z0, $holder // z0 := holder +% get_vreg(z0, holder, is_unsigned=True) # z0 := holder beqz $z0, .L${opcode}_volatile_null sub $z0, $z0, a0 // z0 := field addr (holder - (-offset)) // Atomic load: "fence rw,rw ; LOAD ; fence r,rw" @@ -213,8 +429,8 @@ // Format 22c: B|A|59 CCCC // vB.field := vA %def op_iput(width=32): - srliw s7, xINST, 12 // s7 := B srliw s8, xINST, 8 + srliw s7, xINST, 12 // s7 := B andi s8, s8, 0xF // s8 := A % get_vreg("s8", "s8", width=width) // s8 := value, held across slow path call @@ -223,7 +439,7 @@ FETCH_FROM_THREAD_CACHE /*resolved_field*/a0, .L${opcode}_slow, t0, t1 .L${opcode}_slow_resume: - GET_VREG_OBJECT t0, s7 // t0 := holder +% get_vreg("t0", "s7", is_unsigned=True) # t0 := holder beqz t0, .L${opcode}_null add t0, a0, t0 // t0 := field addr FETCH_ADVANCE_INST 2 @@ -249,7 +465,7 @@ %def op_iput_volatile(width, holder, value, z0, z1): - GET_VREG_OBJECT $z0, $holder // z0 := holder +% get_vreg(z0, holder, is_unsigned=True) # z0 := holder beqz $z0, .L${opcode}_volatile_null sub $z0, $z0, a0 // z0 := field addr (holder - (-offset)) // Ensure the volatile store is released. @@ -286,16 +502,16 @@ // iput-object vA, vB, field@CCCC // Format 22c: B|A|5b CCCC %def op_iput_object(): - srliw s7, xINST, 12 // s7 := B srliw s8, xINST, 8 + srliw s7, xINST, 12 // s7 := B andi s8, s8, 0xF // s8 := A - GET_VREG_OBJECT s9, s8 // s9 := reference +% get_vreg("s9", "s8", is_unsigned=True) # s9 := reference // Fast path: NterpGetInstanceFieldOffset's byte offset from thread-local cache. FETCH_FROM_THREAD_CACHE /*resolved_field*/a0, .L${opcode}_slow, t0, t1 .L${opcode}_slow_resume: // s9 := reference (slow path only) - GET_VREG_OBJECT t0, s7 // t0 := holder +% get_vreg("t0", "s7", is_unsigned=True) # t0 := holder beqz t0, .L${opcode}_null add t1, a0, t0 // t1 := field addr POISON_HEAP_REF s9 // Poisoning maps null to null for the null check in write barrier. @@ -323,14 +539,14 @@ call nterp_get_instance_field_offset // result a0 := field_offset // Reload value, object may have moved. - GET_VREG_OBJECT $value, $src // value := v[A] +% get_vreg(value, src, is_unsigned=True) # value := fp[A] zext // Test for volatile (negative value). bltz a0, .L${opcode}_volatile tail .L${opcode}_slow_resume // resume offset exceeds branch imm .L${opcode}_volatile: - GET_VREG_OBJECT $z0, $holder // z0 := holder +% get_vreg(z0, holder, is_unsigned=True) # z0 := holder beqz $z0, .L${opcode}_volatile_null sub $z1, $z0, a0 // z1 := field addr (holder - (-offset)) // Ensure the volatile store is released. @@ -647,7 +863,7 @@ // Format 21c: AA|69 BBBB %def op_sput_object(): srliw s7, xINST, 8 // s7 := AA (live through slow path) - GET_VREG_OBJECT s8, s7 // s8 := reference, replaced in slow path +% get_vreg("s8", "s7", is_unsigned=True) # s8 := reference, replaced in slow path // Fast path: NterpGetStaticField's resolved_field from thread-local cache. // Stores cache value in a0 to match slow path's return from NterpGetStaticField. @@ -690,7 +906,7 @@ call nterp_get_static_field // result a0 := resolved_field // Reload value, it may have moved. - GET_VREG_OBJECT $value, $src_vreg // value := v[AA] +% get_vreg(value, src_vreg, is_unsigned=True) # value := fp[AA], zext // Test for volatile bit slli $z0, a0, 63 diff --git a/runtime/nterp_helpers.cc b/runtime/nterp_helpers.cc index 328ef629c4..55e58c5d84 100644 --- a/runtime/nterp_helpers.cc +++ b/runtime/nterp_helpers.cc @@ -277,7 +277,11 @@ bool CanMethodUseNterp(ArtMethod* method, InstructionSet isa) { case Instruction::CONST_CLASS: case Instruction::MONITOR_ENTER: case Instruction::MONITOR_EXIT: + case Instruction::CHECK_CAST: + case Instruction::INSTANCE_OF: case Instruction::ARRAY_LENGTH: + case Instruction::NEW_INSTANCE: + case Instruction::NEW_ARRAY: case Instruction::FILLED_NEW_ARRAY: case Instruction::FILLED_NEW_ARRAY_RANGE: case Instruction::FILL_ARRAY_DATA: |