truffle cil interpreter

69
Submitted by Patrick Hagm¨ uller, BSc. Submitted at Institut f¨ ur Systemsoftware Supervisor o.Univ.-Prof. Dipl.-Ing. Dr.Dr.h.c. Hanspeter ossenb¨ ock Co-Supervisors Dr. David Leopoldseder September 2020 JOHANNES KEPLER UNIVERSITY LINZ Altenbergerstraße 69 4040 Linz, ¨ Osterreich www.jku.at DVR 0093696 Truffle CIL Interpreter Master Thesis to obtain the academic degree of Diplom-Ingenieur in the Master’s Program Computer Science

Upload: others

Post on 23-Feb-2022

9 views

Category:

Documents


0 download

TRANSCRIPT

Submitted byPatrick Hagmuller, BSc.

Submitted atInstitut furSystemsoftware

Supervisoro.Univ.-Prof. Dipl.-Ing.Dr.Dr.h.c. HanspeterMossenbock

Co-SupervisorsDr. David Leopoldseder

September 2020

JOHANNES KEPLERUNIVERSITY LINZAltenbergerstraße 694040 Linz, Osterreichwww.jku.atDVR 0093696

Truffle CILInterpreter

Master Thesis

to obtain the academic degree of

Diplom-Ingenieur

in the Master’s Program

Computer Science

2

Abstract

C# is currently one of the most significant programming languages worldwide. At the timeof writing this thesis every reliable ranking of the popularity of programming languages ranksC# in the top five places. E.g. the PYPL PopularitY of Programming Language Index, whichis created by analyzing how often language tutorials are searched on Google, shows C# onplace four with a share of 7.27% (March 2020). Despite its popularity, it is currently notpossible to execute C#-Code on the Java Runtime. A major reason for this fact is that the.NET-Runtime itself is designed for Windows, in contrast to the Java Runtime, which runson multiple platforms. C# is a part of the .NET-Platform and is compiled, like other .NET-Languages, to an intermediate language called common intermediate language. Finding away to enable the execution of CIL Code on the Java Runtime would not only make C#but also other .NET-Languages like VB-.NET etc. cross-platform. Therefore we propose a C#implementation, called Truffle CIL, based on the Truffle framework. Language implementationscreated with the Truffle framework can be executed with the GraalVM, a modified version ofthe Java HotSpot VM featuring a novel JIT compiler, allowing the cross-plattform executionof C# on the JVM.

This thesis presents a novel interpreter for CIL code based on the Truffle framework writtenin Java. The interpreter consists of a recursive descent parser for CIL-Code and an abstractsyntax tree interpreter, implemented using the Truffle framework. We describe our approachfor implementing control flow based on an abstract syntax tree and also explain a way ofrepresenting data at runtime, by using Truffle’s dynamic object model. Finally, we present athorough valuation of the implemented interpreter and discuss the results.

3

Kurzfassung

Die Programmiersprache C# ist aktuell eine der wichtigsten Programmiersprachen weltweit.Beim Verfassen dieser Arbeit wies jedes verlässliche Ranking von Programmiersprachen, inBezug auf ihre Beliebtheit, C# in den Top 5 aus. Der PYPL PopularitY of ProgrammingLanguage Index zum Beispiel, wies C# auf Platz vier aus mit einem Anteil von 7.27 % (März2020). Die Reihung dieses Index wird anhand der Häufigkeit von Suchanfragen bei Google vonTutorials für die jeweilige Programmiersprache erstellt. Trotz der Popularität, ist es derzeitnicht möglich, C#-Code in der Java Runtime auszuführen. Im Gegensatz zur Java Runtime,welche auf mehreren Plattformen läuft, wurde die .NET-Runtime ursprünglich von Microsoftfür Windows entwickelt. Ein Teil dieser .NET-Plattform ist die Programmiersprache C#. Siewird wie andere .NET-Sprachen, in eine Intermediate Language kompiliert, die man als Com-mon Intermediate Language (CIL) bezeichnet. Das hat zur Folge dass, wenn man es schafft,CIL Code in der Java Runtime auszuführen, man nicht nur C#, sondern auch alle anderen.NET-Sprachen, wie z. B. VB-.NET etc., Cross-Plattform-fähig machen würde. Um diesesZiel zu realisieren, stellen wir in dieser Masterarbeit eine Implementierung von C# - basierendauf dem Truffle Framework - vor, welche wir Truffle CIL nennen. Sprachen, die mit demTruffle Framework implementiert wurden, können in der GraalVM, einer Modifikation der JavaHotSpot VM, welche einen neuartigen JIT Compiler unterstützt, ausgeführt werden. Mit dieserArbeit wird ein Ansatz gezeigt, wie man C# in der JVM ausführen kann und dabei gleichzeitigalle .NET-Sprachen Cross-Plattform-fähig macht.Diese Arbeit präsentiert einen neuartigen Interpreter für CIL Code - basierend auf dem Truf-fle Framework - programmiert in Java. Der Interpreter besteht aus einem Parser, der dieMethode des rekursiven Abstiegs implementiert und einem Interpreter für abstrakte Syn-taxbäume, welcher das Truffle Framework verwendet. Es wird der Ansatz beschrieben, dieImplementierung von Kontrollstrukturen, basierend auf abstrakten Syntaxbäumen und aucheine Möglichkeit zur Repräsentation von Daten zur Laufzeit erklärt, indem das Dynamic Ob-ject Model von Truffle zum Einsatz kommt. Am Ende der Arbeit erfolgt eine Präsentation derBewertung der Implementierung.

Contents 4

Contents

1 Introduction 61.1 Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.2 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.3 Novel Solution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

2 System Overview 102.1 Common Language Runtime - CLR . . . . . . . . . . . . . . . . . . . . . . . . . 102.2 Java Virtual Machine - JVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112.3 Graal VM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122.4 Truffle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

3 Truffle CIL - on the JVM 153.1 Recursive Descent Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153.2 AST Interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173.3 Control Flow Represantion at Runtime . . . . . . . . . . . . . . . . . . . . . . . 183.4 Data Representation at Runtime . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4 Implementation: Truffle CIL 244.1 Recursive Descent Parser and Grammar . . . . . . . . . . . . . . . . . . . . . . 244.2 Stack Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264.3 Control Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

4.3.1 Representation of Abstract Syntax Tree Nodes as Classes . . . . . . . . . 294.3.2 Successor Determination . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

4.4 Driving Partial Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354.5 Dynamic Object Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

4.5.1 CILClassInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414.5.2 Method Dispatching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424.5.3 Accessing Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

Contents 5

4.5.4 Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474.6 Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

5 Evaluation 525.1 Benchmarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535.2 Runtime Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

6 Related Work 566.1 Language implementations on top of Truffle . . . . . . . . . . . . . . . . . . . . 566.2 Cross-platform implementations of the .NET framework . . . . . . . . . . . . . . 58

7 Future Work 59

8 Conclusion 61

Acknowledgments 62

Bibliography 65

Introduction 6

Chapter 1

Introduction

This chapter presents an approach to execute programs written for the .NET-Framework on theJava-Runtime. The outlined approach lead us to develop an interpreter for this thesis, calledTruffle CIL. The chapter explains the novelty of cross-compiling .NET-Code to Java-Bytecode,the benefits of this approach and also the major problems, which have to be solved.

If someone wants to start a new software project and wants to solve it with a high levelprogramming language, frequently the decision between Java [1] and C# [2] needs to be taken.On the first impression these programming languages a quite similar. Both languages areobject oriented, statically typed and not executed directly on the target platform but in avirtual machine. In the case of Java in the Java virtual machine [3] and in the case of C# inthe .NET runtime [4]. Both languages are quite popular, the PYPL PopularitY of ProgrammingLanguage Index ranks Java on the second place with a share of 18.84% and C# on the fourthplace with a share of 7.27% (March 2020).

1.1 Problem

Between these programming languages there exists a technology gap. A C# program cannot be executed on the Java runtime and vice versa. Therefore, you also can not use existinglibraries written in the other programming language. To overcome this technology gap, a way to

Introduction 7

executing a program written in another programming language than Java, in the Java runtime,is needed. In this case C#, would be the so called guest language and Java would be thehost language. A framework, written in Java, which supports new language implementers toimplement new guest languages is Truffle.

At the time of starting this project there existed no implementation of a .NET [4] languageas a guest language on the top of the Truffle framework. It is also not possible to execute.NET-Code in the Java Runtime. The term .NET [4] programming language is a collectionof object-oriented high level progamming languages such as C# [2] or Visual Basic [5]. Anapplication compiled with a .NET-oriented compiler is represented through managed code. Thismanaged code is encoded in an abstract form known as the common intermediate language [6]CIL. Typically, the managed code will be executed in the .NET common language runtime.

1.2 Motivation

Implementing CIL as a guest language on top of the Truffle framework [7], would enable usersto run any application written in a .NET based language in the Java Runtime. Tackling thisgoal would also mean that the .NET platform, which was originally designed as a MicrosoftWindows [8] platform, turns into a cross-platform. Because the Java Runtime [1] is cross-platform, it would be possible to execute a .NET [4] application not only on Windows but alsoon OSX [9] or Linux [10].

In this thesis we developed a technical feasibility prototype of the CIL, implemented as a guestlanguage of the Truffle framework. We realized this technical feasibility prototype through anabstract syntax tree (AST) interpreter whichc executes CIL. This AST interpreter is based onthe Truffle framework, a framework for implementing managed languages in Java [11].

The goal of the project is that the feasibility prototype can execute several language bench-mark games [12]. This shows that our project supports a set of non trivial features. TheComputer Language Benchmarks Game [12] project involves a set of algorithms and programs,implemented in various languages. The aim of this project was to compare the execution per-formance of the different development platforms. Running these programs on our interpreteris a good test, because these programs cover the important programming language concepts.

Introduction 8

Based on the results of these benchmarks it should be possible to make a statement on theperformance of the chosen approach of implementing the common intermediate language.

1.3 Novel Solution

Our solution for implementing a Truffle CIL interpreter involves four main technical and scien-tific challenges that need to be solved:

• implementing a Parser for CIL

• creating an AST based on the parsing result

• implementing an AST interpreter

• testing and evaluation

Before we can start to tackle any of these tasks, the abstract representation of a .NET applica-tion is needed. As already mentioned above, the .NET [4] runtime was originally designed byMicrosoft for Windows [8]. On the other hand the Graal VM [13], which we want to use as ourruntime, is available for Windows [8], Linux [10] and macOS [9]. So we have a gap between thesupported platforms. This thesis aims to implement an interpreter for CIL, but we want to im-plement our programs, which should run in the interpreter, in a .NET [4] language, like C# [2],instead of CIL. Therefore, the mono project [14], a cross-platform implementation of the .NETframework, is used. The managed compiler of the mono framework compiles a managed mod-ule. This managed module contains the application code encoded in an abstract binary formknown as Microsoft intermediate Language (MSIL) or common intermediate language (CIL)[15]. In some literature this code is simply referred as intermediate language (IL).

The first challenge that has to be tackled is implementing a Parser for CIL code. The IL codehas to be parsed by a Parser, because a syntactic analysis is necessary. With the result fromthe parsing step the developer can build an abstract syntax tree representing the programm.In the Truffle CIL interpreter project nodes are used to represent CIL instructions and controlstructures.

Introduction 9

For the task of implementing an AST interpreter for CIL, we can identify the following mainexecution challenges:

• Stack handling: The common language runtime knows three different categories ofmemory when executing a method: Argument table, local variable table and the evalua-tion stack [15]. Each of them must be represented in the AST interpreter.

• Bytecode Dispatch: CIL supports goto-Jumps. So potentially every bytecode instruc-tion can be the target of a branching instruction. If we implement the controlflow handlingwe have to consider this fact. The Sulong project [16] identifies so-called basic blocks [17]for functions. Basic blocks [17] consist of sequential instructions and end with a termina-tor instruction that transfers control to the next basic block [17]. We propose a differentapproach: each CIL instruction will be represented by a separate basic block. The Graal-Squeak [18] project showed that it is possible to write an interpreter which implementsdispatching on the instruction level. GraalSqueak [18] implemented bytecode nodes witheither one or, in the case of a conditional jump, two successors.

• Testing, verification and execution: The testing of the different software moduleslike Scanner, Parser and the frontend of the compiler has to be done during the wholeimplementation process. The .NET-Languages are complex programming languages. TheIL instruction set contains 235 different instructions. To verify that a program runningin the Truffle CIL Interpreter acts similar when being executed in the .NET runtime,we had to write a high number of tests. Therefore, we had to implement some kind ofautomation of creating the testfiles, the execution of the tests and checking the results.

• Typesystem: The computational model, which is implemented by the common languageruntime, is fundamentally object-oriented. Everything is build up and based on theconcept of a type. As the ECMA standard [6] specifies two basic kinds of types: valuetypes and reference types. A major part of common language runtime is a unified typesystem, the Common Type System (CTS). As described in the ECMA standard [6], theCTS provides a rich type system that supports the types and operations found in manyprogramming languages. Figure 3.4 illustrates the hierarchy of this type system. Hencewe have to implement a computation model in the Truffle CIL interpreter which supportsthe CTS.

System Overview 10

Chapter 2

System Overview

This chapter explains the context of the Truffle CIL interpreter which is built on top of theGraal VM, a modified version of the HotSpot JVM. In the first part this chapter introducesthe involved platforms: the .NET platform and the Java platform. Later this chapter explainsGraal, a compiler for the Java platform and the language implementation framework Truffle.In the end this chapter shows how all these parts complement to the system architecture of theTruffle CIL interpreter.

2.1 Common Language Runtime - CLR

The .NET platform runtime environment supports the programmers by managing, deploying,and revising their code. It provides features like garbage collection and interaction with systemservices, as explained in detail by David S. Platt [19]. The major part of the .NET platform isits runtime, the so-called common language runtime (CLR). The CLR was developed to execute.NET applications. Similar to a virtual machine, the CLR acts as an operating layer betweenthe application and the underlying operating system.

Figure 2.1 illustrates the process of creating code, which can be executed in the CLR. A Programwhich is written in a programming language that is implemented for the .NET platform like C#[2], Visual Basic, [5] and others will be compiled with a .NET oriented compiler. These compilersgenerate an abstract, intermediate representation of the .NET application. As Serge Lidin [15]points out these intermediate representations are independent of the original programming

System Overview 11

language and of the operating system of the target machine. This intermediate representationconsists of two main parts: The metadata and the managed code. The metadata describesall structural items like classes, class members, class attributes, and others, as well as therelationship between these items. The second part, the managed code, is the part we will focuson in this thesis. It encodes the functionality of the .NET application using a programminglanguage named Microsoft intermediate language (MSIL) respectively common intermediatelanguage (CIL) [20].

Static void Main(string[] args){

for (int i= 0; i<100; i++){

Console.WriteLine(i);}

}

Metadata

IL Code

Metadata

IL Code

Source Code Managed Module

.method private static hidebysigdefault void Main (string[] args) cil managed

{entrypoint.maxstack 2.locals init (int32 V_0, bool V_1)

IL_0000: nopIL_0001: ldc.i4.0IL_0002: stloc.0IL_0003: br.sIL_0012: ……IL_001b: ret

}

Managed Compiler

Figure 2.1: Creation of a managed .NET programm

2.2 Java Virtual Machine - JVM

The Java Virtual Machine (JVM) [3] is a part of the Java Runtime Environment [1], which is aruntime environment in which Java-Bytecode [21] applications run. The JVM [3] provides anoperating layer between the Java program and the underlying operating system.

System Overview 12

2.3 Graal VM

The Graal VM [13] is a new universal virtual machine (VM) that supports different languages.By the time of writing this thesis, one can run applications written in JavaScript, Python,Ruby, R, JVM-based languages like Java [1], Scala, Kotlin, Clojure, and LLVM-based languagessuch as C and C++. The architecture of Graal VM [13] allows developers to implement newlanguages with modest effort [22]. Graal VM [13] interprets abstract syntax trees (AST). Itcontains a highly optimizing compiler and one of the core features of the Graal VM [13] is thatnodes can rewrite itself to more specialized or more general nodes. The Graal VM itself iswritten in Java and serves as a host VM for this project (see Figure 2.2).

2.4 Truffle

The Truffle [7, 22] framework has been developed to support language implementers in im-plementing new languages in Java [1]. As Würthinger et al. explain in [22], the aim of theframework is to build high-performance abstract syntax tree (AST) interpreters on the JVM[3]. The core concept of the framework is, that based upon the syntax of a program, a tree ofTruffle nodes will be built, where each node has an execute method, which in turn executes itschild nodes and returns its own result (see [17]).

Figure 2.2 illustrates the system architecture of the Truffle CIL interpreter project. A programwritten in the guest language will be translated to an AST. The nodes represent the semanticsof the guest language program. As mentioned above we use the Truffle API for the creationof the AST. The Truffle optimizer performs two types of optimizations: Self-Optimization andPartial Evaluation

Self-Optimization

The process of Self-Optimization performed by the Truffle optimizer is descirbed by Würthingeret al. [22]. The novel approach of the Truffle framework is that the AST is rewritten duringinterpretation to incorporate type feedback [7]. The optimizer can substitute a node at its

System Overview 13

Figure 2.2: Architecture of the Truffle CIL Project

parent with a different node during runtime. This means that Truffle AST interpreters areself-optimizing [7]. At runtime the framework rewrites a tree with specialized variants. Forexample, the add instruction in CIL pops two values from the evaluation stack, adds them andpushes the result back on the stack. Depending on the type of the two values on the evaluationstack, the add node will be rewritten to a specialized add node for this valuetype.

Another feature of the Truffle framework, that uses the profiling feedback during execution,are Polymorphic Inline Caches. Polymorphic Inline Cache, is a cache in which items in thecache are represented by a node, which are chained, in the tree. Adding an entry to the cacheresults in adding a new node to the tree. These chained nodes will be sequentially run through,depending if the entry matches the node will proceed with the operation specialized for thisentry, or if not the node delegates the handling of the operation to the next node in the chain.In the case a defined length in the chain will be reached, the whole chain replaces itself withone node responsible for handling the fully megamorphic case [22].

Partial Evaluation

To improve the performance of the execution, frequently called methods of the application willbe compiled to optimized machine code. This is explained in detail by Würthinger et al. in

System Overview 14

their paper [7]. In that case Truffle uses the dynamic Graal compiler to compile the AST tomachine code. Graal assumes that the AST will not be rewritten anymore, in other words theAST is stable. The compiler inlines node execution methods of an AST, which is frequentlyexecuted, into a single method. The compiler checks the speculative assumptions, dependingon if this checks fail or not the compiler transfers back the control back from the compiled codeto the interpreted AST.

The last module in Figure 2.2 represents the Java virtual machine which runs on top of theoperating system. The Java runtime compiles the Graal IR [23], a graph-based intermediaterepresentation, which is generated by Graal, to native machine code. By using the Java virtualmachine the guest language implementation can benefit from existing VM runtime services suchas garbage collection, exception handling, and deoptimization [22].

Truffle CIL - on the JVM 15

Chapter 3

Truffle CIL - on the JVM

This chapter introduces the concepts and components of software architecture necessary to ex-ecute the common intermediate language on the top of a Java Runtime. It motivates designchoices made concerning the system architecture, how to parse CIL and how to represent controlflow. Truffle CIL uses the concept of goto-jumps on bytecode level, this leads to the fact thatpotentially every instruction could be the target of a jump. We will give an answer on howto select a successor in a language that uses this concept. Finally, this chapter explains anapproach on how to represent data at runtime.

Figure 3.1 shows a simplified and abstract semantic of the system architecture. It contains allmajor modules necessary for the interpretation of a .NET-applications on the JVM [3]. Thefigure consists of two layers, on the top a program written in a .NET-language. This programis the input for the execution pipeline at the compile-time which is the left side in the bottompart of the figure. The modules which are necessary at compile-time, can be aggregated in twogroups: Those which are based on the Truffle framework, and those who take the Truffle ASTand compile it with the Graal [13] compiler. The Java VM [3], which is represented on thebottom-right side, takes the compiled Java-Bytecode and executes the code at runtime.

3.1 Recursive Descent Parser

As mentioned in the proposed solution in section 1.3, the first implementation task a developerhas to tackle is implementing a parser for CIL-Code. Figure 3.1 shows that a software com-

Truffle CIL - on the JVM 16

Figure 3.1: Truffle CIL System Overview

ponent which takes care about the Static Analysis of the CIL-Code, is a necessary part of thesoftware architecture of the interpreter discussed in this thesis. For this reason we implementeda parser. One of the very first design decisions we made concerning the implementation ofthe parser, was whether we want to analyze source code in textual form or want to operatewith CIL program as binary files. We decided to take source code files in textual form as theinput for our compilation chain. For this thesis we used the Mono [14] project to compile .NETprograms to executables and the disassembler of Mono [14] to generate the IL code of theseexecutables. These files now have to be syntactically analyzed.

Another design choise that was made, was to write a recursive descent parser from scratchfor this project. One can also use compiler generators like Coco/R [24] or implement otherparsing strategies like bottom-up-parsing. The reason why we decided to implement a parserfrom scratch was, among other things, that it is easier to start with if we have the completeparser code in our developer’s hand and don’t have to take care about writing correct attributedgrammar for a compiler generator and modifying the generated parser for our needs. Anothercause was that CIL is well documented in the Standard ECMA-335 [6]. This documentation

Truffle CIL - on the JVM 17

contains language grammar of CIL. Therefore, implementing a recursive descent parser wasstraight forward.

3.2 AST Interpreter

A major goal of this project is to execute C#-programs1 in an environment different to the.NET -Runtime. Implementing a new runtime from scratch is not an aim of this thesis, hencewe didn’t want to in invest implementation power in this. We decided to target the JavaRuntime platform for our CIL interpreter. By making this decision on the level of the systemarchitecture, we could use the existing infrastructures of the Java VM [3] and benefit fromthese. This means, inter alia, for us as an implementer of a CIL interpreter, that we don’t haveto take care about machine code generation and other machine-specific concepts and let theJava Runtime do this job.

As one can see in Figure 3.1, the compiler for our CIL interpreter is separated in front- and abackend. The separation of these two components brings several advantages:We benefit through a better portability, if we want to target a new platform, we only haveto implement a new backend and vice versa if we want support a new language we only haveto implement a new frontend. The missing link between the frontend and the backend is theintermediate representation. Another advantage of separating frontend and backend is, thatthis system architecture enables optimizations. It is easier to manipulate the intermediaterepresentation than the source code. We use the Java Runtime as the back end of this project.So we benefit from separating the frontend from the backend and therefore we can focus onimplementing the language-specific parts, such as parsing, runtime data management, etc.. Asalready mentioned above, we need an interface between front end and back end, the intermediaterepresentation. It is necessary to find a way of representing the parser result in a way it can beunderstood by the Java Runtime. Creating abstract syntax trees for expressions and statementsis a possible way, among others, for producing an intermediate representation.

In contrast to a concrete syntax tree, which stores all parsing levels and contains also nontermi-nal symbols, the abstract syntax tree stores only operands and operators and doesn’t containany nonterminal symbols. Based on the syntax analysis of the parser, an AST can be built and

1and other .NET applications

Truffle CIL - on the JVM 18

we can so represent the structure of a CIL program. Figure 3.2 shows the AST representationfor CIL program, which performs two addition-operations.

//-------------- Programm header.assembly extern mscorlib { auto }.assembly SimpleAdd {}.module SimpleAdd.exe//-------------- Class declaration.namespace simpleprog .method public static void main() cil managed { .maxstack 2 .entrypoint .locals init (int32 a, int32 b)

ldc.i4 12 stloc a

ldc.i4 2 stloc b

ldloc a ldloc b add ldc.i4.3 add

ret}

Rootnode PushnodeConstantvaluenode

Pushnodelocal

Popnode

PushnodeConstantvaluenode

Pushnodelocal

Popnode

PushnodePopnodelocal

PushnodePopnodelocal

Pushnode Addnode

Popnode

Popnode

PushnodeConstantvaluenode

Pushnode Addnode

Popnode

Popnode

Figure 3.2: Simple CIL program and its corresponding representation as an AST

3.3 Control Flow Represantion at Runtime

The control flow describes the order in which instructions are executed. CIL offers severaldifferent branching instructions, which in principle are goto-statements. There exist branchinginstructions in CIL which jump directly to another instruction, and there exist branchinginstructions where a choice, depending on the result of the execution of the instruction, of two

Truffle CIL - on the JVM 19

. assembly extern mscor l ib { }

. assembly S imp le I f{

. ver 1 : 0 : 0 : 0}. module s i m p l e I f . exe

. method s t a t i c vo id main ( ) c i l managed{. maxstack 2. e n t r y p o i n t

l dc . i 4 21ldc . i 4 42

beq Equall d s t r " not equal "c a l l vo id [ mscor l ib ] System . Console : : Wr i teL ine ( s t r i n g )br E x i t

Equal : l d s t r " equal "c a l l vo id [ mscor l ib ] System . Console : : Wr i teL ine ( s t r i n g )

E x i t : r e t}

Listing 3.1: CIL code with a simple if statement

possible paths to follow has to be made. So how to handle the control flow during runtime, isa task that has to be tackled by a developer if he wants to implement a language like CIL. Anapproach of handling control flow during runtime is implementing dynamic dispatching.

At first we did some research on related projects and other language implementations for Graal[13]. Relevant projects we want to mention here are: TruffleC [25], GraalSqueak [18] andSulong [16]. A project which also aims to implement an intermediate language, based on gotos,is Sulong [16]. The Sulong [16] project is a LLVM bitcode interpreter built on the top of theGraalVM [13]. In Sulong one or more basic blocks, which consist of sequential instructions andend with a terminator instruction that transfers control to the next basic block, are identifiedfor a function [17].

As we can see in Listing 3.1, which implements an if statement, goto-jumps are supported byCIL. In the example the true branch of the if statement is labeled with Equal and in the casethat both values on the top of the stack are equal, it is target of the beq instruction. Knowingthe fact that goto-jumps are supported, we can conclude that every instruction can be a thepotential target of a jump instruction call. As a consequence of the support of goto-jumps, itis hard to combine instructions to a basic block, because all instructions inside such are notallowed to be the target of a jump call from outside the block.

Truffle CIL - on the JVM 20

This circumstance marks a challenge in the implementing part of the control flow. Becausewe want to support branching instructions we have to implement some kind of dispatchinglogic. As mentioned before the approach of Rigger et al. [17] does not fit completely to ourproject. To tackle this task we decided, that for our CIL implementation each instruction shouldcorrespond to a basic block. Figure 3.3 shows, based on an example, the control flow betweenthe instruction blocks. Bringing dispatching on the level of the CIL bytecode means that eachinstruction has one next successor, which points either to the instruction that comes next incode, or in the case of jump instructions, to the jump target. Except for branch instructions,these instructions have potentially two successors. One will be chosen, depending on whetherthe branch condition evaluates to true or false.

6. Instr

2. Instr

3. Instr

4. Instr

5. Instr

1. Instr

7. Instr

ldarg.0stloc.s 0

Loop: ldloc.0ldc.i4.1addldarg.1blt Loopldarg.2…

8. Instr

...

Figure 3.3: Controllflow and bytecode based dispatching in CIL

Niephaus et al. [18] proposed for the GraalSqueak project, a Smalltalk bytecode interpreter,a similar approach. They implemented bytecode nodes with either one or, in the case of aconditional jump, two successors. What we can learn from their approach is that this approachof dispatching works and performs well for their implementation of Squeak. They also showedhow to unroll a bytecode loop with hints for the Graal compiler. In a later chapter (see 4.4) weexplain in detail how to drive partial evaluation and enable loop unrolling with our approachof bytecode dispatching.

Truffle CIL - on the JVM 21

3.4 Data Representation at Runtime

In order to execute a program in any programming language, the runtime system has to supportsome kind of data representation. The most important language concepts that involve datarepresentation in IL are similar to other high level programming languages:

• Fields: Fields are data locations that are typed and named. There exist two differentkinds of fields: member and global fields. Member fields belong to a class type and aredeclared inside a class, while global fields don’t belong to any class type and are declaredoutside the scope of any class.

• Locals: Besides fields also locals are typed and named data locations. Locals are definedinside a method declaration and exists only within the method scope.

• Values: In the common language runtime values are also some kind of types. But whiledeclaring a variable of a class type only leads to the creation of a reference which initiallypoints to nothing, declaring a value type also allocates the instance of this value type.As explained by Lidin [15], value types are the types passed by value, as opposed to thereference types passed by reference.

• References: Refrence types are types which reference a data item. As defined in theECMA-335 standard [6] reference types can be divided in object types, pointer types, andinterface types.

• Arrays: Arrays are data items which store multiple items of the same declared type.The common language runtime differentiates two kinds of arrays: vectors and multidi-mensional arrays.

• Type hierarchies: In the common language runtime it is possible that one class canderive from another class.

• Type checks: By type checking we verify a reference type by resolving its reference.

To solve this challenge for the CIL-interpretation, at first we make an overview of how datais represented in the .Net-Framework. The .NET-Framework uses the so-called Common Type

Truffle CIL - on the JVM 22

System (CTS) [15], a unified type system that is shared by compilers, tools, and the CLIitself. As explained in the Standard ECMA-335 [6], this is the model that defines the rules theCLI follows when declaring, using, and managing types. So one can say, the CTS establishesa framework that not only enables cross-language integration, but also type safety and highperformance code execution. A brief example: in C# there exists the data type int and inVB.NET there exists the data type Integer. A variable declared in both languages, either asint or Integer, uses the same Int32 structure from CTS, after compilation.

Figure 3.4: Common Type System[6]

We should keep in mind that the goal of this thesis is to develop a prototype of a CIL interpreter,which can execute simple language benchmark programs. So for this reason, it is not necessaryto support the full common type system. For our needs, we want to support the following typesof the CTS:

Truffle CIL - on the JVM 23

• Built-in Value Types:

– Integer Types

– Floating Point Types

• Reference Types:

– Name Equivalent (no Delegates and no Boxed Value Types)

– Structural Equivalent (only Arrays)

– Built-In Reference Types

∗ String

∗ Object

Objects, in the context of CIL are defined as values for which it is always possible to determinethe exact type from the value. Exact types of objects are also called object types. Objects arevalues of reference types, but not all reference types describe objects [6]. As one can see inFigure 3.4 Interfaces and Pointers are also reference types but they don’t describe objects.

Implementing these types raises the question of how to represent objects. An obvious possibilitywould be to represent objects as native memory (e.g., the Java memory model [26]). Another,more high level approach, would be to represent CIL objects as Java objects. We chose thisapproach, because the Java object representation comes along with the idea of Truffle, ofmapping guest language semantics to host the language.

There already exists an implementation of an object storage model for the Truffle frameworkwhich accomplishes this. The so-called dynamic object model [27] is highly optimized andpublished as being novel. By using this object model, we don’t have to design all the runtimemechanisms for managing dynamically typed objects from scratch. What we have to do, as alanguage implementer, is to map the CIL objects & arrays (which are objects themselves) todynamic objects. How this works in detail will be explained later (see section 4.5).

Implementation: Truffle CIL 24

Chapter 4

Implementation: Truffle CIL

The following chapter explains in detail how to implement the necessary software modules forthe Truffle CIL Interpreter. The first part of the chapter is about how to parse and scan ILcode. Implementing the memory management, the control flow representation and an objectmodel to represent the typesystem will also be shown in this chapter. In particular this chapterwill point out how the Truffle framework supports us in our implementation tasks. The last partof the chapter deals with the implementation of an execution pipeline and test environment.

Above we already explained that we took the design decision to implement an abstract syntaxtree interpreter for our project. An abstract syntax tree is, among others, an intermediaterepresentation of a program. It is a syntactical representation of source code written in aprogramming language, in our case CIL code. Therefore we implemented a parser for thisproject. This was the first major implementation task for our thesis. Based upon the result ofthe syntactical analysis of the parser we can create the nodes for our AST. Creating the ASTswas the second extensive implementation for our project.

4.1 Recursive Descent Parser and Grammar

Figure 3.1 points out for the Truffle CIL interpreter a component is necessary which parses theCIL code. Because of the design decisions which are explained in section 3.1 we implemented arecursive descent parser. The recursive descent parser for Truffle CIL basically consists of twosubmodules: scanner and parser.

Implementation: Truffle CIL 25

The scanner performs the lexical analysis. It consumes the CIL code as a string and returnsthe corresponding tokens. A token represents a terminal symbol. Several tokens are defined forthe implementation of the parser:

• a none token

• tokens for basic syntax categories:

– id for alpha-numeric identifiers

– qstring for a quoted string

– sqstring for a single qouted string

– int32 token for either a decimal number or “0x” followed by a hexadecimal number,and shall be represented in 32 bits.

– int64 token for either a decimal number or “0x” followed by a hexadecimal number,and shall be represented in 64 bits.

– realnumber is any syntactic representation for a floating-point number

– hexbyte for two digit hex number

• tokens for punctuations like +,-,*,...

• tokens for instruction types

• tokens for directives like .addon, .alogrithm, ...

• tokens for keywords like abstract, aggressiveinlining, ...

• token for end of file

Implementation: Truffle CIL 26

The parser undertakes the syntactic analysis. To tackle this task we implemented a pushdownautomaton. This automaton bases on the grammar for CIL which is completely defined in theStandard ECMA-335 [6]. For each nonterminal symbol the corresponding production is definedin this standard. The parser implements a method for every nonterminal symbol, which detectsthis symbol.

4.2 Stack Handling

In the Standard ECMA-335 [6] there are three different categories of memory local to themethod documented:

• an argument table

• a local variable table

• an evaluation stack

Unlike unmanaged memory, which simply saves pointer addresses and address intervals, in CILthe memory is represented by typed data slots. Every CIL instruction which needs to transferdata has the evaluation stack as a source, a destination, or both.

CIL Method

Argument Table

Local Variable Table

EvaluationStack

Figure 4.1: Different method memory categories and data transfer between them

Figure 4.1 shows by example how these memory categories are represented in the CIL interpreterby a Truffle interpreter frame. The Truffle interpreter frame is part of the Truffle API and

Implementation: Truffle CIL 27

represents a frame containing values of local variables of the guest language. The frame isdivided in any number of frameslots. Each frameslot has an identifier and can have a differenttype.

Figure 4.2: Stack handling in the Truffle CIL interpreter

Figure 4.2 shows by example how the evaluation stack and the locals are represented in theTruffle interpreter frame. For our implementation of the CIL interpreter the first section in theframe is reserved for the evaluation stack. The .maxstack directive, which stands on the beginof every CIL method, specifies the number of slots in the evaluation stack. After the sectionfor the evaluation stack we use one frameslot for the stackpointer. Here a value is saved, whichis the index of the slot in the frame, which represents the top of the evaluation stack. If thisvalue is -1 then the evaluation stack is empty. The last section of the Truffle interpreter framecan be used for saving locals.

In the course of the Truffle CIL project we implemented a CILPushNode and a CILPopNodewhich takes on the stack manipulation. Listing 4.1 shows the implementation of theCILPushNodebased on the specialization method for pushing an integer value on the top of the stack. Atfirst the method gets the current stackpointer from the frame. Therefore, a FrameSlot with the

Implementation: Truffle CIL 28

identifier sp is defined. At next the stackpointer will be incremented so it points to next slot,the new top of stack. Now the integer value is set in this slot.

@NodeChild ( value = " valueNode " , type = CILExprNode . class )

