<<

Author Stefan Rumzucker, BSc

Submission Institute for System Software

Thesis Supervisor o.Univ.-Prof. Dipl.-Ing. Dr.Dr.h.c. Hanspeter Mössenböck

Assistant Thesis Supervisor Dipl.-Ing. Dr. Matthias Grimmer Dipl.-Ing. Dr. Christian Wirth

Linz, May 2016 A WEB FRAMEWORK FOR INTERACTIVE TRACE VISUALIZATION

Master’s Thesis to confer the academic degree of Diplom-Ingenieur in the Master’s Program Computer Science

JOHANNES UNIVERSITY LINZ Altenberger Str. 69 4040 Linz, Austria www.jku.at DVR 0093696 i

Abstract

Most optimizations performed by a modern compiler are applied to a specific intermediate representation (IR). When debugging the compiler it is important for developers to be able to inspect this IR in order to trace the changes and verify its correctness. The data structures used for modelling such an IR can be be complex and hard to comprehend, so it’s essential to offer a suitable representation that allows proper inspection.

This thesis introduces Salver, a modular web framework that supports developers in building tools for inspecting internals of the Graal Compiler. The Graal Compiler is a just-in-time com- piler written in that aims to produce highly optimized code. It uses a graph-based IR, the Graal IR, on which it performs optimizations in several compilation phases. For debug- ging purposes, the most intuitive way to inspect the IR is by providing a graph visualization. Besides the Graal IR, which is considered the high-level IR, there is also a low-level IR (LIR) used to produce machine code. The LIR has different characteristics and therefore requires individual representations when being inspected.

Currently two separate tools are used to inspect either the Graal IR or the LIR. These tools require individual workflows and use different data formats as their input. They are also not able to interchange information or link to data within the other tool.

Salver aims to unify the inspection workflow by having a single extensible tool that can be used to inspect different kinds of information collected during the compilation process of the Graal Compiler. The tool can be used via a modern web browser, without the need of installing additional software, and is also accessible remotely, which allows to share the collected data among other users.

In this thesis we extend the Graal Compiler with additional functionality to provide infor- mation on the Graal IR during compilation. This information is then used within our tool to visualize IR graphs. The tool focuses on graph visualization and the inspection of nodes, which enables to visually perceive changes applied to IR graphs in each phase, helping to gain a better understanding of the optimizations done by the compiler. ii

Kurzfassung

Viele Optimierungen moderner Compilern werden auf eine spezielle interne Zwischendar- stellung (Intermediate Representation, IR) angewendet. Im Falle einer Fehlersuche ist es für Entwickler wichtig in der Lage zu sein diese IR auf ihre Richtigkeit zu prüfen und die Verän- derungen nachvollziehen zu können. Die Datenstrukturen, welche verwendet werden um die IR zu modellieren, sind unter Umständen nur schwer verständlich, darum ist es notwendig eine passende Darstellung für eine Untersuchung zu finden.

Diese Arbeit beschreibt Salver, ein modulares Web Framework, welches Entwickler dabei unterstützt Anwendungen für das Inspizieren von internen Daten des Graal Compilers zu bauen. Der Graal Compiler ist ein Just-In-Time Compiler, welcher in Java geschrieben wurde und darauf abzielt hoch optimierten Code zu produzieren. Er verwendet eine graph-basierte IR, die Graal IR, welche er für Optimierungen in unterschiedlichen Kompilierphasen nutzt. Für eine Fehlersuche ist es hilfreich diese IR auch als Graph darzustellen. Neben der Graal IR, welche als höhere Zwischendarstellung fungiert, gibt es auch eine architekturnähere IR, welche für die Erstellung des Maschinencodes verwendet wird, die sogenannte Low-Level IR (LIR). Diese hat wiederum andere Charakteristiken und benötigt folglich eine individuelle Darstellung zur Inspektion.

Im Moment werden zwei unterschiedliche Anwendungen für die Inspektion der Graal IR beziehungsweise der LIR verwendet. Diese Anwendungen nutzen unterschiedliche Arbeits- abläufe, sowie eigene Formate um Daten zu laden. Auch sind sie nicht in der Lage Infor- mationen untereinander auszutauschen oder einen Zusammenhang zu Daten in der jeweils anderen Anwendung herzustellen.

Salver versucht einen einheitlichen Arbeitsablauf bereitzustellen, indem lediglich eine An- wendung verwendet wird um unterschiedliche Informationen darzustellen, welche während des Kompiliervorgangs des Graal Compilers gesammelt wurden. Diese Anwendung kann in einem modernen Web Browser verwendet werden, wobei keine zusätzliche Software instal- liert werden muss. Zudem kann sie aus der Ferne genutzt werden, was erlaubt die gesam- melten Daten auch mit anderen Nutzern zu teilen.

In dieser Arbeit erweitern wir den Graal Compiler mit zusätzlicher Funktionalität um die nöti- gen Informationen während des Kompiliervorgangs bereitstellen zu können. Diese Informa- tionen werden dann in unserer Anwendung für die Visualisierung der Graphen verwendet. Die Anwendung konzentriert sich auf Graph Visualisierung und Inspektion der einzelnen Knoten, was es erlaubt die Veränderung der IR in jeder Phase nachzuvollziehen um somit ein besseres Verständnis der durchgeführten Optimierungen des Compilers zu erlangen. iii

Contents

Abstract i

Kurzfassung ii

1 Introduction 1 1.1 Motivation ...... 1 1.2 Goals and Scope ...... 2 1.3 Challenges ...... 3 1.4 Structure of the Thesis ...... 4

2 Graal Project 5 2.1 Virtual Machine ...... 5 2.2 Graal Compiler ...... 5

3 Salver Framework 9 3.1 Architecture ...... 9 3.2 Components ...... 11 3.3 Tracing ...... 11 3.3.1 Trace Messages ...... 12 3.3.2 Trace Context ...... 15 3.3.3 Metadata ...... 16 3.3.4 Serialization ...... 18 3.4 Trace Resources ...... 20 3.4.1 Extracted Resources ...... 20 3.4.2 Generated Resources ...... 20 3.4.3 Resource Path ...... 21 3.5 Workflow ...... 21 Contents iv

4 Case Study 25 4.1 Graal Trace Provider ...... 25 4.1.1 Graal IR Data ...... 27 4.1.2 Trace Messages ...... 30 4.1.3 Implementation ...... 32 4.2 Graal IR Visualization Tool ...... 35 4.2.1 Architecture ...... 35 4.2.2 Functionality ...... 37 4.2.3 Trace Messages ...... 38 4.2.4 Graph Resources ...... 38 4.2.5 Visualization ...... 39 4.2.6 ...... 42

5 Evaluation 43

6 Related Work 46

7 Summary 50

Bibliography 57 1

1 Introduction

This chapter introduces Salver, a web framework that aims to simplify the devel- opment of web based tools for inspecting compiler internals. The introduction ad- dresses the goals of this project and outlines the challenges of designing the frame- work.

1.1 Motivation

Modern compilers perform most of their optimizations using a specific intermediate repre- sentation (IR). For debugging purposes it is essential to visualize this IR in a comprehensible way, so that developers are able to inspect and verify the optimizations.

As for this thesis we are focusing on the Graal Compiler, which essentially provides informa- tion on two important compiler internals for inspection, the Graal IR and the low-level IR. The project includes tools that are able to appropriately present this information to the user. The fact that separate tools have to be used for that purpose renders the inspection work- flow inconvenient. It also limits the functionality as these tools are working independently, prohibiting data aggregation that might otherwise offer additional knowledge about the in- ternals. They use different data formats and there is also no common way of transferring information from the compiler to these tools.

To solve these issues we propose an alternative approach in this thesis. We suggest a web based application that integrates all needed functionality into one uniform tool. All relevant information needed for inspection shall be processed and stored in one place. Ideally this is a remote that can serve as the central endpoint for several machines providing such information. Users are then able to use the frontend of the inspection tool via a modern web browser, without the need of installing additional software. Since the collected information can be shared among other users, this approach would allow access to data sets from any previous compilations of all users. It also facilitates data aggregation, which offers additional knowledge about the data set. 1.2 Goals and Scope 2

1.2 Goals and Scope

This thesis describes the effort to design and develop a modular web framework, which aims to simplify the development of tools for software tracing with a special focus on visualization of compiler internals.

Tracing Framework

The main goal of this thesis is to create a flexible and extensible web framework that allows to build web based tools for tracing and inspection of compiler internals. This includes collecting of trace information produced by the compiler as well as processing and storing the resulting data. The framework consists of individual modules that can be used to provide specific visualizations for individual compiler internals.

Web as a Platform

The intention behind the framework is to create web applications, meaning the frontend is accessible via a modern web browser. The browser has to support up to date standards allowing to take full advantage of currently available web technologies. A gives users the ability to access the frontend from almost every machine or device without the need of installing additional software.

Unified Workflow

The framework intends to provide a unified workflow for different types of compiler inter- nals. It tries to do this by providing a common mechanism to create serializable information for tracing purposes within the compiler.

Extensibility

The ability to add new features and domain specific functionality is an essential requirement for the framework and for the tools using it as a basis. The framework can be extended with additional modules as well as plugins to enhance a particular tool.

Case Study

Another goal of this thesis is to show the usefulness of the framework by developing a visual- ization tool for the graph-based intermediate representation of the Graal Compiler. Therefore the compiler needs to be extended in order to provide the relevant information needed for inspection. The tool covers the use case specific functionality and shows the capabilities of the framework as well as the basic workflow. 1.3 Challenges 3

1.3 Challenges

Workflow and Data

The workflow proposed in this thesis tries to offer a convenient solution that allows to trace different kinds of internal information of a software system, which typically involves a large amount of data for a particular information. This is different when compared to other tracing approaches that are mostly used for performance measuring, as the focus is on the data and how it can be stored respectively transformed and eventually visualized.

Our approach needs to allow simple extensions when new kinds of information are required without breaking existing solutions that depend on the same source of information. This is why we do not use a specific schema when creating data for tracing purposes. Furthermore we also enforce tolerant processing of this data, meaning unknown information shall be ignored and missing information shall be complemented if possible.

In addition to a text-based serialization format for our data, we introduce a special binary format that handles schema-less data structures. This format enables fast parsing and typi- cally achieves a smaller file size in contrast to text-based representations.

A goal of our workflow is that it should be both efficient and simple to use, which requires a trade-off. Aside from the convenient way of serializing data objects, which is typically slower and requires more memory, we also support writing the serialized representation directly by hand.

Web Application Limitations

Typically web applications are accessible from almost every device or machine that is capable of running a modern web browser, which is supporting up to date . This can be seen as an advantage over regular desktop applications as the user is not required to install additional software in order to use the tool. The frontend is also platform independent due to the use of standard web technologies.

Though, some disadvantages of web applications, compared to native applications running on a desktop machine, are worse performance and the lack of common features, like file access or custom socket connections. Furthermore browsers are more restricted in terms of available resources and do not allow to allocate arbitrary large amounts of memory. This can become a problem when dealing with visualizations that are created out of large data sets. To mitigate these limitations we introduce a backend, capable of providing this missing features, which is able to communicate and exchange data with the web browser that runs the frontend. 1.4 Structure of the Thesis 4

1.4 Structure of the Thesis

Chapter 2 (Graal Project) outlines the Graal Project and the important parts in the context of this thesis. This includes the Graal Compiler, the basic characteristics of the graph-based Graal IR and the tools currently used for inspecting Graal Compiler internals.

Chapter 3 (Salver Framework) introduces the Salver Framework and outlines the architecture as well as its components. It illustrates the idea behind the framework and covers the basic concepts.

Chapter 4 (Case Study) is divided into two parts. The first part focuses on the Graal Compiler extension, which allows to provide special information for tracing purposes on the Graal IR during compilation. The second part of this chapter deals with the visualization tool for the Graal IR and explains how it consumes and processes the information provided by the compiler.

Chapter 5 (Evaluation) evaluates the extension built in the case study, in terms of perfor- mance and data size.

Chapter 6 (Related Work) discusses similar projects and tools that focus on software tracing, inspection and visualization concerning Graal Compiler internals.

Chapter 7 (Summary) gives an overview of future work that aims to extend the functionality of the framework and the visualization tool. It also lists ideas concerning possible enhance- ments useful for inspecting additional Graal Compiler internals. Finally it concludes with a short summary of this thesis. 5

2 Graal Project

This chapter gives an overview of the Graal Project, especially the Graal Compiler and its high-level intermediate representation used for optimizations.

2.1 Virtual Machine

The Graal VM [1] is a Java Virtual Machine (VM) that extends the Java HotSpot™ VM by another just-in-time (JIT) compiler written in Java. This compiler, the Graal Compiler [2], aims to produce highly optimized code, achieved by performing various optimizations via distinct compilation phases. These optimizations are performed on the Graal IR [3, 4], a graph-based intermediate representation (IR).

In the chapter Case Study we extend the Graal Compiler with basic functionality to gather information during compilation, which we later on use for visualization purposes.

2.2 Graal Compiler

Front End Optimizations Back End

LIR Gen Assembler AMD64

Graph Builder High Tier Mid Tier Low Tier LIR Gen Assembler ARMv8

LIR Gen Assembler ...

Source Graal IR LIR Machine Code

Figure 2.1: The compilation pipeline of the Graal Compiler

The Graal Compiler is divided into three distinct parts, the frontend, the optimizer and the backend. The frontend is responsible for transforming the source into the compiler’s IR, meaning it transforms Java byte code into the Graal IR. The second part, the optimizer, deals with several platform independent optimizations. It is again split into three tiers with each tier dealing with optimization phases of different levels. The high tier is performing high-level optimizations, e.g. constant folding or inlining, whereas the mid tier is applying memory optimizations. The low tier prepares the IR such that it can further be converted into a low-level representation. 2.2 Graal Compiler 6

Throughout the processing of the optimization step, the Graal IR gets changed by these various phases. This is the part we are mostly interested in, as we would like to visualize the IR after each phase to observe the applied changes.

Finally the backend translates the Graal IR into a target specific LIR, which is then used to emit the actual native code after register allocation and peephole optimizations. Figure 2.1 shows the schematic overview of the compilation pipeline within the Graal Compiler [5].

The Graal Compiler can also be used with the Truffle Framework [6, 2] as it provides a frontend that is able to produce Graal IR graphs. The Truffle Framework allows to develop a runtime environment for a wide variety of different languages.

Intermediate Representation

The Graal IR [3, 4] is the high-level IR used by the Graal Compiler to model control-flow and data-flow. It is a graph-based IR that provides an easy way to establish dependencies between nodes using Java annotations. The functionality of these nodes is distinguished by implementing interfaces respectively inheriting bases classes.

An IR graph is essentially an interleaving graph of a control-flow and a data-flow graph, resulting in nodes having input edges as well as successor edges. To represent data-flow, a node has input edges pointing to the nodes that produce its operands, whereas control-flow is represented by nodes having edges pointing to their various possible successors.

cond

If

Begin Begin

End End v1 v2

Merge Add

Phi

Return

Figure 2.2: Example Graal IR graph showing data and control flow edges

The resulting graph of Listing 2.1 is illustrated in Figure 2.2. Red edges represent control- flow whereas blue edges indicate data-flow dependencies. Other associations, like condi- tions, are also modeled via node inputs but are represented as gray edges. 2.2 Graal Compiler 7

if (cond) { res = v1 + v2; } else { res = v1; } return res; Listing 2.1: Example snippet to illustrate data flow and control flow

Data Flow

Data flow is represented by having fields pointing to its operands. These node fields are an- notated with the @Input annotation. Listing 2.2 shows a simple binary AddNode (Figure 2.3) with two operands serving as its inputs.

left right

Add

Figure 2.3: Visual representation of the AddNode class AddNode extends Node { @Input Node left; @Input Node right; }

Listing 2.2: Definition of AddNode with two operands

Control Flow

To represent control flow a node has edges that point to its successors. In the node these edges are annotated with the @Successor annotation. Listing 2.3 shows an IfNode (Fig- ure 2.4) having an input edge for its condition as well as two possible successor nodes for each branch.

cond

If

true false

Figure 2.4: Visual representation of the IfNode 2.2 Graal Compiler 8

class IfNode extends Node { @Input BooleanNode condition; @Successor BeginNode trueSuccessor; @Successor BeginNode falseSuccessor; }

Listing 2.3: Definition of IfNode using @Input and @Successor annotations

Inspection of Compiler Internals

Currently two separate tools are used to view internals of the Graal Compiler. The Ideal Graph Visualizer (IGV) [7] is able to visualize and inspect Graal IR graphs, which allows to trace modifications of the IR during compilation. The IGV receives all relevant information via a socket connection directly from the Graal Compiler as these optimizations are applied. The second tool, the C1Visualizer [8], is used for inspecting low-level information concerning the LIR as well as register allocation. A file containing the relevant information, which can be created separately during compilation, needs to be loaded into the tool.

Graal Compiler

Network File ... Socket

IGV C1Visualizer ...

- Graal IR - Low-Level IR - Register Allocation

Figure 2.5: Tools for inspecting internals of the Graal Compiler

Both tools do not share a common data format and require different workflows to create and load the needed information. Since these tools are likely to stay focused on their cur- rent functionality, additional tools might be needed for the inspection of different internal information, e.g. concerning the Truffle Framework. Figure 2.5 shows an overview of these tools and illustrates how the data is loaded.

See Chapter 6: Related Work for further information on these tools. 9

3 Salver Framework

This chapter introduces Salver, a web framework that aims to simplify the develop- ment of web based tools for tracing software systems. It outlines the architecture and illustrates the idea as well as the concepts of the framework.

Salver is a web framework that was designed with the intention to support developers in building web based tools for inspecting internals of the Graal Compiler. It consists of several independent modules that offer useful functionality for different kinds of use cases, e.g. pro- cessing collected information, serving resources or creating visualizations. The framework supports server-side as well as client-side development and relies on a consistent workflow for tracing different kinds of information.

The server-side part of the framework runs on Node.js [9], a cross-platform runtime environ- ment available for many operating systems including Linux, Windows and Mac OS. It uses Google’s V8 engine, which allows to write backend code in JavaScript, the same language used for developing the frontend running in modern web browsers. Using a single language simplifies development and enables to share code between the backend and the frontend.

The framework tries to rely on standardized language features rather than specific charac- teristics of Node.js and the V8 engine. It is intended to support other runtime environments and engines in the future, especially the Polyglot Engine [10] of the Graal VM or Microsoft’s Chakra [11].

3.1 Architecture

The architecture of Salver can be divided into three distinct parts, each having its own com- ponents for handling different aspects of the workflow. The actual tool built with the frame- work requires a backend part, running on a server, and a frontend part, which is accessible via modern web browsers. The third part is the application being debugged, which is essen- tially the source of information we need for tracing purposes. We call this the trace source. Our Case Study explains the necessary extensions we need to add to the Graal Compiler for serving as a trace source. Figure 3.1 shows an overview of the architecture as well as its components and how they interact with each other. 3.1 Architecture 10

Trace Source Back End Front End

Trace Provider Trace Consumer Resource Controller Resource Viewer

Tool Graal Compiler Salver

Module Module Module Salver Salver 3rd Party Salver Framework

Data Store Browser Resources Client

Java JavaScript

Figure 3.1: Architecture of the Salver Framework

Trace Source The trace source is the software system being debugged, which serves as the source for all necessary information we need for tracing purposes. This system needs to be extended with additional functionality in order to provide the relevant information that can be processed within our backend. We call this information the trace information.

Backend The backend makes up a large part of the actual tool since it contains the com- ponents necessary for consuming and processing the collected trace information. This typically includes data aggregation, transformation and the storage of the resulting data, called trace resources. The backend needs to make these resources available to the frontend and has to serve them on request.

Frontend The frontend contains the components responsible for presenting the resources produced within the backend. It provides the user interface and directly interacts with the backend. The frontend is designed to deal with the minimum amount of infor- mation needed for a particular task. This means for example all resources currently not needed for a desired presentation are just stored within the backend and are only transferred when explicitly requested by the frontend. 3.2 Components 11

3.2 Components

For a unified workflow the framework relies on several individual components. These are responsible for processing and providing trace information respectively resources, which are eventually presented using an appropriate visualization. There are four different compo- nents involved.

Trace Provider The trace provider is part of the trace source and has to provide the neces- sary trace information we need for further processing. It is the task of the provider to transform given information into special messages that can be further transferred to the backend. This messages are called trace messages and are explained in more detail in Section 3.3 Tracing.

Trace Consumer The trace consumer listens for any incoming trace messages and processes them. It is also responsible for creating special resources using these collected mes- sages. This can be a simple mapping by extracting the message’s content or a result of an aggregation comprised of several messages. These so called trace resources, which are explained in Section 3.4 Trace Resources, are then stored respectively forwarded to other components.

Resource Controller The resource controller manages the resources created by the con- sumer. These resources are typically persisted and fetched from a data store but can also be received from the consumer component directly. The controller is also able to communicate with the resource viewer allowing instant notifications and general data exchange between those components.

Resource Viewer The resource viewer, which is part of the frontend, requests resources concerning the desired information that are then visualized. It can also receive re- sources directly from the controller in consequence of incoming trace messages.

3.3 Tracing

We consider tracing to be a special kind of logging, which is used to record different types of information throughout the execution of a software system. This trace information is then typically used for debugging purposes. Depending on the actual information we would like to record, the structure of the resulting data varies. As for the Graal Compiler we are mostly interested in how the Graal IR changes after certain optimization phases, so our trace information needs to model IR graphs at a specific point in time.

Additionally we need to keep track of the currently processed method in order to map IR graphs to their respective methods. This can be achieved by simply logging the relevant information, like a fully qualified name, representing the according methods. Considering only these two kinds of information, we are already required to be able to differentiate between IR graphs and methods, which use different data and diverse structures. 3.3 Tracing 12

As we would like to be able to create, parse and process information of arbitrary structure, we introduce trace messages as a wrapper containing the actual data as well as additional meta information describing the message and its content.

3.3.1 Trace Messages

A trace message encapsulates metadata about the message and the trace data, which is the actual information we would like to preserve for inspection. This guarantees a consistent data structure that can be processed independently of the content or the type of the message. It allows to create trace logs, containing a sequence of messages, that stay parsable even when adding new messages yet unknown to the consumer of the log.

Figure 3.2 shows a trace message that contains data derived from a given object and meta- data created using additional information about that object as well as the current system state. In this case the trace data is directly extracted from the object and models the infor- mation needed for inspecting it later on. Note that once a message is created, its content has to be immutable and should not have any references to the original object, which avoids inconsistencies in case the object changes.

Message Type { Metadata "id": 2342, "name": Context "Graph", Object "nodes": [ Trace Data Time Trace Information ... ], "edges": [ ... ] ... } Trace Message System State

Figure 3.2: Trace message

In order to tell the consumer of a trace log how a particular message has to be processed, it needs to contain a message type. A type defines which data a consumer can expect to be present in the content of the message. Besides the type, which is the only mandatory metadata property, a message is allowed to have arbitrary metadata, e.g. the time a message is created (see Section 3.3.3 Metadata).

{ … } « time » Trace Message

{ … }

{ … } « timeEnd » Trace Message

{ … } ... { … } Trace Log

Figure 3.3: Trace message events

Depending on the message type the trace data can be optional and therefore be omitted. This is the case if messages are used as events, which do not necessary require trace data but are fully defined using only metadata. Figure 3.3 shows an example of messages being used 3.3 Tracing 13 as events. In this case it is sufficient for a message to have only a type and a time property to allow measuring the duration between the time and the timeEnd message.

While trace messages do not specify a distinct representation, most examples throughout this thesis use the JavaScript Object Notation (JSON) [12], a lightweight human-readable text format that allows to represent dictionaries, lists and several primitive value types. This means JSON is considered a valid format that allows to serialize trace messages (see Sec- tion 3.3.4 Serialization).

Data Structure

For trace messages we define a simple data structure with a limited set of value types. This structure does not specify a schema and allows to model information of arbitrary complexity. It is able to hold dictionaries, lists, strings and primitive values like numbers or booleans.

Trace messages itself are just dictionaries with multiple entries defining the trace data as well as the meta information of the message. The trace data is stored in a special entry called @data. All other entries are considered metadata. Predefined or reserved entry names start with an @ symbol, e.g. @msg which defines the mandatory type of the message. Other entry names can be freely used for custom meta information.

Listing 3.1 shows a simple trace message, which contains a Hello World string within the @data entry. The @msg entry defines the message to be of type info. How this message will eventually be handled is totally up to the consumer, which might or might not know how to process it.

{ "@msg": "info", "@data": "Hello World" }

Listing 3.1: Simple trace message of type info

Value Types

As mentioned before, we define several value types that can be used within a trace message respectively within the contained trace data. These are basically common data types typically available in every programming language. Besides primitive values, these types also include composite and abstract data types. As a special case we also allow a value to be null.

The following list shows the allowed types that can be used. As a trace message is just a dictionary we can use any of these to create a valid entry. Any type not listed has to be remodeled and expressed with a different type. 3.3 Tracing 14

„ Dictionary (Map, Object, Associative Array)

„ List (Array, Sequence)

„ String (Character Sequence)

„ Binary (Buffer, Blob, Byte Array)

„ Number (Integer, Floating Point, Decimal)

„ Boolean

„ Null (Nil, None)

Dictionaries and lists are not restricted to contain values of certain types but rather allow any value to be used, which again can be one of these types itself. This allows us to build arbitrary nested structures.

Namespace

A namespace is used to group certain messages and allows to fully qualify a message type in order to avoid possible naming collisions. This also enables consumers to process all messages of a specific namespace without defining the distinct types of these messages.

In general all messages must belong to a namespace, which can be defined as an arbitrary string or a list. When using a string it is suggested to use a unique separator symbol to specify different nesting levels. In case a list is used as a namespace, each item is considered a new nesting level. Message types are resolved using the active namespace of the current trace log, which is set with the first message that starts the log. It is also possible to explicitly define a fully qualified message. Considering the active namespace to be foo:bar, both messages in Listing 3.2 are resolved to the same message type, namely ["foo:bar", "info"].

{ "@msg": "info", "@data": ... } { "@msg": ["foo:bar", "info"], "@data": ... }

Listing 3.2: Resolving trace message types

Global Messages

Global messages are special as they do not belong to any namespace and can appear any- where within a trace log. The type of these messages always starts with a colon (:), which indicates that the current namespace is not used to resolve this type. Typically global mes- sages are ignored by consumers and are solely processed on the application level as they are used to denote special actions. 3.3 Tracing 15

The :begin message is an example for such a global message. It is the first message within a newly created trace log and therefore starts the log by informing the application about upcoming messages and which namespace they belong to.

{ "@msg": ":begin", "@namespace": "foo:bar", "@context": "{exec}" }

Listing 3.3: Global :begin message

„ :begin The begin message is required to create a new trace log that can be processed by con- sumers. This message always has to specify the namespace of the following messages. It also sets the current trace context (see Section 3.3.2 Trace Context) that is further used for all messages within the log that do not explicitly specify their own context. The namespace as well as the trace context are considered metadata and are simply added to the message using special entries as shown in Listing 3.3.

„ :end While this message is optional, it is advisable to explicitly include it in the trace log to inform the consumer that this is the last message that has to be processed. This message can also be used to indicate that the tracing was interrupted and ended unexpectedly. In this case additional information has to be assigned to the message so that the consumer is able to determine the reason of the interrupt.

3.3.2 Trace Context

In general the information we use for tracing is only valid within a certain context. We call this the trace context. It is hierarchically arranged and can be arbitrarily nested to express custom information scopes. The context always involves the current execution of the appli- cation being debugged, which is also the most generic context. It is up to trace providers to define an appropriate trace context allowing consumers to associate information that belongs together.

A trace context is useful for trace providers having a local state that cannot be shared with any other provider. So these providers can safely produce information that should be combined while avoiding the problem of overwriting message information of other providers. It is also helpful for individual instances of the same provider, e.g. in different threads, as they might use a local counter for identifying purposes.

We define this context using the @context meta entry. If the context does not require any nesting it can be identified with a single primitive value, otherwise we use a list. At the top level it should always define the current execution, which is supposed to be unique across all executions. It is recommended to use a random hash value or a high resolution timestamp that could be combined with additional identifying machine specific information.

For example, Figure 3.4 shows an execution resulting in several trace logs provided by dif- ferent instances of trace providers. Although these logs belong together, they are seen as 3.3 Tracing 16

Trace Log Graal IR : 1 Graal IR : 1 Trace Log {exec} / t1 / gir / 1 Trace Provider Trace Provider {exec} / t2 / gir / 1

Trace Log Graal IR : 2 LIR : 1 Trace Log {exec} / t1 / gir / 2 Trace Provider Trace Provider {exec} / t2 / lir / 1

Thread 1 Thread 2

Graal Compiler

Figure 3.4: Trace context of different providers individuals by their consumers because they might use different socket connections. In this example we have two different providers, a Graal IR trace provider and a LIR trace provider, both having a local state. Additionally we have two threads that can instantiate these providers, therefore we require each thread to be in a new trace context. It is also possible that a provider is instantiated several times within the same thread, which might be caused by a failure of the previously used provider. Therefore we need to introduce another nesting level for a new trace context.

With the base context of the execution we require a context nesting level of 4, including the thread, the provider as well as an instance counter. Listing 3.4 shows the trace context for each trace log, assuming {exec} specifies the base context.

Thread 1 Graal IR : 1 => "@context": ["{exec}", "thread-1", "gir", 1] Graal IR : 2 => "@context": ["{exec}", "thread-1", "gir", 2]

Thread 2 Graal IR : 1 => "@context": ["{exec}", "thread-2", "gir", 1] LIR : 1 => "@context": ["{exec}", "thread-2", "lir", 1]

Listing 3.4: Trace context of different providers

3.3.3 Metadata

Metadata allows to enhance the message with additional information that is needed to cor- rectly process the message and its content. Aside from @data, which contains the actual trace data, every property of a message is considered metadata. Property names starting with an at character (@) are reserved and indicate special properties with predefined meanings. Some examples of these special properties are listed below.

„ @msg

The @msg property indicates the type of the current message. It must be included in every trace message. The fully qualified type will be derived using the current namespace. The value is preferable a string containing alphanumeric characters. 3.3 Tracing 17

„ @namespace

Typically this property is set only once along with the global :begin message. It indicates the namespace, which should be used for all following messages.

{ "@msg": ":begin", "@namespace": "foo:bar" }

Although the @namespace property can also be used to fully qualify any message, it is recommended to include the namespace within the @msg property using a list containing the namespace and the type.

{ "@msg": "info", "@namespace": "foo" } { "@msg": [ "foo", "info" ] }

„ @context This property specifies the current trace context in which the message as well as the data is supposed to be valid. Setting this property in the global :begin message results in setting the context for all following messages. Though, the @context property may be set on an individual message to express a different context.

{ "@msg": ":begin", "@context": [ "{exec}", "thread-1", ... ] }

„ @path Assuming that the trace data can almost immediately be transformed into a trace re- source, the @path property can be used to specify the future path to that resource. The path is supposed to be a list of elements which enables the nesting of resources.

{ "@msg": "resource", "@path": [ "foo", "bar", 8 ], "@data": ... }

„ @time

The @time property allows to set the creation time of the message. It does not define any particular type or format so its possible to use a parsable string, a numerical timestamp or anything else that serves this purpose. The consumer of a message needs to be aware of the format being used.

{ "@msg": "time:start", "@time": 623577600 }

„ @note It can be helpful to annotate a message with a short description explaining the intention behind that message. This can be expressed using the @note property.

{ "@msg": "graph", "@note": "After Specialization", "@data": ... } 3.3 Tracing 18

„ @i In some cases it is useful to consecutively number all messages so that they can be uniquely identified within a trace log. If the @i property is required it has to be present in all messages including the first :begin message.

{ "@msg": ":begin", "@i": 0, "@namespace": ... } ... { "@msg": "foo", "@i": 23, "@data": ... }

3.3.4 Serialization

Until now we only defined trace messages to be a flexible data structure that has to sup- port certain value types. In order to persist these messages to a file or transfer them via a network socket we need to serialize them to a printable representation. In general every format that is capable of representing a trace message as we defined it can be used for this purpose. This means all values have to be serializable without any information loss when being deserialized. The JSON format, used throughout this thesis, does not fully support all possible values to be serialized directly but can easily be extended to model non-supported values using EJSON.

JavaScript Object Notation (JSON)

The JavaScript Object Notation (JSON) [12] is a lightweight human-readable text format that was originally introduced to serialize JavaScript objects. It supports different types of values like objects and arrays as well as common primitives, like strings, numbers, booleans and the null value. Due to its simplicity and its small footprint JSON has become a widely used format for data exchange used within any kind of web related application.

Although JSON covers most use cases it lacks of possibilities to represent certain data types. This includes values that should be parsed as binary and numbers exceeding a certain size or accuracy.

Binary It’s not directly possible to include binary data within a JSON representation because its not explicitly specified. This is not necessarily a problem since we can express binary as a printable string, e.g. as hexadecimal values. Though this transformation step needs to be known when parsing the data. The knowledge about this step would be important to regain a binary value, so it might not always be suitable to use this approach. 3.3 Tracing 19

Long Integer JSON does not necessarily specify a maximum length on numbers so technically we can express numbers with arbitrary size. The problem is that some parser implementations do not parse the number correctly without losing information. For example JavaScript currently does not support numbers with the full range of long integers resulting in double values with rough precision.

Extended JSON (EJSON)

We can use Extended JSON (EJSON) [13] for particular types not supported by JSON. The format retains backward-compatibility by introducing custom types represented as objects having special properties. To indicate that an object is an extended value its first property starts with a dollar ($) sign. These values are handled and transformed before the actual data is processed.

Among others EJSON introduces the $binary property and allows to define custom types using the $type and $value properties. Listing 3.5 shows a binary value and a user-defined long integer value.

{ "data": {"$binary": "+o+gFDtU6NfGWw=="}, "max": {"$type": "long", "$value": "9223372036854775807"}} }

Listing 3.5: EJSON example

Universal Binary Format (UBF)

Although JSON is considered lightweight in terms of file size, it still has overhead compared to binary formats in some cases. The Universal Binary Format (UBF) [14], drafted in this thesis, is a modular binary format designed for data interchange. It is similar to JSON but with full support of all possible value types defined for trace messages. Besides the base module, which is required for basic support, several other modules are defined to extend the format with additional functionality.

The constant pool module allows to store and retrieve any kind of value or property name by allowing to use reference IDs that represent the actual values. Considering lots of repeating values, like longer strings or even more complex dictionaries, this saves a large amount of space respectively bandwidth.

Another advantage of a binary format like UBF is that it allows for faster parsing, e.g. num- bers don’t need to be parsed from text but can be read directly. Also in case of UBF every value is associated with a byte size allowing to preallocate the needed space, check available data for completeness before parsing or simply skip values. 3.4 Trace Resources 20

3.4 Trace Resources

Once trace messages are processed by the consumer the resulting data is supposed to be a trace resource. Resources are intended to be ready for visualization, meaning the viewer should be able to display its information with very low effort on further processing it. Addi- tionally resources should be as small as possible so that they only contain information that is essential for a particular visualization. The viewer is able to request additional resources if needed at any time. This reduces the amount of data the viewer has to handle and allows it to focus on user interaction and appropriate rendering. The way resources should be served or queried is not specified and is up to the controller respectively the viewer to decide.

We differentiate between two approaches of creating trace resources, involving either a single or multiple messages.

