// 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(): 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 .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(): 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 .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(): 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 *** %def load(dst, src, width, zext): % if width == 8 and zext: lbu $dst, ($src) % elif width == 8: lb $dst, ($src) % elif width == 16 and zext: lhu $dst, ($src) % elif width == 16: lh $dst, ($src) % elif width == 32: lw $dst, ($src) % elif width == 64: ld $dst, ($src) % else: % assert False, width %#: %def store(src, dst, width): % if width == 8: sb $src, ($dst) % elif width == 16: sh $src, ($dst) % elif width == 32: sw $src, ($dst) % elif width == 64: sd $src, ($dst) % else: % assert False, width %#: // iget vA, vB, field@CCCC // Format 22c: B|A|52 CCCC // vA := vB.field %def op_iget(width=32, zext=False): 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("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) // t1 := value FETCH_ADVANCE_INST 2 % set_vreg("t1", "s8", z0="t0", width=width) GET_INST_OPCODE t0 GOTO_OPCODE t0 .L${opcode}_slow: mv a0, xSELF ld a1, (sp) // a1 := caller ArtMethod* mv a2, xPC mv a3, zero EXPORT_PC call nterp_get_instance_field_offset // result a0 := field_offset // Test for volatile (negative value). bgez a0, .L${opcode}_slow_resume % volatile_path = add_slow_path(op_iget_volatile, width, zext, "s7", "s8", "t0", "t1") tail $volatile_path .L${opcode}_null: tail common_errNullObject %def op_iget_volatile(width, zext, holder, dst, z0, z1): % 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" fence rw, rw % load(dst=z1, src=z0, width=width, zext=zext) fence r, rw // t1 := value FETCH_ADVANCE_INST 2 % set_vreg(z1, dst, z0=z0, width=width) GET_INST_OPCODE $z0 GOTO_OPCODE $z0 .L${opcode}_volatile_null: tail common_errNullObject // iget-wide vA, vB, field@CCCC // Format 22c: B|A|53 CCCC %def op_iget_wide(): % op_iget(width=64) // iget-object vA, vB, field@CCCC // Format 22c: B|A|54 CCCC %def op_iget_object(): 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("t0", "s7", is_unsigned=True) # t0 := holder beqz t0, .L${opcode}_null add t0, a0, t0 // t0 := field addr lwu a0, (t0) // a0 := object UNPOISON_HEAP_REF a0 TEST_IF_MARKING t1, .L${opcode}_mark .L${opcode}_mark_resume: FETCH_ADVANCE_INST 2 SET_VREG_OBJECT a0, s8, z0=t0 GET_INST_OPCODE t0 GOTO_OPCODE t0 .L${opcode}_mark: call art_quick_read_barrier_mark_reg10 // a0, object j .L${opcode}_mark_resume .L${opcode}_slow: % slow_path = add_slow_path(op_iget_object_slow_path, "s7", "s8", "t0", "t1") tail $slow_path .L${opcode}_null: tail common_errNullObject %def op_iget_object_slow_path(holder, dst, z0, z1): mv a0, xSELF ld a1, (sp) // a1 := caller ArtMethod* mv a2, xPC mv a3, zero EXPORT_PC call nterp_get_instance_field_offset // result a0 := field_offset // 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(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" fence rw, rw lwu a0, ($z0) // a0 := object fence r, rw UNPOISON_HEAP_REF a0 TEST_IF_MARKING t1, .L${opcode}_volatile_mark .L${opcode}_volatile_mark_resume: FETCH_ADVANCE_INST 2 SET_VREG_OBJECT a0, $dst, z0=$z0 GET_INST_OPCODE $z0 GOTO_OPCODE $z0 .L${opcode}_volatile_mark: call art_quick_read_barrier_mark_reg10 // a0, object j .L${opcode}_volatile_mark_resume .L${opcode}_volatile_null: tail common_errNullObject // iget-boolean vA, vB, field@CCCC // Format 22c: B|A|55 CCCC %def op_iget_boolean(): % op_iget(width=8, zext=True) // iget-byte vA, vB, field@CCCC // Format 22c: B|A|56 CCCC %def op_iget_byte(): % op_iget(width=8) // iget-char vA, vB, field@CCCC // Format 22c: B|A|57 CCCC %def op_iget_char(): % op_iget(width=16, zext=True) // iget-short vA, vB, field@CCCC // Format 22c: B|A|58 CCCC %def op_iget_short(): % op_iget(width=16) // *** iput *** // iput vA, vB, field@CCCC // Format 22c: B|A|59 CCCC // vB.field := vA %def op_iput(width=32): 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 // 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: % 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 % store(src="s8", dst="t0", width=width) GET_INST_OPCODE t0 GOTO_OPCODE t0 .L${opcode}_slow: mv a0, xSELF ld a1, (sp) // a1 := caller ArtMethod* mv a2, xPC mv a3, zero EXPORT_PC call nterp_get_instance_field_offset // result a0 := field_offset // Test for volatile (negative value). bgez a0, .L${opcode}_slow_resume % volatile_path = add_slow_path(op_iput_volatile, width, "s7", "s8", "t0", "t1") tail $volatile_path .L${opcode}_null: tail common_errNullObject %def op_iput_volatile(width, holder, value, z0, z1): % 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. % if width == 8: fence rw, w sb $value, ($z0) fence rw, rw % elif width == 16: fence rw, w sh $value, ($z0) fence rw, rw % elif width == 32: amoswap.w.rl zero, $value, ($z0) % elif width == 64: amoswap.d.rl zero, $value, ($z0) % else: % assert False, width %#: FETCH_ADVANCE_INST 2 GET_INST_OPCODE $z0 GOTO_OPCODE $z0 .L${opcode}_volatile_null: tail common_errNullObject // iput-wide vA, vB, field@CCCC // Format 22c: B|A|5a CCCC %def op_iput_wide(): % op_iput(width=64) // iput-object vA, vB, field@CCCC // Format 22c: B|A|5b CCCC %def op_iput_object(): srliw s8, xINST, 8 srliw s7, xINST, 12 // s7 := B andi s8, s8, 0xF // s8 := A % 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("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. sw s9, (t1) % object_write_barrier(value="s9", holder="t0", z0="t1", z1="t2", uniq=f"{opcode}") FETCH_ADVANCE_INST 2 GET_INST_OPCODE t0 GOTO_OPCODE t0 .L${opcode}_slow: % slow_path = add_slow_path(op_iput_object_slow_path, "s7", "s8", "s9", "t0", "t1", "t2") tail $slow_path .L${opcode}_null: tail common_errNullObject %def op_iput_object_slow_path(holder, src, value, z0, z1, z2): mv a0, xSELF ld a1, (sp) // a1 := caller ArtMethod* mv a2, xPC mv a3, $value EXPORT_PC call nterp_get_instance_field_offset // result a0 := field_offset // Reload value, object may have moved. % 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(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. POISON_HEAP_REF $value // Poisoning maps null to null for the null check in write barrier. amoswap.w.rl zero, $value, ($z1) % object_write_barrier(value=value, holder=z0, z0=z1, z1=z2, uniq=f"slow_{opcode}") FETCH_ADVANCE_INST 2 GET_INST_OPCODE $z0 GOTO_OPCODE $z0 .L${opcode}_volatile_null: tail common_errNullObject // iput-boolean vA, vB, field@CCCC // Format 22c: B|A|5c CCCC %def op_iput_boolean(): % op_iput(width=8) // iput-byte vA, vB, field@CCCC // Format 22c: B|A|5d CCCC %def op_iput_byte(): % op_iput(width=8) // iput-char vA, vB, field@CCCC // Format 22c: B|A|5e CCCC %def op_iput_char(): % op_iput(width=16) // iput-short vA, vB, field@CCCC // Format 22c: B|A|5f CCCC %def op_iput_short(): % op_iput(width=16) // *** sget *** .macro CLEAR_STATIC_VOLATILE_MARKER reg andi \reg, \reg, ~0x1 .endm // sget vAA, field@BBBB // Format 21c: AA|60 BBBB // vAA := klass.field %def op_sget(width=32, zext=False): srliw s7, xINST, 8 // s7 := AA (live through volatile path) // Fast path: NterpGetStaticField's resolved_field from thread-local cache. // Stores cache value in a0 to match slow path's return from NterpGetStaticField. FETCH_FROM_THREAD_CACHE /*resolved_field*/a0, .L${opcode}_slow, t0, t1 .L${opcode}_slow_resume: lwu t0, ART_FIELD_OFFSET_OFFSET(a0) // t0 := field offset lwu a0, ART_FIELD_DECLARING_CLASS_OFFSET(a0) // a0 := holder TEST_IF_MARKING t2, .L${opcode}_mark .L${opcode}_mark_resume: add t0, t0, a0 // t0 := field addr, after possible a0 update % load(dst="t1", src="t0", width=width, zext=zext) // t1 := value FETCH_ADVANCE_INST 2 % set_vreg("t1", "s7", z0="t0", width=width) GET_INST_OPCODE t0 GOTO_OPCODE t0 .L${opcode}_mark: call art_quick_read_barrier_mark_reg10 // a0, holder j .L${opcode}_mark_resume .L${opcode}_slow: mv a0, xSELF ld a1, (sp) // a1 := caller ArtMethod* mv a2, xPC mv a3, zero EXPORT_PC call nterp_get_static_field // result a0 := resolved_field // Test for volatile bit slli t0, a0, 63 bgez t0, .L${opcode}_slow_resume % volatile_path = add_slow_path(op_sget_volatile, width, zext, "s7", "t0", "t1") tail $volatile_path // Volatile static load. // Temporaries: z0, z1, z2 %def op_sget_volatile(width, zext, dst_vreg, z0, z1): CLEAR_STATIC_VOLATILE_MARKER a0 lwu $z0, ART_FIELD_OFFSET_OFFSET(a0) // z0 := field offset lwu a0, ART_FIELD_DECLARING_CLASS_OFFSET(a0) // a0 := holder TEST_IF_MARKING $z1, .L${opcode}_volatile_mark .L${opcode}_volatile_mark_resume: add $z0, $z0, a0 // z0 := field addr, after possible a0 update // Atomic load: "fence rw,rw ; LOAD ; fence r,rw" fence rw, rw % load(dst=z1, src=z0, width=width, zext=zext) fence r, rw // z1 := value FETCH_ADVANCE_INST 2 % set_vreg(z1, dst_vreg, z0=z0, width=width) GET_INST_OPCODE $z0 GOTO_OPCODE $z0 .L${opcode}_volatile_mark: call art_quick_read_barrier_mark_reg10 // a0, holder j .L${opcode}_volatile_mark_resume // sget-wide vAA, field@BBBB // Format 21c: AA|61 BBBB %def op_sget_wide(): % op_sget(width=64) // sget-object vAA, field@BBBB // Format 21c: AA|62 BBBB // Variant for object load contains extra logic for GC mark. %def op_sget_object(): srliw s7, xINST, 8 // s7 := AA (live through volatile path) // Fast path: NterpGetStaticField's resolved_field from thread-local cache. // Stores cache value in a0 to match slow path's return from NterpGetStaticField. FETCH_FROM_THREAD_CACHE /*resolved_field*/a0, .L${opcode}_slow, t0, t1 .L${opcode}_slow_resume: lwu t0, ART_FIELD_OFFSET_OFFSET(a0) // t0 := field offset lwu a0, ART_FIELD_DECLARING_CLASS_OFFSET(a0) // a0 := holder TEST_IF_MARKING t1, .L${opcode}_mark add t0, t0, a0 // t0 := field addr lwu a0, (t0) // a0 := value (ref) UNPOISON_HEAP_REF a0 .L${opcode}_mark_resume: FETCH_ADVANCE_INST 2 SET_VREG_OBJECT a0, s7, z0=t0 GET_INST_OPCODE t0 GOTO_OPCODE t0 .L${opcode}_mark: call art_quick_read_barrier_mark_reg10 // a0, holder add t0, t0, a0 // t0 := field addr, after a0 update lwu a0, (t0) // a0 := value (ref) UNPOISON_HEAP_REF a0 call art_quick_read_barrier_mark_reg10 // a0, object j .L${opcode}_mark_resume .L${opcode}_slow: % slow_path = add_slow_path(op_sget_object_slow_path, "s7", "t0", "t1") tail $slow_path // Static load, object variant. Contains both slow path and volatile path // due to handler size limitation in op_sget_object. // Hardcoded: a0, a1, a2, a3, xSELF, xPC, xINST, xFP, xREFS // Temporaries: z0, z1 %def op_sget_object_slow_path(dst_vreg, z0, z1): mv a0, xSELF ld a1, (sp) // a1 := caller ArtMethod* mv a2, xPC mv a3, zero EXPORT_PC call nterp_get_static_field // result a0 := resolved_field // Test for volatile bit slli $z0, a0, 63 bltz $z0, .L${opcode}_volatile tail .L${opcode}_slow_resume // resume offset exceeds branch imm .L${opcode}_volatile: CLEAR_STATIC_VOLATILE_MARKER a0 lwu $z0, ART_FIELD_OFFSET_OFFSET(a0) // z0 := field offset lwu a0, ART_FIELD_DECLARING_CLASS_OFFSET(a0) // a0 := holder TEST_IF_MARKING $z1, .L${opcode}_volatile_mark add $z0, $z0, a0 // z0 := field addr fence rw, rw lwu a0, ($z0) // Atomic ref load: "fence rw,rw, ; LOAD ; fence r,rw" fence r, rw UNPOISON_HEAP_REF a0 .L${opcode}_volatile_mark_resume: FETCH_ADVANCE_INST 2 SET_VREG_OBJECT a0, $dst_vreg, z0=$z0 GET_INST_OPCODE $z0 GOTO_OPCODE $z0 .L${opcode}_volatile_mark: call art_quick_read_barrier_mark_reg10 // a0, holder add $z0, $z0, a0 // z0 := field addr, after a0 update fence rw, rw lwu a0, ($z0) // Atomic ref load: "fence rw,rw, ; LOAD ; fence r,rw" fence r, rw UNPOISON_HEAP_REF a0 call art_quick_read_barrier_mark_reg10 // a0, object j .L${opcode}_volatile_mark_resume // sget-boolean vAA, field@BBBB // Format 21c: AA|63 BBBB %def op_sget_boolean(): % op_sget(width=8, zext=True) // sget-byte vAA, field@BBBB // Format 21c: AA|64 BBBB %def op_sget_byte(): % op_sget(width=8) // sget-char vAA, field@BBBB // Format 21c: AA|65 BBBB %def op_sget_char(): % op_sget(width=16, zext=True) // sget-short vAA, field@BBBB // Format 21c: AA|66 BBBB %def op_sget_short(): % op_sget(width=16) // *** sput *** // sput vAA, field@BBBB // Format 21c: AA|67 BBBB // klass.field := vAA %def op_sput(width=32): srliw t2, xINST, 8 // t2 := AA % get_vreg("s7", "t2", width=width) // s7 := value, held across slow path call // Fast path: NterpGetStaticField's resolved_field from thread-local cache. // Stores cache value in a0 to match slow path's return from NterpGetStaticField. FETCH_FROM_THREAD_CACHE /*resolved_field*/a0, .L${opcode}_slow, t0, t1 .L${opcode}_slow_resume: lwu t0, ART_FIELD_OFFSET_OFFSET(a0) lwu a0, ART_FIELD_DECLARING_CLASS_OFFSET(a0) // a0 := holder TEST_IF_MARKING t1, .L${opcode}_mark .L${opcode}_mark_resume: add t0, t0, a0 // t0 := field addr, after possible a0 update FETCH_ADVANCE_INST 2 % store(src="s7", dst="t0", width=width) GET_INST_OPCODE t0 GOTO_OPCODE t0 .L${opcode}_mark: call art_quick_read_barrier_mark_reg10 // a0, holder j .L${opcode}_mark_resume .L${opcode}_slow: mv a0, xSELF ld a1, (sp) mv a2, xPC mv a3, zero EXPORT_PC call nterp_get_static_field // result a0 := resolved_field // Test for volatile bit slli t0, a0, 63 bgez t0, .L${opcode}_slow_resume % volatile_path = add_slow_path(op_sput_volatile, width, "s7", "t0", "t1") tail $volatile_path // Volatile static store. // Temporaries: z0, z1 %def op_sput_volatile(width, value, z0, z1): CLEAR_STATIC_VOLATILE_MARKER a0 lwu $z0, ART_FIELD_OFFSET_OFFSET(a0) // z0 := field offset lwu a0, ART_FIELD_DECLARING_CLASS_OFFSET(a0) // a0 := holder TEST_IF_MARKING $z1, .L${opcode}_volatile_mark .L${opcode}_volatile_mark_resume: add $z0, $z0, a0 // z0 := field addr, after possible a0 update // Ensure the volatile store is released. % if width == 8: fence rw, w sb $value, ($z0) fence rw, rw % elif width == 16: fence rw, w sh $value, ($z0) fence rw, rw % elif width == 32: amoswap.w.rl zero, $value, ($z0) % elif width == 64: amoswap.d.rl zero, $value, ($z0) % else: % assert False, width %#: FETCH_ADVANCE_INST 2 GET_INST_OPCODE $z0 GOTO_OPCODE $z0 .L${opcode}_volatile_mark: call art_quick_read_barrier_mark_reg10 // a0, holder j .L${opcode}_volatile_mark_resume // sput-wide vAA, field@BBBB // Format 21c: AA|68 BBBB %def op_sput_wide(): % op_sput(width=64) // sput-object vAA, field@BBBB // Format 21c: AA|69 BBBB %def op_sput_object(): srliw s7, xINST, 8 // s7 := AA (live through 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. FETCH_FROM_THREAD_CACHE /*resolved_field*/a0, .L${opcode}_slow, t0, t1 .L${opcode}_slow_resume: // s8 := reference (slow path only) lwu t0, ART_FIELD_OFFSET_OFFSET(a0) lwu a0, ART_FIELD_DECLARING_CLASS_OFFSET(a0) // a0 := holder TEST_IF_MARKING t1, .L${opcode}_mark .L${opcode}_mark_resume: add t0, t0, a0 // t0 := field addr, after possible a0 update POISON_HEAP_REF s8 // Poisoning maps null to null for the null check in write barrier. sw s8, (t0) // store reference % object_write_barrier(value="s8", holder="a0", z0="t0", z1="t1", uniq=f"{opcode}") FETCH_ADVANCE_INST 2 GET_INST_OPCODE t0 GOTO_OPCODE t0 .L${opcode}_mark: call art_quick_read_barrier_mark_reg10 // a0, holder j .L${opcode}_mark_resume .L${opcode}_slow: % slow_path = add_slow_path(op_sput_object_slow_path, "s7", "s8", "t0", "t1") tail $slow_path // slow path offset exceeds regular branch imm in FETCH_FROM_THREAD_CACHE // Static store, object variant. Contains both slow path and volatile path // due to handler size limitation in op_sput_object. // Hardcoded: a0, a1, a2, a3, xSELF, xPC, xINST, xFP, xREFS // Temporaries z0, z1 %def op_sput_object_slow_path(src_vreg, value, z0, z1): // Args for nterp_get_static_field mv a0, xSELF ld a1, (sp) mv a2, xPC mv a3, $value EXPORT_PC call nterp_get_static_field // result a0 := resolved_field // Reload value, it may have moved. % get_vreg(value, src_vreg, is_unsigned=True) # value := fp[AA], zext // Test for volatile bit slli $z0, a0, 63 bltz $z0, .L${opcode}_volatile tail .L${opcode}_slow_resume // resume offset exceeds branch imm .L${opcode}_volatile: CLEAR_STATIC_VOLATILE_MARKER a0 lwu $z0, ART_FIELD_OFFSET_OFFSET(a0) // z0 := field offset lwu a0, ART_FIELD_DECLARING_CLASS_OFFSET(a0) // a0 := holder TEST_IF_MARKING $z1, .L${opcode}_volatile_mark .L${opcode}_volatile_mark_resume: add $z0, $z0, a0 // z0 := field addr, after possible a0 update // Ensure the volatile store is released. // \value must NOT be the destination register, the destination gets clobbered! // \value's original value is used in the write barrier below. POISON_HEAP_REF $value // Poisoning maps null to null for the null check in write barrier. amoswap.w.rl zero, $value, ($z0) % object_write_barrier(value=value, holder="a0", z0=z0, z1=z1, uniq=f"slow_{opcode}") FETCH_ADVANCE_INST 2 GET_INST_OPCODE $z0 GOTO_OPCODE $z0 .L${opcode}_volatile_mark: call art_quick_read_barrier_mark_reg10 // a0, holder j .L${opcode}_volatile_mark_resume %def object_write_barrier(value, holder, z0, z1, uniq): beqz $value, .L${uniq}_skip_write_barrier // No object, skip out. ld $z0, THREAD_CARD_TABLE_OFFSET(xSELF) srli $z1, $holder, CARD_TABLE_CARD_SHIFT add $z1, $z0, $z1 sb $z0, ($z1) .L${uniq}_skip_write_barrier: // sput-object vAA, field@BBBB // Format 21c: AA|6a BBBB %def op_sput_boolean(): % op_sput(width=8) // sput-object vAA, field@BBBB // Format 21c: AA|6b BBBB %def op_sput_byte(): % op_sput(width=8) // sput-object vAA, field@BBBB // Format 21c: AA|6c BBBB %def op_sput_char(): % op_sput(width=16) // sput-object vAA, field@BBBB // Format 21c: AA|6d BBBB %def op_sput_short(): % op_sput(width=16)