Electron 6.0.8 may not seem like a remarkable release, but look closely and you will find an unusual extra feature:
That little bullet-point is the culmination of nine months of work by Arm and our partners to enable Chromium and its broader ecosystem on this exciting new platform. Electron powers dozens of amazing and highly popular apps, including Slack, Microsoft Teams, and Microsoft Visual Studio Code. This release means that Electron developers and ISVs can now easily bring their existing apps to Windows on Arm (WoA) and deliver the best possible performance and battery life.
So how did we get here?
It's a chilly, damp morning on December 5 2018. I have cycled at top speed across Cambridge to go to an internal compilers and code-gen conference. I got lost and I'm thoroughly breathless and exhausted. I work in Arm’s Clamshell team in the Open Source Software (OSS) division, and I am due to give a presentation about building Chromium. I am nervously checking my phone, pacing up and down, trying not to think about a lecture theater full of rambunctious compiler engineers, when suddenly, an excellent, distracting message from my boss flits across the top of the screen:
We have an emerging requirement to get an Electron WoA demo ready for CES (8 Jan).
CES 2019 is less than a month away. Electron’s never been previously built for this platform. It contains millions of lines of code unprepared for the idea of Windows running on Arm processors. The compiler’s not ready, V8 had initial support merged only the week before. How are we going to do this?
To make things extra difficult, we also decided we wanted to port Visual Studio Code – a showcase for the technology, one of the largest and most complicated Electron apps. We did not know that to achieve this – we would need to dig into the compiler, Chromium, Electron, V8, Node, and Visual Studio Code itself…
One of the things that is special about my job is that moment when something successfully starts for the first time. It is a mix of excitement and elation. You think it is going to crash on start up (again) and, even if it does start, everything will be heavily broken. But all those assumptions that turned out to be wrong. Once built, Chromium ran with very few issues, and it ran well: performance was great, easily matching – and sometimes beating – native Edge and emulated x86 Chromium. Browsing web pages and running benchmarks felt fast and fluid. We knew that – even at this early stage – getting the demo together was feasible.
Chromium 73 running for the first time on an HP Envy x2 WoA device, 13 December 2018
Over the next few weeks, we wound forward Electron’s complicated nest of dependencies, building a preview of what Electron 5 might look like when it had rolled Chromium 73. We then brought up Visual Studio Code and its modules against our speculative Electron 5 on a standard Linux system. We then attempted to port that over to WoA and hit our first major obstacle.
Electron apps are not just CSS, HTML, and JavaScript: part of what makes them so flexible and powerful is their ability to mix these technologies with native C and C++ libraries. This means that you get the excellent development productivity of the web platform when you want it and high performance when you need it. Native modules provide Visual Studio with capabilities like syntax highlighting, terminals, and many other features.
But whatever we did, whatever we tried, loading even the most trivial native module crashed the entire Electron process. With time tight, we stripped out native modules and built a simplified, but functional, Visual Studio Code prototype just in time for CES. Phew.
Running under the debugger, we could see that the native modules crash happened soon after a module was loaded. And it appeared to be dereferencing a nonsense pointer – something like 0xFFFFFFFD.
0xFFFFFFFD
At that time, debug builds did not function correctly, and we had to use the release-optimized build (making source-level stepping useless). Clang also did not know how to emit CodeView information, so the Windows debug tools were unable to display any local variable values. Our debug process involved stepping through the assembly view, while mentally mapping it back to the source code and figuring out the mapping of variables to registers and the stack. On each run, ASLR would randomise the address space and everything would change, making things even more difficult and frustrating.
We chipped away at the problem for some time. Stack and heap corruption were prime suspects. But after six weeks or so of frustratingly little progress, I noticed that hacking the build and compiling the native modules with Chromium’s Clang (instead of the standard MSVC) meant that everything worked correctly and VSCode launched.
It is an oft-forgotten but fundamental guarantee that any program on a given platform should be able to link with or load any library that is compiled with any compiler. The set of rules to make that happen is called an Application Binary Interface (ABI). A document that sets out (in painstaking detail) information like which arguments get passed in which registers, the sizes of all the various data types, and how the executable format is laid out. However, it appeared that Clang was not speaking the right ABI.
To fix this, I isolated the first few functions of the module where control passed from Clang-generated code to MSVC-generated code, and painstakingly compared them, stepping through each of them many times and figuring out where all the values came from. One of the arguments which was supposed to be passed in the X8 register was actually being passed in X0. Normally, this register is used for passing either the first argument (or the “this” pointer in C++) and the other arguments were being displaced to adjacent registers. At the same time, we discovered a related problem where Clang did not return an expected value in the X0 register.
All of these issues were fixed in Clang and LLVM at the start of May 2019.
Visual Studio Code running for the first time with all native modules, 15 March 2019
With Clang fixed and the Chromium 76 branch cut, we had just over a month to get as many Electron tests passing as possible. Most issues were minor, but the tests highlighted that crash reporting was broken. These tests deliberately crash an Electron process to check that it can successfully generate a post-mortem crash report. This was a critical issue to fix: without reliable crash dumps, third-party app developers would have absolutely no idea where and why their applications crashed in the field – leading to unhappy users.
We implemented the necessary ABI-specific changes in crashpad, but even with these changes applied, we could not get a crash dump. I eventually discovered that the problem happened because the Windows exception handler could not always unwind the stack. Compilers are supposed to annotate their code with the right information to help do this – Clang functioned properly here – but V8 adds a secondary compiler into the mix. V8 also reoptimizes and recompiles arbitrary code at runtime, making things more difficult. We needed to extend V8 so it could emit the right information to help the Windows exception handler unwind and find the right exception handling function.
Fixing this was not easy (exception handling is a complicated topic on WoA), but Microsoft rapidly turned around a patch (assisted by our internal V8 team) which we backported to Electron 6.
In June, we began automatically building Chromium and Electron and running tests on real devices – a fleet of Lenovo Yoga C630s powered by the Qualcomm Snapdragon 850 SoC, built on Arm technology. Stability and functionality achieved the level that we hoped for – but late in the Electron release cycle. The backports and changes were too large to safely merge just before release, so Electron 6 was released without official WoA support.
We continued testing over August while the Electron project finished the initial release and got their build pipeline and infrastructure ready for WoA. On August 27, after nine months of effort, the final changes landed, and Electron 6.0.8 was released on September 9. This is the first deployment of any Chromium technology on WoA, and it works excellently.
Visual Studio Code's master branch, built with Node v12.13.1, node-gyp v5.0.5, featuring Electron 6.1.4 running on a HP ENVY X2 Windows on Arm device, 20 November 2019
Recently, the Electron project released version 7.0.0 with day one support for WoA. Today, the test status for WoA is looking good, and we are continuing our work to build and test Chromium. We still have a few workflow issues to fix – it is not as easy to build native modules for WoA as it should be – we are working on that. We will continue to support Electron, so that the WoA developer ecosystem is able to port their apps easily and gain the performance benefits. We have also prepared a getting started guide covering the basics of porting Electron apps to WoA – we cannot wait to see what is built!
Shipping something as complex as Electron requires a lot of intense technical effort and collaboration across multiple projects. We would like to thank Tom Tan at Microsoft (for his V8 fixes and encyclopedic knowledge of all things that are related to WoA), Mandeep Singh Grangh (for fixing Clang’s ABI issues), Jon Kleinschmidt at GitHub (who built Electron’s continuous integration infrastructure for WoA), Reid Kleckner (for fixing Clang’s virtual function calls), Adam Kallai at the University of Szeged, Sjoerd Meijer in our internal compiler team, Samuel Attard at Slack (who helped us integrate our backports into Electron 6), and Georgia Kouveli and the V8 team at Arm.
[CTAToken URL = "https://www.arm.com/solutions/mobile-computing/laptops" target="_blank" text="Learn more about Arm's laptop solutions" class ="green"]