Author: xianyuqiang chief architect of the compiler

ArkCompiler is a componentalized and configurable multi-language compiler and running platform, which can support either a single language running environment or a combination of multiple languages. It currently supports JavaScript, TypeScript, and Java.

An overview,

HarmonyOS is designed to be a unified operating system for mobile, PC, tablet, TV, car and smart wearable devices.

Figure 1 Multi-device interconnectionCopy the code

Its application development needs support of multiple programming languages and paradigms, among which high-level programming languages include JavaScript, TypeScript, Java, etc., and development paradigms include declarative UI paradigm and distributed programming paradigm. We need compilers and runtimes to support the efficient development, deployment, and operation of these high-level application programming languages. Enable application developers to use the same set of development framework to achieve a development of multiple deployment run. And to provide HarmonyOS users with a unified user experience. Thus, ArkCompiler came into being.

1. The target

ArkCompiler is a unified programming platform designed to support the joint compilation and operation of various programming languages and chip platforms. Its design goal is to provide a multilanguage compiler runtime with pluggable languages and configurable components.

  • Language pluggable: the design architecture supports access to multiple languages. ArkCompiler is capable of providing multi-language runtimes with efficient execution performance and cross-language advantages, as well as efficient and lightweight single-language runtimes on small devices.
  • Component configurability: ArkCompiler has a rich compiler runtime component system. The runtime language and components are compiled through custom configurations to support different performance and memory requirements on a variety of devices, including phones, PCS, tablets, TVS, cars, and smart wearables.

2. The architecture

As shown in figure 2, ArkCompiler contains the compiler, toolchain, runtime and other key components. The ArkCompiler toolchain implements the front-end compiler for the corresponding language, which compiles the high-level language of the front-end development framework into uniform bytecode/binary files. According to different application scenarios, ArkCompiler runtime interpreter interprets and executes bytecode files or JIT/AOT compiler compiles and executes the optimized machine code of the corresponding architecture, thus improving the running efficiency and startup performance.

Figure 2. Operating principle of ArkCompilerCopy the code

Next, this article will start from the front end compiler, run time.

Second, the front-end compiler

The front-end compiler is the bridge between the high-level language and the language runtime. It translates the semantics expressed by the programming language into a medium that can be understood by the runtime, in the case of ArkCompiler solutions, as ArkCompiler bytecode. That is, the ArkCompiler Bytecode (ABC for short) in Figure 3. Some languages also support direct compilation of bytecode into architecture optimized machine code through the AOT Compiler component of ArkCompiler.

Figure 3. ArkCompiler front endCopy the code

1. Front-end compiler function

In ArkCompiler, which needs to support multiple languages, the main function of the front-end compiler is to generate the source code into bytecode files on the Host side, which has such advantages:

  • With the powerful computing power of Host, more and more complex algorithm optimization can be done before running to reduce the work during running and improve the running efficiency.
  • Compared to the common JavaScript runtime, the end-to-end compilation and parsing process can be advanced before release to improve the startup performance of the program.

Figure 4. JavaScript flowCopy the code

Compiler optimization

ArkCompiler provides native support for TypeScript (TS). When compiling TS source code at the front end, the explicit type declaration of TS is used, type derivation is applied for type optimization, and the derived type information is reserved to the runtime through bytecode files, so that the runtime can directly use the type information to execute a fast path. In addition, static type analysis and derivation also make it possible for TS AOT (Ahead of Time) Compiler. The type information obtained from static analysis helps AOT Compiler directly compile and generate high-quality machine code, so that TS source code can be directly run in machine code form, further improving the running performance.

Figure 5 compilation optimizationCopy the code

ArkCompiler bytecode

ArkCompiler Bytecode (ArkCompiler Bytecode) is a hardware – and platform-independent intermediate representation of run-time interpreters that can parse runs, designed to be compact, extensible, and multilingual. Mask device differences and support cross-device distribution, deployment, and operation of applications. ArkCompiler uses the register-based bytecode format. Each register is 64 bits wide and supports a maximum of 65536 registers.

(1) Register

The ArkCompiler register is required to be able to place object references and primitive types, with a width of 64 bits. Registers are scoped to function stack frames. In bytecode instruction encodings, register indexes support variable-length encodings of 4, 8, and 16 bits, reducing the bytecode size while supporting different ranges of register addressing within the method.

