Inline simple setter calls.

Currently only simple setter calls are handled (i.e. no calls in count
operations or compound assignments), and deoptimization in the setter is not
handled at all. Because of the latter, we temporarily hide this feature behind
the --inline-accessors flag, just like inlining getters.

We now use an enum everywhere we depend on the handling of a return value,
passing around several boolean would be more confusing.

Made VisitReturnStatement and the final parts of TryInline more similar, so
matching them visually is a bit easier now.

Simplified the signature of AddLeaveInlined, the target of the HGoto can simply
be retrieved from the function state.

Review URL: https://chromiumcodereview.appspot.com/10836133

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@12286 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
svenpanne@chromium.org 2012-08-10 09:05:42 +00:00
parent 6cfc3f4c18
commit f9aea9fcef
12 changed files with 456 additions and 99 deletions

View File

@ -2239,7 +2239,7 @@ LInstruction* LChunkBuilder::DoEnterInlined(HEnterInlined* instr) {
instr->function(),
undefined,
instr->call_kind(),
instr->is_construct());
instr->inlining_kind());
if (instr->arguments_var() != NULL) {
inner->Bind(instr->arguments_var(), graph()->GetArgumentsObject());
}

View File

@ -493,11 +493,12 @@ void LCodeGen::WriteTranslation(LEnvironment* environment,
case JS_CONSTRUCT:
translation->BeginConstructStubFrame(closure_id, translation_size);
break;
case JS_SETTER:
// TODO(svenpanne) Implement me!
break;
case ARGUMENTS_ADAPTOR:
translation->BeginArgumentsAdaptorFrame(closure_id, translation_size);
break;
default:
UNREACHABLE();
}
for (int i = 0; i < translation_size; ++i) {
LOperand* value = environment->values()->at(i);

View File

@ -1403,20 +1403,28 @@ class HStackCheck: public HTemplateInstruction<1> {
};
enum InliningKind {
NORMAL_RETURN, // Normal function/method call and return.
DROP_EXTRA_ON_RETURN, // Drop an extra value from the environment on return.
CONSTRUCT_CALL_RETURN, // Either use allocated receiver or return value.
SETTER_CALL_RETURN // Use the RHS of the assignment as the return value.
};
class HEnterInlined: public HTemplateInstruction<0> {
public:
HEnterInlined(Handle<JSFunction> closure,
int arguments_count,
FunctionLiteral* function,
CallKind call_kind,
bool is_construct,
InliningKind inlining_kind,
Variable* arguments_var,
ZoneList<HValue*>* arguments_values)
: closure_(closure),
arguments_count_(arguments_count),
function_(function),
call_kind_(call_kind),
is_construct_(is_construct),
inlining_kind_(inlining_kind),
arguments_var_(arguments_var),
arguments_values_(arguments_values) {
}
@ -1427,7 +1435,7 @@ class HEnterInlined: public HTemplateInstruction<0> {
int arguments_count() const { return arguments_count_; }
FunctionLiteral* function() const { return function_; }
CallKind call_kind() const { return call_kind_; }
bool is_construct() const { return is_construct_; }
InliningKind inlining_kind() const { return inlining_kind_; }
virtual Representation RequiredInputRepresentation(int index) {
return Representation::None();
@ -1443,7 +1451,7 @@ class HEnterInlined: public HTemplateInstruction<0> {
int arguments_count_;
FunctionLiteral* function_;
CallKind call_kind_;
bool is_construct_;
InliningKind inlining_kind_;
Variable* arguments_var_;
ZoneList<HValue*>* arguments_values_;
};

View File

@ -166,7 +166,8 @@ void HBasicBlock::Finish(HControlInstruction* end) {
void HBasicBlock::Goto(HBasicBlock* block, FunctionState* state) {
bool drop_extra = state != NULL && state->drop_extra();
bool drop_extra = state != NULL &&
state->inlining_kind() == DROP_EXTRA_ON_RETURN;
bool arguments_pushed = state != NULL && state->arguments_pushed();
if (block->IsInlineReturnTarget()) {
@ -181,10 +182,10 @@ void HBasicBlock::Goto(HBasicBlock* block, FunctionState* state) {
void HBasicBlock::AddLeaveInlined(HValue* return_value,
HBasicBlock* target,
FunctionState* state) {
bool drop_extra = state != NULL && state->drop_extra();
bool arguments_pushed = state != NULL && state->arguments_pushed();
HBasicBlock* target = state->function_return();
bool drop_extra = state->inlining_kind() == DROP_EXTRA_ON_RETURN;
bool arguments_pushed = state->arguments_pushed();
ASSERT(target->IsInlineReturnTarget());
ASSERT(return_value != NULL);
@ -2751,12 +2752,12 @@ void HGraph::ComputeMinusZeroChecks() {
FunctionState::FunctionState(HGraphBuilder* owner,
CompilationInfo* info,
TypeFeedbackOracle* oracle,
ReturnHandlingFlag return_handling)
InliningKind inlining_kind)
: owner_(owner),
compilation_info_(info),
oracle_(oracle),
call_context_(NULL),
return_handling_(return_handling),
inlining_kind_(inlining_kind),
function_return_(NULL),
test_context_(NULL),
entry_(NULL),
@ -3831,28 +3832,29 @@ void HGraphBuilder::VisitReturnStatement(ReturnStatement* stmt) {
ASSERT(!HasStackOverflow());
ASSERT(current_block() != NULL);
ASSERT(current_block()->HasPredecessor());
FunctionState* state = function_state();
AstContext* context = call_context();
if (context == NULL) {
// Not an inlined return, so an actual one.
CHECK_ALIVE(VisitForValue(stmt->expression()));
HValue* result = environment()->Pop();
current_block()->FinishExit(new(zone()) HReturn(result));
} else if (function_state()->is_construct()) {
// Return from an inlined construct call. In a test context the return
// value will always evaluate to true, in a value context the return value
// needs to be a JSObject.
} else if (state->inlining_kind() == CONSTRUCT_CALL_RETURN) {
// Return from an inlined construct call. In a test context the return value
// will always evaluate to true, in a value context the return value needs
// to be a JSObject.
if (context->IsTest()) {
TestContext* test = TestContext::cast(context);
CHECK_ALIVE(VisitForEffect(stmt->expression()));
current_block()->Goto(test->if_true(), function_state());
current_block()->Goto(test->if_true(), state);
} else if (context->IsEffect()) {
CHECK_ALIVE(VisitForEffect(stmt->expression()));
current_block()->Goto(function_return(), function_state());
current_block()->Goto(function_return(), state);
} else {
ASSERT(context->IsValue());
CHECK_ALIVE(VisitForValue(stmt->expression()));
HValue* return_value = Pop();
HValue* receiver = environment()->Lookup(0);
HValue* receiver = environment()->arguments_environment()->Lookup(0);
HHasInstanceTypeAndBranch* typecheck =
new(zone()) HHasInstanceTypeAndBranch(return_value,
FIRST_SPEC_OBJECT_TYPE,
@ -3862,31 +3864,36 @@ void HGraphBuilder::VisitReturnStatement(ReturnStatement* stmt) {
typecheck->SetSuccessorAt(0, if_spec_object);
typecheck->SetSuccessorAt(1, not_spec_object);
current_block()->Finish(typecheck);
if_spec_object->AddLeaveInlined(return_value,
function_return(),
function_state());
not_spec_object->AddLeaveInlined(receiver,
function_return(),
function_state());
if_spec_object->AddLeaveInlined(return_value, state);
not_spec_object->AddLeaveInlined(receiver, state);
}
} else if (state->inlining_kind() == SETTER_CALL_RETURN) {
// Return from an inlined setter call. The returned value is never used, the
// value of an assignment is always the value of the RHS of the assignment.
CHECK_ALIVE(VisitForEffect(stmt->expression()));
if (context->IsTest()) {
HValue* rhs = environment()->arguments_environment()->Lookup(1);
context->ReturnValue(rhs);
} else if (context->IsEffect()) {
current_block()->Goto(function_return(), state);
} else {
ASSERT(context->IsValue());
HValue* rhs = environment()->arguments_environment()->Lookup(1);
current_block()->AddLeaveInlined(rhs, state);
}
} else {
// Return from an inlined function, visit the subexpression in the
// Return from a normal inlined function. Visit the subexpression in the
// expression context of the call.
if (context->IsTest()) {
TestContext* test = TestContext::cast(context);
VisitForControl(stmt->expression(),
test->if_true(),
test->if_false());
VisitForControl(stmt->expression(), test->if_true(), test->if_false());
} else if (context->IsEffect()) {
CHECK_ALIVE(VisitForEffect(stmt->expression()));
current_block()->Goto(function_return(), function_state());
current_block()->Goto(function_return(), state);
} else {
ASSERT(context->IsValue());
CHECK_ALIVE(VisitForValue(stmt->expression()));
HValue* return_value = Pop();
current_block()->AddLeaveInlined(return_value,
function_return(),
function_state());
current_block()->AddLeaveInlined(Pop(), state);
}
}
set_current_block(NULL);
@ -5233,8 +5240,15 @@ void HGraphBuilder::HandlePropertyAssignment(Assignment* expr) {
Handle<AccessorPair> accessors;
Handle<JSObject> holder;
if (LookupAccessorPair(map, name, &accessors, &holder)) {
Handle<JSFunction> setter(JSFunction::cast(accessors->setter()));
AddCheckConstantFunction(holder, object, map, true);
if (FLAG_inline_accessors && TryInlineSetter(setter, expr, value)) {
return;
}
Drop(2);
instr = BuildCallSetter(object, value, map, accessors, holder);
AddInstruction(new(zone()) HPushArgument(object));
AddInstruction(new(zone()) HPushArgument(value));
instr = new(zone()) HCallConstantFunction(setter, 2);
} else {
Drop(2);
CHECK_ALIVE(instr = BuildStoreNamedMonomorphic(object,
@ -6633,10 +6647,10 @@ int HGraphBuilder::InliningAstSize(Handle<JSFunction> target) {
bool HGraphBuilder::TryInline(CallKind call_kind,
Handle<JSFunction> target,
int arguments_count,
HValue* receiver,
HValue* implicit_return_value,
BailoutId ast_id,
BailoutId return_id,
ReturnHandlingFlag return_handling) {
InliningKind inlining_kind) {
int nodes_added = InliningAstSize(target);
if (nodes_added == kNotInlinable) return false;
@ -6789,7 +6803,7 @@ bool HGraphBuilder::TryInline(CallKind call_kind,
// The function state is new-allocated because we need to delete it
// in two different places.
FunctionState* target_state = new FunctionState(
this, &target_info, &target_oracle, return_handling);
this, &target_info, &target_oracle, inlining_kind);
HConstant* undefined = graph()->GetConstantUndefined();
HEnvironment* inner_env =
@ -6798,7 +6812,7 @@ bool HGraphBuilder::TryInline(CallKind call_kind,
function,
undefined,
call_kind,
function_state()->is_construct());
function_state()->inlining_kind());
#ifdef V8_TARGET_ARCH_IA32
// IA32 only, overwrite the caller's context in the deoptimization
// environment with the correct one.
@ -6833,7 +6847,7 @@ bool HGraphBuilder::TryInline(CallKind call_kind,
arguments_count,
function,
call_kind,
function_state()->is_construct(),
function_state()->inlining_kind(),
function->scope()->arguments(),
arguments_values);
function_state()->set_entry(enter_inlined);
@ -6865,27 +6879,42 @@ bool HGraphBuilder::TryInline(CallKind call_kind,
TraceInline(target, caller, NULL);
if (current_block() != NULL) {
// Add default return value (i.e. undefined for normals calls or the newly
// allocated receiver for construct calls) if control can fall off the
// body. In a test context, undefined is false and any JSObject is true.
if (call_context()->IsValue()) {
ASSERT(function_return() != NULL);
HValue* return_value = function_state()->is_construct()
? receiver
: undefined;
current_block()->AddLeaveInlined(return_value,
function_return(),
function_state());
} else if (call_context()->IsEffect()) {
ASSERT(function_return() != NULL);
current_block()->Goto(function_return(), function_state());
FunctionState* state = function_state();
if (state->inlining_kind() == CONSTRUCT_CALL_RETURN) {
// Falling off the end of an inlined construct call. In a test context the
// return value will always evaluate to true, in a value context the
// return value is the newly allocated receiver.
if (call_context()->IsTest()) {
current_block()->Goto(inlined_test_context()->if_true(), state);
} else if (call_context()->IsEffect()) {
current_block()->Goto(function_return(), state);
} else {
ASSERT(call_context()->IsValue());
current_block()->AddLeaveInlined(implicit_return_value, state);
}
} else if (state->inlining_kind() == SETTER_CALL_RETURN) {
// Falling off the end of an inlined setter call. The returned value is
// never used, the value of an assignment is always the value of the RHS
// of the assignment.
if (call_context()->IsTest()) {
inlined_test_context()->ReturnValue(implicit_return_value);
} else if (call_context()->IsEffect()) {
current_block()->Goto(function_return(), state);
} else {
ASSERT(call_context()->IsValue());
current_block()->AddLeaveInlined(implicit_return_value, state);
}
} else {
ASSERT(call_context()->IsTest());
ASSERT(inlined_test_context() != NULL);
HBasicBlock* target = function_state()->is_construct()
? inlined_test_context()->if_true()
: inlined_test_context()->if_false();
current_block()->Goto(target, function_state());
// Falling off the end of a normal inlined function. This basically means
// returning undefined.
if (call_context()->IsTest()) {
current_block()->Goto(inlined_test_context()->if_false(), state);
} else if (call_context()->IsEffect()) {
current_block()->Goto(function_return(), state);
} else {
ASSERT(call_context()->IsValue());
current_block()->AddLeaveInlined(undefined, state);
}
}
}
@ -6941,11 +6970,12 @@ bool HGraphBuilder::TryInlineCall(Call* expr, bool drop_extra) {
}
bool HGraphBuilder::TryInlineConstruct(CallNew* expr, HValue* receiver) {
bool HGraphBuilder::TryInlineConstruct(CallNew* expr,
HValue* implicit_return_value) {
return TryInline(CALL_AS_FUNCTION,
expr->target(),
expr->arguments()->length(),
receiver,
implicit_return_value,
expr->id(),
expr->ReturnId(),
CONSTRUCT_CALL_RETURN);
@ -6964,6 +6994,19 @@ bool HGraphBuilder::TryInlineGetter(Handle<JSFunction> getter,
}
bool HGraphBuilder::TryInlineSetter(Handle<JSFunction> setter,
Assignment* assignment,
HValue* implicit_return_value) {
return TryInline(CALL_AS_METHOD,
setter,
1,
implicit_return_value,
assignment->id(),
assignment->AssignmentId(),
SETTER_CALL_RETURN);
}
bool HGraphBuilder::TryInlineBuiltinFunctionCall(Call* expr, bool drop_extra) {
if (!expr->target()->shared()->HasBuiltinFunctionId()) return false;
BuiltinFunctionId id = expr->target()->shared()->builtin_function_id();
@ -8647,7 +8690,7 @@ void HGraphBuilder::GenerateIsConstructCall(CallRuntime* call) {
ASSERT(call->arguments()->length() == 0);
if (function_state()->outer() != NULL) {
// We are generating graph for inlined function.
HValue* value = function_state()->is_construct()
HValue* value = function_state()->inlining_kind() == CONSTRUCT_CALL_RETURN
? graph()->GetConstantTrue()
: graph()->GetConstantFalse();
return ast_context()->ReturnValue(value);
@ -9237,7 +9280,7 @@ HEnvironment* HEnvironment::CopyForInlining(
FunctionLiteral* function,
HConstant* undefined,
CallKind call_kind,
bool is_construct) const {
InliningKind inlining_kind) const {
ASSERT(frame_type() == JS_FUNCTION);
// Outer environment is a copy of this one without the arguments.
@ -9247,11 +9290,15 @@ HEnvironment* HEnvironment::CopyForInlining(
outer->Drop(arguments + 1); // Including receiver.
outer->ClearHistory();
if (is_construct) {
if (inlining_kind == CONSTRUCT_CALL_RETURN) {
// Create artificial constructor stub environment. The receiver should
// actually be the constructor function, but we pass the newly allocated
// object instead, DoComputeConstructStubFrame() relies on that.
outer = CreateStubEnvironment(outer, target, JS_CONSTRUCT, arguments);
} else if (inlining_kind == SETTER_CALL_RETURN) {
// We need an additional StackFrame::INTERNAL frame for temporarily saving
// the argument of the setter, see StoreStubCompiler::CompileStoreViaSetter.
outer = CreateStubEnvironment(outer, target, JS_SETTER, arguments);
}
if (arity != arguments) {
@ -9271,7 +9318,7 @@ HEnvironment* HEnvironment::CopyForInlining(
// builtin function, pass undefined as the receiver for function
// calls (instead of the global receiver).
if ((target->shared()->native() || !function->is_classic_mode()) &&
call_kind == CALL_AS_FUNCTION && !is_construct) {
call_kind == CALL_AS_FUNCTION && inlining_kind != CONSTRUCT_CALL_RETURN) {
inner->SetValueAt(0, undefined);
}
inner->SetValueAt(arity + 1, LookupContext());

View File

@ -135,9 +135,7 @@ class HBasicBlock: public ZoneObject {
// Add the inlined function exit sequence, adding an HLeaveInlined
// instruction and updating the bailout environment.
void AddLeaveInlined(HValue* return_value,
HBasicBlock* target,
FunctionState* state = NULL);
void AddLeaveInlined(HValue* return_value, FunctionState* state);
// If a target block is tagged as an inline function return, all
// predecessors should contain the inlined exit sequence:
@ -396,7 +394,12 @@ Zone* HBasicBlock::zone() const { return graph_->zone(); }
// Type of stack frame an environment might refer to.
enum FrameType { JS_FUNCTION, JS_CONSTRUCT, ARGUMENTS_ADAPTOR };
enum FrameType {
JS_FUNCTION,
JS_CONSTRUCT,
JS_SETTER,
ARGUMENTS_ADAPTOR
};
class HEnvironment: public ZoneObject {
@ -510,7 +513,7 @@ class HEnvironment: public ZoneObject {
FunctionLiteral* function,
HConstant* undefined,
CallKind call_kind,
bool is_construct) const;
InliningKind inlining_kind) const;
void AddIncomingEdge(HBasicBlock* block, HEnvironment* other);
@ -707,26 +710,18 @@ class TestContext: public AstContext {
};
enum ReturnHandlingFlag {
NORMAL_RETURN,
DROP_EXTRA_ON_RETURN,
CONSTRUCT_CALL_RETURN
};
class FunctionState {
public:
FunctionState(HGraphBuilder* owner,
CompilationInfo* info,
TypeFeedbackOracle* oracle,
ReturnHandlingFlag return_handling);
InliningKind inlining_kind);
~FunctionState();
CompilationInfo* compilation_info() { return compilation_info_; }
TypeFeedbackOracle* oracle() { return oracle_; }
AstContext* call_context() { return call_context_; }
bool drop_extra() { return return_handling_ == DROP_EXTRA_ON_RETURN; }
bool is_construct() { return return_handling_ == CONSTRUCT_CALL_RETURN; }
InliningKind inlining_kind() const { return inlining_kind_; }
HBasicBlock* function_return() { return function_return_; }
TestContext* test_context() { return test_context_; }
void ClearInlinedTestContext() {
@ -756,11 +751,8 @@ class FunctionState {
// inlined. NULL when not inlining.
AstContext* call_context_;
// Indicate whether we have to perform special handling on return from
// inlined functions.
// - DROP_EXTRA_ON_RETURN: Drop an extra value from the environment.
// - CONSTRUCT_CALL_RETURN: Either use allocated receiver or return value.
ReturnHandlingFlag return_handling_;
// The kind of call which is currently being inlined.
InliningKind inlining_kind_;
// When inlining in an effect or value context, this is the return block.
// It is NULL otherwise. When inlining in a test context, there are a
@ -1037,14 +1029,17 @@ class HGraphBuilder: public AstVisitor {
bool TryInline(CallKind call_kind,
Handle<JSFunction> target,
int arguments_count,
HValue* receiver,
HValue* implicit_return_value,
BailoutId ast_id,
BailoutId return_id,
ReturnHandlingFlag return_handling);
InliningKind inlining_kind);
bool TryInlineCall(Call* expr, bool drop_extra = false);
bool TryInlineConstruct(CallNew* expr, HValue* receiver);
bool TryInlineConstruct(CallNew* expr, HValue* implicit_return_value);
bool TryInlineGetter(Handle<JSFunction> getter, Property* prop);
bool TryInlineSetter(Handle<JSFunction> setter,
Assignment* assignment,
HValue* implicit_return_value);
bool TryInlineBuiltinMethodCall(Call* expr,
HValue* receiver,
Handle<Map> receiver_map,

View File

@ -432,11 +432,12 @@ void LCodeGen::WriteTranslation(LEnvironment* environment,
case JS_CONSTRUCT:
translation->BeginConstructStubFrame(closure_id, translation_size);
break;
case JS_SETTER:
// TODO(svenpanne) Implement me!
break;
case ARGUMENTS_ADAPTOR:
translation->BeginArgumentsAdaptorFrame(closure_id, translation_size);
break;
default:
UNREACHABLE();
}
for (int i = 0; i < translation_size; ++i) {
LOperand* value = environment->values()->at(i);

View File

@ -2354,7 +2354,7 @@ LInstruction* LChunkBuilder::DoEnterInlined(HEnterInlined* instr) {
instr->function(),
undefined,
instr->call_kind(),
instr->is_construct());
instr->inlining_kind());
if (instr->arguments_var() != NULL) {
inner->Bind(instr->arguments_var(), graph()->GetArgumentsObject());
}

View File

@ -461,11 +461,12 @@ void LCodeGen::WriteTranslation(LEnvironment* environment,
case JS_CONSTRUCT:
translation->BeginConstructStubFrame(closure_id, translation_size);
break;
case JS_SETTER:
// TODO(svenpanne) Implement me!
break;
case ARGUMENTS_ADAPTOR:
translation->BeginArgumentsAdaptorFrame(closure_id, translation_size);
break;
default:
UNREACHABLE();
}
for (int i = 0; i < translation_size; ++i) {
LOperand* value = environment->values()->at(i);

View File

@ -2179,7 +2179,7 @@ LInstruction* LChunkBuilder::DoEnterInlined(HEnterInlined* instr) {
instr->function(),
undefined,
instr->call_kind(),
instr->is_construct());
instr->inlining_kind());
if (instr->arguments_var() != NULL) {
inner->Bind(instr->arguments_var(), graph()->GetArgumentsObject());
}

View File

@ -380,11 +380,12 @@ void LCodeGen::WriteTranslation(LEnvironment* environment,
case JS_CONSTRUCT:
translation->BeginConstructStubFrame(closure_id, translation_size);
break;
case JS_SETTER:
// TODO(svenpanne) Implement me!
break;
case ARGUMENTS_ADAPTOR:
translation->BeginArgumentsAdaptorFrame(closure_id, translation_size);
break;
default:
UNREACHABLE();
}
for (int i = 0; i < translation_size; ++i) {
LOperand* value = environment->values()->at(i);

View File

@ -2238,7 +2238,7 @@ LInstruction* LChunkBuilder::DoEnterInlined(HEnterInlined* instr) {
instr->function(),
undefined,
instr->call_kind(),
instr->is_construct());
instr->inlining_kind());
if (instr->arguments_var() != NULL) {
inner->Bind(instr->arguments_var(), graph()->GetArgumentsObject());
}

View File

@ -0,0 +1,303 @@
// Copyright 2012 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 --inline-accessors
var accessorCallCount, setterArgument, setterValue, obj, forceDeopt;
// -----------------------------------------------------------------------------
// Helpers for testing inlining of getters.
function TestInlinedGetter(context, expected) {
forceDeopt = 0;
accessorCallCount = 0;
assertEquals(expected, context());
assertEquals(1, accessorCallCount);
assertEquals(expected, context());
assertEquals(2, accessorCallCount);
%OptimizeFunctionOnNextCall(context);
assertEquals(expected, context());
assertEquals(3, accessorCallCount);
%DeoptimizeFunction(context);
%ClearFunctionTypeFeedback(context);
}
function TestGetterInAllContexts(obj, expected) {
function value_context() {
return obj.getterProperty;
}
TestInlinedGetter(value_context, expected);
function test_context() {
if (obj.getterProperty) {
return 111;
} else {
return 222;
}
}
TestInlinedGetter(test_context, expected ? 111 : 222);
function effect_context() {
obj.getterProperty;
return 5678;
}
TestInlinedGetter(effect_context, 5678);
}
// -----------------------------------------------------------------------------
// Test getter returning something 'true'ish in all contexts.
function getter1() {
assertSame(obj, this);
accessorCallCount++;
forceDeopt + 1;
return 1234;
}
function ConstrG1() { }
obj = Object.defineProperty(new ConstrG1(), "getterProperty", { get: getter1 });
TestGetterInAllContexts(obj, 1234);
obj = Object.create(obj);
TestGetterInAllContexts(obj, 1234);
// -----------------------------------------------------------------------------
// Test getter returning false in all contexts.
function getter2() {
assertSame(obj, this);
accessorCallCount++;
forceDeopt + 1;
return false;
}
function ConstrG2() { }
obj = Object.defineProperty(new ConstrG2(), "getterProperty", { get: getter2 });
TestGetterInAllContexts(obj, false);
obj = Object.create(obj);
TestGetterInAllContexts(obj, false);
// -----------------------------------------------------------------------------
// Test getter without a return in all contexts.
function getter3() {
assertSame(obj, this);
accessorCallCount++;
forceDeopt + 1;
}
function ConstrG3() { }
obj = Object.defineProperty(new ConstrG3(), "getterProperty", { get: getter3 });
TestGetterInAllContexts(obj, undefined);
obj = Object.create(obj);
TestGetterInAllContexts(obj, undefined);
// -----------------------------------------------------------------------------
// Test getter with too many arguments without a return in all contexts.
function getter4(a) {
assertSame(obj, this);
assertEquals(undefined, a);
accessorCallCount++;
forceDeopt + 1;
}
function ConstrG4() { }
obj = Object.defineProperty(new ConstrG4(), "getterProperty", { get: getter4 });
TestGetterInAllContexts(obj, undefined);
obj = Object.create(obj);
TestGetterInAllContexts(obj, undefined);
// -----------------------------------------------------------------------------
// Test getter with too many arguments with a return in all contexts.
function getter5(a) {
assertSame(obj, this);
assertEquals(undefined, a);
accessorCallCount++;
forceDeopt + 1;
return 9876;
}
function ConstrG5() { }
obj = Object.defineProperty(new ConstrG5(), "getterProperty", { get: getter5 });
TestGetterInAllContexts(obj, 9876);
obj = Object.create(obj);
TestGetterInAllContexts(obj, 9876);
// -----------------------------------------------------------------------------
// Helpers for testing inlining of setters.
function TestInlinedSetter(context, value, expected) {
forceDeopt = 0;
accessorCallCount = 0;
setterArgument = value;
assertEquals(expected, context(value));
assertEquals(value, setterValue);
assertEquals(1, accessorCallCount);
assertEquals(expected, context(value));
assertEquals(value, setterValue);
assertEquals(2, accessorCallCount);
%OptimizeFunctionOnNextCall(context);
assertEquals(expected, context(value));
assertEquals(value, setterValue);
assertEquals(3, accessorCallCount);
%DeoptimizeFunction(context);
%ClearFunctionTypeFeedback(context);
}
function TestSetterInAllContexts(obj) {
function value_context(value) {
return obj.setterProperty = value;
}
TestInlinedSetter(value_context, 111, 111);
function test_context(value) {
if (obj.setterProperty = value) {
return 333;
} else {
return 444;
}
}
TestInlinedSetter(test_context, true, 333);
TestInlinedSetter(test_context, false, 444);
function effect_context(value) {
obj.setterProperty = value;
return 666;
}
TestInlinedSetter(effect_context, 555, 666);
}
// -----------------------------------------------------------------------------
// Test setter without a return in all contexts.
function setter1(value) {
assertSame(obj, this);
accessorCallCount++;
forceDeopt + 1;
setterValue = value;
}
function ConstrS1() { }
obj = Object.defineProperty(new ConstrS1(), "setterProperty", { set: setter1 });
TestSetterInAllContexts(obj);
obj = Object.create(obj);
TestSetterInAllContexts(obj);
// -----------------------------------------------------------------------------
// Test setter returning something different than the RHS in all contexts.
function setter2(value) {
assertSame(obj, this);
accessorCallCount++;
forceDeopt + 1;
setterValue = value;
return 1000000;
}
function ConstrS2() { }
obj = Object.defineProperty(new ConstrS2(), "setterProperty", { set: setter2 });
TestSetterInAllContexts(obj);
obj = Object.create(obj);
TestSetterInAllContexts(obj);
// -----------------------------------------------------------------------------
// Test setter with too few arguments without a return in all contexts.
function setter3() {
assertSame(obj, this);
accessorCallCount++;
forceDeopt + 1;
setterValue = setterArgument;
}
function ConstrS3() { }
obj = Object.defineProperty(new ConstrS3(), "setterProperty", { set: setter3 });
TestSetterInAllContexts(obj);
obj = Object.create(obj);
TestSetterInAllContexts(obj);
// -----------------------------------------------------------------------------
// Test setter with too few arguments with a return in all contexts.
function setter4() {
assertSame(obj, this);
accessorCallCount++;
forceDeopt + 1;
setterValue = setterArgument;
return 2000000;
}
function ConstrS4() { }
obj = Object.defineProperty(new ConstrS4(), "setterProperty", { set: setter4 });
TestSetterInAllContexts(obj);
obj = Object.create(obj);
TestSetterInAllContexts(obj);
// -----------------------------------------------------------------------------
// Test setter with too many arguments without a return in all contexts.
function setter5(value, foo) {
assertSame(obj, this);
assertEquals(undefined, foo);
accessorCallCount++;
forceDeopt + 1;
setterValue = value;
}
function ConstrS5() { }
obj = Object.defineProperty(new ConstrS5(), "setterProperty", { set: setter5 });
TestSetterInAllContexts(obj);
obj = Object.create(obj);
TestSetterInAllContexts(obj);
// -----------------------------------------------------------------------------
// Test setter with too many arguments with a return in all contexts.
function setter6(value, foo) {
assertSame(obj, this);
assertEquals(undefined, foo);
accessorCallCount++;
forceDeopt + 1;
setterValue = value;
return 3000000;
}
function ConstrS6() { }
obj = Object.defineProperty(new ConstrS6(), "setterProperty", { set: setter6 });
TestSetterInAllContexts(obj);
obj = Object.create(obj);
TestSetterInAllContexts(obj);