Discussion:
How do I make a reference to an JS object.
(too old to reply)
Second Datke
2015-05-31 16:23:17 UTC
Permalink
Hi, I'm working on integrating SpiderMonkey with my game engine. I have some "Nodes", for which I would like to have a data field with an empty value initially and to be set in script. And the node just holds a reference to it's data field, it may read its properties at any time, and also keeps it from being GCed while the node is alive.

As you can see, nodes are mortal, so PersistentRooted is passed, as its documented to be used for objects that lives as long as the program. Heap objects is another option provided in SpiderMonkey, but I'm not very clear about what "heap" is, and it is said that heap objects needs to be traced manually, so what does "Heap" and "trace" means here, what's the relationship of them and GC? If I have many nodes, that is, many PersistentRooted or Heap, would it lead to slow performance?

Currently I used a workaround for it. I defined a "HandleValue *data", initialize with UndefinedHandleValue, and "repoint" to a RootedValue in setters (and obviously getters use rval().set(HandleValue)). I dont know whether it's GC friendly, or may lead to undefined behavior, since Handle/MutableHandle is only for callback parameters, and RootedValue only for stack according to the guide. But it seems work.
Kent Williams
2015-06-01 14:34:38 UTC
Permalink
First off, this is your bible:
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/GC_Rooting_Guide

Second 'Heap' means dynamically allocated. If you look at the section
"GC Things on the heap" it shows you an example.

What you're doing is kind of wrong. You can have (as the example shows)
an instance of JS::Heap

JS::Heap<JS::Value> mSomeObject;

as part of your Node structure/class.

Then you can create objects with JS_NewObject* functions, and assign to
mSomeObject.

You can also assign to a RootedValue, and then assign to a
JS::Heap<JS::Value> object from the RootedValue.


How this works -- and someone correct me if I'm wrong -- is that
JS::Value and JS::RootedObject (and JS::RootedString) are
reference-counting objects. Bare pointers are leaps waiting to happen,
by 'rooting' a pointer, you're letting the GC system know about it.

So when a JS::Rooted<T> object or JS::Heap<T> object goes out of scope,
its reference count is decremented, and objects with a reference count
of zero are candidates for garbage collection.
Post by Second Datke
Hi, I'm working on integrating SpiderMonkey with my game engine. I have some "Nodes", for which I would like to have a data field with an empty value initially and to be set in script. And the node just holds a reference to it's data field, it may read its properties at any time, and also keeps it from being GCed while the node is alive.
As you can see, nodes are mortal, so PersistentRooted is passed, as its documented to be used for objects that lives as long as the program. Heap objects is another option provided in SpiderMonkey, but I'm not very clear about what "heap" is, and it is said that heap objects needs to be traced manually, so what does "Heap" and "trace" means here, what's the relationship of them and GC? If I have many nodes, that is, many PersistentRooted or Heap, would it lead to slow performance?
Currently I used a workaround for it. I defined a "HandleValue *data", initialize with UndefinedHandleValue, and "repoint" to a RootedValue in setters (and obviously getters use rval().set(HandleValue)). I dont know whether it's GC friendly, or may lead to undefined behavior, since Handle/MutableHandle is only for callback parameters, and RootedValue only for stack according to the guide. But it seems work.
_______________________________________________
dev-tech-js-engine mailing list
https://lists.mozilla.org/listinfo/dev-tech-js-engine
--
*Kent Williams*| C++ Developer
*CourseLeaf from Leepfrog Technologies *

/“The Process of Academic Change”
/319-337-3877 | courseleaf.com <http://www.courseleaf.com/>

/This message contains confidential information and is intended only for
the individual named. If you are not the intended recipient of this
transmission or a person responsible for delivering it to the intended
recipient, any disclosure, copying, distribution, or other use of any of
the information in this transmission is strictly prohibited. Please
notify the sender immediately by e-mail if you have received this e-mail
by mistake and delete this e-mail from your system./
Boris Zbarsky
2015-06-01 15:27:33 UTC
Permalink
Post by Second Datke
As you can see, nodes are mortal, so PersistentRooted is passed, as
its documented to be used for objects that lives as long as the
program.
Where is that documented?

