Skip to content

Commit 9cc2518

Browse files
author
duke
committedJun 23, 2020
Automatic merge of jdk:master into master
2 parents 93087e9 + 4076ca8 commit 9cc2518

File tree

14 files changed

+642
-120
lines changed

14 files changed

+642
-120
lines changed
 

‎src/hotspot/cpu/ppc/ppc.ad

+4-1
Original file line numberDiff line numberDiff line change
@@ -2296,10 +2296,13 @@ const bool Matcher::match_rule_supported(int opcode) {
22962296
case Op_FmaVD:
22972297
return (SuperwordUseVSX && UseFMA);
22982298
case Op_Digit:
2299+
return vmIntrinsics::is_intrinsic_available(vmIntrinsics::_isDigit);
22992300
case Op_LowerCase:
2301+
return vmIntrinsics::is_intrinsic_available(vmIntrinsics::_isLowerCase);
23002302
case Op_UpperCase:
2303+
return vmIntrinsics::is_intrinsic_available(vmIntrinsics::_isUpperCase);
23012304
case Op_Whitespace:
2302-
return UseCharacterCompareIntrinsics;
2305+
return vmIntrinsics::is_intrinsic_available(vmIntrinsics::_isWhitespace);
23032306

23042307
case Op_CacheWB:
23052308
case Op_CacheWBPreSync:

‎src/hotspot/share/classfile/vmSymbols.cpp

+71-45
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "memory/metaspaceClosure.hpp"
3333
#include "oops/oop.inline.hpp"
3434
#include "runtime/handles.inline.hpp"
35+
#include "utilities/tribool.hpp"
3536
#include "utilities/xmlstream.hpp"
3637

3738

@@ -460,43 +461,7 @@ int vmIntrinsics::predicates_needed(vmIntrinsics::ID id) {
460461
}
461462
}
462463

463-
bool vmIntrinsics::is_intrinsic_available(vmIntrinsics::ID id) {
464-
return !vmIntrinsics::is_intrinsic_disabled(id) &&
465-
!vmIntrinsics::is_disabled_by_flags(id);
466-
}
467-
468-
bool vmIntrinsics::is_intrinsic_disabled(vmIntrinsics::ID id) {
469-
assert(id != vmIntrinsics::_none, "must be a VM intrinsic");
470-
471-
// Canonicalize DisableIntrinsic to contain only ',' as a separator.
472-
// Note, DirectiveSet may not be created at this point yet since this code
473-
// is called from initial stub geenration code.
474-
char* local_list = (char*)DirectiveSet::canonicalize_disableintrinsic(DisableIntrinsic);
475-
char* save_ptr;
476-
bool found = false;
477-
478-
char* token = strtok_r(local_list, ",", &save_ptr);
479-
while (token != NULL) {
480-
if (strcmp(token, vmIntrinsics::name_at(id)) == 0) {
481-
found = true;
482-
break;
483-
} else {
484-
token = strtok_r(NULL, ",", &save_ptr);
485-
}
486-
}
487-
488-
FREE_C_HEAP_ARRAY(char, local_list);
489-
return found;
490-
}
491-
492-
493-
bool vmIntrinsics::is_disabled_by_flags(const methodHandle& method) {
494-
vmIntrinsics::ID id = method->intrinsic_id();
495-
assert(id != vmIntrinsics::_none, "must be a VM intrinsic");
496-
return is_disabled_by_flags(id);
497-
}
498-
499-
bool vmIntrinsics::is_disabled_by_flags(vmIntrinsics::ID id) {
464+
bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) {
500465
assert(id != vmIntrinsics::_none, "must be a VM intrinsic");
501466

502467
// -XX:-InlineNatives disables nearly all intrinsics except the ones listed in
@@ -861,25 +826,86 @@ static const char* vm_intrinsic_name_bodies =
861826
VM_SYMBOL_IGNORE, VM_SYMBOL_IGNORE, VM_SYMBOL_IGNORE, VM_ALIAS_IGNORE);
862827

863828
static const char* vm_intrinsic_name_table[vmIntrinsics::ID_LIMIT];
829+
static TriBoolArray<vmIntrinsics::ID_LIMIT, int> vm_intrinsic_control_words;
830+
831+
static void init_vm_intrinsic_name_table() {
832+
const char** nt = &vm_intrinsic_name_table[0];
833+
char* string = (char*) &vm_intrinsic_name_bodies[0];
834+
for (int index = vmIntrinsics::FIRST_ID; index < vmIntrinsics::ID_LIMIT; index++) {
835+
nt[index] = string;
836+
string += strlen(string); // skip string body
837+
string += 1; // skip trailing null
838+
}
839+
assert(!strcmp(nt[vmIntrinsics::_hashCode], "_hashCode"), "lined up");
840+
nt[vmIntrinsics::_none] = "_none";
841+
}
864842

865843
const char* vmIntrinsics::name_at(vmIntrinsics::ID id) {
866844
const char** nt = &vm_intrinsic_name_table[0];
867845
if (nt[_none] == NULL) {
868-
char* string = (char*) &vm_intrinsic_name_bodies[0];
869-
for (int index = FIRST_ID; index < ID_LIMIT; index++) {
870-
nt[index] = string;
871-
string += strlen(string); // skip string body
872-
string += 1; // skip trailing null
873-
}
874-
assert(!strcmp(nt[_hashCode], "_hashCode"), "lined up");
875-
nt[_none] = "_none";
846+
init_vm_intrinsic_name_table();
876847
}
848+
877849
if ((uint)id < (uint)ID_LIMIT)
878850
return vm_intrinsic_name_table[(uint)id];
879851
else
880852
return "(unknown intrinsic)";
881853
}
882854