public abstract class CILPushNode extends CILBasicInstrNode {

public CILPushNode ( i n t suc ) { super ( suc ) ; }

@Specia l iza t ion

protected i n t w r i t e I 3 2 ( Vi r tua lFrame frame , i n t value ) {

t ry {

/∗∗ get cu r ren t s t ack po in te r from frame ∗ /

FrameSlot spFrameSlot = frame . getFrameDescr iptor ( ) . f indOrAddFrameSlot ( " sp " ) ;

i n t sp = frame . g e t I n t ( spFrameSlot ) ;

/∗∗ increment s ta ckpo in te r so t h a t i t po in t s to the next s l o t ∗ /

frame . s e t I n t ( spFrameSlot , ++sp ) ;

/∗∗ set value on the top of the stack ∗ /

frame . s e t I n t ( frame . getFrameDescr iptor ( ) . ge tS lo t s ( ) . get ( sp ) , value ) ;

} catch ( FrameSlotTypeException e ) {

e . p r in tS tackTrace ( ) ;

}

return suc ;

}

. . .

}

Listing 4.1: Specialized method to push an int value onto the stack

4.3 Control Flow

Based on the result of our implemented parser, which is presented in section 4.1, we can buildthe AST. The process of creating the AST can be split in to two subtasks: For each parsedCIL instruction we have to model an AST which syntactically represents the instruction. Afterthat a dispatching logic decides the execution order of all these created ASTs. This leads us tothe challenge of control flow representation at runtime, which was already addressed in section3.3.

Implementation: Truffle CIL 29

4.3.1 Representation of Abstract Syntax Tree Nodes as Classes

Figure 4.3: AST for the add instruction

To transform the CIL bytecode into Truffle AST nodes, we implemented a recursive descendingparser for the intermediate language of .NET. During the parsing process the parser createsTruffle nodes for the AST. Basically there exist two base classes of Truffle nodes. Nodes ofthe type CILInstrNode were created for each parsed instruction in the parser. There existtwo subclasses: CILBasicInstrNode which is created for CIL instructions with exactly onepossible successor instruction and CILMultiInstrNode which represents CIL instructions withtwo different possible instructions (e.g. branching instructions). The second base type is theCILExprNode. This is the base class for all nodes that pass values to other Truffle nodes.Figure 4.3 shows the AST representation of a CIL instruction for the add instruction. Listing4.2 one can see the implementation of the CILAddNode, which is among others a inner class ofCILArithmeticNode. CILArithmeticNode in turn is a child class of CILExprNode.

Implementation: Truffle CIL 30

@NodeChildren ( { @NodeChild ( " value2Node " ) , @NodeChild ( " value1Node " ) } )

public abstract class CILAri thmet icNode extends CILExprNode {

public abstract s t a t i c class CILAddNode extends CILAri thmet icNode {

@Specia l iza t ion

protected i n t add ( i n t value2 , i n t value1 ) {

return value2 + value1 ;

}

@Specia l iza t ion

protected long add ( long value2 , long value1 ) {

return value2 + value1 ;

}

@Specia l iza t ion

protected long add ( long value2 , i n t value1 ) {

return value2 + value1 ;

}

@Specia l iza t ion

protected long add ( i n t value2 , long value1 ) {

return value2 + value1 ;

}

@Specia l iza t ion

protected f l o a t add ( f l o a t value2 , f l o a t value1 ) {

return value2 + value1 ;

}

@Specia l iza t ion

protected double add ( double value2 , double value1 ) {

return value2 + value1 ;

}

@Specia l iza t ion

protected double add ( f l o a t value2 , double value1 ) {

return value2 + value1 ;

}

@Specia l iza t ion

protected double add ( double value2 , f l o a t value1 ) {

return value2 + value1 ;

}

}

}

Listing 4.2: Implementation of CILAddNode as nested class in CILArithmeticNode

To isolate the parser from the concrete implementation of the Truffle nodes, we have written aninterface called NodeFactory. This interface declares factory methods for creating the differentnodes. Listing 4.3 illustrates this interface. The class BasicNodeFactory implements the

Implementation: Truffle CIL 31

interface, uses the concrete implementation of the nodes and is responsible for creating these.The Listing 4.4 shows, based on the example of the add instruction, the concrete implementationof such a factory method. The parser should not directly instantiate a node, but instead usethe factory facade.

public inter face NodeFactory {

RootNode createRootNode ( CILParserRuntime runt ime , i n t maxStack , Map<St r ing , Obj> loca ls , S t r i n g name) ;

void set Ins t rNodeL is tToRoot ( CILInstrNode [ ] bas i c Ins t rNodeL i s t ) ;

CILExprNode createDispatcherNode ( CILInstrNode [ ] bas i c Ins t rNodeL i s t ) ;

Node create Ins t rNode ( i n t suc ) ;

Node create Ins t rNode ( i n t trueSuc , i n t fa lseSuc ) ;

CILExprNode createSimpleConstant ( Object constant , CILType type ) ;

CILExprNode createObjNode ( Object ob j ) ;

CILInstrNode createInstrNoneNode ( S t r u c t . Kind methType , i n t opcode , CILParserRuntime runt ime , i n t suc ) ;

CILInstrNode crea te Ins t rPushVa l ( i n t opcode , CILExprNode valueNode , i n t suc ) ;

Node createBrNode ( i n t trueSuc , i n t fa lseSuc , i n t opcode ) ;

Node createCompareBrNode ( i n t trueSuc , i n t fa lseSuc , i n t opcode ) ;

Node createBui l t InMethodNode ( i n t suc , S t r i n g methodName , i n t nArgs ) ;

Node createStat ic InvokeNode ( i n t suc , CILParserRuntime runt ime , S t r i n g typeName , S t r i n g methName , i n tnArgs , S t r i n g retType ) ;

Node createMethodNode ( i n t opcode , S t r i n g typeName , S t r i n g methName , i n t nArgs , S t r i n g retTypeName , Set

<CallConv > cal lConvs , CILParserRuntime runt ime , i n t suc ) ;

Node createVarNode ( i n t opcode , i n t var , i n t suc ) ;

Node c rea te Ins t rType ( i n t opcode , S t r i n g typeName , CILParserRuntime runt ime , i n t suc ) ;

Node createFieldAccessNode ( i n t opcode , S t r i n g className , S t r i n g fieldName , CILParserRuntime runt ime ,

i n t suc ) ;

}

Listing 4.3: Interface for the Nodefactory

Implementation: Truffle CIL 32

@Overridepublic CILInstrNode createInstrNoneNode ( S t r u c t . Kind methType , i n t opcode , CILParserRuntime runt ime , i n t

suc ) {CILExprNode exprNode ;switch ( opcode ) {

case Opcode . add :exprNode = CILAr i thmet icNodeFactory . CILAddNodeGen . create (new CILPopNode ( ) , new

CILPopNode ( ) ) ;return CILPushNodeGen . create ( suc , exprNode ) ;

. . .}

}

Listing 4.4: Excerpt of the concrete implementation of the NodeFactory

4.3.2 Successor Determination

The implementation of the control flow based on an abstract syntax tree involves an importanttask for the developer: The selection of the next - the successor - instructionnode. A potentialapproach would be to take CIL bytecode, analyze it and reconstruct high level loops. On thebasis of the reconstruction, the corresponding code for the Java loop can be generated. Forthis thesis this approach we choose a different approach. As explained below in 3.3, we bringthe decision making of taking a branch to the level of the bytecode. This means that eachinstruction node for the corresponding CIL bytecode has at least one successor pointer, whichpoints to the instruction node that will be executed next. Some instruction nodes, which aregenerated for conditional branching instructions and comparative branching instructions havetwo successor pointers. Conditional branching instructions branch either if the value on the topof the evaluation stack is non-zero, which means the condition is true, or if the value is zero,which means the condition is false. In CIL there exist the following conditional branchinginstructions:

brfalse - Branch if top of the evaluation stack is 0

brfalse.s - Short-parameter form of brfalse

brtrue - Branch if top of the evaluation stack is non-zero

brtrue.s - Short-parameter form of brtrue

Comparative branching instructions consume the two values from the top of the evaluationstackand compare them. In CIL there exist the following comparative branching instructions:

Implementation: Truffle CIL 33

beq - Branch if the first value on the evaluation stack is equal to the second.

beq.s - Short-parameter form of beq.

bne.un - Branch if the two values on the evaluation stack are not equal or unordered. Un-ordered is true when at least one of the operands is a undefined floating point number.

bne.un.s - Short-parameter form of bne.un.

bge - Branch if the first value on the evaluation stack is greater or equal to the second.

bge.s - Short-parameter form of bge.

bge.un - Branch if the first value on the evaluation stack is greater or equal to the second. Inte-ger values are interpreted as unsigned and floating-point values are compared unordered.

bge.un.s - Short-parameter form of bge.un

bgt - Branch if the first value on the evaluation stack is greater than the second.

bgt.s - Short-parameter form of bgt.

bgt.un - Branch if the first value on the evaluation stack is greater than the second. Integervalues are interpreted as unsigned and floating-point values are compared unordered.

bgt.un.s - Short-parameter form of bgt.un

ble - Branch if the first value on the evaluation stack is less or equal to the second.

ble.s - Short-parameter form of ble.

ble.un - Branch if the first value on the evaluation stack is less or equal to the second. Integervalues are interpreted as unsigned and floating-point values are compared unordered.

ble.un.s - Short-parameter form of ble.un

Implementation: Truffle CIL 34

blt - Branch if the first value on the evaluation stack is less than the second.

blt.s - Short-parameter form of blt.

blt.un - Branch if the first value on the evaluation stack is less than the second. Integer valuesare interpreted as unsigned and floating-point values are compared unordered.

blt.un.s - Short-parameter form of ble.un

.method private static hidebysigdefault void Main (string[] args) cil managed

{.entrypoint .maxstack 2.locals init (

int32 V_0,bool V_1)

IL_0000: nopIL_0001: ldc.i4.0 IL_0002: stloc.0 IL_0003: br.s IL_0012

IL_0005: nopIL_0006: ldloc.0 IL_0007: call void class [mscorlib]System.Console::WriteLine(int32)IL_000c: nopIL_000d: nopIL_000e: ldloc.0 IL_000f: ldc.i4.1 IL_0010: addIL_0011: stloc.0 IL_0012: ldloc.0 IL_0013: ldc.i4.s 0x64IL_0015: cltIL_0017: stloc.1 IL_0018: ldloc.1 IL_0019: brtrue.s IL_0005

IL_001b: ret}

InstrNodeidx - 0

ldc.i4.0

InstrNodeidx - 1

InstrNodeidx - 2

InstrNodeidx - 3

InstrNodeidx - 4

InstrNodeidx - 5

InstrNodeidx - 6

InstrNodeidx - 7

InstrNodeidx - 8

InstrNodeidx - 9

InstrNodeidx - 10

InstrNodeidx - 11

InstrNodeidx - 12

InstrNodeidx - 13

InstrNodeidx - 14

InstrNodeidx - 15

stloc.0

br.s IL_0012

ldloc.0

call

ldloc.0

ldc.i4.1

add

stloc.0

ldloc.0

ldc.i4.s 0x64

clt

stloc.1

ldloc.1

brtrue.s IL_0005

ret

DispatcherNode

Figure 4.4: Dispatching of instructionnodes in Truffle CIL

Technically the ASTs, which are created for each in instruction, are an array of CILInstrNode.Figure 4.4 shows that for each instruction one node will be created. Each instruction nodehas a member variable, which holds an index that points to the CILInstrNode in the arraywhich will be executed next. We already explained above that for some branching instructionswe have two possible successors at parsetime, so for these instructions we have two membervariables, which hold the index of the two possible successor instructions. The execute method

Implementation: Truffle CIL 35

@Childrenf i n a l pr ivate CILInstrNode [ ] c i l I n s t r N o d e s ;

public CILDispatcherNode ( CILInstrNode [ ] c i l I n s t r N o d e s ) { th is . c i l I n s t r N o d e s = c i l I n s t r N o d e s ; }

public void executeGeneric ( V i r tua lFrame frame ) {i n t i c = 0 ;while ( ( i c > −1) && ( i c < c i l I n s t r N o d e s . leng th ) ) {

i c = c i l I n s t r N o d e s [ i c ] . executeVoid ( frame ) ;}

}

Listing 4.5: Code for the executionloop in class CILDispatcherNode

of the CILInstrNode returns the index of the next CILInstrNode, in the case of a branchinginstruction, the decision which branch will be taken, will be made here.

The process of interpreting these ASTs happens in the execute method of CILDispatcherN-ode. In this method we implement an execution loop which calls the execute method of theCILInstrNode, which is referenced by the instruction count, in the array.

4.4 Driving Partial Evaluation

Rigger et al. explained in their paper [17] what we understand by the process of partialevaluation in the context of the Graal compiler: Because the compiler assumes that the ASTis stable it inlines node execution methods of a hot AST into a single method. This processenables the compiler to perform optimizations over the AST. Furthermore, they explain thatthe Graal compiler checks these speculative assumptions and if the check fails it deoptimizesthe code. This means that the compiler transfers back the control from the compiled codeto interpreter. The interpreter substitutes the specialized nodes to more generic versions. Inview of good performance we want to avoid this process, because executing compiled code inthe Graal VM is much faster [22]. This implies for implementing the dispatcher node and itsexecution loop that we have to guarantee that the instruction counter is constant and Graalcan use it as a partial evaluation constant.

Figure 4.5 illustrates the execution loop for the instruction nodes, based on the example programof Figure 4.4, in which the successor of every instruction node is a partial evaluation constant.

Implementation: Truffle CIL 36

InstrNodeic=0; suc=1

InstrNodeic=1; suc=2

InstrNodeic=2; suc=9

InstrNodeic=3; suc=4

InstrNodeic=4; suc=5

InstrNodeic=5; suc=6

InstrNodeic=6; suc=7

InstrNodeic=7; suc=8

InstrNodeic=8; suc=9

InstrNodeic=9; suc=10

InstrNodeic=10; suc=11

InstrNodeic=11; suc=12

InstrNodeic=12; suc=13

InstrNodeic=13; suc=14

InstrNodeic=14; trueSuc=3;

falseSuc=15

InstrNodeic=15; suc=-1

DispatcherNode

Partial Evaluation Constant

Figure 4.5: Execution loop with constant successor

Using the example program, which is shown in Figure 4.4, in Listing 4.6 one can see which codewill be generated to unroll the execution loop.

Niephaus et al. [18] raise the problem that in an excution loop as shown in the Listing 4.5Graal does not automatically detect controlflow cycles and it can not unroll the loop. So wehave to modify the execution loop by adding compiler directives and rewriting the loop body.

In Listing 4.7 one can see the rewritten execution loop so it enables partial evaluation. Toenable loop unrolling of the compiler we added the ExplodeLoop attribute to the methodsignature. Similar to GraalSqueak [18] we use the MERGE_EXPLODE strategy, because it is meantfor bytecode interpreters. This instructs the compiler to fully explode all loops and merge copiesof the loop body that have the exact same state. In the beginning of the execution method we

Implementation: Truffle CIL 37

i c = c i l I n s t r N o d e s [ 0 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 1 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 2 ] . executeVoid ( ) ;

inst rNode9 :i c = c i l I n s t r N o d e s [ 9 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 1 0 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 1 1 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 1 2 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 1 3 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 1 4 ] . executeVoid ( ) ;

i f ( i c == 3) {i c = c i l I n s t r N o d e s [ 3 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 4 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 5 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 6 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 7 ] . executeVoid ( ) ;i c = c i l I n s t r N o d e s [ 8 ] . executeVoid ( ) ;goto inst rNode9 ;

} else {i c = c i l I n s t r N o d e s [ 1 5 ] . executeVoid ( ) ;

}

Listing 4.6: Loop unrolling

assert that the length of the array, which holds the instruction nodes, is constant. As alreadymentioned, to generate optimized code we have to guarantee that the instruction counter isreduced to a constant during the partial evaluation. So we add an assertion in the first lineof the execution loop which checks exactly this. Unlike to the simple version of the executionloop (Listing 4.5) inside the loop body it will be checked if the current instruction node is abranching instruction or not. Because a branching instruction has two possible successors theassignment of the next instruction counter in both branching cases has to be done explicitlyinside the execution loop, otherwise the instruction counter is not partial evaluation constantfor the Graal compiler. So if the instruction node is either a comparative branching node ora conditional branching node - all other instruction nodes have always one possible successor- the node will be executed and depending on the result of the comparison respectively thecondition either the true successor of the branching node will be taken as the next instructioncount or the false successor. If the current instruction node is no branching node it will simplybe executed to determine the next instructioncounter.

Implementation: Truffle CIL 38

@ExplodeLoop ( k ind = ExplodeLoop . LoopExplosionKind .MERGE_EXPLODE)public Object executeGeneric ( V i r tua lFrame frame ) {

Compi lerAsserts . compi la t ionConstant ( c i l I n s t r N o d e s . leng th ) ;i n t i c = 0 ;t ry {

while ( i c > −1) {Compi lerAsserts . pa r t i a lEva lua t i onCons tan t ( i c ) ;CILInstrNode cur Inst rNode = c i l I n s t r N o d e s [ i c ] ;

/∗∗ i f the cu r ren t i n s t r u c t i o n i s a comparat ive branching∗or c o n d i t i o n a l branching i n s t r u c t i o n∗ l i k e bne_un , bge_un , . . . or b r fa l se , b r t rue , . . .∗ /

i f ( ( cur Ins t rNode instanceof CILComparativeBrNode )| | ( cur Ins t rNode instanceof CILCondit ionalBrNode ) ) {

CILMul t i Ins t rNode branchInstrNode = ( CILMul t i Ins t rNode ) cur Inst rNode ;i n t suc = branchInstrNode . executeVoid ( frame ) ;i f ( suc == branchInstrNode . trueSuc ) {

i c = branchInstrNode . trueSuc ;continue ;

} else {i c = branchInstrNode . fa lseSuc ;continue ;

}/∗∗ i f the cu r ren t i n s t r u c t i o n has only one poss ib le successor∗ /

} else {i c = cur Ins t rNode . executeVoid ( frame ) ;continue ;

}}

} catch ( CILReturnExcept ion ex ) {/∗∗ In the i n t e r p r e t e r , record p r o f i l i n g i n fo rma t i on t h a t the f u n c t i o n has an e x p l i c i t∗ r e t u r n .∗ /

exceptionTaken . en ter ( ) ;/∗ The except ion t r a n s p o r t s the ac tua l r e t u r n value . ∗ /return ex . getResu l t ( ) ;

}

/∗∗ In the i n t e r p r e t e r , record p r o f i l i n g i n fo rma t i on t h a t the f u n c t i o n ends w i thou t an∗ e x p l i c i t r e t u r n .∗ /

nul lTaken . en ter ( ) ;/∗ Return the d e f a u l t n u l l value . ∗ /return CILNul l .SINGLETON;

}

Listing 4.7: Extended exection loop with compiler directives

Implementation: Truffle CIL 39

4.5 Dynamic Object Model

The last major implementation task of this thesis was implementing the representation of dataat runtime. As described in section 3.4 described we pursued the idea of mapping guest languagesemantics to host language to solve this task. One defintion for the term Object Model by GradyBooch [28] is: The object model encompasses the principles of abstraction, encapsulation,modularity, hierachy, typing, concurrency, and persistence . Wöß et al. introduced in theirpaper [27] a common object storage model (OSM) for Truffle that can be used by languageimplementers to develop new runtimes. By using the existing OSM we not only save thedevelopment of the object storage model of Truffle CIL from scratch, but also benefit fromthe optimizing compiler in the Truffle framework. Wöß et al. showed in their paper [27] thatthe OSM is extensible, featuring built-in support for custom extension mechanisms and is alsohigh-performance. Because we wanted to represent CIL Objects as Java Objects, and theTruffle OSM is part of the Truffle framework which we already use for the implementation ofthe interpreter, it is a logical design decision to also use the OSM.

class A {static int i;static float f;

}

Final Layout l = createLayout();Shape aShape = l.createShape(CIL_TYPE);aShape = aShape.defineProperty(“i“, 0, 0);aShape = aShape.defineProperty(“f“, 0f, 0);

A shape with all fields created by the programmer and a

prototype for all future objects

Create and allocate memory of the size and layout declared

by a shape

new A()

Figure 4.6: A C# object and its definition as a dynamic object

The Truffle OSM maps guest-language object instances to storage objects that store its datain the form of properties. The object layout in OSM is quite simple: a variable number ofproperties, each with a name, a value, and a set of attributes, are the parts of an object. InOSM there exist so-called shapes. A shape is a prototype for the object layout in memory. It

Implementation: Truffle CIL 40

