Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

You're going to have to rewrite it anyway

684 views
Skip to first unread message

Timothy J Fontaine

unread,
Jul 8, 2013, 2:35:36 PM7/8/13
to nod...@googlegroups.com, Bert Belder, Ben Noordhuis, Fedor Indutny, Trevor Norris, Scott Blomquist, Nathan Rajlich, Isaac Schlueter

Node v1.0 is approaching, and v0.12 is imminent (as far as that goes for FOSS
projects). As we work towards getting v0.12 out the door, there have been a lot
of changes happening for node's primary dependency v8. Ben is working on moving
us to the 3.20 branch, follow his progress

As you can tell this is a signficant change to the API, which requires a touch
of virtually every file in our `src/`, this has been a huge headache for him,
and will ultimately cause a huge headache for developers of binary addons.

You're going to have to `#ifdef` around significant portions of the API to keep
your module working across different version of node, this is going to cause
endless amounts of pain and issues for node and developers who have for the
most part been accepting of the churn in our underspecified addon API.

This one is going to hurt.

A lot.

## TL;DR -- A modest proposal

Since you're going to have to rewrite your module anyway, it's time for node to
specify and export the API we are going to "bless" for addons. That is, just
what API we are going to support and make sure continues to work from minor and
major releases, as well as a deprecation policy.

More specifically I think we should be exporting a separate (and not equal)
wrapper around (at the very least) javascript object creation, get/set, function
calling.

Additionally we should package and distribute (if possible in npm) a
transitional library/headers which module authors can target today which will
allow their module to compile and work from v0.8 through v1.0

## The Platform Problem

We currently allow platforms/distributors to build against shared (their own)
versions of many of our dependencies, including but not limited to:

 * v8
   - Holy crap, we're about as tightly coupled to the version of v8 we ship as
chromium itself is.
 * libuv
   - If we weren't strictly coupled to v8, we certainly are for libuv, there
would be no (useful) node, without libuv.
 * openssl
   - This is a must for linux distributions, who like to break DSA keys and then
make every dependency vulnerable as a result (sorry Debian, I keed I keed).
   - This actually allows distributors who know specific things about their
platform to enable/disable the features that allow it to run best.
 * zlib
   - Meh, this isn't such a big deal, it doesn't really change all that often.
 * http_parser
   - Really? People ship this as a separate library?

This functionality was added to appease platform builders, the likes of Debian,
Fedora, and even SmartOS. However, doing so has complicated and muddled the
scenario of building and linking binary addons.

Currently node-gyp downloads the sourceball, extracts the headers from it,
and makes some assumptions from `process.config` about how to build your addon.
In practice this has been working reasonably well.

However, I'm very concerned about this as a long term strategy. It's possible
for someone to have tweaked or twisted the node (or one of its dependencies)
builds, which could lead to some unintended consequences. In the "best" case,
you'll get a compiler error from a changed API or clashing symbol. In the worst
case they have modified the ABI which will manifest itself in unexpected and
often subtle ways.

Not to mention that we have no good answer on how to build and link addon
modules against the proper version of a shared dependency (what if the system
has multiple openssl's, what if they compiled against it in one place, but now
run against it in another).

And last but not least, how do modules consume symbols from our dependencies
that node itself doesn't consume. Consider a specific crypto routine from
openssl that you want to provide as an addon module because node doesn't
currently have an interface for it.

## Enemies without, and enemies within

As if it weren't bad enough that platforms may ship against a version of v8
that we haven't blessed, we (and addon developers) have to fight against the
beast that is the v8 API churn.

I don't really fault Google and the chromium or v8 team for how they are
handling this, more often then not we just end up with ugly compile time
deprecation warnings, letting us know the world is about to break.

However, there have been times -- like right now -- where node can't paper over
the drastic change in the v8 API for module developers. And as a result we
begrudgingly pass the API change to module authors.

To paraphrase, don't forget that execrement will inevitably lose its battle
with gravity.

So what are we going to do?

## Meat and Potatoes

This is where I don't particularly have everything fleshed out, and I'm sure I
will take a considerable amount of heat from people on API decisions that
haven't been made.

I want to export the following interfaces:

 * `node/js.h`
   - Object creation and manipulation.
   - Function calling and Error throwing.
 * `node/platform.h`
   - IO and event loop abstraction.
 * `node/ssl.h`
 * `node/zlib.h`
 * `node/http.h`

While I am not particularly attached to the names of these headers, each
represent an interface that I think module authors would opt to target. I only
feel strongly that we export `js` and `platform` as soon as possible as
they are the primary interactions for every module.

### Basic Principles

There are only a few principles:

 * Avoid (like the plague) any scenario where we expose an ABI to module authors.
   - Where possible use opaque handles and getter/setter functions.
 * The exported API should be a reliable interface which authors can depend on
working across releases.
 * While a dependency may change its API, we have committed to our external API
and need to provide a transitional interface in accordance with our deprecation
policy.
 * The API should never expose an implementation detail to module authors (A
spidermonkey backed node one day?).

### Platform

The `platform` interface is the easiest to discuss, but the pattern would
follow for `ssl`, `zlib`, and `http`.

This would just rexport the existing `uv` API, however with a C-style namespace
of `node_`. Any struct passing should be avoided, and libuv would need to be
updated to reflect that.

### JS

I expect the `js` interface to be the most contentious, and also fraught with
peril.

The interface for addon authors should be C, I don't want to forsake the C++
folk, but I think the binding for that should be based on our C interface.

I was going to describe my ideal interface, and frame it in context of my ruby
and python experience. However, after a brief investigation, the JSAPI for
spidermonkey exports almost exactly the API I had in mind. So read about that

Would it make sense, and would it be worth the effort, for node to export a
JSAPI compatible interface?

Would it make more sense to export a JSAPI influenced API currently targetted
at v8 which could be trivially extended to also support spidermonkey?

UPDATE 2013-07-08:

> It's interesting and worthy to have a conversation about being able to
> provide a backend neutral object model, though our current coupling to v8 and
> its usage in existing addons may not make it possible to entirely hide away
> the eccentricities of the v8 API. But what we can provide is an interface
> that is viable to target against from release to release regardless of how
> the public v8 API changes.

## Prior Art