3.4.1 Extracted Resources

The default approach for creating resources is to directly derive the necessary information from the object we would like to inspect and encapsulate that information within a trace message. That information can then be simply extracted and stored as a trace resource. This approach does not require additional messages to be processed and should be used whenever the resource can already be generated within the trace source.

3.4.2 Generated Resources

Generating a trace resource using multiple messages is needed if a single message does not provide enough information to derive a useful resource. This can be the case when messages are used to provide partial information via events by the time an action occurred. Listing 3.6 illustrates such an example, showing several messages each containing only very sparse data.

{ "@msg": "node:create", "@data": { "id": "A" } } { "@msg": "node:create", "@data": { "id": "B" } } { "@msg": "node:connect", "@data": { "from": "A", "to": "B" } } Listing 3.6: Multiple messages containing only sparse data

Although each of these messages might be derived into a totally valid resource on their own, the intention is different. What we are expecting after collecting these messages is to have a resource that represents a graph as described by these actions. So the consumer needs to process these messages and generate an according resource, which might look as shown in Listing 3.7.

{ "nodes": [ { "id": "A" }, { "id": "B" } ], "edges": [ { "from": "A", "to": "B" } ] } Listing 3.7: Generated resource from multiple messages 3.5 Workflow 21

3.4.3 Resource Path

Each resource needs to have a path and has to be uniquely identifiable within a collection. Uniqueness is achieved by combining the path and the trace context of a resource, which allows to safely generate the path up front within a trace provider. Both the path and the trace context are hierarchically organized, which allows arbitrary nesting of individual re- sources.

Hierarchically Organized

Every resource regardless of its type or structure can serve as the parent of other resources. This allows to store each resource as an individual document that can be seen as a standalone entity.

{ "@path": [ "foo" ], "@data": { "type": "group", ... } } { "@path": [ "foo", 0 ], "@data": { "type": "item", ... } } { "@path": [ "foo", "bar" ], "@data": { "type": "group", ... } } { "@path": [ "foo", "bar", 0 ], "@data": { "type": "item", ... } } { "@path": [ "foo", 1 ], "@data": { "type": "item", ... } } { "@path": [ "foo", 1, 0 ], "@data": { "type": "item", ... } }

Listing 3.8: Hierarchically organized trace resources

This approach allows us to select resources by initiating a query containing the requested path as well as the minimum and maximum allowed depth of the resource path. On condition that this path is unique, we are able to select a single or possibly multiple resources by restricting the depth range accordingly. Listing 3.9 shows such a selection in pseudo code using the resources listed in Listing 3.8. The result of this query are two resources, a group and an item, both having a path containing the specified path of the query.

> query({ "@path": [ "foo", "bar" ] }, { "depth": { "min": 2 } }) { "@path": [ "foo", "bar" ], "@data": { "type": "group", ... } } { "@path": [ "foo", "bar", 0 ], "@data": { "type": "item", ... } }

Listing 3.9: Pseudo code selection of multiple resources

3.5 Workflow

For a better understanding of the proposed workflow we discuss the involved components while explaining the steps needed for creating trace messages and transforming them into resources, which are eventually visualized. Since this thesis mainly focuses on inspecting the graph-based Graal IR we chose a simplified scenario for our sample workflow.

Our simple compiler optimizes our code using a graph-based IR, which is modelled via ob- jects containing node properties and references to establish connections between the nodes. Another limitation of our compiler is that it is only capable of compiling and optimizing 3.5 Workflow 22 one method at a time. Our goal is to get a graphical representation of the IR after each optimization step.

Trace Provider

We assume we can use a that provides appropriate data structures and handles the serialization of our trace messages. This library also needs to be capable of sending these messages to the server that is hosting our application used for inspection.

The trace provider needs to be a part of our compiler. It is responsible for processing in- formation and creating the data we will later use for visualization and inspection. First we need to extract the necessary data out of our IR, which is essentially a graph consisting of nodes and edges. Listing 3.10 shows a sample of such an extracted graph information.

{ "nodes": [ { "id": 0, "name": ... }, { "id": 1, "name": ... }, ... ], "edges": [ { "from": 0, "to": 1 }, { "from": 2, "to": 5 }, ... ] }

Listing 3.10: Sample of an extracted graph information

Together with additional metadata this will form our trace message. We need to define the type of the message in order to be able to specify how the message should be processed by the consumer. We also add a @note meta property to specify a short description, which is in our case the previously applied optimization. Listing 3.11 shows a trace message that is ready to be processed.

{ "@msg": "graph", "@note": "After Parsing", "@data": { "nodes": [ ... ], "edges": [ ... ] } }

Listing 3.11: Sample trace message containing graph information

We repeat this procedure for every optimization we want to trace respectively visualize and inspect, as illustrated in Figure 3.5.

Metadata Trace Message

Object Trace Data Trace Data ...

Trace Message Trace Log

Figure 3.5: Trace message containing the extracted data 3.5 Workflow 23

Trace Consumer

The trace consumer component is already part of our inspection application and belongs to the backend. This component can rely on all backend compatible modules provided by the framework. We utilize that to be able to register for a particular trace log we are interested in and listen for upcoming messages.

The consumer then needs to produce appropriate trace resources using the collected trace messages. In our case, these can be directly extracted from the messages. Rather than persisting these resources in a data store (Figure 3.6), we simply forward them directly to our resource controller. This is reasonable since we are only interested in the current execution and do not want to look at previously created resources. Additionally we expect the data sets to be rather small, which means other components are supposed to handle the data completely.

Trace Message Trace Resource

... Trace Resource ...

Trace Log Data Store

Figure 3.6: Trace resource as a result of trace messages

After consuming and processing several messages, the resulting trace resources might look as shown in Listing 3.12. The graph property of our resource document simply holds the trace data of the messages without any modification. Additionally the @note meta property of the message becomes the title property of our resource.

{ "title": "After Parsing", "graph": { "nodes": [ ... ], "edges": [ ... ] } } { "title": "Canonicalizer", "graph": { "nodes": [ ... ], "edges": [ ... ] } } { "title": "Inlining", "graph": { "nodes": [ ... ], "edges": [ ... ] } } { "title": "Partial Escape", "graph": { "nodes": [ ... ], "edges": [ ... ] } } { "title": "Schedule", "graph": { "nodes": [ ... ], "edges": [ ... ] } }

Listing 3.12: Resulting resources after processing several messages

Resource Controller

Once all relevant resources are created and accessible they can be managed by the resource controller. It is responsible for communicating with frontend components which can be in- formed as soon as new resources are available. The controller also has to deal with handling resource requests as well as serving the resources.

In our case we simply forward all resources directly to the frontend and let the according resource viewer deal with them. This is suitable since the amount of data we expect is easily manageable within a frontend component that runs in the browser. Also the data does not 3.5 Workflow 24 need to be filtered or transformed in any way meaning it can most probably be used directly within a visualization.

Resource Viewer

The step is to actually visualize our previously created resources. This is part of the frontend and is done by the resource viewer. As previously mentioned the viewer receives all resources as they are produced via the resource controller.

Sample Method 3 : Inlining

1 : After Parsing

2 : Canonicalizer

3 : Inlining

4 : Partial Escape

5 : Schedule

Figure 3.7: Resource viewer showing a graph visualization and a list of resources

What we would like to achieve is to have a simple overview of the various forwarded graph resources, which should be individually selectable to show a visualization of the IR graph after a particular optimization step. Figure 3.7 illustrates the expected outcome. 25

4 Case Study

This chapter presents a case study that demonstrates how to use the framework introduced in this thesis. It shows how to extend the Graal Compiler with additional functionality to provide the relevant information. We also take a look at our tool specifically designed to process and visualize the gathered information.

4.1 Graal Trace Provider

In order to use the Graal Compiler as a source for our trace information we need to extend its behavior when being debugged. As for this thesis we are mostly interested in Graal IR graphs and how they change after different optimization phases. The compiler already provides functionality to dump IR graphs with a description specifying the currently applied specialization phase. These instructions can be found all over in the compiler code, allowing us to trace the transformations of these graphs throughout the compilation pipeline. This is automatically enabled when the compiler runs in debug mode.

Within the compiler these IR graphs are represented as Java objects. The actual processing of these dumped graph objects is done by so called dump handlers, that have to be registered in the compiler’s debug configuration. Currently the only handler that is able to process Graal IR graphs is doing so to forward them to the Ideal Graph Visualizer (IGV) [7] (see Section 6 Ideal Graph Visualizer) for inspection. It is not possible to reuse this handler as is, because we would like to include additional information currently not available. As we need to retain compatibility to IGV,extending this handler is also not an option.

In general the existing dump handler follows a completely different approach as opposed to the one introduced in this thesis. This means we need to create a new dump handler that is able to provide all necessary information via trace messages.

Debug Configuration

The Graal Compiler provides a simple way for us to add new functionality without the need of bringing additional dependencies into the project. It does this by allowing to register service providers that are able to enhance its debug configuration. This means we get to handle any object that is being dumped somewhere in the code and can decide whether to process it or not. 4.1 Graal Trace Provider 26

Overview

The intention is to introduce a general mechanism to dump arbitrary objects and either save them or forward them for further inspection. We need to be able to extract the relevant in- formation out of objects we are interested in and create a printable representation in order to further process the data outside of the compiler. As we intend to offer a generic solution that is applicable to different use cases we introduce a pipeline with exchangeable compo- nents for each part of the pipeline. Figure 4.1 illustrates this approach. The figure shows how IR graphs or potentially arbitrary objects are processed by these components so that we can write them to a channel, which is either a file or a network socket.

Object IR Graph

DumpHandler Dumper Serializer Writer

Channel Socket / File

Figure 4.1: General trace pipeline

Dump Handler The main purpose of the dump handler is to only decides whether an object can, re- spectively should be dumped or not. If so the object is forwarded to the dumper. The handler is also responsible for managing an associated dumper as well as for initializ- ing one if none is currently available. It is also responsible for initializing the dumper with an appropriate serializer and setting the writer. This is done as soon as the first object is about to be processed.

Dumper The dumper is doing the actual work of extracting the information out of the given object. It produces temporary internal data which it then forwards to a serializer.

Serializer The serializer takes the data produced by the dumper and creates a printable repre- sentation out of it, which is then forwarded to the writer.

Writer After the data is serialized the writer prints everything to an open channel, which is either a file or a network socket. It is also possible to omit the serialization step and use a writer directly from within a dumper. 4.1 Graal Trace Provider 27

4.1.1 Graal IR Data

Binary Graph Protocol

As mentioned earlier the Graal Compiler already allows to dump different kinds of infor- mation, which also includes graphs describing its IR at a certain point in the compilation pipeline. These graphs can be dumped to files or network sockets using the Binary Graph Protocol. Besides graphs it is also possible to include arbitrarily nested groups, mainly used for grouping together graphs of different optimization phases that belong to a particular method.

The Binary Graph Protocol is very efficient when it comes to file size but it has some lim- itations concerning extensibility and the way the data can be consumed. One problem is that the format specifies no byte length on its values and defines no distinct terminal byte markers, making it hard to parse in single threaded environments. While this seems to be no problem when dealing with the whole data set, it can become one when processing only chunks of this data. Detecting if a series of chunks is already fully parsable might be compli- cated when no such information is provided. This can result in an expensive parsing attempt with possible side effects that have to be restored on failures.

Another drawback of the format is that although it allows to add custom properties at certain positions, it is not flexible enough to add custom information without breaking the current parser of IGV.Therefore we use the data structure introduced in the previous chapter, which allows us to create different representations that can be used for storage respectively data interchange.

Graph Structure

In order to include IR graphs within our trace messages we need to be able to serialize their content. Therefore we need to traverse the graph and extract all nodes and edges as well as the class information. During this step we have to build up our data structure using only value types we defined earlier, like dictionaries, lists or primitives. We define our graph structure as a dictionary having lists of classes, nodes, edges and blocks. We use this representation to allow a compact structure with little redundancies while still retaining a structure readable and comprehensible by humans. Figure 4.2 illustrates the the connections between the individual elements. The dotted lines represent indirect connections that can be resolved via the target node class. This means additional knowledge about fields and properties can be inferred via the meta information of an associated node class. 4.1 Graal Trace Provider 28

