diff options
Diffstat (limited to 'runtime')
31 files changed, 1082 insertions, 355 deletions
diff --git a/runtime/Android.mk b/runtime/Android.mk index 2f8b11361c..b31eaf60d8 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -164,6 +164,7 @@ LIBART_COMMON_SRC_FILES := \ offsets.cc \ os_linux.cc \ parsed_options.cc \ + plugin.cc \ primitive.cc \ quick_exception_handler.cc \ quick/inline_method_analyser.cc \ @@ -177,6 +178,7 @@ LIBART_COMMON_SRC_FILES := \ thread.cc \ thread_list.cc \ thread_pool.cc \ + ti/agent.cc \ trace.cc \ transaction.cc \ type_lookup_table.cc \ @@ -370,6 +372,7 @@ LIBART_ENUM_OPERATOR_OUT_HEADER_FILES := \ stack.h \ thread.h \ thread_state.h \ + ti/agent.h \ verifier/method_verifier.h LIBOPENJDKJVM_SRC_FILES := openjdkjvm/OpenjdkJvm.cc @@ -419,7 +422,7 @@ define build-runtime-library endif ifneq ($(4),libart) ifneq ($(4),libopenjdkjvm) - $$(error expected libart of libopenjdkjvm for argument 4, received $(4)) + $$(error expected libart or libopenjdkjvm for argument 4, received $(4)) endif endif diff --git a/runtime/arch/arm/entrypoints_init_arm.cc b/runtime/arch/arm/entrypoints_init_arm.cc index 0e2a6720ae..492a12d02b 100644 --- a/runtime/arch/arm/entrypoints_init_arm.cc +++ b/runtime/arch/arm/entrypoints_init_arm.cc @@ -133,7 +133,7 @@ void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { qpoints->pReadBarrierMarkReg09 = art_quick_read_barrier_mark_reg09; qpoints->pReadBarrierMarkReg10 = art_quick_read_barrier_mark_reg10; qpoints->pReadBarrierMarkReg11 = art_quick_read_barrier_mark_reg11; - qpoints->pReadBarrierMarkReg12 = art_quick_read_barrier_mark_reg12; + qpoints->pReadBarrierMarkReg12 = nullptr; // Cannot use register 12 (IP) to pass arguments. qpoints->pReadBarrierMarkReg13 = nullptr; // Cannot use register 13 (SP) to pass arguments. qpoints->pReadBarrierMarkReg14 = nullptr; // Cannot use register 14 (LR) to pass arguments. qpoints->pReadBarrierMarkReg15 = nullptr; // Cannot use register 15 (PC) to pass arguments. diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index 0fcf866e18..c4ec72685f 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -191,7 +191,7 @@ .cfi_rel_offset r11, 44 .cfi_rel_offset ip, 48 .cfi_rel_offset lr, 52 - vpush {s0-s31} @ 32 words of float args. + vpush {d0-d15} @ 32 words of float args. .cfi_adjust_cfa_offset 128 sub sp, #8 @ 2 words of space, alignment padding and Method* .cfi_adjust_cfa_offset 8 @@ -210,7 +210,7 @@ .macro RESTORE_SAVE_EVERYTHING_FRAME add sp, #8 @ rewind sp .cfi_adjust_cfa_offset -8 - vpop {s0-s31} + vpop {d0-d15} .cfi_adjust_cfa_offset -128 pop {r0-r12, lr} @ 14 words of callee saves .cfi_restore r0 @@ -1246,9 +1246,15 @@ ENTRY art_quick_alloc_object_region_tlab ldr r2, [r2, r0, lsl #COMPRESSED_REFERENCE_SIZE_SHIFT] // Read barrier for class load. ldr r3, [r9, #THREAD_IS_GC_MARKING_OFFSET] - cbnz r3, .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path + cbnz r3, .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_marking .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit: ALLOC_OBJECT_TLAB_FAST_PATH .Lart_quick_alloc_object_region_tlab_slow_path +.Lart_quick_alloc_object_region_tlab_class_load_read_barrier_marking: + cbz r2, .Lart_quick_alloc_object_region_tlab_slow_path // Null check for loading lock word. + // Check lock word for mark bit, if marked do the allocation. + ldr r3, [r2, MIRROR_OBJECT_LOCK_WORD_OFFSET] + ands r3, #LOCK_WORD_MARK_BIT_MASK_SHIFTED + bne .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path: // The read barrier slow path. Mark // the class. @@ -1817,6 +1823,39 @@ ENTRY art_quick_l2f pop {pc} END art_quick_l2f +.macro CONDITIONAL_CBZ reg, reg_if, dest +.ifc \reg, \reg_if + cbz \reg, \dest +.endif +.endm + +.macro CONDITIONAL_CMPBZ reg, reg_if, dest +.ifc \reg, \reg_if + cmp \reg, #0 + beq \dest +.endif +.endm + +// Use CBZ if the register is in {r0, r7} otherwise compare and branch. +.macro SMART_CBZ reg, dest + CONDITIONAL_CBZ \reg, r0, \dest + CONDITIONAL_CBZ \reg, r1, \dest + CONDITIONAL_CBZ \reg, r2, \dest + CONDITIONAL_CBZ \reg, r3, \dest + CONDITIONAL_CBZ \reg, r4, \dest + CONDITIONAL_CBZ \reg, r5, \dest + CONDITIONAL_CBZ \reg, r6, \dest + CONDITIONAL_CBZ \reg, r7, \dest + CONDITIONAL_CMPBZ \reg, r8, \dest + CONDITIONAL_CMPBZ \reg, r9, \dest + CONDITIONAL_CMPBZ \reg, r10, \dest + CONDITIONAL_CMPBZ \reg, r11, \dest + CONDITIONAL_CMPBZ \reg, r12, \dest + CONDITIONAL_CMPBZ \reg, r13, \dest + CONDITIONAL_CMPBZ \reg, r14, \dest + CONDITIONAL_CMPBZ \reg, r15, \dest +.endm + /* * Create a function `name` calling the ReadBarrier::Mark routine, * getting its argument and returning its result through register @@ -1835,28 +1874,25 @@ END art_quick_l2f .macro READ_BARRIER_MARK_REG name, reg ENTRY \name // Null check so that we can load the lock word. - cmp \reg, #0 - beq .Lret_rb_\name - // Check lock word for mark bit, if marked return. - push {r0} - ldr r0, [\reg, MIRROR_OBJECT_LOCK_WORD_OFFSET] - and r0, #LOCK_WORD_MARK_BIT_MASK_SHIFTED - cbz r0, .Lslow_rb_\name - // Restore LR and return. - pop {r0} - bx lr + SMART_CBZ \reg, .Lret_rb_\name + // Check lock word for mark bit, if marked return. Use IP for scratch since it is blocked. + ldr ip, [\reg, MIRROR_OBJECT_LOCK_WORD_OFFSET] + ands ip, #LOCK_WORD_MARK_BIT_MASK_SHIFTED + beq .Lslow_rb_\name + // Already marked, return right away. + bx lr .Lslow_rb_\name: - pop {r0} - push {r0-r4, r9, r12, lr} @ save return address and core caller-save registers + push {r0-r5, r9, lr} @ save return address and core caller-save registers + @ also save callee save r5 for 16 byte alignment .cfi_adjust_cfa_offset 32 .cfi_rel_offset r0, 0 .cfi_rel_offset r1, 4 .cfi_rel_offset r2, 8 .cfi_rel_offset r3, 12 .cfi_rel_offset r4, 16 - .cfi_rel_offset r9, 20 - .cfi_rel_offset r12, 24 + .cfi_rel_offset r5, 20 + .cfi_rel_offset r9, 24 .cfi_rel_offset lr, 28 vpush {s0-s15} @ save floating-point caller-save registers .cfi_adjust_cfa_offset 64 @@ -1865,48 +1901,11 @@ ENTRY \name mov r0, \reg @ pass arg1 - obj from `reg` .endif bl artReadBarrierMark @ r0 <- artReadBarrierMark(obj) - + mov ip, r0 @ Save result in IP vpop {s0-s15} @ restore floating-point registers .cfi_adjust_cfa_offset -64 - @ If `reg` is a caller-save register, save the result to its - @ corresponding stack slot; it will be restored by the "pop" - @ instruction below. Otherwise, move result into `reg`. - @ - @ (Note that saving `reg` to its stack slot will overwrite the value - @ previously stored by the "push" instruction above. That is - @ alright, as in that case we know that `reg` is not a live - @ register, as it is used to pass the argument and return the result - @ of this function.) - .ifc \reg, r0 - PUSH_REG r0, 0 @ copy result to r0's stack location - .else - .ifc \reg, r1 - PUSH_REG r0, 4 @ copy result to r1's stack location - .else - .ifc \reg, r2 - PUSH_REG r0, 8 @ copy result to r2's stack location - .else - .ifc \reg, r3 - PUSH_REG r0, 12 @ copy result to r3's stack location - .else - .ifc \reg, r4 - PUSH_REG r0, 16 @ copy result to r4's stack location - .else - .ifc \reg, r9 - PUSH_REG r0, 20 @ copy result to r9's stack location - .else - .ifc \reg, r12 - PUSH_REG r0, 24 @ copy result to r12's stack location - .else - mov \reg, r0 @ return result into `reg` - .endif - .endif - .endif - .endif - .endif - .endif - .endif - pop {r0-r4, r9, r12, pc} @ restore caller-save registers and return + pop {r0-r5, r9, lr} @ restore caller-save registers + mov \reg, ip @ copy result to reg .Lret_rb_\name: bx lr END \name @@ -1924,4 +1923,3 @@ READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg08, r8 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg09, r9 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg10, r10 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg11, r11 -READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg12, r12 diff --git a/runtime/arch/arm64/entrypoints_init_arm64.cc b/runtime/arch/arm64/entrypoints_init_arm64.cc index cc5bf29609..55b09c318c 100644 --- a/runtime/arch/arm64/entrypoints_init_arm64.cc +++ b/runtime/arch/arm64/entrypoints_init_arm64.cc @@ -149,7 +149,7 @@ void InitEntryPoints(JniEntryPoints* jpoints, QuickEntryPoints* qpoints) { qpoints->pReadBarrierMarkReg13 = art_quick_read_barrier_mark_reg13; qpoints->pReadBarrierMarkReg14 = art_quick_read_barrier_mark_reg14; qpoints->pReadBarrierMarkReg15 = art_quick_read_barrier_mark_reg15; - qpoints->pReadBarrierMarkReg16 = art_quick_read_barrier_mark_reg16; + qpoints->pReadBarrierMarkReg16 = nullptr; // IP0 is used as a temp by the asm stub. qpoints->pReadBarrierMarkReg17 = art_quick_read_barrier_mark_reg17; qpoints->pReadBarrierMarkReg18 = art_quick_read_barrier_mark_reg18; qpoints->pReadBarrierMarkReg19 = art_quick_read_barrier_mark_reg19; diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index bdad966496..4289cabbc6 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -331,22 +331,23 @@ #endif // Save FP registers. - stp d0, d1, [sp, #8] - stp d2, d3, [sp, #24] - stp d4, d5, [sp, #40] - stp d6, d7, [sp, #56] - stp d8, d9, [sp, #72] - stp d10, d11, [sp, #88] - stp d12, d13, [sp, #104] - stp d14, d15, [sp, #120] - stp d16, d17, [sp, #136] - stp d18, d19, [sp, #152] - stp d20, d21, [sp, #168] - stp d22, d23, [sp, #184] - stp d24, d25, [sp, #200] - stp d26, d27, [sp, #216] - stp d28, d29, [sp, #232] - stp d30, d31, [sp, #248] + str d0, [sp, #8] + stp d1, d2, [sp, #16] + stp d3, d4, [sp, #32] + stp d5, d6, [sp, #48] + stp d7, d8, [sp, #64] + stp d9, d10, [sp, #80] + stp d11, d12, [sp, #96] + stp d13, d14, [sp, #112] + stp d15, d16, [sp, #128] + stp d17, d18, [sp, #144] + stp d19, d20, [sp, #160] + stp d21, d22, [sp, #176] + stp d23, d24, [sp, #192] + stp d25, d26, [sp, #208] + stp d27, d28, [sp, #224] + stp d29, d30, [sp, #240] + str d31, [sp, #256] // Save core registers. str x0, [sp, #264] @@ -430,22 +431,23 @@ .macro RESTORE_SAVE_EVERYTHING_FRAME // Restore FP registers. - ldp d0, d1, [sp, #8] - ldp d2, d3, [sp, #24] - ldp d4, d5, [sp, #40] - ldp d6, d7, [sp, #56] - ldp d8, d9, [sp, #72] - ldp d10, d11, [sp, #88] - ldp d12, d13, [sp, #104] - ldp d14, d15, [sp, #120] - ldp d16, d17, [sp, #136] - ldp d18, d19, [sp, #152] - ldp d20, d21, [sp, #168] - ldp d22, d23, [sp, #184] - ldp d24, d25, [sp, #200] - ldp d26, d27, [sp, #216] - ldp d28, d29, [sp, #232] - ldp d30, d31, [sp, #248] + ldr d0, [sp, #8] + ldp d1, d2, [sp, #16] + ldp d3, d4, [sp, #32] + ldp d5, d6, [sp, #48] + ldp d7, d8, [sp, #64] + ldp d9, d10, [sp, #80] + ldp d11, d12, [sp, #96] + ldp d13, d14, [sp, #112] + ldp d15, d16, [sp, #128] + ldp d17, d18, [sp, #144] + ldp d19, d20, [sp, #160] + ldp d21, d22, [sp, #176] + ldp d23, d24, [sp, #192] + ldp d25, d26, [sp, #208] + ldp d27, d28, [sp, #224] + ldp d29, d30, [sp, #240] + ldr d31, [sp, #256] // Restore core registers. ldr x0, [sp, #264] @@ -1939,10 +1941,13 @@ END art_quick_alloc_object_rosalloc // (for 64 bit alignment). and \xTemp0, \xTemp0, #4 add \xTemp1, \xTemp1, \xTemp0 - and \xTemp1, \xTemp1, #OBJECT_ALIGNMENT_MASK_TOGGLED // Round up the object size by the - // object alignment. (addr + 7) & ~7. - // Add by 7 is done above. - + and \xTemp1, \xTemp1, #OBJECT_ALIGNMENT_MASK_TOGGLED64 // Apply alignemnt mask + // (addr + 7) & ~7. The mask must + // be 64 bits to keep high bits in + // case of overflow. + // Negative sized arrays are handled here since xCount holds a zero extended 32 bit value. + // Negative ints become large 64 bit unsigned ints which will always be larger than max signed + // 32 bit int. Since the max shift for arrays is 3, it can not become a negative 64 bit int. cmp \xTemp1, #MIN_LARGE_OBJECT_THRESHOLD // Possibly a large object, go slow bhs \slowPathLabel // path. @@ -1956,7 +1961,6 @@ END art_quick_alloc_object_rosalloc sub \xTemp2, \xTemp2, \xTemp0 cmp \xTemp1, \xTemp2 bhi \slowPathLabel - // "Point of no slow path". Won't go to the slow path from here on. OK to clobber x0 and x1. // Move old thread_local_pos to x0 // for the return value. @@ -2747,7 +2751,7 @@ READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg12, w12, x12 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg13, w13, x13 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg14, w14, x14 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg15, w15, x15 -READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg16, w16, x16 +// READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg16, w16, x16 ip0 is blocked READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg17, w17, x17 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg18, w18, x18 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg19, w19, x19 diff --git a/runtime/arch/instruction_set.cc b/runtime/arch/instruction_set.cc index 81ca010423..b35e0889e4 100644 --- a/runtime/arch/instruction_set.cc +++ b/runtime/arch/instruction_set.cc @@ -18,6 +18,7 @@ // Explicitly include our own elf.h to avoid Linux and other dependencies. #include "../elf.h" +#include "base/bit_utils.h" #include "globals.h" namespace art { @@ -113,14 +114,44 @@ size_t GetInstructionSetAlignment(InstructionSet isa) { } } -static constexpr size_t kDefaultStackOverflowReservedBytes = 16 * KB; -static constexpr size_t kMipsStackOverflowReservedBytes = kDefaultStackOverflowReservedBytes; -static constexpr size_t kMips64StackOverflowReservedBytes = kDefaultStackOverflowReservedBytes; - -static constexpr size_t kArmStackOverflowReservedBytes = 8 * KB; -static constexpr size_t kArm64StackOverflowReservedBytes = 8 * KB; -static constexpr size_t kX86StackOverflowReservedBytes = 8 * KB; -static constexpr size_t kX86_64StackOverflowReservedBytes = 8 * KB; +#if !defined(ART_STACK_OVERFLOW_GAP_arm) || !defined(ART_STACK_OVERFLOW_GAP_arm64) || \ + !defined(ART_STACK_OVERFLOW_GAP_mips) || !defined(ART_STACK_OVERFLOW_GAP_mips64) || \ + !defined(ART_STACK_OVERFLOW_GAP_x86) || !defined(ART_STACK_OVERFLOW_GAP_x86_64) +#error "Missing defines for stack overflow gap" +#endif + +static constexpr size_t kArmStackOverflowReservedBytes = ART_STACK_OVERFLOW_GAP_arm; +static constexpr size_t kArm64StackOverflowReservedBytes = ART_STACK_OVERFLOW_GAP_arm64; +static constexpr size_t kMipsStackOverflowReservedBytes = ART_STACK_OVERFLOW_GAP_mips; +static constexpr size_t kMips64StackOverflowReservedBytes = ART_STACK_OVERFLOW_GAP_mips64; +static constexpr size_t kX86StackOverflowReservedBytes = ART_STACK_OVERFLOW_GAP_x86; +static constexpr size_t kX86_64StackOverflowReservedBytes = ART_STACK_OVERFLOW_GAP_x86_64; + +static_assert(IsAligned<kPageSize>(kArmStackOverflowReservedBytes), "ARM gap not page aligned"); +static_assert(IsAligned<kPageSize>(kArm64StackOverflowReservedBytes), "ARM64 gap not page aligned"); +static_assert(IsAligned<kPageSize>(kMipsStackOverflowReservedBytes), "Mips gap not page aligned"); +static_assert(IsAligned<kPageSize>(kMips64StackOverflowReservedBytes), + "Mips64 gap not page aligned"); +static_assert(IsAligned<kPageSize>(kX86StackOverflowReservedBytes), "X86 gap not page aligned"); +static_assert(IsAligned<kPageSize>(kX86_64StackOverflowReservedBytes), + "X86_64 gap not page aligned"); + +#if !defined(ART_FRAME_SIZE_LIMIT) +#error "ART frame size limit missing" +#endif + +// TODO: Should we require an extra page (RoundUp(SIZE) + kPageSize)? +static_assert(ART_FRAME_SIZE_LIMIT < kArmStackOverflowReservedBytes, "Frame size limit too large"); +static_assert(ART_FRAME_SIZE_LIMIT < kArm64StackOverflowReservedBytes, + "Frame size limit too large"); +static_assert(ART_FRAME_SIZE_LIMIT < kMipsStackOverflowReservedBytes, + "Frame size limit too large"); +static_assert(ART_FRAME_SIZE_LIMIT < kMips64StackOverflowReservedBytes, + "Frame size limit too large"); +static_assert(ART_FRAME_SIZE_LIMIT < kX86StackOverflowReservedBytes, + "Frame size limit too large"); +static_assert(ART_FRAME_SIZE_LIMIT < kX86_64StackOverflowReservedBytes, + "Frame size limit too large"); size_t GetStackOverflowReservedBytes(InstructionSet isa) { switch (isa) { diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index ac8f5233da..32768b0263 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -910,7 +910,20 @@ MACRO0(RETURN_OR_DELIVER_PENDING_EXCEPTION) END_MACRO // Generate the allocation entrypoints for each allocator. -GENERATE_ALLOC_ENTRYPOINTS_FOR_EACH_ALLOCATOR +GENERATE_ALLOC_ENTRYPOINTS_FOR_NON_REGION_TLAB_ALLOCATORS +// Comment out allocators that have x86_64 specific asm. +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY(_region_tlab, RegionTLAB) +// GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_WITH_ACCESS_CHECK(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_CHECK_AND_ALLOC_ARRAY(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_CHECK_AND_ALLOC_ARRAY_WITH_ACCESS_CHECK(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(_region_tlab, RegionTLAB) +GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(_region_tlab, RegionTLAB) // A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_rosalloc, RosAlloc). DEFINE_FUNCTION art_quick_alloc_object_rosalloc @@ -1003,6 +1016,14 @@ END_FUNCTION art_quick_alloc_object_rosalloc MACRO1(ALLOC_OBJECT_TLAB_FAST_PATH, slowPathLabel) testl %edx, %edx // Check null class jz RAW_VAR(slowPathLabel) + ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH(RAW_VAR(slowPathLabel)) +END_MACRO + +// The common fast path code for art_quick_alloc_object_resolved_region_tlab. +// +// RDI: type_idx, RSI: ArtMethod*, RDX/EDX: the class, RAX: return value. +// RCX: scratch, r8: Thread::Current(). +MACRO1(ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH, slowPathLabel) // Check class status. cmpl LITERAL(MIRROR_CLASS_STATUS_INITIALIZED), MIRROR_CLASS_STATUS_OFFSET(%rdx) jne RAW_VAR(slowPathLabel) @@ -1014,26 +1035,73 @@ MACRO1(ALLOC_OBJECT_TLAB_FAST_PATH, slowPathLabel) // kAccClassIsFinalizable testl LITERAL(ACCESS_FLAGS_CLASS_IS_FINALIZABLE), MIRROR_CLASS_ACCESS_FLAGS_OFFSET(%rdx) jnz RAW_VAR(slowPathLabel) - movq %gs:THREAD_SELF_OFFSET, %r8 // r8 = thread - movq THREAD_LOCAL_END_OFFSET(%r8), %rax // Load thread_local_end. - subq THREAD_LOCAL_POS_OFFSET(%r8), %rax // Compute the remaining buffer size. - movl MIRROR_CLASS_OBJECT_SIZE_OFFSET(%rdx), %ecx // Load the object size. - cmpq %rax, %rcx // Check if it fits. OK to do this - // before rounding up the object size - // assuming the buf size alignment. + ALLOC_OBJECT_INITIALIZED_TLAB_FAST_PATH(RAW_VAR(slowPathLabel)) +END_MACRO + +// The fast path code for art_quick_alloc_object_initialized_region_tlab. +// +// RDI: type_idx, RSI: ArtMethod*, RDX/EDX: the class, RAX: return value. +// RCX: scratch, r8: Thread::Current(). +MACRO1(ALLOC_OBJECT_INITIALIZED_TLAB_FAST_PATH, slowPathLabel) + movq %gs:THREAD_SELF_OFFSET, %r8 // r8 = thread + movl MIRROR_CLASS_OBJECT_SIZE_OFFSET(%rdx), %ecx // Load the object size. + movq THREAD_LOCAL_POS_OFFSET(%r8), %rax + leaq OBJECT_ALIGNMENT_MASK(%rax, %rcx), %rcx // Add size to pos, note that these + // are both 32 bit ints, overflow + // will cause the add to be past the + // end of the thread local region. + // Also sneak in alignment mask add. + andq LITERAL(OBJECT_ALIGNMENT_MASK_TOGGLED64), %rcx // Align the size by 8. (addr + 7) & + // ~7. + cmpq THREAD_LOCAL_END_OFFSET(%r8), %rcx // Check if it fits. ja RAW_VAR(slowPathLabel) - addl LITERAL(OBJECT_ALIGNMENT_MASK), %ecx // Align the size by 8. (addr + 7) & ~7. - andl LITERAL(OBJECT_ALIGNMENT_MASK_TOGGLED), %ecx - movq THREAD_LOCAL_POS_OFFSET(%r8), %rax // Load thread_local_pos - // as allocated object. - addq %rax, %rcx // Add the object size. - movq %rcx, THREAD_LOCAL_POS_OFFSET(%r8) // Update thread_local_pos. - addq LITERAL(1), THREAD_LOCAL_OBJECTS_OFFSET(%r8) // Increase thread_local_objects. - // Store the class pointer in the header. - // No fence needed for x86. + movq %rcx, THREAD_LOCAL_POS_OFFSET(%r8) // Update thread_local_pos. + addq LITERAL(1), THREAD_LOCAL_OBJECTS_OFFSET(%r8) // Increase thread_local_objects. + // Store the class pointer in the + // header. + // No fence needed for x86. POISON_HEAP_REF edx movl %edx, MIRROR_OBJECT_CLASS_OFFSET(%rax) - ret // Fast path succeeded. + ret // Fast path succeeded. +END_MACRO + +// The fast path code for art_quick_alloc_array_region_tlab. +// Inputs: RDI: uint32_t type_idx, RSI: int32_t component_count, RDX: ArtMethod* method +// Temps: RCX: the class, r8, r9 +// Output: RAX: return value. +MACRO1(ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED, slowPathLabel) + movq %rcx, %r8 // Save class for later + movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%rcx), %ecx // Load component type. + UNPOISON_HEAP_REF ecx + movl MIRROR_CLASS_OBJECT_PRIMITIVE_TYPE_OFFSET(%rcx), %ecx // Load primitive type. + shrq LITERAL(PRIMITIVE_TYPE_SIZE_SHIFT_SHIFT), %rcx // Get component size shift. + movq %rsi, %r9 + salq %cl, %r9 // Calculate array count shifted. + // Add array header + alignment rounding. + addq LITERAL(MIRROR_INT_ARRAY_DATA_OFFSET + OBJECT_ALIGNMENT_MASK), %r9 + // Add 4 extra bytes if we are doing a long array. + addq LITERAL(1), %rcx + andq LITERAL(4), %rcx + addq %rcx, %r9 + movq %gs:THREAD_SELF_OFFSET, %rcx // rcx = thread +#if MIRROR_LONG_ARRAY_DATA_OFFSET != MIRROR_INT_ARRAY_DATA_OFFSET + 4 +#error Long array data offset must be 4 greater than int array data offset. +#endif + // Mask out the unaligned part to make sure we are 8 byte aligned. + andq LITERAL(OBJECT_ALIGNMENT_MASK_TOGGLED64), %r9 + movq THREAD_LOCAL_POS_OFFSET(%rcx), %rax + addq %rax, %r9 + cmpq THREAD_LOCAL_END_OFFSET(%rcx), %r9 // Check if it fits. + ja RAW_VAR(slowPathLabel) + movq %r9, THREAD_LOCAL_POS_OFFSET(%rcx) // Update thread_local_pos. + addq LITERAL(1), THREAD_LOCAL_OBJECTS_OFFSET(%rcx) // Increase thread_local_objects. + // Store the class pointer in the + // header. + // No fence needed for x86. + POISON_HEAP_REF r8d + movl %r8d, MIRROR_OBJECT_CLASS_OFFSET(%rax) + movl %esi, MIRROR_ARRAY_LENGTH_OFFSET(%rax) + ret // Fast path succeeded. END_MACRO // The common slow path code for art_quick_alloc_object_tlab and art_quick_alloc_object_region_tlab. @@ -1046,6 +1114,16 @@ MACRO1(ALLOC_OBJECT_TLAB_SLOW_PATH, cxx_name) RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception END_MACRO +// The slow path code for art_quick_alloc_array_region_tlab. +MACRO1(ALLOC_ARRAY_TLAB_SLOW_PATH, cxx_name) + SETUP_SAVE_REFS_ONLY_FRAME // save ref containing registers for GC + // Outgoing argument set up + movq %gs:THREAD_SELF_OFFSET, %rcx // pass Thread::Current() + call CALLVAR(cxx_name) // cxx_name(arg0, arg1, arg2, Thread*) + RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address + RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception +END_MACRO + // A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_tlab, TLAB). DEFINE_FUNCTION art_quick_alloc_object_tlab // Fast path tlab allocation. @@ -1065,6 +1143,82 @@ DEFINE_FUNCTION art_quick_alloc_object_tlab ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeTLAB END_FUNCTION art_quick_alloc_object_tlab +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY(_region_tlab, RegionTLAB). +DEFINE_FUNCTION art_quick_alloc_array_region_tlab + // Fast path region tlab allocation. + // RDI: uint32_t type_idx, RSI: int32_t component_count, RDX: ArtMethod* + // RCX: klass, R8, R9: free. RAX: return val. +#if !defined(USE_READ_BARRIER) + int3 + int3 +#endif + movq ART_METHOD_DEX_CACHE_TYPES_OFFSET_64(%rdx), %rcx // Load dex cache resolved types array + movl 0(%rcx, %rdi, COMPRESSED_REFERENCE_SIZE), %ecx // Load the class + // Null check so that we can load the lock word. + testl %ecx, %ecx + jz .Lart_quick_alloc_array_region_tlab_slow_path + + cmpl LITERAL(0), %gs:THREAD_IS_GC_MARKING_OFFSET + jne .Lart_quick_alloc_array_region_tlab_class_load_read_barrier_marking +.Lart_quick_alloc_array_region_tlab_class_load_read_barrier_slow_path_exit: + ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED .Lart_quick_alloc_array_region_tlab_slow_path +.Lart_quick_alloc_array_region_tlab_class_load_read_barrier_marking: + // Check the mark bit, if it is 1 return. + testl LITERAL(LOCK_WORD_MARK_BIT_MASK_SHIFTED), MIRROR_OBJECT_LOCK_WORD_OFFSET(%ecx) + jnz .Lart_quick_alloc_array_region_tlab_class_load_read_barrier_slow_path_exit +.Lart_quick_alloc_array_region_tlab_class_load_read_barrier_slow_path: + // The read barrier slow path. Mark the class. + PUSH rdi + PUSH rsi + PUSH rdx + // Outgoing argument set up + movq %rcx, %rdi // Pass the class as the first param. + call SYMBOL(artReadBarrierMark) // cxx_name(mirror::Object* obj) + movq %rax, %rcx + POP rdx + POP rsi + POP rdi + jmp .Lart_quick_alloc_array_region_tlab_class_load_read_barrier_slow_path_exit +.Lart_quick_alloc_array_region_tlab_slow_path: + ALLOC_ARRAY_TLAB_SLOW_PATH artAllocArrayFromCodeRegionTLAB +END_FUNCTION art_quick_alloc_array_region_tlab + +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(_region_tlab, RegionTLAB). +DEFINE_FUNCTION art_quick_alloc_array_resolved_region_tlab + // Fast path region tlab allocation. + // RDI: mirror::Class* klass, RSI: int32_t component_count, RDX: ArtMethod* + // RCX: mirror::Class* klass, R8, R9: free. RAX: return val. +#if !defined(USE_READ_BARRIER) + int3 + int3 +#endif + movq %rdi, %rcx + // Already resolved, no null check. + cmpl LITERAL(0), %gs:THREAD_IS_GC_MARKING_OFFSET + jne .Lart_quick_alloc_array_resolved_region_tlab_class_load_read_barrier_marking +.Lart_quick_alloc_array_resolved_region_tlab_class_load_read_barrier_slow_path_exit: + ALLOC_ARRAY_TLAB_FAST_PATH_RESOLVED .Lart_quick_alloc_array_resolved_region_tlab_slow_path +.Lart_quick_alloc_array_resolved_region_tlab_class_load_read_barrier_marking: + // Check the mark bit, if it is 1 return. + testl LITERAL(LOCK_WORD_MARK_BIT_MASK_SHIFTED), MIRROR_OBJECT_LOCK_WORD_OFFSET(%ecx) + jnz .Lart_quick_alloc_array_region_tlab_class_load_read_barrier_slow_path_exit +.Lart_quick_alloc_array_resolved_region_tlab_class_load_read_barrier_slow_path: + // The read barrier slow path. Mark the class. + PUSH rdi + PUSH rsi + PUSH rdx + // Outgoing argument set up + movq %rcx, %rdi // Pass the class as the first param. + call SYMBOL(artReadBarrierMark) // cxx_name(mirror::Object* obj) + movq %rax, %rcx + POP rdx + POP rsi + POP rdi + jmp .Lart_quick_alloc_array_resolved_region_tlab_class_load_read_barrier_slow_path_exit +.Lart_quick_alloc_array_resolved_region_tlab_slow_path: + ALLOC_ARRAY_TLAB_SLOW_PATH artAllocArrayFromCodeResolvedRegionTLAB +END_FUNCTION art_quick_alloc_array_resolved_region_tlab + // A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT(_region_tlab, RegionTLAB). DEFINE_FUNCTION art_quick_alloc_object_region_tlab // Fast path region tlab allocation. @@ -1074,29 +1228,30 @@ DEFINE_FUNCTION art_quick_alloc_object_region_tlab int3 int3 #endif - // Might need a special macro since rsi and edx is 32b/64b mismatched. movq ART_METHOD_DEX_CACHE_TYPES_OFFSET_64(%rsi), %rdx // Load dex cache resolved types array - // Might need to break down into multiple instructions to get the base address in a register. - // Load the class - movl 0(%rdx, %rdi, COMPRESSED_REFERENCE_SIZE), %edx - cmpl LITERAL(0), %gs:THREAD_IS_GC_MARKING_OFFSET - jz .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit + movl 0(%rdx, %rdi, COMPRESSED_REFERENCE_SIZE), %edx // Load the class // Null check so that we can load the lock word. testl %edx, %edx - jz .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit - // Check the mark bit, if it is 1 return. - testl LITERAL(LOCK_WORD_MARK_BIT_MASK_SHIFTED), MIRROR_OBJECT_LOCK_WORD_OFFSET(%edx) - jz .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path + jz .Lart_quick_alloc_object_region_tlab_slow_path + // Test if the GC is marking. + cmpl LITERAL(0), %gs:THREAD_IS_GC_MARKING_OFFSET + jne .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_marking .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit: ALLOC_OBJECT_TLAB_FAST_PATH .Lart_quick_alloc_object_region_tlab_slow_path +.Lart_quick_alloc_object_region_tlab_class_load_read_barrier_marking: + // Check the mark bit, if it is 1 avoid the read barrier. + testl LITERAL(LOCK_WORD_MARK_BIT_MASK_SHIFTED), MIRROR_OBJECT_LOCK_WORD_OFFSET(%edx) + jnz .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path: // The read barrier slow path. Mark the class. PUSH rdi PUSH rsi + subq LITERAL(8), %rsp // 16 byte alignment // Outgoing argument set up movq %rdx, %rdi // Pass the class as the first param. call SYMBOL(artReadBarrierMark) // cxx_name(mirror::Object* obj) movq %rax, %rdx + addq LITERAL(8), %rsp POP rsi POP rdi jmp .Lart_quick_alloc_object_region_tlab_class_load_read_barrier_slow_path_exit @@ -1104,6 +1259,77 @@ DEFINE_FUNCTION art_quick_alloc_object_region_tlab ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeRegionTLAB END_FUNCTION art_quick_alloc_object_region_tlab +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_region_tlab, RegionTLAB). +DEFINE_FUNCTION art_quick_alloc_object_resolved_region_tlab + // Fast path region tlab allocation. + // RDI: mirror::Class* klass, RSI: ArtMethod* + // RDX, RCX, R8, R9: free. RAX: return val. +#if !defined(USE_READ_BARRIER) + int3 + int3 +#endif + movq %rdi, %rdx + cmpl LITERAL(0), %gs:THREAD_IS_GC_MARKING_OFFSET + jne .Lart_quick_alloc_object_resolved_region_tlab_class_load_read_barrier_marking +.Lart_quick_alloc_object_resolved_region_tlab_class_load_read_barrier_slow_path_exit: + ALLOC_OBJECT_RESOLVED_TLAB_FAST_PATH .Lart_quick_alloc_object_resolved_region_tlab_slow_path +.Lart_quick_alloc_object_resolved_region_tlab_class_load_read_barrier_marking: + // Check the mark bit, if it is 1 avoid the read barrier. + testl LITERAL(LOCK_WORD_MARK_BIT_MASK_SHIFTED), MIRROR_OBJECT_LOCK_WORD_OFFSET(%edx) + jnz .Lart_quick_alloc_object_resolved_region_tlab_class_load_read_barrier_slow_path_exit +.Lart_quick_alloc_object_resolved_region_tlab_class_load_read_barrier_slow_path: + // The read barrier slow path. Mark the class. + PUSH rdi + PUSH rsi + subq LITERAL(8), %rsp // 16 byte alignment + // Outgoing argument set up + movq %rdx, %rdi // Pass the class as the first param. + call SYMBOL(artReadBarrierMark) // cxx_name(mirror::Object* obj) + movq %rax, %rdx + addq LITERAL(8), %rsp + POP rsi + POP rdi + jmp .Lart_quick_alloc_object_resolved_region_tlab_class_load_read_barrier_slow_path_exit +.Lart_quick_alloc_object_resolved_region_tlab_slow_path: + ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeResolvedRegionTLAB +END_FUNCTION art_quick_alloc_object_resolved_region_tlab + +// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(_region_tlab, RegionTLAB). +DEFINE_FUNCTION art_quick_alloc_object_initialized_region_tlab + // Fast path region tlab allocation. + // RDI: mirror::Class* klass, RSI: ArtMethod* + // RDX, RCX, R8, R9: free. RAX: return val. +#if !defined(USE_READ_BARRIER) + int3 + int3 +#endif + // Might need a special macro since rsi and edx is 32b/64b mismatched. + movq %rdi, %rdx + cmpl LITERAL(0), %gs:THREAD_IS_GC_MARKING_OFFSET + jne .Lart_quick_alloc_object_initialized_region_tlab_class_load_read_barrier_marking +.Lart_quick_alloc_object_initialized_region_tlab_class_load_read_barrier_slow_path_exit: + ALLOC_OBJECT_INITIALIZED_TLAB_FAST_PATH .Lart_quick_alloc_object_initialized_region_tlab_slow_path +.Lart_quick_alloc_object_initialized_region_tlab_class_load_read_barrier_marking: + // Check the mark bit, if it is 1 avoid the read barrier. + testl LITERAL(LOCK_WORD_MARK_BIT_MASK_SHIFTED), MIRROR_OBJECT_LOCK_WORD_OFFSET(%edx) + jnz .Lart_quick_alloc_object_initialized_region_tlab_class_load_read_barrier_slow_path +.Lart_quick_alloc_object_initialized_region_tlab_class_load_read_barrier_slow_path: + // The read barrier slow path. Mark the class. + PUSH rdi + PUSH rsi + subq LITERAL(8), %rsp // 16 byte alignment + // Outgoing argument set up + movq %rdx, %rdi // Pass the class as the first param. + call SYMBOL(artReadBarrierMark) // cxx_name(mirror::Object* obj) + movq %rax, %rdx + addq LITERAL(8), %rsp + POP rsi + POP rdi + jmp .Lart_quick_alloc_object_initialized_region_tlab_class_load_read_barrier_slow_path_exit +.Lart_quick_alloc_object_initialized_region_tlab_slow_path: + ALLOC_OBJECT_TLAB_SLOW_PATH artAllocObjectFromCodeInitializedRegionTLAB +END_FUNCTION art_quick_alloc_object_initialized_region_tlab + ONE_ARG_DOWNCALL art_quick_resolve_string, artResolveStringFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER ONE_ARG_DOWNCALL art_quick_initialize_static_storage, artInitializeStaticStorageFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER ONE_ARG_DOWNCALL art_quick_initialize_type, artInitializeTypeFromCode, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER diff --git a/runtime/base/logging.h b/runtime/base/logging.h index 6323eee53a..ac21a3f0ea 100644 --- a/runtime/base/logging.h +++ b/runtime/base/logging.h @@ -57,6 +57,7 @@ struct LogVerbosity { bool verifier; bool image; bool systrace_lock_logging; // Enabled with "-verbose:sys-locks". + bool agents; }; // Global log verbosity setting, initialized by InitLogging. diff --git a/runtime/base/macros.h b/runtime/base/macros.h index 3c43253e67..5a50247f5a 100644 --- a/runtime/base/macros.h +++ b/runtime/base/macros.h @@ -75,7 +75,7 @@ template<typename T> ART_FRIEND_TEST(test_set_name, individual_test) ALWAYS_INLINE void* operator new(size_t, void* ptr) noexcept { return ptr; } \ ALWAYS_INLINE void operator delete(void*, void*) noexcept { } \ private: \ - void* operator new(size_t) = delete // NOLINT + void* operator new(size_t) = delete // NOLINT // The arraysize(arr) macro returns the # of elements in an array arr. // The expression is a compile-time constant, and therefore can be @@ -135,13 +135,13 @@ char (&ArraySizeHelper(T (&array)[N]))[N]; #define ARRAYSIZE_UNSAFE(a) \ ((sizeof(a) / sizeof(*(a))) / static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))) -#define SIZEOF_MEMBER(t, f) sizeof((reinterpret_cast<t*>(4096))->f) // NOLINT +#define SIZEOF_MEMBER(t, f) sizeof((reinterpret_cast<t*>(4096))->f) // NOLINT #define OFFSETOF_MEMBER(t, f) \ - (reinterpret_cast<uintptr_t>(&reinterpret_cast<t*>(16)->f) - static_cast<uintptr_t>(16u)) // NOLINT + (reinterpret_cast<uintptr_t>(&reinterpret_cast<t*>(16)->f) - static_cast<uintptr_t>(16u)) // NOLINT #define OFFSETOF_MEMBERPTR(t, f) \ - (reinterpret_cast<uintptr_t>(&(reinterpret_cast<t*>(16)->*f)) - static_cast<uintptr_t>(16)) // NOLINT + (reinterpret_cast<uintptr_t>(&(reinterpret_cast<t*>(16)->*f)) - static_cast<uintptr_t>(16)) // NOLINT #define PACKED(x) __attribute__ ((__aligned__(x), __packed__)) diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 46722ecad7..4d48da6a83 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -4630,18 +4630,23 @@ bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass, } else { value_it.ReadValueToField<false>(field); } + if (self->IsExceptionPending()) { + break; + } DCHECK(!value_it.HasNext() || field_it.HasNextStaticField()); } } } - ArtMethod* clinit = klass->FindClassInitializer(image_pointer_size_); - if (clinit != nullptr) { - CHECK(can_init_statics); - JValue result; - clinit->Invoke(self, nullptr, 0, &result, "V"); - } + if (!self->IsExceptionPending()) { + ArtMethod* clinit = klass->FindClassInitializer(image_pointer_size_); + if (clinit != nullptr) { + CHECK(can_init_statics); + JValue result; + clinit->Invoke(self, nullptr, 0, &result, "V"); + } + } self->AllowThreadSuspension(); uint64_t t1 = NanoTime(); diff --git a/runtime/experimental_flags.h b/runtime/experimental_flags.h index fde1a5f3ab..7faa2dc7e3 100644 --- a/runtime/experimental_flags.h +++ b/runtime/experimental_flags.h @@ -26,6 +26,8 @@ struct ExperimentalFlags { // The actual flag values. enum { kNone = 0x0000, + kAgents = 0x0001, // 0b00000001 + kRuntimePlugins = 0x0002, // 0b00000010 }; constexpr ExperimentalFlags() : value_(0x0000) {} @@ -61,9 +63,19 @@ struct ExperimentalFlags { uint32_t value_; }; -inline std::ostream& operator<<(std::ostream& stream, - const ExperimentalFlags& e ATTRIBUTE_UNUSED) { - stream << "kNone"; +inline std::ostream& operator<<(std::ostream& stream, const ExperimentalFlags& e) { + bool started = false; + if (e & ExperimentalFlags::kAgents) { + stream << (started ? "|" : "") << "kAgents"; + started = true; + } + if (e & ExperimentalFlags::kRuntimePlugins) { + stream << (started ? "|" : "") << "kRuntimePlugins"; + started = true; + } + if (!started) { + stream << "kNone"; + } return stream; } diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc index 88fbf781bc..b574c3bf3a 100644 --- a/runtime/gc/heap.cc +++ b/runtime/gc/heap.cc @@ -230,6 +230,9 @@ Heap::Heap(size_t initial_size, total_wait_time_(0), verify_object_mode_(kVerifyObjectModeDisabled), disable_moving_gc_count_(0), + semi_space_collector_(nullptr), + mark_compact_collector_(nullptr), + concurrent_copying_collector_(nullptr), is_running_on_memory_tool_(Runtime::Current()->IsRunningOnMemoryTool()), use_tlab_(use_tlab), main_space_backup_(nullptr), diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index c2e2a1edd2..6fcad295bb 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -384,30 +384,7 @@ ImageSpace* ImageSpace::CreateBootImage(const char* image_location, &has_system, &cache_filename, &dalvik_cache_exists, &has_cache, &is_global_cache); - // If we're starting with the global cache, and we're the zygote, try to see whether there are - // OTA artifacts from the A/B OTA preopting to move over. - // (It is structurally simpler to check this here, instead of complicating the compile/relocate - // logic below.) const bool is_zygote = Runtime::Current()->IsZygote(); - if (is_global_cache && is_zygote) { - VLOG(startup) << "Checking for A/B OTA data."; - TryMoveOTAArtifacts(cache_filename, dalvik_cache_exists); - - // Retry. There are two cases where the old info is outdated: - // * There wasn't a boot image before (e.g., some failure on boot), but now the OTA preopted - // image has been moved in-place. - // * There was a boot image before, and we tried to move the OTA preopted image, but a failure - // happened and there is no file anymore. - found_image = FindImageFilename(image_location, - image_isa, - &system_filename, - &has_system, - &cache_filename, - &dalvik_cache_exists, - &has_cache, - &is_global_cache); - } - if (is_zygote && !secondary_image) { MarkZygoteStart(image_isa, Runtime::Current()->GetZygoteMaxFailedBoots()); } @@ -529,6 +506,17 @@ ImageSpace* ImageSpace::CreateBootImage(const char* image_location, error_msg); } if (space != nullptr) { + // Check whether there is enough space left over in the data partition. Even if we can load + // the image, we need to be conservative, as some parts of the platform are not very tolerant + // of space constraints. + // ImageSpace doesn't know about the data partition per se, it relies on the FindImageFilename + // helper (which relies on GetDalvikCache). So for now, if we load an image out of /system, + // ignore the check (as it would test for free space in /system instead). + if (!is_system && !CheckSpace(*image_filename, error_msg)) { + // No. Delete the generated image and try to run out of the dex files. + PruneDalvikCache(image_isa); + return nullptr; + } return space; } diff --git a/runtime/gc/space/image_space_fs.h b/runtime/gc/space/image_space_fs.h index 8e852fa54b..fa941c0376 100644 --- a/runtime/gc/space/image_space_fs.h +++ b/runtime/gc/space/image_space_fs.h @@ -79,115 +79,6 @@ static void DeleteDirectoryContents(const std::string& dir, bool recurse) { CHECK_EQ(0, closedir(c_dir)) << "Unable to close directory."; } -static bool HasContent(const char* dir) { - if (!OS::DirectoryExists(dir)) { - return false; - } - DIR* c_dir = opendir(dir); - if (c_dir == nullptr) { - PLOG(WARNING) << "Unable to open " << dir << " to delete it if empty"; - return false; - } - - for (struct dirent* de = readdir(c_dir); de != nullptr; de = readdir(c_dir)) { - const char* name = de->d_name; - if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { - continue; - } - // Something here. - CHECK_EQ(0, closedir(c_dir)) << "Unable to close directory."; - return true; - } - CHECK_EQ(0, closedir(c_dir)) << "Unable to close directory."; - return false; -} - -// Delete this directory, if empty. Then repeat with the parents. Skips non-existing directories. -// If stop_at isn't null, the recursion will stop when a directory with the given name is found. -static void DeleteEmptyDirectoriesUpTo(const std::string& dir, const char* stop_at) { - if (HasContent(dir.c_str())) { - return; - } - if (stop_at != nullptr) { - // This check isn't precise, but good enough in practice. - if (EndsWith(dir, stop_at)) { - return; - } - } - if (OS::DirectoryExists(dir.c_str())) { - if (rmdir(dir.c_str()) != 0) { - PLOG(ERROR) << "Unable to rmdir " << dir; - return; - } - } - size_t last_slash = dir.rfind('/'); - if (last_slash != std::string::npos) { - DeleteEmptyDirectoriesUpTo(dir.substr(0, last_slash), stop_at); - } -} - -static void MoveOTAArtifacts(const char* src, const char* trg) { - DCHECK(OS::DirectoryExists(src)); - DCHECK(OS::DirectoryExists(trg)); - - if (HasContent(trg)) { - LOG(WARNING) << "We do not support merging caches, but the target isn't empty: " << src - << " to " << trg; - return; - } - - if (rename(src, trg) != 0) { - PLOG(ERROR) << "Could not rename OTA cache " << src << " to target " << trg; - } -} - -// This is some dlopen/dlsym and hardcoded data to avoid a dependency on libselinux. Make sure -// this stays in sync! -static bool RelabelOTAFiles(const std::string& dalvik_cache_dir) { - // We only expect selinux on devices. Don't even attempt this on the host. - if (!kIsTargetBuild) { - return true; - } - - // Custom deleter, so we can use std::unique_ptr. - struct HandleDeleter { - void operator()(void* in) { - if (in != nullptr && dlclose(in) != 0) { - PLOG(ERROR) << "Could not close selinux handle."; - } - } - }; - - // Look for selinux library. - std::unique_ptr<void, HandleDeleter> selinux_handle(dlopen("libselinux.so", RTLD_NOW)); - if (selinux_handle == nullptr) { - // Assume everything's OK if we can't open the library. - return true; - } - dlerror(); // Clean dlerror string. - - void* restorecon_ptr = dlsym(selinux_handle.get(), "selinux_android_restorecon"); - if (restorecon_ptr == nullptr) { - // Can't find the relabel function. That's bad. Make sure the zygote fails, as we have no - // other recourse to make this error obvious. - const char* error_string = dlerror(); - LOG(FATAL) << "Could not find selinux restorecon function: " - << ((error_string != nullptr) ? error_string : "(unknown error)"); - UNREACHABLE(); - } - - using RestoreconFn = int (*)(const char*, unsigned int); - constexpr unsigned int kRecursive = 4U; - - RestoreconFn restorecon_fn = reinterpret_cast<RestoreconFn>(restorecon_ptr); - if (restorecon_fn(dalvik_cache_dir.c_str(), kRecursive) != 0) { - LOG(ERROR) << "Failed to restorecon " << dalvik_cache_dir; - return false; - } - - return true; -} - } // namespace impl @@ -226,8 +117,21 @@ static void MarkZygoteStart(const InstructionSet isa, const uint32_t max_failed_ file.reset(OS::CreateEmptyFile(file_name)); if (file.get() == nullptr) { + int saved_errno = errno; PLOG(WARNING) << "Failed to create boot marker."; - return; + if (saved_errno != ENOSPC) { + return; + } + + LOG(WARNING) << "Pruning dalvik cache because of low-memory situation."; + impl::DeleteDirectoryContents(isa_subdir, false); + + // Try once more. + file.reset(OS::OpenFileReadWrite(file_name)); + if (file == nullptr) { + PLOG(WARNING) << "Failed to create boot marker."; + return; + } } } else { if (!file->ReadFully(&num_failed_boots, sizeof(num_failed_boots))) { @@ -262,53 +166,6 @@ static void MarkZygoteStart(const InstructionSet isa, const uint32_t max_failed_ } } -static void TryMoveOTAArtifacts(const std::string& cache_filename, bool dalvik_cache_exists) { - // We really assume here global means /data/dalvik-cache, and we'll inject 'ota.' Make sure - // that's true. - CHECK(StartsWith(cache_filename, "/data/dalvik-cache")) << cache_filename; - - // Inject ota subdirectory. - std::string ota_filename(cache_filename); - ota_filename = ota_filename.insert(strlen("/data/"), "ota/"); - CHECK(StartsWith(ota_filename, "/data/ota/dalvik-cache")) << ota_filename; - - // See if the file exists. - if (OS::FileExists(ota_filename.c_str())) { - VLOG(startup) << "OTA directory does exist, checking for artifacts"; - - size_t last_slash = ota_filename.rfind('/'); - CHECK_NE(last_slash, std::string::npos); - std::string ota_source_dir = ota_filename.substr(0, last_slash); - - // We need the dalvik cache now, really. - if (dalvik_cache_exists) { - size_t last_cache_slash = cache_filename.rfind('/'); - DCHECK_NE(last_cache_slash, std::string::npos); - std::string dalvik_cache_target_dir = cache_filename.substr(0, last_cache_slash); - - // First clean the target cache. - impl::DeleteDirectoryContents(dalvik_cache_target_dir.c_str(), false); - - // Now move things over. - impl::MoveOTAArtifacts(ota_source_dir.c_str(), dalvik_cache_target_dir.c_str()); - - // Last step: ensure the files have the right selinux label. - if (!impl::RelabelOTAFiles(dalvik_cache_target_dir)) { - // This isn't good. We potentially moved files, but they have the wrong label. Delete the - // files. - LOG(WARNING) << "Could not relabel files, must delete dalvik-cache."; - impl::DeleteDirectoryContents(dalvik_cache_target_dir.c_str(), false); - } - } - - // Cleanup. - impl::DeleteDirectoryContents(ota_source_dir.c_str(), true); - impl::DeleteEmptyDirectoriesUpTo(ota_source_dir, "ota"); - } else { - VLOG(startup) << "No OTA directory."; - } -} - } // namespace space } // namespace gc } // namespace art diff --git a/runtime/generated/asm_support_gen.h b/runtime/generated/asm_support_gen.h index 96924722d8..716c23d1b0 100644 --- a/runtime/generated/asm_support_gen.h +++ b/runtime/generated/asm_support_gen.h @@ -98,6 +98,8 @@ DEFINE_CHECK_EQ(static_cast<uint32_t>(LOCK_WORD_MARK_BIT_MASK_SHIFTED), (static_ DEFINE_CHECK_EQ(static_cast<size_t>(OBJECT_ALIGNMENT_MASK), (static_cast<size_t>(art::kObjectAlignment - 1))) #define OBJECT_ALIGNMENT_MASK_TOGGLED 0xfffffff8 DEFINE_CHECK_EQ(static_cast<uint32_t>(OBJECT_ALIGNMENT_MASK_TOGGLED), (static_cast<uint32_t>(~static_cast<uint32_t>(art::kObjectAlignment - 1)))) +#define OBJECT_ALIGNMENT_MASK_TOGGLED64 0xfffffffffffffff8 +DEFINE_CHECK_EQ(static_cast<uint64_t>(OBJECT_ALIGNMENT_MASK_TOGGLED64), (static_cast<uint64_t>(~static_cast<uint64_t>(art::kObjectAlignment - 1)))) #define ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE 128 DEFINE_CHECK_EQ(static_cast<int32_t>(ROSALLOC_MAX_THREAD_LOCAL_BRACKET_SIZE), (static_cast<int32_t>((art::gc::allocator::RosAlloc::kMaxThreadLocalBracketSize)))) #define ROSALLOC_BRACKET_QUANTUM_SIZE_SHIFT 3 diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc index c644cde5db..2401bec9f3 100644 --- a/runtime/java_vm_ext.cc +++ b/runtime/java_vm_ext.cc @@ -48,7 +48,7 @@ static size_t gGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 b static const size_t kWeakGlobalsInitial = 16; // Arbitrary. static const size_t kWeakGlobalsMax = 51200; // Arbitrary sanity check. (Must fit in 16 bits.) -static bool IsBadJniVersion(int version) { +bool JavaVMExt::IsBadJniVersion(int version) { // We don't support JNI_VERSION_1_1. These are the only other valid versions. return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6; } @@ -344,13 +344,6 @@ class JII { } static jint GetEnv(JavaVM* vm, void** env, jint version) { - // GetEnv always returns a JNIEnv* for the most current supported JNI version, - // and unlike other calls that take a JNI version doesn't care if you supply - // JNI_VERSION_1_1, which we don't otherwise support. - if (IsBadJniVersion(version) && version != JNI_VERSION_1_1) { - LOG(ERROR) << "Bad JNI version passed to GetEnv: " << version; - return JNI_EVERSION; - } if (vm == nullptr || env == nullptr) { return JNI_ERR; } @@ -359,8 +352,8 @@ class JII { *env = nullptr; return JNI_EDETACHED; } - *env = thread->GetJniEnv(); - return JNI_OK; + JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm); + return raw_vm->HandleGetEnv(env, version); } private: @@ -388,7 +381,7 @@ class JII { const char* thread_name = nullptr; jobject thread_group = nullptr; if (args != nullptr) { - if (IsBadJniVersion(args->version)) { + if (JavaVMExt::IsBadJniVersion(args->version)) { LOG(ERROR) << "Bad JNI version passed to " << (as_daemon ? "AttachCurrentThreadAsDaemon" : "AttachCurrentThread") << ": " << args->version; @@ -436,7 +429,8 @@ JavaVMExt::JavaVMExt(Runtime* runtime, const RuntimeArgumentMap& runtime_options weak_globals_lock_("JNI weak global reference table lock", kJniWeakGlobalsLock), weak_globals_(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal), allow_accessing_weak_globals_(true), - weak_globals_add_condition_("weak globals add condition", weak_globals_lock_) { + weak_globals_add_condition_("weak globals add condition", weak_globals_lock_), + env_hooks_() { functions = unchecked_functions_; SetCheckJniEnabled(runtime_options.Exists(RuntimeArgumentMap::CheckJni)); } @@ -444,6 +438,26 @@ JavaVMExt::JavaVMExt(Runtime* runtime, const RuntimeArgumentMap& runtime_options JavaVMExt::~JavaVMExt() { } +jint JavaVMExt::HandleGetEnv(/*out*/void** env, jint version) { + for (GetEnvHook hook : env_hooks_) { + jint res = hook(this, env, version); + if (res == JNI_OK) { + return JNI_OK; + } else if (res != JNI_EVERSION) { + LOG(ERROR) << "Error returned from a plugin GetEnv handler! " << res; + return res; + } + } + LOG(ERROR) << "Bad JNI version passed to GetEnv: " << version; + return JNI_EVERSION; +} + +// Add a hook to handle getting environments from the GetEnv call. +void JavaVMExt::AddEnvironmentHook(GetEnvHook hook) { + CHECK(hook != nullptr) << "environment hooks shouldn't be null!"; + env_hooks_.push_back(hook); +} + void JavaVMExt::JniAbort(const char* jni_function_name, const char* msg) { Thread* self = Thread::Current(); ScopedObjectAccess soa(self); @@ -866,7 +880,7 @@ bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, if (version == JNI_ERR) { StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str()); - } else if (IsBadJniVersion(version)) { + } else if (JavaVMExt::IsBadJniVersion(version)) { StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d", path.c_str(), version); // It's unwise to call dlclose() here, but we can mark it @@ -939,7 +953,7 @@ void JavaVMExt::VisitRoots(RootVisitor* visitor) { extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { ScopedTrace trace(__FUNCTION__); const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args); - if (IsBadJniVersion(args->version)) { + if (JavaVMExt::IsBadJniVersion(args->version)) { LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version; return JNI_EVERSION; } diff --git a/runtime/java_vm_ext.h b/runtime/java_vm_ext.h index 3d055cd7ce..ed9d3abfe2 100644 --- a/runtime/java_vm_ext.h +++ b/runtime/java_vm_ext.h @@ -36,6 +36,10 @@ class ParsedOptions; class Runtime; struct RuntimeArgumentMap; +class JavaVMExt; +// Hook definition for runtime plugins. +using GetEnvHook = jint (*)(JavaVMExt* vm, /*out*/void** new_env, jint version); + class JavaVMExt : public JavaVM { public: JavaVMExt(Runtime* runtime, const RuntimeArgumentMap& runtime_options); @@ -171,6 +175,12 @@ class JavaVMExt : public JavaVM { void TrimGlobals() SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!globals_lock_); + jint HandleGetEnv(/*out*/void** env, jint version); + + void AddEnvironmentHook(GetEnvHook hook); + + static bool IsBadJniVersion(int version); + private: // Return true if self can currently access weak globals. bool MayAccessWeakGlobalsUnlocked(Thread* self) const SHARED_REQUIRES(Locks::mutator_lock_); @@ -215,6 +225,9 @@ class JavaVMExt : public JavaVM { Atomic<bool> allow_accessing_weak_globals_; ConditionVariable weak_globals_add_condition_ GUARDED_BY(weak_globals_lock_); + // TODO Maybe move this to Runtime. + std::vector<GetEnvHook> env_hooks_; + DISALLOW_COPY_AND_ASSIGN(JavaVMExt); }; diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc index 5a469e51b4..b35c958b0b 100644 --- a/runtime/jit/profile_saver.cc +++ b/runtime/jit/profile_saver.cc @@ -176,14 +176,13 @@ void ProfileSaver::NotifyJitActivityInternal() { MutexLock wait_mutex(Thread::Current(), wait_lock_); if ((NanoTime() - last_time_ns_saver_woke_up_) > MsToNs(options_.GetMinSavePeriodMs())) { WakeUpSaver(); + } else if (jit_activity_notifications_ > options_.GetMaxNotificationBeforeWake()) { + // Make sure to wake up the saver if we see a spike in the number of notifications. + // This is a precaution to avoid losing a big number of methods in case + // this is a spike with no jit after. + total_number_of_hot_spikes_++; + WakeUpSaver(); } - } else if (jit_activity_notifications_ > options_.GetMaxNotificationBeforeWake()) { - // Make sure to wake up the saver if we see a spike in the number of notifications. - // This is a precaution to avoid "loosing" a big number of methods in case - // this is a spike with no jit after. - total_number_of_hot_spikes_++; - MutexLock wait_mutex(Thread::Current(), wait_lock_); - WakeUpSaver(); } } diff --git a/runtime/jni_env_ext.cc b/runtime/jni_env_ext.cc index 1ee1611ef7..40efc898b8 100644 --- a/runtime/jni_env_ext.cc +++ b/runtime/jni_env_ext.cc @@ -45,6 +45,20 @@ static bool CheckLocalsValid(JNIEnvExt* in) NO_THREAD_SAFETY_ANALYSIS { return in->locals.IsValid(); } +jint JNIEnvExt::GetEnvHandler(JavaVMExt* vm, /*out*/void** env, jint version) { + UNUSED(vm); + // GetEnv always returns a JNIEnv* for the most current supported JNI version, + // and unlike other calls that take a JNI version doesn't care if you supply + // JNI_VERSION_1_1, which we don't otherwise support. + if (JavaVMExt::IsBadJniVersion(version) && version != JNI_VERSION_1_1) { + return JNI_EVERSION; + } + Thread* thread = Thread::Current(); + CHECK(thread != nullptr); + *env = thread->GetJniEnv(); + return JNI_OK; +} + JNIEnvExt* JNIEnvExt::Create(Thread* self_in, JavaVMExt* vm_in) { std::unique_ptr<JNIEnvExt> ret(new JNIEnvExt(self_in, vm_in)); if (CheckLocalsValid(ret.get())) { diff --git a/runtime/jni_env_ext.h b/runtime/jni_env_ext.h index d4accc342b..ac287d488a 100644 --- a/runtime/jni_env_ext.h +++ b/runtime/jni_env_ext.h @@ -54,6 +54,8 @@ struct JNIEnvExt : public JNIEnv { static Offset LocalRefCookieOffset(size_t pointer_size); static Offset SelfOffset(size_t pointer_size); + static jint GetEnvHandler(JavaVMExt* vm, /*out*/void** out, jint version); + jobject NewLocalRef(mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_); void DeleteLocalRef(jobject obj) SHARED_REQUIRES(Locks::mutator_lock_); diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc index c7e4f8b343..174da79030 100644 --- a/runtime/parsed_options.cc +++ b/runtime/parsed_options.cc @@ -23,6 +23,7 @@ #include "gc/heap.h" #include "monitor.h" #include "runtime.h" +#include "ti/agent.h" #include "trace.h" #include "utils.h" @@ -90,6 +91,13 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .Define({"-Xrunjdwp:_", "-agentlib:jdwp=_"}) .WithType<JDWP::JdwpOptions>() .IntoKey(M::JdwpOptions) + // TODO Re-enable -agentlib: once I have a good way to transform the values. + // .Define("-agentlib:_") + // .WithType<std::vector<ti::Agent>>().AppendValues() + // .IntoKey(M::AgentLib) + .Define("-agentpath:_") + .WithType<std::vector<ti::Agent>>().AppendValues() + .IntoKey(M::AgentPath) .Define("-Xms_") .WithType<MemoryKiB>() .IntoKey(M::MemoryInitialSize) @@ -289,6 +297,9 @@ std::unique_ptr<RuntimeParser> ParsedOptions::MakeParser(bool ignore_unrecognize .IntoKey(M::Experimental) .Define("-Xforce-nb-testing") .IntoKey(M::ForceNativeBridge) + .Define("-Xplugin:_") + .WithType<std::vector<Plugin>>().AppendValues() + .IntoKey(M::Plugins) .Ignore({ "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa", "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_", @@ -583,6 +594,42 @@ bool ParsedOptions::DoParse(const RuntimeOptions& options, args.Set(M::HeapGrowthLimit, args.GetOrDefault(M::MemoryMaximumSize)); } + if (args.GetOrDefault(M::Experimental) & ExperimentalFlags::kRuntimePlugins) { + LOG(WARNING) << "Experimental runtime plugin support has been enabled. No guarantees are made " + << "about stability or usage of this plugin support. Use at your own risk. Do " + << "not attempt to write shipping code that relies on the implementation of " + << "runtime plugins."; + } else if (!args.GetOrDefault(M::Plugins).empty()) { + LOG(WARNING) << "Experimental runtime plugin support has not been enabled. Ignored options: "; + for (auto& op : args.GetOrDefault(M::Plugins)) { + LOG(WARNING) << " -plugin:" << op.GetLibrary(); + } + } + + if (args.GetOrDefault(M::Experimental) & ExperimentalFlags::kAgents) { + LOG(WARNING) << "Experimental runtime agent support has been enabled. No guarantees are made " + << "the completeness, accuracy, reliability, or stability of the agent " + << "implementation. Use at your own risk. Do not attempt to write shipping code " + << "that relies on the implementation of any part of this api."; + } else if (!args.GetOrDefault(M::AgentLib).empty() || !args.GetOrDefault(M::AgentPath).empty()) { + LOG(WARNING) << "agent support has not been enabled. Enable experimental agent " + << " support with '-XExperimental:agent'. Ignored options are:"; + for (auto op : args.GetOrDefault(M::AgentLib)) { + if (op.HasArgs()) { + LOG(WARNING) << " -agentlib:" << op.GetName() << "=" << op.GetArgs(); + } else { + LOG(WARNING) << " -agentlib:" << op.GetName(); + } + } + for (auto op : args.GetOrDefault(M::AgentPath)) { + if (op.HasArgs()) { + LOG(WARNING) << " -agentpath:" << op.GetName() << "=" << op.GetArgs(); + } else { + LOG(WARNING) << " -agentpath:" << op.GetName(); + } + } + } + *runtime_options = std::move(args); return true; } @@ -627,6 +674,11 @@ void ParsedOptions::Usage(const char* fmt, ...) { UsageMessage(stream, " -showversion\n"); UsageMessage(stream, " -help\n"); UsageMessage(stream, " -agentlib:jdwp=options\n"); + // TODO add back in once -agentlib actually does something. + // UsageMessage(stream, " -agentlib:library=options (Experimental feature, " + // "requires -Xexperimental:agent, some features might not be supported)\n"); + UsageMessage(stream, " -agentpath:library_path=options (Experimental feature, " + "requires -Xexperimental:agent, some features might not be supported)\n"); UsageMessage(stream, "\n"); UsageMessage(stream, "The following extended options are supported:\n"); @@ -703,6 +755,12 @@ void ParsedOptions::Usage(const char* fmt, ...) { UsageMessage(stream, " -X[no]image-dex2oat (Whether to create and use a boot image)\n"); UsageMessage(stream, " -Xno-dex-file-fallback " "(Don't fall back to dex files without oat files)\n"); + UsageMessage(stream, " -Xplugin:<library.so> " + "(Load a runtime plugin, requires -Xexperimental:runtime-plugins)\n"); + UsageMessage(stream, " -Xexperimental:runtime-plugins" + "(Enable new and experimental agent support)\n"); + UsageMessage(stream, " -Xexperimental:agents" + "(Enable new and experimental agent support)\n"); UsageMessage(stream, "\n"); UsageMessage(stream, "The following previously supported Dalvik options are ignored:\n"); diff --git a/runtime/plugin.cc b/runtime/plugin.cc new file mode 100644 index 0000000000..481b1caa15 --- /dev/null +++ b/runtime/plugin.cc @@ -0,0 +1,88 @@ +/* + * 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 "plugin.h" + +#include <dlfcn.h> +#include "base/stringprintf.h" +#include "base/logging.h" + +namespace art { + +const char* PLUGIN_INITIALIZATION_FUNCTION_NAME = "ArtPlugin_Initialize"; +const char* PLUGIN_DEINITIALIZATION_FUNCTION_NAME = "ArtPlugin_Deinitialize"; + +Plugin::Plugin(const Plugin& other) : library_(other.library_), dlopen_handle_(nullptr) { + if (other.IsLoaded()) { + std::string err; + Load(&err); + } +} + +bool Plugin::Load(/*out*/std::string* error_msg) { + DCHECK(!IsLoaded()); + void* res = dlopen(library_.c_str(), RTLD_LAZY); + if (res == nullptr) { + *error_msg = StringPrintf("dlopen failed: %s", dlerror()); + return false; + } + // Get the initializer function + PluginInitializationFunction init = reinterpret_cast<PluginInitializationFunction>( + dlsym(res, PLUGIN_INITIALIZATION_FUNCTION_NAME)); + if (init != nullptr) { + if (!init()) { + dlclose(res); + *error_msg = StringPrintf("Initialization of plugin failed"); + return false; + } + } else { + LOG(WARNING) << this << " does not include an initialization function"; + } + dlopen_handle_ = res; + return true; +} + +bool Plugin::Unload() { + DCHECK(IsLoaded()); + bool ret = true; + void* handle = dlopen_handle_; + PluginDeinitializationFunction deinit = reinterpret_cast<PluginDeinitializationFunction>( + dlsym(handle, PLUGIN_DEINITIALIZATION_FUNCTION_NAME)); + if (deinit != nullptr) { + if (!deinit()) { + LOG(WARNING) << this << " failed deinitialization"; + ret = false; + } + } else { + LOG(WARNING) << this << " does not include a deinitialization function"; + } + dlopen_handle_ = nullptr; + if (dlclose(handle) != 0) { + LOG(ERROR) << this << " failed to dlclose: " << dlerror(); + ret = false; + } + return ret; +} + +std::ostream& operator<<(std::ostream &os, const Plugin* m) { + return os << *m; +} + +std::ostream& operator<<(std::ostream &os, Plugin const& m) { + return os << "Plugin { library=\"" << m.library_ << "\", handle=" << m.dlopen_handle_ << " }"; +} + +} // namespace art diff --git a/runtime/plugin.h b/runtime/plugin.h new file mode 100644 index 0000000000..18f3977bd5 --- /dev/null +++ b/runtime/plugin.h @@ -0,0 +1,82 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_PLUGIN_H_ +#define ART_RUNTIME_PLUGIN_H_ + +#include <string> +#include "base/logging.h" + +namespace art { + +// This function is loaded from the plugin (if present) and called during runtime initialization. +// By the time this has been called the runtime has been fully initialized but not other native +// libraries have been loaded yet. Failure to initialize is considered a fatal error. +// TODO might want to give initialization function some arguments +using PluginInitializationFunction = bool (*)(); +using PluginDeinitializationFunction = bool (*)(); + +// A class encapsulating a plugin. There is no stable plugin ABI or API and likely never will be. +// TODO Might want to put some locking in this but ATM we only load these at initialization in a +// single-threaded fashion so not much need +class Plugin { + public: + static Plugin Create(std::string lib) { + return Plugin(lib); + } + + bool IsLoaded() const { + return dlopen_handle_ != nullptr; + } + + const std::string& GetLibrary() const { + return library_; + } + + bool Load(/*out*/std::string* error_msg); + bool Unload(); + + + ~Plugin() { + if (IsLoaded() && !Unload()) { + LOG(ERROR) << "Error unloading " << this; + } + } + + Plugin(const Plugin& other); + + // Create move constructor for putting this in a list + Plugin(Plugin&& other) + : library_(other.library_), + dlopen_handle_(other.dlopen_handle_) { + other.dlopen_handle_ = nullptr; + } + + private: + explicit Plugin(std::string library) : library_(library), dlopen_handle_(nullptr) { } + + std::string library_; + void* dlopen_handle_; + + friend std::ostream& operator<<(std::ostream &os, Plugin const& m); +}; + +std::ostream& operator<<(std::ostream &os, Plugin const& m); +std::ostream& operator<<(std::ostream &os, const Plugin* m); + +} // namespace art + +#endif // ART_RUNTIME_PLUGIN_H_ diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 68fa0d32be..ddcfb6d5aa 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -130,6 +130,7 @@ #include "signal_set.h" #include "thread.h" #include "thread_list.h" +#include "ti/agent.h" #include "trace.h" #include "transaction.h" #include "utils.h" @@ -281,6 +282,16 @@ Runtime::~Runtime() { jit_->StopProfileSaver(); } + // TODO Maybe do some locking. + for (auto& agent : agents_) { + agent.Unload(); + } + + // TODO Maybe do some locking + for (auto& plugin : plugins_) { + plugin.Unload(); + } + // Make sure our internal threads are dead before we start tearing down things they're using. Dbg::StopJdwp(); delete signal_catcher_; @@ -960,6 +971,16 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { experimental_flags_ = runtime_options.GetOrDefault(Opt::Experimental); is_low_memory_mode_ = runtime_options.Exists(Opt::LowMemoryMode); + if (experimental_flags_ & ExperimentalFlags::kRuntimePlugins) { + plugins_ = runtime_options.ReleaseOrDefault(Opt::Plugins); + } + if (experimental_flags_ & ExperimentalFlags::kAgents) { + agents_ = runtime_options.ReleaseOrDefault(Opt::AgentPath); + // TODO Add back in -agentlib + // for (auto lib : runtime_options.ReleaseOrDefault(Opt::AgentLib)) { + // agents_.push_back(lib); + // } + } XGcOption xgc_option = runtime_options.GetOrDefault(Opt::GcOption); heap_ = new gc::Heap(runtime_options.GetOrDefault(Opt::MemoryInitialSize), runtime_options.GetOrDefault(Opt::HeapGrowthLimit), @@ -1084,6 +1105,10 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { java_vm_ = new JavaVMExt(this, runtime_options); + // Add the JniEnv handler. + // TODO Refactor this stuff. + java_vm_->AddEnvironmentHook(JNIEnvExt::GetEnvHandler); + Thread::Startup(); // ClassLinker needs an attached thread, but we can't fully attach a thread without creating @@ -1200,6 +1225,16 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { pre_allocated_NoClassDefFoundError_ = GcRoot<mirror::Throwable>(self->GetException()); self->ClearException(); + // Runtime initialization is largely done now. + // We load plugins first since that can modify the runtime state slightly. + // Load all plugins + for (auto& plugin : plugins_) { + std::string err; + if (!plugin.Load(&err)) { + LOG(FATAL) << plugin << " failed to load: " << err; + } + } + // Look for a native bridge. // // The intended flow here is, in the case of a running system: @@ -1232,6 +1267,20 @@ bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) { is_native_bridge_loaded_ = LoadNativeBridge(native_bridge_file_name); } + // Startup agents + // TODO Maybe we should start a new thread to run these on. Investigate RI behavior more. + for (auto& agent : agents_) { + // TODO Check err + int res = 0; + std::string err = ""; + ti::Agent::LoadError result = agent.Load(&res, &err); + if (result == ti::Agent::kInitializationError) { + LOG(FATAL) << "Unable to initialize agent!"; + } else if (result != ti::Agent::kNoError) { + LOG(ERROR) << "Unable to load an agent: " << err; + } + } + VLOG(startup) << "Runtime::Init exiting"; return true; diff --git a/runtime/runtime.h b/runtime/runtime.h index c971646195..6da60f27a3 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -63,6 +63,9 @@ namespace mirror { class String; class Throwable; } // namespace mirror +namespace ti { + class Agent; +} // namespace ti namespace verifier { class MethodVerifier; enum class VerifyMode : int8_t; @@ -80,6 +83,7 @@ class MonitorList; class MonitorPool; class NullPointerHandler; class OatFileManager; +class Plugin; struct RuntimeArgumentMap; class SignalCatcher; class StackOverflowHandler; @@ -698,6 +702,9 @@ class Runtime { std::string class_path_string_; std::vector<std::string> properties_; + std::vector<ti::Agent> agents_; + std::vector<Plugin> plugins_; + // The default stack size for managed threads created by the runtime. size_t default_stack_size_; diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def index b95dfad550..146afc7ad8 100644 --- a/runtime/runtime_options.def +++ b/runtime/runtime_options.def @@ -117,7 +117,10 @@ RUNTIME_OPTIONS_KEY (unsigned int, ZygoteMaxFailedBoots, 10) RUNTIME_OPTIONS_KEY (Unit, NoDexFileFallback) RUNTIME_OPTIONS_KEY (std::string, CpuAbiList) RUNTIME_OPTIONS_KEY (std::string, Fingerprint) -RUNTIME_OPTIONS_KEY (ExperimentalFlags, Experimental, ExperimentalFlags::kNone) // -Xexperimental:{none} +RUNTIME_OPTIONS_KEY (ExperimentalFlags, Experimental, ExperimentalFlags::kNone) // -Xexperimental:{none, agents} +RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentLib) // -agentlib:<libname>=<options>, Requires -Xexperimental:agents +RUNTIME_OPTIONS_KEY (std::vector<ti::Agent>, AgentPath) // -agentpath:<libname>=<options>, Requires -Xexperimental:agents +RUNTIME_OPTIONS_KEY (std::vector<Plugin>, Plugins) // -Xplugin:<library> Requires -Xexperimental:runtime-plugins // Not parse-able from command line, but can be provided explicitly. // (Do not add anything here that is defined in ParsedOptions::MakeParser) diff --git a/runtime/simulator/Android.mk b/runtime/simulator/Android.mk index 953a37733d..a34a84100a 100644 --- a/runtime/simulator/Android.mk +++ b/runtime/simulator/Android.mk @@ -88,9 +88,9 @@ define build-libart-simulator LOCAL_NATIVE_COVERAGE := $(ART_COVERAGE) # For simulator_arm64. ifeq ($$(art_ndebug_or_debug),debug) - LOCAL_SHARED_LIBRARIES += libvixl-arm64 + LOCAL_SHARED_LIBRARIES += libvixld-arm64 else - LOCAL_SHARED_LIBRARIES += libvixl-arm64 + LOCAL_SHARED_LIBRARIES += libvixl-arm64 endif ifeq ($$(art_target_or_host),target) include $(BUILD_SHARED_LIBRARY) diff --git a/runtime/simulator/code_simulator_arm64.h b/runtime/simulator/code_simulator_arm64.h index 69388b122c..59ea34fb80 100644 --- a/runtime/simulator/code_simulator_arm64.h +++ b/runtime/simulator/code_simulator_arm64.h @@ -20,10 +20,10 @@ #include "memory" #include "simulator/code_simulator.h" -// TODO: make vixl clean wrt -Wshadow. +// TODO(VIXL): Make VIXL compile with -Wshadow. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshadow" -#include "a64/simulator-a64.h" +#include "aarch64/simulator-aarch64.h" #pragma GCC diagnostic pop namespace art { diff --git a/runtime/stack_map.h b/runtime/stack_map.h index 4647d67699..dd7e53100f 100644 --- a/runtime/stack_map.h +++ b/runtime/stack_map.h @@ -1050,7 +1050,7 @@ struct CodeInfoEncoding { inline_info_encoding = *reinterpret_cast<const InlineInfoEncoding*>(ptr); ptr += sizeof(InlineInfoEncoding); } else { - inline_info_encoding = InlineInfoEncoding{}; // NOLINT. + inline_info_encoding = InlineInfoEncoding{}; // NOLINT. } header_size = dchecked_integral_cast<uint8_t>(ptr - reinterpret_cast<const uint8_t*>(data)); } diff --git a/runtime/ti/agent.cc b/runtime/ti/agent.cc new file mode 100644 index 0000000000..41a21f70f3 --- /dev/null +++ b/runtime/ti/agent.cc @@ -0,0 +1,138 @@ +/* + * 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 "agent.h" +#include "java_vm_ext.h" +#include "runtime.h" + +namespace art { +namespace ti { + +const char* AGENT_ON_LOAD_FUNCTION_NAME = "Agent_OnLoad"; +const char* AGENT_ON_ATTACH_FUNCTION_NAME = "Agent_OnAttach"; +const char* AGENT_ON_UNLOAD_FUNCTION_NAME = "Agent_OnUnload"; + +Agent Agent::Create(std::string arg) { + size_t eq = arg.find_first_of('='); + if (eq == std::string::npos) { + return Agent(arg, ""); + } else { + return Agent(arg.substr(0, eq), arg.substr(eq + 1, arg.length())); + } +} + +// TODO We need to acquire some locks probably. +Agent::LoadError Agent::Load(/*out*/jint* call_res, /*out*/ std::string* error_msg) { + DCHECK(call_res != nullptr); + DCHECK(error_msg != nullptr); + if (IsStarted()) { + *error_msg = StringPrintf("the agent at %s has already been started!", name_.c_str()); + VLOG(agents) << "err: " << *error_msg; + return kAlreadyStarted; + } + LoadError err = DoDlOpen(error_msg); + if (err != kNoError) { + VLOG(agents) << "err: " << *error_msg; + return err; + } + if (onload_ == nullptr) { + *error_msg = StringPrintf("Unable to start agent %s: No Agent_OnLoad function found", + name_.c_str()); + VLOG(agents) << "err: " << *error_msg; + return kLoadingError; + } + // TODO Need to do some checks that we are at a good spot etc. + *call_res = onload_(static_cast<JavaVM*>(Runtime::Current()->GetJavaVM()), + args_.c_str(), + nullptr); + if (*call_res != 0) { + *error_msg = StringPrintf("Initialization of %s returned non-zero value of %d", + name_.c_str(), *call_res); + VLOG(agents) << "err: " << *error_msg; + return kInitializationError; + } else { + return kNoError; + } +} + +Agent::LoadError Agent::DoDlOpen(/*out*/std::string* error_msg) { + DCHECK(error_msg != nullptr); + dlopen_handle_ = dlopen(name_.c_str(), RTLD_LAZY); + if (dlopen_handle_ == nullptr) { + *error_msg = StringPrintf("Unable to dlopen %s: %s", name_.c_str(), dlerror()); + return kLoadingError; + } + + onload_ = reinterpret_cast<AgentOnLoadFunction>(dlsym(dlopen_handle_, + AGENT_ON_LOAD_FUNCTION_NAME)); + if (onload_ == nullptr) { + VLOG(agents) << "Unable to find 'Agent_OnLoad' symbol in " << this; + } + onattach_ = reinterpret_cast<AgentOnAttachFunction>(dlsym(dlopen_handle_, + AGENT_ON_ATTACH_FUNCTION_NAME)); + if (onattach_ == nullptr) { + VLOG(agents) << "Unable to find 'Agent_OnAttach' symbol in " << this; + } + onunload_= reinterpret_cast<AgentOnUnloadFunction>(dlsym(dlopen_handle_, + AGENT_ON_UNLOAD_FUNCTION_NAME)); + if (onunload_ == nullptr) { + VLOG(agents) << "Unable to find 'Agent_OnUnload' symbol in " << this; + } + return kNoError; +} + +// TODO Lock some stuff probably. +void Agent::Unload() { + if (dlopen_handle_ != nullptr) { + if (onunload_ != nullptr) { + onunload_(Runtime::Current()->GetJavaVM()); + } + dlclose(dlopen_handle_); + dlopen_handle_ = nullptr; + } else { + VLOG(agents) << this << " is not currently loaded!"; + } +} + +Agent::Agent(const Agent& other) + : name_(other.name_), + args_(other.args_), + dlopen_handle_(other.dlopen_handle_), + onload_(other.onload_), + onattach_(other.onattach_), + onunload_(other.onunload_) { + if (other.dlopen_handle_ != nullptr) { + dlopen(other.name_.c_str(), 0); + } +} + +Agent::~Agent() { + if (dlopen_handle_ != nullptr) { + dlclose(dlopen_handle_); + } +} + +std::ostream& operator<<(std::ostream &os, const Agent* m) { + return os << *m; +} + +std::ostream& operator<<(std::ostream &os, Agent const& m) { + return os << "Agent { name=\"" << m.name_ << "\", args=\"" << m.args_ << "\", handle=" + << m.dlopen_handle_ << " }"; +} + +} // namespace ti +} // namespace art diff --git a/runtime/ti/agent.h b/runtime/ti/agent.h new file mode 100644 index 0000000000..521e21e4e4 --- /dev/null +++ b/runtime/ti/agent.h @@ -0,0 +1,130 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_TI_AGENT_H_ +#define ART_RUNTIME_TI_AGENT_H_ + +#include <dlfcn.h> +#include <jni.h> // for jint, JavaVM* etc declarations + +#include "base/stringprintf.h" +#include "runtime.h" +#include "utils.h" + +namespace art { +namespace ti { + +using AgentOnLoadFunction = jint (*)(JavaVM*, const char*, void*); +using AgentOnAttachFunction = jint (*)(JavaVM*, const char*, void*); +using AgentOnUnloadFunction = void (*)(JavaVM*); + +class Agent { + public: + enum LoadError { + kNoError, // No error occurred.. + kAlreadyStarted, // The agent has already been loaded. + kLoadingError, // dlopen or dlsym returned an error. + kInitializationError, // The entrypoint did not return 0. This might require an abort. + }; + + bool IsStarted() const { + return dlopen_handle_ != nullptr; + } + + const std::string& GetName() const { + return name_; + } + + const std::string& GetArgs() const { + return args_; + } + + bool HasArgs() const { + return !GetArgs().empty(); + } + + // TODO We need to acquire some locks probably. + LoadError Load(/*out*/jint* call_res, /*out*/std::string* error_msg); + + // TODO We need to acquire some locks probably. + void Unload(); + + // Tries to attach the agent using its OnAttach method. Returns true on success. + // TODO We need to acquire some locks probably. + LoadError Attach(std::string* error_msg) { + // TODO + *error_msg = "Attach has not yet been implemented!"; + return kLoadingError; + } + + static Agent Create(std::string arg); + + static Agent Create(std::string name, std::string args) { + return Agent(name, args); + } + + ~Agent(); + + // We need move constructor and copy for vectors + Agent(const Agent& other); + + Agent(Agent&& other) + : name_(other.name_), + args_(other.args_), + dlopen_handle_(nullptr), + onload_(nullptr), + onattach_(nullptr), + onunload_(nullptr) { + other.dlopen_handle_ = nullptr; + other.onload_ = nullptr; + other.onattach_ = nullptr; + other.onunload_ = nullptr; + } + + // We don't need an operator= + void operator=(const Agent&) = delete; + + private: + Agent(std::string name, std::string args) + : name_(name), + args_(args), + dlopen_handle_(nullptr), + onload_(nullptr), + onattach_(nullptr), + onunload_(nullptr) { } + + LoadError DoDlOpen(/*out*/std::string* error_msg); + + const std::string name_; + const std::string args_; + void* dlopen_handle_; + + // The entrypoints. + AgentOnLoadFunction onload_; + AgentOnAttachFunction onattach_; + AgentOnUnloadFunction onunload_; + + friend std::ostream& operator<<(std::ostream &os, Agent const& m); +}; + +std::ostream& operator<<(std::ostream &os, Agent const& m); +std::ostream& operator<<(std::ostream &os, const Agent* m); + +} // namespace ti +} // namespace art + +#endif // ART_RUNTIME_TI_AGENT_H_ + |