Add Crankshaft support for smi-only elements
Review URL: http://codereview.chromium.org/8002019 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@9426 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
6dee868e03
commit
0455aadbeb
@ -3346,6 +3346,13 @@ void LCodeGen::DoStoreKeyedFastElement(LStoreKeyedFastElement* instr) {
|
||||
Register key = instr->key()->IsRegister() ? ToRegister(instr->key()) : no_reg;
|
||||
Register scratch = scratch0();
|
||||
|
||||
// This instruction cannot handle the FAST_SMI_ONLY_ELEMENTS -> FAST_ELEMENTS
|
||||
// conversion, so it deopts in that case.
|
||||
if (instr->hydrogen()->ValueNeedsSmiCheck()) {
|
||||
__ tst(value, Operand(kSmiTagMask));
|
||||
DeoptimizeIf(ne, instr->environment());
|
||||
}
|
||||
|
||||
// Do the store.
|
||||
if (instr->key()->IsConstantOperand()) {
|
||||
ASSERT(!instr->hydrogen()->NeedsWriteBarrier());
|
||||
|
@ -908,8 +908,8 @@ class HDeoptimize: public HControlInstruction {
|
||||
class HGoto: public HTemplateControlInstruction<1, 0> {
|
||||
public:
|
||||
explicit HGoto(HBasicBlock* target) {
|
||||
SetSuccessorAt(0, target);
|
||||
}
|
||||
SetSuccessorAt(0, target);
|
||||
}
|
||||
|
||||
virtual Representation RequiredInputRepresentation(int index) const {
|
||||
return Representation::None();
|
||||
@ -3740,7 +3740,9 @@ class HStoreNamedGeneric: public HTemplateInstruction<3> {
|
||||
|
||||
class HStoreKeyedFastElement: public HTemplateInstruction<3> {
|
||||
public:
|
||||
HStoreKeyedFastElement(HValue* obj, HValue* key, HValue* val) {
|
||||
HStoreKeyedFastElement(HValue* obj, HValue* key, HValue* val,
|
||||
ElementsKind elements_kind = FAST_ELEMENTS)
|
||||
: elements_kind_(elements_kind) {
|
||||
SetOperandAt(0, obj);
|
||||
SetOperandAt(1, key);
|
||||
SetOperandAt(2, val);
|
||||
@ -3757,14 +3759,28 @@ class HStoreKeyedFastElement: public HTemplateInstruction<3> {
|
||||
HValue* object() { return OperandAt(0); }
|
||||
HValue* key() { return OperandAt(1); }
|
||||
HValue* value() { return OperandAt(2); }
|
||||
bool value_is_smi() {
|
||||
return elements_kind_ == FAST_SMI_ONLY_ELEMENTS;
|
||||
}
|
||||
|
||||
bool NeedsWriteBarrier() {
|
||||
return StoringValueNeedsWriteBarrier(value());
|
||||
if (value_is_smi()) {
|
||||
return false;
|
||||
} else {
|
||||
return StoringValueNeedsWriteBarrier(value());
|
||||
}
|
||||
}
|
||||
|
||||
bool ValueNeedsSmiCheck() {
|
||||
return value_is_smi();
|
||||
}
|
||||
|
||||
virtual void PrintDataTo(StringStream* stream);
|
||||
|
||||
DECLARE_CONCRETE_INSTRUCTION(StoreKeyedFastElement)
|
||||
|
||||
private:
|
||||
ElementsKind elements_kind_;
|
||||
};
|
||||
|
||||
|
||||
|
164
src/hydrogen.cc
164
src/hydrogen.cc
@ -3347,7 +3347,42 @@ void HGraphBuilder::VisitArrayLiteral(ArrayLiteral* expr) {
|
||||
new(zone()) HConstant(Handle<Object>(Smi::FromInt(i)),
|
||||
Representation::Integer32()));
|
||||
if (FLAG_smi_only_arrays) {
|
||||
HInstruction* elements_kind =
|
||||
AddInstruction(new(zone()) HElementsKind(literal));
|
||||
HBasicBlock* store_fast = graph()->CreateBasicBlock();
|
||||
// Two empty blocks to satisfy edge split form.
|
||||
HBasicBlock* store_fast_edgesplit1 = graph()->CreateBasicBlock();
|
||||
HBasicBlock* store_fast_edgesplit2 = graph()->CreateBasicBlock();
|
||||
HBasicBlock* store_generic = graph()->CreateBasicBlock();
|
||||
HBasicBlock* check_smi_only_elements = graph()->CreateBasicBlock();
|
||||
HBasicBlock* join = graph()->CreateBasicBlock();
|
||||
|
||||
HIsSmiAndBranch* smicheck = new(zone()) HIsSmiAndBranch(value);
|
||||
smicheck->SetSuccessorAt(0, store_fast_edgesplit1);
|
||||
smicheck->SetSuccessorAt(1, check_smi_only_elements);
|
||||
current_block()->Finish(smicheck);
|
||||
store_fast_edgesplit1->Finish(new(zone()) HGoto(store_fast));
|
||||
|
||||
set_current_block(check_smi_only_elements);
|
||||
HCompareConstantEqAndBranch* smi_elements_check =
|
||||
new(zone()) HCompareConstantEqAndBranch(elements_kind,
|
||||
FAST_SMI_ONLY_ELEMENTS,
|
||||
Token::EQ_STRICT);
|
||||
smi_elements_check->SetSuccessorAt(0, store_generic);
|
||||
smi_elements_check->SetSuccessorAt(1, store_fast_edgesplit2);
|
||||
current_block()->Finish(smi_elements_check);
|
||||
store_fast_edgesplit2->Finish(new(zone()) HGoto(store_fast));
|
||||
|
||||
set_current_block(store_fast);
|
||||
AddInstruction(new(zone()) HStoreKeyedFastElement(elements, key, value));
|
||||
store_fast->Goto(join);
|
||||
|
||||
set_current_block(store_generic);
|
||||
AddInstruction(BuildStoreKeyedGeneric(literal, key, value));
|
||||
store_generic->Goto(join);
|
||||
|
||||
join->SetJoinId(expr->id());
|
||||
set_current_block(join);
|
||||
} else {
|
||||
AddInstruction(new(zone()) HStoreKeyedFastElement(elements, key, value));
|
||||
}
|
||||
@ -3979,6 +4014,30 @@ HInstruction* HGraphBuilder::BuildExternalArrayElementAccess(
|
||||
}
|
||||
|
||||
|
||||
HInstruction* HGraphBuilder::BuildFastElementAccess(HValue* elements,
|
||||
HValue* checked_key,
|
||||
HValue* val,
|
||||
ElementsKind elements_kind,
|
||||
bool is_store) {
|
||||
if (is_store) {
|
||||
ASSERT(val != NULL);
|
||||
if (elements_kind == FAST_DOUBLE_ELEMENTS) {
|
||||
return new(zone()) HStoreKeyedFastDoubleElement(
|
||||
elements, checked_key, val);
|
||||
} else { // FAST_ELEMENTS or FAST_SMI_ONLY_ELEMENTS.
|
||||
return new(zone()) HStoreKeyedFastElement(
|
||||
elements, checked_key, val, elements_kind);
|
||||
}
|
||||
}
|
||||
// It's an element load (!is_store).
|
||||
if (elements_kind == FAST_DOUBLE_ELEMENTS) {
|
||||
return new(zone()) HLoadKeyedFastDoubleElement(elements, checked_key);
|
||||
} else { // FAST_ELEMENTS or FAST_SMI_ONLY_ELEMENTS.
|
||||
return new(zone()) HLoadKeyedFastElement(elements, checked_key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HInstruction* HGraphBuilder::BuildMonomorphicElementAccess(HValue* object,
|
||||
HValue* key,
|
||||
HValue* val,
|
||||
@ -3988,15 +4047,18 @@ HInstruction* HGraphBuilder::BuildMonomorphicElementAccess(HValue* object,
|
||||
Handle<Map> map = expr->GetMonomorphicReceiverType();
|
||||
AddInstruction(new(zone()) HCheckNonSmi(object));
|
||||
HInstruction* mapcheck = AddInstruction(new(zone()) HCheckMap(object, map));
|
||||
if (!map->has_fast_elements() &&
|
||||
!map->has_fast_double_elements() &&
|
||||
bool fast_smi_only_elements = map->has_fast_smi_only_elements();
|
||||
bool fast_elements = map->has_fast_elements();
|
||||
bool fast_double_elements = map->has_fast_double_elements();
|
||||
if (!fast_smi_only_elements &&
|
||||
!fast_elements &&
|
||||
!fast_double_elements &&
|
||||
!map->has_external_array_elements()) {
|
||||
return is_store ? BuildStoreKeyedGeneric(object, key, val)
|
||||
: BuildLoadKeyedGeneric(object, key);
|
||||
}
|
||||
HInstruction* elements = AddInstruction(new(zone()) HLoadElements(object));
|
||||
bool fast_double_elements = map->has_fast_double_elements();
|
||||
if (is_store && map->has_fast_elements()) {
|
||||
if (is_store && (fast_elements || fast_smi_only_elements)) {
|
||||
AddInstruction(new(zone()) HCheckMap(
|
||||
elements, isolate()->factory()->fixed_array_map()));
|
||||
}
|
||||
@ -4011,28 +4073,15 @@ HInstruction* HGraphBuilder::BuildMonomorphicElementAccess(HValue* object,
|
||||
return BuildExternalArrayElementAccess(external_elements, checked_key,
|
||||
val, map->elements_kind(), is_store);
|
||||
}
|
||||
ASSERT(map->has_fast_elements() || fast_double_elements);
|
||||
ASSERT(fast_smi_only_elements || fast_elements || fast_double_elements);
|
||||
if (map->instance_type() == JS_ARRAY_TYPE) {
|
||||
length = AddInstruction(new(zone()) HJSArrayLength(object, mapcheck));
|
||||
} else {
|
||||
length = AddInstruction(new(zone()) HFixedArrayBaseLength(elements));
|
||||
}
|
||||
checked_key = AddInstruction(new(zone()) HBoundsCheck(key, length));
|
||||
if (is_store) {
|
||||
if (fast_double_elements) {
|
||||
return new(zone()) HStoreKeyedFastDoubleElement(elements,
|
||||
checked_key,
|
||||
val);
|
||||
} else {
|
||||
return new(zone()) HStoreKeyedFastElement(elements, checked_key, val);
|
||||
}
|
||||
} else {
|
||||
if (fast_double_elements) {
|
||||
return new(zone()) HLoadKeyedFastDoubleElement(elements, checked_key);
|
||||
} else {
|
||||
return new(zone()) HLoadKeyedFastElement(elements, checked_key);
|
||||
}
|
||||
}
|
||||
return BuildFastElementAccess(elements, checked_key, val,
|
||||
map->elements_kind(), is_store);
|
||||
}
|
||||
|
||||
|
||||
@ -4085,7 +4134,7 @@ HValue* HGraphBuilder::HandlePolymorphicElementAccess(HValue* object,
|
||||
for (ElementsKind elements_kind = FIRST_ELEMENTS_KIND;
|
||||
elements_kind <= LAST_ELEMENTS_KIND;
|
||||
elements_kind = ElementsKind(elements_kind + 1)) {
|
||||
// After having handled FAST_ELEMENTS, FAST_SMI_ELEMENTS,
|
||||
// After having handled FAST_ELEMENTS, FAST_SMI_ONLY_ELEMENTS,
|
||||
// FAST_DOUBLE_ELEMENTS and DICTIONARY_ELEMENTS, we need to add some code
|
||||
// that's executed for all external array cases.
|
||||
STATIC_ASSERT(LAST_EXTERNAL_ARRAY_ELEMENTS_KIND ==
|
||||
@ -4112,13 +4161,22 @@ HValue* HGraphBuilder::HandlePolymorphicElementAccess(HValue* object,
|
||||
if (elements_kind == FAST_SMI_ONLY_ELEMENTS ||
|
||||
elements_kind == FAST_ELEMENTS ||
|
||||
elements_kind == FAST_DOUBLE_ELEMENTS) {
|
||||
bool fast_double_elements =
|
||||
elements_kind == FAST_DOUBLE_ELEMENTS;
|
||||
if (is_store && !fast_double_elements) {
|
||||
if (is_store && elements_kind == FAST_SMI_ONLY_ELEMENTS) {
|
||||
AddInstruction(new(zone()) HCheckSmi(val));
|
||||
}
|
||||
if (is_store && elements_kind != FAST_DOUBLE_ELEMENTS) {
|
||||
AddInstruction(new(zone()) HCheckMap(
|
||||
elements, isolate()->factory()->fixed_array_map(),
|
||||
elements_kind_branch));
|
||||
}
|
||||
// TODO(jkummerow): The need for these two blocks could be avoided
|
||||
// in one of two ways:
|
||||
// (1) Introduce ElementsKinds for JSArrays that are distinct from
|
||||
// those for fast objects.
|
||||
// (2) Put the common instructions into a third "join" block. This
|
||||
// requires additional AST IDs that we can deopt to from inside
|
||||
// that join block. They must be added to the Property class (when
|
||||
// it's a keyed property) and registered in the full codegen.
|
||||
HBasicBlock* if_jsarray = graph()->CreateBasicBlock();
|
||||
HBasicBlock* if_fastobject = graph()->CreateBasicBlock();
|
||||
HHasInstanceTypeAndBranch* typecheck =
|
||||
@ -4129,37 +4187,12 @@ HValue* HGraphBuilder::HandlePolymorphicElementAccess(HValue* object,
|
||||
|
||||
set_current_block(if_jsarray);
|
||||
HInstruction* length;
|
||||
if (is_store && elements_kind == FAST_SMI_ONLY_ELEMENTS) {
|
||||
// For now, fall back to the generic stub for
|
||||
// FAST_SMI_ONLY_ELEMENTS
|
||||
access = AddInstruction(BuildStoreKeyedGeneric(object, key, val));
|
||||
} else {
|
||||
length = new(zone()) HJSArrayLength(object, typecheck);
|
||||
AddInstruction(length);
|
||||
checked_key = AddInstruction(new(zone()) HBoundsCheck(key, length));
|
||||
if (is_store) {
|
||||
if (fast_double_elements) {
|
||||
access = AddInstruction(
|
||||
new(zone()) HStoreKeyedFastDoubleElement(elements,
|
||||
checked_key,
|
||||
val));
|
||||
} else {
|
||||
access = AddInstruction(
|
||||
new(zone()) HStoreKeyedFastElement(elements,
|
||||
checked_key,
|
||||
val));
|
||||
}
|
||||
} else {
|
||||
if (fast_double_elements) {
|
||||
access = AddInstruction(
|
||||
new(zone()) HLoadKeyedFastDoubleElement(elements,
|
||||
checked_key));
|
||||
} else {
|
||||
access = AddInstruction(
|
||||
new(zone()) HLoadKeyedFastElement(elements, checked_key));
|
||||
}
|
||||
Push(access);
|
||||
}
|
||||
length = AddInstruction(new(zone()) HJSArrayLength(object, typecheck));
|
||||
checked_key = AddInstruction(new(zone()) HBoundsCheck(key, length));
|
||||
access = AddInstruction(BuildFastElementAccess(
|
||||
elements, checked_key, val, elements_kind, is_store));
|
||||
if (!is_store) {
|
||||
Push(access);
|
||||
}
|
||||
|
||||
*has_side_effects |= access->HasSideEffects();
|
||||
@ -4171,25 +4204,8 @@ HValue* HGraphBuilder::HandlePolymorphicElementAccess(HValue* object,
|
||||
set_current_block(if_fastobject);
|
||||
length = AddInstruction(new(zone()) HFixedArrayBaseLength(elements));
|
||||
checked_key = AddInstruction(new(zone()) HBoundsCheck(key, length));
|
||||
if (is_store) {
|
||||
if (fast_double_elements) {
|
||||
access = AddInstruction(
|
||||
new(zone()) HStoreKeyedFastDoubleElement(elements,
|
||||
checked_key,
|
||||
val));
|
||||
} else {
|
||||
access = AddInstruction(
|
||||
new(zone()) HStoreKeyedFastElement(elements, checked_key, val));
|
||||
}
|
||||
} else {
|
||||
if (fast_double_elements) {
|
||||
access = AddInstruction(
|
||||
new(zone()) HLoadKeyedFastDoubleElement(elements, checked_key));
|
||||
} else {
|
||||
access = AddInstruction(
|
||||
new(zone()) HLoadKeyedFastElement(elements, checked_key));
|
||||
}
|
||||
}
|
||||
access = AddInstruction(BuildFastElementAccess(
|
||||
elements, checked_key, val, elements_kind, is_store));
|
||||
} else if (elements_kind == DICTIONARY_ELEMENTS) {
|
||||
if (is_store) {
|
||||
access = AddInstruction(BuildStoreKeyedGeneric(object, key, val));
|
||||
|
@ -942,6 +942,11 @@ class HGraphBuilder: public AstVisitor {
|
||||
HValue* val,
|
||||
ElementsKind elements_kind,
|
||||
bool is_store);
|
||||
HInstruction* BuildFastElementAccess(HValue* elements,
|
||||
HValue* checked_key,
|
||||
HValue* val,
|
||||
ElementsKind elements_kind,
|
||||
bool is_store);
|
||||
|
||||
HInstruction* BuildMonomorphicElementAccess(HValue* object,
|
||||
HValue* key,
|
||||
|
@ -3238,6 +3238,13 @@ void LCodeGen::DoStoreKeyedFastElement(LStoreKeyedFastElement* instr) {
|
||||
Register elements = ToRegister(instr->object());
|
||||
Register key = instr->key()->IsRegister() ? ToRegister(instr->key()) : no_reg;
|
||||
|
||||
// This instruction cannot handle the FAST_SMI_ONLY_ELEMENTS -> FAST_ELEMENTS
|
||||
// conversion, so it deopts in that case.
|
||||
if (instr->hydrogen()->ValueNeedsSmiCheck()) {
|
||||
__ test(value, Immediate(kSmiTagMask));
|
||||
DeoptimizeIf(not_zero, instr->environment());
|
||||
}
|
||||
|
||||
// Do the store.
|
||||
if (instr->key()->IsConstantOperand()) {
|
||||
ASSERT(!instr->hydrogen()->NeedsWriteBarrier());
|
||||
|
@ -1689,6 +1689,7 @@ MaybeObject* KeyedIC::ComputeMonomorphicStub(JSObject* receiver,
|
||||
Code* generic_stub) {
|
||||
Code* result = NULL;
|
||||
if (receiver->HasFastElements() ||
|
||||
receiver->HasFastSmiOnlyElements() ||
|
||||
receiver->HasExternalArrayElements() ||
|
||||
receiver->HasFastDoubleElements() ||
|
||||
receiver->HasDictionaryElements()) {
|
||||
|
@ -443,7 +443,7 @@ static Handle<Object> CreateArrayLiteralBoilerplate(
|
||||
for (int i = 0; i < content->length(); i++) {
|
||||
Object* current = content->get(i);
|
||||
ASSERT(!current->IsFixedArray());
|
||||
if (!current->IsSmi()) {
|
||||
if (!current->IsSmi() && !current->IsTheHole()) {
|
||||
has_non_smi = true;
|
||||
}
|
||||
}
|
||||
@ -467,7 +467,7 @@ static Handle<Object> CreateArrayLiteralBoilerplate(
|
||||
content->set(i, *result);
|
||||
has_non_smi = true;
|
||||
} else {
|
||||
if (!current->IsSmi()) {
|
||||
if (!current->IsSmi() && !current->IsTheHole()) {
|
||||
has_non_smi = true;
|
||||
}
|
||||
}
|
||||
@ -8250,6 +8250,8 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_OptimizeFunctionOnNextCall) {
|
||||
RUNTIME_FUNCTION(MaybeObject*, Runtime_GetOptimizationStatus) {
|
||||
HandleScope scope(isolate);
|
||||
ASSERT(args.length() == 1);
|
||||
// The least significant bit (after untagging) indicates whether the
|
||||
// function is currently optimized, regardless of reason.
|
||||
if (!V8::UseCrankshaft()) {
|
||||
return Smi::FromInt(4); // 4 == "never".
|
||||
}
|
||||
|
@ -3161,6 +3161,13 @@ void LCodeGen::DoStoreKeyedFastElement(LStoreKeyedFastElement* instr) {
|
||||
Register elements = ToRegister(instr->object());
|
||||
Register key = instr->key()->IsRegister() ? ToRegister(instr->key()) : no_reg;
|
||||
|
||||
// This instruction cannot handle the FAST_SMI_ONLY_ELEMENTS -> FAST_ELEMENTS
|
||||
// conversion, so it deopts in that case.
|
||||
if (instr->hydrogen()->ValueNeedsSmiCheck()) {
|
||||
Condition cc = masm()->CheckSmi(value);
|
||||
DeoptimizeIf(NegateCondition(cc), instr->environment());
|
||||
}
|
||||
|
||||
// Do the store.
|
||||
if (instr->key()->IsConstantOperand()) {
|
||||
ASSERT(!instr->hydrogen()->NeedsWriteBarrier());
|
||||
|
@ -125,3 +125,70 @@ assertKind(element_kind.external_unsigned_int_elements, new Uint32Array(23));
|
||||
assertKind(element_kind.external_float_elements, new Float32Array(7));
|
||||
assertKind(element_kind.external_double_elements, new Float64Array(0));
|
||||
assertKind(element_kind.external_pixel_elements, new PixelArray(512));
|
||||
|
||||
// Crankshaft support for smi-only array elements.
|
||||
function monomorphic(array) {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
array[i] = i + 10;
|
||||
}
|
||||
assertKind(element_kind.fast_smi_only_elements, array);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var a = array[i];
|
||||
assertEquals(i + 10, a);
|
||||
}
|
||||
}
|
||||
var smi_only = [1, 2, 3];
|
||||
for (var i = 0; i < 3; i++) monomorphic(smi_only);
|
||||
%OptimizeFunctionOnNextCall(monomorphic);
|
||||
monomorphic(smi_only);
|
||||
function polymorphic(array, expected_kind) {
|
||||
array[1] = 42;
|
||||
assertKind(expected_kind, array);
|
||||
var a = array[1];
|
||||
assertEquals(42, a);
|
||||
}
|
||||
var smis = [1, 2, 3];
|
||||
var strings = ["one", "two", "three"];
|
||||
var doubles = [0, 0, 0]; doubles[0] = 1.5; doubles[1] = 2.5; doubles[2] = 3.5;
|
||||
assertKind(support_smi_only_arrays
|
||||
? element_kind.fast_double_elements
|
||||
: element_kind.fast_elements,
|
||||
doubles);
|
||||
for (var i = 0; i < 3; i++) {
|
||||
polymorphic(smis, element_kind.fast_smi_only_elements);
|
||||
polymorphic(strings, element_kind.fast_elements);
|
||||
polymorphic(doubles, support_smi_only_arrays
|
||||
? element_kind.fast_double_elements
|
||||
: element_kind.fast_elements);
|
||||
}
|
||||
%OptimizeFunctionOnNextCall(polymorphic);
|
||||
polymorphic(smis, element_kind.fast_smi_only_elements);
|
||||
polymorphic(strings, element_kind.fast_elements);
|
||||
polymorphic(doubles, support_smi_only_arrays
|
||||
? element_kind.fast_double_elements
|
||||
: element_kind.fast_elements);
|
||||
|
||||
// Crankshaft support for smi-only elements in dynamic array literals.
|
||||
function get(foo) { return foo; } // Used to generate dynamic values.
|
||||
|
||||
//function crankshaft_test(expected_kind) {
|
||||
function crankshaft_test() {
|
||||
var a = [get(1), get(2), get(3)];
|
||||
assertKind(element_kind.fast_smi_only_elements, a);
|
||||
var b = [get(1), get(2), get("three")];
|
||||
assertKind(element_kind.fast_elements, b);
|
||||
var c = [get(1), get(2), get(3.5)];
|
||||
// The full code generator doesn't support conversion to fast_double_elements
|
||||
// yet. Crankshaft does, but only with --smi-only-arrays support.
|
||||
if ((%GetOptimizationStatus(crankshaft_test) & 1) &&
|
||||
support_smi_only_arrays) {
|
||||
assertKind(element_kind.fast_double_elements, c);
|
||||
} else {
|
||||
assertKind(element_kind.fast_elements, c);
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < 3; i++) {
|
||||
crankshaft_test();
|
||||
}
|
||||
%OptimizeFunctionOnNextCall(crankshaft_test);
|
||||
crankshaft_test();
|
||||
|
Loading…
Reference in New Issue
Block a user