PersistentRooted should be used for objects that live as long as the
PersistentRooted and which can't affect the lifetime of the
PersistentRooted. I don't know whether that's the case in your
situation or not.

Basically, a PersistentRooted ensures the thing it points to survives GC
as long as the PersistentRooted object exists. It's appropriate in your
case if there can't be any backreferences from the data to your Node
(which does seem unlikely if the data is consumer-provided).
Post by Second Datke
Heap objects is another option provided in SpiderMonkey, but
I'm not very clear about what "heap" is
Heap<T> should be used for anything that's not PersistentRooted and
doesn't have stack lifetime.
Post by Second Datke
and it is said that heap objects needs to be traced manually
Right, that's what keeps them from being garbage collected while you're
still referencing them.
Post by Second Datke
so what does "Heap" and "trace" means here
"Heap" means "the thing that owns this is on the C heap, not the C
stack". "trace" means "tell the GC there is an edge from the thing that
has the Heap member to the thing the Heap is pointing to".
Post by Second Datke
If I have many nodes, that is, many PersistentRooted or Heap, would it lead to slow
performance?
Past the fact that GC has to walk them all, no.

So here is a question for you. You said you have these "Node" objects.
Are they JS objects? Where inside them are you storing the "data" member?
Post by Second Datke
Currently I used a workaround for it. I defined a "HandleValue
*data", initialize with UndefinedHandleValue, and "repoint" to a
RootedValue in setters
This sounds like you're basically leaving dangling pointers in your data
member, since you're pointing them at the stack and then letting the
stack unwind. Please don't do that.

-Boris
Second Datke
2015-06-01 18:33:21 UTC
Permalink
在 2015年6月1日星期一 UTC+8下午11:27:34,Boris Zbarsky写道:
Post by Boris Zbarsky
Post by Second Datke
As you can see, nodes are mortal, so PersistentRooted is passed, as
its documented to be used for objects that lives as long as the
program.
Where is that documented?
PersistentRooted should be used for objects that live as long as the
PersistentRooted and which can't affect the lifetime of the
PersistentRooted. I don't know whether that's the case in your
situation or not.
Basically, a PersistentRooted ensures the thing it points to survives GC
as long as the PersistentRooted object exists. It's appropriate in your
case if there can't be any backreferences from the data to your Node
(which does seem unlikely if the data is consumer-provided).
Post by Second Datke
Heap objects is another option provided in SpiderMonkey, but
I'm not very clear about what "heap" is
Heap<T> should be used for anything that's not PersistentRooted and
doesn't have stack lifetime.
Post by Second Datke
and it is said that heap objects needs to be traced manually
Right, that's what keeps them from being garbage collected while you're
still referencing them.
Post by Second Datke
so what does "Heap" and "trace" means here
"Heap" means "the thing that owns this is on the C heap, not the C
stack". "trace" means "tell the GC there is an edge from the thing that
has the Heap member to the thing the Heap is pointing to".
Post by Second Datke
If I have many nodes, that is, many PersistentRooted or Heap, would it lead to slow
performance?
Past the fact that GC has to walk them all, no.
So here is a question for you. You said you have these "Node" objects.
Are they JS objects? Where inside them are you storing the "data" member?
Post by Second Datke
Currently I used a workaround for it. I defined a "HandleValue
*data", initialize with UndefinedHandleValue, and "repoint" to a
RootedValue in setters
This sounds like you're basically leaving dangling pointers in your data
member, since you're pointing them at the stack and then letting the
stack unwind. Please don't do that.
-Boris
Thanks for your reply and explanation of these structures!
Post by Boris Zbarsky
Use JS::PersistentRooted<T> for things that are alive until the process exits.
I have no idea about whether to trust it. But I also read the comments of
RootingAPI.h above the definition of PersistentRooted<T>, however I cannot
fully understand. As you said, PersistentRooted<T> ensures objects live as long
as the PersistentRooted<T> alive. But what if the PersistentRooted<T> is deleted?
Would it be treated as a common GC thing as other JS GC things?
Post by Boris Zbarsky
And yes, what I want is just reference counting that shared among C++ and JS. I'll
demonstrate my idea more clearly: I have a C++ Node class, which can be accessed
from JS, and it has a JS data field, which can be accessed from both C++ and JS,
and I want it integrated in to the Node class as a field. It's value is assigned in JS
and as long as the Node is alive, whether the JS side holds any reference to the data
or not, the data would not be collected. But when the Node is deleted, if the data is
referenced by JS, it would also keep away from being collected.
In fact, the "Node" in my problem is actually C++ objects, which is wrapped and exposed
to JS with JS class. The data field acts very similar to a field of JS objects. The only difference
is that the C++ Nodes also hold a reference to its data.

