Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add d8 for disassembling javascript #4721

Merged

Conversation

gautam1168
Copy link
Contributor

@gautam1168 gautam1168 commented Feb 11, 2023

For this issue:

#264

@gautam1168
Copy link
Contributor Author

gautam1168 commented Feb 13, 2023

@mattgodbolt Can I get some pointers on how to get a d8 binary onto the server? I can see that the python config in etc/config/python.amazon.properties uses /opt/compiler-explorer directory for finding the python binaries. I don't know how they get there though. How can I go about adding the d8 executable in maybe /opt/compiler-explorer/v8 directory?

Also, I have just built v8 from its main branch. But I could build from one of the release branches that have semver numbers. However, I don't think there is any place where once can just download a built d8 binary from. I couldn't find such a place by googling around. And this reply from a v8 developer on a stack overflow post says the same: https://stackoverflow.com/questions/70831516/v8-javascript-for-c-precompiled-binaries

So, how do I go about putting the binary on the server?

@partouf
Copy link
Contributor

partouf commented Feb 14, 2023

@mattgodbolt Can I get some pointers on how to get a d8 binary onto the server? I can see that the python config in etc/config/python.amazon.properties uses /opt/compiler-explorer directory for finding the python binaries. I don't know how they get there though. How can I go about adding the d8 executable in maybe /opt/compiler-explorer/v8 directory?

Also, I have just built v8 from its main branch. But I could build from one of the release branches that have semver numbers. However, I don't think there is any place where once can just download a built d8 binary from. I couldn't find such a place by googling around. And this reply from a v8 developer on a stack overflow post says the same: https://stackoverflow.com/questions/70831516/v8-javascript-for-c-precompiled-binaries

So, how do I go about putting the binary on the server?

