Bug 877216. Add CallArgs-like structs for use in DOM specialized getters/setters/methods. r=waldo

This commit is contained in:
Boris Zbarsky 2013-05-30 17:47:00 -04:00
parent 7392630c95
commit cb38fb46c2
7 changed files with 204 additions and 77 deletions

View File

@ -5104,7 +5104,7 @@ class CGGenericMethod(CGAbstractBindingMethod):
return CGIndenter(CGGeneric( return CGIndenter(CGGeneric(
"const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
"MOZ_ASSERT(info->type == JSJitInfo::Method);\n" "MOZ_ASSERT(info->type == JSJitInfo::Method);\n"
"JSJitMethodOp method = (JSJitMethodOp)info->op;\n" "JSJitMethodOp method = info->method;\n"
"return method(cx, obj, self, argc, vp);")) "return method(cx, obj, self, argc, vp);"))
class CGSpecializedMethod(CGAbstractStaticMethod): class CGSpecializedMethod(CGAbstractStaticMethod):
@ -5240,7 +5240,7 @@ class CGGenericGetter(CGAbstractBindingMethod):
return CGIndenter(CGGeneric( return CGIndenter(CGGeneric(
"const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
"MOZ_ASSERT(info->type == JSJitInfo::Getter);\n" "MOZ_ASSERT(info->type == JSJitInfo::Getter);\n"
"JSJitPropertyOp getter = info->op;\n" "JSJitGetterOp getter = info->getter;\n"
"return getter(cx, obj, self, vp);")) "return getter(cx, obj, self, vp);"))
class CGSpecializedGetter(CGAbstractStaticMethod): class CGSpecializedGetter(CGAbstractStaticMethod):
@ -5324,7 +5324,7 @@ class CGGenericSetter(CGAbstractBindingMethod):
"JS::Value* argv = JS_ARGV(cx, vp);\n" "JS::Value* argv = JS_ARGV(cx, vp);\n"
"const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n" "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
"MOZ_ASSERT(info->type == JSJitInfo::Setter);\n" "MOZ_ASSERT(info->type == JSJitInfo::Setter);\n"
"JSJitPropertyOp setter = info->op;\n" "JSJitSetterOp setter = info->setter;\n"
"if (!setter(cx, obj, self, argv)) {\n" "if (!setter(cx, obj, self, argv)) {\n"
" return false;\n" " return false;\n"
"}\n" "}\n"
@ -5431,7 +5431,7 @@ class CGMemberJITInfo(CGThing):
"") "")
return ("\n" return ("\n"
"static const JSJitInfo %s = {\n" "static const JSJitInfo %s = {\n"
" %s,\n" " { %s },\n"
" %s,\n" " %s,\n"
" %s,\n" " %s,\n"
" JSJitInfo::%s,\n" " JSJitInfo::%s,\n"
@ -5445,7 +5445,9 @@ class CGMemberJITInfo(CGThing):
def define(self): def define(self):
if self.member.isAttr(): if self.member.isAttr():
getterinfo = ("%s_getterinfo" % self.member.identifier.name) getterinfo = ("%s_getterinfo" % self.member.identifier.name)
getter = ("(JSJitPropertyOp)get_%s" % self.member.identifier.name) # We need the cast here because JSJitGetterOp has a "void* self"
# while we have the right type.
getter = ("(JSJitGetterOp)get_%s" % self.member.identifier.name)
getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True) getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True)
getterconst = self.member.getExtendedAttribute("Constant") getterconst = self.member.getExtendedAttribute("Constant")
getterpure = getterconst or self.member.getExtendedAttribute("Pure") getterpure = getterconst or self.member.getExtendedAttribute("Pure")
@ -5457,7 +5459,9 @@ class CGMemberJITInfo(CGThing):
[self.member.type]) [self.member.type])
if not self.member.readonly or self.member.getExtendedAttribute("PutForwards") is not None: if not self.member.readonly or self.member.getExtendedAttribute("PutForwards") is not None:
setterinfo = ("%s_setterinfo" % self.member.identifier.name) setterinfo = ("%s_setterinfo" % self.member.identifier.name)
setter = ("(JSJitPropertyOp)set_%s" % self.member.identifier.name) # Actually a JSJitSetterOp, but JSJitGetterOp is first in the
# union.
setter = ("(JSJitGetterOp)set_%s" % self.member.identifier.name)
# Setters are always fallible, since they have to do a typed unwrap. # Setters are always fallible, since they have to do a typed unwrap.
result += self.defineJitInfo(setterinfo, setter, "Setter", result += self.defineJitInfo(setterinfo, setter, "Setter",
False, False, False, False, False, False,
@ -5466,8 +5470,8 @@ class CGMemberJITInfo(CGThing):
if self.member.isMethod(): if self.member.isMethod():
methodinfo = ("%s_methodinfo" % self.member.identifier.name) methodinfo = ("%s_methodinfo" % self.member.identifier.name)
name = CppKeywords.checkMethodName(self.member.identifier.name) name = CppKeywords.checkMethodName(self.member.identifier.name)
# Actually a JSJitMethodOp, but JSJitPropertyOp by struct definition. # Actually a JSJitMethodOp, but JSJitGetterOp is first in the union.
method = ("(JSJitPropertyOp)%s" % name) method = ("(JSJitGetterOp)%s" % name)
# Methods are infallible if they are infallible, have no arguments # Methods are infallible if they are infallible, have no arguments
# to unwrap, and have a return type that's infallible to wrap up for # to unwrap, and have a return type that's infallible to wrap up for

View File

@ -31,6 +31,7 @@
#include "mozilla/Assertions.h" #include "mozilla/Assertions.h"
#include "mozilla/Attributes.h" #include "mozilla/Attributes.h"
#include "mozilla/TypeTraits.h"
#include "jstypes.h" #include "jstypes.h"
@ -92,30 +93,50 @@ namespace JS {
* public interface are meant to be used by embedders! See inline comments to * public interface are meant to be used by embedders! See inline comments to
* for details. * for details.
*/ */
class MOZ_STACK_CLASS CallReceiver
namespace detail {
enum UsedRval { IncludeUsedRval, NoUsedRval };
template<UsedRval WantUsedRval>
class MOZ_STACK_CLASS UsedRvalBase;
template<>
class MOZ_STACK_CLASS UsedRvalBase<IncludeUsedRval>
{ {
protected: protected:
#ifdef DEBUG
mutable bool usedRval_; mutable bool usedRval_;
void setUsedRval() const { usedRval_ = true; } void setUsedRval() const { usedRval_ = true; }
void clearUsedRval() const { usedRval_ = false; } void clearUsedRval() const { usedRval_ = false; }
#else };
template<>
class MOZ_STACK_CLASS UsedRvalBase<NoUsedRval>
{
protected:
void setUsedRval() const {} void setUsedRval() const {}
void clearUsedRval() const {} void clearUsedRval() const {}
};
template<UsedRval WantUsedRval>
class MOZ_STACK_CLASS CallReceiverBase : public UsedRvalBase<
#ifdef DEBUG
WantUsedRval
#else
NoUsedRval
#endif #endif
>
{
protected:
Value *argv_; Value *argv_;
friend CallReceiver CallReceiverFromVp(Value *vp);
friend CallReceiver CallReceiverFromArgv(Value *argv);
public: public:
/* /*
* Returns the function being called, as an object. Must not be called * Returns the function being called, as an object. Must not be called
* after rval() has been used! * after rval() has been used!
*/ */
JSObject &callee() const { JSObject &callee() const {
MOZ_ASSERT(!usedRval_); MOZ_ASSERT(!this->usedRval_);
return argv_[-2].toObject(); return argv_[-2].toObject();
} }
@ -124,7 +145,7 @@ class MOZ_STACK_CLASS CallReceiver
* rval() has been used! * rval() has been used!
*/ */
HandleValue calleev() const { HandleValue calleev() const {
MOZ_ASSERT(!usedRval_); MOZ_ASSERT(!this->usedRval_);
return HandleValue::fromMarkedLocation(&argv_[-2]); return HandleValue::fromMarkedLocation(&argv_[-2]);
} }
@ -160,7 +181,7 @@ class MOZ_STACK_CLASS CallReceiver
* fails. * fails.
*/ */
MutableHandleValue rval() const { MutableHandleValue rval() const {
setUsedRval(); this->setUsedRval();
return MutableHandleValue::fromMarkedLocation(&argv_[-2]); return MutableHandleValue::fromMarkedLocation(&argv_[-2]);
} }
@ -171,7 +192,7 @@ class MOZ_STACK_CLASS CallReceiver
Value *base() const { return argv_ - 2; } Value *base() const { return argv_ - 2; }
Value *spAfterCall() const { Value *spAfterCall() const {
setUsedRval(); this->setUsedRval();
return argv_ - 1; return argv_ - 1;
} }
@ -181,7 +202,7 @@ class MOZ_STACK_CLASS CallReceiver
// it. You probably don't want to use these! // it. You probably don't want to use these!
void setCallee(Value aCalleev) const { void setCallee(Value aCalleev) const {
clearUsedRval(); this->clearUsedRval();
argv_[-2] = aCalleev; argv_[-2] = aCalleev;
} }
@ -194,6 +215,15 @@ class MOZ_STACK_CLASS CallReceiver
} }
}; };
} // namespace detail
class MOZ_STACK_CLASS CallReceiver : public detail::CallReceiverBase<detail::IncludeUsedRval>
{
private:
friend CallReceiver CallReceiverFromVp(Value *vp);
friend CallReceiver CallReceiverFromArgv(Value *argv);
};
MOZ_ALWAYS_INLINE CallReceiver MOZ_ALWAYS_INLINE CallReceiver
CallReceiverFromArgv(Value *argv) CallReceiverFromArgv(Value *argv)
{ {
@ -233,11 +263,63 @@ CallReceiverFromVp(Value *vp)
* public interface are meant to be used by embedders! See inline comments to * public interface are meant to be used by embedders! See inline comments to
* for details. * for details.
*/ */
class MOZ_STACK_CLASS CallArgs : public CallReceiver namespace detail {
template<UsedRval WantUsedRval>
class MOZ_STACK_CLASS CallArgsBase :
public mozilla::Conditional<WantUsedRval == detail::IncludeUsedRval,
CallReceiver,
CallReceiverBase<NoUsedRval> >::Type
{ {
protected: protected:
unsigned argc_; unsigned argc_;
public:
/* Returns the number of arguments. */
unsigned length() const { return argc_; }
/* Returns the i-th zero-indexed argument. */
Value &operator[](unsigned i) const {
MOZ_ASSERT(i < argc_);
return this->argv_[i];
}
/* Returns a mutable handle for the i-th zero-indexed argument. */
MutableHandleValue handleAt(unsigned i) const {
MOZ_ASSERT(i < argc_);
return MutableHandleValue::fromMarkedLocation(&this->argv_[i]);
}
/*
* Returns the i-th zero-indexed argument, or |undefined| if there's no
* such argument.
*/
Value get(unsigned i) const {
return i < length() ? this->argv_[i] : UndefinedValue();
}
/*
* Returns true if the i-th zero-indexed argument is present and is not
* |undefined|.
*/
bool hasDefined(unsigned i) const {
return i < argc_ && !this->argv_[i].isUndefined();
}
public:
// These methods are publicly exposed, but we're less sure of the interface
// here than we'd like (because they're hackish and drop assertions). Try
// to avoid using these if you can.
Value *array() const { return this->argv_; }
Value *end() const { return this->argv_ + argc_; }
};
} // namespace detail
class MOZ_STACK_CLASS CallArgs : public detail::CallArgsBase<detail::IncludeUsedRval>
{
private:
friend CallArgs CallArgsFromVp(unsigned argc, Value *vp); friend CallArgs CallArgsFromVp(unsigned argc, Value *vp);
friend CallArgs CallArgsFromSp(unsigned argc, Value *sp); friend CallArgs CallArgsFromSp(unsigned argc, Value *sp);
@ -249,45 +331,6 @@ class MOZ_STACK_CLASS CallArgs : public CallReceiver
return args; return args;
} }
public:
/* Returns the number of arguments. */
unsigned length() const { return argc_; }
/* Returns the i-th zero-indexed argument. */
Value &operator[](unsigned i) const {
MOZ_ASSERT(i < argc_);
return argv_[i];
}
/* Returns a mutable handle for the i-th zero-indexed argument. */
MutableHandleValue handleAt(unsigned i) const {
MOZ_ASSERT(i < argc_);
return MutableHandleValue::fromMarkedLocation(&argv_[i]);
}
/*
* Returns the i-th zero-indexed argument, or |undefined| if there's no
* such argument.
*/
Value get(unsigned i) const {
return i < length() ? argv_[i] : UndefinedValue();
}
/*
* Returns true if the i-th zero-indexed argument is present and is not
* |undefined|.
*/
bool hasDefined(unsigned i) const {
return i < argc_ && !argv_[i].isUndefined();
}
public:
// These methods are publicly exposed, but we're less sure of the interface
// here than we'd like (because they're hackish and drop assertions). Try
// to avoid using these if you can.
Value *array() const { return argv_; }
Value *end() const { return argv_ + argc_; }
}; };
MOZ_ALWAYS_INLINE CallArgs MOZ_ALWAYS_INLINE CallArgs

View File

@ -1453,7 +1453,7 @@ CodeGenerator::visitCallDOMNative(LCallDOMNative *call)
masm.passABIArg(argPrivate); masm.passABIArg(argPrivate);
masm.passABIArg(argArgc); masm.passABIArg(argArgc);
masm.passABIArg(argVp); masm.passABIArg(argVp);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target->jitInfo()->op)); masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, target->jitInfo()->method));
if (target->jitInfo()->isInfallible) { if (target->jitInfo()->isInfallible) {
masm.loadValue(Address(StackPointer, IonDOMMethodExitFrameLayout::offsetOfResult()), masm.loadValue(Address(StackPointer, IonDOMMethodExitFrameLayout::offsetOfResult()),

View File

@ -7791,7 +7791,8 @@ IonBuilder::jsop_setprop(HandlePropertyName name)
// properties. // properties.
RootedFunction setter(cx, commonSetter); RootedFunction setter(cx, commonSetter);
if (isDOM && TestShouldDOMCall(cx, objTypes, setter, JSJitInfo::Setter)) { if (isDOM && TestShouldDOMCall(cx, objTypes, setter, JSJitInfo::Setter)) {
MSetDOMProperty *set = MSetDOMProperty::New(setter->jitInfo()->op, obj, value); JS_ASSERT(setter->jitInfo()->type == JSJitInfo::Setter);
MSetDOMProperty *set = MSetDOMProperty::New(setter->jitInfo()->setter, obj, value);
if (!set) if (!set)
return false; return false;

View File

@ -6586,9 +6586,9 @@ class MSetDOMProperty
: public MAryInstruction<2>, : public MAryInstruction<2>,
public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> > public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >
{ {
const JSJitPropertyOp func_; const JSJitSetterOp func_;
MSetDOMProperty(const JSJitPropertyOp func, MDefinition *obj, MDefinition *val) MSetDOMProperty(const JSJitSetterOp func, MDefinition *obj, MDefinition *val)
: func_(func) : func_(func)
{ {
setOperand(0, obj); setOperand(0, obj);
@ -6598,12 +6598,12 @@ class MSetDOMProperty
public: public:
INSTRUCTION_HEADER(SetDOMProperty) INSTRUCTION_HEADER(SetDOMProperty)
static MSetDOMProperty *New(const JSJitPropertyOp func, MDefinition *obj, MDefinition *val) static MSetDOMProperty *New(const JSJitSetterOp func, MDefinition *obj, MDefinition *val)
{ {
return new MSetDOMProperty(func, obj, val); return new MSetDOMProperty(func, obj, val);
} }
const JSJitPropertyOp fun() { const JSJitSetterOp fun() {
return func_; return func_;
} }
@ -6631,6 +6631,7 @@ class MGetDOMProperty
: info_(jitinfo) : info_(jitinfo)
{ {
JS_ASSERT(jitinfo); JS_ASSERT(jitinfo);
JS_ASSERT(jitinfo->type == JSJitInfo::Getter);
setOperand(0, obj); setOperand(0, obj);
@ -6657,8 +6658,8 @@ class MGetDOMProperty
return new MGetDOMProperty(info, obj, guard); return new MGetDOMProperty(info, obj, guard);
} }
const JSJitPropertyOp fun() { const JSJitGetterOp fun() {
return info_->op; return info_->getter;
} }
bool isInfallible() const { bool isInfallible() const {
return info_->isInfallible; return info_->isInfallible;

View File

@ -14,6 +14,8 @@
#include "jspubtd.h" #include "jspubtd.h"
#include "jsprvtd.h" #include "jsprvtd.h"
#include "js/CallArgs.h"
/* /*
* This macro checks if the stack pointer has exceeded a given limit. If * This macro checks if the stack pointer has exceeded a given limit. If
* |tolerance| is non-zero, it returns true only if the stack pointer has * |tolerance| is non-zero, it returns true only if the stack pointer has
@ -1432,14 +1434,86 @@ JS_GetDataViewByteLength(JSObject *obj);
JS_FRIEND_API(void *) JS_FRIEND_API(void *)
JS_GetDataViewData(JSObject *obj); JS_GetDataViewData(JSObject *obj);
/*
* A class, expected to be passed by value, which represents the CallArgs for a
* JSJitGetterOp.
*/
class JSJitGetterCallArgs : protected JS::MutableHandleValue
{
public:
explicit JSJitGetterCallArgs(const JS::CallArgs& args)
: JS::MutableHandleValue(args.rval())
{}
JS::MutableHandleValue rval() {
return *this;
}
};
/*
* A class, expected to be passed by value, which represents the CallArgs for a
* JSJitSetterOp.
*/
class JSJitSetterCallArgs : protected JS::MutableHandleValue
{
public:
explicit JSJitSetterCallArgs(const JS::CallArgs& args)
: JS::MutableHandleValue(args.handleAt(0))
{}
JS::MutableHandleValue handleAt(unsigned i) {
MOZ_ASSERT(i == 0);
return *this;
}
unsigned length() const { return 1; }
// Add get() or maybe hasDefined() as needed
};
/*
* A class, expected to be passed by reference, which represents the CallArgs
* for a JSJitMethodOp.
*/
class JSJitMethodCallArgs : protected JS::detail::CallArgsBase<JS::detail::NoUsedRval>
{
private:
typedef JS::detail::CallArgsBase<JS::detail::NoUsedRval> Base;
public:
explicit JSJitMethodCallArgs(const JS::CallArgs& args) {
argv_ = args.array();
argc_ = args.length();
}
JS::MutableHandleValue rval() const {
return Base::rval();
}
unsigned length() const { return Base::length(); }
JS::MutableHandleValue handleAt(unsigned i) const {
return Base::handleAt(i);
}
bool hasDefined(unsigned i) const {
return Base::hasDefined(i);
}
// Add get() as needed
};
/* /*
* This struct contains metadata passed from the DOM to the JS Engine for JIT * This struct contains metadata passed from the DOM to the JS Engine for JIT
* optimizations on DOM property accessors. Eventually, this should be made * optimizations on DOM property accessors. Eventually, this should be made
* available to general JSAPI users, but we are not currently ready to do so. * available to general JSAPI users, but we are not currently ready to do so.
*/ */
typedef bool typedef bool
(* JSJitPropertyOp)(JSContext *cx, JSHandleObject thisObj, (* JSJitGetterOp)(JSContext *cx, JSHandleObject thisObj,
void *specializedThis, JS::Value *vp); void *specializedThis, JS::Value *vp);
typedef bool
(* JSJitSetterOp)(JSContext *cx, JSHandleObject thisObj,
void *specializedThis, JS::Value *vp);
typedef bool typedef bool
(* JSJitMethodOp)(JSContext *cx, JSHandleObject thisObj, (* JSJitMethodOp)(JSContext *cx, JSHandleObject thisObj,
void *specializedThis, unsigned argc, JS::Value *vp); void *specializedThis, unsigned argc, JS::Value *vp);
@ -1451,7 +1525,11 @@ struct JSJitInfo {
Method Method
}; };
JSJitPropertyOp op; union {
JSJitGetterOp getter;
JSJitSetterOp setter;
JSJitMethodOp method;
};
uint32_t protoID; uint32_t protoID;
uint32_t depth; uint32_t depth;
OpType type; OpType type;

View File

@ -4604,7 +4604,7 @@ dom_doFoo(JSContext* cx, JSHandleObject obj, void *self, unsigned argc, JS::Valu
} }
const JSJitInfo dom_x_getterinfo = { const JSJitInfo dom_x_getterinfo = {
(JSJitPropertyOp)dom_get_x, { (JSJitGetterOp)dom_get_x },
0, /* protoID */ 0, /* protoID */
0, /* depth */ 0, /* depth */
JSJitInfo::Getter, JSJitInfo::Getter,
@ -4613,7 +4613,7 @@ const JSJitInfo dom_x_getterinfo = {
}; };
const JSJitInfo dom_x_setterinfo = { const JSJitInfo dom_x_setterinfo = {
(JSJitPropertyOp)dom_set_x, { (JSJitGetterOp)dom_set_x },
0, /* protoID */ 0, /* protoID */
0, /* depth */ 0, /* depth */
JSJitInfo::Setter, JSJitInfo::Setter,
@ -4622,7 +4622,7 @@ const JSJitInfo dom_x_setterinfo = {
}; };
const JSJitInfo doFoo_methodinfo = { const JSJitInfo doFoo_methodinfo = {
(JSJitPropertyOp)dom_doFoo, { (JSJitGetterOp)dom_doFoo },
0, /* protoID */ 0, /* protoID */
0, /* depth */ 0, /* depth */
JSJitInfo::Method, JSJitInfo::Method,
@ -4684,7 +4684,7 @@ dom_genericGetter(JSContext *cx, unsigned argc, JS::Value *vp)
const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));
MOZ_ASSERT(info->type == JSJitInfo::Getter); MOZ_ASSERT(info->type == JSJitInfo::Getter);
JSJitPropertyOp getter = info->op; JSJitGetterOp getter = info->getter;
return getter(cx, obj, val.toPrivate(), vp); return getter(cx, obj, val.toPrivate(), vp);
} }
@ -4707,7 +4707,7 @@ dom_genericSetter(JSContext* cx, unsigned argc, JS::Value* vp)
const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));
MOZ_ASSERT(info->type == JSJitInfo::Setter); MOZ_ASSERT(info->type == JSJitInfo::Setter);
JSJitPropertyOp setter = info->op; JSJitSetterOp setter = info->setter;
if (!setter(cx, obj, val.toPrivate(), argv)) if (!setter(cx, obj, val.toPrivate(), argv))
return false; return false;
JS_SET_RVAL(cx, vp, UndefinedValue()); JS_SET_RVAL(cx, vp, UndefinedValue());
@ -4730,7 +4730,7 @@ dom_genericMethod(JSContext* cx, unsigned argc, JS::Value *vp)
const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp)); const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));
MOZ_ASSERT(info->type == JSJitInfo::Method); MOZ_ASSERT(info->type == JSJitInfo::Method);
JSJitMethodOp method = (JSJitMethodOp)info->op; JSJitMethodOp method = info->method;
return method(cx, obj, val.toPrivate(), argc, vp); return method(cx, obj, val.toPrivate(), argc, vp);
} }