Value

Property Meta Info Next

Edge Field Node Class Block

Parent

Figure 4.2: Data structure for IR graphs used within trace messages

Node A node contains only node specific information, namely the class it belongs to as well as the actual values of its properties.

Edge An edge defines a link between two nodes. The meta information of an edge, like its type, can be retrieved via the classes of the connected nodes. Edges can either directly link to a node or via a specific field.

Class General information about nodes which is not instance specific, like its type, is stored separately in a class. A class can be shared with several nodes of the same kind, so it reduces the amount of redundant data, that otherwise would be stored within the node itself. A class can also have another parent class from which it derives additional information.

Block Nodes can be grouped into blocks to show an overview of the control flow, which is especially useful for larger graphs. Dependencies between blocks are defined via nodes that belong to these blocks. Additionally blocks define their successors explicitly.

Data and

Data-flow and control-flow between nodes is expressed using annotated fields within the Java classes. This means nodes can have several special fields containing references to other nodes, which are either declared as inputs (data-flow) or successors (control-flow). Con- sidering this characteristic we can easily combine data-flow and control-flow into one in- terleaving graph. Nodes in this graph have inputs and successors specifically assigned to distinct ports, which are analogous to their fields defined in the Java classes. These ports provide all information we need to identify the type of the edge connecting the two nodes. The referenced node is not connected via a port but directly via the node itself.

Although it would be sufficient to use the field for identifying edge types via the meta in- formation from the node class, we can utilize this knowledge to get a more intuitive edge representation. So we can simply swap the direction of the input edges and allow data flow 4.1 Graal Trace Provider 29

Data Flow Control Flow

value next

from to from to

Figure 4.3: Edge representation to discriminate data-flow and control-flow edges to point from the source node to its consumer. This allows to quickly discriminate data- flow and control-flow edges just by looking at the edge without requesting meta information from the class. Figure 4.3 illustrates this approach.

Data Flow Data flow is represented as an edge pointing from the source node to the input port of the node that is consuming it. Within our trace data this would be represented as shown in Listing 4.1.

{ "from": 4, "to": {"node": 8, "field": "value"} }

Listing 4.1: Data-flow edge representation within trace data

Control Flow Control flow is represented as en edge pointing from the output port of the node to its successor node. We would represent that as shown in Listing 4.2..

{ "from": {"node": 15, "field": "next"}, "to": 16 }

Listing 4.2: Control-flow edge representation within trace data

If multiple edges are pointing from or to a port we can add an index to the edge in order to specify the node’s exact position within the node list (Listing 4.3.).

{ "from": 15, "to": {"node": 42, "field": "values", "index": 0} } ... { "from": 23, "to": {"node": 42, "field": "values", "index": 4} }

Listing 4.3: Representation for multiple edges with index for ordering

Example Listing 4.4 shows an example of a very simple graph (Figure 4.4) containing only two nodes which are connected via the next field of the StartNode.

Start

next Return

Figure 4.4: Simple IR graph with two connected nodes 4.1 Graal Trace Provider 30

{ "graph": { "classes":[ { "id": 5, "name": "Start", "category": "Begin", "parent": 4, "jtype": "com.oracle.graal.nodes.StartNode", "properties": { "stamp": { "jtype": "com.oracle.graal.compiler.common.type.Stamp" } }, "successors": { "next": { "jtype": "com.oracle.graal.nodes.FixedNode" } } }, { "id": 7, "name": "Return", "category": "ControlSink", "parent": 6, "jtype": "com.oracle.graal.nodes.ReturnNode", "properties": { "stamp": { "jtype": "com.oracle.graal.compiler.common.type.Stamp" } }, "inputs": { "result": { "jtype": "com.oracle.graal.nodes.ValueNode", "type": "Value", "isOptional": true }, "memoryMap": { "jtype": "com.oracle.graal.nodes.memory.MemoryMapNode", "type": "Extension", "isOptional": true } } } ..., ], "nodes":[ { "id": 0, "class": 5, "properties": { "stamp": "void" } }, { "id": 1, "class": 7, "properties": { "stamp": "void" } } ], "edges":[ { "from": { "node": 0, "field": "next" }, "to": 1 } ] }}

Listing 4.4: Simple IR graph with two connected nodes

4.1.2 Trace Messages

Data Structure

For the concrete implementation of trace messages in Java we first need to define which types we can use for modelling our data. Most primitives can be directly mapped to the value types we introduced for trace messages. For composite types like dictionaries and lists we support objects that implement Java’s collection type Map respectively List.

For simplicity we also introduce two additional concrete collection types, namely DataDict and DataList. We do this to facilitate the use of collections by removing the unneeded genericity of their base classes. It also helps to identify objects that were explicitly created for tracing purposes and allows to further extend these types with additional functionality. Note that both DataDict and DataList are also ordered collections.

DataDict is used as our default type for dictionaries, including trace messages itself. It is essentially a LinkedHashMap that allows values of any type. Although basically there is no restriction on the type of the key it eventually has to be converted to a string during serialization. For some use cases the order of the entries might be an important characteristic therefore DataDict preserves the insertion order. 4.1 Graal Trace Provider 31

DataList is intended to be the default type for lists with arbitrary values of any type. This includes primitives as well as collection types like dictionaries and lists. Analogous to the dictionary type we defined before, DataList preserves the insertion order of its items.

The following list shows all supported value types that can be used to create data, which can be used within trace messages.

„ Abstract Data Types

Map (DataDict), List (DataList), CharSequence (String)

„ Primitives

byte, short, int, long, float, double, boolean, char

„ Null Reference

null

If a given value is not compatible to any type in this list it has to be converted to a string during serialization, which is done by calling the String.valueOf method. If this is not the desired behavior one has to explicitly convert the value to one of the listed types. For example a Set is not a valid type to be allowed within our data structure but can simply be transformed to a list. For arbitrary data types that can not be transformed directly it should suffice to be able to remodel them with custom dictionaries.

Messages

For dumping Graal IR graphs we use the graal.graph namespace and introduces the fol- lowing trace messages.

„ method (graal.graph)

The method message notifies of a new method that is about to be compiled. It contains basic information like the method name as well as an internal id. The resulting method resource serves as a group for the graphs produced by the compilation phases. The message also contains a path for the future resources, which allows to build up the nesting of methods and graphs.

„ graph (graal.graph)

The graph message contains the actual data about the IR graph that was produced in the current compilation phase. The graph information can be directly extracted from the message and assigned to a method using the attached path.

„ class (graal.graph)

The class message is used to encapsulate the information of node classes. Compared to node properties, this information is not changed by any compilation phase, so we can reuse that data for multiple graphs. 4.1 Graal Trace Provider 32

Trace Context

The trace context is needed to define a scope in which the generated information is valid. This is important because we already create path information of future resources in our dumper, which can not be guaranteed to be unique when using multiple instances or threads. When parsing the messages it can be used to link together messages belonging to a single execution.

For identifying the current execution of the compiler we generate a random hash of eight characters using base58 encoding, e.g. dK72Ht3P. A compilation can consist of several threads so we need to include a thread identifier as well, e.g. main or compiler-thread-4. Additionally we have to consider multiple instances of the same dumper, thus we insert an- other level of context, which simply increments a counter for each instance. This is only valid if we assume that different dumpers also use different namespaces.

The trace context is added as a special @context property to the :begin message as shown in Listing 4.5.

{ "@msg": ":begin", "@context": [ "dK72Ht3P", "main", 0 ], "@namespace": ... }

Listing 4.5: Begin message specifying the trace context

4.1.3 Implementation

This section describes the com.oracle.graal.salver package, which is already part of the Graal Compiler. It contains all relevant components and functionality needed to create trace messages as well as to forward them to a server that is capable of consuming and processing them accordingly.

Options

The package offers several debug options (see Listing 4.6) exposed to the Graal Compiler. These options allow to generally enable the use of trace messages and to set the remote address of the server, which is consuming these messages. We can also decide to directly write all messages into files as opposed to transfer them to a server. Additionally we can chose whether to use a text or a binary format for serializing messages. class SalverOptions { @Option(help = "Enable dumps with trace messages", type = Debug) OptionValue Salver = new OptionValue<>(false);

@Option(help = "Network address", type = Debug) OptionValue SalverAddress = new OptionValue<>("127.0.0.1");

@Option(help = "Network port", type = Debug) OptionValue SalverPort = new OptionValue<>(2343); 4.1 Graal Trace Provider 33

@Option(help = "Dump to files as opposed to send trace messages via sockets", type = Ðâ ãÑ Debug) OptionValue SalverToFile = new OptionValue<>(false);

@Option(help = "Dump trace messages in binary format", type = Debug) public static final OptionValue SalverDumpBinary = new OptionValue<>(false); }

Listing 4.6: Salver debug options

Debug Configuration

A ServiceProdvider can be used to inform the Graal Compiler that we would like to cus- tomize its configuration when running in debug mode. This allows us to register additional dump handlers to process any object that gets dumped during the compilation process. As for this thesis we are interested in handling all occurrences of IR graphs, so we need to register a new dump handler for that purpose, see Listing 4.7.

@ServiceProvider(DebugConfigCustomizer.class) public class SalverDebugConfigCustomizer implements DebugConfigCustomizer {

public void customize(DebugConfig config) { if (Salver.getValue()) { config.dumpHandlers().add(new GraphDumpHandler()); } } }

Listing 4.7: Customize the debug configuration via a service provider

Dump Handler

A dump handler has to implement the DebugDumpHandler (Listing 4.8) interface, which defines a single method allowing it to accept any object as well as a description message useful for further processing. interface DebugDumpHandler { void dump(Object obj, String msg); }

Listing 4.8: Definition of the DebugDumpHandler interface

Typically a dump handler does not simply accept every object but rather focuses on a special type. It checks if the given object is an instance of a certain type or in general if it is valid for further processing by its associated dumper. In our case this is a Graal IR Graph. So the dump method of our handler has to look similar to Listing 4.9. 4.1 Graal Trace Provider 34