If there's no prebuilt's available, we usually create our own builder. (https://github.com/compiler-explorer?q=-builder&type=all&language=&sort=) using Docker and some scripts.

We usually plop non-massive compiler builds here https://github.com/compiler-explorer/misc-builder/tree/main/build - but that repo is a tiny bit polluted at the moment. But if you can make a build script in the style of those, that would be good.

After that, we can invoke that builder and the tarxz will be placed on our S3 ready to download with a tiny configuration change to https://github.com/compiler-explorer/infra

The build script is the important part though.

@mattgodbolt
Copy link
Member

@gautam1168 thanks for this! @partouf has pointed you in the right direction: how would you best like to do this? If you have a simple bash script that can build d8 we can probably help you get it into our infrastrucutre. If you have experience with docker then making something akin to one of the other containers would be useful too.

@gautam1168
Copy link
Contributor Author

I am familiar with docker and I can prepare a script for the same. However, I may have to wait till friday before I can commit serious time on this. I also saw a suggestion in the main issue from @jakobkummerow that nodejs can be used instead of a d8 binary. However, with that approach I think it would not be possible to use the --allow-native-syntax flag to cause optimization of functions early as he showed. I will both try out the syntax he showed in the issue and get the docker script ready in this weekend and update my PRs

@mattgodbolt
Copy link
Member

Fantastic: Thanks @gautam1168. Our build.sh scripts that we run in our docker worlds have a number of useful features, so ideally any build stuff you do should fit in with that if possible (or else we'll adapt them). Thanks! (e.g. https://github.com/compiler-explorer/gcc-builder/blob/main/build/build.sh is a decent template to start with if that makes sense for you). Happy to steer more too if it's helpful

@gautam1168
Copy link
Contributor Author

gautam1168 commented Feb 18, 2023

@mattgodbolt @partouf I actually just found a DockerFile that builds v8. So my work would be pretty easy if I just wanted to build it like the example above. I could just fork and modify it. However as @jakobkummerow pointed out later, I don't actually have to build d8 and instead I can just use nodejs. This is the dockerfile: (https://github.com/gengjiawen/v8-build/blob/master/Dockerfile)

Doing this will also make sure that updating to latest stable release of v8 is as simple as adding another version of nodejs. And its just a download, so no build resources will be needed. At least on my machine it takes a long time to get the v8 build done and it tanks my i7 machine when its building. So I think I'll actually just prepare it to use nodejs. This will be just like python. The server's nodejs doesn't need to be touched, there will just be other nodejs binaries that can be used instead of d8 directly.

If you think we should do a full build instead I can make a d8-builder that is similar to the gcc-builder. I will wait for your reply before I do that though. I think nodejs is definitely the easier and more maintainable path to take here.

@gautam1168
Copy link
Contributor Author

I checked the repo that builds python and I see that you guys are building python from source as well. To stay in line with that Ill have to build nodejs from source, so it would be better to build d8. Also, with d8 I will also get the tools necessary to visualize profile data. So I setup a docker repo to build d8. Its building right now in the container. I will update once I have it working just like the other builders. Im not sure how Ill get the repo added to compiler-explorer. But first Ill finish doing the build inside the container.

@gautam1168
Copy link
Contributor Author

gautam1168 commented Feb 22, 2023

ok I have a repository ready that makes a docker image similar to the other builder repositories in compiler-explorer that builds d8 . I will just do a few more changes like adding the README and stuff and then ask for help to move the repo to compiler-explorer organization.

image

This terminal is in my docker container. So, I can generate the builds as tar.xz files just like the other builders. Here is my repo so far: https://github.com/gautam1168/build-d8

@mattgodbolt
Copy link
Member

Thanks @gautam1168 ! I'll fork your repo into our org and make any changes needed!

@mattgodbolt
Copy link
Member

On second thoughts I'll use your repo as inspiration: the dockerfile won't work for us as it stands but is a very useful starting point! thank you :)

@mattgodbolt
Copy link
Member

So I did a bunch of work to try and make a reproducible build but it seems the tools really don't like it :D I'm going to take a look another day, I think.

@mattgodbolt
Copy link
Member

Oh tell a lie! Your repo is great, I had misread it quite badly...I'll see if I can get this sorted this weekend

@mattgodbolt
Copy link
Member

building now! https://github.com/compiler-explorer/d8-builder/actions/runs/4394733377/jobs/7695947983 - forked from your repo (thanks!) and then tweaked. Your thoughts are welcome: I tried to remove things I found unnecessary but I might be deluding myself.

@mattgodbolt
Copy link
Member

A trunk build: https://github.com/compiler-explorer/infra/actions/runs/4394838600 - we will need to do this daily for "trunk" with a separate PR. Also - what named versions should I build?

@gautam1168
Copy link
Contributor Author

gautam1168 commented Mar 12, 2023 via email

@mattgodbolt
Copy link
Member

@gautam1168 no worries at all! Nothing here is ever time-critical :) I'm just trying to make sure I'm not blocking anything...

@mattgodbolt
Copy link
Member

And good luck in whatever you're doing next in your job!

@gautam1168
Copy link
Contributor Author

@mattgodbolt I have made a PR to the d8-builder repository you setup. Mainly to answer the question about which version to build. Please look at my comment there. There are actually two possible stable versions to choose from at any given time and there are two branches for these versions. I am not sure which one ce should go with. Although I would choose the chromium/number version rather than branch-heads/x.y version, since its the one inside browsers.

@jakobkummerow
Copy link

what named versions should I build?

Probably it's best to follow Chrome stable releases, e.g.:
Chrome 111 -> V8 branch-heads/11.1 (current Stable)
Chrome 112 -> V8 branch-heads/11.2 (current Beta, will go to Stable in early April)
Chrome 113 -> V8 branch doesn't exist yet but will be called branch-heads/11.3

New releases usually happen every four weeks, you can see the calendar here.

The pattern "V8 branch name == Chrome version divided by 10" has been consistently maintained since Chrome 41 IIRC and there are currently no plans to change it, but of course I can't promise that for all eternity.

I'd like to preemptively point out that there is no meaning to "major versions" in V8. For example, the step from 10.9 to 11.0 was based on just the same regular 4-week release cadence as 11.0 to 11.1.

@gautam1168
Copy link
Contributor Author

gautam1168 commented Mar 18, 2023