What object is stored in the data is just free for scripts. If possible, I want it able to be undefined, numbers, strings, booleans, objects etc. Although the very most of time they
are objects, I just want to do it gracefully and more freedom for scripting. Please pay
attention that it is assigned in JS, such as

some_node.data = new AnimationNodeData();

But its lifetime is shared as soon as the assignment takes place.

A little digression. JS::Heap<T> should be traced, and how to trace it? I googled and
found this, is it correct?

http://stackoverflow.com/questions/27393732/how-to-create-handle-and-destroy-jsheapt-objects-in-spidermonkey

And why JS::Heap<T> is designed this way? What if it is traced, or "rooted"
as long as created?

What this answer

Besides, I also looked through the definition of JS::PersistentRooted<T>. It seems that
it just added itself to a global list of JSRuntime. How could it hold up, if there are many
PersistentRooted inside the list?

TBH, I'm sometimes generous about memory now. Since I'm just prototyping. If GC becomes
an impact it can just be delayed. But I want to ensure that there is no leak, and no dangling.

What's more, if the data holds a backreference to node, using JS::PersistentRooted<T> cause a leak. As it's said in the comment in JSRootingAPI.h, that's because the C++ object is
not collected. But in my case, the lifetime of Nodes is managed in C++, JS would only holds
a pointer, In fact nodes would never be collected in JS, but deleted in C++. So the
PersistentRooted is destroyed, when the node is destructed manually, on C++ side.

Thus I think it should not be a problem. It seems the data would not have such
a backreference, I'm just talking for this case.
Post by Boris Zbarsky
The only exception to this is if they are added as roots with the JS_Add<T>Root()
functions or JS::PersistentRooted class, but don't do this unless it's really necessary.
It seems JS::PersistentRooted<T> is not recommended. But what's the difference between
a JS::PersistentRooted<T> and a traced JS::Heap<T>?
Boris Zbarsky
2015-06-01 20:33:25 UTC
Permalink
Post by Second Datke
Post by Boris Zbarsky
Use JS::PersistentRooted<T> for things that are alive until the process exits.
Ah. So in the context of Gecko, this is good "tl;dr" advice, since
pretty much any object can be referenced from JS, so you pretty much
have to assume that if you're using PersistentRooted that will keep the
target alive forever due to cycles through the JS heap keeping the
PersistentRooted alive.

If your situation is different from what I describe above, then you can
use PersistentRooted for things that don't have process lifetime, and in
fact Gecko does just that in cases when the owner of the
PersistentRooted _cannot_ be kept alive from JS.
Post by Second Datke
As you said, PersistentRooted<T> ensures objects live as long
as the PersistentRooted<T> alive. But what if the PersistentRooted<T> is deleted?
Then the object can go away after that point, assuming nothing else is
keeping it alive.
Post by Second Datke
Would it be treated as a common GC thing as other JS GC things?
It's always treated as a common GC thing. It's just that having a
PersistentRooted pointing to it ensures that GC treats it as live.
Post by Second Datke
In fact, the "Node" in my problem is actually C++ objects, which is wrapped and exposed
to JS with JS class. The data field acts very similar to a field of JS objects.
..
Post by Second Datke
What object is stored in the data is just free for scripts. If possible, I want it able to be undefined, numbers, strings, booleans, objects etc. Although the very most of time they
are objects, I just want to do it gracefully and more freedom for scripting..
OK, then you fundamentally have two options.

1) Store the data in a reserved slot on your JS object. Have C++
access get it from that reserved slot (which means your C++ object needs
a way to get to the JS wrapper, and that the JS wrapper is somehow
already guaranteed to have the same lifetime as the C++ object). Don't
store the JS value in a C++ struct directly. The JS engine will handle
the GC interactions here. This option is probably better if you're
guaranteed your JS object outlives your Node C++ thing.

2) Store the data in a Heap<Value> in your C++ object. Implement a
trace JSClass hook (this is the JSTraceOp member of a JSClass, named
"trace"). This member will look something like:

void MyTraceHook(JSTracer* trc, JSObject* myObj) {
Node* myNode = GetNodePointerFromObject(myObj);
JS_CallValueTracer(trc, &myNode->myHeap,
"whatever informational string");
}

this will work as long as your wrapper JS object is alive. If it stops
being alive before the Node is destroyed.... well, what do you want to
happen with the data in that case?
Post by Second Datke
A little digression. JS::Heap<T> should be traced, and how to trace it? I googled and
found this, is it correct?
http://stackoverflow.com/questions/27393732/how-to-create-handle-and-destroy-jsheapt-objects-in-spidermonkey
That applies to cases when you want to trace things as roots. That's
not what you want here; if you did you'd just use a PersistentRooted and
be done with it.
Post by Second Datke
Besides, I also looked through the definition of JS::PersistentRooted<T>. It seems that
it just added itself to a global list of JSRuntime. How could it hold up, if there are many
PersistentRooted inside the list?
Hold up in what sense? That's just the list of GC roots. That list
gets walked during a GC... but so does the whole JS heap anyway.
Post by Second Datke
What's more, if the data holds a backreference to node, using JS::PersistentRooted<T> cause a leak.
Yes, indeed.
Post by Second Datke
As it's said in the comment in JSRootingAPI.h, that's because the C++ object is
not collected. But in my case, the lifetime of Nodes is managed in C++
This part I'm no clear on. You have a JS wrapper for a Node, yes? Can
that wrapper outlive the Node it wraps? If not, why not? If it can,
what happens if the JS wrapper is accessed after the Node has been deleted?
Post by Second Datke
It seems JS::PersistentRooted<T> is not recommended.
Inside Gecko, correct, for the reasons I described above.
Post by Second Datke
But what's the difference between
a JS::PersistentRooted<T> and a traced JS::Heap<T>?
The former stays alive a long as the PersistentRooted object is alive,
period.

The latter is alive only as long as it's reachable from some GC root.
"Tracing" the Heap<T> is just telling the GC about an edge from some
other GC thing (in my example above the Node wrapper JS object) to the
thing stored in the Heap<T>.

-Boris
Boris Zbarsky
2015-06-01 15:30:02 UTC
Permalink
Post by Kent Williams
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/GC_Rooting_Guide
Yes, indeed.
Post by Kent Williams
How this works -- and someone correct me if I'm wrong -- is that
JS::Value and JS::RootedObject (and JS::RootedString) are
reference-counting objects.
I assume you meant JS::RootedValue.

These are not reference counts. They just add an entry to a "list of
things to keep alive". That said, the effect is pretty similar to what
it would be with reference counts, as long as you stick to putting
Rooted<T> on the stack.
Post by Kent Williams
So when a JS::Rooted<T> object or JS::Heap<T> object goes out of scope,
A Heap<T> doesn't do anything to keep anything alive. It just provides
notifications to the GC when it gets assigned to... If you want to keep
something stored in a Heap<T> alive, you have to trace it.

