# HG changeset patch # User lucy # Date 1558446695 -7200 # Node ID e46fe26d7f77112376744fefb40577d73d672227 # Parent 00425a850a2f1ab1740ec5b74f3d160f46b76534 8213084: Rework and enhance Print[Opto]Assembly output Reviewed-by: kvn, thartmann diff --git a/src/hotspot/cpu/aarch64/assembler_aarch64.hpp b/src/hotspot/cpu/aarch64/assembler_aarch64.hpp --- a/src/hotspot/cpu/aarch64/assembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/assembler_aarch64.hpp @@ -629,6 +629,14 @@ enum { instruction_size = 4 }; + //---< calculate length of instruction >--- + // We just use the values set above. + // instruction must start at passed address + static unsigned int instr_len(unsigned char *instr) { return instruction_size; } + + //---< longest instructions >--- + static unsigned int instr_maxlen() { return instruction_size; } + Address adjust(Register base, int offset, bool preIncrement) { if (preIncrement) return Address(Pre(base, offset)); diff --git a/src/hotspot/cpu/aarch64/disassembler_aarch64.hpp b/src/hotspot/cpu/aarch64/disassembler_aarch64.hpp --- a/src/hotspot/cpu/aarch64/disassembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/disassembler_aarch64.hpp @@ -34,4 +34,24 @@ return ""; } + // Returns address of n-th instruction preceding addr, + // NULL if no preceding instruction can be found. + // On ARM(aarch64), we assume a constant instruction length. + // It might be beneficial to check "is_readable" as we do on ppc and s390. + static address find_prev_instr(address addr, int n_instr) { + return addr - Assembler::instruction_size*n_instr; + } + + // special-case instruction decoding. + // There may be cases where the binutils disassembler doesn't do + // the perfect job. In those cases, decode_instruction0 may kick in + // and do it right. + // If nothing had to be done, just return "here", otherwise return "here + instr_len(here)" + static address decode_instruction0(address here, outputStream* st, address virtual_begin = NULL) { + return here; + } + + // platform-specific instruction annotations (like value of loaded constants) + static void annotate(address pc, outputStream* st) { }; + #endif // CPU_AARCH64_DISASSEMBLER_AARCH64_HPP diff --git a/src/hotspot/cpu/arm/assembler_arm_32.hpp b/src/hotspot/cpu/arm/assembler_arm_32.hpp --- a/src/hotspot/cpu/arm/assembler_arm_32.hpp +++ b/src/hotspot/cpu/arm/assembler_arm_32.hpp @@ -199,6 +199,14 @@ static const int LogInstructionSize = 2; static const int InstructionSize = 1 << LogInstructionSize; + //---< calculate length of instruction >--- + // We just use the values set above. + // instruction must start at passed address + static unsigned int instr_len(unsigned char *instr) { return InstructionSize; } + + //---< longest instructions >--- + static unsigned int instr_maxlen() { return InstructionSize; } + static inline AsmCondition inverse(AsmCondition cond) { assert ((cond != al) && (cond != nv), "AL and NV conditions cannot be inversed"); return (AsmCondition)((int)cond ^ 1); diff --git a/src/hotspot/cpu/arm/disassembler_arm.hpp b/src/hotspot/cpu/arm/disassembler_arm.hpp --- a/src/hotspot/cpu/arm/disassembler_arm.hpp +++ b/src/hotspot/cpu/arm/disassembler_arm.hpp @@ -33,4 +33,24 @@ return ""; } + // Returns address of n-th instruction preceding addr, + // NULL if no preceding instruction can be found. + // On ARM, we assume a constant instruction length. + // It might be beneficial to check "is_readable" as we do on ppc and s390. + static address find_prev_instr(address addr, int n_instr) { + return addr - Assembler::InstructionSize*n_instr; + } + + // special-case instruction decoding. + // There may be cases where the binutils disassembler doesn't do + // the perfect job. In those cases, decode_instruction0 may kick in + // and do it right. + // If nothing had to be done, just return "here", otherwise return "here + instr_len(here)" + static address decode_instruction0(address here, outputStream* st, address virtual_begin = NULL) { + return here; + } + + // platform-specific instruction annotations (like value of loaded constants) + static void annotate(address pc, outputStream* st) { }; + #endif // CPU_ARM_DISASSEMBLER_ARM_HPP diff --git a/src/hotspot/cpu/ppc/assembler_ppc.hpp b/src/hotspot/cpu/ppc/assembler_ppc.hpp --- a/src/hotspot/cpu/ppc/assembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.hpp @@ -929,11 +929,13 @@ enum Predict { pt = 1, pn = 0 }; // pt = predict taken - // Instruction must start at passed address. - static int instr_len(unsigned char *instr) { return BytesPerInstWord; } + //---< calculate length of instruction >--- + // With PPC64 being a RISC architecture, this always is BytesPerInstWord + // instruction must start at passed address + static unsigned int instr_len(unsigned char *instr) { return BytesPerInstWord; } - // longest instructions - static int instr_maxlen() { return BytesPerInstWord; } + //---< longest instructions >--- + static unsigned int instr_maxlen() { return BytesPerInstWord; } // Test if x is within signed immediate range for nbits. static bool is_simm(int x, unsigned int nbits) { diff --git a/src/hotspot/cpu/ppc/disassembler_ppc.cpp b/src/hotspot/cpu/ppc/disassembler_ppc.cpp new file mode 100644 --- /dev/null +++ b/src/hotspot/cpu/ppc/disassembler_ppc.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "asm/macroAssembler.inline.hpp" +#include "code/codeCache.hpp" +#include "compiler/disassembler.hpp" +#include "depChecker_ppc.hpp" +#include "gc/cms/concurrentMarkSweepGeneration.inline.hpp" +#include "gc/cms/parOopClosures.inline.hpp" +#include "gc/shared/collectedHeap.hpp" +#include "gc/shared/cardTableBarrierSet.hpp" +#include "gc/shared/genOopClosures.inline.hpp" +#include "oops/oop.inline.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/stubCodeGenerator.hpp" +#include "runtime/stubRoutines.hpp" + +// Macro to print instruction bits. +// numbering of instruction bits on ppc64 is (highest) 0 1 ... 30 31 (lowest). +#define print_instruction_bits(st, instruction, start_bit, end_bit) \ + { assert((start_bit) <= (end_bit), "sanity check"); \ + for (int i=(31-(start_bit));i>=(31-(end_bit));i--) { \ + (st)->print("%d", ((instruction) >> i) & 0x1); \ + } \ + } + +// Macro to decode "bo" instruction bits. +#define print_decoded_bo_bits(env, instruction, end_bit) \ + { int bo_bits = (instruction >> (31 - (end_bit))) & 0x1f; \ + if ( ((bo_bits & 0x1c) == 0x4) || ((bo_bits & 0x1c) == 0xc) ) { \ + switch (bo_bits & 0x3) { \ + case (0 << 1) | (0 << 0): env->print("[no_hint]"); break; \ + case (0 << 1) | (1 << 0): env->print("[reserved]"); break; \ + case (1 << 1) | (0 << 0): env->print("[not_taken]"); break; \ + case (1 << 1) | (1 << 0): env->print("[taken]"); break; \ + default: break; \ + } \ + } else if ( ((bo_bits & 0x14) == 0x10) ) { \ + switch (bo_bits & 0x9) { \ + case (0 << 3) | (0 << 0): env->print("[no_hint]"); break; \ + case (0 << 3) | (1 << 0): env->print("[reserved]"); break; \ + case (1 << 3) | (0 << 0): env->print("[not_taken]"); break; \ + case (1 << 3) | (1 << 0): env->print("[taken]"); break; \ + default: break; \ + } \ + } \ + } + +// Macro to decode "bh" instruction bits. +#define print_decoded_bh_bits(env, instruction, end_bit, is_bclr) \ + { int bh_bits = (instruction >> (31 - (end_bit))) & 0x3; \ + if (is_bclr) { \ + switch (bh_bits) { \ + case (0 << 1) | (0 << 0): env->print("[subroutine_return]"); break; \ + case (0 << 1) | (1 << 0): env->print("[not_return_but_same]"); break; \ + case (1 << 1) | (0 << 0): env->print("[reserved]"); break; \ + case (1 << 1) | (1 << 0): env->print("[not_predictable]"); break; \ + default: break; \ + } \ + } else { \ + switch (bh_bits) { \ + case (0 << 1) | (0 << 0): env->print("[not_return_but_same]"); break; \ + case (0 << 1) | (1 << 0): env->print("[reserved]"); break; \ + case (1 << 1) | (0 << 0): env->print("[reserved]"); break; \ + case (1 << 1) | (1 << 0): env->print("[not_predictable]"); break; \ + default: break; \ + } \ + } \ + } + +address Disassembler::find_prev_instr(address here, int n_instr) { + if (!os::is_readable_pointer(here)) return NULL; // obviously a bad location to decode + + // Find most distant possible starting point. + // Narrow down because we don't want to SEGV while printing. + address start = here - n_instr*Assembler::instr_maxlen(); // starting point can't be further away. + while ((start < here) && !os::is_readable_range(start, here)) { + start = align_down(start, os::min_page_size()) + os::min_page_size(); + } + if (start >= here) { + // Strange. Can only happen with here on page boundary. + return NULL; + } + return start; +} + +address Disassembler::decode_instruction0(address here, outputStream * st, address virtual_begin ) { + if (is_abstract()) { + // The disassembler library was not loaded (yet), + // use AbstractDisassembler's decode method. + return decode_instruction_abstract(here, st, Assembler::instr_len(here), Assembler::instr_maxlen()); + } + + // Currently, "special decoding" doesn't work when decoding error files. + // When decoding an instruction from a hs_err file, the given + // instruction address 'start' points to the instruction's virtual address + // which is not equal to the address where the instruction is located. + // Therefore, we will either crash or decode garbage. + if (is_decode_error_file()) { + return here; + } + + //---< Decode some well-known "instructions" >--- + + address next; + uint32_t instruction = *(uint32_t*)here; + + // Align at next tab position. + const uint tabspacing = 8; + const uint pos = st->position(); + const uint aligned_pos = ((pos+tabspacing-1)/tabspacing)*tabspacing; + st->fill_to(aligned_pos); + + if (instruction == 0x0) { + st->print("illtrap .data 0x0"); + next = here + Assembler::instr_len(here); + } else if (instruction == 0xbadbabe) { + st->print(".data 0xbadbabe"); + next = here + Assembler::instr_len(here); + } else if (Assembler::is_endgroup(instruction)) { + st->print("endgroup"); + next = here + Assembler::instr_len(here); + } else { + next = here; + } + return next; +} + +// print annotations (instruction control bits) +void Disassembler::annotate(address here, outputStream* st) { + // Currently, annotation doesn't work when decoding error files. + // When decoding an instruction from a hs_err file, the given + // instruction address 'start' points to the instruction's virtual address + // which is not equal to the address where the instruction is located. + // Therefore, we will either crash or decode garbage. + if (is_decode_error_file()) { + return; + } + + uint32_t instruction = *(uint32_t*)here; + + // Align at next tab position. + const uint tabspacing = 8; + const uint pos = st->position(); + const uint aligned_pos = ((pos+tabspacing-1)/tabspacing)*tabspacing; + + if (MacroAssembler::is_bcxx(instruction)) { + st->print(",bo=0b"); + print_instruction_bits(st, instruction, 6, 10); + print_decoded_bo_bits(st, instruction, 10); + } else if (MacroAssembler::is_bctr(instruction) || + MacroAssembler::is_bctrl(instruction) || + MacroAssembler::is_bclr(instruction)) { + st->fill_to(aligned_pos); + st->print("bo=0b"); + print_instruction_bits(st, instruction, 6, 10); + print_decoded_bo_bits(st, instruction, 10); + st->print(",bh=0b"); + print_instruction_bits(st, instruction, 19, 20); + print_decoded_bh_bits(st, instruction, 20, + !(MacroAssembler::is_bctr(instruction) || + MacroAssembler::is_bctrl(instruction))); + } else if (MacroAssembler::is_trap_should_not_reach_here(instruction)) { + st->fill_to(aligned_pos + tabspacing); + st->print(";trap: should not reach here"); + } else if (MacroAssembler::is_trap_null_check(instruction)) { + st->fill_to(aligned_pos + tabspacing); + st->print(";trap: null check"); + } else if (MacroAssembler::is_trap_range_check(instruction)) { + st->fill_to(aligned_pos + tabspacing); + st->print(";trap: range check"); + } else if (MacroAssembler::is_trap_ic_miss_check(instruction)) { + st->fill_to(aligned_pos + tabspacing); + st->print(";trap: ic miss check"); + } else if (MacroAssembler::is_trap_zombie_not_entrant(instruction)) { + st->fill_to(aligned_pos + tabspacing); + st->print(";trap: zombie"); + } +} diff --git a/src/hotspot/cpu/ppc/disassembler_ppc.hpp b/src/hotspot/cpu/ppc/disassembler_ppc.hpp --- a/src/hotspot/cpu/ppc/disassembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/disassembler_ppc.hpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2013 SAP SE. All rights reserved. + * Copyright (c) 2012, 2019 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,4 +34,23 @@ return "ppc64"; } + // Find preceding instruction. + // + // Starting at the passed location, the n-th preceding (towards lower addresses) + // location is searched, the contents of which - if interpreted as + // instructions - has the passed location as n-th successor. + // - If no such location exists, NULL is returned. The caller should then + // terminate its search and react properly. + static address find_prev_instr(address here, int n_instr); + + // special-case instruction decoding. + // There may be cases where the binutils disassembler doesn't do + // the perfect job. In those cases, decode_instruction0 may kick in + // and do it right. + // If nothing had to be done, just return "here", otherwise return "here + instr_len(here)" + static address decode_instruction0(address here, outputStream* st, address virtual_begin = NULL); + + // platform-specific instruction annotations (like value of loaded constants) + static void annotate(address pc, outputStream* st); + #endif // CPU_PPC_DISASSEMBLER_PPC_HPP diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.cpp b/src/hotspot/cpu/ppc/vm_version_ppc.cpp --- a/src/hotspot/cpu/ppc/vm_version_ppc.cpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.cpp @@ -708,6 +708,8 @@ uint32_t *code_end = (uint32_t *)a->pc(); a->flush(); + cb.insts()->set_end((u_char*)code_end); + double loop1_seconds,loop2_seconds, rel_diff; uint64_t start1, stop1; @@ -725,10 +727,11 @@ rel_diff = (loop2_seconds - loop1_seconds) / loop1_seconds *100; - if (PrintAssembly) { + if (PrintAssembly || PrintStubCode) { ttyLocker ttyl; tty->print_cr("Decoding section size detection stub at " INTPTR_FORMAT " before execution:", p2i(code)); - Disassembler::decode((u_char*)code, (u_char*)code_end, tty); + // Use existing decode function. This enables the [MachCode] format which is needed to DecodeErrorFile. + Disassembler::decode(&cb, (u_char*)code, (u_char*)code_end, tty); tty->print_cr("Time loop1 :%f", loop1_seconds); tty->print_cr("Time loop2 :%f", loop2_seconds); tty->print_cr("(time2 - time1) / time1 = %f %%", rel_diff); diff --git a/src/hotspot/cpu/s390/assembler_s390.hpp b/src/hotspot/cpu/s390/assembler_s390.hpp --- a/src/hotspot/cpu/s390/assembler_s390.hpp +++ b/src/hotspot/cpu/s390/assembler_s390.hpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2016, 2017 SAP SE. All rights reserved. + * Copyright (c) 2016, 2019 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1531,16 +1531,16 @@ //----------------------------------------------- // Calculate length of instruction. - static int instr_len(unsigned char *instr); + static unsigned int instr_len(unsigned char *instr); // Longest instructions are 6 bytes on z/Architecture. - static int instr_maxlen() { return 6; } + static unsigned int instr_maxlen() { return 6; } // Average instruction is 4 bytes on z/Architecture (just a guess). - static int instr_avglen() { return 4; } + static unsigned int instr_avglen() { return 4; } // Shortest instructions are 2 bytes on z/Architecture. - static int instr_minlen() { return 2; } + static unsigned int instr_minlen() { return 2; } // Move instruction at pc right-justified into passed long int. // Return instr len in bytes as function result. diff --git a/src/hotspot/cpu/s390/assembler_s390.inline.hpp b/src/hotspot/cpu/s390/assembler_s390.inline.hpp --- a/src/hotspot/cpu/s390/assembler_s390.inline.hpp +++ b/src/hotspot/cpu/s390/assembler_s390.inline.hpp @@ -1344,7 +1344,7 @@ // Instruction must start at passed address. // Extra check for illtraps with ID. -inline int Assembler::instr_len(unsigned char *instr) { +inline unsigned int Assembler::instr_len(unsigned char *instr) { switch ((*instr) >> 6) { case 0: return 2; case 1: // fallthru diff --git a/src/hotspot/cpu/s390/disassembler_s390.cpp b/src/hotspot/cpu/s390/disassembler_s390.cpp new file mode 100644 --- /dev/null +++ b/src/hotspot/cpu/s390/disassembler_s390.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "asm/assembler.inline.hpp" +#include "asm/macroAssembler.hpp" +#include "code/codeCache.hpp" +#include "compiler/disassembler.hpp" +#include "depChecker_s390.hpp" +#include "gc/cms/concurrentMarkSweepGeneration.inline.hpp" +#include "gc/cms/parOopClosures.inline.hpp" +#include "gc/shared/collectedHeap.hpp" +#include "gc/shared/cardTableBarrierSet.hpp" +#include "gc/shared/genOopClosures.inline.hpp" +#include "oops/oop.inline.hpp" +#include "runtime/handles.inline.hpp" +#include "runtime/stubCodeGenerator.hpp" +#include "runtime/stubRoutines.hpp" +#include "utilities/align.hpp" + +// List of all major opcodes, as of +// Principles of Operation, Eleventh Edition, March 2015 +bool Disassembler::valid_opcodes[] = +{ true, true, false, false, true, true, true, true, // 0x00..07 + false, false, true, true, true, true, true, true, // 0x08..0f + true, true, true, true, true, true, true, true, // 0x10..17 + true, true, true, true, true, true, true, true, // 0x18..1f + true, true, true, true, true, true, true, true, // 0x20..27 + true, true, true, true, true, true, true, true, // 0x28..2f + true, true, true, true, true, true, true, true, // 0x30..37 + true, true, true, true, true, true, true, true, // 0x38..3f + true, true, true, true, true, true, true, true, // 0x40..47 + true, true, true, true, true, true, true, true, // 0x48..4f + true, true, false, false, true, true, true, true, // 0x50..57 + true, true, true, true, true, true, true, true, // 0x58..5f + true, false, false, false, false, false, false, true, // 0x60..67 + true, true, true, true, true, true, true, true, // 0x68..6f + true, true, false, false, false, false, false, false, // 0x70..77 + true, true, true, true, true, true, true, true, // 0x78..7f + true, false, true, true, true, true, true, true, // 0x80..87 + true, true, true, true, true, true, true, true, // 0x88..8f + true, true, true, true, true, true, true, true, // 0x90..97 + true, true, true, true, false, false, false, false, // 0x98..9f + false, false, false, false, false, true, false, true, // 0xa0..a7 + true, true, false, false, true, true, true, true, // 0xa8..af + false, true, true, true, false, false, true, true, // 0xb0..b7 + false, true, true, true, false, true, true, true, // 0xb8..bf + true, false, true, false, true, false, true, false, // 0xc0..c7 + true, false, false, false, true, false, false, false, // 0xc8..cf + true, true, true, true, true, true, true, true, // 0xd0..d7 + false, true, true, true, true, true, true, true, // 0xd8..df + false, true, true, true, false, true, false, true, // 0xe0..e7 + true, true, true, true, true, true, true, true, // 0xe8..ef + true, true, true, true, false, false, false, false, // 0xf0..f7 + true, true, true, true, true, true, false, false, // 0xf8..ff +}; +// Check for valid opcodes. +// +// The major opcode (one byte) at the passed location is inspected. +// If the opcode found is assigned, the function returns true, false otherwise. +// The true indication is not reliable. It may well be that the major opcode is +// assigned, but there exists a minor opcode field in the instruction which +// which has unassigned values. +bool Disassembler::is_valid_opcode_at(address here) { + return valid_opcodes[*here]; +} + +// This method does plain instruction decoding, no frills. +// It may be called before the binutils disassembler kicks in +// to handle special cases the binutils disassembler does not. +// Instruction address, comments, and the like have to be output by caller. +address Disassembler::decode_instruction0(address here, outputStream * st, address virtual_begin) { + if (is_abstract()) { + // The disassembler library was not loaded (yet), + // use AbstractDisassembler's decode-method. + return decode_instruction_abstract(here, st, Assembler::instr_len(here), Assembler::instr_maxlen()); + } + + // Currently, "special decoding" doesn't work when decoding error files. + // When decoding an instruction from a hs_err file, the given + // instruction address 'start' points to the instruction's virtual address + // which is not equal to the address where the instruction is located. + // Therefore, we will either crash or decode garbage. + if (is_decode_error_file()) { + return here; + } + + //---< Decode some well-known "instructions" >--- + + address next; + uint16_t instruction_2bytes = *(uint16_t*)here; + + if (Assembler::is_z_nop((long)instruction_2bytes)) { +#if 1 + st->print("nop "); // fill up to operand column, leads to better code comment alignment + next = here + 2; +#else + // Compact disassembler output. Does not work the easy way. + // Currently unusable, search does not terminate, risk of crash. + // TODO: rework required. + // Terminate search loop when reaching CodeEntryAlignment-aligned offset + // or, at the latest, when reaching the next page boundary. + int n_nops = 0; + while(is_same_page(here, here+2*n_nops) && Assembler::is_z_nop((long)instruction_2bytes)) { + n_nops++; + instruction_2bytes = *(uint16_t*)(here+2*n_nops); + } + if (n_nops <= 4) { // do not group few subsequent nops + st->print("nop "); // fill up to operand column, leads to better code comment alignment + next = here + 2; + } else { + st->print("nop count=%d", n_nops); + next = here + 2*n_nops; + } +#endif + } else if (Assembler::is_z_sync((long)instruction_2bytes)) { + // Specific names. Make use of lightweight sync. + st->print("sync "); + if (Assembler::is_z_sync_full((long)instruction_2bytes) ) st->print("heavyweight"); + if (Assembler::is_z_sync_light((long)instruction_2bytes)) st->print("lightweight"); + next = here + 2; + } else if (instruction_2bytes == 0x0000) { +#if 1 + st->print("illtrap .nodata"); + next = here + 2; +#else + // Compact disassembler output. Does not work the easy way. + // Currently unusable, search does not terminate, risk of crash. + // TODO: rework required. + // Terminate search loop when reaching CodeEntryAlignment-aligned offset + // or, at the latest, when reaching the next page boundary. + int n_traps = 0; + while(is_same_page(here, here+2*n_nops) && (instruction_2bytes == 0x0000)) { + n_traps++; + instruction_2bytes = *(uint16_t*)(here+2*n_traps); + } + if (n_traps <= 4) { // do not group few subsequent illtraps + st->print("illtrap .nodata"); + next = here + 2; + } else { + st->print("illtrap .nodata count=%d", n_traps); + next = here + 2*n_traps; + } +#endif + } else if ((instruction_2bytes & 0xff00) == 0x0000) { + st->print("illtrap .data 0x%2.2x", instruction_2bytes & 0x00ff); + next = here + 2; + } else { + next = here; + } + return next; +} + +// Count the instructions contained in the range [begin..end). +// The range must exactly contain the instructions, i.e. +// - the first instruction starts @begin +// - the last instruction ends @(end-1) +// The caller has to make sure that the given range is readable. +// This function performs no safety checks! +// Return value: +// - The number of instructions, if there was exact containment. +// - If there is no exact containment, a negative value is returned. +// Its absolute value is the number of instructions from begin to end, +// where the last instruction counted runs over the range end. +// - 0 (zero) is returned if there was a parameter error +// (inverted range, bad starting point). +int Disassembler::count_instr(address begin, address end) { + if (end < begin+2) return 0; // no instructions in range + if (!Disassembler::is_valid_opcode_at(begin)) return 0; // bad starting point + + address p = begin; + int n = 0; + while(p < end) { + p += Assembler::instr_len(p); + n++; + } + return (p == end) ? n : -n; +} + +// Find preceding instruction. +// +// Starting at the passed location, the n-th preceding (towards lower addresses) +// instruction is searched. With variable length instructions, there may be +// more than one solution, or no solution at all (if the passed location +// does not point to the start of an instruction or if the storage area +// does not contain instructions at all). +// instructions - has the passed location as n-th successor. +// - If multiple such locations exist between (here-n*instr_maxlen()) and here, +// the most distant location is selected. +// - If no such location exists, NULL is returned. The caller should then +// terminate its search and react properly. +// Must be placed here in disassembler_s390.cpp. It does not compile +// in the header. There the class 'Assembler' is not available. +address Disassembler::find_prev_instr(address here, int n_instr) { + if (!os::is_readable_pointer(here)) return NULL; // obviously a bad location to decode + + // Find most distant possible starting point. + // Narrow down because we don't want to SEGV while printing. + address start = here - n_instr*Assembler::instr_maxlen(); // starting point can't be further away. + while ((start < here) && !os::is_readable_range(start, here)) { + start = align_down(start, os::min_page_size()) + os::min_page_size(); + } + if (start >= here) { + // Strange. Can only happen with here on page boundary. + return NULL; + } + + //---< Find a starting point >--- + int i_count = 0; + while ((start < here) && ((i_count = count_instr(start, here)) <= 0)) start += 2; + if (i_count == 0) return NULL; // There is something seriously wrong + + //---< Narrow down distance (estimate was too large) >--- + while(i_count-- > n_instr) { + start += Assembler::instr_len(start); + } + assert(n_instr >= count_instr(start, here), "just checking"); + return start; +} + + +// Print annotations (value of loaded constant) +void Disassembler::annotate(address here, outputStream* st) { + // Currently, annotation doesn't work when decoding error files. + // When decoding an instruction from a hs_err file, the given + // instruction address 'start' points to the instruction's virtual address + // which is not equal to the address where the instruction is located. + // Therefore, we will either crash or decode garbage. + if (is_decode_error_file()) { + return; + } + + if (MacroAssembler::is_load_const(here)) { + long value = MacroAssembler::get_const(here); + const int tsize = 8; + + st->fill_to(60); + st->print(";const %p | %ld | %23.15e", (void *)value, value, (double)value); + } +} diff --git a/src/hotspot/cpu/s390/disassembler_s390.hpp b/src/hotspot/cpu/s390/disassembler_s390.hpp --- a/src/hotspot/cpu/s390/disassembler_s390.hpp +++ b/src/hotspot/cpu/s390/disassembler_s390.hpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2016 SAP SE. All rights reserved. + * Copyright (c) 2016, 2019 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,11 +27,36 @@ #define CPU_S390_DISASSEMBLER_S390_HPP static int pd_instruction_alignment() { - return 1; + return 2; } static const char* pd_cpu_opts() { - return "zarch"; + return "s390"; } + static bool valid_opcodes[256]; + static bool is_valid_opcode_at(address here); + + // Find preceding instruction. + // + // Starting at the passed location, the n-th preceding (towards lower addresses) + // location is searched, the contents of which - if interpreted as + // instructions - has the passed location as n-th successor. + // - If multiple such locations exist between (here-n*instr_maxlen()) and here, + // the most distant location is selected. + // - If no such location exists, NULL is returned. The caller should then + // terminate its search and react properly. + static address find_prev_instr(address here, int n_instr); + static int count_instr(address begin, address end); + + // special-case instruction decoding. + // There may be cases where the binutils disassembler doesn't do + // the perfect job. In those cases, decode_instruction0 may kick in + // and do it right. + // If nothing had to be done, just return "here", otherwise return "here + instr_len(here)" + static address decode_instruction0(address here, outputStream* st, address virtual_begin = NULL); + + // platform-specific instruction annotations (like value of loaded constants) + static void annotate(address pc, outputStream* st); + #endif // CPU_S390_DISASSEMBLER_S390_HPP diff --git a/src/hotspot/cpu/s390/s390.ad b/src/hotspot/cpu/s390/s390.ad --- a/src/hotspot/cpu/s390/s390.ad +++ b/src/hotspot/cpu/s390/s390.ad @@ -1,6 +1,6 @@ // -// Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. -// Copyright (c) 2017, SAP SE. All rights reserved. +// Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. +// Copyright (c) 2017, 2019 SAP SE. All rights reserved. // DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. // // This code is free software; you can redistribute it and/or modify it @@ -1388,7 +1388,6 @@ __ z_br(R1_ic_miss_stub_addr); __ bind(valid); } - } uint MachUEPNode::size(PhaseRegAlloc *ra_) const { @@ -4318,7 +4317,7 @@ match(Set dst src); ins_cost(DEFAULT_COST); size(6); - format %{ "LGFI $dst,$src\t # (int)" %} + format %{ "LGFI $dst,$src\t # (int)" %} ins_encode %{ __ z_lgfi($dst$$Register, $src$$constant); %} // Sign-extend to 64 bit, it's at no cost. ins_pipe(pipe_class_dummy); %} @@ -4327,7 +4326,7 @@ match(Set dst src); ins_cost(DEFAULT_COST_LOW); size(4); - format %{ "LGHI $dst,$src\t # (int)" %} + format %{ "LGHI $dst,$src\t # (int)" %} ins_encode %{ __ z_lghi($dst$$Register, $src$$constant); %} // Sign-extend to 64 bit, it's at no cost. ins_pipe(pipe_class_dummy); %} @@ -4723,7 +4722,7 @@ match(Set dst (LoadN mem)); ins_cost(MEMORY_REF_COST); size(Z_DISP3_SIZE); - format %{ "LoadN $dst,$mem\t# (cOop)" %} + format %{ "LoadN $dst,$mem\t # (cOop)" %} opcode(LLGF_ZOPC, LLGF_ZOPC); ins_encode(z_form_rt_mem_opt(dst, mem)); ins_pipe(pipe_class_dummy); @@ -4734,7 +4733,7 @@ match(Set dst (LoadNKlass mem)); ins_cost(MEMORY_REF_COST); size(Z_DISP3_SIZE); - format %{ "LoadNKlass $dst,$mem\t# (klass cOop)" %} + format %{ "LoadNKlass $dst,$mem\t # (klass cOop)" %} opcode(LLGF_ZOPC, LLGF_ZOPC); ins_encode(z_form_rt_mem_opt(dst, mem)); ins_pipe(pipe_class_dummy); @@ -4787,7 +4786,7 @@ predicate(false && (CompressedOops::base()==NULL)&&(CompressedOops::shift()==0)); ins_cost(MEMORY_REF_COST); size(Z_DISP3_SIZE); - format %{ "DecodeLoadN $dst,$mem\t# (cOop Load+Decode)" %} + format %{ "DecodeLoadN $dst,$mem\t # (cOop Load+Decode)" %} opcode(LLGF_ZOPC, LLGF_ZOPC); ins_encode(z_form_rt_mem_opt(dst, mem)); ins_pipe(pipe_class_dummy); @@ -4798,7 +4797,7 @@ predicate(false && (CompressedKlassPointers::base()==NULL)&&(CompressedKlassPointers::shift()==0)); ins_cost(MEMORY_REF_COST); size(Z_DISP3_SIZE); - format %{ "DecodeLoadNKlass $dst,$mem\t# (load/decode NKlass)" %} + format %{ "DecodeLoadNKlass $dst,$mem\t # (load/decode NKlass)" %} opcode(LLGF_ZOPC, LLGF_ZOPC); ins_encode(z_form_rt_mem_opt(dst, mem)); ins_pipe(pipe_class_dummy); @@ -4826,7 +4825,7 @@ predicate(CompressedOops::base() == NULL || !ExpandLoadingBaseDecode); ins_cost(MEMORY_REF_COST+3 * DEFAULT_COST + BRANCH_COST); // TODO: s390 port size(VARIABLE_SIZE); - format %{ "decodeN $dst,$src\t# (decode cOop)" %} + format %{ "decodeN $dst,$src\t # (decode cOop)" %} ins_encode %{ __ oop_decoder($dst$$Register, $src$$Register, true); %} ins_pipe(pipe_class_dummy); %} @@ -4850,7 +4849,7 @@ (CompressedOops::base()== NULL || !ExpandLoadingBaseDecode_NN)); ins_cost(MEMORY_REF_COST+2 * DEFAULT_COST); // TODO: s390 port size(VARIABLE_SIZE); - format %{ "decodeN $dst,$src\t# (decode cOop NN)" %} + format %{ "decodeN $dst,$src\t # (decode cOop NN)" %} ins_encode %{ __ oop_decoder($dst$$Register, $src$$Register, false); %} ins_pipe(pipe_class_dummy); %} @@ -4873,7 +4872,7 @@ effect(KILL cr); predicate(false); // TODO: s390 port size(VARIABLE_SIZE); - format %{ "decodeN $dst = ($src == 0) ? NULL : ($src << 3) + $base + pow2_offset\t# (decode cOop)" %} + format %{ "decodeN $dst = ($src == 0) ? NULL : ($src << 3) + $base + pow2_offset\t # (decode cOop)" %} ins_encode %{ __ oop_decoder($dst$$Register, $src$$Register, true, $base$$Register, (jlong)MacroAssembler::get_oop_base_pow2_offset((uint64_t)(intptr_t)CompressedOops::base())); @@ -4887,7 +4886,7 @@ effect(KILL cr); predicate(false); // TODO: s390 port size(VARIABLE_SIZE); - format %{ "decodeN $dst = ($src << 3) + $base + pow2_offset\t# (decode cOop)" %} + format %{ "decodeN $dst = ($src << 3) + $base + pow2_offset\t # (decode cOop)" %} ins_encode %{ __ oop_decoder($dst$$Register, $src$$Register, false, $base$$Register, (jlong)MacroAssembler::get_oop_base_pow2_offset((uint64_t)(intptr_t)CompressedOops::base())); @@ -4937,7 +4936,7 @@ !ExpandLoadingBaseEncode)); ins_cost(MEMORY_REF_COST+3 * DEFAULT_COST); // TODO: s390 port size(VARIABLE_SIZE); - format %{ "encodeP $dst,$src\t# (encode cOop)" %} + format %{ "encodeP $dst,$src\t # (encode cOop)" %} ins_encode %{ __ oop_encoder($dst$$Register, $src$$Register, true, Z_R1_scratch, -1, all_outs_are_Stores(this)); %} ins_pipe(pipe_class_dummy); %} @@ -4960,7 +4959,7 @@ !ExpandLoadingBaseEncode_NN)); ins_cost(MEMORY_REF_COST+3 * DEFAULT_COST); // TODO: s390 port size(VARIABLE_SIZE); - format %{ "encodeP $dst,$src\t# (encode cOop)" %} + format %{ "encodeP $dst,$src\t # (encode cOop)" %} ins_encode %{ __ oop_encoder($dst$$Register, $src$$Register, false, Z_R1_scratch, -1, all_outs_are_Stores(this)); %} ins_pipe(pipe_class_dummy); %} @@ -4972,7 +4971,7 @@ predicate(false); ins_cost(MEMORY_REF_COST+2 * DEFAULT_COST); // TODO: s390 port size(VARIABLE_SIZE); - format %{ "encodeP $dst = ($src>>3) +$base + pow2_offset\t# (encode cOop)" %} + format %{ "encodeP $dst = ($src>>3) +$base + pow2_offset\t # (encode cOop)" %} ins_encode %{ jlong offset = -(jlong)MacroAssembler::get_oop_base_pow2_offset (((uint64_t)(intptr_t)CompressedOops::base()) >> CompressedOops::shift()); @@ -4988,7 +4987,7 @@ predicate(false); ins_cost(MEMORY_REF_COST+2 * DEFAULT_COST); // TODO: s390 port size(VARIABLE_SIZE); - format %{ "encodeP $dst = ($src>>3) +$base + $pow2_offset\t# (encode cOop)" %} + format %{ "encodeP $dst = ($src>>3) +$base + $pow2_offset\t # (encode cOop)" %} ins_encode %{ __ oop_encoder($dst$$Register, $src$$Register, false, $base$$Register, $pow2_offset$$constant); %} ins_pipe(pipe_class_dummy); %} @@ -5041,7 +5040,7 @@ match(Set mem (StoreN mem src)); ins_cost(MEMORY_REF_COST); size(Z_DISP_SIZE); - format %{ "ST $src,$mem\t# (cOop)" %} + format %{ "ST $src,$mem\t # (cOop)" %} opcode(STY_ZOPC, ST_ZOPC); ins_encode(z_form_rt_mem_opt(src, mem)); ins_pipe(pipe_class_dummy); @@ -5052,7 +5051,7 @@ match(Set mem (StoreNKlass mem src)); ins_cost(MEMORY_REF_COST); size(Z_DISP_SIZE); - format %{ "ST $src,$mem\t# (cKlass)" %} + format %{ "ST $src,$mem\t # (cKlass)" %} opcode(STY_ZOPC, ST_ZOPC); ins_encode(z_form_rt_mem_opt(src, mem)); ins_pipe(pipe_class_dummy); @@ -5064,7 +5063,7 @@ match(Set cr (CmpN src1 src2)); ins_cost(DEFAULT_COST); size(2); - format %{ "CLR $src1,$src2\t# (cOop)" %} + format %{ "CLR $src1,$src2\t # (cOop)" %} opcode(CLR_ZOPC); ins_encode(z_rrform(src1, src2)); ins_pipe(pipe_class_dummy); @@ -5074,7 +5073,7 @@ match(Set cr (CmpN src1 src2)); ins_cost(DEFAULT_COST); size(6); - format %{ "CLFI $src1,$src2\t# (cOop) compare immediate narrow" %} + format %{ "CLFI $src1,$src2\t # (cOop) compare immediate narrow" %} ins_encode %{ AddressLiteral cOop = __ constant_oop_address((jobject)$src2$$constant); __ relocate(cOop.rspec(), 1); @@ -5087,7 +5086,7 @@ match(Set cr (CmpN src1 src2)); ins_cost(DEFAULT_COST); size(6); - format %{ "CLFI $src1,$src2\t# (NKlass) compare immediate narrow" %} + format %{ "CLFI $src1,$src2\t # (NKlass) compare immediate narrow" %} ins_encode %{ AddressLiteral NKlass = __ constant_metadata_address((Metadata*)$src2$$constant); __ relocate(NKlass.rspec(), 1); @@ -5100,7 +5099,7 @@ match(Set cr (CmpN src1 src2)); ins_cost(DEFAULT_COST); size(2); - format %{ "LTR $src1,$src2\t# (cOop) LTR because comparing against zero" %} + format %{ "LTR $src1,$src2\t # (cOop) LTR because comparing against zero" %} opcode(LTR_ZOPC); ins_encode(z_rrform(src1, src1)); ins_pipe(pipe_class_dummy); @@ -6795,7 +6794,7 @@ effect(KILL cr); // R1 is killed, too. ins_cost(3 * DEFAULT_COST); size(14); - format %{ "SLL $dst,$src,[$nbits] & 31\t# use RISC-like SLLG also for int" %} + format %{ "SLL $dst,$src,[$nbits] & 31\t # use RISC-like SLLG also for int" %} ins_encode %{ __ z_lgr(Z_R1_scratch, $nbits$$Register); __ z_nill(Z_R1_scratch, BitsPerJavaInteger-1); @@ -6809,7 +6808,7 @@ instruct sllI_reg_imm(iRegI dst, iRegI src, immI nbits) %{ match(Set dst (LShiftI src nbits)); size(6); - format %{ "SLL $dst,$src,$nbits\t# use RISC-like SLLG also for int" %} + format %{ "SLL $dst,$src,$nbits\t # use RISC-like SLLG also for int" %} ins_encode %{ int Nbit = $nbits$$constant; assert((Nbit & (BitsPerJavaInteger - 1)) == Nbit, "Check shift mask in ideal graph"); @@ -7125,7 +7124,7 @@ instruct overflowNegI_rReg(flagsReg cr, immI_0 zero, iRegI op2) %{ match(Set cr (OverflowSubI zero op2)); effect(DEF cr, USE op2); - format %{ "NEG $op2\t# overflow check int" %} + format %{ "NEG $op2\t # overflow check int" %} ins_encode %{ __ clear_reg(Z_R0_scratch, false, false); __ z_sr(Z_R0_scratch, $op2$$Register); @@ -7136,7 +7135,7 @@ instruct overflowNegL_rReg(flagsReg cr, immL_0 zero, iRegL op2) %{ match(Set cr (OverflowSubL zero op2)); effect(DEF cr, USE op2); - format %{ "NEGG $op2\t# overflow check long" %} + format %{ "NEGG $op2\t # overflow check long" %} ins_encode %{ __ clear_reg(Z_R0_scratch, true, false); __ z_sgr(Z_R0_scratch, $op2$$Register); @@ -9191,7 +9190,7 @@ effect(USE lbl); ins_cost(BRANCH_COST); size(4); - format %{ "branch_con_short,$cmp $cr, $lbl" %} + format %{ "branch_con_short,$cmp $lbl" %} ins_encode(z_enc_branch_con_short(cmp, lbl)); ins_pipe(pipe_class_dummy); // If set to 1 this indicates that the current instruction is a @@ -9213,7 +9212,7 @@ // Make more expensive to prefer compare_and_branch over separate instructions. ins_cost(2 * BRANCH_COST); size(6); - format %{ "branch_con_far,$cmp $cr, $lbl" %} + format %{ "branch_con_far,$cmp $lbl" %} ins_encode(z_enc_branch_con_far(cmp, lbl)); ins_pipe(pipe_class_dummy); // This is not a short variant of a branch, but the long variant.. @@ -9782,7 +9781,7 @@ match(TailCall jump_target method_oop); ins_cost(CALL_COST); size(2); - format %{ "Jmp $jump_target\t# $method_oop holds method oop" %} + format %{ "Jmp $jump_target\t # $method_oop holds method oop" %} ins_encode %{ __ z_br($jump_target$$Register); %} ins_pipe(pipe_class_dummy); %} @@ -10790,7 +10789,7 @@ predicate(UseByteReverseInstruction); // See Matcher::match_rule_supported ins_cost(DEFAULT_COST); size(4); - format %{ "LRVR $dst,$src\t# byte reverse int" %} + format %{ "LRVR $dst,$src\t # byte reverse int" %} opcode(LRVR_ZOPC); ins_encode(z_rreform(dst, src)); ins_pipe(pipe_class_dummy); @@ -10801,7 +10800,7 @@ predicate(UseByteReverseInstruction); // See Matcher::match_rule_supported ins_cost(DEFAULT_COST); // TODO: s390 port size(FIXED_SIZE); - format %{ "LRVGR $dst,$src\t# byte reverse long" %} + format %{ "LRVGR $dst,$src\t # byte reverse long" %} opcode(LRVGR_ZOPC); ins_encode(z_rreform(dst, src)); ins_pipe(pipe_class_dummy); @@ -10821,8 +10820,8 @@ effect(KILL tmp, KILL cr); ins_cost(3 * DEFAULT_COST); size(14); - format %{ "SLLG $dst,$src,32\t# no need to always count 32 zeroes first\n\t" - "IILH $dst,0x8000 \t# insert \"stop bit\" to force result 32 for zero src.\n\t" + format %{ "SLLG $dst,$src,32\t # no need to always count 32 zeroes first\n\t" + "IILH $dst,0x8000 \t # insert \"stop bit\" to force result 32 for zero src.\n\t" "FLOGR $dst,$dst" %} ins_encode %{ @@ -10859,7 +10858,7 @@ effect(KILL tmp, KILL cr); ins_cost(DEFAULT_COST); size(4); - format %{ "FLOGR $dst,$src \t# count leading zeros (long)\n\t" %} + format %{ "FLOGR $dst,$src \t # count leading zeros (long)\n\t" %} ins_encode %{ __ z_flogr($dst$$Register, $src$$Register); %} ins_pipe(pipe_class_dummy); %} @@ -10884,14 +10883,14 @@ effect(TEMP_DEF dst, TEMP tmp, KILL cr); ins_cost(8 * DEFAULT_COST); // TODO: s390 port size(FIXED_SIZE); // Emitted code depends on PreferLAoverADD being on/off. - format %{ "LLGFR $dst,$src \t# clear upper 32 bits (we are dealing with int)\n\t" - "LCGFR $tmp,$src \t# load 2's complement (32->64 bit)\n\t" - "AGHI $dst,-1 \t# tmp1 = src-1\n\t" - "AGHI $tmp,-1 \t# tmp2 = -src-1 = ~src\n\t" - "NGR $dst,$tmp \t# tmp3 = tmp1&tmp2\n\t" - "FLOGR $dst,$dst \t# count trailing zeros (int)\n\t" - "AHI $dst,-64 \t# tmp4 = 64-(trailing zeroes)-64\n\t" - "LCR $dst,$dst \t# res = -tmp4" + format %{ "LLGFR $dst,$src \t # clear upper 32 bits (we are dealing with int)\n\t" + "LCGFR $tmp,$src \t # load 2's complement (32->64 bit)\n\t" + "AGHI $dst,-1 \t # tmp1 = src-1\n\t" + "AGHI $tmp,-1 \t # tmp2 = -src-1 = ~src\n\t" + "NGR $dst,$tmp \t # tmp3 = tmp1&tmp2\n\t" + "FLOGR $dst,$dst \t # count trailing zeros (int)\n\t" + "AHI $dst,-64 \t # tmp4 = 64-(trailing zeroes)-64\n\t" + "LCR $dst,$dst \t # res = -tmp4" %} ins_encode %{ Register Rdst = $dst$$Register; @@ -10937,12 +10936,12 @@ effect(TEMP_DEF dst, KILL tmp, KILL cr); ins_cost(8 * DEFAULT_COST); // TODO: s390 port size(FIXED_SIZE); // Emitted code depends on PreferLAoverADD being on/off. - format %{ "LCGR $dst,$src \t# preserve src\n\t" - "NGR $dst,$src \t#" - "AGHI $dst,-1 \t# tmp1 = src-1\n\t" - "FLOGR $dst,$dst \t# count trailing zeros (long), kill $tmp\n\t" - "AHI $dst,-64 \t# tmp4 = 64-(trailing zeroes)-64\n\t" - "LCR $dst,$dst \t#" + format %{ "LCGR $dst,$src \t # preserve src\n\t" + "NGR $dst,$src \t #\n\t" + "AGHI $dst,-1 \t # tmp1 = src-1\n\t" + "FLOGR $dst,$dst \t # count trailing zeros (long), kill $tmp\n\t" + "AHI $dst,-64 \t # tmp4 = 64-(trailing zeroes)-64\n\t" + "LCR $dst,$dst \t #" %} ins_encode %{ Register Rdst = $dst$$Register; @@ -10969,7 +10968,7 @@ predicate(UsePopCountInstruction && VM_Version::has_PopCount()); ins_cost(DEFAULT_COST); size(24); - format %{ "POPCNT $dst,$src\t# pop count int" %} + format %{ "POPCNT $dst,$src\t # pop count int" %} ins_encode %{ Register Rdst = $dst$$Register; Register Rsrc = $src$$Register; @@ -10996,7 +10995,7 @@ predicate(UsePopCountInstruction && VM_Version::has_PopCount()); ins_cost(DEFAULT_COST); // TODO: s390 port size(FIXED_SIZE); - format %{ "POPCNT $dst,$src\t# pop count long" %} + format %{ "POPCNT $dst,$src\t # pop count long" %} ins_encode %{ Register Rdst = $dst$$Register; Register Rsrc = $src$$Register; diff --git a/src/hotspot/cpu/s390/vm_version_s390.cpp b/src/hotspot/cpu/s390/vm_version_s390.cpp --- a/src/hotspot/cpu/s390/vm_version_s390.cpp +++ b/src/hotspot/cpu/s390/vm_version_s390.cpp @@ -804,6 +804,8 @@ address code_end = a->pc(); a->flush(); + cbuf.insts()->set_end(code_end); + // Print the detection code. bool printVerbose = Verbose || PrintAssembly || PrintStubCode; if (printVerbose) { @@ -812,8 +814,8 @@ tty->print_cr("Stub length is %ld bytes, codebuffer reserves %d bytes, %ld bytes spare.", code_end-code, cbuf_size, cbuf_size-(code_end-code)); - // Use existing decode function. This enables the [Code] format which is needed to DecodeErrorFile. - Disassembler::decode((u_char*)code, (u_char*)code_end, tty); + // Use existing decode function. This enables the [MachCode] format which is needed to DecodeErrorFile. + Disassembler::decode(&cbuf, code, code_end, tty); } // Prepare for detection code execution and clear work buffer. diff --git a/src/hotspot/cpu/sparc/assembler_sparc.hpp b/src/hotspot/cpu/sparc/assembler_sparc.hpp --- a/src/hotspot/cpu/sparc/assembler_sparc.hpp +++ b/src/hotspot/cpu/sparc/assembler_sparc.hpp @@ -335,6 +335,14 @@ Lookaside = 1 << 4 }; + //---< calculate length of instruction >--- + // With SPARC being a RISC architecture, this always is BytesPerInstWord + // instruction must start at passed address + static unsigned int instr_len(unsigned char *instr) { return BytesPerInstWord; } + + //---< longest instructions >--- + static unsigned int instr_maxlen() { return BytesPerInstWord; } + static bool is_in_wdisp_range(address a, address b, int nbits) { intptr_t d = intptr_t(b) - intptr_t(a); return is_simm(d, nbits + 2); diff --git a/src/hotspot/cpu/sparc/disassembler_sparc.hpp b/src/hotspot/cpu/sparc/disassembler_sparc.hpp --- a/src/hotspot/cpu/sparc/disassembler_sparc.hpp +++ b/src/hotspot/cpu/sparc/disassembler_sparc.hpp @@ -33,4 +33,24 @@ return "v9only"; } + // Returns address of n-th instruction preceding addr, + // NULL if no preceding instruction can be found. + // With SPARC being a RISC architecture, this always is BytesPerInstWord + // It might be beneficial to check "is_readable" as we do on ppc and s390. + static address find_prev_instr(address addr, int n_instr) { + return addr - BytesPerInstWord*n_instr; + } + + // special-case instruction decoding. + // There may be cases where the binutils disassembler doesn't do + // the perfect job. In those cases, decode_instruction0 may kick in + // and do it right. + // If nothing had to be done, just return "here", otherwise return "here + instr_len(here)" + static address decode_instruction0(address here, outputStream* st, address virtual_begin = NULL) { + return here; + } + + // platform-specific instruction annotations (like value of loaded constants) + static void annotate(address pc, outputStream* st) { }; + #endif // CPU_SPARC_DISASSEMBLER_SPARC_HPP diff --git a/src/hotspot/cpu/sparc/sparc.ad b/src/hotspot/cpu/sparc/sparc.ad --- a/src/hotspot/cpu/sparc/sparc.ad +++ b/src/hotspot/cpu/sparc/sparc.ad @@ -1,5 +1,5 @@ // -// Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved. +// Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved. // DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. // // This code is free software; you can redistribute it and/or modify it @@ -1295,7 +1295,8 @@ #ifndef PRODUCT ATTRIBUTE_PRINTF(2, 3) static void print_helper(outputStream* st, const char* format, ...) { - if (st->position() > 0) { + const int tab_size = 8; + if (st->position() > tab_size) { st->cr(); st->sp(); } diff --git a/src/hotspot/cpu/x86/assembler_x86.hpp b/src/hotspot/cpu/x86/assembler_x86.hpp --- a/src/hotspot/cpu/x86/assembler_x86.hpp +++ b/src/hotspot/cpu/x86/assembler_x86.hpp @@ -630,6 +630,17 @@ _true = 7 }; + //---< calculate length of instruction >--- + // As instruction size can't be found out easily on x86/x64, + // we just use '4' for len and maxlen. + // instruction must start at passed address + static unsigned int instr_len(unsigned char *instr) { return 4; } + + //---< longest instructions >--- + // Max instruction length is not specified in architecture documentation. + // We could use a "safe enough" estimate (15), but just default to + // instruction length guess from above. + static unsigned int instr_maxlen() { return 4; } // NOTE: The general philopsophy of the declarations here is that 64bit versions // of instructions are freely declared without the need for wrapping them an ifdef. diff --git a/src/hotspot/cpu/x86/disassembler_x86.hpp b/src/hotspot/cpu/x86/disassembler_x86.hpp --- a/src/hotspot/cpu/x86/disassembler_x86.hpp +++ b/src/hotspot/cpu/x86/disassembler_x86.hpp @@ -33,4 +33,25 @@ return ""; } + // Returns address of n-th instruction preceding addr, + // NULL if no preceding instruction can be found. + // On CISC architectures, it is difficult to impossible to step + // backwards in the instruction stream. Therefore just return NULL. + // It might be beneficial to check "is_readable" as we do on ppc and s390. + static address find_prev_instr(address addr, int n_instr) { + return NULL; + } + + // special-case instruction decoding. + // There may be cases where the binutils disassembler doesn't do + // the perfect job. In those cases, decode_instruction0 may kick in + // and do it right. + // If nothing had to be done, just return "here", otherwise return "here + instr_len(here)" + static address decode_instruction0(address here, outputStream* st, address virtual_begin = NULL) { + return here; + } + + // platform-specific instruction annotations (like value of loaded constants) + static void annotate(address pc, outputStream* st) { }; + #endif // CPU_X86_DISASSEMBLER_X86_HPP diff --git a/src/hotspot/cpu/x86/x86_64.ad b/src/hotspot/cpu/x86/x86_64.ad --- a/src/hotspot/cpu/x86/x86_64.ad +++ b/src/hotspot/cpu/x86/x86_64.ad @@ -918,19 +918,19 @@ st->print("\t"); } - st->print_cr("popq rbp"); + st->print_cr("popq rbp"); if (do_polling() && C->is_method_compilation()) { st->print("\t"); if (SafepointMechanism::uses_thread_local_poll()) { - st->print_cr("movq rscratch1, poll_offset[r15_thread] #polling_page_address\n\t" - "testl rax, [rscratch1]\t" + st->print_cr("movq rscratch1, poll_offset[r15_thread] #polling_page_address\n\t" + "testl rax, [rscratch1]\t" "# Safepoint: poll for GC"); } else if (Assembler::is_polling_page_far()) { - st->print_cr("movq rscratch1, #polling_page_address\n\t" - "testl rax, [rscratch1]\t" + st->print_cr("movq rscratch1, #polling_page_address\n\t" + "testl rax, [rscratch1]\t" "# Safepoint: poll for GC"); } else { - st->print_cr("testl rax, [rip + #offset_to_poll_page]\t" + st->print_cr("testl rax, [rip + #offset_to_poll_page]\t" "# Safepoint: poll for GC"); } } @@ -10303,10 +10303,10 @@ match(Set p (AddI (AndI (CmpLTMask p q) y) (SubI p q))); effect(KILL cr); ins_cost(300); - format %{ "subl $p,$q\t# cadd_cmpLTMask\n\t" - "jge done\n\t" - "addl $p,$y\n" - "done: " %} + format %{ "subl $p,$q\t# cadd_cmpLTMask\n\t" + "jge done\n\t" + "addl $p,$y\n" + "done: " %} ins_encode %{ Register Rp = $p$$Register; Register Rq = $q$$Register; @@ -10328,10 +10328,10 @@ ins_cost(300); - format %{ "cmpl $p, $q\t# and_cmpLTMask\n\t" - "jlt done\n\t" - "xorl $y, $y\n" - "done: " %} + format %{ "cmpl $p, $q\t# and_cmpLTMask\n\t" + "jlt done\n\t" + "xorl $y, $y\n" + "done: " %} ins_encode %{ Register Rp = $p$$Register; Register Rq = $q$$Register; @@ -11888,7 +11888,7 @@ %{ match(Set cr (CmpU src zero)); - format %{ "testl $src, $src\t# unsigned" %} + format %{ "testl $src, $src\t# unsigned" %} opcode(0x85); ins_encode(REX_reg_reg(src, src), OpcP, reg_reg(src, src)); ins_pipe(ialu_cr_reg_imm); @@ -12431,7 +12431,7 @@ effect(USE labl); ins_cost(300); - format %{ "j$cop,u $labl" %} + format %{ "j$cop,u $labl" %} size(6); ins_encode %{ Label* L = $labl$$label; @@ -12445,7 +12445,7 @@ effect(USE labl); ins_cost(200); - format %{ "j$cop,u $labl" %} + format %{ "j$cop,u $labl" %} size(6); ins_encode %{ Label* L = $labl$$label; @@ -12461,10 +12461,10 @@ ins_cost(200); format %{ $$template if ($cop$$cmpcode == Assembler::notEqual) { - $$emit$$"jp,u $labl\n\t" + $$emit$$"jp,u $labl\n\t" $$emit$$"j$cop,u $labl" } else { - $$emit$$"jp,u done\n\t" + $$emit$$"jp,u done\n\t" $$emit$$"j$cop,u $labl\n\t" $$emit$$"done:" } @@ -12666,10 +12666,10 @@ ins_cost(300); format %{ $$template if ($cop$$cmpcode == Assembler::notEqual) { - $$emit$$"jp,u,s $labl\n\t" - $$emit$$"j$cop,u,s $labl" + $$emit$$"jp,u,s $labl\n\t" + $$emit$$"j$cop,u,s $labl" } else { - $$emit$$"jp,u,s done\n\t" + $$emit$$"jp,u,s done\n\t" $$emit$$"j$cop,u,s $labl\n\t" $$emit$$"done:" } @@ -12745,7 +12745,7 @@ match(SafePoint); effect(KILL cr); - format %{ "testl rax, [rip + #offset_to_poll_page]\t" + format %{ "testl rax, [rip + #offset_to_poll_page]\t" "# Safepoint: poll for GC" %} ins_cost(125); ins_encode %{ @@ -12761,7 +12761,7 @@ match(SafePoint poll); effect(KILL cr, USE poll); - format %{ "testl rax, [$poll]\t" + format %{ "testl rax, [$poll]\t" "# Safepoint: poll for GC" %} ins_cost(125); ins_encode %{ @@ -12777,7 +12777,7 @@ match(SafePoint poll); effect(KILL cr, USE poll); - format %{ "testl rax, [$poll]\t" + format %{ "testl rax, [$poll]\t" "# Safepoint: poll for GC" %} ins_cost(125); size(4); /* setting an explicit size will cause debug builds to assert if size is incorrect */ diff --git a/src/hotspot/cpu/zero/assembler_zero.hpp b/src/hotspot/cpu/zero/assembler_zero.hpp --- a/src/hotspot/cpu/zero/assembler_zero.hpp +++ b/src/hotspot/cpu/zero/assembler_zero.hpp @@ -37,6 +37,12 @@ public: void pd_patch_instruction(address branch, address target, const char* file, int line); + + //---< calculate length of instruction >--- + static unsigned int instr_len(unsigned char *instr) { return 1; } + + //---< longest instructions >--- + static unsigned int instr_maxlen() { return 1; } }; class MacroAssembler : public Assembler { diff --git a/src/hotspot/cpu/zero/disassembler_zero.hpp b/src/hotspot/cpu/zero/disassembler_zero.hpp --- a/src/hotspot/cpu/zero/disassembler_zero.hpp +++ b/src/hotspot/cpu/zero/disassembler_zero.hpp @@ -34,4 +34,24 @@ return ""; } + // Returns address of n-th instruction preceding addr, + // NULL if no preceding instruction can be found. + // On ZERO, we assume a constant instruction length of 1 byte. + // It might be beneficial to check "is_readable" as we do on ppc and s390. + static address find_prev_instr(address addr, int n_instr) { + return addr - 1*n_instr; + } + + // special-case instruction decoding. + // There may be cases where the binutils disassembler doesn't do + // the perfect job. In those cases, decode_instruction0 may kick in + // and do it right. + // If nothing had to be done, just return "here", otherwise return "here + instr_len(here)" + static address decode_instruction0(address here, outputStream* st, address virtual_begin = NULL) { + return here; + } + + // platform-specific instruction annotations (like value of loaded constants) + static void annotate(address pc, outputStream* st) { }; + #endif // CPU_ZERO_DISASSEMBLER_ZERO_HPP diff --git a/src/hotspot/share/asm/codeBuffer.cpp b/src/hotspot/share/asm/codeBuffer.cpp --- a/src/hotspot/share/asm/codeBuffer.cpp +++ b/src/hotspot/share/asm/codeBuffer.cpp @@ -86,7 +86,8 @@ // External buffer, in a predefined CodeBlob. // Important: The code_start must be taken exactly, and not realigned. CodeBuffer::CodeBuffer(CodeBlob* blob) { - initialize_misc("static buffer"); + // Provide code buffer with meaningful name + initialize_misc(blob->name()); initialize(blob->content_begin(), blob->content_size()); verify_section_allocation(); } @@ -1035,7 +1036,9 @@ } void CodeBuffer::block_comment(intptr_t offset, const char * comment) { - _code_strings.add_comment(offset, comment); + if (_collect_comments) { + _code_strings.add_comment(offset, comment); + } } const char* CodeBuffer::code_string(const char* str) { @@ -1148,15 +1151,23 @@ const char* CodeStrings::_prefix = " ;; "; // default: can be changed via set_prefix +// Check if any block comments are pending for the given offset. +bool CodeStrings::has_block_comment(intptr_t offset) const { + if (_strings == NULL) return false; + CodeString* c = find(offset); + return c != NULL; +} + void CodeStrings::print_block_comment(outputStream* stream, intptr_t offset) const { - check_valid(); - if (_strings != NULL) { + check_valid(); + if (_strings != NULL) { CodeString* c = find(offset); while (c && c->offset() == offset) { stream->bol(); stream->print("%s", _prefix); // Don't interpret as format strings since it could contain % - stream->print_raw_cr(c->string()); + stream->print_raw(c->string()); + stream->bol(); // advance to next line only if string didn't contain a cr() at the end. c = c->next_comment(); } } @@ -1186,7 +1197,7 @@ void CodeBuffer::decode() { ttyLocker ttyl; - Disassembler::decode(decode_begin(), insts_end()); + Disassembler::decode(decode_begin(), insts_end(), tty); _decode_begin = insts_end(); } @@ -1217,4 +1228,10 @@ } } +// Directly disassemble code buffer. +void CodeBuffer::decode(address start, address end) { + ttyLocker ttyl; + Disassembler::decode(this, start, end, tty); +} + #endif // PRODUCT diff --git a/src/hotspot/share/asm/codeBuffer.hpp b/src/hotspot/share/asm/codeBuffer.hpp --- a/src/hotspot/share/asm/codeBuffer.hpp +++ b/src/hotspot/share/asm/codeBuffer.hpp @@ -289,6 +289,7 @@ const char* add_string(const char * string) PRODUCT_RETURN_(return NULL;); void add_comment(intptr_t offset, const char * comment) PRODUCT_RETURN; + bool has_block_comment(intptr_t offset) const; void print_block_comment(outputStream* stream, intptr_t offset) const PRODUCT_RETURN; // MOVE strings from other to this; invalidate other. void assign(CodeStrings& other) PRODUCT_RETURN; @@ -296,6 +297,7 @@ void copy(CodeStrings& other) PRODUCT_RETURN; // FREE strings; invalidate this. void free() PRODUCT_RETURN; + // Guarantee that _strings are used at most once; assign and free invalidate a buffer. inline void check_valid() const { #ifdef ASSERT @@ -377,6 +379,7 @@ OopRecorder* _oop_recorder; CodeStrings _code_strings; + bool _collect_comments; // Indicate if we need to collect block comments at all. OopRecorder _default_oop_recorder; // override with initialize_oop_recorder Arena* _overflow_arena; @@ -403,6 +406,14 @@ #if INCLUDE_AOT _immutable_PIC = false; #endif + + // Collect block comments, but restrict collection to cases where a disassembly is output. + _collect_comments = ( PrintAssembly + || PrintStubCode + || PrintMethodHandleStubs + || PrintInterpreter + || PrintSignatureHandlers + ); } void initialize(address code_start, csize_t code_size) { @@ -604,6 +615,23 @@ } } + // Directly disassemble code buffer. + // Print the comment associated with offset on stream, if there is one. + virtual void print_block_comment(outputStream* stream, address block_begin) { +#ifndef PRODUCT + intptr_t offset = (intptr_t)(block_begin - _total_start); // I assume total_start is not correct for all code sections. + _code_strings.print_block_comment(stream, offset); +#endif + } + bool has_block_comment(address block_begin) { +#ifndef PRODUCT + intptr_t offset = (intptr_t)(block_begin - _total_start); // I assume total_start is not correct for all code sections. + return _code_strings.has_block_comment(offset); +#else + return false; +#endif + } + // Code generation void relocate(address at, RelocationHolder const& rspec, int format = 0) { _insts.relocate(at, rspec, format); @@ -650,7 +678,8 @@ void decode(); void print(); #endif - + // Directly disassemble code buffer. + void decode(address start, address end); // The following header contains architecture-specific implementations #include CPU_HEADER(codeBuffer) diff --git a/src/hotspot/share/code/codeBlob.cpp b/src/hotspot/share/code/codeBlob.cpp --- a/src/hotspot/share/code/codeBlob.cpp +++ b/src/hotspot/share/code/codeBlob.cpp @@ -183,8 +183,14 @@ jio_snprintf(stub_id, sizeof(stub_id), "%s%s", name1, name2); if (PrintStubCode) { ttyLocker ttyl; + tty->print_cr("- - - [BEGIN] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); tty->print_cr("Decoding %s " INTPTR_FORMAT, stub_id, (intptr_t) stub); - Disassembler::decode(stub->code_begin(), stub->code_end()); + Disassembler::decode(stub->code_begin(), stub->code_end(), tty); + if ((stub->oop_maps() != NULL) && AbstractDisassembler::show_structs()) { + tty->print_cr("- - - [OOP MAPS]- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + stub->oop_maps()->print(); + } + tty->print_cr("- - - [END] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); tty->cr(); } Forte::register_stub(stub_id, stub->code_begin(), stub->code_end()); @@ -263,6 +269,7 @@ } void BufferBlob::free(BufferBlob *blob) { + assert(blob != NULL, "caller must check for NULL"); ThreadInVMfromUnknown __tiv; // get to VM state in case we block on CodeCache_lock blob->flush(); { diff --git a/src/hotspot/share/code/codeBlob.hpp b/src/hotspot/share/code/codeBlob.hpp --- a/src/hotspot/share/code/codeBlob.hpp +++ b/src/hotspot/share/code/codeBlob.hpp @@ -211,7 +211,7 @@ const ImmutableOopMap* oop_map_for_return_address(address return_address); virtual void preserve_callee_argument_oops(frame fr, const RegisterMap* reg_map, OopClosure* f) = 0; - // Frame support + // Frame support. Sizes are in word units. int frame_size() const { return _frame_size; } void set_frame_size(int size) { _frame_size = size; } @@ -230,6 +230,10 @@ void dump_for_addr(address addr, outputStream* st, bool verbose) const; void print_code(); + bool has_block_comment(address block_begin) const { + intptr_t offset = (intptr_t)(block_begin - code_begin()); + return _strings.has_block_comment(offset); + } // Print the comment associated with offset on stream, if there is one virtual void print_block_comment(outputStream* stream, address block_begin) const { intptr_t offset = (intptr_t)(block_begin - code_begin()); diff --git a/src/hotspot/share/code/exceptionHandlerTable.cpp b/src/hotspot/share/code/exceptionHandlerTable.cpp --- a/src/hotspot/share/code/exceptionHandlerTable.cpp +++ b/src/hotspot/share/code/exceptionHandlerTable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -185,10 +185,24 @@ } void ImplicitExceptionTable::print(address base) const { - tty->print("{"); - for( uint i=0; iprint("< " INTPTR_FORMAT ", " INTPTR_FORMAT " > ", p2i(base + *adr(i)), p2i(base + *(adr(i)+1))); - tty->print_cr("}"); + const uint n = len(); + if (n > 0) { + const uint items_per_line = 3; + uint i; + tty->print_cr("ImplicitExceptionTable (size = %d entries, %d bytes):", n, size_in_bytes()); + tty->print("{"); + for (i = 0; i < n; i++) { + if (i%items_per_line == 0) { + tty->cr(); + tty->fill_to(3); + } + tty->print("< " INTPTR_FORMAT ", " INTPTR_FORMAT " > ", p2i(base + *adr(i)), p2i(base + *(adr(i)+1))); + } + tty->bol(); + tty->print_cr("}"); + } else { + tty->print_cr("ImplicitExceptionTable is empty"); + } } ImplicitExceptionTable::ImplicitExceptionTable(const nmethod* nm) { diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -24,6 +24,7 @@ #include "precompiled.hpp" #include "jvm.h" +#include "asm/assembler.inline.hpp" #include "code/codeCache.hpp" #include "code/compiledIC.hpp" #include "code/compiledMethod.inline.hpp" @@ -456,14 +457,17 @@ { MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); int native_nmethod_size = CodeBlob::allocation_size(code_buffer, sizeof(nmethod)); + CodeOffsets offsets; offsets.set_value(CodeOffsets::Verified_Entry, vep_offset); offsets.set_value(CodeOffsets::Frame_Complete, frame_complete); - nm = new (native_nmethod_size, CompLevel_none) nmethod(method(), compiler_none, native_nmethod_size, - compile_id, &offsets, - code_buffer, frame_size, - basic_lock_owner_sp_offset, - basic_lock_sp_offset, oop_maps); + nm = new (native_nmethod_size, CompLevel_none) + nmethod(method(), compiler_none, native_nmethod_size, + compile_id, &offsets, + code_buffer, frame_size, + basic_lock_owner_sp_offset, + basic_lock_sp_offset, + oop_maps); NOT_PRODUCT(if (nm != NULL) native_nmethod_stats.note_native_nmethod(nm)); } @@ -593,9 +597,9 @@ _native_basic_lock_sp_offset(basic_lock_sp_offset) { { - int scopes_data_offset = 0; - int deoptimize_offset = 0; - int deoptimize_mh_offset = 0; + int scopes_data_offset = 0; + int deoptimize_offset = 0; + int deoptimize_mh_offset = 0; debug_only(NoSafepointVerifier nsv;) assert_locked_or_safepoint(CodeCache_lock); @@ -658,18 +662,32 @@ xtty->stamp(); xtty->end_head(" address='" INTPTR_FORMAT "'", (intptr_t) this); } - // print the header part first - print(); - // then print the requested information + // Print the header part, then print the requested information. + // This is both handled in decode2(), called via print_code() -> decode() if (PrintNativeNMethods) { + tty->print_cr("-------------------------- Assembly (native nmethod) ---------------------------"); print_code(); - if (oop_maps != NULL) { - oop_maps->print(); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); +#if defined(SUPPORT_DATA_STRUCTS) + if (AbstractDisassembler::show_structs()) { + if (oop_maps != NULL) { + tty->print("oop maps:"); // oop_maps->print_on(tty) outputs a cr() at the beginning + oop_maps->print_on(tty); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + } + } +#endif + } else { + print(); // print the header part only. + } +#if defined(SUPPORT_DATA_STRUCTS) + if (AbstractDisassembler::show_structs()) { + if (PrintRelocations) { + print_relocations(); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); } } - if (PrintRelocations) { - print_relocations(); - } +#endif if (xtty != NULL) { xtty->tail("print_native_nmethod"); } @@ -746,22 +764,21 @@ } else { _deopt_mh_handler_begin = NULL; } - } else { + } else #endif - // Exception handler and deopt handler are in the stub section - assert(offsets->value(CodeOffsets::Exceptions) != -1, "must be set"); - assert(offsets->value(CodeOffsets::Deopt ) != -1, "must be set"); - - _exception_offset = _stub_offset + offsets->value(CodeOffsets::Exceptions); - _deopt_handler_begin = (address) this + _stub_offset + offsets->value(CodeOffsets::Deopt); - if (offsets->value(CodeOffsets::DeoptMH) != -1) { - _deopt_mh_handler_begin = (address) this + _stub_offset + offsets->value(CodeOffsets::DeoptMH); - } else { - _deopt_mh_handler_begin = NULL; + { + // Exception handler and deopt handler are in the stub section + assert(offsets->value(CodeOffsets::Exceptions) != -1, "must be set"); + assert(offsets->value(CodeOffsets::Deopt ) != -1, "must be set"); + + _exception_offset = _stub_offset + offsets->value(CodeOffsets::Exceptions); + _deopt_handler_begin = (address) this + _stub_offset + offsets->value(CodeOffsets::Deopt); + if (offsets->value(CodeOffsets::DeoptMH) != -1) { + _deopt_mh_handler_begin = (address) this + _stub_offset + offsets->value(CodeOffsets::DeoptMH); + } else { + _deopt_mh_handler_begin = NULL; + } } -#if INCLUDE_JVMCI - } -#endif if (offsets->value(CodeOffsets::UnwindHandler) != -1) { _unwind_handler_offset = code_offset() + offsets->value(CodeOffsets::UnwindHandler); } else { @@ -787,8 +804,7 @@ _verified_entry_point = code_begin() + offsets->value(CodeOffsets::Verified_Entry); _osr_entry_point = code_begin() + offsets->value(CodeOffsets::OSR_Entry); _exception_cache = NULL; - - _scopes_data_begin = (address) this + scopes_data_offset; + _scopes_data_begin = (address) this + scopes_data_offset; _pc_desc_container.reset_to(scopes_pcs_begin()); @@ -909,33 +925,72 @@ xtty->stamp(); xtty->end_head(); } - // print the header part first - print(); - // then print the requested information + // Print the header part, then print the requested information. + // This is both handled in decode2(). if (printmethod) { - print_code(); - print_pcs(); - if (oop_maps()) { - oop_maps()->print(); + HandleMark hm; + ResourceMark m; + if (is_compiled_by_c1()) { + tty->cr(); + tty->print_cr("============================= C1-compiled nmethod =============================="); + } + if (is_compiled_by_jvmci()) { + tty->cr(); + tty->print_cr("=========================== JVMCI-compiled nmethod ============================="); + } + tty->print_cr("----------------------------------- Assembly -----------------------------------"); + decode2(tty); +#if defined(SUPPORT_DATA_STRUCTS) + if (AbstractDisassembler::show_structs()) { + // Print the oops from the underlying CodeBlob as well. + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + print_oops(tty); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + print_metadata(tty); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + print_pcs(); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + if (oop_maps() != NULL) { + tty->print("oop maps:"); // oop_maps()->print_on(tty) outputs a cr() at the beginning + oop_maps()->print_on(tty); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + } + } +#endif + } else { + print(); // print the header part only. + } + +#if defined(SUPPORT_DATA_STRUCTS) + if (AbstractDisassembler::show_structs()) { + if (printmethod || PrintDebugInfo || CompilerOracle::has_option_string(_method, "PrintDebugInfo")) { + print_scopes(); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + } + if (printmethod || PrintRelocations || CompilerOracle::has_option_string(_method, "PrintRelocations")) { + print_relocations(); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + } + if (printmethod || PrintDependencies || CompilerOracle::has_option_string(_method, "PrintDependencies")) { + print_dependencies(); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + } + if (printmethod || PrintExceptionHandlers) { + print_handler_table(); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + print_nul_chk_table(); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + } + + if (printmethod) { + print_recorded_oops(); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); + print_recorded_metadata(); + tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); } } - if (printmethod || PrintDebugInfo || CompilerOracle::has_option_string(_method, "PrintDebugInfo")) { - print_scopes(); - } - if (printmethod || PrintRelocations || CompilerOracle::has_option_string(_method, "PrintRelocations")) { - print_relocations(); - } - if (printmethod || PrintDependencies || CompilerOracle::has_option_string(_method, "PrintDependencies")) { - print_dependencies(); - } - if (printmethod || PrintExceptionHandlers) { - print_handler_table(); - print_nul_chk_table(); - } - if (printmethod) { - print_recorded_oops(); - print_recorded_metadata(); - } +#endif + if (xtty != NULL) { xtty->tail("print_nmethod"); } @@ -2062,10 +2117,9 @@ assert(cb != NULL && cb == this, ""); ttyLocker ttyl; tty->print_cr("implicit exception happened at " INTPTR_FORMAT, p2i(pc)); - print(); + // Print all available nmethod info. + print_nmethod(true); method()->print_codes(); - print_code(); - print_pcs(); } #endif if (cont_offset == 0) { @@ -2076,7 +2130,6 @@ } - void nmethod_init() { // make sure you didn't forget to adjust the filler fields assert(sizeof(nmethod) % oopSize == 0, "nmethod size must be multiple of a word"); @@ -2124,12 +2177,14 @@ bool ok() { return _ok; } virtual void do_oop(oop* p) { if (oopDesc::is_oop_or_null(*p)) return; + // Print diagnostic information before calling print_nmethod(). + // Assertions therein might prevent call from returning. + tty->print_cr("*** non-oop " PTR_FORMAT " found at " PTR_FORMAT " (offset %d)", + p2i(*p), p2i(p), (int)((intptr_t)p - (intptr_t)_nm)); if (_ok) { _nm->print_nmethod(true); _ok = false; } - tty->print_cr("*** non-oop " PTR_FORMAT " found at " PTR_FORMAT " (offset %d)", - p2i(*p), p2i(p), (int)((intptr_t)p - (intptr_t)_nm)); } virtual void do_oop(narrowOop* p) { ShouldNotReachHere(); } }; @@ -2238,107 +2293,104 @@ // Printing operations void nmethod::print() const { + ttyLocker ttyl; // keep the following output all in one block + print(tty); +} + +void nmethod::print(outputStream* st) const { ResourceMark rm; - ttyLocker ttyl; // keep the following output all in one block - - tty->print("Compiled method "); + + st->print("Compiled method "); if (is_compiled_by_c1()) { - tty->print("(c1) "); + st->print("(c1) "); } else if (is_compiled_by_c2()) { - tty->print("(c2) "); + st->print("(c2) "); } else if (is_compiled_by_jvmci()) { - tty->print("(JVMCI) "); + st->print("(JVMCI) "); } else { - tty->print("(nm) "); + st->print("(n/a) "); } print_on(tty, NULL); if (WizardMode) { - tty->print("((nmethod*) " INTPTR_FORMAT ") ", p2i(this)); - tty->print(" for method " INTPTR_FORMAT , p2i(method())); - tty->print(" { "); - tty->print_cr("%s ", state()); - tty->print_cr("}:"); + st->print("((nmethod*) " INTPTR_FORMAT ") ", p2i(this)); + st->print(" for method " INTPTR_FORMAT , p2i(method())); + st->print(" { "); + st->print_cr("%s ", state()); + st->print_cr("}:"); } - if (size () > 0) tty->print_cr(" total in heap [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(this), - p2i(this) + size(), - size()); - if (relocation_size () > 0) tty->print_cr(" relocation [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(relocation_begin()), - p2i(relocation_end()), - relocation_size()); - if (consts_size () > 0) tty->print_cr(" constants [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(consts_begin()), - p2i(consts_end()), - consts_size()); - if (insts_size () > 0) tty->print_cr(" main code [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(insts_begin()), - p2i(insts_end()), - insts_size()); - if (stub_size () > 0) tty->print_cr(" stub code [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(stub_begin()), - p2i(stub_end()), - stub_size()); - if (oops_size () > 0) tty->print_cr(" oops [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(oops_begin()), - p2i(oops_end()), - oops_size()); - if (metadata_size () > 0) tty->print_cr(" metadata [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(metadata_begin()), - p2i(metadata_end()), - metadata_size()); - if (scopes_data_size () > 0) tty->print_cr(" scopes data [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(scopes_data_begin()), - p2i(scopes_data_end()), - scopes_data_size()); - if (scopes_pcs_size () > 0) tty->print_cr(" scopes pcs [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(scopes_pcs_begin()), - p2i(scopes_pcs_end()), - scopes_pcs_size()); - if (dependencies_size () > 0) tty->print_cr(" dependencies [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(dependencies_begin()), - p2i(dependencies_end()), - dependencies_size()); - if (handler_table_size() > 0) tty->print_cr(" handler table [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(handler_table_begin()), - p2i(handler_table_end()), - handler_table_size()); - if (nul_chk_table_size() > 0) tty->print_cr(" nul chk table [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(nul_chk_table_begin()), - p2i(nul_chk_table_end()), - nul_chk_table_size()); + if (size () > 0) st->print_cr(" total in heap [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(this), + p2i(this) + size(), + size()); + if (relocation_size () > 0) st->print_cr(" relocation [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(relocation_begin()), + p2i(relocation_end()), + relocation_size()); + if (consts_size () > 0) st->print_cr(" constants [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(consts_begin()), + p2i(consts_end()), + consts_size()); + if (insts_size () > 0) st->print_cr(" main code [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(insts_begin()), + p2i(insts_end()), + insts_size()); + if (stub_size () > 0) st->print_cr(" stub code [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(stub_begin()), + p2i(stub_end()), + stub_size()); + if (oops_size () > 0) st->print_cr(" oops [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(oops_begin()), + p2i(oops_end()), + oops_size()); + if (metadata_size () > 0) st->print_cr(" metadata [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(metadata_begin()), + p2i(metadata_end()), + metadata_size()); + if (scopes_data_size () > 0) st->print_cr(" scopes data [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(scopes_data_begin()), + p2i(scopes_data_end()), + scopes_data_size()); + if (scopes_pcs_size () > 0) st->print_cr(" scopes pcs [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(scopes_pcs_begin()), + p2i(scopes_pcs_end()), + scopes_pcs_size()); + if (dependencies_size () > 0) st->print_cr(" dependencies [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(dependencies_begin()), + p2i(dependencies_end()), + dependencies_size()); + if (handler_table_size() > 0) st->print_cr(" handler table [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(handler_table_begin()), + p2i(handler_table_end()), + handler_table_size()); + if (nul_chk_table_size() > 0) st->print_cr(" nul chk table [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(nul_chk_table_begin()), + p2i(nul_chk_table_end()), + nul_chk_table_size()); #if INCLUDE_JVMCI - if (speculations_size () > 0) tty->print_cr(" speculations [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(speculations_begin()), - p2i(speculations_end()), - speculations_size()); - if (jvmci_data_size () > 0) tty->print_cr(" JVMCI data [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", - p2i(jvmci_data_begin()), - p2i(jvmci_data_end()), - jvmci_data_size()); + if (speculations_size () > 0) st->print_cr(" speculations [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(speculations_begin()), + p2i(speculations_end()), + speculations_size()); + if (jvmci_data_size () > 0) st->print_cr(" JVMCI data [" INTPTR_FORMAT "," INTPTR_FORMAT "] = %d", + p2i(jvmci_data_begin()), + p2i(jvmci_data_end()), + jvmci_data_size()); #endif } -#ifndef PRODUCT - -void nmethod::print_scopes() { - // Find the first pc desc for all scopes in the code and print it. - ResourceMark rm; - for (PcDesc* p = scopes_pcs_begin(); p < scopes_pcs_end(); p++) { - if (p->scope_decode_offset() == DebugInformationRecorder::serialized_null) - continue; - - ScopeDesc* sd = scope_desc_at(p->real_pc(this)); - while (sd != NULL) { - sd->print_on(tty, p); - sd = sd->sender(); - } - } +void nmethod::print_code() { + HandleMark hm; + ResourceMark m; + ttyLocker ttyl; + // Call the specialized decode method of this class. + decode(tty); } +#ifndef PRODUCT // called InstanceKlass methods are available only then. Declared as PRODUCT_RETURN + void nmethod::print_dependencies() { ResourceMark rm; ttyLocker ttyl; // keep the following output all in one block @@ -2354,57 +2406,379 @@ deps.log_dependency(); // put it into the xml log also } } - - +#endif + +#if defined(SUPPORT_DATA_STRUCTS) + +// Print the oops from the underlying CodeBlob. +void nmethod::print_oops(outputStream* st) { + HandleMark hm; + ResourceMark m; + st->print("Oops:"); + if (oops_begin() < oops_end()) { + st->cr(); + for (oop* p = oops_begin(); p < oops_end(); p++) { + Disassembler::print_location((unsigned char*)p, (unsigned char*)oops_begin(), (unsigned char*)oops_end(), st, true, false); + st->print(PTR_FORMAT " ", *((uintptr_t*)p)); + if (*p == Universe::non_oop_word()) { + st->print_cr("NON_OOP"); + continue; // skip non-oops + } + if (*p == NULL) { + st->print_cr("NULL-oop"); + continue; // skip non-oops + } + (*p)->print_value_on(st); + st->cr(); + } + } else { + st->print_cr(" "); + } +} + +// Print metadata pool. +void nmethod::print_metadata(outputStream* st) { + HandleMark hm; + ResourceMark m; + st->print("Metadata:"); + if (metadata_begin() < metadata_end()) { + st->cr(); + for (Metadata** p = metadata_begin(); p < metadata_end(); p++) { + Disassembler::print_location((unsigned char*)p, (unsigned char*)metadata_begin(), (unsigned char*)metadata_end(), st, true, false); + st->print(PTR_FORMAT " ", *((uintptr_t*)p)); + if (*p && *p != Universe::non_oop_word()) { + (*p)->print_value_on(st); + } + st->cr(); + } + } else { + st->print_cr(" "); + } +} + +#ifndef PRODUCT // ScopeDesc::print_on() is available only then. Declared as PRODUCT_RETURN +void nmethod::print_scopes_on(outputStream* st) { + // Find the first pc desc for all scopes in the code and print it. + ResourceMark rm; + st->print("scopes:"); + if (scopes_pcs_begin() < scopes_pcs_end()) { + st->cr(); + for (PcDesc* p = scopes_pcs_begin(); p < scopes_pcs_end(); p++) { + if (p->scope_decode_offset() == DebugInformationRecorder::serialized_null) + continue; + + ScopeDesc* sd = scope_desc_at(p->real_pc(this)); + while (sd != NULL) { + sd->print_on(st, p); // print output ends with a newline + sd = sd->sender(); + } + } + } else { + st->print_cr(" "); + } +} +#endif + +#ifndef PRODUCT // RelocIterator does support printing only then. void nmethod::print_relocations() { ResourceMark m; // in case methods get printed via the debugger tty->print_cr("relocations:"); RelocIterator iter(this); iter.print(); } - - -void nmethod::print_pcs() { +#endif + +void nmethod::print_pcs_on(outputStream* st) { ResourceMark m; // in case methods get printed via debugger - tty->print_cr("pc-bytecode offsets:"); - for (PcDesc* p = scopes_pcs_begin(); p < scopes_pcs_end(); p++) { - p->print(this); + st->print("pc-bytecode offsets:"); + if (scopes_pcs_begin() < scopes_pcs_end()) { + st->cr(); + for (PcDesc* p = scopes_pcs_begin(); p < scopes_pcs_end(); p++) { + p->print_on(st, this); // print output ends with a newline + } + } else { + st->print_cr(" "); } } +void nmethod::print_handler_table() { + ExceptionHandlerTable(this).print(); +} + +void nmethod::print_nul_chk_table() { + ImplicitExceptionTable(this).print(code_begin()); +} + void nmethod::print_recorded_oops() { - tty->print_cr("Recorded oops:"); - for (int i = 0; i < oops_count(); i++) { - oop o = oop_at(i); - tty->print("#%3d: " INTPTR_FORMAT " ", i, p2i(o)); - if (o == Universe::non_oop_word()) { - tty->print("non-oop word"); + const int n = oops_count(); + const int log_n = (n<10) ? 1 : (n<100) ? 2 : (n<1000) ? 3 : (n<10000) ? 4 : 6; + tty->print("Recorded oops:"); + if (n > 0) { + tty->cr(); + for (int i = 0; i < n; i++) { + oop o = oop_at(i); + tty->print("#%*d: " INTPTR_FORMAT " ", log_n, i, p2i(o)); + if (o == (oop)Universe::non_oop_word()) { + tty->print("non-oop word"); + } else if (o == NULL) { + tty->print("NULL-oop"); + } else { + o->print_value_on(tty); + } + tty->cr(); + } + } else { + tty->print_cr(" "); + } +} + +void nmethod::print_recorded_metadata() { + const int n = metadata_count(); + const int log_n = (n<10) ? 1 : (n<100) ? 2 : (n<1000) ? 3 : (n<10000) ? 4 : 6; + tty->print("Recorded metadata:"); + if (n > 0) { + tty->cr(); + for (int i = 0; i < n; i++) { + Metadata* m = metadata_at(i); + tty->print("#%*d: " INTPTR_FORMAT " ", log_n, i, p2i(m)); + if (m == (Metadata*)Universe::non_oop_word()) { + tty->print("non-metadata word"); + } else if (m == NULL) { + tty->print("NULL-oop"); + } else { + Metadata::print_value_on_maybe_null(tty, m); + } + tty->cr(); + } + } else { + tty->print_cr(" "); + } +} +#endif + +#if defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_ABSTRACT_ASSEMBLY) + +void nmethod::print_constant_pool(outputStream* st) { + //----------------------------------- + //---< Print the constant pool >--- + //----------------------------------- + int consts_size = this->consts_size(); + if ( consts_size > 0 ) { + unsigned char* cstart = this->consts_begin(); + unsigned char* cp = cstart; + unsigned char* cend = cp + consts_size; + unsigned int bytes_per_line = 4; + unsigned int CP_alignment = 8; + unsigned int n; + + st->cr(); + + //---< print CP header to make clear what's printed >--- + if( ((uintptr_t)cp&(CP_alignment-1)) == 0 ) { + n = bytes_per_line; + st->print_cr("[Constant Pool]"); + Disassembler::print_location(cp, cstart, cend, st, true, true); + Disassembler::print_hexdata(cp, n, st, true); + st->cr(); } else { - if (o != NULL) { - o->print_value(); - } else { - tty->print_cr("NULL"); + n = (uintptr_t)cp&(bytes_per_line-1); + st->print_cr("[Constant Pool (unaligned)]"); + } + + //---< print CP contents, bytes_per_line at a time >--- + while (cp < cend) { + Disassembler::print_location(cp, cstart, cend, st, true, false); + Disassembler::print_hexdata(cp, n, st, false); + cp += n; + n = bytes_per_line; + st->cr(); + } + + //---< Show potential alignment gap between constant pool and code >--- + cend = code_begin(); + if( cp < cend ) { + n = 4; + st->print_cr("[Code entry alignment]"); + while (cp < cend) { + Disassembler::print_location(cp, cstart, cend, st, false, false); + cp += n; + st->cr(); } } - tty->cr(); + } else { + st->print_cr("[Constant Pool (empty)]"); } + st->cr(); } -void nmethod::print_recorded_metadata() { - tty->print_cr("Recorded metadata:"); - for (int i = 0; i < metadata_count(); i++) { - Metadata* m = metadata_at(i); - tty->print("#%3d: " INTPTR_FORMAT " ", i, p2i(m)); - if (m == (Metadata*)Universe::non_oop_word()) { - tty->print("non-metadata word"); - } else { - Metadata::print_value_on_maybe_null(tty, m); +#endif + +// Disassemble this nmethod. +// Print additional debug information, if requested. This could be code +// comments, block comments, profiling counters, etc. +// The undisassembled format is useful no disassembler library is available. +// The resulting hex dump (with markers) can be disassembled later, or on +// another system, when/where a disassembler library is available. +void nmethod::decode2(outputStream* ost) const { + + // Called from frame::back_trace_with_decode without ResourceMark. + ResourceMark rm; + + // Make sure we have a valid stream to print on. + outputStream* st = ost ? ost : tty; + +#if defined(SUPPORT_ABSTRACT_ASSEMBLY) && ! defined(SUPPORT_ASSEMBLY) + const bool use_compressed_format = true; + const bool compressed_with_comments = use_compressed_format && (AbstractDisassembler::show_comment() || + AbstractDisassembler::show_block_comment()); +#else + const bool use_compressed_format = Disassembler::is_abstract(); + const bool compressed_with_comments = use_compressed_format && (AbstractDisassembler::show_comment() || + AbstractDisassembler::show_block_comment()); +#endif + + st->cr(); + this->print(st); + st->cr(); + +#if defined(SUPPORT_ASSEMBLY) + //---------------------------------- + //---< Print real disassembly >--- + //---------------------------------- + if (! use_compressed_format) { + Disassembler::decode(const_cast(this), st); + return; + } +#endif + +#if defined(SUPPORT_ABSTRACT_ASSEMBLY) + + // Compressed undisassembled disassembly format. + // The following stati are defined/supported: + // = 0 - currently at bol() position, nothing printed yet on current line. + // = 1 - currently at position after print_location(). + // > 1 - in the midst of printing instruction stream bytes. + int compressed_format_idx = 0; + int code_comment_column = 0; + const int instr_maxlen = Assembler::instr_maxlen(); + const uint tabspacing = 8; + unsigned char* start = this->code_begin(); + unsigned char* p = this->code_begin(); + unsigned char* end = this->code_end(); + unsigned char* pss = p; // start of a code section (used for offsets) + + if ((start == NULL) || (end == NULL)) { + st->print_cr("PrintAssembly not possible due to uninitialized section pointers"); + return; + } +#endif + +#if defined(SUPPORT_ABSTRACT_ASSEMBLY) + //---< plain abstract disassembly, no comments or anything, just section headers >--- + if (use_compressed_format && ! compressed_with_comments) { + const_cast(this)->print_constant_pool(st); + + //---< Open the output (Marker for post-mortem disassembler) >--- + st->print_cr("[MachCode]"); + const char* header = NULL; + address p0 = p; + while (p < end) { + address pp = p; + while ((p < end) && (header == NULL)) { + header = nmethod_section_label(p); + pp = p; + p += Assembler::instr_len(p); + } + if (pp > p0) { + AbstractDisassembler::decode_range_abstract(p0, pp, start, end, st, Assembler::instr_maxlen()); + p0 = pp; + p = pp; + header = NULL; + } else if (header != NULL) { + st->bol(); + st->print_cr("%s", header); + header = NULL; + } } - tty->cr(); + //---< Close the output (Marker for post-mortem disassembler) >--- + st->bol(); + st->print_cr("[/MachCode]"); + return; } +#endif + +#if defined(SUPPORT_ABSTRACT_ASSEMBLY) + //---< abstract disassembly with comments and section headers merged in >--- + if (compressed_with_comments) { + const_cast(this)->print_constant_pool(st); + + //---< Open the output (Marker for post-mortem disassembler) >--- + st->print_cr("[MachCode]"); + while ((p < end) && (p != NULL)) { + const int instruction_size_in_bytes = Assembler::instr_len(p); + + //---< Block comments for nmethod. Interrupts instruction stream, if any. >--- + // Outputs a bol() before and a cr() after, but only if a comment is printed. + // Prints nmethod_section_label as well. + if (AbstractDisassembler::show_block_comment()) { + print_block_comment(st, p); + if (st->position() == 0) { + compressed_format_idx = 0; + } + } + + //---< New location information after line break >--- + if (compressed_format_idx == 0) { + code_comment_column = Disassembler::print_location(p, pss, end, st, false, false); + compressed_format_idx = 1; + } + + //---< Code comment for current instruction. Address range [p..(p+len)) >--- + unsigned char* p_end = p + (ssize_t)instruction_size_in_bytes; + S390_ONLY(if (p_end > end) p_end = end;) // avoid getting past the end + + if (AbstractDisassembler::show_comment() && const_cast(this)->has_code_comment(p, p_end)) { + //---< interrupt instruction byte stream for code comment >--- + if (compressed_format_idx > 1) { + st->cr(); // interrupt byte stream + st->cr(); // add an empty line + code_comment_column = Disassembler::print_location(p, pss, end, st, false, false); + } + const_cast(this)->print_code_comment_on(st, code_comment_column, p, p_end ); + st->bol(); + compressed_format_idx = 0; + } + + //---< New location information after line break >--- + if (compressed_format_idx == 0) { + code_comment_column = Disassembler::print_location(p, pss, end, st, false, false); + compressed_format_idx = 1; + } + + //---< Nicely align instructions for readability >--- + if (compressed_format_idx > 1) { + Disassembler::print_delimiter(st); + } + + //---< Now, finally, print the actual instruction bytes >--- + unsigned char* p0 = p; + p = Disassembler::decode_instruction_abstract(p, st, instruction_size_in_bytes, instr_maxlen); + compressed_format_idx += p - p0; + + if (Disassembler::start_newline(compressed_format_idx-1)) { + st->cr(); + compressed_format_idx = 0; + } + } + //---< Close the output (Marker for post-mortem disassembler) >--- + st->bol(); + st->print_cr("[/MachCode]"); + return; + } +#endif } -#endif // PRODUCT +#if defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_ABSTRACT_ASSEMBLY) const char* nmethod::reloc_string_for(u_char* begin, u_char* end) { RelocIterator iter(this, begin, end); @@ -2414,7 +2788,9 @@ switch (iter.type()) { case relocInfo::none: return "no_reloc"; case relocInfo::oop_type: { - stringStream st; + // Get a non-resizable resource-allocated stringStream. + // Our callees make use of (nested) ResourceMarks. + stringStream st(NEW_RESOURCE_ARRAY(char, 1024), 1024); oop_Relocation* r = iter.oop_reloc(); oop obj = r->oop_value(); st.print("oop("); @@ -2516,17 +2892,28 @@ return NULL; } -void nmethod::print_nmethod_labels(outputStream* stream, address block_begin) const { - if (block_begin == entry_point()) stream->print_cr("[Entry Point]"); - if (block_begin == verified_entry_point()) stream->print_cr("[Verified Entry Point]"); - if (JVMCI_ONLY(_exception_offset >= 0 &&) block_begin == exception_begin()) stream->print_cr("[Exception Handler]"); - if (block_begin == stub_begin()) stream->print_cr("[Stub Code]"); - if (JVMCI_ONLY(_deopt_handler_begin != NULL &&) block_begin == deopt_handler_begin()) stream->print_cr("[Deopt Handler Code]"); - - if (has_method_handle_invokes()) - if (block_begin == deopt_mh_handler_begin()) stream->print_cr("[Deopt MH Handler Code]"); - - if (block_begin == consts_begin()) stream->print_cr("[Constants]"); +const char* nmethod::nmethod_section_label(address pos) const { + const char* label = NULL; + if (pos == code_begin()) label = "[Instructions begin]"; + if (pos == entry_point()) label = "[Entry Point]"; + if (pos == verified_entry_point()) label = "[Verified Entry Point]"; + if (has_method_handle_invokes() && (pos == deopt_mh_handler_begin())) label = "[Deopt MH Handler Code]"; + if (pos == consts_begin() && pos != insts_begin()) label = "[Constants]"; + // Check stub_code before checking exception_handler or deopt_handler. + if (pos == this->stub_begin()) label = "[Stub Code]"; + if (JVMCI_ONLY(_exception_offset >= 0 &&) pos == exception_begin()) label = "[Exception Handler]"; + if (JVMCI_ONLY(_deopt_handler_begin != NULL &&) pos == deopt_handler_begin()) label = "[Deopt Handler Code]"; + return label; +} + +void nmethod::print_nmethod_labels(outputStream* stream, address block_begin, bool print_section_labels) const { + if (print_section_labels) { + const char* label = nmethod_section_label(block_begin); + if (label != NULL) { + stream->bol(); + stream->print_cr("%s", label); + } + } if (block_begin == entry_point()) { methodHandle m = method(); @@ -2623,7 +3010,24 @@ } } -void nmethod::print_code_comment_on(outputStream* st, int column, u_char* begin, u_char* end) { +// Returns whether this nmethod has code comments. +bool nmethod::has_code_comment(address begin, address end) { + // scopes? + ScopeDesc* sd = scope_desc_in(begin, end); + if (sd != NULL) return true; + + // relocations? + const char* str = reloc_string_for(begin, end); + if (str != NULL) return true; + + // implicit exceptions? + int cont_offset = ImplicitExceptionTable(this).at(begin - code_begin()); + if (cont_offset != 0) return true; + + return false; +} + +void nmethod::print_code_comment_on(outputStream* st, int column, address begin, address end) { // First, find an oopmap in (begin, end]. // We use the odd half-closed interval so that oop maps and scope descs // which are tied to the byte after a call are printed with the call itself. @@ -2636,7 +3040,7 @@ address pc = base + pair->pc_offset(); if (pc > begin) { if (pc <= end) { - st->move_to(column); + st->move_to(column, 6, 0); st->print("; "); om->print_on(st); } @@ -2648,7 +3052,7 @@ // Print any debug info present at this pc. ScopeDesc* sd = scope_desc_in(begin, end); if (sd != NULL) { - st->move_to(column); + st->move_to(column, 6, 0); if (sd->bci() == SynchronizationEntryBCI) { st->print(";*synchronization entry"); } else if (sd->bci() == AfterBci) { @@ -2704,8 +3108,11 @@ // Print all scopes for (;sd != NULL; sd = sd->sender()) { - st->move_to(column); + st->move_to(column, 6, 0); st->print("; -"); + if (sd->should_reexecute()) { + st->print(" (reexecute)"); + } if (sd->method() == NULL) { st->print("method is NULL"); } else { @@ -2722,20 +3129,24 @@ } // Print relocation information + // Prevent memory leak: allocating without ResourceMark. + ResourceMark rm; const char* str = reloc_string_for(begin, end); if (str != NULL) { if (sd != NULL) st->cr(); - st->move_to(column); + st->move_to(column, 6, 0); st->print("; {%s}", str); } int cont_offset = ImplicitExceptionTable(this).at(begin - code_begin()); if (cont_offset != 0) { - st->move_to(column); + st->move_to(column, 6, 0); st->print("; implicit exception: dispatches to " INTPTR_FORMAT, p2i(code_begin() + cont_offset)); } } +#endif + class DirectNativeCallWrapper: public NativeCallWrapper { private: NativeCall* _call; @@ -2842,12 +3253,14 @@ return CompiledDirectStaticCall::before(return_addr); } -#ifndef PRODUCT - +#if defined(SUPPORT_DATA_STRUCTS) void nmethod::print_value_on(outputStream* st) const { st->print("nmethod"); print_on(st, NULL); } +#endif + +#ifndef PRODUCT void nmethod::print_calls(outputStream* st) { RelocIterator iter(this); @@ -2869,14 +3282,6 @@ } } -void nmethod::print_handler_table() { - ExceptionHandlerTable(this).print(); -} - -void nmethod::print_nul_chk_table() { - ImplicitExceptionTable(this).print(code_begin()); -} - void nmethod::print_statistics() { ttyLocker ttyl; if (xtty != NULL) xtty->head("statistics type='nmethod'"); diff --git a/src/hotspot/share/code/nmethod.hpp b/src/hotspot/share/code/nmethod.hpp --- a/src/hotspot/share/code/nmethod.hpp +++ b/src/hotspot/share/code/nmethod.hpp @@ -377,6 +377,7 @@ void make_unloaded(); bool has_dependencies() { return dependencies_size() != 0; } + void print_dependencies() PRODUCT_RETURN; void flush_dependencies(bool delete_immediately); bool has_flushed_dependencies() { return _has_flushed_dependencies; } void set_has_flushed_dependencies() { @@ -505,18 +506,40 @@ void verify_scopes(); void verify_interrupt_point(address interrupt_point); + // Disassemble this nmethod with additional debug information, e.g. information about blocks. + void decode2(outputStream* st) const; + void print_constant_pool(outputStream* st); + + // Avoid hiding of parent's 'decode(outputStream*)' method. + void decode(outputStream* st) const { decode2(st); } // just delegate here. + // printing support void print() const; + void print(outputStream* st) const; + void print_code(); + +#if defined(SUPPORT_DATA_STRUCTS) + // print output in opt build for disassembler library void print_relocations() PRODUCT_RETURN; - void print_pcs() PRODUCT_RETURN; - void print_scopes() PRODUCT_RETURN; - void print_dependencies() PRODUCT_RETURN; - void print_value_on(outputStream* st) const PRODUCT_RETURN; + void print_pcs() { print_pcs_on(tty); } + void print_pcs_on(outputStream* st); + void print_scopes() { print_scopes_on(tty); } + void print_scopes_on(outputStream* st) PRODUCT_RETURN; + void print_value_on(outputStream* st) const; + void print_handler_table(); + void print_nul_chk_table(); + void print_recorded_oops(); + void print_recorded_metadata(); + + void print_oops(outputStream* st); // oops from the underlying CodeBlob. + void print_metadata(outputStream* st); // metadata in metadata pool. +#else + // void print_pcs() PRODUCT_RETURN; + void print_pcs() { return; } +#endif + void print_calls(outputStream* st) PRODUCT_RETURN; - void print_handler_table() PRODUCT_RETURN; - void print_nul_chk_table() PRODUCT_RETURN; - void print_recorded_oops() PRODUCT_RETURN; - void print_recorded_metadata() PRODUCT_RETURN; + static void print_statistics() PRODUCT_RETURN; void maybe_print_nmethod(DirectiveSet* directive); void print_nmethod(bool print_code); @@ -532,14 +555,21 @@ // Prints block-level comments, including nmethod specific block labels: virtual void print_block_comment(outputStream* stream, address block_begin) const { +#if defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_ABSTRACT_ASSEMBLY) print_nmethod_labels(stream, block_begin); CodeBlob::print_block_comment(stream, block_begin); +#endif } - void print_nmethod_labels(outputStream* stream, address block_begin) const; + bool has_block_comment(address block_begin) { + return CodeBlob::has_block_comment(block_begin); + } + void print_nmethod_labels(outputStream* stream, address block_begin, bool print_section_labels=true) const; + const char* nmethod_section_label(address pos) const; + // returns whether this nmethod has code comments. + bool has_code_comment(address begin, address end); // Prints a comment for one native instruction (reloc info, pc desc) void print_code_comment_on(outputStream* st, int column, address begin, address end); - static void print_statistics() PRODUCT_RETURN; // Compiler task identification. Note that all OSR methods // are numbered in an independent sequence if CICountOSR is true, diff --git a/src/hotspot/share/code/pcDesc.cpp b/src/hotspot/share/code/pcDesc.cpp --- a/src/hotspot/share/code/pcDesc.cpp +++ b/src/hotspot/share/code/pcDesc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,19 +40,24 @@ return code->code_begin() + pc_offset(); } -void PcDesc::print(CompiledMethod* code) { +void PcDesc::print_on(outputStream* st, CompiledMethod* code) { #ifndef PRODUCT ResourceMark rm; - tty->print_cr("PcDesc(pc=" PTR_FORMAT " offset=%x bits=%x):", p2i(real_pc(code)), pc_offset(), _flags); + st->print("PcDesc(pc=" PTR_FORMAT " offset=%x bits=%x):", p2i(real_pc(code)), pc_offset(), _flags); if (scope_decode_offset() == DebugInformationRecorder::serialized_null) { + st->cr(); return; } + int tab = 8; + int pos = st->position() + 2; // current column plus two spaces + pos = ((pos+tab-1)/tab)*tab; + for (ScopeDesc* sd = code->scope_desc_at(real_pc(code)); sd != NULL; sd = sd->sender()) { - sd->print_on(tty); + sd->print_on(st); } #endif } diff --git a/src/hotspot/share/code/pcDesc.hpp b/src/hotspot/share/code/pcDesc.hpp --- a/src/hotspot/share/code/pcDesc.hpp +++ b/src/hotspot/share/code/pcDesc.hpp @@ -92,7 +92,8 @@ // Returns the real pc address real_pc(const CompiledMethod* code) const; - void print(CompiledMethod* code); + void print(CompiledMethod* code) { print_on(tty, code); } + void print_on(outputStream* st, CompiledMethod* code); bool verify(CompiledMethod* code); }; diff --git a/src/hotspot/share/code/vmreg.cpp b/src/hotspot/share/code/vmreg.cpp --- a/src/hotspot/share/code/vmreg.cpp +++ b/src/hotspot/share/code/vmreg.cpp @@ -39,7 +39,7 @@ void VMRegImpl::print_on(outputStream* st) const { if( is_reg() ) { - assert( VMRegImpl::regName[value()], "" ); + assert(VMRegImpl::regName[value()], "VMRegImpl::regName[" INTPTR_FORMAT "] returns NULL", value()); st->print("%s",VMRegImpl::regName[value()]); } else if (is_stack()) { int stk = value() - stack0->value(); diff --git a/src/hotspot/share/code/vtableStubs.cpp b/src/hotspot/share/code/vtableStubs.cpp --- a/src/hotspot/share/code/vtableStubs.cpp +++ b/src/hotspot/share/code/vtableStubs.cpp @@ -80,7 +80,7 @@ void VtableStub::print_on(outputStream* st) const { - st->print("vtable stub (index = %d, receiver_location = " INTX_FORMAT ", code = [" INTPTR_FORMAT ", " INTPTR_FORMAT "[)", + st->print("vtable stub (index = %d, receiver_location = " INTX_FORMAT ", code = [" INTPTR_FORMAT ", " INTPTR_FORMAT "])", index(), p2i(receiver_location()), p2i(code_begin()), p2i(code_end())); } diff --git a/src/hotspot/share/compiler/abstractDisassembler.cpp b/src/hotspot/share/compiler/abstractDisassembler.cpp new file mode 100644 --- /dev/null +++ b/src/hotspot/share/compiler/abstractDisassembler.cpp @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +// AbstractDisassembler is the base class for +// platform-specific Disassembler classes. + +#include "precompiled.hpp" +#include "asm/assembler.inline.hpp" +#include "compiler/abstractDisassembler.hpp" +#include "oops/oop.inline.hpp" +#include "utilities/debug.hpp" +#include "utilities/ostream.hpp" + +// Default values for what is being printed as line prefix when disassembling a single instruction. +// Can be overridden by command line parameter PrintAssemblyOptions. +bool AbstractDisassembler::_show_data_hex = true; +bool AbstractDisassembler::_show_data_int = false; +bool AbstractDisassembler::_show_data_float = false; +bool AbstractDisassembler::_align_instr = false; +bool AbstractDisassembler::_show_pc = true; +bool AbstractDisassembler::_show_offset = false; +bool AbstractDisassembler::_show_structs = false; +bool AbstractDisassembler::_show_comment = false; +bool AbstractDisassembler::_show_block_comment = false; +#if defined(ARM) || defined(AARCH64) +bool AbstractDisassembler::_show_bytes = false; // set "true" to see what's in memory bit by bit + // might prove cumbersome because instr_len is hard to find on arm +#endif +#if defined(PPC) +bool AbstractDisassembler::_show_bytes = false; // set "true" to see what's in memory bit by bit +#endif +#if defined(S390) +bool AbstractDisassembler::_show_bytes = false; // set "true" to see what's in memory bit by bit +#endif +#if defined(SPARC) +bool AbstractDisassembler::_show_bytes = false; // set "true" to see what's in memory bit by bit +#endif +#if defined(X86) +bool AbstractDisassembler::_show_bytes = false; // set "true" to see what's in memory bit by bit + // might prove cumbersome because instr_len is hard to find on x86 +#endif +#if defined(ZERO) +bool AbstractDisassembler::_show_bytes = false; // set "true" to see what's in memory bit by bit +#endif + +// Return #bytes printed. Callers may use that for output alignment. +// Print instruction address, and offset from blob begin. +// Offset width (2, 4, 6, 8 bytes) is adapted to size of blob. +// Working assumption: we are at st->bol() upon entry. If not, it's the +// caller's responsibility to guarantee proper alignment. +int AbstractDisassembler::print_location(address here, address begin, address end, outputStream* st, bool align, bool print_header) { + const int pos_0 = st->position(); + + if (show_pc() || show_offset()) { + st->print(" "); + } + + if (show_pc()) { + if (print_header) { + st->print(" %*s", 18, "Address"); + } else { + st->print(" " PTR_FORMAT, p2i(here)); + } + } + + if (show_offset()) { +#ifdef ASSERT + if ((uintptr_t)begin > (uintptr_t)here) st->print(">>begin(" PTR_FORMAT ") > here(" PTR_FORMAT ")<<", p2i(begin), p2i(here)); + if ((uintptr_t)end < (uintptr_t)here) st->print(">> end(" PTR_FORMAT ") < here(" PTR_FORMAT ")<<", p2i(end), p2i(here)); + assert((uintptr_t)begin <= (uintptr_t)end, "inverted address range"); +#endif + const int blob_len = end - begin; + const int offset = here - begin; + const int width = (blob_len < (1<< 8)) ? 2 : (blob_len < (1<<16)) ? 4 : (blob_len < (1<<24)) ? 6 : 8; + if (print_header) { + st->print(" %*s", width+5, "offset"); + } else { + st->print(" (+0x%*.*x)", width, width, offset); + } + } + + if ((show_pc() || show_offset()) && !print_header) { + st->print(": "); + } + + if (align) { + const uint tabspacing = 8; + const uint pos = st->position(); + const uint aligned_pos = ((pos+tabspacing-1)/tabspacing)*tabspacing /* - 1 */; + st->fill_to(aligned_pos); + } + + return st->position() - pos_0; +} + + +// Return #bytes printed. Callers may use that for output alignment. +// Print instruction in hexadecimal representation, using 2-byte blocks. +// Used with real disassemblies. Not so useful with abstract disassemblies. +int AbstractDisassembler::print_instruction(address here, int len, int max_len, outputStream* st, bool align, bool print_header) { + if (show_bytes()) { + const int block_bytes = 2; + const int pos_0 = st->position(); + address pos = here; + + //---< print instruction bytes in blocks >--- + // must print byte by byte: address might be unaligned. + for (; pos <= here + len - block_bytes; pos += block_bytes) { + for (address byte = pos; byte < pos + block_bytes; byte++) { + st->print("%2.2x", *byte); + } + st->print(" "); + } + + //---< Print the remaining bytes of the instruction >--- + if ((len & (block_bytes - 1)) != 0) { + for (; pos < here + len; pos++) { + st->print("%2.2x", *pos); + } + } + + //---< filler for shorter than max_len instructions >--- + for (int i = len+1; i < max_len; i++) { + st->print(" "); + } + + st->print(" "); // separator space. + print_delimiter(st); + return st->position() - pos_0; + } + + if (align) { + const uint tabspacing = 8; + const uint pos = st->position(); + const uint aligned_pos = ((pos+tabspacing-1)/tabspacing)*tabspacing /* - 1 */; + st->fill_to(aligned_pos); + } + + return 0; +} + + +// Return #bytes printed. Callers may use that for output alignment. +// Print data (e.g. constant pool entries) in hex format. +// Depending on the alignment, short, int, and long entities are printed. +// If selected, data is formatted as int/long and float/double values in addition. +int AbstractDisassembler::print_hexdata(address here, int len, outputStream* st, bool print_header) { + const int tsize = 8; + const int pos_0 = st->position(); + int pos = pos_0; + int align = ((pos+tsize-1)/tsize)*tsize; + st->fill_to(align); + + //---< printing hex data >--- + if (show_data_hex()) { + switch (len) { + case 1: if (print_header) { + st->print("hex1"); + } else { + st->print("0x%02x", *here); + } + st->fill_to(align += tsize); + case 2: if (print_header) { + st->print(" hex2"); + } else { + if (((uintptr_t)(here)&0x01) == 0) { + st->print("0x%04x", *((jushort*)here)); + } + } + st->fill_to(align += tsize); + case 4: if (print_header) { + st->print(" hex4"); + } else { + if (((uintptr_t)(here)&0x03) == 0) { + st->print("0x%08x", *((juint*)here)); + } + } + st->fill_to(align += 2*tsize); + case 8: if (print_header) { + st->print(" hex8"); + } else { + if (((uintptr_t)(here)&0x07) == 0) { + st->print(PTR_FORMAT, *((uintptr_t*)here)); + } + } + st->fill_to(align += 3*tsize); + break; + default: ; + } + pos = st->position(); + align = ((pos+tsize-1)/tsize)*tsize; + st->fill_to(align); + } + + //---< printing int/long data >--- + if (show_data_int()) { + switch (len) { + case 4: if (print_header) { + st->print(" int"); + } else { + if (((uintptr_t)(here)&0x03) == 0) { + st->print("%12.1d", *((jint*)here)); + } + } + st->fill_to(align += 2*tsize); + case 8: if (print_header) { + st->print(" long"); + } else { + if (((uintptr_t)(here)&0x07) == 0) { + st->print("%23.1ld", *((jlong*)here)); + } + } + st->fill_to(align += 3*tsize); + break; + default: ; + } + pos = st->position(); + align = ((pos+tsize-1)/tsize)*tsize; + st->fill_to(align); + } + + //---< printing float/double data >--- + if (show_data_float()) { + switch (len) { + case 4: if (print_header) { + st->print(" float"); + } else { + if (((uintptr_t)(here)&0x03) == 0) { + st->print("%15.7e", (double)*((float*)here)); + } + } + st->fill_to(align += 2*tsize); + case 8: if (print_header) { + st->print(" double"); + } else { + if (((uintptr_t)(here)&0x07) == 0) { + st->print("%23.15e", *((double*)here)); + } + } + st->fill_to(align += 3*tsize); + break; + default: ; + } + } + + return st->position() - pos_0; +} + + +// Return #bytes printed. Callers may use that for output alignment. +// Print an instruction delimiter. +int AbstractDisassembler::print_delimiter(outputStream* st) { + if (align_instr()) { st->print("| "); return 2; } + else return 0; +} + + +// Decodes the one instruction at address start in a platform-independent format. +// Returns the start of the next instruction (which is 'start' plus 'instruction_size_in_bytes'). +// The parameter max_instr_size_in_bytes is used for output alignment purposes only. +address AbstractDisassembler::decode_instruction_abstract(address start, + outputStream* st, + const int instruction_size_in_bytes, + const int max_instr_size_in_bytes) { + assert(instruction_size_in_bytes > 0, "no zero-size instructions!"); + assert(max_instr_size_in_bytes >= instruction_size_in_bytes, "inconsistent call parameters"); + + //---< current instruction is at the start address >--- + unsigned char* current = (unsigned char*) start; + int filler_limit = align_instr() ? max_instr_size_in_bytes : ((instruction_size_in_bytes+abstract_instruction_bytes_per_block-1)/abstract_instruction_bytes_per_block) + *abstract_instruction_bytes_per_block; + + //---< print the instruction's bytes >--- + for (int i = 1; i <= instruction_size_in_bytes; i++) { + st->print("%02x", *current); + ++current; + if (abstract_instruction_bytes_per_block <= max_instr_size_in_bytes) { + if (i%abstract_instruction_bytes_per_block == 0) st->print(" "); + } else { + if (i == instruction_size_in_bytes) st->print(" "); + } + } + + //---< print some filler spaces to column-align instructions >--- + for (int i = instruction_size_in_bytes+1; i <= filler_limit; i++) { + st->print(" "); + if (abstract_instruction_bytes_per_block <= max_instr_size_in_bytes) { + if (i%abstract_instruction_bytes_per_block == 0) st->print(" "); + } else { + if (i == instruction_size_in_bytes) st->print(" "); + } + } + + //---< the address of the next instruction >--- + return (address) current; +} + + +// Decodes all instructions in the given range [start..end) +// calling decode_instruction_abstract for each instruction. +// The format is platform dependent only to the extend that +// it respects the actual instruction length where possible. +// Does not print any markers or decorators. +void AbstractDisassembler::decode_range_abstract(address range_start, address range_end, + address start, address end, + outputStream* st, + const int max_instr_size_in_bytes) { + assert(st != NULL, "need an output stream (no default)!"); + int idx = 0; + address pos = range_start; + + while ((pos != NULL) && (pos < range_end)) { + int instr_size_in_bytes = Assembler::instr_len(pos); + + if (idx == 0) print_location(pos, start, end, st, false, false); + else print_delimiter(st); + + //---< print the instruction's bytes >--- + // don't access storage beyond end of range + if (pos + instr_size_in_bytes <= range_end) { + pos = decode_instruction_abstract(pos, st, instr_size_in_bytes, max_instr_size_in_bytes); + } else { + // If the range to be decoded contains garbage at the end (e.g. 0xcc initializer bytes), + // instruction size calculation may run out of sync. Just terminate in that case. + pos = range_end; + } + + idx += instr_size_in_bytes; + if (start_newline(idx)) { + st->cr(); + idx = 0; + } + } +} + + +// Decodes all instructions in the given range [start..end). +// The output is enclosed in [MachCode] and [/MachCode] tags for later recognition. +// The format is platform dependent only to the extend that +// it respects the actual instruction length where possible. +void AbstractDisassembler::decode_abstract(address start, address end, outputStream* ost, + const int max_instr_size_in_bytes) { + int idx = 0; + address pos = start; + + outputStream* st = (ost == NULL) ? tty : ost; + + //---< Open the output (Marker for post-mortem disassembler) >--- + st->bol(); + st->print_cr("[MachCode]"); + + decode_range_abstract(start, end, start, end, st, max_instr_size_in_bytes); + + //---< Close the output (Marker for post-mortem disassembler) >--- + st->bol(); + st->print_cr("[/MachCode]"); +} diff --git a/src/hotspot/share/compiler/abstractDisassembler.hpp b/src/hotspot/share/compiler/abstractDisassembler.hpp new file mode 100644 --- /dev/null +++ b/src/hotspot/share/compiler/abstractDisassembler.hpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_COMPILER_ABSTRACTDISASSEMBLER_HPP +#define SHARE_COMPILER_ABSTRACTDISASSEMBLER_HPP + +// AbstractDisassembler is the base class for +// platform-specific Disassembler classes. + +#include "utilities/globalDefinitions.hpp" + +class AbstractDisassembler { + + private: + // These are some general settings which control + // abstract disassembly output. + enum { + // that many bytes are dumped in one line. + abstract_instruction_bytes_per_line = 32, + // instruction bytes are grouped in blocks of that many bytes. + abstract_instruction_bytes_per_block = 2, + // instructions have this default len. + abstract_instruction_size_in_bytes = 1, + // instructions have this maximum len. + abstract_instruction_maxsize_in_bytes = 1 + }; + + static bool _align_instr; // vertical alignment of instructions in abstract disassembly + static bool _show_pc; // print the instruction address + static bool _show_offset; // print the instruction offset (from start of blob) + static bool _show_bytes; // print instruction bytes + static bool _show_data_hex; // print instruction bytes + static bool _show_data_int; // print instruction bytes + static bool _show_data_float; // print instruction bytes + static bool _show_structs; // print compiler data structures (relocations, oop maps, scopes, metadata, ...) + static bool _show_comment; // print instruction comments + static bool _show_block_comment; // print block comments + + public: + // Platform-independent location and instruction formatting. + // All functions return #characters printed. + static int print_location(address here, address begin, address end, outputStream* st, bool align, bool print_header); + static int print_instruction(address here, int len, int max_len, outputStream* st, bool align, bool print_header); + static int print_hexdata(address here, int len, outputStream* st, bool print_header = false); + static int print_delimiter(outputStream* st); + static bool start_newline(int byte_count) { return byte_count >= abstract_instruction_bytes_per_line; } + + static void toggle_align_instr() { _align_instr = !_align_instr; } + static void toggle_show_pc() { _show_pc = !_show_pc; } + static void toggle_show_offset() { _show_offset = !_show_offset; } + static void toggle_show_bytes() { _show_bytes = !_show_bytes; } + static void toggle_show_data_hex() { _show_data_hex = !_show_data_hex; } + static void toggle_show_data_int() { _show_data_int = !_show_data_int; } + static void toggle_show_data_float() { _show_data_float = !_show_data_float; } + static void toggle_show_structs() { _show_structs = !_show_structs; } + static void toggle_show_comment() { _show_comment = !_show_comment; } + static void toggle_show_block_comment() { _show_block_comment = !_show_block_comment; } + + static bool align_instr() { return _align_instr; } + static bool show_pc() { return _show_pc; } + static bool show_offset() { return _show_offset; } + static bool show_bytes() { return _show_bytes; } + static bool show_data_hex() { return _show_data_hex; } + static bool show_data_int() { return _show_data_int; } + static bool show_data_float() { return _show_data_float; } + static bool show_structs() { return _show_structs; } + static bool show_comment() { return _show_comment; } + static bool show_block_comment() { return _show_block_comment; } + + // Decodes the one instruction at address start in a platform-independent + // format. Returns the start of the next instruction (which is + // 'start' plus 'instruction_size_in_bytes'). The parameter max_instr_size_in_bytes + // is used for output alignment purposes only. + static address decode_instruction_abstract(address start, + outputStream* st, + const int instruction_size_in_bytes, + const int max_instr_size_in_bytes = abstract_instruction_maxsize_in_bytes); + + // Decodes all instructions in the given range [start..end) + // calling decode_instruction_abstract for each instruction. + // The format is platform dependent only to the extend that + // it respects the actual instruction length where possible. + // Does not print any markers or decorators. + static void decode_range_abstract(address range_start, address range_end, + address start, address end, + outputStream* st, + const int max_instr_size_in_bytes = abstract_instruction_maxsize_in_bytes); + + // Decodes all instructions in the given range in a platform-independent + // format, calling decode_instruction_abstract for each instruction. + static void decode_abstract(address start, address end, + outputStream* st, + const int max_instr_size_in_bytes = abstract_instruction_maxsize_in_bytes); +}; + +#endif // SHARE_COMPILER_ABSTRACTDISASSEMBLER_HPP diff --git a/src/hotspot/share/compiler/disassembler.cpp b/src/hotspot/share/compiler/disassembler.cpp --- a/src/hotspot/share/compiler/disassembler.cpp +++ b/src/hotspot/share/compiler/disassembler.cpp @@ -23,7 +23,7 @@ */ #include "precompiled.hpp" -#include "asm/macroAssembler.hpp" +#include "asm/assembler.inline.hpp" #include "ci/ciUtilities.hpp" #include "classfile/javaClasses.hpp" #include "code/codeCache.hpp" @@ -43,6 +43,7 @@ void* Disassembler::_library = NULL; bool Disassembler::_tried_to_load_library = false; +bool Disassembler::_library_usable = false; // This routine is in the shared library: Disassembler::decode_func_virtual Disassembler::_decode_instructions_virtual = NULL; @@ -55,127 +56,46 @@ #define COMMENT_COLUMN 52 LP64_ONLY(+8) /*could be an option*/ #define BYTES_COMMENT ";..." /* funky byte display comment */ -bool Disassembler::load_library() { - if (_decode_instructions_virtual != NULL || _decode_instructions != NULL) { - // Already succeeded. - return true; - } - if (_tried_to_load_library) { - // Do not try twice. - // To force retry in debugger: assign _tried_to_load_library=0 - return false; - } - // Try to load it. - char ebuf[1024]; - char buf[JVM_MAXPATHLEN]; - os::jvm_path(buf, sizeof(buf)); - int jvm_offset = -1; - int lib_offset = -1; -#ifdef STATIC_BUILD - char* p = strrchr(buf, '/'); - *p = '\0'; - strcat(p, "/lib/"); - lib_offset = jvm_offset = strlen(buf); -#else - { - // Match "jvm[^/]*" in jvm_path. - const char* base = buf; - const char* p = strrchr(buf, *os::file_separator()); - if (p != NULL) lib_offset = p - base + 1; - p = strstr(p ? p : base, "jvm"); - if (p != NULL) jvm_offset = p - base; - } -#endif - // Find the disassembler shared library. - // Search for several paths derived from libjvm, in this order: - // 1. /jre/lib///libhsdis-.so (for compatibility) - // 2. /jre/lib///hsdis-.so - // 3. /jre/lib//hsdis-.so - // 4. hsdis-.so (using LD_LIBRARY_PATH) - if (jvm_offset >= 0) { - // 1. /jre/lib///libhsdis-.so - strcpy(&buf[jvm_offset], hsdis_library_name); - strcat(&buf[jvm_offset], os::dll_file_extension()); - _library = os::dll_load(buf, ebuf, sizeof ebuf); - if (_library == NULL && lib_offset >= 0) { - // 2. /jre/lib///hsdis-.so - strcpy(&buf[lib_offset], hsdis_library_name); - strcat(&buf[lib_offset], os::dll_file_extension()); - _library = os::dll_load(buf, ebuf, sizeof ebuf); - } - if (_library == NULL && lib_offset > 0) { - // 3. /jre/lib//hsdis-.so - buf[lib_offset - 1] = '\0'; - const char* p = strrchr(buf, *os::file_separator()); - if (p != NULL) { - lib_offset = p - buf + 1; - strcpy(&buf[lib_offset], hsdis_library_name); - strcat(&buf[lib_offset], os::dll_file_extension()); - _library = os::dll_load(buf, ebuf, sizeof ebuf); - } - } - } - if (_library == NULL) { - // 4. hsdis-.so (using LD_LIBRARY_PATH) - strcpy(&buf[0], hsdis_library_name); - strcat(&buf[0], os::dll_file_extension()); - _library = os::dll_load(buf, ebuf, sizeof ebuf); - } - if (_library != NULL) { - _decode_instructions_virtual = CAST_TO_FN_PTR(Disassembler::decode_func_virtual, - os::dll_lookup(_library, decode_instructions_virtual_name)); - } - if (_decode_instructions_virtual == NULL && _library != NULL) { - // could not spot in new version, try old version - _decode_instructions = CAST_TO_FN_PTR(Disassembler::decode_func, - os::dll_lookup(_library, decode_instructions_name)); - use_new_version = false; - } else { - use_new_version = true; - } - _tried_to_load_library = true; - if (_decode_instructions_virtual == NULL && _decode_instructions == NULL) { - tty->print_cr("Could not load %s; %s; %s", buf, - ((_library != NULL) - ? "entry point is missing" - : (WizardMode || PrintMiscellaneous) - ? (const char*)ebuf - : "library not loadable"), - "PrintAssembly is disabled"); - return false; - } - - // Success. - tty->print_cr("Loaded disassembler from %s", buf); - return true; -} - - class decode_env { private: - nmethod* _nm; - CodeBlob* _code; + outputStream* _output; // where the disassembly is directed to + CodeBuffer* _codeBuffer; // != NULL only when decoding a CodeBuffer + CodeBlob* _codeBlob; // != NULL only when decoding a CodeBlob + nmethod* _nm; // != NULL only when decoding a nmethod CodeStrings _strings; - outputStream* _output; - address _start, _end; - ptrdiff_t _offset; + address _start; // != NULL when decoding a range of unknown type + address _end; // != NULL when decoding a range of unknown type char _option_buf[512]; char _print_raw; - bool _print_pc; - bool _print_bytes; - address _cur_insn; - int _bytes_per_line; // arch-specific formatting option + address _cur_insn; // address of instruction currently being decoded + int _bytes_per_line; // arch-specific formatting option + int _pre_decode_alignment; + int _post_decode_alignment; bool _print_file_name; + bool _print_help; + bool _helpPrinted; + static bool _optionsParsed; + enum { + tabspacing = 8 + }; + + // Check if the event matches the expected tag + // The tag must be a substring of the event, and + // the tag must be a token in the event, i.e. separated by delimiters static bool match(const char* event, const char* tag) { - size_t taglen = strlen(tag); - if (strncmp(event, tag, taglen) != 0) + size_t eventlen = strlen(event); + size_t taglen = strlen(tag); + if (eventlen < taglen) // size mismatch + return false; + if (strncmp(event, tag, taglen) != 0) // string mismatch return false; char delim = event[taglen]; return delim == '\0' || delim == ' ' || delim == '/' || delim == '='; } + // Merge new option string with previously recorded options void collect_options(const char* p) { if (p == NULL || p[0] == '\0') return; size_t opt_so_far = strlen(_option_buf); @@ -187,14 +107,56 @@ char* q = fillp; while ((q = strpbrk(q, " \t\n")) != NULL) *q++ = ','; - // Note that multiple PrintAssemblyOptions flags accumulate with \n, - // which we want to be changed to a comma... } + void process_options(outputStream* ost); + void print_insn_labels(); - void print_insn_bytes(address pc0, address pc); + void print_insn_prefix(); void print_address(address value); + // Properly initializes _start/_end. Overwritten too often if + // printing of instructions is called for each instruction. + void set_start(address s) { _start = s; } + void set_end (address e) { _end = e; } + void set_nm (nmethod* nm) { _nm = nm; } + void set_output(outputStream* st) { _output = st; } + +#if defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_ABSTRACT_ASSEMBLY) + // The disassembler library (sometimes) uses tabs to nicely align the instruction operands. + // Depending on the mnemonic length and the column position where the + // mnemonic is printed, alignment may turn out to be not so nice. + // To improve, we assume 8-character tab spacing and left-align the mnemonic on a tab position. + // Instruction comments are aligned 4 tab positions to the right of the mnemonic. + void calculate_alignment() { + _pre_decode_alignment = ((output()->position()+tabspacing-1)/tabspacing)*tabspacing; + _post_decode_alignment = _pre_decode_alignment + 4*tabspacing; + } + + void start_insn(address pc) { + _cur_insn = pc; + output()->bol(); + print_insn_labels(); + print_insn_prefix(); + } + + void end_insn(address pc) { + address pc0 = cur_insn(); + outputStream* st = output(); + + if (AbstractDisassembler::show_comment()) { + if ((_nm != NULL) && _nm->has_code_comment(pc0, pc)) { + _nm->print_code_comment_on(st, _post_decode_alignment, pc0, pc); + // this calls reloc_string_for which calls oop::print_value_on + } + print_hook_comments(pc0, _nm != NULL); + } + Disassembler::annotate(pc0, output()); + // follow each complete insn by a nice newline + st->bol(); + } +#endif + struct SourceFileInfo { struct Link : public CHeapObj { const char* file; @@ -241,40 +203,28 @@ static GrowableArray* _cached_src_lines; public: - decode_env(CodeBlob* code, outputStream* output, - CodeStrings c = CodeStrings(), ptrdiff_t offset = 0); + decode_env(CodeBuffer* code, outputStream* output); + decode_env(CodeBlob* code, outputStream* output, CodeStrings c = CodeStrings() /* , ptrdiff_t offset */); + decode_env(nmethod* code, outputStream* output, CodeStrings c = CodeStrings()); + // Constructor for a 'decode_env' to decode an arbitrary + // piece of memory, hopefully containing code. + decode_env(address start, address end, outputStream* output); - address decode_instructions(address start, address end); - - void start_insn(address pc) { - _cur_insn = pc; - output()->bol(); - print_insn_labels(); - } - - void end_insn(address pc) { - address pc0 = cur_insn(); - outputStream* st = output(); - if (_print_bytes && pc > pc0) - print_insn_bytes(pc0, pc); - if (_nm != NULL) { - _nm->print_code_comment_on(st, COMMENT_COLUMN, pc0, pc); - // this calls reloc_string_for which calls oop::print_value_on - } - print_hook_comments(pc0, _nm != NULL); - // follow each complete insn by a nice newline - st->cr(); - } + // Add 'original_start' argument which is the the original address + // the instructions were located at (if this is not equal to 'start'). + address decode_instructions(address start, address end, address original_start = NULL); address handle_event(const char* event, address arg); - outputStream* output() { return _output; } - address cur_insn() { return _cur_insn; } - const char* options() { return _option_buf; } - static void hook(const char* file, int line, address pc); + outputStream* output() { return _output; } + address cur_insn() { return _cur_insn; } + const char* options() { return _option_buf; } + static void hook(const char* file, int line, address pc); void print_hook_comments(address pc, bool newline); }; +bool decode_env::_optionsParsed = false; + decode_env::SourceFileInfoTable decode_env::_src_table; const char* decode_env::_cached_src = NULL; GrowableArray* decode_env::_cached_src_lines = NULL; @@ -361,50 +311,185 @@ } } -decode_env::decode_env(CodeBlob* code, outputStream* output, CodeStrings c, - ptrdiff_t offset) : _nm(NULL), - _start(NULL), - _end(NULL), - _option_buf(), - _print_raw('\0'), - _cur_insn(NULL) { +decode_env::decode_env(CodeBuffer* code, outputStream* output) { + memset(this, 0, sizeof(*this)); _output = output ? output : tty; - _code = code; - if (code != NULL && code->is_nmethod()) + _codeBlob = NULL; + _codeBuffer = code; + _helpPrinted = false; + + process_options(_output); +} + +decode_env::decode_env(CodeBlob* code, outputStream* output, CodeStrings c) { + memset(this, 0, sizeof(*this)); // Beware, this zeroes bits of fields. + _output = output ? output : tty; + _codeBlob = code; + _codeBuffer = NULL; + _helpPrinted = false; + if (_codeBlob != NULL && _codeBlob->is_nmethod()) { _nm = (nmethod*) code; + } _strings.copy(c); - _offset = offset; + process_options(_output); +} + +decode_env::decode_env(nmethod* code, outputStream* output, CodeStrings c) { + memset(this, 0, sizeof(*this)); // Beware, this zeroes bits of fields. + _output = output ? output : tty; + _codeBlob = NULL; + _codeBuffer = NULL; + _nm = code; + _start = _nm->code_begin(); + _end = _nm->code_end(); + _helpPrinted = false; + _strings.copy(c); + + process_options(_output); +} + +// Constructor for a 'decode_env' to decode a memory range [start, end) +// of unknown origin, assuming it contains code. +decode_env::decode_env(address start, address end, outputStream* output) { + assert(start < end, "Range must have a positive size, [" PTR_FORMAT ".." PTR_FORMAT ").", p2i(start), p2i(end)); + memset(this, 0, sizeof(*this)); + _output = output ? output : tty; + _codeBlob = NULL; + _codeBuffer = NULL; + _start = start; + _end = end; + _helpPrinted = false; + + process_options(_output); +} + +void decode_env::process_options(outputStream* ost) { // by default, output pc but not bytes: - _print_pc = true; - _print_bytes = false; - _bytes_per_line = Disassembler::pd_instruction_alignment(); - _print_file_name= true; + _print_help = false; + _bytes_per_line = Disassembler::pd_instruction_alignment(); + _print_file_name = true; + + if (_optionsParsed) return; // parse only once // parse the global option string: collect_options(Disassembler::pd_cpu_opts()); collect_options(PrintAssemblyOptions); - if (strstr(options(), "hsdis-")) { - if (strstr(options(), "hsdis-print-raw")) - _print_raw = (strstr(options(), "xml") ? 2 : 1); - if (strstr(options(), "hsdis-print-pc")) - _print_pc = !_print_pc; - if (strstr(options(), "hsdis-print-bytes")) - _print_bytes = !_print_bytes; + if (strstr(options(), "print-raw")) { + _print_raw = (strstr(options(), "xml") ? 2 : 1); } + if (strstr(options(), "help")) { - tty->print_cr("PrintAssemblyOptions help:"); - tty->print_cr(" hsdis-print-raw test plugin by requesting raw output"); - tty->print_cr(" hsdis-print-raw-xml test plugin by requesting raw xml"); - tty->print_cr(" hsdis-print-pc turn off PC printing (on by default)"); - tty->print_cr(" hsdis-print-bytes turn on instruction byte output"); - tty->print_cr("combined options: %s", options()); + _print_help = true; + } + if (strstr(options(), "align-instr")) { + AbstractDisassembler::toggle_align_instr(); + } + if (strstr(options(), "show-pc")) { + AbstractDisassembler::toggle_show_pc(); + } + if (strstr(options(), "show-offset")) { + AbstractDisassembler::toggle_show_offset(); + } + if (strstr(options(), "show-bytes")) { + AbstractDisassembler::toggle_show_bytes(); + } + if (strstr(options(), "show-data-hex")) { + AbstractDisassembler::toggle_show_data_hex(); + } + if (strstr(options(), "show-data-int")) { + AbstractDisassembler::toggle_show_data_int(); + } + if (strstr(options(), "show-data-float")) { + AbstractDisassembler::toggle_show_data_float(); + } + if (strstr(options(), "show-structs")) { + AbstractDisassembler::toggle_show_structs(); + } + if (strstr(options(), "show-comment")) { + AbstractDisassembler::toggle_show_comment(); + } + if (strstr(options(), "show-block-comment")) { + AbstractDisassembler::toggle_show_block_comment(); + } + _optionsParsed = true; + + if (_print_help && ! _helpPrinted) { + _helpPrinted = true; + ost->print_cr("PrintAssemblyOptions help:"); + ost->print_cr(" print-raw test plugin by requesting raw output"); + ost->print_cr(" print-raw-xml test plugin by requesting raw xml"); + ost->cr(); + ost->print_cr(" show-pc toggle printing current pc, currently %s", AbstractDisassembler::show_pc() ? "ON" : "OFF"); + ost->print_cr(" show-offset toggle printing current offset, currently %s", AbstractDisassembler::show_offset() ? "ON" : "OFF"); + ost->print_cr(" show-bytes toggle printing instruction bytes, currently %s", AbstractDisassembler::show_bytes() ? "ON" : "OFF"); + ost->print_cr(" show-data-hex toggle formatting data as hex, currently %s", AbstractDisassembler::show_data_hex() ? "ON" : "OFF"); + ost->print_cr(" show-data-int toggle formatting data as int, currently %s", AbstractDisassembler::show_data_int() ? "ON" : "OFF"); + ost->print_cr(" show-data-float toggle formatting data as float, currently %s", AbstractDisassembler::show_data_float() ? "ON" : "OFF"); + ost->print_cr(" show-structs toggle compiler data structures, currently %s", AbstractDisassembler::show_structs() ? "ON" : "OFF"); + ost->print_cr(" show-comment toggle instruction comments, currently %s", AbstractDisassembler::show_comment() ? "ON" : "OFF"); + ost->print_cr(" show-block-comment toggle block comments, currently %s", AbstractDisassembler::show_block_comment() ? "ON" : "OFF"); + ost->print_cr(" align-instr toggle instruction alignment, currently %s", AbstractDisassembler::align_instr() ? "ON" : "OFF"); + ost->print_cr("combined options: %s", options()); } } +// Disassembly Event Handler. +// This method receives events from the disassembler library hsdis +// via event_to_env for each decoding step (installed by +// Disassembler::decode_instructions(), replacing the default +// callback method). This enables dumping additional info +// and custom line formatting. +// In a future extension, calling a custom decode method will be +// supported. We can use such a method to decode instructions the +// binutils decoder does not handle to our liking (suboptimal +// formatting, incomplete information, ...). +// Returns: +// - NULL for all standard invocations. The function result is not +// examined (as of now, 20190409) by the hsdis decoder loop. +// - next for 'insn0' invocations. +// next == arg: the custom decoder didn't do anything. +// next > arg: the custom decoder did decode the instruction. +// next points to the next undecoded instruction +// (continuation point for decoder loop). +// +// "Normal" sequence of events: +// insns - start of instruction stream decoding +// mach - display architecture +// format - display bytes-per-line +// for each instruction: +// insn - start of instruction decoding +// insn0 - custom decoder invocation (if any) +// addr - print address value +// /insn - end of instruction decoding +// /insns - premature end of instruction stream due to no progress +// address decode_env::handle_event(const char* event, address arg) { - if (match(event, "insn")) { + +#if defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_ABSTRACT_ASSEMBLY) + + //---< Event: end decoding loop (error, no progress) >--- + if (decode_env::match(event, "/insns")) { + // Nothing to be done here. + return NULL; + } + + //---< Event: start decoding loop >--- + if (decode_env::match(event, "insns")) { + // Nothing to be done here. + return NULL; + } + + //---< Event: finish decoding an instruction >--- + if (decode_env::match(event, "/insn")) { + output()->fill_to(_post_decode_alignment); + end_insn(arg); + return NULL; + } + + //---< Event: start decoding an instruction >--- + if (decode_env::match(event, "insn")) { start_insn(arg); } else if (match(event, "/insn")) { end_insn(arg); @@ -413,26 +498,59 @@ print_address(arg); return arg; } - } else if (match(event, "mach")) { - static char buffer[32] = { 0, }; - if (strcmp(buffer, (const char*)arg) != 0 || - strlen((const char*)arg) > sizeof(buffer) - 1) { + calculate_alignment(); + output()->fill_to(_pre_decode_alignment); + return NULL; + } + + //---< Event: call custom decoder (platform specific) >--- + if (decode_env::match(event, "insn0")) { + return Disassembler::decode_instruction0(arg, output(), arg); + } + + //---< Event: Print address >--- + if (decode_env::match(event, "addr")) { + print_address(arg); + return arg; + } + + //---< Event: mach (inform about machine architecture) >--- + // This event is problematic because it messes up the output. + // The event is fired after the instruction address has already + // been printed. The decoded instruction (event "insn") is + // printed afterwards. That doesn't look nice. + if (decode_env::match(event, "mach")) { + guarantee(arg != NULL, "event_to_env - arg must not be NULL for event 'mach'"); + static char buffer[64] = { 0, }; + // Output suppressed because it messes up disassembly. + // Only print this when the mach changes. + if (false && (strcmp(buffer, (const char*)arg) != 0 || + strlen((const char*)arg) > sizeof(buffer) - 1)) { // Only print this when the mach changes strncpy(buffer, (const char*)arg, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; - output()->print_cr("[Disassembling for mach='%s']", arg); + output()->print_cr("[Disassembling for mach='%s']", (const char*)arg); } - } else if (match(event, "format bytes-per-line")) { + return NULL; + } + + //---< Event: format bytes-per-line >--- + if (decode_env::match(event, "format bytes-per-line")) { _bytes_per_line = (int) (intptr_t) arg; - } else { - // ignore unrecognized markup + return NULL; } +#endif return NULL; } +static void* event_to_env(void* env_pv, const char* event, void* arg) { + decode_env* env = (decode_env*) env_pv; + return env->handle_event(event, (address) arg); +} + // called by the disassembler to print out jump targets and data addresses void decode_env::print_address(address adr) { - outputStream* st = _output; + outputStream* st = output(); if (adr == NULL) { st->print("NULL"); @@ -477,9 +595,11 @@ if (_nm == NULL) { // Don't do this for native methods, as the function name will be printed in // nmethod::reloc_string_for(). - ResourceMark rm; + // Allocate the buffer on the stack instead of as RESOURCE array. + // In case we do DecodeErrorFile, Thread will not be initialized, + // causing a "assert(current != __null) failed" failure. const int buflen = 1024; - char* buf = NEW_RESOURCE_ARRAY(char, buflen); + char buf[buflen]; int offset; if (os::dll_address_to_function_name(adr, buf, buflen, &offset)) { st->print(PTR_FORMAT " = %s", p2i(adr), buf); @@ -495,54 +615,31 @@ } void decode_env::print_insn_labels() { - address p = cur_insn(); - outputStream* st = output(); - CodeBlob* cb = _code; - if (cb != NULL) { - cb->print_block_comment(st, p); - } - _strings.print_block_comment(st, (intptr_t)(p - _start + _offset)); - if (_print_pc) { - st->print(" " PTR_FORMAT ": ", p2i(p)); + if (AbstractDisassembler::show_block_comment()) { + address p = cur_insn(); + outputStream* st = output(); + + //---< Block comments for nmethod >--- + // Outputs a bol() before and a cr() after, but only if a comment is printed. + // Prints nmethod_section_label as well. + if (_nm != NULL) { + _nm->print_block_comment(st, p); + } + if (_codeBlob != NULL) { + _codeBlob->print_block_comment(st, p); + } + if (_codeBuffer != NULL) { + _codeBuffer->print_block_comment(st, p); + } + _strings.print_block_comment(st, (intptr_t)(p - _start)); } } -void decode_env::print_insn_bytes(address pc, address pc_limit) { +void decode_env::print_insn_prefix() { + address p = cur_insn(); outputStream* st = output(); - size_t incr = 1; - size_t perline = _bytes_per_line; - if ((size_t) Disassembler::pd_instruction_alignment() >= sizeof(int) - && !((uintptr_t)pc % sizeof(int)) - && !((uintptr_t)pc_limit % sizeof(int))) { - incr = sizeof(int); - if (perline % incr) perline += incr - (perline % incr); - } - while (pc < pc_limit) { - // tab to the desired column: - st->move_to(COMMENT_COLUMN); - address pc0 = pc; - address pc1 = pc + perline; - if (pc1 > pc_limit) pc1 = pc_limit; - for (; pc < pc1; pc += incr) { - if (pc == pc0) { - st->print(BYTES_COMMENT); - } else if ((uint)(pc - pc0) % sizeof(int) == 0) { - st->print(" "); // put out a space on word boundaries - } - if (incr == sizeof(int)) { - st->print("%08x", *(int*)pc); - } else { - st->print("%02x", (*pc)&0xFF); - } - } - st->cr(); - } -} - - -static void* event_to_env(void* env_pv, const char* event, void* arg) { - decode_env* env = (decode_env*) env_pv; - return env->handle_event(event, (address) arg); + AbstractDisassembler::print_location(p, _start, _end, st, false, false); + AbstractDisassembler::print_instruction(p, Assembler::instr_len(p), Assembler::instr_maxlen(), st, true, false); } ATTRIBUTE_PRINTF(2, 3) @@ -575,16 +672,31 @@ return (int)(cnt1 - cnt0); } -address decode_env::decode_instructions(address start, address end) { - _start = start; _end = end; +// The 'original_start' argument holds the the original address where +// the instructions were located in the originating system. If zero (NULL) +// is passed in, there is no original address. +address decode_env::decode_instructions(address start, address end, address original_start /* = 0*/) { + // CodeComment in Stubs. + // Properly initialize _start/_end. Overwritten too often if + // printing of instructions is called for each instruction. + assert((_start == NULL) || (start == NULL) || (_start == start), "don't overwrite CTOR values"); + assert((_end == NULL) || (end == NULL) || (_end == end ), "don't overwrite CTOR values"); + if (start != NULL) set_start(start); + if (end != NULL) set_end(end); + if (original_start == NULL) { + original_start = start; + } - assert(((((intptr_t)start | (intptr_t)end) % Disassembler::pd_instruction_alignment()) == 0), "misaligned insn addr"); + //---< Check (and correct) alignment >--- + // Don't check alignment of end, it is not aligned. + if (((uint64_t)start & ((uint64_t)Disassembler::pd_instruction_alignment() - 1)) != 0) { + output()->print_cr("Decode range start:" PTR_FORMAT ": ... (unaligned)", p2i(start)); + start = (address)((uint64_t)start & ~((uint64_t)Disassembler::pd_instruction_alignment() - 1)); + } - const int show_bytes = false; // for disassembler debugging - - //_version = Disassembler::pd_cpu_version(); - - if (!Disassembler::can_decode()) { + // Trying to decode instructions doesn't make sense if we + // couldn't load the disassembler library. + if (Disassembler::is_abstract()) { return NULL; } @@ -625,16 +737,177 @@ options()); } +// ---------------------------------------------------------------------------- +// Disassembler +// Used as a static wrapper for decode_env. +// Each method will create a decode_env before decoding. +// You can call the decode_env methods directly if you already have one. -void Disassembler::decode(CodeBlob* cb, outputStream* st) { - ttyLocker ttyl; - if (!load_library()) return; - if (cb->is_nmethod()) { - decode((nmethod*)cb, st); + +bool Disassembler::load_library(outputStream* st) { + // Do not try to load multiple times. Failed once -> fails always. + // To force retry in debugger: assign _tried_to_load_library=0 + if (_tried_to_load_library) { + return _library_usable; + } + +#if defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_ABSTRACT_ASSEMBLY) + // Print to given stream, if any. + // Print to tty if Verbose is on and no stream given. + st = ((st == NULL) && Verbose) ? tty : st; + + // Compute fully qualified library name. + char ebuf[1024]; + char buf[JVM_MAXPATHLEN]; + os::jvm_path(buf, sizeof(buf)); + int jvm_offset = -1; + int lib_offset = -1; +#ifdef STATIC_BUILD + char* p = strrchr(buf, '/'); + *p = '\0'; + strcat(p, "/lib/"); + lib_offset = jvm_offset = strlen(buf); +#else + { + // Match "libjvm" instead of "jvm" on *nix platforms. Creates better matches. + // Match "[lib]jvm[^/]*" in jvm_path. + const char* base = buf; + const char* p = strrchr(buf, *os::file_separator()); +#ifdef _WIN32 + p = strstr(p ? p : base, "jvm"); +#else + p = strstr(p ? p : base, "libjvm"); +#endif + if (p != NULL) lib_offset = p - base + 1; + if (p != NULL) jvm_offset = p - base; + } +#endif + + // Find the disassembler shared library. + // Search for several paths derived from libjvm, in this order: + // 1. /jre/lib///libhsdis-.so (for compatibility) + // 2. /jre/lib///hsdis-.so + // 3. /jre/lib//hsdis-.so + // 4. hsdis-.so (using LD_LIBRARY_PATH) + if (jvm_offset >= 0) { + // 1. /jre/lib///libhsdis-.so + strcpy(&buf[jvm_offset], hsdis_library_name); + strcat(&buf[jvm_offset], os::dll_file_extension()); + _library = os::dll_load(buf, ebuf, sizeof ebuf); + if (_library == NULL && lib_offset >= 0) { + // 2. /jre/lib///hsdis-.so + strcpy(&buf[lib_offset], hsdis_library_name); + strcat(&buf[lib_offset], os::dll_file_extension()); + _library = os::dll_load(buf, ebuf, sizeof ebuf); + } + if (_library == NULL && lib_offset > 0) { + // 3. /jre/lib//hsdis-.so + buf[lib_offset - 1] = '\0'; + const char* p = strrchr(buf, *os::file_separator()); + if (p != NULL) { + lib_offset = p - buf + 1; + strcpy(&buf[lib_offset], hsdis_library_name); + strcat(&buf[lib_offset], os::dll_file_extension()); + _library = os::dll_load(buf, ebuf, sizeof ebuf); + } + } + } + if (_library == NULL) { + // 4. hsdis-.so (using LD_LIBRARY_PATH) + strcpy(&buf[0], hsdis_library_name); + strcat(&buf[0], os::dll_file_extension()); + _library = os::dll_load(buf, ebuf, sizeof ebuf); + } + + // load the decoder function to use (new or old version). + if (_library != NULL) { + _decode_instructions_virtual = CAST_TO_FN_PTR(Disassembler::decode_func_virtual, + os::dll_lookup(_library, decode_instructions_virtual_name)); + } + if (_decode_instructions_virtual == NULL && _library != NULL) { + // could not spot in new version, try old version + _decode_instructions = CAST_TO_FN_PTR(Disassembler::decode_func, + os::dll_lookup(_library, decode_instructions_name)); + use_new_version = false; + } else { + use_new_version = true; + } + _tried_to_load_library = true; + _library_usable = _decode_instructions_virtual != NULL || _decode_instructions != NULL; + + // Create a dummy environment to initialize PrintAssemblyOptions. + // The PrintAssemblyOptions must be known for abstract disassemblies as well. + decode_env dummy((unsigned char*)(&buf[0]), (unsigned char*)(&buf[1]), st); + + // Report problems during dll_load or dll_lookup, if any. + if (st != NULL) { + // Success. + if (_library_usable) { + st->print_cr("Loaded disassembler from %s", buf); + } else { + st->print_cr("Could not load %s; %s; %s", + buf, + ((_library != NULL) + ? "entry point is missing" + : ((WizardMode || PrintMiscellaneous) + ? (const char*)ebuf + : "library not loadable")), + "PrintAssembly defaults to abstract disassembly."); + } + } +#endif + return _library_usable; +} + + +// Directly disassemble code buffer. +void Disassembler::decode(CodeBuffer* cb, address start, address end, outputStream* st) { +#if defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_ABSTRACT_ASSEMBLY) + //---< Test memory before decoding >--- + if (!(cb->contains(start) && cb->contains(end))) { + //---< Allow output suppression, but prevent writing to a NULL stream. Could happen with +PrintStubCode. >--- + if (st != NULL) { + st->print("Memory range [" PTR_FORMAT ".." PTR_FORMAT "] not contained in CodeBuffer", p2i(start), p2i(end)); + } return; } + if (!os::is_readable_range(start, end)) { + //---< Allow output suppression, but prevent writing to a NULL stream. Could happen with +PrintStubCode. >--- + if (st != NULL) { + st->print("Memory range [" PTR_FORMAT ".." PTR_FORMAT "] not readable", p2i(start), p2i(end)); + } + return; + } + decode_env env(cb, st); - env.output()->print_cr("----------------------------------------------------------------------"); + env.output()->print_cr("--------------------------------------------------------------------------------"); + env.output()->print("Decoding CodeBuffer (" PTR_FORMAT ")", p2i(cb)); + if (cb->name() != NULL) { + env.output()->print(", name: %s,", cb->name()); + } + env.output()->print_cr(" at [" PTR_FORMAT ", " PTR_FORMAT "] " JLONG_FORMAT " bytes", p2i(start), p2i(end), ((jlong)(end - start))); + + if (is_abstract()) { + AbstractDisassembler::decode_abstract(start, end, env.output(), Assembler::instr_maxlen()); + } else { + env.decode_instructions(start, end); + } + env.output()->print_cr("--------------------------------------------------------------------------------"); +#endif +} + +// Directly disassemble code blob. +void Disassembler::decode(CodeBlob* cb, outputStream* st, CodeStrings c) { +#if defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_ABSTRACT_ASSEMBLY) + if (cb->is_nmethod()) { + // If we have an nmethod at hand, + // call the specialized decoder directly. + decode((nmethod*)cb, st, c); + return; + } + + decode_env env(cb, st); + env.output()->print_cr("--------------------------------------------------------------------------------"); if (cb->is_aot()) { env.output()->print("A "); if (cb->is_compiled()) { @@ -648,57 +921,78 @@ env.output()->print_cr("%s", cb->name()); } } else { - env.output()->print_cr("%s", cb->name()); + env.output()->print("Decoding CodeBlob"); + if (cb->name() != NULL) { + env.output()->print(", name: %s,", cb->name()); + } } - env.output()->print_cr(" at [" PTR_FORMAT ", " PTR_FORMAT "] " JLONG_FORMAT " bytes", p2i(cb->code_begin()), p2i(cb->code_end()), ((jlong)(cb->code_end() - cb->code_begin())) * sizeof(unsigned char*)); - env.decode_instructions(cb->code_begin(), cb->code_end()); + env.output()->print_cr(" at [" PTR_FORMAT ", " PTR_FORMAT "] " JLONG_FORMAT " bytes", p2i(cb->code_begin()), p2i(cb->code_end()), ((jlong)(cb->code_end() - cb->code_begin()))); + + if (is_abstract()) { + AbstractDisassembler::decode_abstract(cb->code_begin(), cb->code_end(), env.output(), Assembler::instr_maxlen()); + } else { + env.decode_instructions(cb->code_begin(), cb->code_end()); + } + env.output()->print_cr("--------------------------------------------------------------------------------"); +#endif } -void Disassembler::decode(address start, address end, outputStream* st, CodeStrings c, - ptrdiff_t offset) { +// Decode a nmethod. +// This includes printing the constant pool and all code segments. +// The nmethod data structures (oop maps, relocations and the like) are not printed. +void Disassembler::decode(nmethod* nm, outputStream* st, CodeStrings c) { +#if defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_ABSTRACT_ASSEMBLY) ttyLocker ttyl; - if (!load_library()) return; - decode_env env(CodeCache::find_blob_unsafe(start), st, c, offset); - env.decode_instructions(start, end); + + decode_env env(nm, st); + env.output()->print_cr("--------------------------------------------------------------------------------"); + nm->print_constant_pool(env.output()); + env.output()->print_cr("--------------------------------------------------------------------------------"); + env.output()->cr(); + if (is_abstract()) { + AbstractDisassembler::decode_abstract(nm->code_begin(), nm->code_end(), env.output(), Assembler::instr_maxlen()); + } else { + env.decode_instructions(nm->code_begin(), nm->code_end()); + } + env.output()->print_cr("--------------------------------------------------------------------------------"); +#endif } -void Disassembler::decode(nmethod* nm, outputStream* st) { - ttyLocker ttyl; - if (!load_library()) return; - decode_env env(nm, st); - env.output()->print_cr("----------------------------------------------------------------------"); +// Decode a range, given as [start address, end address) +void Disassembler::decode(address start, address end, outputStream* st, CodeStrings c /*, ptrdiff_t offset */) { +#if defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_ABSTRACT_ASSEMBLY) + //---< Test memory before decoding >--- + if (!os::is_readable_range(start, end)) { + //---< Allow output suppression, but prevent writing to a NULL stream. Could happen with +PrintStubCode. >--- + if (st != NULL) { + st->print("Memory range [" PTR_FORMAT ".." PTR_FORMAT "] not readable", p2i(start), p2i(end)); + } + return; + } - unsigned char* p = nm->code_begin(); - unsigned char* end = nm->code_end(); + if (is_abstract()) { + AbstractDisassembler::decode_abstract(start, end, st, Assembler::instr_maxlen()); + return; + } - nm->method()->method_holder()->name()->print_symbol_on(env.output()); - env.output()->print("."); - nm->method()->name()->print_symbol_on(env.output()); - nm->method()->signature()->print_symbol_on(env.output()); -#if INCLUDE_JVMCI +// Don't do that fancy stuff. If we just have two addresses, live with it +// and treat the memory contents as "amorphic" piece of code. +#if 0 + CodeBlob* cb = CodeCache::find_blob_unsafe(start); + if (cb != NULL) { + // If we have an CodeBlob at hand, + // call the specialized decoder directly. + decode(cb, st, c); + } else +#endif { - const char* jvmciName = nm->jvmci_name(); - if (jvmciName != NULL) { - env.output()->print(" (%s)", jvmciName); - } + // This seems to be just a chunk of memory. + decode_env env(start, end, st); + env.output()->print_cr("--------------------------------------------------------------------------------"); + env.decode_instructions(start, end); + env.output()->print_cr("--------------------------------------------------------------------------------"); } #endif - env.output()->print_cr(" [" PTR_FORMAT ", " PTR_FORMAT "] " JLONG_FORMAT " bytes", p2i(p), p2i(end), ((jlong)(end - p))); - - // Print constant table. - if (nm->consts_size() > 0) { - nm->print_nmethod_labels(env.output(), nm->consts_begin()); - int offset = 0; - for (address p = nm->consts_begin(); p < nm->consts_end(); p += 4, offset += 4) { - if ((offset % 8) == 0) { - env.output()->print_cr(" " PTR_FORMAT " (offset: %4d): " PTR32_FORMAT " " PTR64_FORMAT, p2i(p), offset, *((int32_t*) p), *((int64_t*) p)); - } else { - env.output()->print_cr(" " PTR_FORMAT " (offset: %4d): " PTR32_FORMAT, p2i(p), offset, *((int32_t*) p)); - } - } - } - - env.decode_instructions(p, end); } // To prevent excessive code expansion in the interpreter generator, we diff --git a/src/hotspot/share/compiler/disassembler.hpp b/src/hotspot/share/compiler/disassembler.hpp --- a/src/hotspot/share/compiler/disassembler.hpp +++ b/src/hotspot/share/compiler/disassembler.hpp @@ -25,7 +25,11 @@ #ifndef SHARE_COMPILER_DISASSEMBLER_HPP #define SHARE_COMPILER_DISASSEMBLER_HPP +#include "utilities/globalDefinitions.hpp" + +#include "asm/assembler.hpp" #include "asm/codeBuffer.hpp" +#include "compiler/abstractDisassembler.hpp" #include "runtime/globals.hpp" #include "utilities/macros.hpp" @@ -34,7 +38,8 @@ // The disassembler prints out assembly code annotated // with Java specific information. -class Disassembler { +// Disassembler inherits from AbstractDisassembler +class Disassembler : public AbstractDisassembler { friend class decode_env; private: // this is the type of the dll entry point: @@ -57,26 +62,58 @@ static void* _library; // bailout static bool _tried_to_load_library; + static bool _library_usable; // points to the decode function. static decode_func_virtual _decode_instructions_virtual; static decode_func _decode_instructions; - // tries to load library and return whether it succedded. - static bool load_library(); + + // tries to load library and return whether it succeeded. + // Allow (diagnostic) output redirection. + // No output at all if stream is NULL. Can be overridden + // with -Verbose flag, in which case output goes to tty. + static bool load_library(outputStream* st = NULL); + + // Check if the two addresses are on the same page. + static bool is_same_page(address a1, address a2) { + return (((uintptr_t)a1 ^ (uintptr_t)a2) & (~0x0fffUL)) == 0L; + } // Machine dependent stuff #include CPU_HEADER(disassembler) public: - static bool can_decode() { - ttyLocker tl; - return (_decode_instructions_virtual != NULL) || - (_decode_instructions != NULL) || - load_library(); + // We can always decode code blobs. + // Either we have a disassembler library available (successfully loaded) + // or we will resort to the abstract disassembler. This method informs + // about which decoding format is used. + // We can also enforce using the abstract disassembler. + static bool is_abstract() { + if (!_tried_to_load_library /* && !UseAbstractDisassembler */) { + load_library(); + } + return ! _library_usable /* || UseAbstractDisassembler */; // Not available until DecodeErrorFile is supported. } - static void decode(CodeBlob *cb, outputStream* st = NULL); - static void decode(nmethod* nm, outputStream* st = NULL); - static void decode(address begin, address end, outputStream* st = NULL, - CodeStrings c = CodeStrings(), ptrdiff_t offset = 0); + + // Check out if we are doing a live disassembly or a post-mortem + // disassembly where the binary data was loaded from a hs_err file. + static bool is_decode_error_file() { +// Activate once post-mortem disassembly (from hs-err file) is available. +#if 0 + return DecodeErrorFile && (strlen(DecodeErrorFile) != 0); +#else + return false; +#endif + } + + // Directly disassemble code buffer. + static void decode(CodeBuffer* cb, address start, address end, outputStream* st = NULL); + // Directly disassemble code blob. + static void decode(CodeBlob *cb, outputStream* st = NULL, CodeStrings c = CodeStrings()); + // Directly disassemble nmethod. + static void decode(nmethod* nm, outputStream* st = NULL, CodeStrings c = CodeStrings()); + // Disassemble an arbitrary memory range. + static void decode(address start, address end, outputStream* st = NULL, CodeStrings c = CodeStrings() /* , ptrdiff_t offset */); + static void _hook(const char* file, int line, class MacroAssembler* masm); // This functions makes it easy to generate comments in the generated diff --git a/src/hotspot/share/compiler/oopMap.cpp b/src/hotspot/share/compiler/oopMap.cpp --- a/src/hotspot/share/compiler/oopMap.cpp +++ b/src/hotspot/share/compiler/oopMap.cpp @@ -511,7 +511,7 @@ void ImmutableOopMap::print_on(outputStream* st) const { OopMapValue omv; - st->print("ImmutableOopMap{"); + st->print("ImmutableOopMap {"); for(OopMapStream oms(this); !oms.is_done(); oms.next()) { omv = oms.current(); omv.print_on(st); @@ -523,44 +523,51 @@ void OopMap::print_on(outputStream* st) const { OopMapValue omv; - st->print("OopMap{"); + st->print("OopMap {"); for(OopMapStream oms((OopMap*)this); !oms.is_done(); oms.next()) { omv = oms.current(); omv.print_on(st); } - st->print("off=%d}", (int) offset()); + // Print hex offset in addition. + st->print("off=%d/0x%x}", (int) offset(), (int) offset()); } void OopMap::print() const { print_on(tty); } void ImmutableOopMapSet::print_on(outputStream* st) const { const ImmutableOopMap* last = NULL; - for (int i = 0; i < _count; ++i) { + const int len = count(); + + st->print_cr("ImmutableOopMapSet contains %d OopMaps", len); + + for (int i = 0; i < len; i++) { const ImmutableOopMapPair* pair = pair_at(i); const ImmutableOopMap* map = pair->get_from(this); if (map != last) { st->cr(); map->print_on(st); - st->print("pc offsets: "); + st->print(" pc offsets: "); } last = map; st->print("%d ", pair->pc_offset()); } + st->cr(); } void ImmutableOopMapSet::print() const { print_on(tty); } void OopMapSet::print_on(outputStream* st) const { - int i, len = om_count(); + const int len = om_count(); - st->print_cr("OopMapSet contains %d OopMaps\n",len); + st->print_cr("OopMapSet contains %d OopMaps", len); - for( i = 0; i < len; i++) { + for( int i = 0; i < len; i++) { OopMap* m = at(i); st->print_cr("#%d ",i); m->print_on(st); st->cr(); } + st->cr(); } void OopMapSet::print() const { print_on(tty); } @@ -580,15 +587,17 @@ const ImmutableOopMap* ImmutableOopMapSet::find_map_at_offset(int pc_offset) const { ImmutableOopMapPair* pairs = get_pairs(); + ImmutableOopMapPair* last = NULL; - int i; - for (i = 0; i < _count; ++i) { + for (int i = 0; i < _count; ++i) { if (pairs[i].pc_offset() >= pc_offset) { + last = &pairs[i]; break; } } - ImmutableOopMapPair* last = &pairs[i]; + // Heal Coverity issue: potential index out of bounds access. + guarantee(last != NULL, "last may not be null"); assert(last->pc_offset() == pc_offset, "oopmap not found"); return last->get_from(this); } diff --git a/src/hotspot/share/opto/block.cpp b/src/hotspot/share/opto/block.cpp --- a/src/hotspot/share/opto/block.cpp +++ b/src/hotspot/share/opto/block.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -267,7 +267,7 @@ #ifndef PRODUCT void Block::dump_bidx(const Block* orig, outputStream* st) const { - if (_pre_order) st->print("B%d",_pre_order); + if (_pre_order) st->print("B%d", _pre_order); else st->print("N%d", head()->_idx); if (Verbose && orig != this) { @@ -291,30 +291,36 @@ } void Block::dump_head(const PhaseCFG* cfg, outputStream* st) const { - // Print the basic block + // Print the basic block. dump_bidx(this, st); - st->print(": #\t"); + st->print(": "); - // Print the incoming CFG edges and the outgoing CFG edges + // Print the outgoing CFG edges. + st->print("#\tout( "); for( uint i=0; i<_num_succs; i++ ) { non_connector_successor(i)->dump_bidx(_succs[i], st); st->print(" "); } - st->print("<- "); + + // Print the incoming CFG edges. + st->print(") <- "); if( head()->is_block_start() ) { + st->print("in( "); for (uint i=1; iget_block_for_node(s); p->dump_pred(cfg, p, st); } else { - while (!s->is_block_start()) + while (!s->is_block_start()) { s = s->in(0); + } st->print("N%d ", s->_idx ); } } + st->print(") "); } else { - st->print("BLOCK HEAD IS JUNK "); + st->print("BLOCK HEAD IS JUNK "); } // Print loop, if any @@ -327,12 +333,15 @@ while (bx->is_connector()) { bx = cfg->get_block_for_node(bx->pred(1)); } - st->print("\tLoop: B%d-B%d ", bhead->_pre_order, bx->_pre_order); + st->print("Loop( B%d-B%d ", bhead->_pre_order, bx->_pre_order); // Dump any loop-specific bits, especially for CountedLoops. loop->dump_spec(st); + st->print(")"); } else if (has_loop_alignment()) { - st->print(" top-of-loop"); + st->print("top-of-loop"); } + + // Print frequency and other optimization-relevant information st->print(" Freq: %g",_freq); if( Verbose || WizardMode ) { st->print(" IDom: %d/#%d", _idom ? _idom->_pre_order : 0, _dom_depth); diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -713,16 +713,19 @@ TraceTime t1("Total compilation time", &_t_totalCompilation, CITime, CITimeVerbose); TraceTime t2(NULL, &_t_methodCompilation, CITime, false); +#if defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_ABSTRACT_ASSEMBLY) + bool print_opto_assembly = directive->PrintOptoAssemblyOption; + // We can always print a disassembly, either abstract (hex dump) or + // with the help of a suitable hsdis library. Thus, we should not + // couple print_assembly and print_opto_assembly controls. + // But: always print opto and regular assembly on compile command 'print'. + bool print_assembly = directive->PrintAssemblyOption; + set_print_assembly(print_opto_assembly || print_assembly); +#else + set_print_assembly(false); // must initialize. +#endif + #ifndef PRODUCT - bool print_opto_assembly = directive->PrintOptoAssemblyOption; - if (!print_opto_assembly) { - bool print_assembly = directive->PrintAssemblyOption; - if (print_assembly && !Disassembler::can_decode()) { - tty->print_cr("PrintAssembly request changed to PrintOptoAssembly"); - print_opto_assembly = true; - } - } - set_print_assembly(print_opto_assembly); set_parsed_irreducible_loop(false); if (directive->ReplayInlineOption) { @@ -1027,6 +1030,8 @@ #ifndef PRODUCT set_print_assembly(PrintFrameConverterAssembly); set_parsed_irreducible_loop(false); +#else + set_print_assembly(false); // Must initialize. #endif set_has_irreducible_loop(false); // no loops @@ -2552,12 +2557,25 @@ //------------------------------dump_asm--------------------------------------- // Dump formatted assembly -#ifndef PRODUCT -void Compile::dump_asm(int *pcs, uint pc_limit) { +#if defined(SUPPORT_OPTO_ASSEMBLY) +void Compile::dump_asm_on(outputStream* st, int* pcs, uint pc_limit) { + + int pc_digits = 3; // #chars required for pc + int sb_chars = 3; // #chars for "start bundle" indicator + int tab_size = 8; + if (pcs != NULL) { + int max_pc = 0; + for (uint i = 0; i < pc_limit; i++) { + max_pc = (max_pc < pcs[i]) ? pcs[i] : max_pc; + } + pc_digits = ((max_pc < 4096) ? 3 : ((max_pc < 65536) ? 4 : ((max_pc < 65536*256) ? 6 : 8))); // #chars required for pc + } + int prefix_len = ((pc_digits + sb_chars + tab_size - 1)/tab_size)*tab_size; + bool cut_short = false; - tty->print_cr("#"); - tty->print("# "); _tf->dump(); tty->cr(); - tty->print_cr("#"); + st->print_cr("#"); + st->print("# "); _tf->dump_on(st); st->cr(); + st->print_cr("#"); // For all blocks int pc = 0x0; // Program counter @@ -2575,16 +2593,18 @@ continue; } n = block->head(); - if (pcs && n->_idx < pc_limit) { - tty->print("%3.3x ", pcs[n->_idx]); - } else { - tty->print(" "); + if ((pcs != NULL) && (n->_idx < pc_limit)) { + pc = pcs[n->_idx]; + st->print("%*.*x", pc_digits, pc_digits, pc); } - block->dump_head(_cfg); + st->fill_to(prefix_len); + block->dump_head(_cfg, st); if (block->is_connector()) { - tty->print_cr(" # Empty connector block"); + st->fill_to(prefix_len); + st->print_cr("# Empty connector block"); } else if (block->num_preds() == 2 && block->pred(1)->is_CatchProj() && block->pred(1)->as_CatchProj()->_con == CatchProjNode::fall_through_index) { - tty->print_cr(" # Block is sole successor of call"); + st->fill_to(prefix_len); + st->print_cr("# Block is sole successor of call"); } // For all instructions @@ -2620,34 +2640,39 @@ !n->is_top() && // Debug info table constants !(n->is_Con() && !n->is_Mach())// Debug info table constants ) { - if (pcs && n->_idx < pc_limit) - tty->print("%3.3x", pcs[n->_idx]); - else - tty->print(" "); - tty->print(" %c ", starts_bundle); + if ((pcs != NULL) && (n->_idx < pc_limit)) { + pc = pcs[n->_idx]; + st->print("%*.*x", pc_digits, pc_digits, pc); + } else { + st->fill_to(pc_digits); + } + st->print(" %c ", starts_bundle); starts_bundle = ' '; - tty->print("\t"); - n->format(_regalloc, tty); - tty->cr(); + st->fill_to(prefix_len); + n->format(_regalloc, st); + st->cr(); } // If we have an instruction with a delay slot, and have seen a delay, // then back up and print it if (valid_bundle_info(n) && node_bundling(n)->use_unconditional_delay()) { - assert(delay != NULL, "no unconditional delay instruction"); + // Coverity finding - Explicit null dereferenced. + guarantee(delay != NULL, "no unconditional delay instruction"); if (WizardMode) delay->dump(); if (node_bundling(delay)->starts_bundle()) starts_bundle = '+'; - if (pcs && n->_idx < pc_limit) - tty->print("%3.3x", pcs[n->_idx]); - else - tty->print(" "); - tty->print(" %c ", starts_bundle); + if ((pcs != NULL) && (n->_idx < pc_limit)) { + pc = pcs[n->_idx]; + st->print("%*.*x", pc_digits, pc_digits, pc); + } else { + st->fill_to(pc_digits); + } + st->print(" %c ", starts_bundle); starts_bundle = ' '; - tty->print("\t"); - delay->format(_regalloc, tty); - tty->cr(); + st->fill_to(prefix_len); + delay->format(_regalloc, st); + st->cr(); delay = NULL; } @@ -2656,19 +2681,13 @@ // Print the exception table for this offset _handler_table.print_subtable_for(pc); } + st->bol(); // Make sure we start on a new line } - - if (pcs && n->_idx < pc_limit) - tty->print_cr("%3.3x", pcs[n->_idx]); - else - tty->cr(); - + st->cr(); // one empty line between blocks assert(cut_short || delay == NULL, "no unconditional delay branch"); - } // End of per-block dump - tty->cr(); - - if (cut_short) tty->print_cr("*** disassembly is cut short ***"); + + if (cut_short) st->print_cr("*** disassembly is cut short ***"); } #endif diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -1348,7 +1348,13 @@ static void print_statistics() PRODUCT_RETURN; // Dump formatted assembly - void dump_asm(int *pcs = NULL, uint pc_limit = 0) PRODUCT_RETURN; +#if defined(SUPPORT_OPTO_ASSEMBLY) + void dump_asm_on(outputStream* ost, int* pcs, uint pc_limit); + void dump_asm(int* pcs = NULL, uint pc_limit = 0) { dump_asm_on(tty, pcs, pc_limit); } +#else + void dump_asm_on(outputStream* ost, int* pcs, uint pc_limit) { return; } + void dump_asm(int* pcs = NULL, uint pc_limit = 0) { return; } +#endif void dump_pc(int *pcs, int pc_limit, Node *n); // Verify ADLC assumptions during startup diff --git a/src/hotspot/share/opto/output.cpp b/src/hotspot/share/opto/output.cpp --- a/src/hotspot/share/opto/output.cpp +++ b/src/hotspot/share/opto/output.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1106,12 +1106,17 @@ #endif // Create an array of unused labels, one for each basic block, if printing is enabled -#ifndef PRODUCT +#if defined(SUPPORT_OPTO_ASSEMBLY) int *node_offsets = NULL; uint node_offset_limit = unique(); - if (print_assembly()) - node_offsets = NEW_RESOURCE_ARRAY(int, node_offset_limit); + if (print_assembly()) { + node_offsets = NEW_RESOURCE_ARRAY(int, node_offset_limit); + } + if (node_offsets != NULL) { + // We need to initialize. Unused array elements may contain garbage and mess up PrintOptoAssembly. + memset(node_offsets, 0, node_offset_limit*sizeof(int)); + } #endif NonSafepointEmitter non_safepoints(this); // emit non-safepoints lazily @@ -1381,9 +1386,10 @@ } // Save the offset for the listing -#ifndef PRODUCT - if (node_offsets && n->_idx < node_offset_limit) +#if defined(SUPPORT_OPTO_ASSEMBLY) + if ((node_offsets != NULL) && (n->_idx < node_offset_limit)) { node_offsets[n->_idx] = cb->insts_size(); + } #endif // "Normal" instruction case @@ -1430,9 +1436,10 @@ cb->set_insts_end(cb->insts_end() - Pipeline::instr_unit_size()); // Save the offset for the listing -#ifndef PRODUCT - if (node_offsets && delay_slot->_idx < node_offset_limit) +#if defined(SUPPORT_OPTO_ASSEMBLY) + if ((node_offsets != NULL) && (delay_slot->_idx < node_offset_limit)) { node_offsets[delay_slot->_idx] = cb->insts_size(); + } #endif // Support a SafePoint in the delay slot @@ -1541,7 +1548,14 @@ return; } -#ifndef PRODUCT +#if defined(SUPPORT_ABSTRACT_ASSEMBLY) || defined(SUPPORT_ASSEMBLY) || defined(SUPPORT_OPTO_ASSEMBLY) + if (print_assembly()) { + tty->cr(); + tty->print_cr("============================= C2-compiled nmethod =============================="); + } +#endif + +#if defined(SUPPORT_OPTO_ASSEMBLY) // Dump the assembly code, including basic-block numbers if (print_assembly()) { ttyLocker ttyl; // keep the following output all in one block @@ -1555,11 +1569,15 @@ ""); } if (method() != NULL) { + tty->print_cr("----------------------------------- MetaData -----------------------------------"); method()->print_metadata(); } else if (stub_name() != NULL) { - tty->print_cr("Generating RuntimeStub - %s", stub_name()); + tty->print_cr("----------------------------- RuntimeStub %s -------------------------------", stub_name()); } + tty->cr(); + tty->print_cr("--------------------------------- OptoAssembly ---------------------------------"); dump_asm(node_offsets, node_offset_limit); + tty->print_cr("--------------------------------------------------------------------------------"); if (xtty != NULL) { // print_metadata and dump_asm above may safepoint which makes us loose the ttylock. // Retake lock too make sure the end tag is coherent, and that xmlStream->pop_tag is done @@ -1570,7 +1588,6 @@ } } #endif - } void Compile::FillExceptionTables(uint cnt, uint *call_returns, uint *inct_starts, Label *blk_labels) { diff --git a/src/hotspot/share/runtime/stubCodeGenerator.cpp b/src/hotspot/share/runtime/stubCodeGenerator.cpp --- a/src/hotspot/share/runtime/stubCodeGenerator.cpp +++ b/src/hotspot/share/runtime/stubCodeGenerator.cpp @@ -60,7 +60,7 @@ st->print("%s", group()); st->print("::"); st->print("%s", name()); - st->print(" [" INTPTR_FORMAT ", " INTPTR_FORMAT "[ (%d bytes)", p2i(begin()), p2i(end()), size_in_bytes()); + st->print(" [" INTPTR_FORMAT ", " INTPTR_FORMAT "] (%d bytes)", p2i(begin()), p2i(end()), size_in_bytes()); } void StubCodeDesc::print() const { print_on(tty); } @@ -98,9 +98,12 @@ // of this stub. offset = cdesc->begin() - outer_cbuf->insts()->start(); #endif - cdesc->print(); + ttyLocker ttyl; + tty->print_cr("- - - [BEGIN] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + cdesc->print_on(tty); tty->cr(); - Disassembler::decode(cdesc->begin(), cdesc->end(), NULL, cs, offset); + Disassembler::decode(cdesc->begin(), cdesc->end(), tty, cs /*, offset */); + tty->print_cr("- - - [END] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); tty->cr(); } } diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -43,6 +43,26 @@ #define ATTRIBUTE_ALIGNED(x) #endif +// These are #defines to selectively turn on/off the Print(Opto)Assembly +// capabilities. Choices should be led by a tradeoff between +// code size and improved supportability. +// if PRINT_ASSEMBLY then PRINT_ABSTRACT_ASSEMBLY must be true as well +// to have a fallback in case hsdis is not available. +#if defined(PRODUCT) + #define SUPPORT_ABSTRACT_ASSEMBLY + #define SUPPORT_ASSEMBLY + #undef SUPPORT_OPTO_ASSEMBLY // Can't activate. In PRODUCT, many dump methods are missing. + #undef SUPPORT_DATA_STRUCTS // Of limited use. In PRODUCT, many print methods are empty. +#else + #define SUPPORT_ABSTRACT_ASSEMBLY + #define SUPPORT_ASSEMBLY + #define SUPPORT_OPTO_ASSEMBLY + #define SUPPORT_DATA_STRUCTS +#endif +#if defined(SUPPORT_ASSEMBLY) && !defined(SUPPORT_ABSTRACT_ASSEMBLY) + #define SUPPORT_ABSTRACT_ASSEMBLY +#endif + // This file holds all globally used constants & types, class (forward) // declarations and a few frequently used utility functions.