Thankyou @jakobkummerow for clarifying this. I suppose we could just list the branches using git branch | grep branch-heads/* and look at the latest branch available. Since we are just going to show the version number in the dropdown and whether or not a particular version is in beta or stable can be just determined by the person using it. We can use the second latest branch name listed by this command to make sure we build on a branch only after all changes that are supposed to ever happen in that one have happened.

@gautam1168
Copy link
Contributor Author

gautam1168 commented Mar 22, 2023

@mattgodbolt I have added a javascript.amazon.properties file where I have assumed that the v8 build will be available in /opt/compiler-explorer/d8-11.1/out/x64.release

I looked at the other compilers in CE and they usually have few versions that are specified in the properties files in the group field. However, since v8 seems to have more releases than these other compilers I am not sure how many should be there. Maybe it is good enough to just put the 11.1 version up right now?

In any case, is there a way I can setup my local environment to check if my configuration for amazon.properties is correct? Or is there an alpha environment where I could test it?

I will make a separate pull request to parse the assembly generated by d8 into something that is closer to the output generated by the clang/gcc compilers where you can click to see documentation of instructions and jump to addresses etc. For now I would like to get this change merged with just the default output of v8.

I am working some more on this, before I will remove the [Work In Progress] today, however if you can help me with testing the javascript.amazon.properties file it will clear a major blindspot for me.

@gautam1168 gautam1168 changed the title [Work In Progress] Add d8 for disassembling javascript Add d8 for disassembling javascript Mar 22, 2023
@gautam1168
Copy link
Contributor Author

actually @mattgodbolt I think it doesn't really make sense to have different versions of v8 listed, because really unlike a llvm based C compiler where people are looking to compare what has changed in the generated assembly from version to version, this one is just there for looking at what is getting generated for a snippet.

I personally do not know of anyone who would want the different versions. Although there might be some interest in looking at the assembly generated by other javascript engines for the same code. So I think all we need is the latest version of v8 build. Unless @jakobkummerow tells us otherwise. My own interest in this is to use it in an online course I'm doing to compare the assembly generated by v8 vs the assembly generated by gcc for c++.

So I will just wait for your comment now @mattgodbolt to proceed from here. I have removed the [Work In Progress] from this PR and I will make any changes that might be requested to make this PR mergeable.

@gautam1168
Copy link
Contributor Author

@mattgodbolt whenever you have some time can you take a look at this and let me know what I need to do to get this merged?

@mattgodbolt
Copy link
Member

I'm so sorry this has taken so long to get feedback to you: I'll do my best to help this over the finish line in the next few days!

@mattgodbolt
Copy link
Member

I've added a nightly trunk build installer. It will build d8 nightly from head and install it to /opt/compiler-explorer/d8-trunk/d8. If you let me know what other version(s) are needed, then I can biud those, else this one will update daily.

@mattgodbolt
Copy link
Member

@gautam1168 gautam1168 force-pushed the features/d8-compiler-support branch 3 times, most recently from fb56c0b to 629e578 Compare March 30, 2023 07:15
@gautam1168
Copy link
Contributor Author

I've added a nightly trunk build installer. It will build d8 nightly from head and install it to /opt/compiler-explorer/d8-trunk/d8. If you let me know what other version(s) are needed, then I can biud those, else this one will update daily.

I have added 11.3 in the group which is the latest version of v8 right now. I still don't know if this is going to be useful to people but this will make the configuration flexible to add more versions in the future if needed.

@gautam1168
Copy link
Contributor Author

also @mattgodbolt the npm run debugger command now fails as the ts-node-esm command does not recognise --insepct as an argument. I did some digging and the way to make the debugger attach now would be to update the command as follows:

"debugger": "cross-env NODE_ENV=DEV NODE_OPTIONS='--inspect' ts-node-esm app.ts --debug"

Should I add this change to the package.json in this PR?

@mattgodbolt
Copy link
Member

also @mattgodbolt the npm run debugger command now fails as the ts-node-esm command does not recognise --insepct as an argument. I did some digging and the way to make the debugger attach now would be to update the command as follows:

Thanks for spotting that. If you'd like to fix it, please do it in a separate PR! Thanks :)

I tend to use an IDE so didn't spot this 😊

Copy link
Member

@mattgodbolt mattgodbolt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I'll build and install the 11.8 node js and then test locally before a final merge. Thanks so much for your patience.

@@ -0,0 +1,13 @@
function square(a) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks : I understand what happened here. This workaround works for me!

options=
supportsExecute=false
stubText=

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some extra whitespace here (I can remove it though)

compiler.v8trunk.exe=/opt/compiler-explorer/d8-trunk/d8
compiler.v8trunk.options=--print-opt-code --redirect-code-traces --allow-natives-syntax

compiler.v8113.exe=/opt/compiler-explorer/d8-11.3/d8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I'll get this one installed too.

mattgodbolt added a commit to compiler-explorer/infra that referenced this pull request Apr 1, 2023
@mattgodbolt
Copy link
Member

I'm going to merge and fix up the cosmetic issues!
image
Working locally! Thank you so much! I'll try and get this deployed shortly.

@mattgodbolt mattgodbolt merged commit 3c58187 into compiler-explorer:main Apr 1, 2023
@mattgodbolt
Copy link
Member

In local testing I can't for the life of me find the actual disassembly output. For example I see:

--- Raw source ---
(a) {
  return a * a * a * a * a;
}

--- Optimized code ---

Instructions (size = 96)
0x5591ac944040     0  8b59d0               movl rbx,[rcx-0x30]
0x5591ac944043     3  4903de               REX.W addq rbx,r14
0x5591ac944046     6  f6431301             testb [rbx+0x13],0x1
0x5591ac94404a     a  0f85704204a0         jnz 0x55914c9882c0  (CompileLazyDeoptimizedCode)    ;; near builtin entry
0x5591ac944050    10  55                   push rbp
0x5591ac944051    11  4889e5               REX.W movq rbp,rsp
0x5591ac944054    14  56                   push rsi
0x5591ac944055    15  57                   push rdi
0x5591ac944056    16  50                   push rax
0x5591ac944057    17  4883ec08             REX.W subq rsp,0x8
0x5591ac94405b    1b  488975e0             REX.W movq [rbp-0x20],rsi
0x5591ac94405f    1f  493b65a0             REX.W cmpq rsp,[r13-0x60] (external value (StackGuard::address_of_jslimit()))
0x5591ac944063    23  0f8605000000         jna 0x5591ac94406e  <+0x2e>
0x5591ac944069    29  e927000000           jmp 0x5591ac944095  <+0x55>
0x5591ac94406e    2e  ba50000000           movl rdx,0x50
0x5591ac944073    33  52                   push rdx
0x5591ac944074    34  48bb501c194c91550000 REX.W movq rbx,0x55914c191c50
0x5591ac94407e    3e  b801000000           movl rax,0x1
0x5591ac944083    43  48be353c1800dd140000 REX.W movq rsi,0x14dd00183c35    ;; object: 0x14dd00183c35 <NativeContext[276]>
0x5591ac94408d    4d  e82e250da0           call 0x55914ca165c0  (CEntry_Return1_ArgvOnStack_NoBuiltinExit)    ;; near builtin entry
0x5591ac944092    52  ebd5                 jmp 0x5591ac944069  <+0x29>
0x5591ac944094    54  90                   nop
0x5591ac944095    55  41ff55c8             call [r13-0x38]
0x5591ac944099    59  41ff55d0             call [r13-0x30]
0x5591ac94409d    5d  0f1f00               nop

Inlined functions (count = 0)

Deoptimization Input Data (deopt points = 2)
 index  bytecode-offset    pc
     0                2    NA 
     1               -1    52 

Safepoints (entries = 1, byte size = 12)
0x5591ac944092     52  slots (sp->fp): 10000000  deopt      1 trampoline:     59

RelocInfo (size = 5)
0x5591ac94404c  near builtin entry
0x5591ac944085  full embedded object  (0x14dd00183c35 <NativeContext[114]>)
0x5591ac94408e  near builtin entry

--- End code ---

there's no indication of any MUL or similar operation there, such as I might imagine... I will deploy and we can iterate though :)

mattgodbolt added a commit that referenced this pull request Apr 1, 2023
@jakobkummerow
Copy link

A blind guess, without having investigated: try some more (e.g. a hundred) invocations of the function before optimizing it. Calling it just once or twice won't be enough to collect feedback, because even feedback collection takes some time to kick in (to save time and memory for code that's only executed once).
If that helps, then it probably makes sense to add --no-lazy-feedback-allocation to the d8 command-line flags.

@mattgodbolt
Copy link
Member

@jakobkummerow the example code has some intrinsic magic thing to alledgedly "optimize on the next call"

@mattgodbolt
Copy link
Member

I'll get this deployed and then we can see though

@jakobkummerow
Copy link

@mattgodbolt Yes, I was guessing you've been using example code similar to the default.mjs that's part of this PR, and I'm saying it's not surprising that that isn't quite enough. Try this:

function square(a) {  // Misnomer, whatever.
  return a * a * a * a * a;
}

// Call function a bunch of times to fill type information
for (let i = 0; i < 100; i++) square(23);

%OptimizeFunctionOnNextCall(square);
square(71);

Of course you can also get it deployed first and then iterate.

@mattgodbolt
Copy link
Member

We should probably update the example code if that's needed :)

@mattgodbolt
Copy link
Member

This is now live, and indeed calling it in a loop generates slightly more understandable code!

@jaredwy
Copy link
Contributor

jaredwy commented Apr 2, 2023

Don't have any feedback for the PR but just saw this in my emails and wanted to say I am so excited to see this land!

@gautam1168
Copy link
Contributor Author

gautam1168 commented Apr 2, 2023

Hello. So happy to see this is live now.

@mattgodbolt so I think the reason you cannot see the exact multiply instruction is because that is done using a builtin function in the javascript engine instead of a direct assembly instruction. I think its this line over here
image

Although I may be wrong.

I have also got the output for the same code in turbolizer and we can see that the bytecode has the multiplication in it:
image

But then it doesn't look like it gets translated to a simple one instruction multiplication even after optimization. It seems to stay as a function call.

Also I see that in the live version the trunk compiler is not working. Ill check it today.

@jakobkummerow
Copy link

It's cool to see this in action :)

the reason you cannot see the exact multiply instruction is because...

...there's no type feedback, because type feedback collection only starts after a while, and optimized compilation was manually forced before that had happened. So the optimizing compiler doesn't know that it should expect integer multiplications, so it emits code that will just get deoptimized when it's called. The Turbolizer output makes this clear (if you know how to read it...).

One workaround is to call the function more often before optimizing it. In this particular example, 10 calls seems to be the threshold, but it would probably be unwise to rely so tightly on engine-internal heuristics that can and do change over time, so 100 calls would be a more robust default choice.

Another workaround is to manually trigger the start of feedback collection:

%PrepareFunctionForOptimization(square);
square(13);  // Collect type feedback
square(13);  // once more, for any premonomorphic ICs (not in this example)
%OptimizeFunctionOnNextCall(square);
square(13);  // Optimize.

A third workaround, and IMHO the best option, is to add --no-lazy-feedback-allocation to the d8 command-line flags, which has the effect of implicitly calling %PrepareFunctionForOptimization(...) for every function right away. (That used to be the default behavior until a couple of years ago, when we introduced lazy feedback allocation as an optimization.)

@jakobkummerow
Copy link

Taking a step back, I'm wondering whether it would be (1) feasible and (2) desirable to add a --compiler-explorer flag to d8 upstream, which would simplify all this on your end. This special mode could e.g. detect calls in top-level code, and automatically turn square(13) into the sequence that causes optimization, in this example %PrepareF...(square); for (let i = 0; i < 3; i++) square(13); %OptimizeF...(square); square(13);.
Hacking that kind of pattern detection and code mutation into V8's compilation pipeline (which obviously isn't built for this, and should continue to prioritize performance in regular production scenarios) is where feasibility concerns arise. I'm also not sure about desirability, because while this would simplify things for simple cases, it would also take away control (e.g. if snippets wanted to intentionally create complicated type feedback situations before triggering optimization), and it would cause V8's behavior to deviate in Compiler Explorer from what it normally does in Chrome/Node/..., which could easily create more confusion than it avoids.

@mathiasbynens
Copy link

Have y’all considered using jsvu to grab precompiled d8 binaries? https://github.com/GoogleChromeLabs/jsvu Perhaps it could simplify your setup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants