

Show HN: My weekend project - Common API for Javascript Engines - udp
https://github.com/udp/CAJE

======
apaprocki
I've tried to do this to some extent to prevent native code hooked up to the
embedded engine from being tied to one implementation, but it doesn't work
very well in practice. Speaking for Spidermonkey, there are many, many
functions which exist in jsapi.h which will not translate well to some common
wrapper but will be very common in embedded use when performance is a concern.
There are many flavors of strings and functions to create them / manipulate
them because they all have slightly different semantics. Why copy a string
when you can pass ownership of it to the engine? Are you supplying UTF-16 or
ASCII null-terminated strings? GC issues are another area where engine-
specific APIs are needed. You might need to add GC tracing into your class, or
manually root values that are stored in the heap, or a number of other things.
This could be useful for some very simple situations (such as just creating a
shell that can execute JS in either environment with a command line switch).

For example, in the source you use JS_AddValueRoot/JS_RemoveValueRoot, which
will bog down performance if the app purposely wants to keep values on the
stack to avoid having to call into the engine to manage roots.
CAJE::MozValueRef::AsString() always allocates memory and potentially flattens
the string and copies it every time the method is called. You might want to
simply peek at the bytes without allocating a copy. All of these things will
add up over time and you'd basically wind up with disjoint features again and
a large subset of what is already in jsapi.h.

~~~
udp
Hmm, I understand where you're coming from, but I'd argue that many
applications embedding Javascript engines - particularly libraries trying to
provide JS bindings - won't need anything more complicated than simple
script/function/value manipulation (or at least, nothing that can't be wrapped
into something high-level and engine agnostic).

It's true that some of the current APIs are implemented in a clunky way, but
I'm quite confident there are better ways to do things while still maintaining
support for both engines.

~~~
apaprocki
For simple things it might work, but if a library wrapper was written using
your wrapper instead of the native API then there is a good chance it will not
be as performance optimized if it was simply using the engine API. That could
mean that the work put into that binding would have to be forked / duplicated
to create a lower-level binding that could take advantages of some of the
engine APIs you don't wrap in the common interface.

~~~
udp
Understood, but I'm not ready to give up on this just yet :-)

I know you think this is a futile effort, but I'd welcome any improvements
from someone who knows more about the engine internals than I do. I'm
currently working out what I can do instead of rooting every ValueRef.

~~~
apaprocki
For the rooting, you can't know if someone is storing you on the stack unless
you get hack-y[1]. So you'd have to assume the opposite, that the caller is
storing you on the stack and give them something like .root() to call if they
are not.

[1] In the end, the way stack-scanning GC works is that it has OS-specific
code (see jsnativestack.cpp[2] in SM) to get the highest stack address (stack
grows down in all the archs you care about). Then since you know someone is
calling into your constructor, you can declare a variable on the stack and get
its address to find out the current stack pointer. If 'this' is between the
current stack pointer and the stack base, you are on the stack and do not need
to root yourself.[3]

[2] [http://hg.mozilla.org/mozilla-
central/file/a1b89b4913ca/js/s...](http://hg.mozilla.org/mozilla-
central/file/a1b89b4913ca/js/src/jsnativestack.cpp)

[3] I coded an example up to show you (it uses an OSX-specific pthread API to
get the stack base -- see the SM code for other platforms)
<http://pastebin.com/dVDGWJP5>

------
pagekalisedown
Can't help but think:

<http://xkcd.com/927/>

~~~
udp
True, but this is more of a convenience wrapper than a standard.

It can happily wrap existing contexts, so it means that eg. one set of
function bindings can be used with both V8 and TraceMonkey (even _without_ the
embedding application using the CAJE API, only the bindings).

~~~
pagekalisedown
Reminds me of this:

<http://www.adrianboeing.com/pal/>

Great in theory, but not worth the effort, so very low rate of adoption.