-Boris
Second Datke
2015-06-01 17:28:49 UTC
Permalink
在 2015年6月1日星期一 UTC+8下午10:35:23,Kent Williams写道:
Post by Kent Williams
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/GC_Rooting_Guide
Second 'Heap' means dynamically allocated. If you look at the section
"GC Things on the heap" it shows you an example.
What you're doing is kind of wrong. You can have (as the example shows)
an instance of JS::Heap
JS::Heap<JS::Value> mSomeObject;
as part of your Node structure/class.
Then you can create objects with JS_NewObject* functions, and assign to
mSomeObject.
You can also assign to a RootedValue, and then assign to a
JS::Heap<JS::Value> object from the RootedValue.
How this works -- and someone correct me if I'm wrong -- is that
JS::Value and JS::RootedObject (and JS::RootedString) are
reference-counting objects. Bare pointers are leaps waiting to happen,
by 'rooting' a pointer, you're letting the GC system know about it.
So when a JS::Rooted<T> object or JS::Heap<T> object goes out of scope,
its reference count is decremented, and objects with a reference count
of zero are candidates for garbage collection.
Post by Second Datke
Hi, I'm working on integrating SpiderMonkey with my game engine. I have some "Nodes", for which I would like to have a data field with an empty value initially and to be set in script. And the node just holds a reference to it's data field, it may read its properties at any time, and also keeps it from being GCed while the node is alive.
As you can see, nodes are mortal, so PersistentRooted is passed, as its documented to be used for objects that lives as long as the program. Heap objects is another option provided in SpiderMonkey, but I'm not very clear about what "heap" is, and it is said that heap objects needs to be traced manually, so what does "Heap" and "trace" means here, what's the relationship of them and GC? If I have many nodes, that is, many PersistentRooted or Heap, would it lead to slow performance?
Currently I used a workaround for it. I defined a "HandleValue *data", initialize with UndefinedHandleValue, and "repoint" to a RootedValue in setters (and obviously getters use rval().set(HandleValue)). I dont know whether it's GC friendly, or may lead to undefined behavior, since Handle/MutableHandle is only for callback parameters, and RootedValue only for stack according to the guide. But it seems work.
_______________________________________________
dev-tech-js-engine mailing list
https://lists.mozilla.org/listinfo/dev-tech-js-engine
--
*Kent Williams*| C++ Developer
*CourseLeaf from Leepfrog Technologies *
/“The Process of Academic Change”
/319-337-3877 | courseleaf.com <http://www.courseleaf.com/>
/This message contains confidential information and is intended only for
the individual named. If you are not the intended recipient of this
transmission or a person responsible for delivering it to the intended
recipient, any disclosure, copying, distribution, or other use of any of
the information in this transmission is strictly prohibited. Please
notify the sender immediately by e-mail if you have received this e-mail
by mistake and delete this e-mail from your system./
Thanks. I read it for several times and I'm not very clear of some concepts in it (such as "Rooting", "Heap", "Trace" etc.) so I'm asking here, since I'm not very familiar about memory stuffs.

And yes, what I want is just reference counting that shared among C++ and JS. I'll demonstrate my idea more clearly: I have a C++ Node class, which can be accessed from JS, and it has a JS data field, which can be accessed from both C++ and JS, and I want it integrated in to the Node class as a field. It's value is assigned in JS and as long as the Node is alive, whether the JS side holds any reference to the data or not, the data would not be collected. But when the Node is deleted, if the data is referenced by JS, it would also keep away from being collected.
Boris Zbarsky
2015-06-01 18:27:21 UTC
Permalink
Post by Second Datke
And yes, what I want is just reference counting that shared among C++ and JS.
No, you don't, unless you know your references are acyclic or you have a
reference cycle collector.
Post by Second Datke
I'll demonstrate my idea more clearly: I have a C++ Node class, which can be accessed from JS
How, exactly? The details here matter.
Post by Second Datke
But when the Node is deleted
Can the Node be kept alive by JS, or only by C++?

-Boris
Second Datke
2015-06-01 18:51:49 UTC
Permalink
在 2015年6月2日星期二 UTC+8上午2:27:23,Boris Zbarsky写道:
Post by Boris Zbarsky
Post by Second Datke
And yes, what I want is just reference counting that shared among C++ and JS.
No, you don't, unless you know your references are acyclic or you have a
reference cycle collector.
Post by Second Datke
I'll demonstrate my idea more clearly: I have a C++ Node class, which can be accessed from JS
How, exactly? The details here matter.
Post by Second Datke
But when the Node is deleted
Can the Node be kept alive by JS, or only by C++?
-Boris
Some details is inside my latest reply.

