| /* |
| * 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 |
| * 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 "precompiled.hpp" |
| #include "jvm.h" |
| #include "classfile/compactHashtable.inline.hpp" |
| #include "classfile/javaClasses.hpp" |
| #include "logging/logMessage.hpp" |
| #include "memory/heapShared.inline.hpp" |
| #include "memory/metadataFactory.hpp" |
| #include "memory/metaspaceShared.hpp" |
| #include "oops/compressedOops.inline.hpp" |
| #include "runtime/vmThread.hpp" |
| #include "utilities/numberSeq.hpp" |
| #include <sys/stat.h> |
| |
| ///////////////////////////////////////////////////// |
| // |
| // The compact hash table writer implementations |
| // |
| CompactHashtableWriter::CompactHashtableWriter(int num_buckets, |
| CompactHashtableStats* stats) { |
| assert(DumpSharedSpaces, "dump-time only"); |
| assert(num_buckets > 0, "no buckets"); |
| _num_buckets = num_buckets; |
| _num_entries = 0; |
| _buckets = NEW_C_HEAP_ARRAY(GrowableArray<Entry>*, _num_buckets, mtSymbol); |
| for (int i=0; i<_num_buckets; i++) { |
| _buckets[i] = new (ResourceObj::C_HEAP, mtSymbol) GrowableArray<Entry>(0, true, mtSymbol); |
| } |
| |
| _stats = stats; |
| _compact_buckets = NULL; |
| _compact_entries = NULL; |
| _num_empty_buckets = 0; |
| _num_value_only_buckets = 0; |
| _num_other_buckets = 0; |
| } |
| |
| CompactHashtableWriter::~CompactHashtableWriter() { |
| for (int index = 0; index < _num_buckets; index++) { |
| GrowableArray<Entry>* bucket = _buckets[index]; |
| delete bucket; |
| } |
| |
| FREE_C_HEAP_ARRAY(GrowableArray<Entry>*, _buckets); |
| } |
| |
| // Add a symbol entry to the temporary hash table |
| void CompactHashtableWriter::add(unsigned int hash, u4 value) { |
| int index = hash % _num_buckets; |
| _buckets[index]->append_if_missing(Entry(hash, value)); |
| _num_entries++; |
| } |
| |
| void CompactHashtableWriter::allocate_table() { |
| int entries_space = 0; |
| for (int index = 0; index < _num_buckets; index++) { |
| GrowableArray<Entry>* bucket = _buckets[index]; |
| int bucket_size = bucket->length(); |
| if (bucket_size == 1) { |
| entries_space++; |
| } else { |
| entries_space += 2 * bucket_size; |
| } |
| } |
| |
| if (entries_space & ~BUCKET_OFFSET_MASK) { |
| vm_exit_during_initialization("CompactHashtableWriter::allocate_table: Overflow! " |
| "Too many entries."); |
| } |
| |
| _compact_buckets = MetaspaceShared::new_ro_array<u4>(_num_buckets + 1); |
| _compact_entries = MetaspaceShared::new_ro_array<u4>(entries_space); |
| |
| _stats->bucket_count = _num_buckets; |
| _stats->bucket_bytes = _compact_buckets->size() * BytesPerWord; |
| _stats->hashentry_count = _num_entries; |
| _stats->hashentry_bytes = _compact_entries->size() * BytesPerWord; |
| } |
| |
| // Write the compact table's buckets |
| void CompactHashtableWriter::dump_table(NumberSeq* summary) { |
| u4 offset = 0; |
| for (int index = 0; index < _num_buckets; index++) { |
| GrowableArray<Entry>* bucket = _buckets[index]; |
| int bucket_size = bucket->length(); |
| if (bucket_size == 1) { |
| // bucket with one entry is compacted and only has the symbol offset |
| _compact_buckets->at_put(index, BUCKET_INFO(offset, VALUE_ONLY_BUCKET_TYPE)); |
| |
| Entry ent = bucket->at(0); |
| _compact_entries->at_put(offset++, ent.value()); |
| _num_value_only_buckets++; |
| } else { |
| // regular bucket, each entry is a symbol (hash, offset) pair |
| _compact_buckets->at_put(index, BUCKET_INFO(offset, REGULAR_BUCKET_TYPE)); |
| |
| for (int i=0; i<bucket_size; i++) { |
| Entry ent = bucket->at(i); |
| _compact_entries->at_put(offset++, u4(ent.hash())); // write entry hash |
| _compact_entries->at_put(offset++, ent.value()); |
| } |
| if (bucket_size == 0) { |
| _num_empty_buckets++; |
| } else { |
| _num_other_buckets++; |
| } |
| } |
| summary->add(bucket_size); |
| } |
| |
| // Mark the end of the buckets |
| _compact_buckets->at_put(_num_buckets, BUCKET_INFO(offset, TABLEEND_BUCKET_TYPE)); |
| assert(offset == (u4)_compact_entries->length(), "sanity"); |
| } |
| |
| |
| // Write the compact table |
| void CompactHashtableWriter::dump(SimpleCompactHashtable *cht, const char* table_name) { |
| NumberSeq summary; |
| allocate_table(); |
| dump_table(&summary); |
| |
| int table_bytes = _stats->bucket_bytes + _stats->hashentry_bytes; |
| address base_address = address(MetaspaceShared::shared_rs()->base()); |
| cht->init(base_address, _num_entries, _num_buckets, |
| _compact_buckets->data(), _compact_entries->data()); |
| |
| LogMessage(cds, hashtables) msg; |
| if (msg.is_info()) { |
| double avg_cost = 0.0; |
| if (_num_entries > 0) { |
| avg_cost = double(table_bytes)/double(_num_entries); |
| } |
| msg.info("Shared %s table stats -------- base: " PTR_FORMAT, |
| table_name, (intptr_t)base_address); |
| msg.info("Number of entries : %9d", _num_entries); |
| msg.info("Total bytes used : %9d", table_bytes); |
| msg.info("Average bytes per entry : %9.3f", avg_cost); |
| msg.info("Average bucket size : %9.3f", summary.avg()); |
| msg.info("Variance of bucket size : %9.3f", summary.variance()); |
| msg.info("Std. dev. of bucket size: %9.3f", summary.sd()); |
| msg.info("Empty buckets : %9d", _num_empty_buckets); |
| msg.info("Value_Only buckets : %9d", _num_value_only_buckets); |
| msg.info("Other buckets : %9d", _num_other_buckets); |
| } |
| } |
| |
| ///////////////////////////////////////////////////////////// |
| // |
| // Customization for dumping Symbol and String tables |
| |
| void CompactSymbolTableWriter::add(unsigned int hash, Symbol *symbol) { |
| uintx deltax = MetaspaceShared::object_delta(symbol); |
| // When the symbols are stored into the archive, we already check that |
| // they won't be more than MAX_SHARED_DELTA from the base address, or |
| // else the dumping would have been aborted. |
| assert(deltax <= MAX_SHARED_DELTA, "must not be"); |
| u4 delta = u4(deltax); |
| |
| CompactHashtableWriter::add(hash, delta); |
| } |
| |
| void CompactStringTableWriter::add(unsigned int hash, oop string) { |
| CompactHashtableWriter::add(hash, CompressedOops::encode(string)); |
| } |
| |
| void CompactSymbolTableWriter::dump(CompactHashtable<Symbol*, char> *cht) { |
| CompactHashtableWriter::dump(cht, "symbol"); |
| } |
| |
| void CompactStringTableWriter::dump(CompactHashtable<oop, char> *cht) { |
| CompactHashtableWriter::dump(cht, "string"); |
| } |
| |
| ///////////////////////////////////////////////////////////// |
| // |
| // The CompactHashtable implementation |
| // |
| |
| void SimpleCompactHashtable::serialize(SerializeClosure* soc) { |
| soc->do_ptr((void**)&_base_address); |
| soc->do_u4(&_entry_count); |
| soc->do_u4(&_bucket_count); |
| soc->do_ptr((void**)&_buckets); |
| soc->do_ptr((void**)&_entries); |
| } |
| |
| bool SimpleCompactHashtable::exists(u4 value) { |
| assert(!DumpSharedSpaces, "run-time only"); |
| |
| if (_entry_count == 0) { |
| return false; |
| } |
| |
| unsigned int hash = (unsigned int)value; |
| int index = hash % _bucket_count; |
| u4 bucket_info = _buckets[index]; |
| u4 bucket_offset = BUCKET_OFFSET(bucket_info); |
| int bucket_type = BUCKET_TYPE(bucket_info); |
| u4* entry = _entries + bucket_offset; |
| |
| if (bucket_type == VALUE_ONLY_BUCKET_TYPE) { |
| return (entry[0] == value); |
| } else { |
| u4*entry_max = _entries + BUCKET_OFFSET(_buckets[index + 1]); |
| while (entry <entry_max) { |
| if (entry[1] == value) { |
| return true; |
| } |
| entry += 2; |
| } |
| return false; |
| } |
| } |
| |
| template <class I> |
| inline void SimpleCompactHashtable::iterate(const I& iterator) { |
| for (u4 i = 0; i < _bucket_count; i++) { |
| u4 bucket_info = _buckets[i]; |
| u4 bucket_offset = BUCKET_OFFSET(bucket_info); |
| int bucket_type = BUCKET_TYPE(bucket_info); |
| u4* entry = _entries + bucket_offset; |
| |
| if (bucket_type == VALUE_ONLY_BUCKET_TYPE) { |
| iterator.do_value(_base_address, entry[0]); |
| } else { |
| u4*entry_max = _entries + BUCKET_OFFSET(_buckets[i + 1]); |
| while (entry < entry_max) { |
| iterator.do_value(_base_address, entry[1]); |
| entry += 2; |
| } |
| } |
| } |
| } |
| |
| template <class T, class N> void CompactHashtable<T, N>::serialize(SerializeClosure* soc) { |
| SimpleCompactHashtable::serialize(soc); |
| soc->do_u4(&_type); |
| } |
| |
| class CompactHashtable_SymbolIterator { |
| SymbolClosure* const _closure; |
| public: |
| CompactHashtable_SymbolIterator(SymbolClosure *cl) : _closure(cl) {} |
| inline void do_value(address base_address, u4 offset) const { |
| Symbol* sym = (Symbol*)((void*)(base_address + offset)); |
| _closure->do_symbol(&sym); |
| } |
| }; |
| |
| template <class T, class N> void CompactHashtable<T, N>::symbols_do(SymbolClosure *cl) { |
| CompactHashtable_SymbolIterator iterator(cl); |
| iterate(iterator); |
| } |
| |
| class CompactHashtable_OopIterator { |
| OopClosure* const _closure; |
| public: |
| CompactHashtable_OopIterator(OopClosure *cl) : _closure(cl) {} |
| inline void do_value(address base_address, u4 offset) const { |
| narrowOop v = (narrowOop)offset; |
| oop obj = HeapShared::decode_from_archive(v); |
| _closure->do_oop(&obj); |
| } |
| }; |
| |
| template <class T, class N> void CompactHashtable<T, N>::oops_do(OopClosure* cl) { |
| assert(_type == _string_table || _bucket_count == 0, "sanity"); |
| CompactHashtable_OopIterator iterator(cl); |
| iterate(iterator); |
| } |
| |
| // Explicitly instantiate these types |
| template class CompactHashtable<Symbol*, char>; |
| template class CompactHashtable<oop, char>; |
| |
| #ifndef O_BINARY // if defined (Win32) use binary files. |
| #define O_BINARY 0 // otherwise do nothing. |
| #endif |
| |
| //////////////////////////////////////////////////////// |
| // |
| // HashtableTextDump |
| // |
| HashtableTextDump::HashtableTextDump(const char* filename) : _fd(-1) { |
| struct stat st; |
| if (os::stat(filename, &st) != 0) { |
| quit("Unable to get hashtable dump file size", filename); |
| } |
| _size = st.st_size; |
| _fd = os::open(filename, O_RDONLY | O_BINARY, 0); |
| if (_fd < 0) { |
| quit("Unable to open hashtable dump file", filename); |
| } |
| _base = os::map_memory(_fd, filename, 0, NULL, _size, true, false); |
| if (_base == NULL) { |
| quit("Unable to map hashtable dump file", filename); |
| } |
| _p = _base; |
| _end = _base + st.st_size; |
| _filename = filename; |
| _prefix_type = Unknown; |
| _line_no = 1; |
| } |
| |
| HashtableTextDump::~HashtableTextDump() { |
| os::unmap_memory((char*)_base, _size); |
| if (_fd >= 0) { |
| close(_fd); |
| } |
| } |
| |
| void HashtableTextDump::quit(const char* err, const char* msg) { |
| vm_exit_during_initialization(err, msg); |
| } |
| |
| void HashtableTextDump::corrupted(const char *p, const char* msg) { |
| char info[100]; |
| jio_snprintf(info, sizeof(info), |
| "%s. Corrupted at line %d (file pos %d)", |
| msg, _line_no, (int)(p - _base)); |
| quit(info, _filename); |
| } |
| |
| bool HashtableTextDump::skip_newline() { |
| if (_p[0] == '\r' && _p[1] == '\n') { |
| _p += 2; |
| } else if (_p[0] == '\n') { |
| _p += 1; |
| } else { |
| corrupted(_p, "Unexpected character"); |
| } |
| _line_no++; |
| return true; |
| } |
| |
| int HashtableTextDump::skip(char must_be_char) { |
| corrupted_if(remain() < 1, "Truncated"); |
| corrupted_if(*_p++ != must_be_char, "Unexpected character"); |
| return 0; |
| } |
| |
| void HashtableTextDump::skip_past(char c) { |
| for (;;) { |
| corrupted_if(remain() < 1, "Truncated"); |
| if (*_p++ == c) { |
| return; |
| } |
| } |
| } |
| |
| void HashtableTextDump::check_version(const char* ver) { |
| int len = (int)strlen(ver); |
| corrupted_if(remain() < len, "Truncated"); |
| if (strncmp(_p, ver, len) != 0) { |
| quit("wrong version of hashtable dump file", _filename); |
| } |
| _p += len; |
| skip_newline(); |
| } |
| |
| void HashtableTextDump::scan_prefix_type() { |
| _p++; |
| if (strncmp(_p, "SECTION: String", 15) == 0) { |
| _p += 15; |
| _prefix_type = StringPrefix; |
| } else if (strncmp(_p, "SECTION: Symbol", 15) == 0) { |
| _p += 15; |
| _prefix_type = SymbolPrefix; |
| } else { |
| _prefix_type = Unknown; |
| } |
| skip_newline(); |
| } |
| |
| int HashtableTextDump::scan_prefix(int* utf8_length) { |
| if (*_p == '@') { |
| scan_prefix_type(); |
| } |
| |
| switch (_prefix_type) { |
| case SymbolPrefix: |
| *utf8_length = scan_symbol_prefix(); break; |
| case StringPrefix: |
| *utf8_length = scan_string_prefix(); break; |
| default: |
| tty->print_cr("Shared input data type: Unknown."); |
| corrupted(_p, "Unknown data type"); |
| } |
| |
| return _prefix_type; |
| } |
| |
| int HashtableTextDump::scan_string_prefix() { |
| // Expect /[0-9]+: / |
| int utf8_length = 0; |
| get_num(':', &utf8_length); |
| if (*_p != ' ') { |
| corrupted(_p, "Wrong prefix format for string"); |
| } |
| _p++; |
| return utf8_length; |
| } |
| |
| int HashtableTextDump::scan_symbol_prefix() { |
| // Expect /[0-9]+ (-|)[0-9]+: / |
| int utf8_length = 0; |
| get_num(' ', &utf8_length); |
| if (*_p == '-') { |
| _p++; |
| } |
| int ref_num; |
| get_num(':', &ref_num); |
| if (*_p != ' ') { |
| corrupted(_p, "Wrong prefix format for symbol"); |
| } |
| _p++; |
| return utf8_length; |
| } |
| |
| jchar HashtableTextDump::unescape(const char* from, const char* end, int count) { |
| jchar value = 0; |
| |
| corrupted_if(from + count > end, "Truncated"); |
| |
| for (int i=0; i<count; i++) { |
| char c = *from++; |
| switch (c) { |
| case '0': case '1': case '2': case '3': case '4': |
| case '5': case '6': case '7': case '8': case '9': |
| value = (value << 4) + c - '0'; |
| break; |
| case 'a': case 'b': case 'c': |
| case 'd': case 'e': case 'f': |
| value = (value << 4) + 10 + c - 'a'; |
| break; |
| case 'A': case 'B': case 'C': |
| case 'D': case 'E': case 'F': |
| value = (value << 4) + 10 + c - 'A'; |
| break; |
| default: |
| ShouldNotReachHere(); |
| } |
| } |
| return value; |
| } |
| |
| void HashtableTextDump::get_utf8(char* utf8_buffer, int utf8_length) { |
| // cache in local vars |
| const char* from = _p; |
| const char* end = _end; |
| char* to = utf8_buffer; |
| int n = utf8_length; |
| |
| for (; n > 0 && from < end; n--) { |
| if (*from != '\\') { |
| *to++ = *from++; |
| } else { |
| corrupted_if(from + 2 > end, "Truncated"); |
| char c = from[1]; |
| from += 2; |
| switch (c) { |
| case 'x': |
| { |
| jchar value = unescape(from, end, 2); |
| from += 2; |
| assert(value <= 0xff, "sanity"); |
| *to++ = (char)(value & 0xff); |
| } |
| break; |
| case 't': *to++ = '\t'; break; |
| case 'n': *to++ = '\n'; break; |
| case 'r': *to++ = '\r'; break; |
| case '\\': *to++ = '\\'; break; |
| default: |
| corrupted(_p, "Unsupported character"); |
| } |
| } |
| } |
| corrupted_if(n > 0, "Truncated"); // expected more chars but file has ended |
| _p = from; |
| skip_newline(); |
| } |
| |
| // NOTE: the content is NOT the same as |
| // UTF8::as_quoted_ascii(const char* utf8_str, int utf8_length, char* buf, int buflen). |
| // We want to escape \r\n\t so that output [1] is more readable; [2] can be more easily |
| // parsed by scripts; [3] quickly processed by HashtableTextDump::get_utf8() |
| void HashtableTextDump::put_utf8(outputStream* st, const char* utf8_string, int utf8_length) { |
| const char *c = utf8_string; |
| const char *end = c + utf8_length; |
| for (; c < end; c++) { |
| switch (*c) { |
| case '\t': st->print("\\t"); break; |
| case '\r': st->print("\\r"); break; |
| case '\n': st->print("\\n"); break; |
| case '\\': st->print("\\\\"); break; |
| default: |
| if (isprint(*c)) { |
| st->print("%c", *c); |
| } else { |
| st->print("\\x%02x", ((unsigned int)*c) & 0xff); |
| } |
| } |
| } |
| } |