summaryrefslogtreecommitdiffstats
path: root/js/src/jit/x86-shared/Disassembler-x86-shared.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /js/src/jit/x86-shared/Disassembler-x86-shared.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/jit/x86-shared/Disassembler-x86-shared.cpp')
-rw-r--r--js/src/jit/x86-shared/Disassembler-x86-shared.cpp568
1 files changed, 568 insertions, 0 deletions
diff --git a/js/src/jit/x86-shared/Disassembler-x86-shared.cpp b/js/src/jit/x86-shared/Disassembler-x86-shared.cpp
new file mode 100644
index 000000000..e033cfa5c
--- /dev/null
+++ b/js/src/jit/x86-shared/Disassembler-x86-shared.cpp
@@ -0,0 +1,568 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/Disassembler.h"
+
+#include "jit/x86-shared/Encoding-x86-shared.h"
+
+using namespace js;
+using namespace js::jit;
+using namespace js::jit::X86Encoding;
+using namespace js::jit::Disassembler;
+
+MOZ_COLD static bool REX_W(uint8_t rex) { return (rex >> 3) & 0x1; }
+MOZ_COLD static bool REX_R(uint8_t rex) { return (rex >> 2) & 0x1; }
+MOZ_COLD static bool REX_X(uint8_t rex) { return (rex >> 1) & 0x1; }
+MOZ_COLD static bool REX_B(uint8_t rex) { return (rex >> 0) & 0x1; }
+
+MOZ_COLD static uint8_t
+MakeREXFlags(bool w, bool r, bool x, bool b)
+{
+ uint8_t rex = (w << 3) | (r << 2) | (x << 1) | (b << 0);
+ MOZ_RELEASE_ASSERT(REX_W(rex) == w);
+ MOZ_RELEASE_ASSERT(REX_R(rex) == r);
+ MOZ_RELEASE_ASSERT(REX_X(rex) == x);
+ MOZ_RELEASE_ASSERT(REX_B(rex) == b);
+ return rex;
+}
+
+MOZ_COLD static ModRmMode
+ModRM_Mode(uint8_t modrm)
+{
+ return ModRmMode((modrm >> 6) & 0x3);
+}
+
+MOZ_COLD static uint8_t
+ModRM_Reg(uint8_t modrm)
+{
+ return (modrm >> 3) & 0x7;
+}
+
+MOZ_COLD static uint8_t
+ModRM_RM(uint8_t modrm)
+{
+ return (modrm >> 0) & 0x7;
+}
+
+MOZ_COLD static bool
+ModRM_hasSIB(uint8_t modrm)
+{
+ return ModRM_Mode(modrm) != ModRmRegister && ModRM_RM(modrm) == hasSib;
+}
+MOZ_COLD static bool
+ModRM_hasDisp8(uint8_t modrm)
+{
+ return ModRM_Mode(modrm) == ModRmMemoryDisp8;
+}
+MOZ_COLD static bool
+ModRM_hasRIP(uint8_t modrm)
+{
+#ifdef JS_CODEGEN_X64
+ return ModRM_Mode(modrm) == ModRmMemoryNoDisp && ModRM_RM(modrm) == noBase;
+#else
+ return false;
+#endif
+}
+MOZ_COLD static bool
+ModRM_hasDisp32(uint8_t modrm)
+{
+ return ModRM_Mode(modrm) == ModRmMemoryDisp32 ||
+ ModRM_hasRIP(modrm);
+}
+
+MOZ_COLD static uint8_t
+SIB_SS(uint8_t sib)
+{
+ return (sib >> 6) & 0x3;
+}
+
+MOZ_COLD static uint8_t
+SIB_Index(uint8_t sib)
+{
+ return (sib >> 3) & 0x7;
+}
+
+MOZ_COLD static uint8_t
+SIB_Base(uint8_t sib)
+{
+ return (sib >> 0) & 0x7;
+}
+
+MOZ_COLD static bool
+SIB_hasRIP(uint8_t sib)
+{
+ return SIB_Base(sib) == noBase && SIB_Index(sib) == noIndex;
+}
+
+MOZ_COLD static bool
+HasRIP(uint8_t modrm, uint8_t sib, uint8_t rex)
+{
+ return ModRM_hasRIP(modrm) && SIB_hasRIP(sib);
+}
+
+MOZ_COLD static bool
+HasDisp8(uint8_t modrm)
+{
+ return ModRM_hasDisp8(modrm);
+}
+
+MOZ_COLD static bool
+HasDisp32(uint8_t modrm, uint8_t sib)
+{
+ return ModRM_hasDisp32(modrm) ||
+ (SIB_Base(sib) == noBase &&
+ SIB_Index(sib) == noIndex &&
+ ModRM_Mode(modrm) == ModRmMemoryNoDisp);
+}
+
+MOZ_COLD static uint32_t
+Reg(uint8_t modrm, uint8_t sib, uint8_t rex)
+{
+ return ModRM_Reg(modrm) | (REX_R(rex) << 3);
+}
+
+MOZ_COLD static bool
+HasBase(uint8_t modrm, uint8_t sib)
+{
+ return !ModRM_hasSIB(modrm) ||
+ SIB_Base(sib) != noBase ||
+ SIB_Index(sib) != noIndex ||
+ ModRM_Mode(modrm) != ModRmMemoryNoDisp;
+}
+
+MOZ_COLD static RegisterID
+DecodeBase(uint8_t modrm, uint8_t sib, uint8_t rex)
+{
+ return HasBase(modrm, sib)
+ ? RegisterID((ModRM_hasSIB(modrm) ? SIB_Base(sib) : ModRM_RM(modrm)) | (REX_B(rex) << 3))
+ : invalid_reg;
+}
+
+MOZ_COLD static RegisterID
+DecodeIndex(uint8_t modrm, uint8_t sib, uint8_t rex)
+{
+ RegisterID index = RegisterID(SIB_Index(sib) | (REX_X(rex) << 3));
+ return ModRM_hasSIB(modrm) && index != noIndex ? index : invalid_reg;
+}
+
+MOZ_COLD static uint32_t
+DecodeScale(uint8_t modrm, uint8_t sib, uint8_t rex)
+{
+ return ModRM_hasSIB(modrm) ? SIB_SS(sib) : 0;
+}
+
+#define PackOpcode(op0, op1, op2) ((op0) | ((op1) << 8) | ((op2) << 16))
+#define Pack2ByteOpcode(op1) PackOpcode(OP_2BYTE_ESCAPE, op1, 0)
+#define Pack3ByteOpcode(op1, op2) PackOpcode(OP_2BYTE_ESCAPE, op1, op2)
+
+uint8_t*
+js::jit::Disassembler::DisassembleHeapAccess(uint8_t* ptr, HeapAccess* access)
+{
+ VexOperandType type = VEX_PS;
+ uint32_t opcode = OP_HLT;
+ uint8_t modrm = 0;
+ uint8_t sib = 0;
+ uint8_t rex = 0;
+ int32_t disp = 0;
+ int32_t imm = 0;
+ bool haveImm = false;
+ int opsize = 4;
+
+ // Legacy prefixes
+ switch (*ptr) {
+ case PRE_LOCK:
+ case PRE_PREDICT_BRANCH_NOT_TAKEN: // (obsolete), aka %cs
+ case 0x3E: // aka predict-branch-taken (obsolete)
+ case 0x36: // %ss
+ case 0x26: // %es
+ case 0x64: // %fs
+ case 0x65: // %gs
+ case 0x67: // address-size override
+ MOZ_CRASH("Unable to disassemble instruction");
+ case PRE_SSE_F2: // aka REPNZ/REPNE
+ type = VEX_SD;
+ ptr++;
+ break;
+ case PRE_SSE_F3: // aka REP/REPE/REPZ
+ type = VEX_SS;
+ ptr++;
+ break;
+ case PRE_SSE_66: // aka PRE_OPERAND_SIZE
+ type = VEX_PD;
+ opsize = 2;
+ ptr++;
+ break;
+ default:
+ break;
+ }
+
+ // REX and VEX prefixes
+ {
+ int x = 0, b = 0, m = 1, w = 0;
+ int r, l, p;
+ switch (*ptr) {
+#ifdef JS_CODEGEN_X64
+ case PRE_REX | 0x0: case PRE_REX | 0x1: case PRE_REX | 0x2: case PRE_REX | 0x3:
+ case PRE_REX | 0x4: case PRE_REX | 0x5: case PRE_REX | 0x6: case PRE_REX | 0x7:
+ case PRE_REX | 0x8: case PRE_REX | 0x9: case PRE_REX | 0xa: case PRE_REX | 0xb:
+ case PRE_REX | 0xc: case PRE_REX | 0xd: case PRE_REX | 0xe: case PRE_REX | 0xf:
+ rex = *ptr++ & 0xf;
+ goto rex_done;
+#endif
+ case PRE_VEX_C4: {
+ if (type != VEX_PS)
+ MOZ_CRASH("Unable to disassemble instruction");
+ ++ptr;
+ uint8_t c4a = *ptr++ ^ 0xe0;
+ uint8_t c4b = *ptr++ ^ 0x78;
+ r = (c4a >> 7) & 0x1;
+ x = (c4a >> 6) & 0x1;
+ b = (c4a >> 5) & 0x1;
+ m = (c4a >> 0) & 0x1f;
+ w = (c4b >> 7) & 0x1;
+ l = (c4b >> 2) & 0x1;
+ p = (c4b >> 0) & 0x3;
+ break;
+ }
+ case PRE_VEX_C5: {
+ if (type != VEX_PS)
+ MOZ_CRASH("Unable to disassemble instruction");
+ ++ptr;
+ uint8_t c5 = *ptr++ ^ 0xf8;
+ r = (c5 >> 7) & 0x1;
+ l = (c5 >> 2) & 0x1;
+ p = (c5 >> 0) & 0x3;
+ break;
+ }
+ default:
+ goto rex_done;
+ }
+ type = VexOperandType(p);
+ rex = MakeREXFlags(w, r, x, b);
+ switch (m) {
+ case 0x1:
+ opcode = Pack2ByteOpcode(*ptr++);
+ goto opcode_done;
+ case 0x2:
+ opcode = Pack3ByteOpcode(ESCAPE_38, *ptr++);
+ goto opcode_done;
+ case 0x3:
+ opcode = Pack3ByteOpcode(ESCAPE_3A, *ptr++);
+ goto opcode_done;
+ default:
+ MOZ_CRASH("Unable to disassemble instruction");
+ }
+ if (l != 0) // 256-bit SIMD
+ MOZ_CRASH("Unable to disassemble instruction");
+ }
+ rex_done:;
+ if (REX_W(rex))
+ opsize = 8;
+
+ // Opcode.
+ opcode = *ptr++;
+ switch (opcode) {
+#ifdef JS_CODEGEN_X64
+ case OP_PUSH_EAX + 0: case OP_PUSH_EAX + 1: case OP_PUSH_EAX + 2: case OP_PUSH_EAX + 3:
+ case OP_PUSH_EAX + 4: case OP_PUSH_EAX + 5: case OP_PUSH_EAX + 6: case OP_PUSH_EAX + 7:
+ case OP_POP_EAX + 0: case OP_POP_EAX + 1: case OP_POP_EAX + 2: case OP_POP_EAX + 3:
+ case OP_POP_EAX + 4: case OP_POP_EAX + 5: case OP_POP_EAX + 6: case OP_POP_EAX + 7:
+ case OP_PUSH_Iz:
+ case OP_PUSH_Ib:
+ opsize = 8;
+ break;
+#endif
+ case OP_2BYTE_ESCAPE:
+ opcode |= *ptr << 8;
+ switch (*ptr++) {
+ case ESCAPE_38:
+ case ESCAPE_3A:
+ opcode |= *ptr++ << 16;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ opcode_done:;
+
+ // ModR/M
+ modrm = *ptr++;
+
+ // SIB
+ if (ModRM_hasSIB(modrm))
+ sib = *ptr++;
+
+ // Address Displacement
+ if (HasDisp8(modrm)) {
+ disp = int8_t(*ptr++);
+ } else if (HasDisp32(modrm, sib)) {
+ memcpy(&disp, ptr, sizeof(int32_t));
+ ptr += sizeof(int32_t);
+ }
+
+ // Immediate operand
+ switch (opcode) {
+ case OP_PUSH_Ib:
+ case OP_IMUL_GvEvIb:
+ case OP_GROUP1_EbIb:
+ case OP_GROUP1_EvIb:
+ case OP_TEST_EAXIb:
+ case OP_GROUP2_EvIb:
+ case OP_GROUP11_EvIb:
+ case OP_GROUP3_EbIb:
+ case Pack2ByteOpcode(OP2_PSHUFD_VdqWdqIb):
+ case Pack2ByteOpcode(OP2_PSLLD_UdqIb): // aka OP2_PSRAD_UdqIb, aka OP2_PSRLD_UdqIb
+ case Pack2ByteOpcode(OP2_PEXTRW_GdUdIb):
+ case Pack2ByteOpcode(OP2_SHUFPS_VpsWpsIb):
+ case Pack3ByteOpcode(ESCAPE_3A, OP3_PEXTRD_EdVdqIb):
+ case Pack3ByteOpcode(ESCAPE_3A, OP3_BLENDPS_VpsWpsIb):
+ case Pack3ByteOpcode(ESCAPE_3A, OP3_PINSRD_VdqEdIb):
+ // 8-bit signed immediate
+ imm = int8_t(*ptr++);
+ haveImm = true;
+ break;
+ case OP_RET_Iz:
+ // 16-bit unsigned immediate
+ memcpy(&imm, ptr, sizeof(int16_t));
+ ptr += sizeof(int16_t);
+ haveImm = true;
+ break;
+ case OP_ADD_EAXIv:
+ case OP_OR_EAXIv:
+ case OP_AND_EAXIv:
+ case OP_SUB_EAXIv:
+ case OP_XOR_EAXIv:
+ case OP_CMP_EAXIv:
+ case OP_PUSH_Iz:
+ case OP_IMUL_GvEvIz:
+ case OP_GROUP1_EvIz:
+ case OP_TEST_EAXIv:
+ case OP_MOV_EAXIv:
+ case OP_GROUP3_EvIz:
+ // 32-bit signed immediate
+ memcpy(&imm, ptr, sizeof(int32_t));
+ ptr += sizeof(int32_t);
+ haveImm = true;
+ break;
+ case OP_GROUP11_EvIz:
+ // opsize-sized signed immediate
+ memcpy(&imm, ptr, opsize);
+ imm = (imm << (32 - opsize * 8)) >> (32 - opsize * 8);
+ ptr += opsize;
+ haveImm = true;
+ break;
+ default:
+ break;
+ }
+
+ // Interpret the opcode.
+ if (HasRIP(modrm, sib, rex))
+ MOZ_CRASH("Unable to disassemble instruction");
+
+ size_t memSize = 0;
+ OtherOperand otherOperand(imm);
+ HeapAccess::Kind kind = HeapAccess::Unknown;
+ RegisterID gpr(RegisterID(Reg(modrm, sib, rex)));
+ XMMRegisterID xmm(XMMRegisterID(Reg(modrm, sib, rex)));
+ ComplexAddress addr(disp,
+ DecodeBase(modrm, sib, rex),
+ DecodeIndex(modrm, sib, rex),
+ DecodeScale(modrm, sib, rex));
+ switch (opcode) {
+ case OP_GROUP11_EvIb:
+ if (gpr != RegisterID(GROUP11_MOV))
+ MOZ_CRASH("Unable to disassemble instruction");
+ MOZ_RELEASE_ASSERT(haveImm);
+ memSize = 1;
+ kind = HeapAccess::Store;
+ break;
+ case OP_GROUP11_EvIz:
+ if (gpr != RegisterID(GROUP11_MOV))
+ MOZ_CRASH("Unable to disassemble instruction");
+ MOZ_RELEASE_ASSERT(haveImm);
+ memSize = opsize;
+ kind = HeapAccess::Store;
+ break;
+ case OP_MOV_GvEv:
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(gpr);
+ memSize = opsize;
+ kind = HeapAccess::Load;
+ break;
+ case OP_MOV_GvEb:
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(gpr);
+ memSize = 1;
+ kind = HeapAccess::Load;
+ break;
+ case OP_MOV_EvGv:
+ if (!haveImm)
+ otherOperand = OtherOperand(gpr);
+ memSize = opsize;
+ kind = HeapAccess::Store;
+ break;
+ case OP_MOV_EbGv:
+ if (!haveImm)
+ otherOperand = OtherOperand(gpr);
+ memSize = 1;
+ kind = HeapAccess::Store;
+ break;
+ case Pack2ByteOpcode(OP2_MOVZX_GvEb):
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(gpr);
+ memSize = 1;
+ kind = HeapAccess::Load;
+ break;
+ case Pack2ByteOpcode(OP2_MOVZX_GvEw):
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(gpr);
+ memSize = 2;
+ kind = HeapAccess::Load;
+ break;
+ case Pack2ByteOpcode(OP2_MOVSX_GvEb):
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(gpr);
+ memSize = 1;
+ kind = opsize == 8 ? HeapAccess::LoadSext64 : HeapAccess::LoadSext32;
+ break;
+ case Pack2ByteOpcode(OP2_MOVSX_GvEw):
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(gpr);
+ memSize = 2;
+ kind = opsize == 8 ? HeapAccess::LoadSext64 : HeapAccess::LoadSext32;
+ break;
+#ifdef JS_CODEGEN_X64
+ case OP_MOVSXD_GvEv:
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(gpr);
+ memSize = 4;
+ kind = HeapAccess::LoadSext64;
+ break;
+#endif // JS_CODEGEN_X64
+ case Pack2ByteOpcode(OP2_MOVDQ_VdqWdq): // aka OP2_MOVDQ_VsdWsd
+ case Pack2ByteOpcode(OP2_MOVAPS_VsdWsd):
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(xmm);
+ memSize = 16;
+ kind = HeapAccess::Load;
+ break;
+ case Pack2ByteOpcode(OP2_MOVSD_VsdWsd): // aka OP2_MOVPS_VpsWps
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(xmm);
+ switch (type) {
+ case VEX_SS: memSize = 4; break;
+ case VEX_SD: memSize = 8; break;
+ case VEX_PS:
+ case VEX_PD: memSize = 16; break;
+ default: MOZ_CRASH("Unexpected VEX type");
+ }
+ kind = HeapAccess::Load;
+ break;
+ case Pack2ByteOpcode(OP2_MOVDQ_WdqVdq):
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(xmm);
+ memSize = 16;
+ kind = HeapAccess::Store;
+ break;
+ case Pack2ByteOpcode(OP2_MOVSD_WsdVsd): // aka OP2_MOVPS_WpsVps
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(xmm);
+ switch (type) {
+ case VEX_SS: memSize = 4; break;
+ case VEX_SD: memSize = 8; break;
+ case VEX_PS:
+ case VEX_PD: memSize = 16; break;
+ default: MOZ_CRASH("Unexpected VEX type");
+ }
+ kind = HeapAccess::Store;
+ break;
+ case Pack2ByteOpcode(OP2_MOVD_VdEd):
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(xmm);
+ switch (type) {
+ case VEX_PD: memSize = 4; break;
+ default: MOZ_CRASH("Unexpected VEX type");
+ }
+ kind = HeapAccess::Load;
+ break;
+ case Pack2ByteOpcode(OP2_MOVQ_WdVd):
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(xmm);
+ switch (type) {
+ case VEX_PD: memSize = 8; break;
+ default: MOZ_CRASH("Unexpected VEX type");
+ }
+ kind = HeapAccess::Store;
+ break;
+ case Pack2ByteOpcode(OP2_MOVD_EdVd): // aka OP2_MOVQ_VdWd
+ MOZ_RELEASE_ASSERT(!haveImm);
+ otherOperand = OtherOperand(xmm);
+ switch (type) {
+ case VEX_SS: memSize = 8; kind = HeapAccess::Load; break;
+ case VEX_PD: memSize = 4; kind = HeapAccess::Store; break;
+ default: MOZ_CRASH("Unexpected VEX type");
+ }
+ break;
+ default:
+ MOZ_CRASH("Unable to disassemble instruction");
+ }
+
+ *access = HeapAccess(kind, memSize, addr, otherOperand);
+ return ptr;
+}
+
+#ifdef DEBUG
+void
+js::jit::Disassembler::DumpHeapAccess(const HeapAccess& access)
+{
+ switch (access.kind()) {
+ case HeapAccess::Store: fprintf(stderr, "store"); break;
+ case HeapAccess::Load: fprintf(stderr, "load"); break;
+ case HeapAccess::LoadSext32: fprintf(stderr, "loadSext32"); break;
+ case HeapAccess::LoadSext64: fprintf(stderr, "loadSext64"); break;
+ default: fprintf(stderr, "unknown"); break;
+ }
+ fprintf(stderr, "%u ", unsigned(access.size()));
+
+ switch (access.otherOperand().kind()) {
+ case OtherOperand::Imm:
+ fprintf(stderr, "imm %d", access.otherOperand().imm());
+ break;
+ case OtherOperand::GPR:
+ fprintf(stderr, "gpr %s", X86Encoding::GPRegName(access.otherOperand().gpr()));
+ break;
+ case OtherOperand::FPR:
+ fprintf(stderr, "fpr %s", X86Encoding::XMMRegName(access.otherOperand().fpr()));
+ break;
+ default: fprintf(stderr, "unknown");
+ }
+
+ fprintf(stderr, " @ ");
+
+ if (access.address().isPCRelative()) {
+ fprintf(stderr, MEM_o32r " ", ADDR_o32r(access.address().disp()));
+ } else if (access.address().hasIndex()) {
+ if (access.address().hasBase()) {
+ fprintf(stderr, MEM_obs " ",
+ ADDR_obs(access.address().disp(), access.address().base(),
+ access.address().index(), access.address().scale()));
+ } else {
+ fprintf(stderr, MEM_os " ",
+ ADDR_os(access.address().disp(),
+ access.address().index(), access.address().scale()));
+ }
+ } else if (access.address().hasBase()) {
+ fprintf(stderr, MEM_ob " ", ADDR_ob(access.address().disp(), access.address().base()));
+ } else {
+ fprintf(stderr, MEM_o " ", ADDR_o(access.address().disp()));
+ }
+
+ fprintf(stderr, "\n");
+}
+#endif