First, the reference might be cyclic (though very unlikely). But the lifetime of C++
side is manually managed, so the cycle would be broken as soon as the Node is
deleted. I just want the data not collected if it is referenced by other objects in JS.

Please ignore what would happen if the backreference is accessed after the Node's
death. That's one of the reason why it makes no sense for me to have these kind of
reference in data. But a Node's destruction would trigger an event, which might
notify something to invalidate the references. However it's not related to this problem.

According to what I have said, it is obvious that nodes are created in C++, and managed
by C++. Scripts, of course, is allowed to create nodes, but it's made available with a
factory function, which just called "new" and add the node to a global node manager,
and returns a handle, which wraps a pointer to node to JS. JS would call "destroy"
explicitly when time comes to destroy a node. So a node's lifetime is *not related
to GC at all*.

I apologize for ignoring these important details in my topic.
Boris Zbarsky
2015-06-01 20:42:21 UTC
Permalink
But the lifetime of C++ side is manually managed
See my questions about that in reply to your latest mail.
I just want the data not collected if it is referenced by other objects in JS.
That will be the case no matter what. As long as the JS object is
reachable from other live stuff, it will stay alive. The only question
is what happens when it's _not_ directly reachable from other JS stuff.
According to what I have said, it is obvious that nodes are created in C++, and managed
by C++. Scripts, of course, is allowed to create nodes, but it's made available with a
factory function, which just called "new" and add the node to a global node manager,
and returns a handle, which wraps a pointer to node to JS. JS would call "destroy"
explicitly when time comes to destroy a node. So a node's lifetime is *not related
to GC at all*.
If this is the case, then using a PersistentRooted member in a Node is
just fine.

-Boris
Second Datke
2015-06-01 21:27:04 UTC
Permalink
在 2015年6月2日星期二 UTC+8上午4:42:22,Boris Zbarsky写道:
Post by Boris Zbarsky
But the lifetime of C++ side is manually managed
See my questions about that in reply to your latest mail.
I just want the data not collected if it is referenced by other objects in JS.
That will be the case no matter what. As long as the JS object is
reachable from other live stuff, it will stay alive. The only question
is what happens when it's _not_ directly reachable from other JS stuff.
According to what I have said, it is obvious that nodes are created in C++, and managed
by C++. Scripts, of course, is allowed to create nodes, but it's made available with a
factory function, which just called "new" and add the node to a global node manager,
and returns a handle, which wraps a pointer to node to JS. JS would call "destroy"
explicitly when time comes to destroy a node. So a node's lifetime is *not related
to GC at all*.
If this is the case, then using a PersistentRooted member in a Node is
just fine.
-Boris
Well, got it. And I become much more clearer about GC things inside SpiderMonkey
now, thanks to your help :D

In case the wrapper JS object become dangling, if you are interested:

Game engines, to some degree, is different from Web Browser Engines. For example,
there are no specification for it; I'm not racing with bleeding edge commercial
ones; they can be armed with high level tools such as JavaScript for ease and flexibility,
but there could also be many constraints for scripting ... In one word, things got much
much more simple and predictable in game engines (I'm saying about gameplay level,
which is for scripts).

For example, if one unit A is chasing another B, and B dead suddenly, then A should
stop at once, and clear its "target" field. At least in my design, the scene is updated
"unit by unit", every unit just care about itself. A tick is invoked through an "Update"
callback of a Node, passing a wrapper in. If you doesn't store it elsewhere in the
function, it just destructs. There would be few cross-reference, but
cross-references are made through a handle storing UUID, which can be used to
validate objects, instead of pointers.

If a unit just found that one of it's essential components is somehow missing, it could
just throw a Fatal Error then abort(). Besides, if a node is deleted, it must be removed
out of the scene formerly. Any operation to an object that doesnot exist in the scene
just makes no sense.

Continue reading on narkive:
Loading...