-
Notifications
You must be signed in to change notification settings - Fork 424
Conversation
src/serializer/serializer.js
Outdated
); | ||
} | ||
|
||
// Next call the bootstrap function with the process intrinsic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@NTillmann This is the Call I was referring to. I want to do the equivalent of realm.$GlobalEnv.execute
but call a function value that was the completion value of the previous execution. This is a very common way to initialize internals in JS engines/browsers in a way that doesn't leak those internals onto the global object (unlike React Native).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems reasonable to me. I think you should break it out into a separate function and co-located it with execute. Likewise, you may want to break out lines 176-197 and co-locate it with execute in environment.js.
src/intrinsics/node/process.js
Outdated
case 'function': | ||
throw new Error('Functions could be supported but are not yet.'); | ||
case 'object': { | ||
if (value === 'null') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can this be? Do you mean value === null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops. yea.
src/serializer/serializer.js
Outdated
[realm.intrinsics.process] | ||
); | ||
} catch (err) { | ||
if (err instanceof JoinedAbruptCompletions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This exception catching for ...Completions seems dubious, but I can see how to copy&pasted it from evaluateCompletion. Let me check with Herman what should happen here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, here, you can collapse all of those ...Completions cases into a single "if (err instanceof Completion) res = err; else ...". This will log the issue in a reasonable way, and there is no way to recover at this point anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like call of this belongs in a helper somewhere but I'm not sure where.
src/intrinsics/node/process.js
Outdated
import { ThrowCompletion } from "../../completions.js"; | ||
|
||
function throwError(realm, name) { | ||
throw new ThrowCompletion( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have realm.createErrorThrowCompletion to make this easier.
src/intrinsics/index.js
Outdated
} | ||
|
||
// node | ||
if (realm.compatibility === 'node' || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You filed in assume to make the "jsc" compatibility more precise by adding a version identifier. #395 Maybe. But then you probably also want to make "node"/"node-cli" versioned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't quite understand what "node" is. Looks like it is currently just for testing source maps. It probably should have some general "CommonJS" mode where you can't assume a particular version and many of the variables are abstract.
"node-cli" is interesting because I think we can make the versioning very automatic based on what node version you're running but then the environment is locked to that version of node. So it doesn't quite make sense to specify it.
src/global.js
Outdated
@@ -415,5 +414,18 @@ export default function (realm: Realm): ObjectValue { | |||
}); | |||
} | |||
|
|||
if (realm.compatibility === "browser") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it safe to hide this for the React Native builds or does it need it for some feature detection thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, let's just keep it under this compatibility flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite sure I understand what you are trying to do here. What exactly does the bootstrap function do? Where does it get is input from? How do you designate input as unknown?
src/intrinsics/node/process.js
Outdated
); | ||
} | ||
|
||
// Takes a value from the host realm and clones it into a Prepack Realm. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm none too sure about this comment. It seems to me that you are taking JS value and making a corresponding Prepack value.
src/serializer/serializer.js
Outdated
); | ||
} | ||
|
||
// Next call the bootstrap function with the process intrinsic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems reasonable to me. I think you should break it out into a separate function and co-located it with execute. Likewise, you may want to break out lines 176-197 and co-locate it with execute in environment.js.
@hermanventer The bootstrap function will get its input from The first argument is normally the JavaScript module that is going to be executed. I was thinking that will be set up as a constant string value equal to the file about to be Prepacked. This will cause the Node environment to load that module file into memory via a call to the The abstract input is the rest of the Since the Node.js uses dynamic module source code loading as the program executes, it needs this different mechanism to be compatible with the ecosystem. Unlike browser/react native where bundles are created by statically analyzing dependency graphs. |
Seeing what |
The most I can figure out quickly from all this is that you might be able to prepack the bootstrap function. I'm wondering just how much value this would unlock. What am I missing? |
@hermanventer That is correct. I still need to implement contextify and fs module. The next follow ups on the |
e4303b3
to
1aa70c8
Compare
a453fbe
to
21b632d
Compare
'transform-es2015-parameters', | ||
], | ||
retainLines: true, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this depends on a "devDependency" atm as a hack. I didn't want to add it as a hard dependency since this is still experimental and we'll just implement these directly eventually.
The purpose of this is to be able to land facebookarchive#397 with some test coverage. In its current form it is too specific in its implementation and only works on a specific version of Node. In the future we'll want to make that more abstract and general so that it can work on ranges and other major versions.
The purpose of this is to be able to land facebookarchive#397 with some test coverage. In its current form it is too specific in its implementation and only works on a specific version of Node. In the future we'll want to make that more abstract and general so that it can work on ranges and other major versions.
The purpose of this is to be able to land #397 with some test coverage. In its current form it is too specific in its implementation and only works on a specific version of Node. In the future we'll want to make that more abstract and general so that it can work on ranges and other major versions.
9634a7e
to
ef7448f
Compare
I added a unit test that is only enabled in CI for now (not |
I added an additional test case that tests the path where ArrayBuffer #598 is needed. CI should now fail until that's fixed. |
f0dcbe6
to
c3c3261
Compare
After rebasing on top of the ArrayBuffer fix I think that this should now be in a landable state. After land, I'll follow up with Issues for a few ideas for future improvements. |
@@ -0,0 +1,17 @@ | |||
# Prepack test input | |||
node ./bin/prepack.js ./test/node-cli/Simple.js --out Simple-test.js --compatibility node-cli |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does prepack.js get into ./bin?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's checked into the repo already. https://github.com/facebook/prepack/tree/master/bin
It's an alias for lib/prepack-cli.js
which gets there from the build.
src/intrinsics/node/buffer.js
Outdated
|
||
declare var process: any; | ||
|
||
function getBufferFromArg(realm: Realm, value: Value): Uint8Array { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just type value as an ObjectValue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And add the invariant at each callsite instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is actually preferable because it makes it easier for the reader to verify that the invariant is true.
src/intrinsics/node/buffer.js
Outdated
declare var process: any; | ||
|
||
function getBufferFromArg(realm: Realm, value: Value): Uint8Array { | ||
invariant(value instanceof ObjectValue && value.$ViewedArrayBuffer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This invariant is redundant.
src/intrinsics/node/buffer.js
Outdated
|
||
declare var process: any; | ||
|
||
function getBufferFromArg(realm: Realm, value: Value): Uint8Array { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should probably use DataBlock instead of Uint8Array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is supposed to return a native Uint8Array
in the Node environment. Not the Prepack value. It's what I pass to nativeFS.read
etc. If the internal representation of DataBlock
changes, I want that to be an error so that we can do a conversion from that format to Uint8Array
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough, but it makes me wonder why we have DataBlock type in the first place.
src/intrinsics/node/buffer.js
Outdated
|
||
export default function (realm: Realm): ObjectValue { | ||
let nativeBuffer = process.binding('buffer'); | ||
let nativeBufferPrototype = (require('buffer'): any).Buffer.prototype; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what this is doing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm. This is defined in native C code. This is related to the setupBufferJS
thing below. The runtime we're currently running in has already called setupBufferJS
, read from a mutated object and that's how it gets a handle on this object.
https://github.com/nodejs/node/blob/v7.9.0/src/node_buffer.cc#L1197-L1230
We can't do the same thing at this point. Luckily this same object is reachable another way through another module which I found here.
src/intrinsics/node/buffer.js
Outdated
let utf8Slice = new NativeFunctionValue(realm, "Buffer.prototype.utf8Slice", "utf8Slice", 0, (context, args) => { | ||
let self = getBufferFromArg(realm, context); | ||
let decodedArgs = args.map((arg, i) => { | ||
return ToInteger(realm, arg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a good place to follow the arrow with an expression rather than a block with a return.
src/intrinsics/node/buffer.js
Outdated
let decodedArgs = args.map((arg, i) => { | ||
return ToInteger(realm, arg); | ||
}); | ||
let utf8String = nativeBufferPrototype.utf8Slice.apply(self, decodedArgs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit surprised by this. The utf8Slice I can find does not expect a this parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's defined here: https://github.com/nodejs/node/blob/v7.9.0/src/node_buffer.cc#L1209
src/intrinsics/node/buffer.js
Outdated
let self = getBufferFromArg(realm, context); | ||
let decodedArgs = args.map((arg, i) => { | ||
if (i === 0) { | ||
return getBufferFromArg(realm, arg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems inconsistent with utf8Slice and I can't think of a good reason why it should be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utf8Slice
creates a string from a Buffer. No side-effects. The rest of the arguments are all integers if they're defined.
copy
copies bytes from this
Buffer by mutating a target Buffer
. The rest of the arguments are integers if defined. The return value is an integer.
https://github.com/nodejs/node/blob/v7.9.0/src/node_buffer.cc#L524-L557
return ToInteger(realm, arg); | ||
} | ||
}); | ||
let bytesCopied = nativeBufferPrototype.copy.apply(self, decodedArgs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you unpacked args with a pattern, you might get away with calling copy directly.
src/intrinsics/node/buffer.js
Outdated
throw new Error("TODO"); | ||
}); | ||
|
||
Set(realm, obj, "setupBufferJS", setupBufferJS, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be more readable if moved to just after the definition of setupBufferJS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only really skimmed over this since it is quite large and somewhat strange to me. It seems self contained enough that I'm not overly concerned that it will break things, so I'm going to let you go ahead with this.
src/intrinsics/node/fs.js
Outdated
|
||
declare var process: any; | ||
|
||
function getBufferFromArg(realm: Realm, value: Value): Uint8Array { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a duplicate of the one in buffer.js.
src/intrinsics/node/fs.js
Outdated
return realm.intrinsics.undefined; | ||
}); | ||
obj.defineNativeMethod("internalModuleStat", 0, (context, args) => { | ||
const fileName = ToString(realm, args[0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather use a pattern with more descriptive names.
src/intrinsics/node/fs.js
Outdated
enumerable: true, | ||
}); | ||
|
||
// TODO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment can be a bit more descriptive. When your future self comes back it he might appreciate a hint as to what your present self had in mind.
src/intrinsics/node/process.js
Outdated
declare var process: any; | ||
|
||
// Takes a value from the host realm and create it into a Prepack Realm. | ||
// This seems like it could be a useful primitive elsewhere. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps move this to Realm.js.
src/intrinsics/node/process.js
Outdated
enumerable: true, | ||
configurable: true | ||
}); | ||
// TODO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what?
src/intrinsics/node/process.js
Outdated
return realm.intrinsics.undefined; | ||
}); | ||
TTYPrototype.defineNativeMethod("writeUtf8String", 0, (context, args) => { | ||
// let req = args[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
src/intrinsics/node/process.js
Outdated
let fd = ToInteger(realm, args[0]); | ||
return new StringValue(realm, nativeTTYWrap.guessHandleType(fd)); | ||
// TODO: Make this abstract so that changing the pipe at runtime is | ||
// possible. Currently this cause an introspection error. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
causes
src/intrinsics/node/process.js
Outdated
); | ||
return realm.createAbstract(types, values, [], buildNode, undefined, `(process.binding('tty_wrap').isTTY(${fd}))`); | ||
}); | ||
// TODO |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
} | ||
|
||
export function prepackNodeCLISync(filename: string, options: Options = defaultOptions) { | ||
if (process.version !== "v7.9.0") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems rather specific. Wouldn't later versions work as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not at the moment. The strategy is very fragile because I only model the minimal needed. Which is why this whole thing is so hacky. The ideal would be to have more of an automatic pass-through or making more things abstract.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, I'm modeling private APIs here. They do change between minor versions. The rationale for picking this strategy over modeling public APIs is outlined in the PR comment.
Expose `process` on intrinsics. We won't expose it on the global through the normal route. Instead, we'll let Node's on script set it up for us.
This lets us run from within a module scope for convenience.
This is just a sufficient amount of hacks to get the base line running. Needs more implementations and more of this needs to be automated.
I enable this mode only on CI for now. Not to `yarn test`. Since it is locked to a particular version of Node so we don't want to force people to run it locally.
The sync API doesn't accept a callback.
I've noticed this may still throw errors when attempting to load built-in modules like "zlib". |
@jdalton I think it'll be expected that many things won't work out of the box until the ecosystem shifts towards supporting "heap snapshot". E.g. Any hanging open socket at the end won't work 100%. We'll have to keep chasing the minimal set needed. Ideally Prepack can help minimize the things that can and can't be. I'll take a look at why zlib doesn't work. Keep em coming. :) |
@sebmarkbage Cool, thanks! |
This adds a compatibility option for
node-cli
. The goal of this is to Prepack a whole Node.js CLI environment including Node's module system. This is useful for an end application but not so useful for a small library yet.This takes a bit different approach than compiling a single bundle. Instead of running the target bundle in the Prepack realm, it actually runs Node.js's JavaScript code inside the Prepack realm. This code is embedded within the runtime and we can get to it through
process.binding('natives')
when we run Prepack.The script file will be passed as a
process.argv
.Node's CLI code will then pick that up and start loading modules from the file system. The idea is that reads from
fs
while Prepacking will be considered constant. That way all the relevant modules will be automatically picked up and executed in the Prepack realm.Finally, we can snap shot the entire Node system.
This PR takes the approach of building intrinsics only for the native code in Node.js (which are all on the
process
object). An alternative solution would be to build intrinsics from the Node.js public APIs. The tradeoff is that the public API is much bigger surface area but moves slower, where as the native APIs are considered more private and therefore moves faster but have smaller surface area.My guess is that it will be pretty easy to keep up with the native APIs but not the JS APIs so I picked that solution. It guarantees that the JS parts are 100% compatible with the Node version you're running.
This is still a WIP. I have more intrinsics to implement before this works.