855+
vmIntrinsics::ID vmIntrinsics::find_id(const char* name) {
856+
const char** nt = &vm_intrinsic_name_table[0];
857+
if (nt[_none] == NULL) {
858+
init_vm_intrinsic_name_table();
859+
}
860+
861+
for (int index = FIRST_ID; index < ID_LIMIT; ++index) {
862+
if (0 == strcmp(name, nt[index])) {
863+
return ID_from(index);
864+
}
865+
}
866+
867+
return _none;
868+
}
869+
870+
bool vmIntrinsics::is_disabled_by_flags(const methodHandle& method) {
871+
vmIntrinsics::ID id = method->intrinsic_id();
872+
return is_disabled_by_flags(id);
873+
}
874+
875+
bool vmIntrinsics::is_disabled_by_flags(vmIntrinsics::ID id) {
876+
assert(id > _none && id < ID_LIMIT, "must be a VM intrinsic");
877+
878+
// not initialized yet, process Control/DisableIntrinsic
879+
if (vm_intrinsic_control_words[_none].is_default()) {
880+
for (ControlIntrinsicIter iter(ControlIntrinsic); *iter != NULL; ++iter) {
881+
vmIntrinsics::ID id = vmIntrinsics::find_id(*iter);
882+
883+
if (id != vmIntrinsics::_none) {
884+
vm_intrinsic_control_words[id] = iter.is_enabled() && !disabled_by_jvm_flags(id);
885+
}
886+
}
887+
888+
// Order matters, DisableIntrinsic can overwrite ControlIntrinsic
889+
for (ControlIntrinsicIter iter(DisableIntrinsic, true/*disable_all*/); *iter != NULL; ++iter) {
890+
vmIntrinsics::ID id = vmIntrinsics::find_id(*iter);
891+
892+
if (id != vmIntrinsics::_none) {
893+
vm_intrinsic_control_words[id] = false;
894+
}
895+
}
896+
897+
vm_intrinsic_control_words[_none] = true;
898+
}
899+
900+
TriBool b = vm_intrinsic_control_words[id];
901+
if (b.is_default()) {
902+
// unknown yet, query and cache it
903+
b = vm_intrinsic_control_words[id] = !disabled_by_jvm_flags(id);
904+
}
905+
906+
return !b;
907+
}
908+
883909
// These are flag-matching functions:
884910
inline bool match_F_R(jshort flags) {
885911
const int req = 0;

‎src/hotspot/share/classfile/vmSymbols.hpp

+21-5
Original file line numberDiff line numberDiff line change
@@ -1635,7 +1635,10 @@ class vmIntrinsics: AllStatic {
16351635
vmSymbols::SID sig,
16361636
jshort flags);
16371637

1638+
// check if the intrinsic is disabled by course-grained flags.
1639+
static bool disabled_by_jvm_flags(vmIntrinsics::ID id);
16381640
public:
1641+
static ID find_id(const char* name);
16391642
// Given a method's class, name, signature, and access flags, report its ID.
16401643
static ID find_id(vmSymbols::SID holder,
16411644
vmSymbols::SID name,
@@ -1685,12 +1688,25 @@ class vmIntrinsics: AllStatic {
16851688
// 'method' requires predicated logic.
16861689
static int predicates_needed(vmIntrinsics::ID id);
16871690

1688-
// Returns true if a compiler intrinsic is disabled by command-line flags
1689-
// and false otherwise.
1690-
static bool is_disabled_by_flags(const methodHandle& method);
1691+
// There are 2 kinds of JVM options to control intrinsics.
1692+
// 1. Disable/Control Intrinsic accepts a list of intrinsic IDs.
1693+
// ControlIntrinsic is recommended. DisableIntrinic will be deprecated.
1694+
// Currently, the DisableIntrinsic list prevails if an intrinsic appears on
1695+
// both lists.
1696+
//
1697+
// 2. Explicit UseXXXIntrinsics options. eg. UseAESIntrinsics, UseCRC32Intrinsics etc.
1698+
// Each option can control a group of intrinsics. The user can specify them but
1699+
// their final values are subject to hardware inspection (VM_Version::initialize).
1700+
// Stub generators are controlled by them.
1701+
//
1702+
// An intrinsic is enabled if and only if neither the fine-grained control(1) nor
1703+
// the corresponding coarse-grained control(2) disables it.
16911704
static bool is_disabled_by_flags(vmIntrinsics::ID id);
1692-
static bool is_intrinsic_disabled(vmIntrinsics::ID id);
1693-
static bool is_intrinsic_available(vmIntrinsics::ID id);
1705+
1706+
static bool is_disabled_by_flags(const methodHandle& method);
1707+
static bool is_intrinsic_available(vmIntrinsics::ID id) {
1708+
return !is_disabled_by_flags(id);
1709+
}
16941710
};
16951711

16961712
#endif // SHARE_CLASSFILE_VMSYMBOLS_HPP

‎src/hotspot/share/compiler/compilerDirectives.cpp

+109-57
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333

3434
CompilerDirectives::CompilerDirectives() : _next(NULL), _match(NULL), _ref_count(0) {
3535
_c1_store = new DirectiveSet(this);
36-
_c1_store->init_disableintrinsic();
36+
_c1_store->init_control_intrinsic();
3737
_c2_store = new DirectiveSet(this);
38-
_c2_store->init_disableintrinsic();
38+
_c2_store->init_control_intrinsic();
3939
};
4040

4141
CompilerDirectives::~CompilerDirectives() {
@@ -179,14 +179,14 @@ DirectiveSet* CompilerDirectives::get_for(AbstractCompiler *comp) {
179179
}
180180
}
181181

182-
// In the list of disabled intrinsics, the ID of the disabled intrinsics can separated:
183-
// - by ',' (if -XX:DisableIntrinsic is used once when invoking the VM) or
184-
// - by '\n' (if -XX:DisableIntrinsic is used multiple times when invoking the VM) or
185-
// - by ' ' (if DisableIntrinsic is used on a per-method level, e.g., with CompileCommand).
182+
// In the list of Control/disabled intrinsics, the ID of the control intrinsics can separated:
183+
// - by ',' (if -XX:Control/DisableIntrinsic is used once when invoking the VM) or
184+
// - by '\n' (if -XX:Control/DisableIntrinsic is used multiple times when invoking the VM) or
185+
// - by ' ' (if Control/DisableIntrinsic is used on a per-method level, e.g., with CompileCommand).
186186
//
187-
// To simplify the processing of the list, the canonicalize_disableintrinsic() method
187+
// To simplify the processing of the list, the canonicalize_control_intrinsic() method
188188
// returns a new copy of the list in which '\n' and ' ' is replaced with ','.
189-
ccstrlist DirectiveSet::canonicalize_disableintrinsic(ccstrlist option_value) {
189+
ccstrlist DirectiveSet::canonicalize_control_intrinsic(ccstrlist option_value) {
190190
char* canonicalized_list = NEW_C_HEAP_ARRAY(char, strlen(option_value) + 1, mtCompiler);
191191
int i = 0;
192192
char current;
@@ -202,9 +202,57 @@ ccstrlist DirectiveSet::canonicalize_disableintrinsic(ccstrlist option_value) {
202202
return canonicalized_list;
203203
}
204204

205-
void DirectiveSet::init_disableintrinsic() {
206-
// Canonicalize DisableIntrinsic to contain only ',' as a separator.
207-
this->DisableIntrinsicOption = canonicalize_disableintrinsic(DisableIntrinsic);
205+
ControlIntrinsicIter::ControlIntrinsicIter(ccstrlist option_value, bool disable_all)
206+
: _disableIntrinsic(disable_all) {
207+
_list = (char*)DirectiveSet::canonicalize_control_intrinsic(option_value);
208+
_saved_ptr = _list;
209+
_enabled = false;
210+
211+
_token = strtok_r(_saved_ptr, ",", &_saved_ptr);
212+
next_token();
213+
}
214+
215+
ControlIntrinsicIter::~ControlIntrinsicIter() {
216+
FREE_C_HEAP_ARRAY(char, _list);
217+
}
218+
219+
// pre-increment
220+
ControlIntrinsicIter& ControlIntrinsicIter::operator++() {
221+
_token = strtok_r(NULL, ",", &_saved_ptr);
222+
next_token();
223+
return *this;
224+
}
225+
226+
void ControlIntrinsicIter::next_token() {
227+
if (_token && !_disableIntrinsic) {
228+
char ch = _token[0];
229+
230+
if (ch != '+' && ch != '-') {
231+
warning("failed to parse %s. must start with +/-!", _token);
232+
} else {
233+
_enabled = ch == '+';
234+
_token++;
235+
}
236+
}
237+
}
238+
239+
void DirectiveSet::init_control_intrinsic() {
240+
for (ControlIntrinsicIter iter(ControlIntrinsic); *iter != NULL; ++iter) {
241+
vmIntrinsics::ID id = vmIntrinsics::find_id(*iter);
242+
243+
if (id != vmIntrinsics::_none) {
244+
_intrinsic_control_words[id] = iter.is_enabled();
245+
}
246+
}
247+
248+
// Order matters, DisableIntrinsic can overwrite ControlIntrinsic
249+
for (ControlIntrinsicIter iter(DisableIntrinsic, true/*disable_all*/); *iter != NULL; ++iter) {
250+
vmIntrinsics::ID id = vmIntrinsics::find_id(*iter);
251+
252+
if (id != vmIntrinsics::_none) {
253+
_intrinsic_control_words[id] = false;
254+
}
255+
}
208256
}
209257

210258
DirectiveSet::DirectiveSet(CompilerDirectives* d) :_inlinematchers(NULL), _directive(d) {
@@ -213,6 +261,7 @@ DirectiveSet::DirectiveSet(CompilerDirectives* d) :_inlinematchers(NULL), _direc
213261
compilerdirectives_c2_flags(init_defaults_definition)
214262
compilerdirectives_c1_flags(init_defaults_definition)
215263
memset(_modified, 0, sizeof(_modified));
264+
_intrinsic_control_words.fill_in(/*default value*/TriBool());
216265
}
217266

218267
DirectiveSet::~DirectiveSet() {
@@ -223,12 +272,6 @@ DirectiveSet::~DirectiveSet() {
223272
delete tmp;
224273
tmp = next;
225274
}
226-
227-
// When constructed, DirectiveSet canonicalizes the DisableIntrinsic flag
228-
// into a new string. Therefore, that string is deallocated when
229-
// the DirectiveSet is destroyed.
230-
assert(this->DisableIntrinsicOption != NULL, "");
231-
FREE_C_HEAP_ARRAY(char, (void *)this->DisableIntrinsicOption);
232275
}
233276

234277
// Backward compatibility for CompileCommands
@@ -280,10 +323,6 @@ DirectiveSet* DirectiveSet::compilecommand_compatibility_init(const methodHandle
280323
}
281324
}
282325

283-
// Read old value of DisableIntrinsicOption, in case we need to free it
284-
// and overwrite it with a new value.
285-
ccstrlist old_disable_intrinsic_value = set->DisableIntrinsicOption;
286-
287326
// inline and dontinline (including exclude) are implemented in the directiveset accessors
288327
#define init_default_cc(name, type, dvalue, cc_flag) { type v; if (!_modified[name##Index] && CompilerOracle::has_option_value(method, #cc_flag, v) && v != this->name##Option) { set->name##Option = v; changed = true;} }
289328
compilerdirectives_common_flags(init_default_cc)
@@ -292,11 +331,45 @@ DirectiveSet* DirectiveSet::compilecommand_compatibility_init(const methodHandle
292331

293332
// Canonicalize DisableIntrinsic to contain only ',' as a separator.
294333
ccstrlist option_value;
334+
bool need_reset = true; // if Control/DisableIntrinsic redefined, only need to reset control_words once
335+
336+
if (!_modified[ControlIntrinsicIndex] &&
337+
CompilerOracle::has_option_value(method, "ControlIntrinsic", option_value)) {
338+
ControlIntrinsicIter iter(option_value);
339+
340+
if (need_reset) {
341+
set->_intrinsic_control_words.fill_in(TriBool());
342+
need_reset = false;
343+
}
344+
345+
while (*iter != NULL) {
346+
vmIntrinsics::ID id = vmIntrinsics::find_id(*iter);
347+
if (id != vmIntrinsics::_none) {
348+
set->_intrinsic_control_words[id] = iter.is_enabled();
349+
}
350+
351+
++iter;
352+
}
353+
}
354+
355+
295356
if (!_modified[DisableIntrinsicIndex] &&
296357
CompilerOracle::has_option_value(method, "DisableIntrinsic", option_value)) {
297-
set->DisableIntrinsicOption = canonicalize_disableintrinsic(option_value);
298-
assert(old_disable_intrinsic_value != NULL, "");
299-
FREE_C_HEAP_ARRAY(char, (void *)old_disable_intrinsic_value);
358+
ControlIntrinsicIter iter(option_value, true/*disable_all*/);
359+
360+
if (need_reset) {
361+
set->_intrinsic_control_words.fill_in(TriBool());
362+
need_reset = false;
363+
}
364+
365+
while (*iter != NULL) {
366+
vmIntrinsics::ID id = vmIntrinsics::find_id(*iter);
367+
if (id != vmIntrinsics::_none) {
368+
set->_intrinsic_control_words[id] = false;
369+
}
370+
371+
++iter;
372+
}
300373
}
301374

302375

@@ -397,38 +470,23 @@ void DirectiveSet::print_inline(outputStream* st) {
397470

398471
bool DirectiveSet::is_intrinsic_disabled(const methodHandle& method) {
399472
vmIntrinsics::ID id = method->intrinsic_id();
400-
assert(id != vmIntrinsics::_none, "must be a VM intrinsic");
401-
402-
ResourceMark rm;
403-
404-
// Create a copy of the string that contains the list of disabled
405-
// intrinsics. The copy is created because the string
406-
// will be modified by strtok(). Then, the list is tokenized with
407-
// ',' as a separator.
408-
size_t length = strlen(DisableIntrinsicOption);
409-
char* local_list = NEW_RESOURCE_ARRAY(char, length + 1);
410-
strncpy(local_list, DisableIntrinsicOption, length + 1);
411-
char* save_ptr;
412-
413-
char* token = strtok_r(local_list, ",", &save_ptr);
414-
while (token != NULL) {
415-
if (strcmp(token, vmIntrinsics::name_at(id)) == 0) {
416-
return true;
417-
} else {
418-
token = strtok_r(NULL, ",", &save_ptr);
419-
}
420-
}
473+
assert(id > vmIntrinsics::_none && id < vmIntrinsics::ID_LIMIT, "invalid intrinsic_id!");
421474

422-
return false;
475+
TriBool b = _intrinsic_control_words[id];
476+
if (b.is_default()) {
477+
return false; // if unset, every intrinsic is enabled.
478+
} else {
479+
return !b;
480+
}
423481
}
424482

425483
DirectiveSet* DirectiveSet::clone(DirectiveSet const* src) {
426484
DirectiveSet* set = new DirectiveSet(NULL);
427-
// Ordinary allocations of DirectiveSet would call init_disableintrinsic()
428-
// immediately to create a new copy for set->DisableIntrinsicOption.
485+
// Ordinary allocations of DirectiveSet would call init_control_intrinsic()
486+
// immediately to create a new copy for set->Control/DisableIntrinsicOption.
429487
// However, here it does not need to because the code below creates
430-
// a copy of src->DisableIntrinsicOption that initializes
431-
// set->DisableIntrinsicOption.
488+
// a copy of src->Control/DisableIntrinsicOption that initializes
489+
// set->Control/DisableIntrinsicOption.
432490

433491
memcpy(set->_modified, src->_modified, sizeof(src->_modified));
434492

@@ -443,13 +501,7 @@ DirectiveSet* DirectiveSet::clone(DirectiveSet const* src) {
443501
compilerdirectives_c2_flags(copy_members_definition)
444502
compilerdirectives_c1_flags(copy_members_definition)
445503

446-
// Create a local copy of the DisableIntrinsicOption.
447-
assert(src->DisableIntrinsicOption != NULL, "");
448-
size_t len = strlen(src->DisableIntrinsicOption) + 1;
449-
char* s = NEW_C_HEAP_ARRAY(char, len, mtCompiler);
450-
strncpy(s, src->DisableIntrinsicOption, len);
451-
assert(s[len-1] == '\0', "");
452-
set->DisableIntrinsicOption = s;
504+
set->_intrinsic_control_words = src->_intrinsic_control_words;
453505
return set;
454506
}
455507

‎src/hotspot/share/compiler/compilerDirectives.hpp

+30-3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "compiler/methodMatcher.hpp"
3131
#include "compiler/compilerOracle.hpp"
3232
#include "utilities/exceptions.hpp"
33+
#include "utilities/tribool.hpp"
3334

3435
// Directives flag name, type, default value, compile command name
3536
#define compilerdirectives_common_flags(cflags) \
@@ -46,7 +47,8 @@
4647
cflags(DumpReplay, bool, false, DumpReplay) \
4748
cflags(DumpInline, bool, false, DumpInline) \
4849
cflags(CompilerDirectivesIgnoreCompileCommands, bool, CompilerDirectivesIgnoreCompileCommands, X) \
49-
cflags(DisableIntrinsic, ccstrlist, DisableIntrinsic, DisableIntrinsic)
50+
cflags(DisableIntrinsic, ccstrlist, DisableIntrinsic, DisableIntrinsic) \
51+
cflags(ControlIntrinsic, ccstrlist, ControlIntrinsic, ControlIntrinsic)
5052

5153
#ifdef COMPILER1
5254
#define compilerdirectives_c1_flags(cflags)
@@ -99,11 +101,12 @@ class DirectiveSet : public CHeapObj<mtCompiler> {
99101
private:
100102
InlineMatcher* _inlinematchers;
101103
CompilerDirectives* _directive;
104+
TriBoolArray<vmIntrinsics::ID_LIMIT, int> _intrinsic_control_words;
102105

103106
public:
104107
DirectiveSet(CompilerDirectives* directive);
105108
~DirectiveSet();
106-
void init_disableintrinsic();
109+
void init_control_intrinsic();
107110
CompilerDirectives* directive();
108111
bool parse_and_add_inline(char* str, const char*& error_msg);
109112
void append_inline(InlineMatcher* m);
@@ -115,7 +118,7 @@ class DirectiveSet : public CHeapObj<mtCompiler> {
115118
bool matches_inline(const methodHandle& method, int inline_action);
116119
static DirectiveSet* clone(DirectiveSet const* src);
117120
bool is_intrinsic_disabled(const methodHandle& method);
118-
static ccstrlist canonicalize_disableintrinsic(ccstrlist option_value);
121+
static ccstrlist canonicalize_control_intrinsic(ccstrlist option_value);
119122
void finalize(outputStream* st);
120123

121124
typedef enum {
@@ -126,8 +129,10 @@ class DirectiveSet : public CHeapObj<mtCompiler> {
126129
number_of_flags
127130
} flags;
128131

132+
private:
129133
bool _modified[number_of_flags]; // Records what options where set by a directive
130134

135+
public:
131136
#define flag_store_definition(name, type, dvalue, cc_flag) type name##Option;
132137
compilerdirectives_common_flags(flag_store_definition)
133138
compilerdirectives_c2_flags(flag_store_definition)
@@ -157,6 +162,28 @@ void print(outputStream* st) {
157162
}
158163
};
159164

165+
// Iterator of ControlIntrinsic
166+
// if disable_all is true, it accepts DisableIntrinsic(deprecated) and all intrinsics
167+
// appear in the list are to disable
168+
class ControlIntrinsicIter {
169+
private:
170+
bool _enabled;
171+
char* _token;
172+
char* _saved_ptr;
173+
char* _list;
174+
const bool _disableIntrinsic;
175+
void next_token();
176+
177+
public:
178+
ControlIntrinsicIter(ccstrlist option, bool disable_all = false);
179+
~ControlIntrinsicIter();
180+
181+
bool is_enabled() const { return _enabled; }
182+
const char* operator*() const { return _token; }
183+
184+
ControlIntrinsicIter& operator++();
185+
};
186+
160187
class CompilerDirectives : public CHeapObj<mtCompiler> {
161188
private:
162189
CompilerDirectives* _next;

‎src/hotspot/share/compiler/compilerOracle.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -490,12 +490,12 @@ static void scan_flag_and_value(const char* type, const char* line, int& total_b
490490
ResourceMark rm;
491491
char* value = NEW_RESOURCE_ARRAY(char, strlen(line) + 1);
492492
char* next_value = value;
493-
if (sscanf(line, "%*[ \t]%255[_a-zA-Z0-9]%n", next_value, &bytes_read) == 1) {
493+
if (sscanf(line, "%*[ \t]%255[_a-zA-Z0-9+\\-]%n", next_value, &bytes_read) == 1) {
494494
total_bytes_read += bytes_read;
495495
line += bytes_read;
496496
next_value += bytes_read;
497497
char* end_value = next_value-1;
498-
while (sscanf(line, "%*[ \t]%255[_a-zA-Z0-9]%n", next_value, &bytes_read) == 1) {
498+
while (sscanf(line, "%*[ \t]%255[_a-zA-Z0-9+\\-]%n", next_value, &bytes_read) == 1) {
499499
total_bytes_read += bytes_read;
500500
line += bytes_read;
501501
*end_value = ' '; // override '\0'

‎src/hotspot/share/opto/library_call.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -6522,21 +6522,21 @@ bool LibraryCallKit::inline_digestBase_implCompressMB(int predicate) {
65226522

65236523
switch (predicate) {
65246524
case 0:
6525-
if (UseSHA1Intrinsics) {
6525+
if (vmIntrinsics::is_intrinsic_available(vmIntrinsics::_sha_implCompress)) {
65266526
klass_SHA_name = "sun/security/provider/SHA";
65276527
stub_name = "sha1_implCompressMB";
65286528
stub_addr = StubRoutines::sha1_implCompressMB();
65296529
}
65306530
break;
65316531
case 1:
6532-
if (UseSHA256Intrinsics) {
6532+
if (vmIntrinsics::is_intrinsic_available(vmIntrinsics::_sha2_implCompress)) {
65336533
klass_SHA_name = "sun/security/provider/SHA2";
65346534
stub_name = "sha256_implCompressMB";
65356535
stub_addr = StubRoutines::sha256_implCompressMB();
65366536
}
65376537
break;
65386538
case 2:
6539-
if (UseSHA512Intrinsics) {
6539+
if (vmIntrinsics::is_intrinsic_available(vmIntrinsics::_sha5_implCompress)) {
65406540
klass_SHA_name = "sun/security/provider/SHA5";
65416541
stub_name = "sha512_implCompressMB";
65426542
stub_addr = StubRoutines::sha512_implCompressMB();

‎src/hotspot/share/runtime/globals.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,10 @@ const size_t minimumSymbolTableSize = 1024;
350350
diagnostic(ccstrlist, DisableIntrinsic, "", \
351351
"do not expand intrinsics whose (internal) names appear here") \
352352
\
353+
diagnostic(ccstrlist, ControlIntrinsic, "", \
354+
"Control intrinsics using a list of +/- (internal) names, " \
355+
"separated by commas") \
356+
\
353357
develop(bool, TraceCallFixup, false, \
354358
"Trace all call fixups") \
355359
\
+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*
23+
*/
24+
25+
#ifndef SHARE_UTILITIES_TRIBOOL_HPP
26+
#define SHARE_UTILITIES_TRIBOOL_HPP
27+
28+
#include "utilities/globalDefinitions.hpp"
29+
30+
// 2-bit boolean type: H|L
31+
// the high-bit H is set if it's not default value.
32+
// the low-bit L represents true and false.
33+
class TriBool{
34+
template <size_t SZ, typename T>
35+
friend class TriBoolArray;
36+
37+
private:
38+
unsigned int _value : 2;
39+
explicit TriBool(u1 raw) : _value(raw & 3) {}
40+
41+
public:
42+
TriBool() : _value(0) {}
43+
TriBool(bool value) : _value(((u1)value) | 2) {}
44+
TriBool(const TriBool& o): _value(o._value) {}
45+
46+
TriBool& operator=(bool value) {
47+
_value = ((u1)value) | 2;
48+
return *this;
49+
}
50+
51+
TriBool& operator=(const TriBool& o) {
52+
_value = o._value;
53+
return *this;
54+
}
55+
56+
bool is_default() const {
57+
return !static_cast<bool>(_value >> 1);
58+
}
59+
60+
/*explicit*/ operator bool() const {
61+
return (_value & 1);
62+
}
63+
};
64+
65+
// compacted array of TriBool
66+
template <size_t SZ, typename T>
67+
class TriBoolArray {
68+
private:
69+
class TriBoolAssigner : public TriBool {
70+
public:
71+
TriBoolAssigner(T& slot, size_t offset) : TriBool(static_cast<u1>(slot >> offset)),
72+
_slot(slot), _offset(offset) {}
73+
74+
TriBoolAssigner& operator=(bool newval) {
75+
_slot ^= ((u1)_value) << _offset; // reset the tribool
76+
_value = (u1)newval| 2;
77+
_slot |= ((u1)_value) << _offset;
78+
return *this;
79+
};
80+
81+
TriBoolAssigner& operator=(TriBool tb) {
82+
_slot ^= ((u1)_value) << _offset; // reset the tribool
83+
_value = (u1)tb._value;
84+
_slot |= ((u1)_value) << _offset;
85+
return *this;
86+
}
87+
88+
private:
89+
T& _slot;
90+
size_t _offset;
91+
};
92+
93+
public:
94+
TriBoolArray() {}
95+
96+
TriBoolArray(const TriBool& init) {
97+
fill_in(init);
98+
}
99+
100+
TriBool operator[](size_t x) const {
101+
size_t index = x / (_slot_size);
102+
size_t offset = x % (_slot_size);
103+
T raw = (_array[index] >> (2 * offset)) & 3;
104+
return TriBool(static_cast<u1>(raw));
105+
}
106+
107+
TriBoolAssigner operator[](size_t x) {
108+
size_t index = x / (_slot_size);
109+
size_t offset = x % (_slot_size);
110+
return TriBoolAssigner(_array[index], 2 * offset);
111+
}
112+
113+
void fill_in(const TriBool& val) {
114+
if (val.is_default()) {
115+
memset(_array, 0, _size * sizeof(T));
116+
}
117+
else {
118+
for (size_t i = 0; i < SZ; ++i) {
119+
(*this)[i] = val;
120+
}
121+
}
122+
}
123+
124+
void fill_in(const TriBool* beg, const TriBool* end) {
125+
size_t i = 0;
126+
127+
while (i < SZ && beg != end) {
128+
(*this)[i++] = *beg++;
129+
}
130+
}
131+
132+
private:
133+
const static size_t _bits_in_T = sizeof(T) * 8; // bits in a byte
134+
const static size_t _slot_size = _bits_in_T >> 1; // one TriBool occupies 2bits
135+
const static size_t _size = (2 * SZ + _bits_in_T - 1) / (_bits_in_T);
136+
T _array[_size];
137+
};
138+
139+
#endif // SHARE_UTILITIES_TRIBOOL_HPP

‎test/hotspot/gtest/compiler/test_directivesParser.cpp

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -93,6 +93,20 @@ TEST_VM_F(DirectivesParserTest, simple_match) {
9393

9494
}
9595

96+
TEST_VM_F(DirectivesParserTest, control_intrinsic) {
97+
test_positive(
98+
"[" "\n"
99+
" {" "\n"
100+
" match: \"foo/bar.*\"," "\n"
101+
" c2: {" "\n"
102+
" DisableIntrinsic: \"_compareToL\"," "\n"
103+
" ControlIntrinsic: \"+_mulAdd,+_getInt,-_arraycopy,+_compareToL\"" "\n"
104+
" }" "\n"
105+
" }" "\n"
106+
"]" "\n");
107+
108+
}
109+
96110
TEST_VM_F(DirectivesParserTest, nesting_arrays) {
97111
test_negative(
98112
"[" "\n"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
#include "precompiled.hpp"
25+
#include "unittest.hpp"
26+
#include "utilities/tribool.hpp"
27+
28+
TEST(tribool, TriBool) {
29+
TriBool t1;
30+
ASSERT_EQ(t1.is_default(), true);
31+
ASSERT_EQ((bool)t1, false);
32+
33+
TriBool t2(false);
34+
ASSERT_TRUE(t2.is_default() == false && (bool)t2 == false);
35+
36+
TriBool t3(true);
37+
ASSERT_TRUE(t3.is_default() == false && (bool)t3 == true);
38+
39+
TriBool t4 = false;
40+
ASSERT_TRUE(t4.is_default() == false && (bool)t4 == false);
41+
42+
if (t2 || !t3 || t4) {
43+
ASSERT_TRUE(false); //boom
44+
}
45+
46+
TriBool flags[4];
47+
flags[0] = TriBool();
48+
flags[1] = false;
49+
flags[2] = true;
50+
51+
ASSERT_EQ(flags[0].is_default(), true) << "should be default";
52+
ASSERT_EQ(!flags[1].is_default() && !flags[1], true) << "should be not default and not set";
53+
ASSERT_EQ(!flags[2].is_default() && flags[2], true) << "should be not default and set";
54+
ASSERT_EQ(flags[3].is_default() == true, true) << "should be default";
55+
}
56+
57+
template <size_t SZ, typename T>
58+
struct Tester {
59+
static void doit() {
60+
// test fill_in(value)
61+
control_words.fill_in(TriBool());
62+
for (size_t i = 0; i < SZ; ++i) {
63+
EXPECT_TRUE(control_words[i].is_default());
64+
}
65+
66+
TriBool F = false;
67+
control_words.fill_in(F);
68+
for (size_t i = 0; i < SZ; ++i) {
69+
EXPECT_TRUE(!control_words[i].is_default() && control_words[i] == false);
70+
}
71+
72+
// test fill_in(beg, end)
73+
TriBool Vec[4];
74+
Vec[0] = TriBool();
75+
Vec[1] = TriBool();
76+
Vec[2] = true;
77+
Vec[3] = false;
78+
79+
control_words.fill_in(&Vec[0], Vec + 4);
80+
81+
if (0 < SZ) {
82+
EXPECT_TRUE(control_words[0].is_default());
83+
}
84+
85+
if (1 < SZ) {
86+
EXPECT_TRUE(control_words[1].is_default());
87+
}
88+
89+
if (2 < SZ) {
90+
EXPECT_TRUE(!control_words[2].is_default() && control_words[2] == true);
91+
}
92+
93+
if (3 < SZ) {
94+
EXPECT_TRUE(!control_words[3].is_default() && control_words[3] == false);
95+
}
96+
97+
// test assignment
98+
for (size_t i = 0; i < SZ; ++i) {
99+
control_words[i] = true;
100+
EXPECT_TRUE(!control_words[i].is_default() && control_words[i] == true);
101+
}
102+
103+
for (size_t i = 0; i < SZ; ++i) {
104+
control_words[i] = false;
105+
EXPECT_TRUE(!control_words[i].is_default() && control_words[i] == false);
106+
}
107+
108+
for (size_t i = 0; i < SZ; ++i) {
109+
if ((i%2) == 0) {
110+
control_words[i] = TriBool(true);
111+
}
112+
else {
113+
control_words[i] = TriBool(false);
114+
}
115+
}
116+
117+
// test copy constructor(default)
118+
copy = control_words;
119+
for (size_t i = 0; i < SZ; ++i) {
120+
if ((i%2) == 0) {
121+
EXPECT_TRUE(!copy[i].is_default() && copy[i] == true)
122+
<< "even value must be true.";
123+
}
124+
else {
125+
EXPECT_TRUE(!copy[i].is_default() && copy[i] == false)
126+
<< "odd value must be false.";
127+
}
128+
}
129+
130+
// test const operator[](fastpath)
131+
const TriBoolArray<SZ, T>& cref = control_words;
132+
for (size_t i = 0; i < SZ; ++i) {
133+
if ((i%2) == 0) {
134+
EXPECT_TRUE(!cref[i].is_default() && cref[i] == true)
135+
<< "even value must be true.";
136+
}
137+
else {
138+
EXPECT_TRUE(!cref[i].is_default() && cref[i] == false)
139+
<< "odd value must be false.";
140+
}
141+
}
142+
143+
EXPECT_GE(sizeof(control_words) * 8, (2 * SZ)) << "allocated too less";
144+
EXPECT_LE(sizeof(control_words), (((2 * SZ) / (sizeof(T) * 8) + 1) * sizeof(T)))
145+
<< "allocated too much";
146+
}
147+
148+
// because doit probably can't allocate jumbo arrays on stack, use static members
149+
static TriBoolArray<SZ, T> control_words;
150+
static TriBoolArray<SZ, T> copy;
151+
};
152+
153+
template<size_t SZ, typename T>
154+
TriBoolArray<SZ, T> Tester<SZ, T>::control_words;
155+
156+
template<size_t SZ, typename T>
157+
TriBoolArray<SZ, T> Tester<SZ, T>::copy;
158+
159+
TEST(tribool, TriBoolArray) {
160+
Tester<1, int>::doit();
161+
Tester<2, int>::doit();
162+
Tester<3, int>::doit();
163+
Tester<7, int>::doit();
164+
Tester<8, int>::doit();
165+
Tester<14, int>::doit();
166+
Tester<16, int>::doit();
167+
Tester<27, int>::doit();
168+
Tester<32, int>::doit();
169+
Tester<34, int>::doit();
170+
Tester<81, int>::doit();
171+
Tester<128, int>::doit();
172+
Tester<328, int>::doit(); // the no of intrinsics in jdk15
173+
174+
Tester<1024, int>::doit();
175+
Tester<1025, int>::doit();
176+
177+
Tester<4 <<10/*4k*/ , int>::doit();
178+
Tester<16<<10/*16k*/, int>::doit();
179+
Tester<32<<10/*32k*/, int>::doit();
180+
Tester<1 <<20/*1M*/ , int>::doit();
181+
Tester<4 <<20/*4M*/ , int>::doit();
182+
}
183+
184+
TriBool global_single;
185+
TriBoolArray<2, unsigned int> global_tuple;
186+
TEST(tribool, StaticInitializer) {
187+
EXPECT_TRUE(global_single.is_default());
188+
EXPECT_TRUE(global_tuple[0].is_default());
189+
EXPECT_TRUE(global_tuple[1].is_default());
190+
}

‎test/hotspot/jtreg/compiler/escapeAnalysis/TestGetClass.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,6 +28,9 @@
2828
* @run main/othervm -XX:-TieredCompilation -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_getClass
2929
* -XX:CompileCommand=quiet -XX:CompileCommand=compileonly,compiler.escapeAnalysis.TestGetClass::test
3030
* -XX:+PrintCompilation compiler.escapeAnalysis.TestGetClass
31+
* @run main/othervm -XX:-TieredCompilation -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:ControlIntrinsic=-_getClass
32+
* -XX:CompileCommand=quiet -XX:CompileCommand=compileonly,compiler.escapeAnalysis.TestGetClass::test
33+
* -XX:+PrintCompilation compiler.escapeAnalysis.TestGetClass
3134
*/
3235

3336
package compiler.escapeAnalysis;

‎test/hotspot/jtreg/compiler/intrinsics/IntrinsicAvailableTest.java

+30-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,25 @@
3939
* -XX:+WhiteBoxAPI
4040
* -XX:-UseCRC32Intrinsics
4141
* compiler.intrinsics.IntrinsicAvailableTest
42+
* @run main/othervm -Xbootclasspath/a:.
43+
* -XX:+UnlockDiagnosticVMOptions
44+
* -XX:+WhiteBoxAPI
45+
* -XX:ControlIntrinsic=+_updateCRC32
46+
* -XX:-UseCRC32Intrinsics
47+
* compiler.intrinsics.IntrinsicAvailableTest
48+
* @run main/othervm -Xbootclasspath/a:.
49+
* -XX:+UnlockDiagnosticVMOptions
50+
* -XX:+WhiteBoxAPI
51+
* -XX:ControlIntrinsic=-_updateCRC32
52+
* -XX:+UseCRC32Intrinsics
53+
* compiler.intrinsics.IntrinsicAvailableTest
54+
*
55+
* @run main/othervm -Xbootclasspath/a:.
56+
* -XX:+UnlockDiagnosticVMOptions
57+
* -XX:+WhiteBoxAPI
58+
* -XX:ControlIntrinsic=+_updateCRC32
59+
* -XX:+UseCRC32Intrinsics
60+
* compiler.intrinsics.IntrinsicAvailableTest
4261
*/
4362

4463

@@ -93,7 +112,17 @@ public boolean isOsr() {
93112
}
94113

95114
protected void checkIntrinsicForCompilationLevel(Executable method, int compLevel) throws Exception {
96-
boolean intrinsicEnabled = Boolean.valueOf(getVMOption("UseCRC32Intrinsics"));
115+
boolean intrinsicEnabled = true;
116+
String controlIntrinsic = getVMOption("ControlIntrinsic", "");
117+
118+
if (controlIntrinsic.contains("+_updateCRC32")) {
119+
intrinsicEnabled = true;
120+
} else if (controlIntrinsic.contains("-_updateCRC32")) {
121+
intrinsicEnabled = false;
122+
}
123+
124+
intrinsicEnabled &= Boolean.valueOf(getVMOption("UseCRC32Intrinsics"));
125+
97126
boolean intrinsicAvailable = WHITE_BOX.isIntrinsicAvailable(method,
98127
compLevel);
99128

‎test/hotspot/jtreg/compiler/intrinsics/IntrinsicDisabledTest.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,26 @@
3939
* -XX:CompileCommand=option,jdk.internal.misc.Unsafe::putChar,ccstrlist,DisableIntrinsic,_getCharVolatile,_getInt
4040
* -XX:CompileCommand=option,jdk.internal.misc.Unsafe::putCharVolatile,ccstrlist,DisableIntrinsic,_getIntVolatile
4141
* compiler.intrinsics.IntrinsicDisabledTest
42-
*/
42+
* @run main/othervm -Xbootclasspath/a:.
43+
* -XX:+UnlockDiagnosticVMOptions
44+
* -XX:+WhiteBoxAPI
45+
* -XX:ControlIntrinsic=-_putCharVolatile,-_putInt
46+
* -XX:ControlIntrinsic=-_putIntVolatile
47+
* -XX:CompileCommand=option,jdk.internal.misc.Unsafe::putChar,ccstrlist,ControlIntrinsic,-_getCharVolatile,-_getInt
48+
* -XX:CompileCommand=option,jdk.internal.misc.Unsafe::putCharVolatile,ccstrlist,ControlIntrinsic,-_getIntVolatile
49+
* compiler.intrinsics.IntrinsicDisabledTest
50+
* @run main/othervm -Xbootclasspath/a:.
51+
* -XX:+UnlockDiagnosticVMOptions
52+
* -XX:+WhiteBoxAPI
53+
* -XX:ControlIntrinsic=+putIntVolatile,+_putCharVolatile,+_putInt
54+
* -XX:DisableIntrinsic=_putCharVolatile,_putInt
55+
* -XX:DisableIntrinsic=_putIntVolatile
56+
* -XX:CompileCommand=option,jdk.internal.misc.Unsafe::putChar,ccstrlist,ControlIntrinsic,+_getCharVolatile,+_getInt
57+
* -XX:CompileCommand=option,jdk.internal.misc.Unsafe::putCharVolatile,ccstrlist,ControlIntrinsic,+_getIntVolatile
58+
* -XX:CompileCommand=option,jdk.internal.misc.Unsafe::putChar,ccstrlist,DisableIntrinsic,_getCharVolatile,_getInt
59+
* -XX:CompileCommand=option,jdk.internal.misc.Unsafe::putCharVolatile,ccstrlist,DisableIntrinsic,_getIntVolatile
60+
* compiler.intrinsics.IntrinsicDisabledTest
61+
*/
4362

4463
package compiler.intrinsics;
4564

0 commit comments

Comments
 (0)
Please sign in to comment.