Eliminate map checks of constant values.
R=ulan@chromium.org Review URL: https://chromiumcodereview.appspot.com/19954005 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@15819 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
b8d7bee4a3
commit
babce318d1
@ -2035,9 +2035,14 @@ LInstruction* LChunkBuilder::DoCheckInstanceType(HCheckInstanceType* instr) {
|
||||
|
||||
|
||||
LInstruction* LChunkBuilder::DoCheckPrototypeMaps(HCheckPrototypeMaps* instr) {
|
||||
LUnallocated* temp1 = TempRegister();
|
||||
LOperand* temp2 = TempRegister();
|
||||
LUnallocated* temp1 = NULL;
|
||||
LOperand* temp2 = NULL;
|
||||
if (!instr->CanOmitPrototypeChecks()) {
|
||||
temp1 = TempRegister();
|
||||
temp2 = TempRegister();
|
||||
}
|
||||
LCheckPrototypeMaps* result = new(zone()) LCheckPrototypeMaps(temp1, temp2);
|
||||
if (instr->CanOmitPrototypeChecks()) return result;
|
||||
return AssignEnvironment(result);
|
||||
}
|
||||
|
||||
@ -2049,8 +2054,10 @@ LInstruction* LChunkBuilder::DoCheckFunction(HCheckFunction* instr) {
|
||||
|
||||
|
||||
LInstruction* LChunkBuilder::DoCheckMaps(HCheckMaps* instr) {
|
||||
LOperand* value = UseRegisterAtStart(instr->value());
|
||||
LOperand* value = NULL;
|
||||
if (!instr->CanOmitMapChecks()) value = UseRegisterAtStart(instr->value());
|
||||
LInstruction* result = new(zone()) LCheckMaps(value);
|
||||
if (instr->CanOmitMapChecks()) return result;
|
||||
return AssignEnvironment(result);
|
||||
}
|
||||
|
||||
|
@ -5232,6 +5232,7 @@ void LCodeGen::DoCheckMapCommon(Register map_reg,
|
||||
|
||||
|
||||
void LCodeGen::DoCheckMaps(LCheckMaps* instr) {
|
||||
if (instr->hydrogen()->CanOmitMapChecks()) return;
|
||||
Register map_reg = scratch0();
|
||||
LOperand* input = instr->value();
|
||||
ASSERT(input->IsRegister());
|
||||
@ -5304,6 +5305,8 @@ void LCodeGen::DoClampTToUint8(LClampTToUint8* instr) {
|
||||
|
||||
|
||||
void LCodeGen::DoCheckPrototypeMaps(LCheckPrototypeMaps* instr) {
|
||||
if (instr->hydrogen()->CanOmitPrototypeChecks()) return;
|
||||
|
||||
Register prototype_reg = ToRegister(instr->temp());
|
||||
Register map_reg = ToRegister(instr->temp2());
|
||||
|
||||
@ -5312,12 +5315,10 @@ void LCodeGen::DoCheckPrototypeMaps(LCheckPrototypeMaps* instr) {
|
||||
|
||||
ASSERT(prototypes->length() == maps->length());
|
||||
|
||||
if (!instr->hydrogen()->CanOmitPrototypeChecks()) {
|
||||
for (int i = 0; i < prototypes->length(); i++) {
|
||||
__ LoadHeapObject(prototype_reg, prototypes->at(i));
|
||||
__ ldr(map_reg, FieldMemOperand(prototype_reg, HeapObject::kMapOffset));
|
||||
DoCheckMapCommon(map_reg, maps->at(i), instr->environment());
|
||||
}
|
||||
for (int i = 0; i < prototypes->length(); i++) {
|
||||
__ LoadHeapObject(prototype_reg, prototypes->at(i));
|
||||
__ ldr(map_reg, FieldMemOperand(prototype_reg, HeapObject::kMapOffset));
|
||||
DoCheckMapCommon(map_reg, maps->at(i), instr->environment());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -884,7 +884,8 @@ HValue* CodeStubGraphBuilder<StoreGlobalStub>::BuildCodeInitializedStub() {
|
||||
// Check that the map of the global has not changed: use a placeholder map
|
||||
// that will be replaced later with the global object's map.
|
||||
Handle<Map> placeholder_map = isolate()->factory()->meta_map();
|
||||
AddInstruction(HCheckMaps::New(receiver, placeholder_map, zone()));
|
||||
AddInstruction(HCheckMaps::New(
|
||||
receiver, placeholder_map, zone(), top_info()));
|
||||
|
||||
HValue* cell = Add<HConstant>(placeholder_cell, Representation::Tagged());
|
||||
HObjectAccess access(HObjectAccess::ForCellPayload(isolate()));
|
||||
|
@ -307,6 +307,9 @@ DEFINE_int(parallel_recompilation_delay, 0,
|
||||
DEFINE_bool(omit_prototype_checks_for_leaf_maps, true,
|
||||
"do not emit prototype checks if all prototypes have leaf maps, "
|
||||
"deoptimize the optimized code if the layout of the maps changes.")
|
||||
DEFINE_bool(omit_map_checks_for_leaf_maps, true,
|
||||
"do not emit check maps for constant values that have a leaf map, "
|
||||
"deoptimize the optimized code if the layout of the maps changes.")
|
||||
|
||||
// Experimental profiler changes.
|
||||
DEFINE_bool(experimental_profiler, true, "enable all profiler experiments")
|
||||
|
@ -1680,7 +1680,7 @@ void HCheckMaps::PrintDataTo(StringStream* stream) {
|
||||
for (int i = 1; i < map_set()->length(); ++i) {
|
||||
stream->Add(",%p", *map_set()->at(i));
|
||||
}
|
||||
stream->Add("]");
|
||||
stream->Add("]%s", CanOmitMapChecks() ? "(omitted)" : "");
|
||||
}
|
||||
|
||||
|
||||
@ -2775,6 +2775,22 @@ HLoadNamedFieldPolymorphic::HLoadNamedFieldPolymorphic(HValue* context,
|
||||
}
|
||||
|
||||
|
||||
HCheckMaps* HCheckMaps::New(HValue* value,
|
||||
Handle<Map> map,
|
||||
Zone* zone,
|
||||
CompilationInfo* info,
|
||||
HValue* typecheck) {
|
||||
HCheckMaps* check_map = new(zone) HCheckMaps(value, zone, typecheck);
|
||||
check_map->map_set_.Add(map, zone);
|
||||
if (map->CanOmitMapChecks() &&
|
||||
value->IsConstant() &&
|
||||
HConstant::cast(value)->InstanceOf(map)) {
|
||||
check_map->omit(info);
|
||||
}
|
||||
return check_map;
|
||||
}
|
||||
|
||||
|
||||
void HCheckMaps::FinalizeUniqueValueId() {
|
||||
if (!map_unique_ids_.is_empty()) return;
|
||||
Zone* zone = block()->zone();
|
||||
|
@ -2738,12 +2738,7 @@ class HLoadExternalArrayPointer: public HUnaryOperation {
|
||||
class HCheckMaps: public HTemplateInstruction<2> {
|
||||
public:
|
||||
static HCheckMaps* New(HValue* value, Handle<Map> map, Zone* zone,
|
||||
HValue *typecheck = NULL) {
|
||||
HCheckMaps* check_map = new(zone) HCheckMaps(value, zone, typecheck);
|
||||
check_map->map_set_.Add(map, zone);
|
||||
return check_map;
|
||||
}
|
||||
|
||||
CompilationInfo* info, HValue *typecheck = NULL);
|
||||
static HCheckMaps* New(HValue* value, SmallMapList* maps, Zone* zone,
|
||||
HValue *typecheck = NULL) {
|
||||
HCheckMaps* check_map = new(zone) HCheckMaps(value, zone, typecheck);
|
||||
@ -2777,6 +2772,8 @@ class HCheckMaps: public HTemplateInstruction<2> {
|
||||
return check_map;
|
||||
}
|
||||
|
||||
bool CanOmitMapChecks() { return omit_; }
|
||||
|
||||
virtual bool HasEscapingOperandAt(int index) { return false; }
|
||||
virtual Representation RequiredInputRepresentation(int index) {
|
||||
return Representation::Tagged();
|
||||
@ -2812,7 +2809,7 @@ class HCheckMaps: public HTemplateInstruction<2> {
|
||||
private:
|
||||
// Clients should use one of the static New* methods above.
|
||||
HCheckMaps(HValue* value, Zone *zone, HValue* typecheck)
|
||||
: map_unique_ids_(0, zone) {
|
||||
: omit_(false), map_unique_ids_(0, zone) {
|
||||
SetOperandAt(0, value);
|
||||
// Use the object value for the dependency if NULL is passed.
|
||||
// TODO(titzer): do GVN flags already express this dependency?
|
||||
@ -2824,6 +2821,16 @@ class HCheckMaps: public HTemplateInstruction<2> {
|
||||
SetGVNFlag(kDependsOnElementsKind);
|
||||
}
|
||||
|
||||
void omit(CompilationInfo* info) {
|
||||
omit_ = true;
|
||||
for (int i = 0; i < map_set_.length(); i++) {
|
||||
Handle<Map> map = map_set_.at(i);
|
||||
map->AddDependentCompilationInfo(DependentCode::kPrototypeCheckGroup,
|
||||
info);
|
||||
}
|
||||
}
|
||||
|
||||
bool omit_;
|
||||
SmallMapList map_set_;
|
||||
ZoneList<UniqueValueId> map_unique_ids_;
|
||||
};
|
||||
@ -3302,6 +3309,11 @@ class HConstant: public HTemplateInstruction<0> {
|
||||
return handle_;
|
||||
}
|
||||
|
||||
bool InstanceOf(Handle<Map> map) {
|
||||
return handle_->IsJSObject() &&
|
||||
Handle<JSObject>::cast(handle_)->map() == *map;
|
||||
}
|
||||
|
||||
bool IsSpecialDouble() const {
|
||||
return has_double_value_ &&
|
||||
(BitCast<int64_t>(double_value_) == BitCast<int64_t>(-0.0) ||
|
||||
|
@ -1045,7 +1045,7 @@ HValue* HGraphBuilder::BuildCheckHeapObject(HValue* obj) {
|
||||
|
||||
HValue* HGraphBuilder::BuildCheckMap(HValue* obj,
|
||||
Handle<Map> map) {
|
||||
HCheckMaps* check = HCheckMaps::New(obj, map, zone());
|
||||
HCheckMaps* check = HCheckMaps::New(obj, map, zone(), top_info());
|
||||
AddInstruction(check);
|
||||
return check;
|
||||
}
|
||||
@ -1208,7 +1208,7 @@ HInstruction* HGraphBuilder::BuildUncheckedMonomorphicElementAccess(
|
||||
if (is_store && (fast_elements || fast_smi_only_elements) &&
|
||||
store_mode != STORE_NO_TRANSITION_HANDLE_COW) {
|
||||
HCheckMaps* check_cow_map = HCheckMaps::New(
|
||||
elements, isolate()->factory()->fixed_array_map(), zone);
|
||||
elements, isolate()->factory()->fixed_array_map(), zone, top_info());
|
||||
check_cow_map->ClearGVNFlag(kDependsOnElementsKind);
|
||||
AddInstruction(check_cow_map);
|
||||
}
|
||||
@ -1276,7 +1276,8 @@ HInstruction* HGraphBuilder::BuildUncheckedMonomorphicElementAccess(
|
||||
length);
|
||||
} else {
|
||||
HCheckMaps* check_cow_map = HCheckMaps::New(
|
||||
elements, isolate()->factory()->fixed_array_map(), zone);
|
||||
elements, isolate()->factory()->fixed_array_map(),
|
||||
zone, top_info());
|
||||
check_cow_map->ClearGVNFlag(kDependsOnElementsKind);
|
||||
AddInstruction(check_cow_map);
|
||||
}
|
||||
@ -4450,7 +4451,7 @@ void HOptimizedGraphBuilder::VisitArrayLiteral(ArrayLiteral* expr) {
|
||||
// De-opt if elements kind changed from boilerplate_elements_kind.
|
||||
Handle<Map> map = Handle<Map>(original_boilerplate_object->map(),
|
||||
isolate());
|
||||
AddInstruction(HCheckMaps::New(literal, map, zone()));
|
||||
AddInstruction(HCheckMaps::New(literal, map, zone(), top_info()));
|
||||
}
|
||||
|
||||
// The array is expected in the bailout environment during computation
|
||||
@ -4541,7 +4542,7 @@ static Representation ComputeLoadStoreRepresentation(Handle<Map> type,
|
||||
|
||||
void HOptimizedGraphBuilder::AddCheckMap(HValue* object, Handle<Map> map) {
|
||||
BuildCheckHeapObject(object);
|
||||
AddInstruction(HCheckMaps::New(object, map, zone()));
|
||||
AddInstruction(HCheckMaps::New(object, map, zone(), top_info()));
|
||||
}
|
||||
|
||||
|
||||
@ -5506,7 +5507,8 @@ HInstruction* HOptimizedGraphBuilder::BuildMonomorphicElementAccess(
|
||||
Handle<Map> map,
|
||||
bool is_store,
|
||||
KeyedAccessStoreMode store_mode) {
|
||||
HCheckMaps* mapcheck = HCheckMaps::New(object, map, zone(), dependency);
|
||||
HCheckMaps* mapcheck = HCheckMaps::New(
|
||||
object, map, zone(), top_info(), dependency);
|
||||
AddInstruction(mapcheck);
|
||||
if (dependency) {
|
||||
mapcheck->ClearGVNFlag(kDependsOnElementsKind);
|
||||
@ -5690,7 +5692,7 @@ HValue* HOptimizedGraphBuilder::HandlePolymorphicElementAccess(
|
||||
if (is_store && !IsFastDoubleElementsKind(elements_kind)) {
|
||||
AddInstruction(HCheckMaps::New(
|
||||
elements, isolate()->factory()->fixed_array_map(),
|
||||
zone(), mapcompare));
|
||||
zone(), top_info(), mapcompare));
|
||||
}
|
||||
if (map->IsJSArray()) {
|
||||
HInstruction* length = AddLoad(object, HObjectAccess::ForArrayLength(),
|
||||
|
@ -5802,6 +5802,7 @@ void LCodeGen::DoCheckMapCommon(Register reg,
|
||||
|
||||
|
||||
void LCodeGen::DoCheckMaps(LCheckMaps* instr) {
|
||||
if (instr->hydrogen()->CanOmitMapChecks()) return;
|
||||
LOperand* input = instr->value();
|
||||
ASSERT(input->IsRegister());
|
||||
Register reg = ToRegister(input);
|
||||
@ -5992,6 +5993,7 @@ void LCodeGen::DoClampTToUint8NoSSE2(LClampTToUint8NoSSE2* instr) {
|
||||
|
||||
|
||||
void LCodeGen::DoCheckPrototypeMaps(LCheckPrototypeMaps* instr) {
|
||||
if (instr->hydrogen()->CanOmitPrototypeChecks()) return;
|
||||
Register reg = ToRegister(instr->temp());
|
||||
|
||||
ZoneList<Handle<JSObject> >* prototypes = instr->prototypes();
|
||||
@ -5999,11 +6001,9 @@ void LCodeGen::DoCheckPrototypeMaps(LCheckPrototypeMaps* instr) {
|
||||
|
||||
ASSERT(prototypes->length() == maps->length());
|
||||
|
||||
if (!instr->hydrogen()->CanOmitPrototypeChecks()) {
|
||||
for (int i = 0; i < prototypes->length(); i++) {
|
||||
__ LoadHeapObject(reg, prototypes->at(i));
|
||||
DoCheckMapCommon(reg, maps->at(i), instr);
|
||||
}
|
||||
for (int i = 0; i < prototypes->length(); i++) {
|
||||
__ LoadHeapObject(reg, prototypes->at(i));
|
||||
DoCheckMapCommon(reg, maps->at(i), instr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2063,8 +2063,10 @@ LInstruction* LChunkBuilder::DoCheckInstanceType(HCheckInstanceType* instr) {
|
||||
|
||||
|
||||
LInstruction* LChunkBuilder::DoCheckPrototypeMaps(HCheckPrototypeMaps* instr) {
|
||||
LUnallocated* temp = TempRegister();
|
||||
LUnallocated* temp = NULL;
|
||||
if (!instr->CanOmitPrototypeChecks()) temp = TempRegister();
|
||||
LCheckPrototypeMaps* result = new(zone()) LCheckPrototypeMaps(temp);
|
||||
if (instr->CanOmitPrototypeChecks()) return result;
|
||||
return AssignEnvironment(result);
|
||||
}
|
||||
|
||||
@ -2081,8 +2083,10 @@ LInstruction* LChunkBuilder::DoCheckFunction(HCheckFunction* instr) {
|
||||
|
||||
|
||||
LInstruction* LChunkBuilder::DoCheckMaps(HCheckMaps* instr) {
|
||||
LOperand* value = UseRegisterAtStart(instr->value());
|
||||
LOperand* value = NULL;
|
||||
if (!instr->CanOmitMapChecks()) value = UseRegisterAtStart(instr->value());
|
||||
LCheckMaps* result = new(zone()) LCheckMaps(value);
|
||||
if (instr->CanOmitMapChecks()) return result;
|
||||
return AssignEnvironment(result);
|
||||
}
|
||||
|
||||
|
@ -3669,6 +3669,12 @@ bool Map::CanOmitPrototypeChecks() {
|
||||
}
|
||||
|
||||
|
||||
bool Map::CanOmitMapChecks() {
|
||||
return !HasTransitionArray() && !is_dictionary_map() &&
|
||||
FLAG_omit_map_checks_for_leaf_maps;
|
||||
}
|
||||
|
||||
|
||||
int DependentCode::number_of_entries(DependencyGroup group) {
|
||||
if (length() == 0) return 0;
|
||||
return Smi::cast(get(group))->value();
|
||||
|
@ -5626,6 +5626,7 @@ class Map: public HeapObject {
|
||||
inline void NotifyLeafMapLayoutChange();
|
||||
|
||||
inline bool CanOmitPrototypeChecks();
|
||||
inline bool CanOmitMapChecks();
|
||||
|
||||
void AddDependentCompilationInfo(DependentCode::DependencyGroup group,
|
||||
CompilationInfo* info);
|
||||
|
@ -4954,6 +4954,7 @@ void LCodeGen::DoCheckMapCommon(Register reg,
|
||||
|
||||
|
||||
void LCodeGen::DoCheckMaps(LCheckMaps* instr) {
|
||||
if (instr->hydrogen()->CanOmitMapChecks()) return;
|
||||
LOperand* input = instr->value();
|
||||
ASSERT(input->IsRegister());
|
||||
Register reg = ToRegister(input);
|
||||
@ -5021,6 +5022,7 @@ void LCodeGen::DoClampTToUint8(LClampTToUint8* instr) {
|
||||
|
||||
|
||||
void LCodeGen::DoCheckPrototypeMaps(LCheckPrototypeMaps* instr) {
|
||||
if (instr->hydrogen()->CanOmitPrototypeChecks()) return;
|
||||
Register reg = ToRegister(instr->temp());
|
||||
|
||||
ZoneList<Handle<JSObject> >* prototypes = instr->prototypes();
|
||||
@ -5028,11 +5030,9 @@ void LCodeGen::DoCheckPrototypeMaps(LCheckPrototypeMaps* instr) {
|
||||
|
||||
ASSERT(prototypes->length() == maps->length());
|
||||
|
||||
if (!instr->hydrogen()->CanOmitPrototypeChecks()) {
|
||||
for (int i = 0; i < prototypes->length(); i++) {
|
||||
__ LoadHeapObject(reg, prototypes->at(i));
|
||||
DoCheckMapCommon(reg, maps->at(i), instr);
|
||||
}
|
||||
for (int i = 0; i < prototypes->length(); i++) {
|
||||
__ LoadHeapObject(reg, prototypes->at(i));
|
||||
DoCheckMapCommon(reg, maps->at(i), instr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1949,8 +1949,10 @@ LInstruction* LChunkBuilder::DoCheckInstanceType(HCheckInstanceType* instr) {
|
||||
|
||||
|
||||
LInstruction* LChunkBuilder::DoCheckPrototypeMaps(HCheckPrototypeMaps* instr) {
|
||||
LUnallocated* temp = TempRegister();
|
||||
LUnallocated* temp = NULL;
|
||||
if (!instr->CanOmitPrototypeChecks()) temp = TempRegister();
|
||||
LCheckPrototypeMaps* result = new(zone()) LCheckPrototypeMaps(temp);
|
||||
if (instr->CanOmitPrototypeChecks()) return result;
|
||||
return AssignEnvironment(result);
|
||||
}
|
||||
|
||||
@ -1962,8 +1964,10 @@ LInstruction* LChunkBuilder::DoCheckFunction(HCheckFunction* instr) {
|
||||
|
||||
|
||||
LInstruction* LChunkBuilder::DoCheckMaps(HCheckMaps* instr) {
|
||||
LOperand* value = UseRegisterAtStart(instr->value());
|
||||
LOperand* value = NULL;
|
||||
if (!instr->CanOmitMapChecks()) value = UseRegisterAtStart(instr->value());
|
||||
LCheckMaps* result = new(zone()) LCheckMaps(value);
|
||||
if (instr->CanOmitMapChecks()) return result;
|
||||
return AssignEnvironment(result);
|
||||
}
|
||||
|
||||
|
55
test/mjsunit/omit-constant-mapcheck.js
Normal file
55
test/mjsunit/omit-constant-mapcheck.js
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright 2013 the V8 project authors. All rights reserved.
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following
|
||||
// disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived
|
||||
// from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Flags: --allow-natives-syntax
|
||||
|
||||
var g1 = { a:1 }
|
||||
|
||||
function load() {
|
||||
return g1.a;
|
||||
}
|
||||
|
||||
assertEquals(1, load());
|
||||
assertEquals(1, load());
|
||||
%OptimizeFunctionOnNextCall(load);
|
||||
assertEquals(1, load());
|
||||
delete g1.a;
|
||||
assertEquals(undefined, load());
|
||||
|
||||
var g2 = { a:2 }
|
||||
|
||||
function load2() {
|
||||
return g2.a;
|
||||
}
|
||||
|
||||
assertEquals(2, load2());
|
||||
assertEquals(2, load2());
|
||||
%OptimizeFunctionOnNextCall(load2);
|
||||
assertEquals(2, load2());
|
||||
g2.b = 10;
|
||||
g2.a = 5;
|
||||
assertEquals(5, load2());
|
Loading…
Reference in New Issue
Block a user