(2) Add register

An accumulator register, commonly known as an accumulator, is a special register that is implicitly used by instructions. The main purpose of using an accumulator is to improve instruction code density without sacrificing performance. In ArkCompiler bytecode, the previous instruction takes the accumulator as the result output, and the next instruction takes the accumulator as the input, which can effectively improve the instruction density and reduce the size of bytecode. At the same time, the front-end compiler can effectively eliminate redundant accumulator load and store operations by analyzing and optimizing the data flow and control flow in the bytecode generation stage.

(3) Basic type support

ArkCompiler bytecode provides register manipulation support for 32-bit (I32) and 64-bit (I64) integer values, with 8-bit and 16-bit values emulated by extending to 32-bit. Registers of ieEE-754 double precision floating-point F64 values are supported. F32 data types (IEEE-754 single precision) are also emulated by converting to F64 values. Primitive data types do not require virtual machine recording, tracing, and derivation, but are represented by specialized bytecodes that operate on different primitive data types, including the symbolic nature of integer values. In order to make more efficient use of the instruction space of bytecode, the design introduces more specialized bytecode for data types and operations used at high frequencies and more general bytecode for data types and operations used at low frequencies.

(4) Support for language-related types

ArkCompiler supports a hierarchical type system based on the language in which it executes. Thus, strings, arrays, exception objects, etc. created or loaded from the constant pool are hierarchical data objects that match the specific language specification.

(5) Dynamically typed language support

To support dynamically typed languages like JS/TS, ArkCompiler represents dynamically typed values with a special tag value (“Any”) that wraps the value itself and the corresponding type information (both primitive and object reference type data). The virtual register is wide enough to hold Any values. At the same time, in the execution context of dynamically typed language code, statically determined type instruction sequences including type checking instructions may also be used to represent dynamically typed semantics.

ArkCompiler runtime

The ArkCompiler Runtime, as shown in Figure 6, is divided into Core Runtime and language-independent Runtime plugins.

The core runtime is mainly composed of the common core components of the runtime, including the Public ISA module that defines the bytecode format and behavior, and the ArkCompiler Base Platform module that interconnects with the system calls. The Common Tool module supports tools such as Debugger and Profiler, and the ArkCompiler File module supports bytecode File processing. Optional language-independent infrastructure components such as interpreters, memory management, compilers, and concurrency are also provided.

The runtime plug-in of each language contains the implementation of features specific to each language and the standard library to support the running behavior of the language in accordance with the corresponding language specification, which is customized by each language according to the needs.

Figure 6 Runtime frameworkCopy the code

1. Execution engine

The ArkCompiler runtime execution engine has a variety of components, including an interpreter, JIT compiler, and AOT compiler, as shown in Figure 7.

Figure 7 Execution engine structureCopy the code

(1) Interpreter

The interpreter can run bytecode directly from the front-end compiler.

(2) JIT Compiler

JIT compilers typically execute Code at runtime for a period of time. After profiling data is generated, JIT compiles high-quality machine Code (Optimized Code II above) from profiling data. (JIT can compile and generate optimal machine instructions in real time based on code execution)

3) AOT Compiler

AOT Compiler directly generates high-quality target machine Code (Optimized Code I) based on static information before running. Profile Guided Optimization (PGO) configuration file can be used as one of the inputs of AOT Compiler. Give the AOT Compiler some instructions, such as the scope of compilation and which optimization techniques to use when compiling a method. Typically such PGO profiles are generated through runtime profiling or big data analysis on devices of the same size.

Optimization code generated by JIT compiler or AOT compiler is usually generated under certain optimization assumptions or optimization inference. If this premise is not true at run time, a Deopt (inverse optimization) is required to fall back to the interpreter execution, which is rarely the case.

2. Customize requirements

The performance of each execution engine is shown in Figure 8:

Figure 8 Performance comparison of each execution engineCopy the code

The ArkCompiler runtime supports different customization requirements for multiple devices through an on-demand combination of different execution modes.

  • On low-end IOT devices, the ArkCompiler execution engine supports a pure interpreter execution mode to meet the memory constraints of small devices.
  • On high-end devices, the ArkCompiler execution engine supports the mode of interpreter running with AOT compiler and JIT compiler, and AOT compiler is used to compile a considerable part of the code, so that the program can run on high-quality optimized code at the beginning and get the best execution performance.
  • On other devices, the strategy is selected according to the hardware constraints of the device, and the range of code that requires AOT-compiled is set to be used frequently. Other code relies on the interpreter to run with JIT Compiler to maximize the performance of the application.

Explanation in order to improve performance, in particular, under the architecture of the interpreter agreed to explain execution context some frequently used data on the corresponding physical register, in Arm64 framework, for example, in the context of the current bytecode instruction address, accumulator value mapping table, the interpreter stack frame, instructions, the current thread object, etc., directly on the fixed register, Avoid frequent load and write operations on the stack.

3. The concurrent

The development and operation of complex mobile applications have a strong demand for concurrency. The ArkCompiler runtime provides responsive Actor concurrent programming model support in addition to the standard “Java multithreaded programming” and “runtime support.” Under this model, the implementers do not share any data and communicate with each other through message mechanism. At present, some Actor concurrent models in the industry, such as web-worker implementation of traditional JS engine, have defects of slow startup speed and high memory consumption.

In order to make use of the multi-core capability of the device to obtain better performance improvement, on the basis of the Actor memory isolation model, ArkCompiler runtime improves the startup performance of Actor and saves memory overhead by sharing immutable or immutable objects, built-in code blocks, method bytecode, etc., in the Actor instance. Achieve the goal of implementing a lightweight Actor concurrency model.

Figure 9 lightweight Actor implementationCopy the code

4. Cross-language optimization

In some cases, the HarmonyOS app is actually made up of code in multiple languages. For the HarmonyOS JS/TS application, for example, some of the system libraries, frameworks and capabilities that the application relies on are implemented using C/C++ and Java. The HarmonyOS framework also provides a JS NAPI for JS/TS to interact with C/C++ and a Channel mechanism for JS/TS to interact with Java. Considering the development and operation efficiency requirements of interaction scenarios between different languages, ArkCompiler and development framework are jointly designed to provide the corresponding optimization mechanism.

(1) JS/TS interacts with C/C++

In the TS version of the OPERATING system platform API implementation, we usually need to face the SCENARIO of C/C++ code accessing and manipulating TS objects. For this business scenario, ArkCompiler can generate C/C++ header files containing layout descriptions of TS objects and C/C++ implementation libraries that operate on these TS objects, based on the TS source class declarations and runtime conventions. In C/C++ code, direct manipulation of TS objects is achieved by including the TS object description header file and linking to the corresponding implementation library. It should be noted that since the TS type or its inherent layout is not always fixed, the code implementation of TS object operations inserts type checking to fall back to the common slow path if the object type or layout changes at runtime.

Figure 10. Cross-language interactionCopy the code

(2) JS/TS interaction with Java

Some of the capabilities required by HarmonyOS applications are provided through the system, framework, or Java libraries of the application. HarmonyOS also has a lot of JS/TS code interacting with Java code. In common cases, JS/TS code and Java code have their own independent operating environment and are unknown to each other’s data representation and calling convention. Therefore, data interaction between JS/TS and Java usually requires standard JSON serialization and deserialization processes, as well as mutual invocation via the bridge of Native layer. This can cause overhead in some scenarios and affect the user experience.

ArkCompiler takes advantage of supporting multiple languages at the same time, and has information of data representation, object layout and function calling convention of different languages at runtime, which makes it possible to directly access data, operate objects and call methods across languages. At the same time, the more certain type information provided by The Java code becomes an additional input to the JS/TS type derivation, which facilitates the compilation optimization of JS/TS. On the other hand, this allows us to provide developers with a more simplified multi-language programming model, reducing the amount of extra hand-written business-neutral cross-language interaction code.

Figure 11. Simplified multilingual programming modelCopy the code

Four,

In the era of IoT supported by HarmonyOS, ArkCompiler is designed in collaboration with hardware, operating systems, development frameworks and programming languages to meet the requirements of application ecology, development experience and user experience. Improve the development and operational efficiency of HarmonyOS applications.

In the future, ArkCompiler will continue to optimize the basic experience, and further integrate HarmonyOS ‘requirements for interconnection of everything, providing underlying solutions and optimization mechanisms in terms of compiler and runtime in innovative scenarios such as cross-terminal migration and multi-terminal collaboration to improve the development and operation experience of distributed applications.