@Override public void dump(Object obj, String msg) { if (obj instanceof Graph) { // Dump IR Graph } }

Listing 4.9: Dump method of a dump handler for Graal IR graphs

The GraphDumpHandler is our implementation of a DumpHandler, which is able to deal with a given object that is an instance of a Graal IR Graph.

Dumper

The purpose of a dumper is to process the object that should be dumped. It extracts all relevant information in order to produce data that is considered serializable. A dumper does not necessarily need to adhere to a specific interface. It is up to the dump handler to know how to use the dumper.

For our implementation we define the GraphDumper, which is able to process a given Graal IR Graph. The GraphDumper supports extracting nodes including their associated edges and the node’s class information. It is also able to resolve the currently processed method to which the given IR graph belongs to.

Serializer

In order to be able to write the data produced by dumper we need a Serializer (List- ing 4.10). A serializer can transform given data into any representation that allows seri- alization of our data structure without data loss. It must provide a single method which accepts any object which is then serialized. The transformed data is then forwarded to an associated writer. interface Serializer extends Flushable { Serializer serialize(Object obj) throws IOException; }

Listing 4.10: Definition of the Serializer interface

Currently we support two serializers that differ in the resulting output format, which is either a textual or a binary representation.

„ JSONSerializer

The JSONSerializer transforms a given data object into a textual representation us- ing the JavaScript Object Notation (JSON). Primitive values and strings are mapped to their equivalent types. A collection type, like dictionary or list, is mapped to an object respectively an array. 4.2 Graal IR Visualization Tool 35

„ UBFSerializer

The UBFSerializer is used to provide a binary representation of the given data object using the Universal Binary Format (UBF).

Writer

In order to write the serialized data we introduce the DumpWriter (Listing 4.11) interface. A writer implementing that interface allows to write binary data, strings as well as several primitives. Typically a writer is used by a serializer, but it is also possible to directly use it via a dumper. interface DumpWriter extends Closeable, Flushable, AutoCloseable { DumpWriter write(byte b) throws IOException; DumpWriter write(byte[] arr) throws IOException; DumpWriter write(ByteBuffer buf) throws IOException; DumpWriter write(CharSequence csq) throws IOException; ... }

Listing 4.11: Definition of the DumpWriter interface

Our implementation defines the ChannelDumpWriter, which is able to write the given data to any WritableByteChannel. This channel typically represents a file or a network socket.

4.2 Graal IR Visualization Tool

This section covers the development of the Intermediate Representation Visualizer (IRV), a visualization tool for inspecting the graph-based Graal IR. The goal of this tool is to provide similar functionality as the Ideal Graph Visualizer (IGV) (see Section 6 Ideal Graph Visualizer), which is currently the default tool used for inspecting Graal IR graphs. As this is a case study we only implement a subset of features to show the feasibility of a web based inspection tool.

4.2.1 Architecture

The tool consists of a back end and a front end. It supports processing trace messages as well as visualizing the resulting resources. Figure 4.5 illustrates the architecture of the tool and gives an overview of the involved components.

The back end consists of a trace consumer and a resource controller component, which deal with the incoming trace messages respectively the resulting graph resources. Additionally the back end includes a data store to persist the generated graph resources. The front end only consists of a single component, the viewer, which presents the graph resources to the user. 4.2 Graal IR Visualization Tool 36

Backend Frontend

Graph Trace Messages IRV Trace Consumer Filter Layouter Graal IR Graal IR Data

Graph List Graph View Layout Graph IRV Resource Controller Web Component Web Component Data Resource Graph Resources IRV Viewer Web Component

Graph Resources Graphs Data Store Resources

Figure 4.5: Architecture of the IRV plugin

IRV Trace Consumer

The trace consumer registers for messages that belong to the graal.graph namespace, as we defined them earlier. It then extracts the method and graph resources out of these messages and stores them in our data store. It further notifies the resource controller about newly available resources.

IRV Resource Controller

The resource controller is responsible for serving the graph resources requested by the front end, respectively the viewer. It supports querying multiple resources and allows to apply basic filtering.

IRV Viewer

The viewer component, which is part of the front end, handles the requested graph resources and provides an user interface for selecting individual graphs. Basically it consists of two individual subcomponents, a graph list and a graph view.

Graph List Once all graph resources are fetched from the back end they are added to the graph list. The graph list provides a hierarchical list of all available graphs, which can be selected for further inspection.

Graph View The actual visualization of the current graph is done within the graph view component. It gets updated as soon as a new graph is selected in the list. 4.2 Graal IR Visualization Tool 37

4.2.2 Functionality

The following list outlines the basic functionality our tool offers so far.

Figure 4.6: Screenshot of the Intermediate Representation Visualizer (IRV)

„ Graph Visualization The visualization of the IR graphs is certainly the most important part as it provides a visual overview of the current state of the IR graph. The tool tries to render a graph that is similar to the one in IGV. It does this by using a slight variation of graph layout algorithm used within IGV.Figure 4.6 shows a screenshot of a rendered graph within the viewer component.

„ Node Inspection Each node offers way more details as opposed to what is actually shown in the view. The tool allows to inspect each node by selecting it. The idea is to display the additional information within pop ups or overlays using contextual information of the current node selection as well as its surroundings.

„ Search & Filter Another important aspect of our visualization is to allow searching and filtering within a given data set. Searching for nodes results in a list that can be directly linked to visual representations within the view. This allows to adjust and center the viewport accordingly to reflect the current node selection. Filtering allows to reduce the number of nodes by specifying a certain criteria, which is used to focus on different aspects of the graph. This is done by building a temporary subgraph containing only nodes which fulfil that criteria.

„ Node Coloring For a better overview it is helpful to allow colors to be used for different types of nodes. Similar to filters the tool offers a way to apply certain properties to individual nodes. Currently this functionality is used to assign a color depending on the category of a node. 4.2 Graal IR Visualization Tool 38

Currently missing functionality and possible future features are discussed in Section 7 Future Work.

4.2.3 Trace Messages

The IRV tool can handle all messages defined in the graal.graph namespace, as we defined them in the previous section (see Section 4.1 Graal Trace Provider).

„ method

„ graph

„ class

Currently the tool uses MongoDB [15] as its data store and persists each resource as an individual document. It uses the attached path to model a hierarchical structure across these documents within the same collection.

4.2.4 Graph Resources

Once a graph resources are loaded into the front end we need to create separate graph objects by using the information we obtain from these resources. Graphs can also have subgraphs with a reduced set of nodes depending on the applied filters or selections. Besides these graphs we also require graph layouts to store the node positions calculated by the layout algorithm. Figure 4.7 illustrates the connections between those objects.

Original Graph

equal

N 0 Filter N 0 L 0

Selection Node N 1 N 2 N 1 L 1

N 3 N 3 L 3

Graph Subgraph Graph Layout

Figure 4.7: Graph, Subgraph and Graph Layout

Graph

A graph basically offers the same data as the resources we created in the back end. The difference between the raw resource and the object we use within the front end is that it provides graph specific functionality as well as cross-references between its elements. 4.2 Graal IR Visualization Tool 39

Subgraph

A graph can actually be a subgraph of the original one, which means they share nodes and edges but typically contain only a subset of the elements of the original graph. A subgraph can be created by applying filters and selections.

Filter A filter allows to reduce the number of nodes or edges with a subset of the original graph by specifying a certain criteria that has to be fulfilled. This is useful if some nodes currently not relevant to the user should be removed in order to improve the graph layout. For example, using a criteria might filter out nodes of a certain type or those that do not belong to the control flow.

Selection Using a selection allows to build a subgraph based on certain nodes that are typically manually selected by the user. This can be used when a focused view of just a couple of nodes is required.

Graph Layout

The graph layout is a special data structure that encapsulates the graph object and allows to set node positions without modifying properties of the original nodes. This enables to reuse a particular graph within several different views or temporary subgraphs.

4.2.5 Visualization

Data Flow and Control Flow

Our tool offers a different approach to draw edges between nodes. We assign distinct sides of a node to either data-flow or control-flow edges. A data-flow edge is allowed to point from the right side of a node to the top side of a node. For the control-flow we use the bottom and the left side. So we use the right respectively the left side when referencing the node directly rather than via a specific port. That allows to easily identify the type of an edge just by looking at the connection points. Figure 4.8 illustrates this approach.

Data Flow Control Flow

Figure 4.8: Data-flow and control-flow edge directions 4.2 Graal IR Visualization Tool 40

Input and Successor Node Lists

If a field does not only reference a single node but a list, we still display this field as a single port within our visualization. This means the port is shared between the edges pointing to the referenced nodes as illustrated in Figure 4.9.

Figure 4.9: Multiple nodes share an input port via a node list

This allows for a clearer layout especially if the list contains lots of nodes. As we loose the knowledge of the order within this list, we need to provide the ordering via contextual information shown when the regarding port is select.

Layout Algorithm

In order to achieve a graph visualization that is comparable with the one IGV offers, we use a slight variation of its layout algorithm. IGV uses a hierarchical layout algorithm [7] that was specifically adapted to provide a clear layout for Graal IR graphs. It is also optimized to be fast and can handle a layout calculation for several thousand nodes efficiently.

The algorithm is ported from the original Java implementation to run in modern browsers with only a few modifications. The following list describes the basic steps of the layout algorithm illustrated in Figure 4.10.

„ Build Data Structure The layout algorithm uses a lightweight data structure to represent the graph and its elements. This data structure keeps a mapping to the original nodes and edges which is important to be able to write back calculated node positions.

„ Remove Cycles The algorithm expects the graph to not contain any directed cycles. Therefore if the graph contains any we need to reverse the edges while retaining that information in order to be able to correct the direction in the last step of the algorithm.

„ Assign Layers This step assigns a layer to each node. All nodes in the same layer are also expected to be in the same row when rendered.

„ Create Dummy Nodes Further steps require that a node only points to another node that is located in a layer directly above or beyond. For longer edges we need to introduce intermediate dummy nodes at each layer between the connected nodes. 4.2 Graal IR Visualization Tool 41

„ Assign Y-Coordinates Based on their layers, this step assigns the y-coordinate to all nodes.

„ Reduce Edge Crossings In this step all nodes within a layer are reordered such that the number of edge crossings is reduced.

„ Assign X-Coordinates By retaining the ordering constraints of the previous step we assign the x-coordinate to all nodes.

„ Write Data In the last step we write the layout information into our graph layout, which is connected to the original graph in order to be able to render it accordingly.

Build Data Structure Remove Cycles Assign Layers Create Dummy Nodes

5 5 1 1 3 3

2 3 2 3 1 1 4 4

5 4 5 4 2 2 6 6

6 6

Assign Y-Coordinates Reduce Edge Crossings Assign X-Coordinates Result / Write Data

1 1 1 1

2 3 2 3 2 3 2 3

5 4 4 5 4 5 4 5

6 6 6 6

Figure 4.10: Basic steps of the layout algorithm

Rendering

Rendering a large amount of elements can easily become a bottleneck in web browsers. Especially for IR graphs that can contain several thousand nodes and edges this can result in performance problems when simply rendered as is.

The viewer component of IRV mainly uses SVG [16] for rendering all elements in the graph. This is because SVG seamlessly integrate into the DOM [17] and offer a convenient way of 4.2 Graal IR Visualization Tool 42 defining the elements that should be rendered. The problem with a DOM based approach is that possibly a large amount of elements is generated, which slows down the rendering process. This means that potentially every drawable object is represented as a DOM element, so e.g. if a node contains a group consisting of a rectangle and a text element, the amount of elements already triples. Therefore IRV only creates elements in the DOM if the node is also visible within the current viewport of the client. For a view that requires a large amount of elements to be present within the DOM, e.g. when zoomed out, we use a hybrid solution consisting of SVG and the HTML5 canvas [18]. Unlike using SVG, the canvas approach does not produce DOM elements but allows to directly draw pixel-based graphics onto the canvas area. This is faster but also more complex as element styling, user interaction and varying screen resolutions complicate the drawing procedure. Depending on the current zoom level and the amount of visible nodes, the viewer decides which approach to use for rendering.

4.2.6 User Interface

The user interface (Figure 4.11) of the viewer is kept fairly simple, offering only necessary functionality to be directly accessible. The intention is to provide additional context sensitive information only when suitable or explicitly requested by the user. Though, a goal is to imitate the current user interface of IGV and to provide a similar user experience.

Trace Context Selector Viewer Menu

Contextual Information Search

Details Pane

Resource List View

Settings

Figure 4.11: IRV user interface overview

It offers a central view used for graph rendering as well as a resource list on the left side, which contains all currently available graphs. On the top we can select the current trace context for which we would like to inspect its resources. On the right side we can find several panels concerning search, settings and detailed information on currently selected elements. 43

5 Evaluation

This chapter evaluates the extension built in our case study in terms of performance and file size of the resulting dumps. We compare the approach presented in this theses with the Binary Graph Protocol.

The code was executed on an Intel Core i7-2760QM 2.40GHz running Xubuntu 14.04 64- Bit with 8GB RAM. The Graal Compiler is built on revision 8619b47 (graal-core) from the official repository1 and uses OpenJDK 1.8.0_65 64-Bit JVMCI VM (build 25.66-b00-internal- jvmci-0.9-dev).

The com.oracle.graal.salver package was extended with additional functionality, not yet included in graal-core, to allow writing messages in chunks and to support binary dumps.

Trace Messages vs Binary Graph Protocol

In this section we compare trace messages to the Binary Graph Protocol (BGP) currently used by Graal to send graph dumps to IGV. We focus on execution time overhead and file size needed by individual graphs dump. The fact that the BGP is a binary format especially designed for the purpose of storing graph dump structures makes it pretty efficient in terms of file size already. Additionally it uses a constant pool for recurring objects. For a reasonable comparison we use UBF as the serialization format for our trace messages. We also write the output directly to files rather then using a socket endpoint. if (obj instanceof Graph) { ensureInitialized(); Graph graph = (Graph) obj; if (graph.getNodeCount() < 4096) { return; } long startTime = SalverDumpDebug.startTime(); dumper.dump(graph, msg); SalverDumpDebug.measureTime(startTime, graph); }

Listing 5.1: DraphDumperHandler: Additional code for debugging

1. https://github.com/graalvm/graal-core 5 Evaluation 44

For our comparison we measure the time needed for dumping an individual IR graph. There- fore we extend the GraphDumpHandler with additional code for time measuring as shown in Listing 5.1. We also use a threshold, so that graphs are only dumped if they consist of a minimum of 4096 nodes, which reduces the amount of data we have to deal with and allows to focus on larger graphs only.

We compare the two approaches using the Graal VM, where the Graal Compiler is used for normal compilations instead of the server compiler. This allows us to trace compilations of frequently used methods during the execution of the VM. We also use the bootstrap mech- anism of the VM, which triggers an initial compilation process by starting with all methods of java.lang.Object, resulting in the compilation of further methods. Listing 5.2 shows the command line arguments we used for our tests. The flags are needed to enable Graal’s debug mode as well as the output of trace messages and BGP dumps. mx --vm jvmci vm -XX:+BootstrapJVMCI -G:Dump= -G:+Salver -G:+SalverToFile Ðâ ãÑ -G:+PrintIdealGraph -G:+PrintIdealGraphFile -version Listing 5.2: Command line arguments used for comparison tests

We measure the time needed for dumping a single graph and build an average value using the arithmetic of all individual timings across five measurement runs. Each run includes approximately 1580 graph dumps with a node count between 4096 and 8600.

Trace Messages 52 ms

Binary Graph Protocol 14 ms

025 50 75 100 Time (ms)

Figure 5.1: Time needed for dumping a graph

As we can observe, the flexibility of our approach comes with a cost. The creation of the ex- plicit trace data object derived from the original information and the serialization process of this data results in a performance overhead. As we can see in our comparison (Figure 5.1), using trace messages is slower, about 3.8 times, than writing the data directly when process- ing the objects, as this is done by BGP. We have to consider that trace messages provide a generic and extensible approach for data dumps while BGP focuses on graphs only.

Trace Messages 485 MiB

Binary Graph Protocol 352 MiB

0 100 200 300 400 500 600 File Size (MiB)

Figure 5.2: File size needed for graph dumps 5 Evaluation 45

Additionally we look at the dump files created during the compilation process (Figure 5.2). On average dumps using trace messages are about 1.4 times larger. The larger file size can be explained by the additional information that has to be stored with each trace message so that it can be considered self-defining. The BGP requires the consumer of the dumps to have this knowledge in order to be able to process and understand the data.

Trace Messages: UBF vs JSON

In order to show the potential of UBF compared to the text-based JSON format, we look at the file size needed for dumps using UBF respectively JSON as their serialization format.

Trace Messages: UBF 485 MiB

Trace Messages: JSON 1 343 MiB

0 200 400 600 800 1000 1200 1400 File Size (MiB)

Figure 5.3: File size needed for graph dumps (UBF vs JSON)

Figure 5.3 shows that there is a difference between the file sizes of the two serialization formats. While both containing the exact same information when parsed, on average the JSON file is about 2.8 times larger than the file using UBF.This difference is mostly caused by the constant pool module that allows to address reoccurring objects with IDs, which similar to what the BGP is doing. 46

6 Related Work

This chapter presents related work in the context of trace visualization. It lists projects concerning Graal respectively Truffle specific visualizations as well as gen- eral approaches similar to the framework presented in this thesis.

Graal Specific Trace Visualization

Ideal Graph Visualizer

Figure 6.1: Screenshot of the Ideal Graph Visualizer (IGV)

The Ideal Graph Visualizer (IGV) [7] (Figure 6.1) is a tool originally developed by Thomas Würthinger for visualizing the graph-based intermediate representation of the Java HotSpot™ server compiler. Currently it is also the tool of choice when debugging optimizations applied to the Graal IR. IGV is capable of displaying IR graphs after certain optimization phases done by the Graal Compiler.

It allows to inspect IR instructions represented as nodes as well as their control and data flow. Among several useful features like customizable filtering and graph coloring, IGV provides a difference view between phases to better observe applied changes.

The specialized layout algorithm used in IGV is heavily optimized for the use case of handling graphs representing the Graal IR. It is a hierarchical algorithm with several optimizations like edge crossing reduction or edge cutting. Therefore it not only provides good performance on large graphs but also a more comprehensible visualization. 6 Related Work 47

IGV is platform independent desktop application written in Java that uses the NetBeans platform as a basis. The tool is part of the Graal Project and can be used to debug the Graal IR produced by the Graal Compiler. Graal is capable of directly sending graph dumps via network sockets to the IGV which accepts incoming data using the binary graph protocol or a compatible XML representation.

C1Visualizer

Figure 6.2: Screenshot of the C1Visualizer (C1V)

The C1Visualizer (C1V) [8] (Figure 6.2) is a visualization tool originally built by Christian Wimmer for the Java HotSpot™ client compiler. It can be used to display high-level and low- level IR of the client compiler. For the high-level IR it supports visualizations for control and data flow graphs as well as a textual representation. The low-level IR (LIR) can be inspected via a syntax-highlighted textual form with navigation functionality. Additionally it allows to inspect the corresponding Java byte codes of the original compiled methods and offers a chart providing information on register allocations.

The tool is a Java application based on the NetBeans platform. To load data one has to open a dump file created during compilation. The Graal Compiler also supports writing compatible dump files, since especially the LIR visualization functionality is interesting for debugging.

Graal IR Visualizer

The Graal IR Visualizer (GIRV) [19] (Figure 6.3) is an interactive visualization tool for Graal’s graph-based intermediate representation. It’s a visualization system consisting of a separate conversion utility and a visualization tool. First Graal IR dumps in the binary graph protocol format need to be converted to a suitable data representation. This is done by a Java based utility that reuses parts of IGV to produce a JSON representation of each graph. These files can then be viewed by the visualization tool which is a client side web application written in JavaScript.

Compared to IGV the web based visualization tool provides similar functionality but lacks certain features like customizable filtering or node coloring. GIRV does not use a layout 6 Related Work 48

Figure 6.3: Screenshot of the Graal IR Visualizer (GIRV) algorithm similar to the one IGV uses which results in a very different graph layout and a slow layout calculation for graphs containing more than several hundred nodes. It also does not allow to directly receive and process IR dumps although this might be possible with an additional server component built around the conversion utility.

Truffle Specific Trace Visualization

Truffle AVis

Figure 6.4: Screenshot of Truffle AVis

Truffle AVis (Figure 6.4) is a project that attempts to visualize Truffle ASTs with a focus on node specialization. The tool is able to process a trace dump containing action events for creating, inserting and replacing Truffle nodes and allows to replay the dump up to a certain event. For any captured action event an AST can be drawn and inspected using a tree visualization as well as a tree list. Replace action events additionally provide reasons for different node replacements due to specializations. ASTs can be compared to each other allowing to visualize changes between two action events. It also provides basic functionality of AST to source code mapping. 6 Related Work 49

The tool is a client-side only web application written in JavaScript. It requires a dump file containing action events in JSON representation to be loaded by the user. Since parsing and processing of the events is happening inside the main thread, large files can become a performance issue influencing the responsiveness of the user interface.

Trace Visualization

Catapult Project Trace-Viewer

Figure 6.5: Screenshot of the Trace-Viewer integrated in Google Chrome

1 The Trace-Viewer [20] (Figure 6.5) is a web application integrated in Google Chrome , which allows diagnosing performance specific problems via event profiling.

The tool already provides rich analysis features as well as visualization capabilities for dif- ferent types of trace files. It can be used for viewing Chrome traces, Linux kernel traces [21] and Android Systrace [22] files helping to analyze application performance by capturing and displaying execution times of different processes. By providing several extension mech- anisms the Trace-Viewer can be extended to support other types of trace formats or provide domain specific visualizations to allow inspecting complex data sets.

The Trace Event Format [23] is the default trace data representation processable by the Trace- Viewer. The viewer recognizes several different input variations of the format, e.g. a JSON representation. The idea is similar to the structure of trace events presented in this thesis as it also specifies a common set of properties for each event which can be extended with additional information. The format defines special events in general intended for perfor- mance tracing which is different to our approach. These predefined events allow for a more compact representation since properties can be abbreviated for smaller footprints.

1. Accessible via chrome://tracing 50

7 Summary

This chapter outlines possible future work on the Salver Framework addressing ad- ditional functionality and optimizations. It also lists ideas for use cases concerning the Graal Compiler and the Truffle Framework. Finally it concludes this thesis with a short summary.

Future Work

Salver Framework

In general the framework is intended for arbitrary use cases involving visualizations as a result of collected information on internal data of a software system. At the moment the framework mainly focuses on visualizing internals of the Graal Compiler and especially the Graal IR. Also the functionality of the trace source involving trace messages and serialization is currently only available in Java and directly coupled to the Graal Compiler.

Additional enhancements are needed to offer different kinds of visualizations, as for now the framework only supports graph visualization. It can be further extended to be used within different projects by providing standalone libraries with basic trace source functionality for various programming languages.

Source Compilation

Currently the framework is intended to be used in a development environment only, so it is not yet fully optimized for production. This means that several code fragments are compiled on the fly to be executable on the current platform, resulting in unnecessary overhead.

On the server side this is negligible after the initial startup but on the client side this means that every reload of the page results in a recompilation of the sources. This is currently not a priority issue since runtime performance is not influenced by that behavior as long as the is not reloaded and no additional resources are required. In general, tools built with the framework are supposed to be used as single page applications, meaning all functionality can be used without the need of navigating to a different page. 7 Summary 51

Intermediate Representation Visualizer

IGV Functionality

The IRV tool only provides basic functionality for inspecting the Graal IR whereas IGV offers many more features as well as higher performance on large data sets. In order to consider IRV a reasonable alternative to the IGV desktop application we also need to provide this func- tionality in a similar way and possibly extend it with additional features. Besides improving performance, the following features are planned for further releases.

„ Visualize groups of nodes for better overview

„ User-defined customizable filters

„ Build subgraph views via selections or filters

Layout Algorithm

Currently the layout algorithm ported from the IGV component does not completely utilize the fact that an IR graph is an interleaving graph of both the control-flow and the data-flow. This additional knowledge could help to provide a clearer layout by better arranging the graph while focusing on the control-flow and allowing to combine edges.

Besides that the arranging of a node’s x-coordinate as well as the edge routing can be im- proved to gain a better visual experience. For performance and usability reasons we could also consider optimizing the layout algorithm to be used within a [24]. This would result in a smooth user experience since the UI thread would not be locked in case of any long running calculations.

Resource Loading

Every source file is currently served individually, which results in lots of requests to the server. Since browsers can only handle a certain amount of simultaneous downloads, this adds a large overhead to the startup of the frontend. The startup can therefore be drastically improved by creating bundles containing relevant precompiled sources. This reduces the amount of requests needed to load all source files and allows for faster parsing.

HTTP2 might deprecate bundling in the future and would even improve the startup by al- lowing the client to selectively cache certain source files. 7 Summary 52

MongoDB Document Limits

There is currently a hard limit on the size of a document that can be stored in the . In version 3.0, it is not allowed to store documents larger than 16 megabytes [25], which helps to ensure that single documents cannot use excessive amounts of RAM or bandwidth.

It’s possible though to store documents larger than the maximum allowed size using the GridFS API. The downside with this approach is that we would lose useful features like querying documents.

Another restriction on documents is the nesting level of objects and arrays which is limited to a maximum of 100. Having this limitation is not a problem as for reasonable use cases the nesting level almost never exceeds a depth of 10.

Currently an average IR graph containing up to five thousand nodes and edges approximately makes up one megabyte in the BSON data format, which is MongoDB’s internal storage format. This varies heavily depending on the amount of different classes, attached properties and of course the number of edges.

If the storage limit of documents might become a problem in the future there are a few possible ways to mitigate this problem.

„ Minimize property names At the moment all property names used to describe graphs and their elements are op- timized for readability and most importantly for data reusability without the need of detailed knowledge about the data structure. This means property names consist of longer strings, e.g. successors resulting in larger data sets. These could be replaced by shorter codes, e.g. succ, to save bytes

„ Split graphs into several documents Another possibility would be to split a graph into several documents containing different parts, e.g. one document containing only definitions of node classes. The disadvantage is that these parts have to be assembled on an application level which might result in a more complex process when querying graphs.

Graal Compiler

Low-Level Intermediate Representation

The current tool of choice when investigating low-level IR (LIR) specific things is the C1Visualizer. It displays LIR in a syntax-highlighted textual form with navigation and also provides information on register allocations.

By extending Graal with trace messages, specifically for LIR, a plugin could provide similar information and probably give additional knowledge with visualizations that combine the LIR information with the graph-based Graal IR. 7 Summary 53

Truffle Framework

Tree Rewriting

Having a visual representation of an AST outlining the specialized and therefore rewritten nodes can be very valuable for language implementers using the Truffle Framework as well as for developers working on the framework itself.

Currently the only way to provide the information needed for these visualizations is by ex- tending the core of the Truffle API. Truffle related trace information and the resulting visual- izations are not part of this thesis as for now we only have a prototype with limited support for the needed functionality.

Possibly every node action – create, insert, replace – must be recorded in order to allow rebuilding the AST later on. This can result in a very large data set probably containing unnecessary information for the user. Finding a more efficient approach for collecting all relevant information might be advisable.

Source Code Mapping

A graph visualization of a Truffle AST might not always be that intuitive for people especially for developers who would typically expect to view source code. For that use case a mapping between the visualization and its corresponding source code can be useful. The code view can be extended with additional inline information, e.g. node specialization, and provide functionality to link to the visualization and vice versa. 7 Summary 54

Conclusion

This thesis introduces a web framework that aims to reduce the development effort of build- ing web based applications for trace visualization. It focuses on collecting and processing diverse data sets to provide useful visual representations of this information. Tools using the framework are intended to heavily rely on user interactions to adapt and manipulate the data being used for visualization.

The framework was mainly developed to be used along with the Graal Project for inspecting internals of the Graal Compiler. The intention is to improve the inspection process by solving mainly three issues of the current approach.

The main problem is the various tools that are currently needed for inspecting different data sets. Having to use individual tools for every type of data increases the workflow complex- ity as well as the time needed to gain insights on the data. We solve this by providing a flexible approach for working with diverse data sets and allowing to combine the necessary functionality into a single extensible tool. This also solves the second issue concerning data aggregations of different data sets. By having all relevant data in one place we are able to connect certain information or derive additional knowledge, which is not possible if the data is split among tools that are not able to communicate with each other. The last issue covered is the data sharing aspect. Since the framework is used to build web applications, the tools are accessible remotely, which allows other users to easily access and share their data from any device capable of running the according web frontend.

The current versions of the framework and the tool, developed in this thesis, are still con- sidered prototypes and are not yet providing all of their intended functionality. While still in an early stage, the framework already shows that it is feasible to build useful tracing tools that run in modern web browsers. 55

List of Figures

2.1 The compilation pipeline of the Graal Compiler ...... 5 2.2 Example Graal IR graph showing data and control flow edges ...... 6 2.3 Visual representation of the AddNode ...... 7 2.4 Visual representation of the IfNode ...... 7 2.5 Tools for inspecting internals of the Graal Compiler ...... 8

3.1 Architecture of the Salver Framework ...... 10 3.2 Trace message ...... 12 3.3 Trace message events ...... 12 3.4 Trace context of different providers ...... 16 3.5 Trace message containing the extracted data ...... 22 3.6 Trace resource as a result of trace messages ...... 23 3.7 Resource viewer showing a graph visualization and a list of resources . . . . . 24

4.1 General trace pipeline ...... 26 4.2 Data structure for IR graphs used within trace messages ...... 28 4.3 Edge representation to discriminate data-flow and control-flow ...... 29 4.4 Simple IR graph with two connected nodes ...... 29 4.5 Architecture of the IRV plugin ...... 36 4.6 Screenshot of the Intermediate Representation Visualizer (IRV) ...... 37 4.7 Graph, Subgraph and Graph Layout ...... 38 4.8 Data-flow and control-flow edge directions ...... 39 4.9 Multiple nodes share an input port via a node list ...... 40 4.10 Basic steps of the layout algorithm ...... 41 4.11 IRV user interface overview ...... 42

5.1 Time needed for dumping a graph ...... 44 5.2 File size needed for graph dumps ...... 44 5.3 File size needed for graph dumps (UBF vs JSON) ...... 45

6.1 Screenshot of the Ideal Graph Visualizer (IGV) ...... 46 6.2 Screenshot of the C1Visualizer (C1V) ...... 47 6.3 Screenshot of the Graal IR Visualizer (GIRV) ...... 48 6.4 Screenshot of Truffle AVis ...... 48 6.5 Screenshot of the Trace-Viewer integrated in Google Chrome ...... 49 56

Listings

2.1 Example snippet to illustrate data flow and control flow ...... 7 2.2 Definition of AddNode with two operands ...... 7 2.3 Definition of IfNode using @Input and @Successor annotations ...... 8

3.1 Simple trace message of type info ...... 13 3.2 Resolving trace message types ...... 14 3.3 Global :begin message ...... 15 3.4 Trace context of different providers ...... 16 3.5 EJSON example ...... 19 3.6 Multiple messages containing only sparse data ...... 20 3.7 Generated resource from multiple messages ...... 20 3.8 Hierarchically organized trace resources ...... 21 3.9 Pseudo code selection of multiple resources ...... 21 3.10 Sample of an extracted graph information ...... 22 3.11 Sample trace message containing graph information ...... 22 3.12 Resulting resources after processing several messages ...... 23

4.1 Data-flow edge representation within trace data ...... 29 4.2 Control-flow edge representation within trace data ...... 29 4.3 Representation for multiple edges with index for ordering ...... 29 4.4 Simple IR graph with two connected nodes ...... 30 4.5 Begin message specifying the trace context ...... 32 4.6 Salver debug options ...... 32 4.7 Customize the debug configuration via a service provider ...... 33 4.8 Definition of the DebugDumpHandler interface ...... 33 4.9 Dump method of a dump handler for Graal IR graphs ...... 34 4.10 Definition of the Serializer interface ...... 34 4.11 Definition of the DumpWriter interface ...... 35

5.1 DraphDumperHandler: Additional code for debugging ...... 43 5.2 Command line arguments used for comparison tests ...... 44 57

Bibliography

[1] OpenJDK Community. Graal Project. URL: http://openjdk.java.net/projects/ graal/.

[2] Thomas Würthinger et al. “One VM to Rule Them All”. In: Proceedings of the 2013 ACM International Symposium on New Ideas, New Paradigms, and Reflections on Program- ming & Software. Onward! 2013. Indianapolis, Indiana, USA: ACM, 2013, pp. 187– 204. DOI: 10.1145/2509578.2509581.

[3] Gilles Duboscq et al. “An Intermediate Representation for Speculative Optimizations in a Dynamic Compiler”. In: Proceedings of the 7th ACM Workshop on Virtual Machines and Intermediate Languages. VMIL ’13. Indianapolis, Indiana, USA: ACM, 2013, pp. 1– 10. DOI: 10.1145/2542142.2542143.

[4] Gilles Duboscq et al. “Graal IR: An Extensible Declarative Intermediate Representa- tion”. In: 2nd Asia-Pacific Programming Languages and Compilers Workshop. APPLC ’13. Feb. 2013.

[5] Lukas Stadler. “Partial Escape Analysis and Scalar Replacement for Java”. PhD thesis. Johannes Kepler University Linz, May 2014.

[6] Christian Wimmer and Thomas Würthinger. “Truffle: A Self-optimizing Runtime Sys- tem”. In: Proceedings of the 3rd Annual Conference on Systems, Programming, and Ap- plications: Software for Humanity. SPLASH ’12. Tucson, Arizona, USA: ACM, 2012, pp. 13–14. DOI: 10.1145/2384716.2384723.

[7] Thomas Würthinger. “Visualization of Program Dependence Graphs”. MA thesis. Jo- hannes Kepler University Linz, Aug. 2007.

[8] Christian Wimmer. Java HotSpot™ Client Compiler Visualizer. URL: https://java. net/projects/c1visualizer/.

[9] Node.js Foundation. Node.js - A JavaScript runtime built on Chrome’s V8 JavaScript engine. URL: https://nodejs.org/.

[10] Oracle Labs. Graal VM Polyglot Engine. URL: http://www.oracle.com/technetwork/ oracle-labs/program-languages/polyglot/.

[11] Microsoft. ChakraCore. URL: https://github.com/Microsoft/ChakraCore. [12] Ecma International. Standard ECMA-404 - The JSON Data Interchange Format. Oct. 2013. URL: http://www.ecma- international.org/publications/standards/ Ecma-404.htm.

[13] Development Group. EJSON. URL: https://www.meteor.com/ejson. Bibliography 58

[14] Universal Binary Format - A modular binary format for data interchange. URL: http: //ubfspec.org/.

[15] MongoDB, Inc. MongoDB - An open-source, document database. URL: https://www. mongodb.org/.

[16] Erik Dahlström et al. (SVG) 1.1 (Second Edition). W3C Rec- ommendation. W3C, Aug. 2011. URL: http://www.w3.org/TR/2011/REC-SVG11- 20110816/.

[17] W3C. (DOM). Tech. rep. W3C. URL: https://www.w3.org/ DOM/.

[18] Rik Cabanier et al. HTML Canvas 2D Context. W3C Recommendation. W3C, Nov. 2015. URL: https://www.w3.org/TR/2015/REC-2dcontext-20151119/.

[19] Ingomar Wesp. “Dynamic Visualization of Compiler Graphs”. MA thesis. Johannes Kepler University Linz, July 2015.

[20] Catapult Project Trace-Viewer. URL: https : / / github . com / catapult - project / catapult.

[21] Red Hat Inc. ftrace - Function Tracer. 2008. URL: https://www.kernel.org/doc/ Documentation/trace/ftrace.txt.

[22] Android Systrace. URL: http://developer.android.com/tools/help/systrace. .

[23] Trace Event Format. URL: https://docs.google.com/document/d/1CvAClvFfyA5R- PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview.

[24] Ian Hickson. Web Workers. Working Draft. W3C, Sept. 2015. URL: https://www.w3. org/TR/2015/WD-workers-20150924/.

[25] MongoDB, Inc. MongoDB Limits and Thresholds. URL: https://docs.mongodb.org/ v3.0/reference/limits/. 59

Curriculum Vitae

Personal Information

Name Date of Birth Nationality Stefan Rumzucker October 5 1989 Austrian

Address Email Figulystraße 38 [email protected] A-4020 Linz

Education

2009 - 2014 Bachelor of Science in Computer Science Johannes Kepler University Linz

2000 - 2008 BRG/BORG Kirchdorf a. . Krems

1996 - 2000 Volksschule Kirchdorf a. d. Krems

Experience

2015 - 2016 Student Researcher Johannes Kepler University Linz

2008 - 2009 Community Service LKH Kirchdorf a. d. Krems 60

Eidesstattliche Erklärung

Ich erkläre an Eides statt, dass ich die vorliegende Masterarbeit selbstständig und ohne frem- de Hilfe verfasst, andere als die angegebenen Quellen und Hilfsmittel nicht benutzt bzw. die wörtlich oder sinngemäß entnommenen Stellen als solche kenntlich gemacht habe. Die vor- liegende Masterarbeit ist mit dem elektronisch übermittelten Textdokument identisch.

Linz, Mai 2016 Ort, Datum Unterschrift