/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80:
*/ // Copyright 2011 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file.
// A Disassembler object is used to disassemble a block of code instruction by // instruction. The default implementation of the NameConverter object can be // overriden to modify register names or to do symbol lookup on addresses. // // The example below will disassemble a block of code and print it to stdout. // // disasm::NameConverter converter; // disasm::Disassembler d(converter); // for (uint8_t* pc = begin; pc < end;) { // disasm::EmbeddedVector<char, disasm::ReasonableBufferSize> buffer; // uint8_t* prev_pc = pc; // pc += d.InstructionDecode(buffer, pc); // printf("%p %08x %s\n", // prev_pc, *reinterpret_cast<int32_t*>(prev_pc), buffer); // } // // The Disassembler class also has a convenience method to disassemble a block // of code into a FILE*, meaning that the above functionality could also be // achieved by just calling Disassembler::Disassemble(stdout, begin, end);
// Decoder decodes and disassembles instructions into an output buffer. // It uses the converter to convert register names and call destinations into // more informative description. class Decoder { public:
Decoder(const disasm::NameConverter& converter, V8Vector<char> out_buffer)
: converter_(converter), out_buffer_(out_buffer), out_buffer_pos_(0) {
out_buffer_[out_buffer_pos_] = '\0';
}
~Decoder() {}
// Writes one disassembled instruction into 'buffer' (0-terminated). // Returns the length of the disassembled machine instruction in bytes. int InstructionDecode(uint8_t* instruction);
// Support for assertions in the Decoder formatting functions. #define STRING_STARTS_WITH(string, compare_string) \
(strncmp(string, compare_string, strlen(compare_string)) == 0)
// Append the ch to the output buffer. void Decoder::PrintChar(constchar ch) { out_buffer_[out_buffer_pos_++] = ch; }
// Append the str to the output buffer. void Decoder::Print(constchar* str) { char cur = *str++; while (cur != '\0' && (out_buffer_pos_ < int(out_buffer_.length() - 1))) {
PrintChar(cur);
cur = *str++;
}
out_buffer_[out_buffer_pos_] = 0;
}
int Decoder::switch_nf(Instruction* instr) { int nf = 0; switch (instr->InstructionBits() & kRvvNfMask) { case 0x20000000:
nf = 2; break; case 0x40000000:
nf = 3; break; case 0x60000000:
nf = 4; break; case 0x80000000:
nf = 5; break; case 0xa0000000:
nf = 6; break; case 0xc0000000:
nf = 7; break; case 0xe0000000:
nf = 8; break;
} return nf;
}
int Decoder::switch_sew(Instruction* instr) { int width = 0; if ((instr->InstructionBits() & kBaseOpcodeMask) != LOAD_FP &&
(instr->InstructionBits() & kBaseOpcodeMask) != STORE_FP) return -1; switch (instr->InstructionBits() & (kRvvWidthMask | kRvvMewMask)) { case 0x0:
width = 8; break; case 0x00005000:
width = 16; break; case 0x00006000:
width = 32; break; case 0x00007000:
width = 64; break; case 0x10000000:
width = 128; break; case 0x10005000:
width = 256; break; case 0x10006000:
width = 512; break; case 0x10007000:
width = 1024; break; default:
width = -1; break;
} return width;
}
// Handle all register based formatting in this function to reduce the // complexity of FormatOption. int Decoder::FormatRegister(Instruction* instr, constchar* format) {
MOZ_ASSERT(format[0] == 'r'); if (format[1] == 's') { // 'rs[12]: Rs register. if (format[2] == '1') { int reg = instr->Rs1Value();
PrintRegister(reg); return 3;
} elseif (format[2] == '2') { int reg = instr->Rs2Value();
PrintRegister(reg); return 3;
}
MOZ_CRASH();
} elseif (format[1] == 'd') { // 'rd: rd register. int reg = instr->RdValue();
PrintRegister(reg); return 2;
}
MOZ_CRASH();
}
// Handle all FPUregister based formatting in this function to reduce the // complexity of FormatOption. int Decoder::FormatFPURegisterOrRoundMode(Instruction* instr, constchar* format) {
MOZ_ASSERT(format[0] == 'f'); if (format[1] == 's') { // 'fs[1-3]: Rs register. if (format[2] == '1') { int reg = instr->Rs1Value();
PrintFPURegister(reg); return 3;
} elseif (format[2] == '2') { int reg = instr->Rs2Value();
PrintFPURegister(reg); return 3;
} elseif (format[2] == '3') { int reg = instr->Rs3Value();
PrintFPURegister(reg); return 3;
}
MOZ_CRASH();
} elseif (format[1] == 'd') { // 'fd: fd register. int reg = instr->RdValue();
PrintFPURegister(reg); return 2;
} elseif (format[1] == 'r') { // 'frm
MOZ_ASSERT(STRING_STARTS_WITH(format, "frm"));
PrintRoundingMode(instr); return 3;
}
MOZ_CRASH();
}
// Handle all C extension register based formatting in this function to reduce // the complexity of FormatOption. int Decoder::FormatRvcRegister(Instruction* instr, constchar* format) {
MOZ_ASSERT(format[0] == 'C');
MOZ_ASSERT(format[1] == 'r' || format[1] == 'f'); if (format[2] == 's') { // 'Crs[12]: Rs register. if (format[3] == '1') { if (format[4] == 's') { // 'Crs1s: 3-bits register int reg = instr->RvcRs1sValue(); if (format[1] == 'r') {
PrintRegister(reg);
} elseif (format[1] == 'f') {
PrintFPURegister(reg);
} return 5;
} int reg = instr->RvcRs1Value(); if (format[1] == 'r') {
PrintRegister(reg);
} elseif (format[1] == 'f') {
PrintFPURegister(reg);
} return 4;
} elseif (format[3] == '2') { if (format[4] == 's') { // 'Crs2s: 3-bits register int reg = instr->RvcRs2sValue(); if (format[1] == 'r') {
PrintRegister(reg);
} elseif (format[1] == 'f') {
PrintFPURegister(reg);
} return 5;
} int reg = instr->RvcRs2Value(); if (format[1] == 'r') {
PrintRegister(reg);
} elseif (format[1] == 'f') {
PrintFPURegister(reg);
} return 4;
}
MOZ_CRASH();
} elseif (format[2] == 'd') { // 'Crd: rd register. int reg = instr->RvcRdValue(); if (format[1] == 'r') {
PrintRegister(reg);
} elseif (format[1] == 'f') {
PrintFPURegister(reg);
} return 3;
}
MOZ_CRASH();
}
// FormatOption takes a formatting string and interprets it based on // the current instructions. The format string points to the first // character of the option string (the option escape has already been // consumed by the caller.) FormatOption returns the number of // characters that were consumed from the formatting string. int Decoder::FormatOption(Instruction* instr, constchar* format) { switch (format[0]) { case'C': { // `C extension if (format[1] == 'r' || format[1] == 'f') { return FormatRvcRegister(instr, format);
} elseif (format[1] == 'i') { return FormatRvcImm(instr, format);
} elseif (format[1] == 's') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "Cshamt"));
PrintRvcShamt(instr); return 6;
}
MOZ_CRASH();
} case'c': { // `csr: CSR registers if (format[1] == 's') { if (format[2] == 'r') {
PrintCSRReg(instr); return 3;
}
}
MOZ_CRASH();
} case'i': { // 'imm12, 'imm12x, 'imm20U, or 'imm20J: Immediates. if (format[3] == '1') { if (format[4] == '2') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "imm12")); if (format[5] == 'x') {
PrintImm12X(instr); return 6;
}
PrintImm12(instr); return 5;
}
} elseif (format[3] == '2' && format[4] == '0') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "imm20")); switch (format[5]) { case'U':
MOZ_ASSERT(STRING_STARTS_WITH(format, "imm20U"));
PrintImm20U(instr); break; case'J':
MOZ_ASSERT(STRING_STARTS_WITH(format, "imm20J"));
PrintImm20J(instr); break;
} return 6;
}
MOZ_CRASH();
} case'o': { // 'offB or 'offS: Offsets. if (format[3] == 'B') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "offB"));
PrintBranchOffset(instr); return 4;
} elseif (format[3] == 'S') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "offS"));
PrintStoreOffset(instr); return 4;
}
MOZ_CRASH();
} case'r': { // 'r: registers. return FormatRegister(instr, format);
} case'f': { // 'f: FPUregisters or `frm return FormatFPURegisterOrRoundMode(instr, format);
} case'a': { // 'a: Atomic acquire and release.
PrintAcquireRelease(instr); return 1;
} case'p': { // `pre
MOZ_ASSERT(STRING_STARTS_WITH(format, "pre"));
PrintMemoryOrder(instr, true); return 3;
} case's': { // 's32 or 's64: Shift amount. if (format[1] == '3') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "s32"));
PrintShamt32(instr); return 3;
} elseif (format[1] == '6') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "s64"));
PrintShamt(instr); return 3;
} elseif (format[1] == 'u') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "suc"));
PrintMemoryOrder(instr, false); return 3;
} elseif (format[1] == 'e') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "sew"));
PrintRvvSEW(instr); return 3;
} elseif (format[1] == 'i') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "simm5"));
PrintRvvSimm5(instr); return 5;
}
MOZ_CRASH();
} case'v': { if (format[1] == 'd') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "vd"));
PrintVd(instr); return 2;
} elseif (format[2] == '1') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "vs1"));
PrintVs1(instr); return 3;
} elseif (format[2] == '2') {
MOZ_ASSERT(STRING_STARTS_WITH(format, "vs2"));
PrintVs2(instr); return 3;
} else {
MOZ_ASSERT(STRING_STARTS_WITH(format, "vm"));
PrintRvvVm(instr); return 2;
}
} case'l': {
MOZ_ASSERT(STRING_STARTS_WITH(format, "lmul"));
PrintRvvLMUL(instr); return 4;
} case'u': { if (STRING_STARTS_WITH(format, "uimm5")) {
PrintRvvUimm5(instr); return 5;
} else {
MOZ_ASSERT(STRING_STARTS_WITH(format, "uimm"));
PrintUimm(instr); return 4;
}
} case't': { // 'target: target of branch instructions'
MOZ_ASSERT(STRING_STARTS_WITH(format, "target"));
PrintTarget(instr); return 6;
}
}
MOZ_CRASH();
}
// Format takes a formatting string for a whole instruction and prints it into // the output buffer. All escaped options are handed to FormatOption to be // parsed further. void Decoder::Format(Instruction* instr, constchar* format) { char cur = *format++; while ((cur != 0) && (out_buffer_pos_ < (out_buffer_.length() - 1))) { if (cur == '\'') { // Single quote is used as the formatting escape.
format += FormatOption(instr, format);
} else {
out_buffer_[out_buffer_pos_++] = cur;
}
cur = *format++;
}
out_buffer_[out_buffer_pos_] = '\0';
}
// The disassembler may end up decoding data inlined in the code. We do not want // it to crash if the data does not ressemble any known instruction. #define VERIFY(condition) \ if (!(condition)) { \
Unknown(instr); \ return; \
}
// For currently unimplemented decodings the disassembler calls Unknown(instr) // which will just print "unknown" of the instruction bits. void Decoder::Unknown(Instruction* instr) { Format(instr, "unknown"); }
// Print the register name according to the active name converter. void Decoder::PrintRegister(int reg) {
Print(converter_.NameOfCPURegister(reg));
}
void Decoder::PrintRs1(Instruction* instr) { int reg = instr->Rs1Value();
PrintRegister(reg);
}
void Decoder::PrintRs2(Instruction* instr) { int reg = instr->Rs2Value();
PrintRegister(reg);
}
void Decoder::PrintRd(Instruction* instr) { int reg = instr->RdValue();
PrintRegister(reg);
}
void Decoder::PrintUimm(Instruction* instr) { int val = instr->Rs1Value();
out_buffer_pos_ += SNPrintF(out_buffer_ + out_buffer_pos_, "0x%x", val);
}
void Decoder::PrintVs1(Instruction* instr) { int reg = instr->Vs1Value();
PrintVRegister(reg);
}
void Decoder::PrintVs2(Instruction* instr) { int reg = instr->Vs2Value();
PrintVRegister(reg);
}
void Decoder::PrintVd(Instruction* instr) { int reg = instr->VdValue();
PrintVRegister(reg);
}
// Print the FPUregister name according to the active name converter. void Decoder::PrintFPURegister(int freg) {
Print(converter_.NameOfXMMRegister(freg));
}
void Decoder::PrintFRs1(Instruction* instr) { int reg = instr->Rs1Value();
PrintFPURegister(reg);
}
void Decoder::PrintFRs2(Instruction* instr) { int reg = instr->Rs2Value();
PrintFPURegister(reg);
}
void Decoder::PrintFRs3(Instruction* instr) { int reg = instr->Rs3Value();
PrintFPURegister(reg);
}
void Decoder::PrintFRd(Instruction* instr) { int reg = instr->RdValue();
PrintFPURegister(reg);
}
void Decoder::PrintCSRReg(Instruction* instr) {
int32_t csr_reg = instr->CsrValue();
std::string s; switch (csr_reg) { case csr_fflags: // Floating-Point Accrued Exceptions (RW)
s = "csr_fflags"; break; case csr_frm: // Floating-Point Dynamic Rounding Mode (RW)
s = "csr_frm"; break; case csr_fcsr: // Floating-Point Control and Status Register (RW)
s = "csr_fcsr"; break; case csr_cycle:
s = "csr_cycle"; break; case csr_time:
s = "csr_time"; break; case csr_instret:
s = "csr_instret"; break; case csr_cycleh:
s = "csr_cycleh"; break; case csr_timeh:
s = "csr_timeh"; break; case csr_instreth:
s = "csr_instreth"; break; default:
MOZ_CRASH();
}
out_buffer_pos_ += SNPrintF(out_buffer_ + out_buffer_pos_, "%s", s.c_str());
}
void Decoder::PrintRoundingMode(Instruction* instr) { int frm = instr->RoundMode();
std::string s; switch (frm) { case RNE:
s = "RNE"; break; case RTZ:
s = "RTZ"; break; case RDN:
s = "RDN"; break; case RUP:
s = "RUP"; break; case RMM:
s = "RMM"; break; case DYN:
s = "DYN"; break; default:
MOZ_CRASH();
}
out_buffer_pos_ += SNPrintF(out_buffer_ + out_buffer_pos_, "%s", s.c_str());
}
void Decoder::PrintMemoryOrder(Instruction* instr, bool is_pred) { int memOrder = instr->MemoryOrder(is_pred);
std::string s; if ((memOrder & PSI) == PSI) {
s += "i";
} if ((memOrder & PSO) == PSO) {
s += "o";
} if ((memOrder & PSR) == PSR) {
s += "r";
} if ((memOrder & PSW) == PSW) {
s += "w";
}
out_buffer_pos_ += SNPrintF(out_buffer_ + out_buffer_pos_, "%s", s.c_str());
}
// Printing of instruction name. void Decoder::PrintInstructionName(Instruction* instr) {}
// RISCV Instruction Decode Routine void Decoder::DecodeRType(Instruction* instr) { switch (instr->InstructionBits() & kRTypeMask) { case RO_ADD:
Format(instr, "add 'rd, 'rs1, 'rs2"); break; case RO_SUB: if (instr->Rs1Value() == zero.code())
Format(instr, "neg 'rd, 'rs2"); else
Format(instr, "sub 'rd, 'rs1, 'rs2"); break; case RO_SLL:
Format(instr, "sll 'rd, 'rs1, 'rs2"); break; case RO_SLT: if (instr->Rs2Value() == zero.code())
Format(instr, "sltz 'rd, 'rs1"); elseif (instr->Rs1Value() == zero.code())
Format(instr, "sgtz 'rd, 'rs2"); else
Format(instr, "slt 'rd, 'rs1, 'rs2"); break; case RO_SLTU: if (instr->Rs1Value() == zero.code())
Format(instr, "snez 'rd, 'rs2"); else
Format(instr, "sltu 'rd, 'rs1, 'rs2"); break; case RO_XOR:
Format(instr, "xor 'rd, 'rs1, 'rs2"); break; case RO_SRL:
Format(instr, "srl 'rd, 'rs1, 'rs2"); break; case RO_SRA:
Format(instr, "sra 'rd, 'rs1, 'rs2"); break; case RO_OR:
Format(instr, "or 'rd, 'rs1, 'rs2"); break; case RO_AND:
Format(instr, "and 'rd, 'rs1, 'rs2"); break; #ifdef JS_CODEGEN_RISCV64 case RO_ADDW:
Format(instr, "addw 'rd, 'rs1, 'rs2"); break; case RO_SUBW: if (instr->Rs1Value() == zero.code())
Format(instr, "negw 'rd, 'rs2"); else
Format(instr, "subw 'rd, 'rs1, 'rs2"); break; case RO_SLLW:
Format(instr, "sllw 'rd, 'rs1, 'rs2"); break; case RO_SRLW:
Format(instr, "srlw 'rd, 'rs1, 'rs2"); break; case RO_SRAW:
Format(instr, "sraw 'rd, 'rs1, 'rs2"); break; #endif/* JS_CODEGEN_RISCV64 */ // TODO(riscv): Add RISCV M extension macro case RO_MUL:
Format(instr, "mul 'rd, 'rs1, 'rs2"); break; case RO_MULH:
Format(instr, "mulh 'rd, 'rs1, 'rs2"); break; case RO_MULHSU:
Format(instr, "mulhsu 'rd, 'rs1, 'rs2"); break; case RO_MULHU:
Format(instr, "mulhu 'rd, 'rs1, 'rs2"); break; case RO_DIV:
Format(instr, "div 'rd, 'rs1, 'rs2"); break; case RO_DIVU:
Format(instr, "divu 'rd, 'rs1, 'rs2"); break; case RO_REM:
Format(instr, "rem 'rd, 'rs1, 'rs2"); break; case RO_REMU:
Format(instr, "remu 'rd, 'rs1, 'rs2"); break; #ifdef JS_CODEGEN_RISCV64 case RO_MULW:
Format(instr, "mulw 'rd, 'rs1, 'rs2"); break; case RO_DIVW:
Format(instr, "divw 'rd, 'rs1, 'rs2"); break; case RO_DIVUW:
Format(instr, "divuw 'rd, 'rs1, 'rs2"); break; case RO_REMW:
Format(instr, "remw 'rd, 'rs1, 'rs2"); break; case RO_REMUW:
Format(instr, "remuw 'rd, 'rs1, 'rs2"); break; #endif/*JS_CODEGEN_RISCV64*/ // TODO(riscv): End Add RISCV M extension macro default: { switch (instr->BaseOpcode()) { case AMO:
DecodeRAType(instr); break; case OP_FP:
DecodeRFPType(instr); break; default:
UNSUPPORTED_RISCV();
}
}
}
}
void Decoder::DecodeRAType(Instruction* instr) { // TODO(riscv): Add macro for RISCV A extension // Special handling for A extension instructions because it uses func5 // For all A extension instruction, V8 simulator is pure sequential. No // Memory address lock or other synchronizaiton behaviors. switch (instr->InstructionBits() & kRATypeMask) { case RO_LR_W:
Format(instr, "lr.w'a 'rd, ('rs1)"); break; case RO_SC_W:
Format(instr, "sc.w'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOSWAP_W:
Format(instr, "amoswap.w'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOADD_W:
Format(instr, "amoadd.w'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOXOR_W:
Format(instr, "amoxor.w'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOAND_W:
Format(instr, "amoand.w'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOOR_W:
Format(instr, "amoor.w'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOMIN_W:
Format(instr, "amomin.w'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOMAX_W:
Format(instr, "amomax.w'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOMINU_W:
Format(instr, "amominu.w'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOMAXU_W:
Format(instr, "amomaxu.w'a 'rd, 'rs2, ('rs1)"); break; #ifdef JS_CODEGEN_RISCV64 case RO_LR_D:
Format(instr, "lr.d'a 'rd, ('rs1)"); break; case RO_SC_D:
Format(instr, "sc.d'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOSWAP_D:
Format(instr, "amoswap.d'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOADD_D:
Format(instr, "amoadd.d'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOXOR_D:
Format(instr, "amoxor.d'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOAND_D:
Format(instr, "amoand.d'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOOR_D:
Format(instr, "amoor.d'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOMIN_D:
Format(instr, "amomin.d'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOMAX_D:
Format(instr, "amoswap.d'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOMINU_D:
Format(instr, "amominu.d'a 'rd, 'rs2, ('rs1)"); break; case RO_AMOMAXU_D:
Format(instr, "amomaxu.d'a 'rd, 'rs2, ('rs1)"); break; #endif/*JS_CODEGEN_RISCV64*/ // TODO(riscv): End Add macro for RISCV A extension default: {
UNSUPPORTED_RISCV();
}
}
}
void Decoder::DecodeRFPType(Instruction* instr) { // OP_FP instructions (F/D) uses func7 first. Some further uses fun3 and rs2()
int Decoder::ConstantPoolSizeAt(uint8_t* instr_ptr) {
UNSUPPORTED_RISCV();
MOZ_CRASH();
}
// Disassemble the instruction at *instr_ptr into the output buffer. int Decoder::InstructionDecode(byte* instr_ptr) {
Instruction* instr = Instruction::At(instr_ptr); // Print raw instruction bytes.
out_buffer_pos_ += SNPrintF(out_buffer_ + out_buffer_pos_, "%08x ",
instr->InstructionBits()); switch (instr->InstructionType()) { case Instruction::kRType:
DecodeRType(instr); break; case Instruction::kR4Type:
DecodeR4Type(instr); break; case Instruction::kIType:
DecodeIType(instr); break; case Instruction::kSType:
DecodeSType(instr); break; case Instruction::kBType:
DecodeBType(instr); break; case Instruction::kUType:
DecodeUType(instr); break; case Instruction::kJType:
DecodeJType(instr); break; case Instruction::kCRType:
DecodeCRType(instr); break; case Instruction::kCAType:
DecodeCAType(instr); break; case Instruction::kCJType:
DecodeCJType(instr); break; case Instruction::kCIType:
DecodeCIType(instr); break; case Instruction::kCIWType:
DecodeCIWType(instr); break; case Instruction::kCSSType:
DecodeCSSType(instr); break; case Instruction::kCLType:
DecodeCLType(instr); break; case Instruction::kCSType:
DecodeCSType(instr); break; case Instruction::kCBType:
DecodeCBType(instr); break; #ifdef CAN_USE_RVV_INSTRUCTIONS case Instruction::kVType:
DecodeVType(instr); break; #endif default:
Format(instr, "UNSUPPORTED");
UNSUPPORTED_RISCV();
} return instr->InstructionSize();
}
constchar* NameConverter::NameInCode(uint8_t* addr) const { // The default name converter is called for unknown code. So we will not try // to access any memory. return"";
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.