A lot of these ideas came from a discussion I had with
[Joshua Clulow](http://blog.sysmgr.org/) while en route to
[NodeConf](http://nodeconf.com).

Part of that conversation was about [v8+](https://github.com/wesolows/v8plus)
which was written by a particularly talented coworker, who had a rather nasty
experience writing for the existing C++ API (such as it is).

There's some overlap in how it works and how I envisioned the new API. However,
I'm not sure I'm particularly fond of automatically converting objects into
nvlists, though that does solve some of the release and retain issues.

In general I would advocate opaque handles and getter and setter functions,
with a helper API which could do that wholesale conversion for you.

Really though this matters less in a world where addon authors are following
some defined "Best Practices".

 * Only pass and return "primitives" to/from the javascript/C boundary
   - Primitives would be things like: `String`, `Number`, `Buffer`.
 * Only perform objection manipulation in javascript where the JIT can work
its magic

## Dessert

Work on this needs to begin as soon as possible. We should be able to
distribute it in npm, and authors should be able to target it by including a
few headers in their source and adding a dependency stanza in their
`binding.gyp`, and by doing so their module will work from v0.8 through
v1.0

I mean, you're going to have to rewrite it anyway.

Mikeal Rogers

unread,
Jul 8, 2013, 2:53:11 PM7/8/13
to nod...@googlegroups.com, Bert Belder, Ben Noordhuis, Fedor Indutny, Trevor Norris, Scott Blomquist, Nathan Rajlich, Isaac Schlueter
+1 to all of this.

I think you implied it but maybe never explicitly declared it: does this mean node.js is going back to shipping with its deps and not allowing linking the way that some Linux distro's have demanded?

This is all the right direction, IMO. node.js should provide the best platform imaginable for people building on top of it. If some of those decisions benefit those building on node.js but piss off people that are trying to make it conform with a lower level platform's packaging system, fuck em!

Another question. If node.js becomes somewhat v8 neutral, and the spidermonkey guys put in enough work to proof-of-concept a working SM version, would the primary node.js distro even consider moving to it if it showed significant improvements over using v8? This is, of course, theoretical, but my main objection as well as others to the idea of moving to SM or even being neutral was one of compatibility which it sounds like you're about to solve.

-Mikeal

--
--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to
nodejs+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en
 
---
You received this message because you are subscribed to the Google Groups "nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodejs+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Stephen Belanger

unread,
Jul 8, 2013, 2:53:49 PM7/8/13
to nod...@googlegroups.com, Bert Belder, Ben Noordhuis, Fedor Indutny, Trevor Norris, Scott Blomquist, Nathan Rajlich, Isaac Schlueter
I feel like this is a good chance to just throw out backwards compatibility and start fresh. There are many decisions that were made in both C++ and Javascript sides that could have been done better, in retrospect. They stuck though because no one wanted to break compatibility with old versions of node. A good example is the recent changes to streams which, while they are an improvement, are basically a bad hack to twist a better API to sorta-kinda work with the bad API.
--

Dean Mao

unread,
Jul 8, 2013, 3:02:56 PM7/8/13
to nod...@googlegroups.com, Bert Belder, Ben Noordhuis, Fedor Indutny, Trevor Norris, Scott Blomquist, Nathan Rajlich, Isaac Schlueter
Is there a summary of ABI changes for us native module authors?

Trevor Norris

unread,
Jul 8, 2013, 3:08:47 PM7/8/13
to Dean Mao, nod...@googlegroups.com, Bert Belder, Ben Noordhuis, Fedor Indutny, Trevor Norris, Scott Blomquist, Nathan Rajlich, Isaac Schlueter

Mikeal Rogers

unread,
Jul 8, 2013, 3:10:11 PM7/8/13
to nod...@googlegroups.com, Bert Belder, Ben Noordhuis, Fedor Indutny, Trevor Norris, Scott Blomquist, Nathan Rajlich, Isaac Schlueter
As an ecosystem node.js has far less compiled addons than any of its contemporaries (Python, Ruby, etc) which is probably why most of us have heard way less about the pain of API changes in v8 for module authors as we have about streams2.

That said, compiled modules are still growing, and sizable communities now exist that are built on top of node-serial and leveldb which are both compiled modules. Since v8 is breaking stuff anyway it's not a big hit to the community to take this change for compiled addons, especially if it ensures better compatibility going forward.

Breaking compatibility in JavaScript can't happen. There's already more modules depending on that API than exist in the entire Python ecosystem (which was devastated by their 2 -> 3 API break) so it just isn't feasible to break API at that scale without destroying a lot of momentum in the ecosystem.

Almost a year ago we tried to change the name of a core module after a full release of deprecation warnings and couldn't do it because it broke too much of the ecosystem, I don't see how we could consider an even larger break a year later when the ecosystem has more than doubled.

-Mikeal

Fedor Indutny

unread,
Jul 8, 2013, 3:12:47 PM7/8/13
to Dean Mao, nod...@googlegroups.com, Bert Belder, Ben Noordhuis, Trevor Norris, Scott Blomquist, Nathan Rajlich, Isaac Schlueter
+1, but lets keep it small and simple.

Cheers,
Fedor.

Jérémy Lal

unread,
Jul 8, 2013, 4:17:09 PM7/8/13
to nod...@googlegroups.com, Mikeal Rogers
On 08/07/2013 20:53, Mikeal Rogers wrote:
> +1 to all of this.
>
> I think you implied it but maybe never explicitly declared it: does
> this mean node.js is going back to shipping with its deps and not
> allowing linking the way that some Linux distro's have demanded?
>
> This is all the right direction, IMO. node.js should provide the best
> platform imaginable for people building on top of it. If some of
> those decisions benefit those building on node.js but piss off people
> that are trying to make it conform with a lower level platform's
> packaging system, fuck em!

This is outrageously stupid to say that.

J�r�my.

Timothy J Fontaine

unread,
Jul 8, 2013, 4:18:51 PM7/8/13
to nod...@googlegroups.com, Bert Belder, Fedor Indutny, Trevor Norris, Scott Blomquist, Nathan Rajlich, Isaac Schlueter, Ben Noordhuis
On Mon, Jul 8, 2013 at 11:53 AM, Mikeal Rogers <mikeal...@gmail.com> wrote:
I think you implied it but maybe never explicitly declared it: does this mean node.js is going back to shipping with its deps and not allowing linking the way that some Linux distro's have demanded?

No, this just makes it easier to ensure that the version of the dependency you're consuming matches what the running node is using.
 
This is all the right direction, IMO. node.js should provide the best platform imaginable for people building on top of it. If some of those decisions benefit those building on node.js but piss off people that are trying to make it conform with a lower level platform's packaging system, fuck em!

I think this solution can satisfy both camps, however I would expect the priority to be on defining the JS interface.
 
Another question. If node.js becomes somewhat v8 neutral, and the spidermonkey guys put in enough work to proof-of-concept a working SM version, would the primary node.js distro even consider moving to it if it showed significant improvements over using v8? This is, of course, theoretical, but my main objection as well as others to the idea of moving to SM or even being neutral was one of compatibility which it sounds like you're about to solve.

Short answer, yes I can see scenarios where it would be to our benefit to be able to be backend neutral, either for performance, or as a means to bootstrap onto another platform. However that would be a long term goal, as it would require core modules to be written to consume the neutral API, which is non-trivial.

Presuming of course a neutral api can be agreed upon.

Mikeal Rogers

unread,
Jul 8, 2013, 4:22:43 PM7/8/13
to nod...@googlegroups.com, Bert Belder, Fedor Indutny, Trevor Norris, Scott Blomquist, Nathan Rajlich, Isaac Schlueter, Ben Noordhuis

On Jul 8, 2013, at 1:18PM, Timothy J Fontaine <tjfon...@gmail.com> wrote:

Presuming of course a neutral api can be agreed upon.

taking JSAPI is a good direction in that it's probably easier to agree on something that isn't being written from scratch. the decision can be binary: we either take this API or we don't.

Mark Volkmann

unread,
Jul 8, 2013, 7:29:36 PM7/8/13
to nod...@googlegroups.com, Mikeal Rogers
Yes, it is. Mikeal, it would serve you well to choose better words.

Luke Arduini

unread,
Jul 8, 2013, 8:28:31 PM7/8/13
to nod...@googlegroups.com


On Monday, July 8, 2013, Mikeal Rogerswrote:

+1 to all of this.

I think you implied it but maybe never explicitly declared it: does this mean node.js is going back to shipping with its deps and not allowing linking the way that some Linux distro's have demanded?

This is all the right direction, IMO. node.js should provide the best platform imaginable for people building on top of it. If some of those decisions benefit those building on node.js but piss off people that are trying to make it conform with a lower level platform's packaging system, fuck em!


+1 

Tim Caswell

unread,
Jul 8, 2013, 10:58:56 PM7/8/13
to nod...@googlegroups.com
I can vouch for a stable and documented C API.  The main thing that stopped me from re-implementing node on top of SpiderMonkey was the SM APIs changing constantly and the docs on the wiki being constantly out of date.  They were too new for the last stable release and too old for the latest mainline code.  (Also the build system for SM itself was a mess and very particular).

Contrast with my luvit project which I did largely on my own for the first few months and had a good chunk of node re-implemented.  Lua's C API is simple and consistent.  I didn't have to use C++ at all which made things much easier for me.  C is a fairly simple language, C++ is crazy complicated.

I'm a scripter.  I've been writing programs in scripting languages for over 20 years.  I've been know to write a node addon or two because I really like the extra power it gives (node-webgl was a fun 24 hour hack).

My only advice for the new common API is to keep it simple and avoid C++ classes.  I'm not encouraging the stack oriented nature of the Lua API, but being able to work in terms of objects, properties, functions, etc (the same terms you use in JS) would be great.  Treat everything as the simple data it is.

I can draft some APIs based on my experience with writing libuv bindings for various runtimes if you're interested.

-Tim Caswell


--

Tim Caswell

unread,
Jul 9, 2013, 2:58:28 AM7/9/13
to nod...@googlegroups.com
After a quick chat with TJ and Trevor on IRC, I've decided to write up a quick sample of the kind of API I would like as a JavaScript person.  The evolving code is at https://gist.github.com/creationix/5954513.

I'll inline some snippets here for conversation.

First is an ultra simple library that has an "addNumbers" function that adds two values as integers.

#include "js_api.h"
 
 
// Equal to the following JS snippet:
// function addNumbers(a, b) { a |= 0; b |= 0; return a + b; }
bool simple_add_numbers(js_context* C) {
int32_t a = js_to_int32(C, 1);
int32_t b = js_to_int32(C, 2);
return js_return(C, js_create_int32(C, a + b));
}
 
// The module exports an object with functions on it.
bool export_simple(js_context* C) {
int exports = js_create_object(C); // Create a new object and place it on the stack
js_set_function(C, exports, "addNumbers", simple_add_numbers);
return js_return(C, exports);
}
 
// If we wanted to export the add function directly, we could do
int export_simple(js_context* C) {
return js_return_function(C, simple_add_numbers);
}


A slightly more complicated example is one that creates a constructor with a prototype method.

#include "js_api.h"
 
// Sometimes you want to access the js "this" value in a function
 
// function Point(x, y) { this.x = x; this.y = y; }
bool point_constructor(js_context* C) {
js_set(C, 0, "x", 1); // this.x = arguments[0]
js_set(C, 0, "y", 2); // this.y = arguments[1]
return true; // js returns undefined, but C returns true meaning no error.
}
 
// Point.prototype.add = function () { var x = this.x | 0; var y = this.y | 0; return x + y; };
bool point_add(js_context* C) {
int32_t x = js_get_int32(C, 0, "x"); // var x = this.x
int32_t y = js_get_int32(C, 0, "y"); // var y = this.y
return js_return_int32(C, x + y); // return x + y
}
 
bool export_point(js_context* C) {
int Point = js_create_function(C, point_constructor);
// All functions in JavaScript already have a prototype object. Get it.
int proto = js_load(C, Point, "prototype");
// Add a function to the prototype object
js_set_function(C, proto, "add", point_add);
// Then return the constructor as the module exports
return js_return(C, Point);
}
I'm still working out sample APIs for things like passing a C struct to JS and back, wrapping external binary buffers, and binary data in general, but this should be enough to get a general idea of the kind of API I'm looking for.

The important thing is that it's as 1:1 with the JS it represents as possible, but in simple C.

Jeffery Olson

unread,
Jul 9, 2013, 3:17:09 AM7/9/13
to nod...@googlegroups.com
And you said you didn't care for JSAPI! :P

Floby

unread,
Jul 9, 2013, 5:16:16 AM7/9/13
to nod...@googlegroups.com, Bert Belder, Ben Noordhuis, Fedor Indutny, Trevor Norris, Scott Blomquist, Nathan Rajlich, Isaac Schlueter
Tim's examples are pretty nice.
The only things missing for all my use cases are storing pointers in JS objects so I can get them back when I need it.

something like

js_set_pointer(C, myObject, pointer);
myType *pointer = js_get_pointer(C, myObject);

Louis Santillan

unread,
Jul 9, 2013, 12:22:40 PM7/9/13
to nod...@googlegroups.com
I said it years ago, v8 needs something like SpiderMonkey's cookbook <https://developer.mozilla.org/en-US/docs/SpiderMonkey/JSAPI_Cookbook>.But you can bug Stephen Beal about this, because Chromium & the v8 teams' priority is to support chrome, not embedders.  Stephen has had a few run ins with v8 & chromium devs and continue to ignore him.


-L
--

Tim Caswell

unread,
Jul 9, 2013, 1:08:49 PM7/9/13
to nod...@googlegroups.com
On Tue, Jul 9, 2013 at 4:16 AM, Floby <floren...@gmail.com> wrote:
Tim's examples are pretty nice.
The only things missing for all my use cases are storing pointers in JS objects so I can get them back when I need it.

something like

js_set_pointer(C, myObject, pointer);
myType *pointer = js_get_pointer(C, myObject);

Ok, now the proposed API has a more complete feature set including an example of how to embed C structs inside JS objects. https://gist.github.com/creationix/5954513#file-point-c (Also I ran all the code through gcc and clang with -pendantic to make sure my header is valid code)

#include "js_api.h"
#include <stdlib.h> // malloc, free
 
// Suppose I want to create a point type that's backed by a real C struct for points.
 
typedef struct {
double x;
double y;
} my_point;
 
void cleanup_point(void* point, const char* type) {
free(point);
}
 
bool create_point(js_context* C) {
my_point* point = (my_point*)malloc(sizeof(my_point));
point->x = js_to_double(C, 1);
point->y = js_to_double(C, 2);
int proto = js_named_read_ref(C, "my_point_proto");
return js_return_external_pointer_with_proto(C, point, "my_point", proto, cleanup_point);
}
 
bool add_method(js_context* C) {
my_point* point = (my_point*)js_to_pointer(C, 0, "my_point");
if (!point) {
return js_throw_type_error(C, "Expected this to be 'my_point' instance");
}
return js_return_double(C, point->x * point->y);
}
 
bool export_point(js_context* C) {
int proto = js_create_object(C);
js_set_function(C, proto, "add", add_method);
js_named_ref(C, proto, "my_point_proto");
return js_return_function(C, create_point);
}




 

--

Elijah Insua

unread,
Jul 9, 2013, 1:24:17 PM7/9/13
to nod...@googlegroups.com
Can you explain what the magic "slot" numbers are used for?

Tim Caswell

unread,
Jul 9, 2013, 1:34:54 PM7/9/13
to nod...@googlegroups.com
On Tue, Jul 9, 2013 at 12:24 PM, Elijah Insua <tmp...@gmail.com> wrote:
Can you explain what the magic "slot" numbers are used for?

They are basically local handles.  Each JS value that's created during the scope of a function gets a unique slot value (some negative integer).  If you want a value to live past the current function, you need to either js_ref it or js_return/js_throw it or store it as a property on some object that's going to outlive the current function.  The shim wrapping this code can loop through all the created slots and manage memory accordingly.

Also slot of "0" references "this" and positive slots are passed in arguments.

Micheil Smith

unread,
Jul 9, 2013, 1:39:00 PM7/9/13
to nod...@googlegroups.com
How would a asynchronous libuv schedule example look?

– Micheil

Tim Caswell

unread,
Jul 9, 2013, 2:04:34 PM7/9/13
to nod...@googlegroups.com
On Tue, Jul 9, 2013 at 12:39 PM, Micheil Smith <mic...@brandedcode.com> wrote:
How would a asynchronous libuv schedule example look?

Not entirely sure what you mean.  I did add support for external pointers (for things like uv_handle_t and uv_ref_t) and a way to add arbitrary js values to the set of GC roots (for storage in some C struct passed to the C callback later on).  That should be enough to wrap the various libuv apis.

Marcel Laverdet

unread,
Jul 9, 2013, 2:20:21 PM7/9/13
to nodejs
I don't understand why you hate C++ so much? If you're trying to convert v8's C++ API into a C API you're going to need a lot of ref/unref action which isn't necessary given C++'s scoping semantics. They built the API in C++ for a reason. I'm not even sure the API you're conjuring up here is even possible without adding in non-trivial CPU & memory overhead?

Tim Caswell

unread,
Jul 9, 2013, 2:41:56 PM7/9/13
to nod...@googlegroups.com
On Tue, Jul 9, 2013 at 1:20 PM, Marcel Laverdet <mar...@laverdet.com> wrote:
I don't understand why you hate C++ so much? If you're trying to convert v8's C++ API into a C API you're going to need a lot of ref/unref action which isn't necessary given C++'s scoping semantics. They built the API in C++ for a reason. I'm not even sure the API you're conjuring up here is even possible without adding in non-trivial CPU & memory overhead?

Well, if it's not possible, then it's not possible.  But I have used a similar API in other VMs and it works there.

My contribution here is to give my perspective as a C/C++ non-expert.  Most node.js developers are JS developers.  Most (not all obviously) of the binary node addons I know of were written by people with little C background.

Timothy J Fontaine

unread,
Jul 9, 2013, 2:44:41 PM7/9/13
to nod...@googlegroups.com
I don't hate C++, on the other hand there are particular warts that it brings to the table that aren't desirable, for instance ABI compatibility is sketchy at best.

This is not about trying to do the v8 API in C, this is about providing an API that modules can depend on that we guarantee to work from release to release. Something we've punted on from the get go, and it hasn't been too big of an issue because the v8 churn hasn't been too great, however the 3.20 changes are basically forcing our hand.

The API Tim has been describing is interesting, it's a mix between what you expect from JSAPI (or Python, Ruby embedding) along with a sprinkling of Lua embedding concepts. I asked him to sketch an idea of the API he would like to use, it's a useful conversation, it's by no means set in stone.

It is certainly not the intention to add overhead as part of this API, but the goal at the moment is to define what the API looks like, which is increasingly feeling like exporting a JSAPI interface with some extras. And the model that Tim likes could be implemented on that as well quite trivially.

Stephen Belanger

unread,
Jul 9, 2013, 3:26:07 PM7/9/13
to nod...@googlegroups.com
The named_ref thing to get at the prototype within the constructor seems kind of awkward to me. Something like js_this() makes more sense to me.

The js_return_external_pointer_with_proto structure also seems awkward. Can we not do something like js_set_context(C, "point", point); and js_get_context(…) to get the struct pointer? To me, it makes more sense to think of the struct to interact with as metadata, rather than trying to hack it into looking like the return value. Adding it to the return seems like it's trying to hack a non-js data structure to pretend it's a js object. I'm thinking something a little more like this; https://gist.github.com/Qard/5960388#file-point-c-L16

Tim Caswell

unread,
Jul 9, 2013, 4:24:43 PM7/9/13
to nod...@googlegroups.com
On Tue, Jul 9, 2013 at 2:26 PM, Stephen Belanger <cyruz...@gmail.com> wrote:
The named_ref thing to get at the prototype within the constructor seems kind of awkward to me. Something like js_this() makes more sense to me.

The named ref is so that you have some local storage to create the prototype when setting in the module and get a reference to it later when creating the object.
 
The js_return_external_pointer_with_proto structure also seems awkward. Can we not do something like js_set_context(C, "point", point); and js_get_context(…) to get the struct pointer? To me, it makes more sense to think of the struct to interact with as metadata, rather than trying to hack it into looking like the return value. Adding it to the return seems like it's trying to hack a non-js data structure to pretend it's a js object. I'm thinking something a little more like this; https://gist.github.com/Qard/5960388#file-point-c-L16

I'm fine with simply having `js_set_proto(C, obj, proto)` and `js_get_proto(C, obj)` and make object __proto__ mutable.  (Then I wouldn't need all the *_with_proto variants).  But I was under the impression that that's a bad idea for JS engines.

As far treating structs as named metadata on objects, I'm also fine with that (again it would reduce the number of combinations I have), but I'm not sure it's possible efficiently in the various JS VMs.

Thanks for the feedback.  Remember this is a rough draft to convey my ideas, not a set-in-stone spec.

Tim Caswell

unread,
Jul 9, 2013, 4:30:16 PM7/9/13
to nod...@googlegroups.com
Actually, thinking about it.  I don't like the named refs.  It would be more elegant to use closures the same as you would do in JavaScript.

Here is a new version using closures instead of named refs:

bool create_point(js_context* C) {
my_point* point = (my_point*)malloc(sizeof(my_point));
point->x = js_to_double(C, 1);
point->y = js_to_double(C, 2);
int proto = js_closure_get(C, "proto");
return js_return_external_pointer_with_proto(C, point, "my_point", proto, cleanup_point);
}
 
bool export_point(js_context* C) {
int proto = js_create_object(C);
js_set_function(C, proto, "add", add_method);
int scope = js_create_object(C);
js_set(C, scope, "proto", proto);
return js_return_closure(C, create_point, scope);
}

Stephen Belanger

unread,
Jul 9, 2013, 4:31:03 PM7/9/13
to nod...@googlegroups.com
That sounds better to me. I'm just thinking it's more C-like for function to do one specific thing. The js_return_externel_pointer_with_proto seems more like; add proto to object, add struct reference to object, return object.

Marcel Laverdet

unread,
Jul 9, 2013, 8:30:38 PM7/9/13
to nodejs
But if you're unfamiliar with C/C++ then "myType *pointer = js_get_pointer(C, myObject);" is no more familiar than "Local<Value> my_value = arguments[2]->This();" or whatever. You don't necessarily have to understand what's happening with the <>'s and ->'s to get utility from them.

Specifically C++ brings the possibility of much better memory management. If you do "Local<Value> my_thing" C++ can determine automatically when you're done with it and throw it away. With C you have to specifically say you're done, and if you don't say you're done then there's a silent leak. Basically I feel that in C it's much easier to do something very wrong and not know it. In C++ many times if you do something wrong it just won't compile, which is more frustrating right now but way less frustrating when your code hits production.

Jorge Chamorro

unread,
Jul 9, 2013, 10:49:58 PM7/9/13
to nod...@googlegroups.com
+1
--
( Jorge )();

Floby

unread,
Jul 10, 2013, 3:57:09 AM7/10/13
to nod...@googlegroups.com
As far as I understand what is proposed with the C API. Is that embedder will never directly use v8 or anything else handlers for JS values. As you see in Tim's proposal, JS values are merely ints (which I thing should be typedef'd to something else) which means that the functions returning those handles will probably create a local handle attached to the local scope. This scope is managed by the glue between the C API and the JS VM. As I see it, C embedders would not "own" any of the references they are passed and therefore needn't manage them.

Also, I think, despite C being an old compiled language, it has concepts closer to JS than C++ does. In JS we need to :
  • get/set values to hashes
  • some of these values are functions
  • functions called in a certain way expose a  `this` variable
I think these 3 things are more easily done in C than C++. Or at least with a better 1 to 1 equivalence. C functions that should be used as JS functions only need to accept a Context as arguments (which would be equivalent to `arguments` in JS modulo `this`) and when dealt with as values their should be passed around as function pointers.
For me it's simple enough to justify its use. The only sketchy thing is how to manage C pointers stored in JS objects, I know how I would do it for v8, but I never played around with SM so I wouldn't know.

Louis Santillan

unread,
Jul 10, 2013, 10:08:14 AM7/10/13
to nod...@googlegroups.com
There was the v8 abstraction layer, k7 [1].


-L

Tim Caswell

unread,
Jul 10, 2013, 1:53:31 PM7/10/13
to nod...@googlegroups.com
On Tue, Jul 9, 2013 at 7:30 PM, Marcel Laverdet <mar...@laverdet.com> wrote:
But if you're unfamiliar with C/C++ then "myType *pointer = js_get_pointer(C, myObject);" is no more familiar than "Local<Value> my_value = arguments[2]->This();" or whatever. You don't necessarily have to understand what's happening with the <>'s and ->'s to get utility from them.

It's not that a C or C++ is impossible to learn, it's that a C is a *much* simpler language to learn.  There is one and only one pointer type, there are no templates, no references, no magic destructors, no classes, no virtual functions, etc.  It's all very direct and plain.

If you're going to write a binary addon for node and haven't yet learned C or C++, it's going to be a much shorter learning curve to learn just C than learn C++.

Yes these features in C++ are there for a reason and can make for prettier APIs *once* you're comfortable with C++.  My argument is that many people aren't comfortable with C++.  I know a little and have written a node addon or two using V8's API, but I'm not comfortable with the language.  With luvit, on the other hand, I only have to use C to write bindings and I'm much more productive and happy, even though the APIs are sometimes more verbose.

 

Specifically C++ brings the possibility of much better memory management. If you do "Local<Value> my_thing" C++ can determine automatically when you're done with it and throw it away. With C you have to specifically say you're done, and if you don't say you're done then there's a silent leak. Basically I feel that in C it's much easier to do something very wrong and not know it. In C++ many times if you do something wrong it just won't compile, which is more frustrating right now but way less frustrating when your code hits production.

Don't forget that the shim layer between bindings and V8 can handle this for you.  Nowhere in my API do you have to manually say you're done with a javascript object.  There are hooks the other direction so that the engine can tell you when it's done with an object in case you've embedded some memory you manage in it, but that's simple logic.

My goal is to expose a very JS-like semantic to bindings authors since they are interfacing with JS.
 
-Tim Caswell

Stephen Belanger

unread,
Jul 10, 2013, 2:15:35 PM7/10/13
to nod...@googlegroups.com
C++ also becomes pretty awkward when trying to interact with the API of a VM that expects to do things in a very specific way. You can't really use the inheritance mechanics on V8 types without making things even more awkward, so the class-based advantage of C++ deteriorates rather quickly. I feel like the design of the C API is much more explicit and descriptive of what it is doing at each step.
--

Michael Schoonmaker

unread,
Jul 10, 2013, 3:32:13 PM7/10/13
to nod...@googlegroups.com
Another point to consider is that libuv is itself written in C. Cognitively-speaking, the more similar we can make the two in terms of concept and feel, the better.
Reply all
Reply to author
Forward
0 new messages