Merge "MIPS: HRor clean-up"
diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S
index 0691f2a..699ab3e 100644
--- a/runtime/arch/mips/quick_entrypoints_mips.S
+++ b/runtime/arch/mips/quick_entrypoints_mips.S
@@ -1312,7 +1312,114 @@
.endm
// Generate the allocation entrypoints for each allocator.
-GENERATE_ALL_ALLOC_ENTRYPOINTS
+GENERATE_ALLOC_ENTRYPOINTS_FOR_EACH_ALLOCATOR
+GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_tlab, TLAB)
+// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_rosalloc, RosAlloc).
+ENTRY art_quick_alloc_object_rosalloc
+
+ # Fast path rosalloc allocation
+ # a0: type_idx
+ # a1: ArtMethod*
+ # s1: Thread::Current
+ # -----------------------------
+ # t0: class
+ # t1: object size
+ # t2: rosalloc run
+ # t3: thread stack top offset
+ # t4: thread stack bottom offset
+ # v0: free list head
+ #
+ # t5, t6 : temps
+
+ lw $t0, ART_METHOD_DEX_CACHE_TYPES_OFFSET_32($a1) # Load dex cache resolved types
+ # array.
+
+ sll $t5, $a0, COMPRESSED_REFERENCE_SIZE_SHIFT # Shift the value.
+ addu $t5, $t0, $t5 # Compute the index.
+ lw $t0, 0($t5) # Load class (t0).
+ beqz $t0, .Lart_quick_alloc_object_rosalloc_slow_path
+
+ li $t6, MIRROR_CLASS_STATUS_INITIALIZED
+ lw $t5, MIRROR_CLASS_STATUS_OFFSET($t0) # Check class status.
+ bne $t5, $t6, .Lart_quick_alloc_object_rosalloc_slow_path
+
+ # Add a fake dependence from the following access flag and size loads to the status load. This
+ # is to prevent those loads from being reordered above the status load and reading wrong values.
+ xor $t5, $t5, $t5
+ addu $t0, $t0, $t5
+
+ lw $t5, MIRROR_CLASS_ACCESS_FLAGS_OFFSET($t0) # Check if access flags has
+ li $t6, ACCESS_FLAGS_CLASS_IS_FINALIZABLE # kAccClassIsFinalizable.
+ and $t6, $t5, $t6
+ bnez $t6, .Lart_quick_alloc_object_rosalloc_slow_path
+
+ lw $t3, THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET($s1) # Check if thread local allocation
+ lw $t4, THREAD_LOCAL_ALLOC_STACK_END_OFFSET($s1) # stack has any room left.
+ bgeu $t3, $t4, .Lart_quick_alloc_object_rosalloc_slow_path
+
+ lw $t1, MIRROR_CLASS_OBJECT_SIZE_OFFSET($t0) # Load object size (t1).
+ li $t5, ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE # Check if size is for a thread local
+ # allocation.
+ bgtu $t1, $t5, .Lart_quick_alloc_object_rosalloc_slow_path
+
+ # Compute the rosalloc bracket index from the size. Allign up the size by the rosalloc bracket
+ # quantum size and divide by the quantum size and subtract by 1.
+
+ addiu $t1, $t1, -1 # Decrease obj size and shift right
+ srl $t1, $t1, ROSALLOC_BRACKET_QUANTUM_SIZE_SHIFT # by quantum.
+
+ sll $t2, $t1, POINTER_SIZE_SHIFT
+ addu $t2, $t2, $s1
+ lw $t2, THREAD_ROSALLOC_RUNS_OFFSET($t2) # Load rosalloc run (t2).
+
+ # Load the free list head (v0).
+ # NOTE: this will be the return val.
+
+ lw $v0, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)($t2)
+ beqz $v0, .Lart_quick_alloc_object_rosalloc_slow_path
+ nop
+
+ # Load the next pointer of the head and update the list head with the next pointer.
+
+ lw $t5, ROSALLOC_SLOT_NEXT_OFFSET($v0)
+ sw $t5, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_HEAD_OFFSET)($t2)
+
+ # Store the class pointer in the header. This also overwrites the first pointer. The offsets are
+ # asserted to match.
+
+#if ROSALLOC_SLOT_NEXT_OFFSET != MIRROR_OBJECT_CLASS_OFFSET
+#error "Class pointer needs to overwrite next pointer."
+#endif
+
+ POISON_HEAP_REF $t0
+ sw $t0, MIRROR_OBJECT_CLASS_OFFSET($v0)
+
+ # Push the new object onto the thread local allocation stack and increment the thread local
+ # allocation stack top.
+
+ sw $v0, 0($t3)
+ addiu $t3, $t3, COMPRESSED_REFERENCE_SIZE
+ sw $t3, THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET($s1)
+
+ # Decrement the size of the free list.
+
+ lw $t5, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_SIZE_OFFSET)($t2)
+ addiu $t5, $t5, -1
+ sw $t5, (ROSALLOC_RUN_FREE_LIST_OFFSET + ROSALLOC_RUN_FREE_LIST_SIZE_OFFSET)($t2)
+
+ sync # Fence.
+
+ jalr $zero, $ra
+ nop
+
+ .Lart_quick_alloc_object_rosalloc_slow_path:
+
+ SETUP_REFS_ONLY_CALLEE_SAVE_FRAME
+ jal artAllocObjectFromCodeRosAlloc
+ move $a2 ,$s1 # Pass self as argument.
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+
+END art_quick_alloc_object_rosalloc
/*
* Entry from managed code to resolve a string, this stub will allocate a String and deliver an
diff --git a/runtime/interpreter/mterp/arm/footer.S b/runtime/interpreter/mterp/arm/footer.S
index 75e0037..617f572 100644
--- a/runtime/interpreter/mterp/arm/footer.S
+++ b/runtime/interpreter/mterp/arm/footer.S
@@ -128,9 +128,11 @@
*/
MterpFallback:
EXPORT_PC
+#if MTERP_LOGGING
mov r0, rSELF
add r1, rFP, #OFF_FP_SHADOWFRAME
bl MterpLogFallback
+#endif
MterpCommonFallback:
mov r0, #0 @ signal retry with reference interpreter.
b MterpDone
@@ -144,9 +146,6 @@
* uint32_t* rFP (should still be live, pointer to base of vregs)
*/
MterpExceptionReturn:
- ldr r2, [rFP, #OFF_FP_RESULT_REGISTER]
- str r0, [r2]
- str r1, [r2, #4]
mov r0, #1 @ signal return to caller.
b MterpDone
MterpReturn:
diff --git a/runtime/interpreter/mterp/arm/op_aget.S b/runtime/interpreter/mterp/arm/op_aget.S
index 2cc4d66..11f7079 100644
--- a/runtime/interpreter/mterp/arm/op_aget.S
+++ b/runtime/interpreter/mterp/arm/op_aget.S
@@ -1,11 +1,11 @@
-%default { "load":"ldr", "shift":"2", "is_object":"0", "data_offset":"MIRROR_INT_ARRAY_DATA_OFFSET" }
+%default { "load":"ldr", "shift":"2", "data_offset":"MIRROR_INT_ARRAY_DATA_OFFSET" }
/*
* Array get, 32 bits or less. vAA <- vBB[vCC].
*
* Note: using the usual FETCH/and/shift stuff, this fits in exactly 17
* instructions. We use a pair of FETCH_Bs instead.
*
- * for: aget, aget-object, aget-boolean, aget-byte, aget-char, aget-short
+ * for: aget, aget-boolean, aget-byte, aget-char, aget-short
*
* NOTE: assumes data offset for arrays is the same for all non-wide types.
* If this changes, specialize.
@@ -25,9 +25,5 @@
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
$load r2, [r0, #$data_offset] @ r2<- vBB[vCC]
GET_INST_OPCODE ip @ extract opcode from rINST
- .if $is_object
- SET_VREG_OBJECT r2, r9 @ vAA<- r2
- .else
SET_VREG r2, r9 @ vAA<- r2
- .endif
GOTO_OPCODE ip @ jump to next instruction
diff --git a/runtime/interpreter/mterp/arm/op_iget_object_quick.S b/runtime/interpreter/mterp/arm/op_iget_object_quick.S
index 1f8dc5a..fe29106 100644
--- a/runtime/interpreter/mterp/arm/op_iget_object_quick.S
+++ b/runtime/interpreter/mterp/arm/op_iget_object_quick.S
@@ -1 +1,17 @@
-%include "arm/op_iget_quick.S" {"is_object":"1"}
+ /* For: iget-object-quick */
+ /* op vA, vB, offset@CCCC */
+ mov r2, rINST, lsr #12 @ r2<- B
+ FETCH r1, 1 @ r1<- field byte offset
+ GET_VREG r0, r2 @ r0<- object we're operating on
+ cmp r0, #0 @ check object for null
+ beq common_errNullObject @ object was null
+ bl artIGetObjectFromMterp @ (obj, offset)
+ ldr r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
+ ubfx r2, rINST, #8, #4 @ r2<- A
+ PREFETCH_INST 2
+ cmp r3, #0
+ bne MterpPossibleException @ bail out
+ SET_VREG_OBJECT r0, r2 @ fp[A]<- r0
+ ADVANCE 2 @ advance rPC
+ GET_INST_OPCODE ip @ extract opcode from rINST
+ GOTO_OPCODE ip @ jump to next instruction
diff --git a/runtime/interpreter/mterp/arm/op_iget_quick.S b/runtime/interpreter/mterp/arm/op_iget_quick.S
index 9229afc..0eaf364 100644
--- a/runtime/interpreter/mterp/arm/op_iget_quick.S
+++ b/runtime/interpreter/mterp/arm/op_iget_quick.S
@@ -1,5 +1,5 @@
-%default { "load":"ldr", "is_object":"0" }
- /* For: iget-quick, iget-object-quick */
+%default { "load":"ldr" }
+ /* For: iget-quick, iget-boolean-quick, iget-byte-quick, iget-char-quick, iget-short-quick */
/* op vA, vB, offset@CCCC */
mov r2, rINST, lsr #12 @ r2<- B
FETCH r1, 1 @ r1<- field byte offset
@@ -9,10 +9,6 @@
beq common_errNullObject @ object was null
$load r0, [r3, r1] @ r0<- obj.field
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
- .if $is_object
- SET_VREG_OBJECT r0, r2 @ fp[A]<- r0
- .else
SET_VREG r0, r2 @ fp[A]<- r0
- .endif
GET_INST_OPCODE ip @ extract opcode from rINST
GOTO_OPCODE ip @ jump to next instruction
diff --git a/runtime/interpreter/mterp/mterp.cc b/runtime/interpreter/mterp/mterp.cc
index 060fe76..9975458 100644
--- a/runtime/interpreter/mterp/mterp.cc
+++ b/runtime/interpreter/mterp/mterp.cc
@@ -607,5 +607,14 @@
}
}
+extern "C" mirror::Object* artIGetObjectFromMterp(mirror::Object* obj, uint32_t field_offset)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ if (UNLIKELY(obj == nullptr)) {
+ ThrowNullPointerExceptionFromInterpreter();
+ return nullptr;
+ }
+ return obj->GetFieldObject<mirror::Object>(MemberOffset(field_offset));
+}
+
} // namespace interpreter
} // namespace art
diff --git a/runtime/interpreter/mterp/out/mterp_arm.S b/runtime/interpreter/mterp/out/mterp_arm.S
index 33036e6..2d6f057 100644
--- a/runtime/interpreter/mterp/out/mterp_arm.S
+++ b/runtime/interpreter/mterp/out/mterp_arm.S
@@ -2013,7 +2013,7 @@
* Note: using the usual FETCH/and/shift stuff, this fits in exactly 17
* instructions. We use a pair of FETCH_Bs instead.
*
- * for: aget, aget-object, aget-boolean, aget-byte, aget-char, aget-short
+ * for: aget, aget-boolean, aget-byte, aget-char, aget-short
*
* NOTE: assumes data offset for arrays is the same for all non-wide types.
* If this changes, specialize.
@@ -2033,11 +2033,7 @@
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
ldr r2, [r0, #MIRROR_INT_ARRAY_DATA_OFFSET] @ r2<- vBB[vCC]
GET_INST_OPCODE ip @ extract opcode from rINST
- .if 0
- SET_VREG_OBJECT r2, r9 @ vAA<- r2
- .else
SET_VREG r2, r9 @ vAA<- r2
- .endif
GOTO_OPCODE ip @ jump to next instruction
/* ------------------------------ */
@@ -2106,7 +2102,7 @@
* Note: using the usual FETCH/and/shift stuff, this fits in exactly 17
* instructions. We use a pair of FETCH_Bs instead.
*
- * for: aget, aget-object, aget-boolean, aget-byte, aget-char, aget-short
+ * for: aget, aget-boolean, aget-byte, aget-char, aget-short
*
* NOTE: assumes data offset for arrays is the same for all non-wide types.
* If this changes, specialize.
@@ -2126,11 +2122,7 @@
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
ldrb r2, [r0, #MIRROR_BOOLEAN_ARRAY_DATA_OFFSET] @ r2<- vBB[vCC]
GET_INST_OPCODE ip @ extract opcode from rINST
- .if 0
- SET_VREG_OBJECT r2, r9 @ vAA<- r2
- .else
SET_VREG r2, r9 @ vAA<- r2
- .endif
GOTO_OPCODE ip @ jump to next instruction
@@ -2145,7 +2137,7 @@
* Note: using the usual FETCH/and/shift stuff, this fits in exactly 17
* instructions. We use a pair of FETCH_Bs instead.
*
- * for: aget, aget-object, aget-boolean, aget-byte, aget-char, aget-short
+ * for: aget, aget-boolean, aget-byte, aget-char, aget-short
*
* NOTE: assumes data offset for arrays is the same for all non-wide types.
* If this changes, specialize.
@@ -2165,11 +2157,7 @@
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
ldrsb r2, [r0, #MIRROR_BYTE_ARRAY_DATA_OFFSET] @ r2<- vBB[vCC]
GET_INST_OPCODE ip @ extract opcode from rINST
- .if 0
- SET_VREG_OBJECT r2, r9 @ vAA<- r2
- .else
SET_VREG r2, r9 @ vAA<- r2
- .endif
GOTO_OPCODE ip @ jump to next instruction
@@ -2184,7 +2172,7 @@
* Note: using the usual FETCH/and/shift stuff, this fits in exactly 17
* instructions. We use a pair of FETCH_Bs instead.
*
- * for: aget, aget-object, aget-boolean, aget-byte, aget-char, aget-short
+ * for: aget, aget-boolean, aget-byte, aget-char, aget-short
*
* NOTE: assumes data offset for arrays is the same for all non-wide types.
* If this changes, specialize.
@@ -2204,11 +2192,7 @@
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
ldrh r2, [r0, #MIRROR_CHAR_ARRAY_DATA_OFFSET] @ r2<- vBB[vCC]
GET_INST_OPCODE ip @ extract opcode from rINST
- .if 0
- SET_VREG_OBJECT r2, r9 @ vAA<- r2
- .else
SET_VREG r2, r9 @ vAA<- r2
- .endif
GOTO_OPCODE ip @ jump to next instruction
@@ -2223,7 +2207,7 @@
* Note: using the usual FETCH/and/shift stuff, this fits in exactly 17
* instructions. We use a pair of FETCH_Bs instead.
*
- * for: aget, aget-object, aget-boolean, aget-byte, aget-char, aget-short
+ * for: aget, aget-boolean, aget-byte, aget-char, aget-short
*
* NOTE: assumes data offset for arrays is the same for all non-wide types.
* If this changes, specialize.
@@ -2243,11 +2227,7 @@
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
ldrsh r2, [r0, #MIRROR_SHORT_ARRAY_DATA_OFFSET] @ r2<- vBB[vCC]
GET_INST_OPCODE ip @ extract opcode from rINST
- .if 0
- SET_VREG_OBJECT r2, r9 @ vAA<- r2
- .else
SET_VREG r2, r9 @ vAA<- r2
- .endif
GOTO_OPCODE ip @ jump to next instruction
@@ -7127,7 +7107,7 @@
.balign 128
.L_op_iget_quick: /* 0xe3 */
/* File: arm/op_iget_quick.S */
- /* For: iget-quick, iget-object-quick */
+ /* For: iget-quick, iget-boolean-quick, iget-byte-quick, iget-char-quick, iget-short-quick */
/* op vA, vB, offset@CCCC */
mov r2, rINST, lsr #12 @ r2<- B
FETCH r1, 1 @ r1<- field byte offset
@@ -7137,11 +7117,7 @@
beq common_errNullObject @ object was null
ldr r0, [r3, r1] @ r0<- obj.field
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
- .if 0
- SET_VREG_OBJECT r0, r2 @ fp[A]<- r0
- .else
SET_VREG r0, r2 @ fp[A]<- r0
- .endif
GET_INST_OPCODE ip @ extract opcode from rINST
GOTO_OPCODE ip @ jump to next instruction
@@ -7167,26 +7143,24 @@
.balign 128
.L_op_iget_object_quick: /* 0xe5 */
/* File: arm/op_iget_object_quick.S */
-/* File: arm/op_iget_quick.S */
- /* For: iget-quick, iget-object-quick */
+ /* For: iget-object-quick */
/* op vA, vB, offset@CCCC */
mov r2, rINST, lsr #12 @ r2<- B
FETCH r1, 1 @ r1<- field byte offset
- GET_VREG r3, r2 @ r3<- object we're operating on
- ubfx r2, rINST, #8, #4 @ r2<- A
- cmp r3, #0 @ check object for null
+ GET_VREG r0, r2 @ r0<- object we're operating on
+ cmp r0, #0 @ check object for null
beq common_errNullObject @ object was null
- ldr r0, [r3, r1] @ r0<- obj.field
- FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
- .if 1
+ bl artIGetObjectFromMterp @ (obj, offset)
+ ldr r3, [rSELF, #THREAD_EXCEPTION_OFFSET]
+ ubfx r2, rINST, #8, #4 @ r2<- A
+ PREFETCH_INST 2
+ cmp r3, #0
+ bne MterpPossibleException @ bail out
SET_VREG_OBJECT r0, r2 @ fp[A]<- r0
- .else
- SET_VREG r0, r2 @ fp[A]<- r0
- .endif
+ ADVANCE 2 @ advance rPC
GET_INST_OPCODE ip @ extract opcode from rINST
GOTO_OPCODE ip @ jump to next instruction
-
/* ------------------------------ */
.balign 128
.L_op_iput_quick: /* 0xe6 */
@@ -7373,7 +7347,7 @@
.L_op_iget_boolean_quick: /* 0xef */
/* File: arm/op_iget_boolean_quick.S */
/* File: arm/op_iget_quick.S */
- /* For: iget-quick, iget-object-quick */
+ /* For: iget-quick, iget-boolean-quick, iget-byte-quick, iget-char-quick, iget-short-quick */
/* op vA, vB, offset@CCCC */
mov r2, rINST, lsr #12 @ r2<- B
FETCH r1, 1 @ r1<- field byte offset
@@ -7383,11 +7357,7 @@
beq common_errNullObject @ object was null
ldrb r0, [r3, r1] @ r0<- obj.field
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
- .if 0
- SET_VREG_OBJECT r0, r2 @ fp[A]<- r0
- .else
SET_VREG r0, r2 @ fp[A]<- r0
- .endif
GET_INST_OPCODE ip @ extract opcode from rINST
GOTO_OPCODE ip @ jump to next instruction
@@ -7397,7 +7367,7 @@
.L_op_iget_byte_quick: /* 0xf0 */
/* File: arm/op_iget_byte_quick.S */
/* File: arm/op_iget_quick.S */
- /* For: iget-quick, iget-object-quick */
+ /* For: iget-quick, iget-boolean-quick, iget-byte-quick, iget-char-quick, iget-short-quick */
/* op vA, vB, offset@CCCC */
mov r2, rINST, lsr #12 @ r2<- B
FETCH r1, 1 @ r1<- field byte offset
@@ -7407,11 +7377,7 @@
beq common_errNullObject @ object was null
ldrsb r0, [r3, r1] @ r0<- obj.field
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
- .if 0
- SET_VREG_OBJECT r0, r2 @ fp[A]<- r0
- .else
SET_VREG r0, r2 @ fp[A]<- r0
- .endif
GET_INST_OPCODE ip @ extract opcode from rINST
GOTO_OPCODE ip @ jump to next instruction
@@ -7421,7 +7387,7 @@
.L_op_iget_char_quick: /* 0xf1 */
/* File: arm/op_iget_char_quick.S */
/* File: arm/op_iget_quick.S */
- /* For: iget-quick, iget-object-quick */
+ /* For: iget-quick, iget-boolean-quick, iget-byte-quick, iget-char-quick, iget-short-quick */
/* op vA, vB, offset@CCCC */
mov r2, rINST, lsr #12 @ r2<- B
FETCH r1, 1 @ r1<- field byte offset
@@ -7431,11 +7397,7 @@
beq common_errNullObject @ object was null
ldrh r0, [r3, r1] @ r0<- obj.field
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
- .if 0
- SET_VREG_OBJECT r0, r2 @ fp[A]<- r0
- .else
SET_VREG r0, r2 @ fp[A]<- r0
- .endif
GET_INST_OPCODE ip @ extract opcode from rINST
GOTO_OPCODE ip @ jump to next instruction
@@ -7445,7 +7407,7 @@
.L_op_iget_short_quick: /* 0xf2 */
/* File: arm/op_iget_short_quick.S */
/* File: arm/op_iget_quick.S */
- /* For: iget-quick, iget-object-quick */
+ /* For: iget-quick, iget-boolean-quick, iget-byte-quick, iget-char-quick, iget-short-quick */
/* op vA, vB, offset@CCCC */
mov r2, rINST, lsr #12 @ r2<- B
FETCH r1, 1 @ r1<- field byte offset
@@ -7455,11 +7417,7 @@
beq common_errNullObject @ object was null
ldrsh r0, [r3, r1] @ r0<- obj.field
FETCH_ADVANCE_INST 2 @ advance rPC, load rINST
- .if 0
- SET_VREG_OBJECT r0, r2 @ fp[A]<- r0
- .else
SET_VREG r0, r2 @ fp[A]<- r0
- .endif
GET_INST_OPCODE ip @ extract opcode from rINST
GOTO_OPCODE ip @ jump to next instruction
@@ -12204,9 +12162,11 @@
*/
MterpFallback:
EXPORT_PC
+#if MTERP_LOGGING
mov r0, rSELF
add r1, rFP, #OFF_FP_SHADOWFRAME
bl MterpLogFallback
+#endif
MterpCommonFallback:
mov r0, #0 @ signal retry with reference interpreter.
b MterpDone
@@ -12220,9 +12180,6 @@
* uint32_t* rFP (should still be live, pointer to base of vregs)
*/
MterpExceptionReturn:
- ldr r2, [rFP, #OFF_FP_RESULT_REGISTER]
- str r0, [r2]
- str r1, [r2, #4]
mov r0, #1 @ signal return to caller.
b MterpDone
MterpReturn:
diff --git a/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc b/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc
new file mode 100644
index 0000000..54879fb
--- /dev/null
+++ b/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+
+#include "base/casts.h"
+#include "base/macros.h"
+#include "java_vm_ext.h"
+#include "jni_env_ext.h"
+#include "thread-inl.h"
+
+namespace art {
+namespace {
+
+static volatile std::atomic<bool> vm_was_shutdown(false);
+
+extern "C" JNIEXPORT void JNICALL Java_Main_waitAndCallIntoJniEnv(JNIEnv* env, jclass) {
+ // Wait until the runtime is shutdown.
+ while (!vm_was_shutdown.load()) {
+ usleep(1000);
+ }
+ std::cout << "About to call exception check\n";
+ env->ExceptionCheck();
+ LOG(ERROR) << "Should not be reached!";
+}
+
+// NO_RETURN does not work with extern "C" for target builds.
+extern "C" JNIEXPORT void JNICALL Java_Main_destroyJavaVMAndExit(JNIEnv* env, jclass) {
+ // Fake up the managed stack so we can detach.
+ Thread* const self = Thread::Current();
+ self->SetTopOfStack(nullptr);
+ self->SetTopOfShadowStack(nullptr);
+ JavaVM* vm = down_cast<JNIEnvExt*>(env)->vm;
+ vm->DetachCurrentThread();
+ vm->DestroyJavaVM();
+ vm_was_shutdown.store(true);
+ // Give threads some time to get stuck in ExceptionCheck.
+ usleep(1000000);
+ if (env != nullptr) {
+ // Use env != nullptr to trick noreturn.
+ exit(0);
+ }
+}
+
+} // namespace
+} // namespace art
diff --git a/test/136-daemon-jni-shutdown/expected.txt b/test/136-daemon-jni-shutdown/expected.txt
new file mode 100644
index 0000000..f0b6353
--- /dev/null
+++ b/test/136-daemon-jni-shutdown/expected.txt
@@ -0,0 +1,5 @@
+JNI_OnLoad called
+About to call exception check
+About to call exception check
+About to call exception check
+About to call exception check
diff --git a/test/136-daemon-jni-shutdown/info.txt b/test/136-daemon-jni-shutdown/info.txt
new file mode 100644
index 0000000..06a12df
--- /dev/null
+++ b/test/136-daemon-jni-shutdown/info.txt
@@ -0,0 +1 @@
+Test that daemon threads that call into a JNI env after the runtime is shutdown do not crash.
\ No newline at end of file
diff --git a/test/136-daemon-jni-shutdown/src/Main.java b/test/136-daemon-jni-shutdown/src/Main.java
new file mode 100644
index 0000000..6eceb75
--- /dev/null
+++ b/test/136-daemon-jni-shutdown/src/Main.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Test that daemon threads that call into a JNI env after the runtime is shutdown do not crash.
+ */
+public class Main {
+
+ public final static int THREAD_COUNT = 4;
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ Thread t = new Thread(new DaemonRunnable());
+ t.setDaemon(true);
+ t.start();
+ }
+ // Give threads time to start and become stuck in waitAndCallIntoJniEnv.
+ Thread.sleep(1000);
+ destroyJavaVMAndExit();
+ }
+
+ static native void waitAndCallIntoJniEnv();
+ static native void destroyJavaVMAndExit();
+
+ private static class DaemonRunnable implements Runnable {
+ public void run() {
+ for (;;) {
+ waitAndCallIntoJniEnv();
+ }
+ }
+ }
+}
diff --git a/test/Android.libarttest.mk b/test/Android.libarttest.mk
index f74a516..b922b45 100644
--- a/test/Android.libarttest.mk
+++ b/test/Android.libarttest.mk
@@ -30,6 +30,7 @@
051-thread/thread_test.cc \
117-nopatchoat/nopatchoat.cc \
1337-gc-coverage/gc_coverage.cc \
+ 136-daemon-jni-shutdown/daemon_jni_shutdown.cc \
137-cfi/cfi.cc \
139-register-natives/regnative.cc \
141-class-unload/jni_unload.cc \