Shape createShapeFromObj ( Obj c lazz , CILClassIn fo c l a s s I n f o ) {Shape this_shape = c l a s s I n f o . getSuper ( ) . shapeProtoType . createSeparateShape ( c l a s s I n f o )

for ( Obj f i e l d : c lazz . type . f i e l d s . values ( ) ) {i f ( f i e l d . k ind == Obj . Kind . var ) {

switch ( f i e l d . type . k ind ) {case Bool :

th is_shape = this_shape . de f ineProper ty ( f i e l d . name, false , 0) ;break ;

. . .}

Listing 4.8: Method for creating a shape based on its parsed CILClassInfo

public class CILNewObjNode extends CILExprNode {. . .public Object executeGeneric ( V i r tua lFrame frame ) {

Object newObj = c l a s s I n f o . shapePrototype . newInstance ( )/ / c a l l cons t r uc to r s t u f freturn newObj ;

}}

Listing 4.9: Code snippet of CILNewObjNode

defines names for properties with different attributes. Figure 4.6 compares a C# object andthe definition of the same object layout as a shape. A shape is something like a collectionof properties which can adapt dynamically by adding or removing properties. Adapting thiscollection, e.g. defining a new property, results in a new shape. In our CIL interpreter a shapewon’t change anymore at runtime, because the object layout of CIL is not dynamic. So we don’tneed dynamic objects. Knowing this fact, we can create shapes into eagerly during parsing.

The code snippet in 4.8 shows an excerpt of a method which can be called by the parser, afterthe parser has parsed the complete definition of a class, to create a shape for the class.

The Listing 4.9 shows a snippet of the implementation of the CILNewObjNode. By calling thenewInstance() method, which is implemented in the shape, a new object will be instantiatedin the execute method of the CILNewObjNode. This new object will be passed as the result ofthe CILNewObjNode.

Implementation: Truffle CIL 41

4.5.1 CILClassInfo

As mentioned above in 4.5, the parser has to create a data structure, representing the classdefinition, which we can pass to the OSM. For this reason we implemented a simple symbol tableand the class CILClassInfo, which is a helper class to realize static and strict type-system.

CILClassInfoCILClassInfo

#className: String

+CILClassInfo(className: String)

#super_: CILClassInfo#shapePrototype: Shape-staticMethods: Map<String, CallTarget>-dynamicMethods: Map<String, CallTarget>

+CILClassInfo(className: String, super_: CILClassInfo

-staticFields: Map<String, Object>

+addStaticCallTarget(methName: String, callTarget: CallTarget)+addDynamicCallTarget(methName: String, callTarget: CallTarget)

CILClassInfo root = new CILClassInfo(“System.Object“)

Figure 4.7: Structure of CILClassInfo

The CILClassInfo has members for the classname of the class it is representing and a referenceto its base-class, which is again from the type CILClassInfo. The CILClassInfo-class alsoholds three maps. The first map stores the names of all static-methods of the class, which theCILClassInfo describes, as keys and the CallTargets for these methods as the correspondingvalues. The second map stores the same for all dynamic-methods, in other words methods,which are local to the concrete object-instance. The last map stores the names of all static-fields of the class as keys. The corresponding value is from the type Object and holds theactual value of the static-field. If the static-field is declared as a primitive type, the value willbe boxed in a Java-Object e.g. int32 to Integer.

As Figure 4.7 shows, there exists a root object of the type CILClassInfo. This object representsthe class System.Object in CIL, which is the base class of every class in .NET.

Implementation: Truffle CIL 42

4.5.2 Method Dispatching

By implementing instructions in Truffle CIL, which call a method, we have to differ betweenstatic and instance methods. Methods which are shared by all instances of a type belongto static methods. Because static methods are not special to an instance of a type callingthem don’t require an instance reference. Instance methods, in contrast to static methods, areinstance-specific and they therefore need the instance reference as the first argument in themethod signature. There exists two different kinds of instance methods: virtual and nonvirtualmethods. A virtual method is marked with the keyword virtual in the method header. Unliketo nonvirtual methods, virtual methods can be overridden in derived classes by these classes’own virtual methods.[15] These methods must have the same signature. With the instructioncallvirt a virtual method is called in CIL. A virtual method is either abstract or it contains aconcrete implementation which will be executed by calling the method. In CIL virtual methodsare implemented through virtual method tables. Each class holds such a table which containsall virtual methods that are implemented inside the class. If a programmer declares a nonvirtualmethod in a derived class with the same name and method signature as in the base class, thenthe method of the base class won’t be overridden. Instead the method of the derived class hidesthe method of the base class. By explicitly specifying the owning class, the hidden method canstill be called from a derived class.

4.5.2.1 Static Dispatching

Figure 4.8 shows how the AST in Truffle CIL is implemented for static dispatching. TruffleCIL creates this tree for static call instructions. The CILInvokeNode has at least one childnode, from the class type CILDispatchStaticMethNode. The CILDispatchStaticMethNodereturns, as a result of its execution, a DirectCallNode to the CILInvokeNode. The instanceof the class CILContext exists only once in the parser runtime. The CILContext holds amap which stores class names as keys and the corresponding CILClassInfo objects as values.The CILDispatchStaticMethNode uses this map to get the CILClassInfo object for the classwhich implements the static method which should be invoked. As explained in section 4.5.1,CILClassInfo has a map, which can be used to get the CallTarget object for a static method,by using the methodname as the key. The CILDispatchStaticMethNode creates in his execu-tion routine a DirectCallNode for the CallTarget. Listing 4.10 contains the specialization of

Implementation: Truffle CIL 43

CILInvokeNode

CILPushNode

CILDispatchStaticMeth

Node

ArgumentNodes

CILPushNode

CILPopNode

CILLiteralNode

...

ArgumentNodes

CILPushNode

CILPopNode

CILLiteralNode

...

Figure 4.8: AST for static dispatching

this execution routine. The implementation inline chaches the CallTarget, this allows Truffleto perform method inlining.

In order to handle method parameters, the CILInvokeNode has another children of an arrayof CILExprNodes. The length of this array is equal to the number of parameters, because eachargument is represented by an element in this array. In CIL, the method arguments, mustbe in the same order as the method signature, on the top of the stack, before executing thecall instruction. Therefore the constructor of the CILInvokeNode fills the argument array withpopnodes.

@Specia l iza t ion ( l i m i t = " INLINE_CACHE_SIZE" )public Object doDi rec t ( @Cached( " runt ime . getLanguage ( ) " ) CILLanguage language ,

@Cached( " language . f indCILContex t ( ) " ) CILContext c i lCon tex t ,@Cached( " c i l C o n t e x t . ge tC lass In fo ( typeName ) " ) CILClassIn fo c lass In fo ,@Cached( " c l a s s I n f o . f i n d S t a t i c C a l l T a r g e t (methName) " ) Ca l lTa rge t ca l lTa rge t ,@Cached( " create ( c a l l T a r g e t ) " ) Di rectCal lNode cal lNode ) {

return cal lNode ;}

Listing 4.10: Inline cached specialization of the static dispatch

Implementation: Truffle CIL 44

@Specia l iza t ion ( guards = " d i rec tCa l lNode != n u l l " )@ExplodeLooppublic Object executeCal l ( V i r tua lFrame frame , Di rectCal lNode d i rec tCa l lNode ) {

Object [ ] args = new Object [ ci lArgumentNodes . leng th ] ;for ( i n t i =0; i <args . leng th ; i ++) {

args [ i ] = cilArgumentNodes [ i ] . executeGeneric ( frame ) ;}

return d i rec tCa l lNode . c a l l ( args ) ;}

Listing 4.11: Specialized method of the CILInvokeNode, which invokes a method

If the CILInvokeNode will be executed, at first all argument nodes will be executed. This leadsto that the method arguments will be popped from the top of the stack. By executing thecall function of the DirectCallNode with the arguments of the stack, the static method willbe invoked. The listing 4.11 contains the code for the function invocation. The result of theinvoked static method will be pushed to the top of the stack by pushnode.

4.5.2.2 Dynamic Dispatching

CILInvokeNode

CILPushNode

CILDispatchVirtMethNode

ArgumentNodes

CILPushNode

CILPopNode

CILLiteralNode

...

ArgumentNodes

CILPushNode

CILPopNode

CILLiteralNode

...

CILReadNode

Figure 4.9: AST for dynamic dispatching

Implementation: Truffle CIL 45

public DirectCal lNode d ispa tchV i r tMe th ( DynamicObject instance ,@Cached( " ins tance . getShape ( ) " ) Shape shape ,@Cached( " shape . getSharedData ( ) " ) Object sharedData ,@Cached( " castSharedData ( sharedData ) " ) CILClassIn fo c lass In fo ,@Cached( " c l a s s I n f o . f indDynamicCal lTarget (methName) " ) Ca l lTa rge t

ca l lTa rge t ,@Cached( " create ( c a l l T a r g e t ) " ) Di rectCal lNode cal lNode ) {

return cal lNode ;}

Listing 4.12: Inline cached specialization of the virtual dispatch

For call instructions or callvirt instructions associated with the keyword instance theTruffle CIL interpreter creates the AST with nodes as shown in the Figure 4.9. In contrastto the AST for static dispatching, the CILInovkeNode has a child node from the class typeCILDispatchVirtMethNode. Like for static dispatching the CILDispatchVirtMethNode re-turns, as a result of its execution, a DirectCallNode to the CILInvokeNode. As explained insection 4.5.2, instance methods are specific to the instance of the class, which is always passedas the first argument on the stack. Therefore CILDispatchVirtMethNode has a child nodefrom the type CILReadNode. This node provides the instance of the class, to which the instancemethod that should be invoked is specific. The CILReadNode reads a stackslot and returns itscontent. In contrast to a push- and a popnode, the readnode doesn’t change the stack.

In section 4.5 we explained that in the OSM instances are created based on shapes and eachinstance holds a reference to its shape. The OSM allows it to give a shape a sharedData object.For the Truffle CIL interpreter, we assign the CILClassInfo object of the corresponding class,which is represented by the shape. So we have a reference from the shape to the CILClassInfo.The listing 4.12 shows how we use this fact to get the CILClassInfo object of the instance,in the execution routine of the CILDispatchVirtMethNode. As explained above in 4.5.2.1, theCILInvokeNode has an array of childnodes, to handle methodparams. In the case of dynamicdispatching, the length of this array is the number of methodparams plus one. Because theinstance reference of the method is always an unlisted first argument.

4.5.3 Accessing Fields

As for method dispatching we have to differ between instance fields and static fields. Instancefields belong to a concrete object instance. Every time an object instance will be created, alsoits instance fields will be created. Static fields are independent of the object instance and so

Implementation: Truffle CIL 46

they are shared between all objects of the same class type. Because of this, there exisit differentinstructions for instance and static fields in CIL.

For addressing instance fields in CIL there exists the following set of instructions:

ldfld - Consumes the instance from the stack and pushes the value of the field on the stack.

ldflda - Consumes the instance from the stack and pushes a managed pointer to the instancefield on the stack.

stfld - Consumes the value and the instance reference from the stack. Stores the value in theinstance field.

These instructions are the counterpart for static fields:

ldsfld - Pushes the value of the field on the stack.

ldsflda - Pushes a managed pointer to the field on the stack.

stsfld - Consumes the value from the stack. Stores the value in the field.

As pointed out in section 4.5.1 static fields are implemented in the class CILClassInfo. For thelanguage implementer the actual implementation of the node class is very close. The Listing4.13 shows the execution routine for loading a value. Therefore, the CILStaticLoadFld uses ahashmap in the CILClassInfo, which holds the values of the fields boxed in objects.

public Object executeGeneric ( V i r tua lFrame frame ) {

return c l a s s I n f o . ge tVa lueOfS ta t i cF ie ld ( f ieldName ) ;

}

Listing 4.13: Execute method of the CILStaticLoadFld node

Figure 4.10 shows the AST for the stfld instruction. The CILStoreFld has two child nodes,both pop nodes. The reason for this is that the reference of the instance, which holds the field,as well as the value which should be stored in the field, are both passed onto the stack. Section4.5.1 describes that for each field in the definition of a class, a property in the shape will bedefined. Listing 4.14 illustrates how these properties are used to set a value in a instantiatedDynamicObject. In the example an integer value will be stored in an integer field.

Implementation: Truffle CIL 47

CILStoreFldNode

CILPopNode

CILPopNode

Figure 4.10: AST for accessing fields

@Specia l iza t ionprotected Object newArray ( Vi r tua lFrame frame , i n t len ) {

Shape shape = CILClassIn fo . roo t . shapePrototype . createSeparateShape (UUID . randomUUID ( ) ) ;i f ( typeName . equals ( " System . In t32 " ) ) {

for ( i n t i =0; i < len ; i ++) {shape = shape . de f ineProper ty ( i , 0 , 0) ;

}}else . . .return shape . newInstance ( ) ;

}

Listing 4.15: Specialized method to create an array with a static length

@Specia l iza t ion

protected i n t w r i t e I 3 2 ( Vi r tua lFrame frame , i n t value , DynamicObject ob j ) {

ob j . se t ( f ieldName , value ) ;

return suc ;

}

Listing 4.14: Specialized method to store a primitive int value in a field.

4.5.4 Arrays

In CIL the instruction newarr creates an array. The array is of a fixed length which is definedthrough a native int or int32 value on the top of the stack. The newarr instruction popsthis value, creates the array and pushes a reference to the created array back onto the top ofthe stack.

Implementation: Truffle CIL 48

In Truffle CIL arrays are implemented as DynamicObjects. Each element in the array is re-flected by in a separate property in the DynamicObject. So the DynamicObject has n propertiesat which n is the length of the array. The name of the properties corresponds to the elementindex. The specialized method of the CILNewArrayNode creates a shape with all defined prop-erties and instantiates a DynamicObject based on this shape. Listing 4.15 shows the code forcreating an integer array.

For accessing elements in an array, CIL has two different instruction sets. One for loadingelements of an array and one for storing elements in an array. There exist different load andstore instructions for several different types.

Load instructions:

ldelem.i1 - loads an element of the type int8 from the array

ldelem.u1 - loads an element of the type unsigned int8 from the array

ldelem.i2 - loads an element of the type int16 from the array

ldelem.u2 - loads an element of the type unsigned int16 from the array

ldelem.i4 - loads an element of the type int32 from the array

ldelem.u4 - loads an element of the type unsigned int32 from the array

ldelem.i8 - loads an element of the type int64 from the array

ldelem.i - loads an element of the type native int from the array

ldelem.r4 - loads an element of the type float32 from the array

ldelem.r8 - loads an element of the type float64 from the array

ldelem.ref - loads an element with an object reference from the array

Implementation: Truffle CIL 49

ldelem <token> - loads an element, which’s type is specified by the token

Store instructions:

stelem.i1 - stores a value in an element of the type int8 in the array

stelem.i2 - stores a value in an element of the type int16 in the array

stelem.i4 - stores a value in an element of the type int32 in the array

stelem.r4 - stores a value in an element of the type float32 in the array

stelem.i8 - stores a value in an element of the type int64 in the array

stelem.r8 - stores a value in an element of the type float64 in the array

stelem.i - stores a value in an element of the type native int in the array

stelem.ref - stores a value in an element of the type object reference in the array

stelem <token> - stores a value in an element of the type specified by the token in the array

All load instructions consume the index of the element which should be loaded and the refer-ence from the array which holds the element from the stack (in this order). The load instruc-tion pushes the value from the element back onto the top of the stack. For this reason theCILLoadElemNode has two popnodes as childnodes. For each different load instruction thereexists a specialization method in CILLoadElemNode. By taking the example of loading an int32value, Listing 4.16 shows that the specialization method just has to call the getter method ofthe DynamicObject and use the index as the identifier of the property.

@Specia l iza t ion

protected i n t l de lemi4 ( I n tege r idx , DynamicObject ar ray ) {

return ( I n tege r ) ar ray . get ( i dx ) ;

}

Listing 4.16: Specialized method to load an int32 value

Implementation: Truffle CIL 50

The store instructions need the value to be stored, the index of the element which should holdthe value and the reference of the array which holds the element on the top of the stack (inthis order). The store instructions pop these three items from the stack. Figure ?? illustratesthe construction of an AST for a store instruction. Because the CILStoreElemNode consumesthree items from the stack it has three popnodes as childnodes. The different store instructionslead to specialization methods for each one. The specialization method itself uses the settermethod for a property of the DynamicObject.

4.6 Testing

Starting to develop a test environment, the first question that we have to answer is how we writeour test programs. Because we want to verify that a .NET program executed in our TruffleCIL Interpreter behaves very similar to being executed in the Java Runtim, we decided to notwrite the IL-Code by ourselves. Instead we write C# programs. These programs are compiledby the compiler named csc, which is part of the mono [14] framework. The mono frameworkalso includes disassembler, which we use to generate our IL testpograms from the compiled C#programs. Figure 4.11 shows an abstraction of the execution pipeline. To verify the correctnessof our Truffle CIL Interpreter, we check the results of the executed test programs. Therefore,we compare the result of the test program executed in the Truffle CIL Interpreter with theresult of the same program executed in the mono runtime [14].

As mentioned above in section 1.3, the IL instruction set contains a high number of instructions.For that reason a large set of test cases is necessary, which cover all the different IL instructions.The JTT tests are available in the Graal repository [29] and are a wide set of tests with theaim to test java bytecode. For this thesis we ported these tests to C# programs. For our testenvironment we use the JUnit framework [30]. Each test program results in a separate testcase. We implemented a testsuite which automates the discussed execution pipeline. Eeachtest program prints its results of the execution to the output. By matching the output of theexecution in the mono runtime [14] and in our interpreter we can decide if a test failed ornot.

Implementation: Truffle CIL 51

hello.cs hello.exe hello.il

outout

csc hello.cs monodis hello.exe

mono hello.exe

Figure 4.11: Truffle CIL execution pipeline for test cases

Evaluation 52

Chapter 5

Evaluation

This chapter presents the evaluation of the Truffle CIL Interpreter. A number of algorithmsand benchmark games are evaluated with respect to the runtime performance. By comparingthe runtime performance of the Truffle CIL Interpreter with the known implementation of the.NET framework by mono, we can draw conclusions regarding the performance of the currentimplementation of our project.

The discussed Truffle CIL Interpreter, was evaluated by running a set of different programs.All benchmarks were executed on an Intel i7-5557U processor with 2 cores, 4 virtual threadsfeaturing 16GB of RAM and a core speed of 3.1 GHz running macOS Catalina(64 bit).

We parametrized each benchmark so that its execution results in high workload for our testsystem. In order to get a performance reference to compare with, we executed the benchmarkprograms in the mono runtime. We ran the benchmark programs in our Truffle CIL Interpreteron the top of the Graal VM. To find out how much our Truffle CIL Interpreter benefits fromthe support of compilation by Graal, we also ran the tests in an interpreter only mode, by usingthe standard Java JDK, instead of Graal. Because Graal optimizes functions which are calleda certain number of times, we executed each program in a loop a several amount of times. Forour evaluation we wanted to ignore the warm up phase of the compilation, so we just took thelast 10 iterations of the execution loop. For each iteration the execution time is measured. Forthese 10 iterations we calculated the arithmetic mean. In order to reduce statistical outliers werepeated this 10 times and calculated the geometric mean over the arithmetic means.

Evaluation 53

5.1 Benchmarks

The following gives a brief overview of the benchmarks used for the evaluation of the TruffleCIL interpreter.

Fibonacci For our evaluation we used an iterative implementation of the algorithm for calcu-lating the fibonacci series.

Sieve of Eratosthenes The sieve of Eratosthenes is a well known algorithm for calculatingprime numbers.

N-Body The N-Body program is part of The Computer Language Benchmarks Game [12], acollection of benchmark programs with the aim of measuring the performance of differentprogramming languages. This program is a numeric benchmark which performs a N-Bodysimulation.

Mandelbrot Mandelbrot is also part of The Computer Language Benchmarks Game [12]. Themandelbrot benchmark based on the mandelbrot set. This set is obtained from thequadratic recurrence equation [31].

Binarytrees The Computer Language Benchmarks Game include a binarytrees benchmark.This program performs many allocations of binarytrees and recursive calls up to a specifieddepth.

5.2 Runtime Performance

Figure 5.1 illustrates the results of our evaluation. The results were normalized for the chart, sothat a value of 1 represents the runtime speed which takes the respective benchmark in the monoruntime. As one can see the Truffle CIL interpreter performs quiet different for the respectivebenchmarks regarding the performance. What we can learn from our tests is that the TruffleCIL Interpreter using the Graal VM is between 26 times (binarytrees) and 226 times (sieve oferatosthenes) slower than the mono runtime. But also executing the benchmarks on the topof the Graal VM and using its support of compilation is significantly faster than executing the

Evaluation 54

Fibonacci

SieveofEratosthene

s

N-Body

Mandelbrot

Binary

trees

0

50

100

150

200

250

300

350

400

450

500

1 1 1 1 1

452

336

491

314

107

38

226

194

3824

Runtim

espeed

.NET RuntimeInterpreter onlyGraal VM

Figure 5.1: Evaluation of the Truffle CIL Interpreter

Evaluation 55

benchmarks only in the interpreter. In special in the case of the fibonacci benchmark we cansee that our implementation benefits from the optimizations performed by Graal: Executingthe benchmark in the Graal VM is 12 times faster than executing it in interpreter, withoutsupport of Graal.

This thesis does not focus on optimizing the performance but it is about to show the feasibilityof this project. So as mentioned below in chapter 7, increasing the performance will be a subjectfor future work. The reason why the N-Body and Sieve of Eratosthenes perform notably badcomparing to the other benchmarks, is that both use a lot of instructions concerning arrays.Especially the creation of arrays and the access of arrays. We explained above in section 4.5.4that arrays are implemented as DynamicObjects in Truffle CIL. In these two benchmarks wesee the impact of the overhead of the dynamic object model concerning the performance in ourimplementation. We have to keep in mind that in our implementation creating an array meansthat at runtime for each itemslot in the array a property in the shape will be defined and afterthat a dynamic object will be instantiated. So if a program uses the new array instructionoften, this leads to bad performance. Profiling revealed that storing an element in an array alsoconsumes a significantly share of the runtime. The implementation of the store instruction foran array element calls the setter method of the corresponding dynamicobject.

Related Work 56

Chapter 6

Related Work

This chapter presents related work in the context of the Truffle CIL interpreter project. Atfirst it will comment on related language implementations on top of the Truffle framework.Secondly cross-platform implementations of the .NET platform will be discussed. At the end ofthe chapter we compare these implementations with our Truffle CIL interpreter and point outthat our project is unique and novel.

Primarily the .NET runtime [4] was developed by Microsoft [8] for the Windows OperatingSystem. Meanwhile there exist a lot of cross compilers and projects which aim to make the.NET runtime cross-platform capable. Some of them will be mentioned in section 6.2, but noneof them executes the .NET intermediate language on the Java Runtime [1]. To go one stepbeyond the approach in this thesis shows how to execute .NET code on the Graal VM [13], whichis described above in section 2.3. We used the Truffle framework as a language implementationframework for our approach. As also mentioned above in 2.4, there already exists a number ofimplementations, based upon the Truffle framework, for complex programming languages. Butby the time of writing this theses there existed no implementation of a .NET language for theGraal VM.

6.1 Language implementations on top of Truffle

As previously stated there exist a number of implementations of different programming lan-guages on top of the Truffle framework. Studying these projects is revealing for our Truffle CIL

Related Work 57

interpreter. We can learn a lot which helps us to solve development tasks for our project, espe-cially how to handle control flow at runtime. The process of control flow dispatching is a majorpart of the implementation of a programming language. As in section 3.3 explained severalinstructions in IL work as goto-statements. How other language implementations, especiallythese which also support goto-statements, on the top of Truffle solve the implementation taskof dispatching at runtime will be shown in the following:

Sulong Sulong [16] is LLVM bitcode interpreter built on the Graal VM. So every programminglanguage that can be transformed to LLVM bitcode can be executed with Sulong [16]. Thisproject is among others interesting for this thesis, because it implements an intermediatelanguage similar to our Truffle CIL interpreter. Rigger et al. raise the issue in theirpaper [17] that implementing an interpreter for an intermediate language, like LLVM, ontop of Truffle is different from other language implementations for Truffle like JavaScript,Python and others: the LLVM code generates unstructured control flow which is hardto handle for an AST interpreter. The solution, which is also explained by Rigger etal. [17], is the construction of so-called basic blocks and a basic block dispatch node inthe AST. Each basic block holds a number of sequential instructions and ends with aninstruction, which transfers the control to another basic block. The basic block dispatchnode is responsible for this control transfer between the basic blocks.

.NET programs often link native libraries, because the application uses functions fromother libraries. These libraries are commonly only available as a binary in machine code.The Solung project showed how to seamlessly integrate e.g. standard C libraries by theusage of the Graal Native Function Interface (Graal NFI, [32]).

GraalSqueak Squeak is an open-source Smalltalk programming system [33]. GraalSqueak [18]in turn is a language implementation of Squeak/Smalltalk on the top of the Truffle frame-work. This project implemented dispatching on the level of bytecode nodes, and for go-toidentify basic blocks or something similar. Every node has either one or in the case of con-ditional jump two successors. Because this project shows the feasibility of this approachthis motivated us to implement a similar strategy. This project is especially interestingif we deal with the performance of such a dispatching strategy. The paper of Niephauset al. [18] explains, on the example of GraalSqueak, how to unroll a bytecode loop andwhich additional hints have to be passed into the compiler to gain good performance.

Related Work 58

Truffle/C The Truffle/C project implements a C interpreter in Java using the Truffle framework.How the interpreter and the runtime are implemented can be seen in the thesis of Rigger[34] and Grimmer [35]. How to implement a statically typed programming language suchas C, in contrast to existing implementations of dynamic languages, can be learned fromthis project. This project also shows how to use the Graal Native Function Interface(GNFI) to call native functions efficiently.

6.2 Cross-platform implementations of the .NETframework

Mono The mono platform [14] is an implemention of the .NET framework based on the ECMAstandard [6]. The primary company which works on mono is Xamarin [36], a subsidiaryof Microsoft [37]. Unlike the .NET runtime from Microsoft [37], mono is a cross-platform,which supports Windows, macOS and Linux. The mono platform comes along with anumber of tools like compilers, disassembler, etc. Because the Graal VM currently onlysupports macOS and Linux we can not use the .NET framework from Microsoft. Withregards to creating programs in CIL code for testing and so on, mono is a good additionfor the Truffle CIL project.

.NET Core .NET Core [38] is an open-source, general-purpose development platform main-tained by Microsoft [37]. .NET Core is cross-platform and runs on Windows, macOS andLinux. It implements the .NET Standard [39] and for that reason it provides compatibil-ity with the .NET Framework. The .NET Standard specifies a subset of .NET APIs thatare available on all .NET implementations on all supported platforms.

Unlike the discussed implementations of the .NET framework, which contain a runtime thatexecutes CIL code, our presented approach compiles CIL code to Java bytecode. This Javabytecode in turn will be executed in the Java runtime. The possibility to execute .NET codein the Java runtime makes our approach novel.

Future Work 59

Chapter 7

Future Work

This chapter lists future work around the Truffle CIL interpreter project which could be worthto research on. It mentions development tasks which would enhance the Truffle CIL interpreter,which is currently just a feasibility prototype.

Shared libraries At the moment Truffle CIL does not support the linking of shared libraries.So it is not possible to call native functions of the .NET API. Implementing a way tosupport shared libraries, maybe based on GNFI [32], will be definitely necessary if onewants to execute existing .NET applications in the Truffle CIL interpreter.

Generics The language feature of generics is currently not supported. It is neither possible todefine generic types nor to call a generic method.

Indirect loading and storing Indirect loading and storing is not supported by the Truffle CILinterpreter at its current state. These features are necessary for pointer operations. Indi-rect loading instructions retrieve a value from a pointer and put the value onto the topof the stack. In CIL indirect storing instructions in turn, pop a value from the stack andstore this value at a location specified by an address.

Managed exception handling Handling exceptions that are raised in the managed code, is afeature of the common language runtime. If someone wants to execute more complexprograms, then implementing the support of the deceleration of exception handlers andthe processing of these handlers in the interpreter would be a potential task for the future.

Future Work 60

Performance As already mentioned this thesis focused on showing the feasibility of the pre-sented project. Section 5 pointed out that in relation to performance there is still a lotof potential for engineering work with the aim of optimizations.

Conclusion 61

Chapter 8

Conclusion

The evaluation part of this thesis shows that our Truffle CIL interpreter performs between afactor of 24 and 226 slower than the mono .NET runtime. We have to keep in mind that thisthesis didn’t focus on performance optimization of the different instructions. The variation ofthe performance can be explained by how often certain instructions are used in the differentbenchmarks.

Even though the discussed project is a feasibility prototype it’s still pretty mature. To achievecomplete .NET support for GraalVM some more engineering power has to be invested to tacklecurrent limitations. The evaluation of the approach on a set of benchmarks shows that ourapproach of executing .NET code in the Java Runtime is feasible, but as to performance thereis some slowdown comparing to the known implementations of the .NET Runtime.

Conclusion 62

Acknowledgments

In the following let me thank some persons without whom this thesis would not have beenpossible. I have been extremely fortunate that my co-supervisor for this thesis was DavidLeopoldseder. I can not express in written words how grateful I am for his support. Notonly does he have impressive expertise in the subject matter, he also always helped me outwith my issues, gave me valuable feedback to suggested solutions and throughout this all hemotivated me in the right moments. I would then also like to thank my professor HanspeterMössenböck.

Of course I want to thank my family who always supported me amazingly. In particular I wantto thank my aunt and my uncle, Sylvia and Horst Hagmüller who motivated me to become asoftware engineer.

Finally let me thank my good friend Philip Gruber who proofread this thesis.

List of Figures 63

List of Figures

2.1 Creation of a managed .NET programm . . . . . . . . . . . . . . . . . . . . . . 112.2 Architecture of the Truffle CIL Project . . . . . . . . . . . . . . . . . . . . . . . 13

3.1 Truffle CIL System Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163.2 Simple CIL program and its corresponding representation as an AST . . . . . . 183.3 Controllflow and bytecode based dispatching in CIL . . . . . . . . . . . . . . . . 203.4 Common Type System[6] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

4.1 Different method memory categories and data transfer between them . . . . . . 264.2 Stack handling in the Truffle CIL interpreter . . . . . . . . . . . . . . . . . . . . 274.3 AST for the add instruction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294.4 Dispatching of instructionnodes in Truffle CIL . . . . . . . . . . . . . . . . . . . 344.5 Execution loop with constant successor . . . . . . . . . . . . . . . . . . . . . . . 364.6 A C# object and its definition as a dynamic object . . . . . . . . . . . . . . . . 394.7 Structure of CILClassInfo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414.8 AST for static dispatching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434.9 AST for dynamic dispatching . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444.10 AST for accessing fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474.11 Truffle CIL execution pipeline for test cases . . . . . . . . . . . . . . . . . . . . 51

5.1 Evaluation of the Truffle CIL Interpreter . . . . . . . . . . . . . . . . . . . . . . 54

Listings 64

Listings

3.1 CIL code with a simple if statement . . . . . . . . . . . . . . . . . . . . . . . . . 19

4.1 Specialized method to push an int value onto the stack . . . . . . . . . . . . . . 284.2 Implementation of CILAddNode as nested class in CILArithmeticNode . . . . . 304.3 Interface for the Nodefactory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314.4 Excerpt of the concrete implementation of the NodeFactory . . . . . . . . . . . 324.5 Code for the executionloop in class CILDispatcherNode . . . . . . . . . . . . . . 354.6 Loop unrolling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374.7 Extended exection loop with compiler directives . . . . . . . . . . . . . . . . . . 384.8 Method for creating a shape based on its parsed CILClassInfo . . . . . . . . . . 404.9 Code snippet of CILNewObjNode . . . . . . . . . . . . . . . . . . . . . . . . . . 404.10 Inline cached specialization of the static dispatch . . . . . . . . . . . . . . . . . 434.11 Specialized method of the CILInvokeNode, which invokes a method . . . . . . . 444.12 Inline cached specialization of the virtual dispatch . . . . . . . . . . . . . . . . . 454.13 Execute method of the CILStaticLoadFld node . . . . . . . . . . . . . . . . . . 464.15 Specialized method to create an array with a static length . . . . . . . . . . . . 474.14 Specialized method to store a primitive int value in a field. . . . . . . . . . . . . 474.16 Specialized method to load an int32 value . . . . . . . . . . . . . . . . . . . . . 49

Bibliography 65

Bibliography

[1] Oracle. Java. https://java.com/de/. Accessed: 2019-07-13.

[2] Microsoft. C# guide. Accessed: 2019-07-13.

[3] Tim Lindholm, Frank Yellin, Gilad Bracha, Alex Buckley, and Daniel Smith. Thejava R© virtual machine specification. https://docs.oracle.com/javase/specs/jvms/se12/html/index.html. Accessed: 2019-07-13.

[4] Microsoft. .net. https://dotnet.microsoft.com. [Online; accessed 13-July-2019].

[5] Microsoft. Visual basic guide. https://docs.microsoft.com/en-us/dotnet/visual-basic/. Accessed: 2019-07-13.

[6] ECMA International. Standard ECMA-335 - Common Language Infrastructure (CLI).Geneva, Switzerland, 5 edition, December 2010.

[7] Thomas Würthinger, Andreas Wöss, Lukas Stadler, Gilles Duboscq, Doug Simon, andChristian Wimmer. Self-optimizing ast interpreters. In Proceedings of the 8th Symposiumon Dynamic Languages, DLS ’12, pages 73–82, New York, NY, USA, 2012. ACM.

[8] Microsoft. Microsoft windows. https://www.microsoft.com/de-at/windows. Accessed:2019-07-13.

[9] Apple. macos. https://www.apple.com/de/macos. Accessed: 2019-07-13.

[10] The Linux Foundation. The linux foundation. Accessed: 2019-07-13.

Bibliography 66

[11] Christian Wimmer and Thomas Würthinger. Truffle: A self-optimizing runtime system.In Proceedings of the 3rd Annual Conference on Systems, Programming, and Applications:Software for Humanity, SPLASH ’12, pages 13–14, New York, NY, USA, 2012. ACM.

[12] The Computer Language Benchmarks Game. The computer language benchmarks game.https://benchmarksgame-team.pages.debian.net/benchmarksgame/index.html. Ac-cessed: 2019-07-13.

[13] Oracle. Graal vm. http://www.graalvm.org. [Online; accessed 1-Februar-2019].

[14] .NET Foundation and Xamarin (a Microsoft subsidiary). Mono. https://www.mono-project.com, 2004. Accessed: 2019-02-01.

[15] Serge Lidin. .NET IL Assembler. Apress, Berkely, CA, USA, 1st edition, 2014.

[16] Manuel Rigger, Matthias Grimmer, and Hanspeter Mössenböck. Sulong - execution ofllvm-based languages on the jvm: position paper. pages 1–4, 07 2016.

[17] Manuel Rigger, Matthias Grimmer, Christian Wimmer, Thomas Würthinger, andHanspeter Mössenböck. Bringing low-level languages to the jvm: Efficient execution ofllvm ir on truffle. In Proceedings of the 8th International Workshop on Virtual Machinesand Intermediate Languages, VMIL 2016, pages 6–15, New York, NY, USA, 2016. ACM.

[18] Fabio Niephaus, Tim Felgentreff, and Robert Hirschfeld. Graalsqueak: A fast smalltalkbytecode interpreter written in an ast interpreter framework. pages 30–35, 07 2018.

[19] David S. Platt. Introducing Microsoft .Net, Third Edition. Microsoft Press, USA, 3rdedition, 2003.

[20] Microsoft. .net. https://docs.microsoft.com/en-us/dotnet. [Online; accessed 15-March-2020].

[21] Tim Lindholm, Frank Yellin, Gilad Bracha, Alex Buckley, and Daniel Smith. The javavirtual machine instruction set. https://docs.oracle.com/javase/specs/jvms/se12/html/jvms-6.html. Accessed: 2019-07-13.

Bibliography 67

[22] Thomas Würthinger, Christian Wimmer, Andreas Wöß, Lukas Stadler, Gilles Duboscq,Christian Humer, Gregor Richards, Doug Simon, and Mario Wolczko. One vm to rulethem all. In Proceedings of the 2013 ACM International Symposium on New Ideas, NewParadigms, and Reflections on Programming & Software, Onward! 2013, pages 187–204,New York, NY, USA, 2013. ACM.

[23] Gilles Duboscq, Lukas Stadler, Thomas Wuerthinger, Doug Simon, Christian Wimmer,and Hanspeter Mössenböck. Graal ir: An extensible declarative intermediate representa-tion. 02 2013.

[24] Hanspeter Mössenböck, Markus Löberbauer, Albrecht Wöß, and University of Linz. TheCompiler Generator Coco/R. http://www.ssw.uni-linz.ac.at/Coco/, 2018. [Online;accessed 31-March-2019].

[25] Matthias Grimmer, Manuel Rigger, Roland Schatz, Lukas Stadler, and Hanspeter Mössen-böck. Trufflec: Dynamic execution of c on a java virtual machine. In Proceedings of the2014 International Conference on Principles and Practices of Programming on the JavaPlatform: Virtual Machines, Languages, and Tools, PPPJ ’14, pages 17–26, New York,NY, USA, 2014. ACM.

[26] James Gosling, Bill Joy, Guy Steele, and Gilad Bracha. Java(TM) Language Specification,The (3rd Edition) (Java (Addison-Wesley)). Addison-Wesley Professional, 2005.

[27] Andreas Wöss, Christian Wirth, Daniele Bonetta, Chris Seaton, Christian Humer, andHanspeter Mössenböck. An object storage model for the truffle language implementa-tion framework. In Proceedings of the 2014 International Conference on Principles andPractices of Programming on the Java Platform: Virtual Machines, Languages, and Tools,PPPJ ’14, pages 133–144, New York, NY, USA, 2014. ACM.

[28] Grady Booch. Object Oriented Design with Applications. Benjamin-Cummings PublishingCo., Inc., Redwood City, CA, USA, 1991.

[29] Oracle. graal. https://github.com/oracle/graal. [Online; accessed 1-March-2020].

Bibliography 68

[30] Kent Beck and Erich Gamma. Junit. https://junit.org/junit5/. [Online; accessed1-March-2020].

[31] MathWorld. Mandelbrot set. https://mathworld.wolfram.com/MandelbrotSet.html.Accessed: 2020-04-13.

[32] Matthias Grimmer, Manuel Rigger, Lukas Stadler, Roland Schatz, and Hanspeter Mössen-böck. An efficient native function interface for java. pages 35–44, 09 2013.

[33] Squeak.org. Squeak. https://squeak.org. [Online; accessed 1-March-2020].

[34] Manuel Rigger. Truffle/c interpreter. Master’s thesis, Johannes Kepler Universität, Linz,4 2014.

[35] Matthias Grimmer. A runtime environment for the truffle/c vm. Master’s thesis, JohannesKepler Universität, Linz, 11 2013.

[36] Xamarin. Xamarin. https://dotnet.microsoft.com/apps/xamarin. Accessed: 2019-03-15.

[37] Microsoft. Microsoft. https://www.microsoft.com. Accessed: 2019-07-13.

[38] Microsoft. .net core. https://docs.microsoft.com/en-us/dotnet/core/. Accessed:2020-03-15.

[39] Microsoft. .net standard. https://docs.microsoft.com/en-us/dotnet/standard/net-standard. Accessed: 2020-03-15.

Eidesstattliche Erklärung 69

Eidesstattliche Erklärung

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

Linz, am 18. September 2020

Patrick Hagmüller, BSc.