From e0b5528c29f32c831d46bc3cc0e961c93747853d Mon Sep 17 00:00:00 2001 From: Martok Date: Mon, 10 Apr 2023 15:40:27 +0200 Subject: [PATCH] Issue #2142 - Optimize .initializers scoping and emitter * Refactor code for emitting the .initializers array into ClassEmitter * Only emit .initializers scope when actually required * Remove unfinished code to handle non-present class field initialisers * Use predicate count_if and any_of of ListNode * Remove unnecessary parameters for class field parsing Based-on: m-c 1553744, 1555979, 1555037/1, 1535804/{1-5} --- js/src/builtin/ReflectParse.cpp | 34 ++++---- js/src/frontend/BytecodeEmitter.cpp | 116 ++++++++++++---------------- js/src/frontend/BytecodeEmitter.h | 3 +- js/src/frontend/FullParseHandler.h | 1 - js/src/frontend/ObjectEmitter.cpp | 74 +++++++++++++++++- js/src/frontend/ObjectEmitter.h | 47 +++++++++++ js/src/frontend/ParseNode.h | 6 +- js/src/frontend/Parser.cpp | 45 ++++++----- js/src/frontend/Parser.h | 4 +- js/src/frontend/SharedContext.h | 2 +- 10 files changed, 215 insertions(+), 117 deletions(-) diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 2b885574da..6ca0c30e16 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2766,24 +2766,22 @@ ASTSerializer::classField(ClassField* classField, MutableHandleValue dst) { RootedValue key(cx), val(cx); // Dig through the lambda and get to the actual expression - if (classField->initializer()) { - ParseNode* value = classField->initializer() - ->body() - ->head()->as() - .scopeBody()->as() - .head()->as() - .kid()->as() - .right(); - // RawUndefinedExpr is the node we use for "there is no initializer". If one - // writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr - // node, but rather a variable reference. - // Behavior for "there is no initializer" should be { ..., "init": null } - if (value->getKind() != PNK_RAW_UNDEFINED) { - if (!expression(value, &val)) - return false; - } else { - val.setNull(); - } + ParseNode* value = classField->initializer() + ->body() + ->head()->as() + .scopeBody()->as() + .head()->as() + .kid()->as() + .right(); + // RawUndefinedExpr is the node we use for "there is no initializer". If one + // writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr + // node, but rather a variable reference. + // Behavior for "there is no initializer" should be { ..., "init": null } + if (value->getKind() != PNK_RAW_UNDEFINED) { + if (!expression(value, &val)) + return false; + } else { + val.setNull(); } return propertyName(&classField->name(), &key) && builder.classField(key, val, &classField->pn_pos, dst); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 4f0ec4962c..931b414f10 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -7942,18 +7942,7 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy FieldInitializers BytecodeEmitter::setupFieldInitializers(ListNode* classMembers) { - size_t numFields = 0; - - for (ParseNode* propdef : classMembers->contents()) { - if (propdef->is()) { - FunctionNode* initializer = propdef->as().initializer(); - // Don't include fields without initializers. - if (initializer != nullptr) { - numFields++; - } - continue; - } - } + size_t numFields = classMembers->count_if([](ParseNode* propdef) { return propdef->is(); }); return FieldInitializers(numFields); } @@ -7984,15 +7973,10 @@ BytecodeEmitter::setupFieldInitializers(ListNode* classMembers) bool BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) { - size_t numFieldKeys = 0; - for (ParseNode* propdef : obj->contents()) { - if (propdef->is()) { - ClassField* field = &propdef->as(); - if (field->name().getKind() == PNK_COMPUTED_NAME) { - numFieldKeys++; - } - } - } + size_t numFieldKeys = obj->count_if([](ParseNode* propdef) { + return propdef->is() && + propdef->as().name().getKind() == PNK_COMPUTED_NAME; + }); if (numFieldKeys == 0) return true; @@ -8021,8 +8005,9 @@ BytecodeEmitter::emitCreateFieldKeys(ListNode* obj) } bool -BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj) +BytecodeEmitter::emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj) { + // [stack] HOMEOBJ HERITAGE? FieldInitializers fieldInitializers = setupFieldInitializers(obj); MOZ_ASSERT(fieldInitializers.valid); size_t numFields = fieldInitializers.numFieldInitializers; @@ -8030,50 +8015,28 @@ BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj) if (numFields == 0) return true; - // .initializers is a variable that stores an array of lambdas containing - // code (the initializer) for each field. Upon an object's construction, - // these lambdas will be called, defining the values. - - NameOpEmitter noe(this, cx->names().dotInitializers, - NameOpEmitter::Kind::Initialize); - if (!noe.prepareForRhs()) { + if (!ce.prepareForFieldInitializers(numFields)) { + // [stack] HOMEOBJ HERITAGE? ARRAY return false; } - if (!emitUint32Operand(JSOP_NEWARRAY, numFields)) { - // [stack] CTOR? OBJ ARRAY - return false; - } - - size_t curFieldIndex = 0; for (ParseNode* propdef : obj->contents()) { - if (propdef->is()) { - FunctionNode* initializer = propdef->as().initializer(); - if (initializer == nullptr) { - continue; - } + if (!propdef->is()) + continue; - if (!emitTree(initializer)) { - // [stack] CTOR? OBJ ARRAY LAMBDA - return false; - } - - if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldIndex)) { - // [stack] CTOR? OBJ ARRAY - return false; - } - - curFieldIndex++; + FunctionNode* initializer = propdef->as().initializer(); + if (!emitTree(initializer)) { + // [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA + return false; + } + if (!ce.emitStoreFieldInitializer()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; } } - if (!noe.emitAssignment()) { - // [stack] CTOR? OBJ ARRAY - return false; - } - - if (!emit1(JSOP_POP)) { - // [stack] CTOR? OBJ + if (!ce.emitFieldInitializersEnd()) { + // [stack] HOMEOBJ HERITAGE? return false; } @@ -8698,20 +8661,37 @@ BytecodeEmitter::emitClass(ClassNode* classNode) } if (constructor) { - FunctionNode* ctor; - // .fieldKeys must be declared outside the scope .initializers is declared - // in, hence this extra scope. + // See |Parser::classMember(...)| for the reason why |.initializers| is + // created within its own scope. Maybe lse; + FunctionNode* ctor; if (constructor->is()) { - lse.emplace(this); - if (!lse->emitScope(ScopeKind::Lexical, constructor->as().scopeBindings())) - return false; + LexicalScopeNode* constructorScope = &constructor->as(); - // Any class with field initializers will have a constructor - if (!emitCreateFieldInitializers(classMembers)) - return false; - ctor = &constructor->as().scopeBody()->as().method(); + // The constructor scope should only contain the |.initializers| binding. + MOZ_ASSERT(!constructorScope->isEmptyScope()); + MOZ_ASSERT(constructorScope->scopeBindings()->length == 1); + MOZ_ASSERT(constructorScope->scopeBindings()->trailingNames[0].name() == + cx->names().dotInitializers); + + // As an optimization omit the |.initializers| binding when no instance + // fields are present. + bool hasInstanceFields = classMembers->any_of([](ParseNode* propdef) { + return propdef->is(); + }); + if (hasInstanceFields) { + lse.emplace(this); + if (!lse->emitScope(ScopeKind::Lexical, constructorScope->scopeBindings())) + return false; + + // Any class with field initializers will have a constructor + if (!emitCreateFieldInitializers(ce, classMembers)) + return false; + } + ctor = &constructorScope->scopeBody()->as().method(); } else { + // The |.initializers| binding is never emitted when in self-hosting mode. + MOZ_ASSERT(emitterMode == BytecodeEmitter::SelfHosting); ctor = &constructor->as().method(); } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 29e8862499..15a46ac7d0 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -122,6 +122,7 @@ typedef Vector BytecodeVector; typedef Vector SrcNotesVector; class CallOrNewEmitter; +class ClassEmitter; class ElemOpEmitter; class EmitterScope; class NestableControl; @@ -531,7 +532,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter FieldInitializers setupFieldInitializers(ListNode* classMembers); MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj); - MOZ_MUST_USE bool emitCreateFieldInitializers(ListNode* obj); + MOZ_MUST_USE bool emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj); const FieldInitializers& findFieldInitializersForCall(); MOZ_MUST_USE bool emitInitializeInstanceFields(); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 345fd483a8..cede9a013b 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -487,7 +487,6 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return true; } - UnaryNodeType newInitialYieldExpression(uint32_t begin, Node gen) { TokenPos pos(begin, begin + 1); return new_(PNK_INITIALYIELD, JSOP_INITIALYIELD, pos, gen); diff --git a/js/src/frontend/ObjectEmitter.cpp b/js/src/frontend/ObjectEmitter.cpp index 37c03935ab..fc4ff4d303 100644 --- a/js/src/frontend/ObjectEmitter.cpp +++ b/js/src/frontend/ObjectEmitter.cpp @@ -484,6 +484,71 @@ bool ObjectEmitter::emitObject(size_t propertyCount) return true; } +bool ClassEmitter::prepareForFieldInitializers(size_t numFields) +{ + MOZ_ASSERT(classState_ == ClassState::Class); + + // .initializers is a variable that stores an array of lambdas containing + // code (the initializer) for each field. Upon an object's construction, + // these lambdas will be called, defining the values. + initializersAssignment_.emplace(bce_, bce_->cx->names().dotInitializers, + NameOpEmitter::Kind::Initialize); + if (!initializersAssignment_->prepareForRhs()) { + return false; + } + + if (!bce_->emitUint32Operand(JSOP_NEWARRAY, numFields)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; + } + + MOZ_ASSERT(fieldIndex_ == 0); +#ifdef DEBUG + classState_ = ClassState::FieldInitializers; + numFields_ = numFields; +#endif + return true; +} + +bool ClassEmitter::emitStoreFieldInitializer() +{ + MOZ_ASSERT(classState_ == ClassState::FieldInitializers); + MOZ_ASSERT(fieldIndex_ < numFields_); + // [stack] HOMEOBJ HERITAGE? ARRAY METHOD + + if (!bce_->emitUint32Operand(JSOP_INITELEM_ARRAY, fieldIndex_)) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; + } + + fieldIndex_++; + return true; +} + +bool ClassEmitter::emitFieldInitializersEnd() +{ + MOZ_ASSERT(propertyState_ == PropertyState::Start || + propertyState_ == PropertyState::Init); + MOZ_ASSERT(classState_ == ClassState::FieldInitializers); + MOZ_ASSERT(fieldIndex_ == numFields_); + + if (!initializersAssignment_->emitAssignment()) { + // [stack] HOMEOBJ HERITAGE? ARRAY + return false; + } + initializersAssignment_.reset(); + + if (!bce_->emit1(JSOP_POP)) { + // [stack] HOMEOBJ HERITAGE? + return false; + } + +#ifdef DEBUG + classState_ = ClassState::FieldInitializersEnd; +#endif + return true; +} + bool ObjectEmitter::emitEnd() { MOZ_ASSERT(propertyState_ == PropertyState::Start || @@ -614,7 +679,8 @@ void ClassEmitter::setName(JS::Handle name) bool ClassEmitter::emitInitConstructor(bool needsHomeObject) { MOZ_ASSERT(propertyState_ == PropertyState::Start); - MOZ_ASSERT(classState_ == ClassState::Class); + MOZ_ASSERT(classState_ == ClassState::Class || + classState_ == ClassState::FieldInitializersEnd); // [stack] HOMEOBJ CTOR @@ -640,7 +706,8 @@ bool ClassEmitter::emitInitDefaultConstructor(const Maybe& classStart, const Maybe& classEnd) { MOZ_ASSERT(propertyState_ == PropertyState::Start); - MOZ_ASSERT(classState_ == ClassState::Class); + MOZ_ASSERT(classState_ == ClassState::Class || + classState_ == ClassState::FieldInitializersEnd); if (classStart && classEnd) { // In the case of default class constructors, emit the start and end @@ -706,7 +773,8 @@ bool ClassEmitter::emitEnd(Kind kind) { MOZ_ASSERT(propertyState_ == PropertyState::Start || propertyState_ == PropertyState::Init); - MOZ_ASSERT(classState_ == ClassState::InitConstructor); + MOZ_ASSERT(classState_ == ClassState::InitConstructor || + classState_ == ClassState::FieldInitializersEnd); // [stack] CTOR HOMEOBJ diff --git a/js/src/frontend/ObjectEmitter.h b/js/src/frontend/ObjectEmitter.h index dd983a5b72..423284a455 100644 --- a/js/src/frontend/ObjectEmitter.h +++ b/js/src/frontend/ObjectEmitter.h @@ -18,6 +18,7 @@ #include "jsscript.h" // FunctionAsyncKind #include "frontend/EmitterScope.h" // EmitterScope +#include "frontend/NameOpEmitter.h" // NameOpEmitter #include "frontend/TDZCheckCache.h" // TDZCheckCache #include "js/RootingAPI.h" // JS::Handle, JS::Rooted #include "vm/String.h" // JSAtom @@ -527,6 +528,23 @@ class MOZ_RAII AutoSaveLocalStrictMode // // ce.emitEnd(ClassEmitter::Kind::Expression); // +// `class X extends Y { field0 = expr0; ... }` +// ClassEmitter ce(this); +// ce.emitScope(scopeBindings); +// emit(Y); +// ce.emitDerivedClass(atom_of_X, nullptr, false); +// +// ce.prepareForFieldInitializers(fields.length()); +// for (auto field : fields) { +// emit(field.expr_method()); +// ce.emitStoreFieldInitializer(); +// } +// ce.emitFieldInitializersEnd(); +// +// emit(function_for_constructor); +// ce.emitInitConstructor(/* needsHomeObject = */ false); +// ce.emitEnd(ClassEmitter::Kind::Expression); +// // `m() {}` in class // // after emitInitConstructor/emitInitDefaultConstructor // ce.prepareForPropValue(Some(offset_of_m)); @@ -655,6 +673,21 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // | // +-------------------------------+ // | + // | prepareForFieldInitializers +-------------------+ + // +----------------------------->| FieldInitializers |-+ + // | +-------------------+ | + // | | + // | +-------------------------------------------------+ + // | | + // | | (expr emitStoreFieldInitializer)* + // | | + // | | + // | | emitFieldInitializersEnd +----------------------+ + // | +-------------------------->| FieldInitializersEnd |-+ + // | +----------------------+ | + // | | + // |<------------------------------------------------------+ + // | // | // | emitInitConstructor +-----------------+ // +-+--------------------------->+->| InitConstructor |-+ @@ -680,13 +713,23 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter // After calling emitInitConstructor or emitInitDefaultConstructor. InitConstructor, + // After calling prepareForFieldInitializers + // and 0 or more calls to emitFieldInitializersEnd. + FieldInitializers, + + // After calling emitFieldInitializersEnd. + FieldInitializersEnd, + // After calling emitEnd. End, }; ClassState classState_ = ClassState::Start; + size_t numFields_ = 0; #endif JS::Rooted name_; + mozilla::Maybe initializersAssignment_; + size_t fieldIndex_ = 0; public: explicit ClassEmitter(BytecodeEmitter* bce); @@ -715,6 +758,10 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter const mozilla::Maybe& classStart, const mozilla::Maybe& classEnd); + MOZ_MUST_USE bool prepareForFieldInitializers(size_t numFields); + MOZ_MUST_USE bool emitStoreFieldInitializer(); + MOZ_MUST_USE bool emitFieldInitializersEnd(); + MOZ_MUST_USE bool emitEnd(Kind kind); private: diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 9a91ee9b3e..c4e6945e5a 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -2143,7 +2143,7 @@ class ClassField : public BinaryNode public: ClassField(ParseNode* name, ParseNode* initializer) : BinaryNode(PNK_CLASSFIELD, JSOP_NOP, - initializer == nullptr ? name->pn_pos : TokenPos::box(name->pn_pos, initializer->pn_pos), + TokenPos::box(name->pn_pos, initializer->pn_pos), name, initializer) { } @@ -2156,9 +2156,7 @@ class ClassField : public BinaryNode ParseNode& name() const { return *left(); } - FunctionNode* initializer() const { - return right() ? &right()->as() : nullptr; - } + FunctionNode* initializer() const { return &right()->as(); } }; class SwitchStatement : public BinaryNode diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 80c7cd3b2f..e74a8d3ee9 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -557,13 +557,10 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt } void -FunctionBox::initFieldInitializer(ParseContext* enclosing, bool hasHeritage) +FunctionBox::initFieldInitializer(ParseContext* enclosing) { - this->initWithEnclosingParseContext(enclosing, FunctionSyntaxKind::Expression); - allowSuperProperty_ = false; - allowSuperCall_ = false; + this->initWithEnclosingParseContext(enclosing, FunctionSyntaxKind::Method); allowArguments_ = false; - needsThisTDZChecks_ = hasHeritage; } void @@ -7419,7 +7416,7 @@ JSOpFromPropertyType(PropertyType propType) template bool -Parser::classMember(YieldHandling yieldHandling, DefaultHandling defaultHandling, +Parser::classMember(YieldHandling yieldHandling, const ParseContext::ClassStatement& classStmt, HandlePropertyName className, uint32_t classStartOffset, bool hasHeritage, @@ -7492,8 +7489,7 @@ Parser::classMember(YieldHandling yieldHandling, DefaultHandling d numFields++; - FunctionNodeType initializer = fieldInitializerOpt(yieldHandling, hasHeritage, - propAtom, numFieldKeys); + FunctionNodeType initializer = fieldInitializerOpt(propAtom, numFieldKeys); if (!initializer) return false; @@ -7558,8 +7554,25 @@ Parser::classMember(YieldHandling yieldHandling, DefaultHandling d funName = propAtom; } - // .fieldKeys must be declared outside the scope .initializers is declared in, - // hence this extra scope. + // When |super()| is invoked, we search for the nearest scope containing + // |.initializers| to initialize the class fields. This set-up precludes + // declaring |.initializers| in the class scope, because in some syntactic + // contexts |super()| can appear nested in a class, while actually belonging + // to an outer class definition. + // + // Example: + // class Outer extends Base { + // field = 1; + // constructor() { + // class Inner { + // field = 2; + // + // // The super() call in the computed property name mustn't access + // // Inner's |.initializers| array, but instead Outer's. + // [super()]() {} + // } + // } + // } Maybe dotInitializersScope; if (isConstructor && !options().selfHostingMode) { dotInitializersScope.emplace(this); @@ -7747,9 +7760,8 @@ Parser::classDefinition(YieldHandling yieldHandling, size_t numFieldKeys = 0; for (;;) { bool done; - if (!classMember(yieldHandling, defaultHandling, classStmt, className, - classStartOffset, hasHeritage, numFields, numFieldKeys, - classMembers, &done)) + if (!classMember(yieldHandling, classStmt, className, classStartOffset, + hasHeritage, numFields, numFieldKeys, classMembers, &done)) return null(); if (done) break; @@ -7831,7 +7843,6 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class if (!funbox) return null(); funbox->initWithEnclosingParseContext(pc, functionSyntaxKind); - handler.setFunctionBox(funNode, funbox); funbox->setEnd(pos().end); // Push a ParseContext on to the stack. @@ -7944,8 +7955,7 @@ Parser::synthesizeConstructor(HandleAtom className, uint32_t class template typename ParseHandler::FunctionNodeType -Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage, - HandleAtom propAtom, size_t& numFieldKeys) +Parser::fieldInitializerOpt(HandleAtom propAtom, size_t& numFieldKeys) { bool hasInitializer = false; if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN)) @@ -7983,8 +7993,7 @@ Parser::fieldInitializerOpt(YieldHandling yieldHandling, bool hasH FunctionAsyncKind::SyncFunction, false); if (!funbox) return null(); - funbox->initFieldInitializer(pc, hasHeritage); - handler.setFunctionBox(funNode, funbox); + funbox->initFieldInitializer(pc); funbox->setStart(tokenStream, firstTokenPos); // Push a SourceParseContext on to the stack. diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 5222d6398c..fd0033ad7d 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -1489,7 +1489,6 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) ClassNodeType classDefinition(YieldHandling yieldHandling, ClassContext classContext, DefaultHandling defaultHandling); MOZ_MUST_USE bool classMember(YieldHandling yieldHandling, - DefaultHandling defaultHandling, const ParseContext::ClassStatement& classStmt, HandlePropertyName className, uint32_t classStartOffset, bool hasHeritage, @@ -1502,8 +1501,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) uint32_t classStartOffset, uint32_t classEndOffset, size_t numFieldsWithInitializers, ListNodeType& classMembers); - FunctionNodeType fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage, - HandleAtom atom, size_t& numFieldKeys); + FunctionNodeType fieldInitializerOpt(HandleAtom atom, size_t& numFieldKeys); FunctionNodeType synthesizeConstructor(HandleAtom className, uint32_t classNameOffset, bool hasHeritage); diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index edb2b93788..8aa068c1fc 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -455,7 +455,7 @@ class FunctionBox : public ObjectBox, public SharedContext void initFromLazyFunction(); void initStandaloneFunction(Scope* enclosingScope); void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind); - void initFieldInitializer(ParseContext* enclosing, bool hasHeritage); + void initFieldInitializer(ParseContext* enclosing); ObjectBox* toObjectBox() override { return this; } JSFunction* function() const { return &object->as(); }