Noury Bouraqadi
email: noury.bouraqadi@imt-nord-europe.fr

Dave Mason
email: dmason@torontomu.ca

PharoJS: Transpiling Pharo Classes to JS ECMAScript 5 versus ECMAScript 6

Keywords: Javascript, Transpilation, Class-Based Programming, Performance

PharoJS is an open source infrastructure (framework + middleware + tools) that allows developing and testing in Pharo Smalltalk, applications which ultimately run on a JavaScript (JS) interpreter. Historically, and up to Pharo version 9, PharoJS generated JS code following the ECMAScript 5 (ES5) standard. Since, ES5 has no explicit support for classes, PharoJS transpiler had to generate JS code that mimicked the Pharo class structures, behaviors and hierachies. After migrating PharoJS to Pharo version 10, we decided to adopt the ECMAScript 6 (ES6) standard. One of the major features provided by ES6 is a set of new constructs to explicitly support class-based programming. In this paper, we describe the changes we have made to PharoJS to switch from ES5 of ES6. We describe the consequences of adopting ES6 on generated code, and the positive impact on all peformance metrics.

Introduction

JavaScript (JS) is a mainstream language that is used in various application domains. Originally designed for client-side Web development, JS-based software can be found nowadays on servers, desktops, smartphones, as well as small micro-controllers. The size of the JS community, as well as the important actors involved (e.g. Google) support a large investment in JS. The result is highly performant JS Virtual Machines (VM), and an ever-expanding ecosystem of tools, frameworks, and libraries.

We introduced PharoJS [START_REF] Bouraqadi | Mocks, proxies, and transpilation as development strategies for web development[END_REF][START_REF] Bouraqadi | Test-driven development for generated portable javascript apps[END_REF] to build software using the Pharo language and its IDE, while still benefiting from the JS ecosystem. With PharoJS, development is done 100% in Pharo. For final test stages, and for deployment, the Pharo code is transpiled to JS.

In this paper we compare two implementations of PharoJS. The historical one, relies on ECMAScript 5 (ES5). The latest one, introduced for Pharo 10, adopts the more modern ES6. We support this study by performing various benchmarks.

In the following, section 2 discusses the differences between ES5 and ES6 with respect to class-based programming. Then, section 3 presents PharoJS class transpilation, and how it evolved from ES5 to ES6 style. Section 4 reports metrics we have collected that compare the two versions of PharoJS. Before concluding remarks, section 5 discusses some related work.

Classes in ECMAScript 5 vs. ECMAScript 6 2.1. From Prototypes to Classes

JS is a prototype-based Object-Oriented (OO) language. Each object contains a collection of values termed properties. These are variables owned by the object, which may reference other objects.

Objects are white boxes. Since there is no encapsulation, properties of any object can be read or written anywhere.

JS provides single inheritance among objects. Each object references a parent through the __proto__ property. Missing properties are looked up in the parent and ancestors through the inheritance chain. The root of the inheritance tree is an object that has no prototype. That is, its __proto__ property is a null reference.

Beside prototypical objects, JS core concepts include functions and arrays. All of these can have user-defined properties. There are additional types, including symbols, booleans, numbers and strings that have pre-defined properties, but they cannot have new properties defined. 1All of the above are objects in the sense that they can have fields accessed, including invoking methods. There are also two special values for null references which are not objects: undefined and null.

The concept of class was introduced later by the ECMAScript standard committee. To ensure backward compatibility, concepts related to classes were introduced in two steps, on top of the concepts of functions and prototypes. First, ECMAScript 5 (ES5) introduced constructor functions along with the new operator to define and instantiate classes [START_REF] Stefanov | Object-Oriented JavaScript[END_REF]. Then, ECMAScript 6 (ES6) introduced the class, extends, and super keywords to explicitly define classes and better support inheritance [START_REF] Haverbeke | Eloquent Javascript[END_REF].

ECMAScript 5 Style Classes

ES5 has no explicit support for classes. Nevertheless, it's possible to emulate them. This is done by using functions as constructors along with the new operator. Such functions initialize new objects that they reference using the this pseudo-variable. In this section we describe how. A complete example is provided in appendix A.

The current object in the context of a ES5 function is referenced in functions bound to it via the this pseudo-variable. This is similar to the self pseudo-variable in Smalltalk. For the following explanations of how this is bound, assume the following preamble: funct ion s e t X Y ( newX , newY ) { t h i s . x = newX ; t h i s . y = newY ; } l e t a P o i n t = { } ;

Default property access

When a function is called other than by dot syntax, any reference to this refers to the default context. This is global in NodeJS and window in a web browser. Therefore calling: s e t X Y ( 1 0 , 2 0 ) ; would have the (presumably unintended) consequence of setting x and y in the global or window object.

Explicitly bound property access

A function can be bound to any object, making this reference it. This is can be done explicitly by calling bind() as shown in the following example:

( s e t X Y . b i n d ( a P o i n t ) ) ( 1 0 , 2 0 ) ; In this code, function setXY is bound to an empty object aPoint . After binding, the function is evaluated with parameters 10 and 20 .

In this and all following examples, the aPoint structure is initialized to become {x: 10, y: 20} .

Direct property access

When a method is invoked using the dot syntax, such as: a P o i n t . s e t = s e t X Y ; a P o i n t . s e t ( 1 0 , 2 0 ) the object aPoint is examined for a property called set which is called with this bound to the object aPoint . This example has the drawback that the object aPoint now is larger than the desired x and y properties, because it also has the set property (and in a realistic program many more properties).

__proto__ property access

If the object doesn't have the target property, the __proto__ reference is followed to a prototype object. And, this continues if the target property is not found there. a P o i n t . _ _ p r o t o _ _ = { s e t : s e t X Y } ; a P o i n t . s e t ( 1 0 , 2 0 ) This version doesn't use any additional space because the __proto__ property is defined for all objects. Additionally, objects assigned to the __proto__ property are available to all related instances.

Constructor property access

When invoking the new operator on a function, the JS interpreter creates a new object. Then, the new object is bound to the function playing the role of a constructor. Next, the bound function is performed to initialize the object. The result of the new operator is a newly created and initialized object.

To mimic classes sharing methods among their instances, the ES5 approach relies on the constructor function's prototype property. This property references an object that serves as the prototype for new objects. Upon invoking the new operator, the __proto__ of the newly created object is set to the value of the constructor function's prototype property. So, any slot added to the prototype will be available to new objects. Assuming we had the constructor function Point defined as part of the following listing: The new Point() returns a new object with the __proto__ property set to the prototype property of the constructor function. The result is the object containing the same properties as in __proto__ .

funct ion P o i n t ( ) { } //

Inheritance

Lastly, to implement a a mechanism like class inheritance, ES5 requires making the prototype property of the "subclass" constructor function inherit from the one of the "superclass" constructor functions. Thus, we rely on prototype inheritance to achieve the inheritance of instance methods.

However, we still need to ensure that instance initialization is also inherited. To do so, we call the "superclass" constructor function in the "subclass" constructor function.

Implicitly, constructor functions play the role of classes. Class side methods are simply defined properties of constructor functions. To achieve Pharo-like parallel inheritance for classes as well as their instances, we ensure in PharoJS for ES5 that a "subclass" constructor function inherits from the "superclass" constructor function.

The last brick is to support super message sends. We achieve it by binding this in a "subclass" method to the overridden "superclass" method to be called. This is illustrated in the following JS code snipet. We define an instance method instanceMethod in a "subclass" Subclass . It looks up the overriden instance method starting from its "superclass" accessed via __proto__ . Then, the inherited method is peformed on the receiver (this). The keyword class allows defining classes. A class definition may include some methods as well as a constructor function, that takes care of initializing new instances. Class-side methods can be defined using the static keyword.

S u b c

Under the hood, ES6 classes are just functions. All methods defined in a class, as well as the constructor are properties of the class' prototype . An instance has its __proto__ property set to reference the prototype of its class.

The class definition may include a reference to a superclass using the extends reserved keyword. The super keyword allow calling overridden constructors or methods, including class-side methods (keyword static).

If no superclass is provided, the root Object superclass is used. Oddly enough, this is true only for the instance side. For the class side, a class without an explicit superclass will have its __proto__ referencing prototype of the Function JS class. This is a source of metaclass compatibility issues [START_REF] Bouraqadi-Saâdani | Safe Metaclass Programming[END_REF][START_REF] Bouraqadi | Safe metaclass composition using mixin-based inheritance[END_REF]. Instance methods from Object may send messages to the class. However Object class methods are not inherited when no superclass is provided in the class definition.

PharoJS Class Transpilation

In this section we discuss how PharoJS transpiles Pharo classes to equivalent JS objects. PharoJS does support transpiling code from Pharo versions 7 and up to 10. For Pharo versions 7 up to 9, PharoJS generates JS code that complies with ES5. For Pharo 10, we have 2 different PharoJS versions:

• one generates ES5 style JS, and • the latest one produces ES6 style JS code.

In the remainder of this paper, we focus on these 2 versions of PharoJS for Pharo 10. We do use them for comparison (see section 4). References to Pharo denote Pharo 10.

Regardless of the generated JS class definition style (ES5 or ES6), there are various cases and issues to tackle:

• General case with typically application specific Pharo classes and traits.

• Referencing JS classes in Pharo.

• Pharo-like class-metaclass parallel inheritance hierarchies. • Class initialization.

General Case

The typical general case is one with a new user-defined, application-specific class. The definition of a such class as well as all its methods from both instance and class side are transpiled from Pharo to JS. Depending on the PharoJS version, we use either the ECMAScript 5 or the ECMAScript 6 style (see section 2).

Traits

In the current version of PharoJS we provide partial support for reflection, but we ignore traits as entities. Traits are non-existant in the generated JS. While classes are materialized as objects, traits are implicitly conveyed without reification. For every transpiled Pharo class that uses a trait, we generate JS code that includes the transpiled version of all trait methods.

Instance Variables and Slots

Pharo instance variables materialize as JS instance properties. They are not explicitly declared in the resulting JS code because, in JS, property declaration is implicit upon the first reference. A JS object structure is dynamic. Fields come into existence upon the first write to a property.

The current PharoJS version provides some support for reflection on instance variables. However, this is currently limited to instVarNamed: and instVarNamed:put:. Slot metaobjects are unsupported for the time being.

Class Variables

Pharo class variables are shared among the class, all its subclasses, and all their instances. Class variables are transpiled as properties of a JS object referenced by a property cp$ of the owning class. All references to class variables are transpiled as direct accesses to that object. We rely for this on JS's lack of encapsulation.

For example suppose a Pharo class MyClass has 2 class variables X and Y . The JS MyClass class resulting from transpilation will include the initialization code for property cp$ .

In ES5, we assign property cp$ as following.

MyClass . cp$ = { X : null , Y : n u l l } ;

In ES6, static property cp$ is included in the class definition.

Shared Pool Dictionaries

A shared pool is conceptually a set of variables that are shared among multiple related classes and their instances. In Pharo, shared pools are defined as subclasses of the SharedPool class. Variables belonging to shared pools are defined as class variables of SharedPool subclasses.

Upon transpilation of shared pools to JS, we adopt the same approach as Pharo. A shared pool from Pharo is transpiled as JS a class. Variables belonging to a shared pool are transpiled to class variables as presented in the dedicated section 3.1.3. For example, the transpilation of a shared pool MySharedPool with variables X and Y will produce the following ES6 style JS code 2 .

c l a s s MySharedPool extends O b j e c t { cp$ = { X : null , Y : n u l l } ; }
References to a variable belonging to some shared pool are transpiled to direct accesses. We rely on the lack of encapsulation in JS. The following listing provide JS code generated for read and write of variables X and Y from the above MySharedPool pool dictionary.

Referencing JS Classes in Pharo

We support referencing JS classes in Pharo through the concept of a JS class placeholder. A JS class placeholder is simply a Pharo class that answers true to message isJsClassPlaceholder . JS class placeholders are critical for Pharo's code portability to JS. Indeed, Pharo and JS core library APIs are different. So, we extensively use JS placeholders to extend JS core library classes such as Object and Array with methods that support Pharo APIs. So, Pharo classes that use Pharo core libaries can be transpiled as they are, without any change.

We need JS class placeholders in 3 different situations:

• Transpile a Pharo class that inherits from a JS class, typically Object.

• Send a message to a JS class, typically for instance creation.

• Extend JS classes, typically adding extra methods.

In the first two cases, JS class placeholders are empty classes. Their definitions, are simply skipped.

The last case is about JS class extensions. Methods and class variables if any, are transpiled and linked to the referenced JS class.

Implementing JS Class Extensions

In ES5 style JS implementing class extensions is straight forward. We use the same approach as for the general case of defining a class following ES5 style JS. Methods are transpiled to functions that are assigned to properties of the constructor function of it's prototype (see section 2.2). Class variables are transpiled as described in section 3.1.3.

In ES6, so called monkey patching an existing class is trickier. ES6 accepts adding extra properties referencing functions or objects to an already defined class. However, ES6 forbids creating functions outside classes that contain messages sent to super. Our workaround has 2 steps.

1. First, we transpile class extensions to anonymous classes with the same superclass as the target class. 2. Next, we assign all properties of the anonymous class to the target JS class we want to extend.

This, is illustrated in the following code where we show how to extend a presumably existing JS class ExistingJsClass . Please note that the actual generated code does the same operations as listed below, but in a slightly more optimized and less readable style. l e t e x i s t i n g J s S u p e r c l a s s = E x i s t i n g J s C l a s s . p r o t o t y p e . i n s t a n c e M e t h o d ; E x i s t i n g J s C l a s s . c l a s s M e t h o d = c l a s s E x t e n s i o n . c l a s s M e t h o d ; E x i s t i n g J s C l a s s . cp$ = E x i s t i n g J s C l a s s . cp$ ? ? { } ; O b j e c t . a s s i g n ( E x i s t i n g J s C l a s s . cp$ , c l a s s E x t e n s i o n . cp$ ) ;

It is important to point out that the anonymous class (referenced by variable classExtension ) must inherit from the superclass of the extended class ( ExistingJsClass. __proto__ ). This is because messages to super are statically bound at compile time.

The above class extension transpilation strategy supports multiple extensions for the same class. In case of collisions, methods from the last applied extensions overrwrite all the previous ones. However, we ensure the union of class variables. We rely on the Object.assign() static method to copy all class variables declared in a class extension to the target extended class.

Parallel Inheritance Hierarchies

Part of the PharoJS generated code in ES5 style deals with the inheritance link. It ensures we have parallel inheritance hierarchies (see section 2.2.6). With ES6, this is taken care of by JavaScript. However, some JS classes from core or third party libaries may suffer from the metaclass compatibility issue (see section 2.3).

Since we extend some JS classes such as Object with some instance and class side methods, we need to ensure they are inherited so we can seamlessly use them in all subclasses, including ones from core JS or provided by third party libraries. This is why we need to ensure parallel inheritance also for JS classes referenced in PharoJS applications. Such JS classes have placeholders in Pharo. So, upon transpiling placeholders, we generate code that fixes the inheritance links. The code is basically the same for both ES5 and ES6.

Class Initialization

Pharo provides class side initialize methods to allow developers perform initializations after loading classes from source code. Examples are assigning initial values to class variables or shared pools.

In the JS code generated for PharoJS, the initialize message is sent to all classes that implement a class side initialize method. We include in this list, any existing JS class with a class extension that introduces a class side initialize method.

Sometimes class initializations require other classes. Iceberg, the Pharo's package management system, ensures that all dependencies are loaded before sending initialize to newly loaded classes. To account for dependencies, PharoJS appends the initialize messages at the end of generated JS code. We thus ensure class initializations are performed after all classes are completely defined, and have clean parallel inheritance hierarchies.

Benchmarks

Metrics, Environment, and Applications

We ran benchmarks to compare the JS generated by 2 versions of the PharoJS transpiler. The only difference was that one supports EcmaScript 5 style JS classes, while the other generates JS code with classes following the Ecmascript 6 style. In this section, we report metrics we have obtained, as well as our benchmarking process.

Metric Dimensions, and Measurement Process

We have conducted benchmarks to compare the 2 PharoJS implementations with respect to the following 4 metrics:

1. JS Generation Time: This is the time taken to transpile Pharo code into a standalone working JS code; 2. JS Code Size: This is the size of JS code generated in the previous step; 3. JS Load Time: This is the load time of the generated JS; 4. JS Execution Time: This is the duration to run JS code generated from the Pharo code.

JS Generation Time

PharoJS generates standalone, ready to use, JS. This is done using PjExporter and its subclasses that wrap the PharoJS transpiler and perform the following sequence of operations:

• Transpile all classes reachable from a starting point class.

• Transpile extra classes such as PjClass that import some kernel class methods from Pharo's kernel metaclasses such as Behavior or ClassDescription .

• Transpile class extensions that provides Pharo API to core JS classes such as Object or Array . • Inject a few handwritten JS functions that allow bootstrapping core classes and support Pharo-like parallel hierarchies.

To test JS generation time we have introduced the PjExportBenchmarks class in package

PharoJs-Benchmarking-Application . We performed 100 iterations of the export of PjNodeTimingApplication and PjBrowserTimingApplication . Each export time was measured using the timeToRunWithoutGC message sent to the block performing the export. Before the 100 iterations we performed an initial Smalltalk garbageCollect .

JS Code Size

This metric results in a fixed value for a given machine, only varying if we generate for another device. However, there are differences between JS code generated for NodeJS, or for a web browser. For example, the two JS environments have different sets of global variables. This is why we do the size comparison for both.

To generate the 2 files for each JS environment, we evaluate the following expressions in a Pharo playground:

P j N o d e T i m i n g A p p l i c a t i o n e x p o r t A p p . P j B r o w s e r T i m i n g A p p l i c a t i o n e x p o r t A p p .

JS Load Time

To measure load time, we rely on JS console timers [START_REF] Mozilla | Console -webapis | mozilla[END_REF]. We generate JS code using PharoJS by sending exportApp message as described above. Then we do the following manual changes:

• Remove the last line that sends the start message to the entry-point class. This is because we don't want to measure the execution, but only the load duration. • Insert console.time('PharoJS'); as the first statement of the generated JS file. • Append console.timeEnd('PharoJS'); at the end of the generated JS file.

Note that the measured time includes class initializations as well as inheritance and metaclass links setup.

The modified JS code displays time required for 1 iteration. To have some statistically meaningful data, we run 100 iterations for each environment. No warmup was required as these benchmarks are dominated by I/O and we observed no appreciable variance. On NodeJS we introduce a shell script that calls node <generatedJsFile> 100 times. For the web browser, we appended the following lines to the PharoJS generated JS code: We rely on localStorage to save the iteration index. Then, we trigger page reload at the end of the script. This is repeated until the desired number of iterations (100) is reached. Note that by default, the web browser clears the log after each reload. So, to collect measurements, we activate Preserve log in the browser's developer tools.

JS Execution Time

For each metric, we measured the median time from 10 runs following 5 warmup runs. The duration of a single run was measured as the sum of 50 to 300 iterations. We have chosen the number of iterations to be large enough, while keeping the duration of each benchmark under 10 minutes.

Applications used for Benchmarks

The PharoJS transpiler requires an entry point class. The transpiler converts to JavaScript all classes reachable from the entry-point class. Exporter adds some extra code to allow bootstrapping core classes, and sending the message start to the entry-point class.

For the purpose of benchmarking we relied on our benchmarking infrastructure gathered under PjTimingApplication (provided as part of the PharoJS libraries). It is used in the following classes that provide some utility methods (e.g. for logging), as well as interpreter specific support methods:

• PjNodeTimingApplication : targets NodeJS.

• PjBrowserTimingApplication : targets JS interpreters embedded in web browsers PjTimingApplication measures execution time for transpiling various Pharo language constructs and objects (e.g. non-return blocks). But, the PharoJS evolution addressed in this paper focused only on class generation. So, we consider only execution time metrics for two popular micro-benchmarks involving multiple classes and message sends:

• Richards implemented in package PharoJs-Benchmarking-Richards • DeltaBlue implemented in package PharoJs-Benchmarking-DeltaBlue .

The Richards benchmark was initially written in BCPL by Martin Richards from the Computer Laboratory of the University of Cambridge, UK. It is "a machine and language independent benchmark test designed for the comparison of System Implementation Languages and their implementations on various machines by various compilers. " [START_REF] Richards | Bench -internet archive -way back machine[END_REF] The DeltaBlue benchmark is based on an incremental constraint solver, originally implemented in Smalltalk [START_REF] Freeman-Benson | The DeltaBlue algorithm: An incremental constraint hierarchy solver[END_REF]. Multiple implementations exist also for various other languages [START_REF] Marr | Are we fast yet? comparing language implementations with objects, closures, and arrays[END_REF].

Environment

Transpilation was done using 64-bit Pharo 10.0.0 build 536 images. PharoJS was installed from 2 branches sharing a common parent commit: Generation time and load time are coupled to code size. Since ES5 has no explicit support for classes, we need to generate code that mimicks the desired features, such as subclass to superclass inheritance relationships, or messages sent to super. ES6 instead provides statements and constructs that directly support them. This results in less code to generate and load.

Execution time is also positively affected with the transition from ES5 to ES6 style JS code. Generating code for ES5 that correctly captures the Smalltalk semantics produces some very non-idiomatic code, whereas the structure for ES6 is much more idiomatic. We suspect that this is a result of JS compiler and VM optimizations, for example when looking up methods in a class hierarchy.

Related Work

Related work falls into two categories: code compilation or transpilation into Javascript, and looking at the relationship of ES5 to ES6 code.

Javascript as a Target for Language Implementation

Because Javascript is so ubiquitous, it is a very popular target for code generation. There are numerous projects to translate to Javascript from a variety of languages [START_REF] Ashkenas | List of languages that compile to javascript[END_REF]. There are more than a dozen different projects for each of such popular object-oriented languages as Ruby, Python, Java, and C#, in addition to Smalltalk and Java. Detailed analysis of all of them is beyond the scope of this paper. We will focus on four of these systems.

Amber [12]

Amber is a development IDE that runs in the browser. It predates PharoJS, so was created in ES5 (actually probably ES3 or earlier), and has not upgraded. Its performance was already worse than PharoJS, so with the PharoJS performance boost from ES6 syntax, Amber falls further behind.

SqueakJS [13]

SqueakJS is handwritten Javascript implementation of a Smalltalk VM. The primary JS code is a Smalltalk VM/interpreter and the interpreter is a single object, with almost all operations calling methods on, or otherwise manipulating this.

While it is written in ES5 or earlier, we speculate that it probably doesn't suffer a significant performance hit from not having moved to ES6.

Powerlang-JS [14, 15]

Powerlang-JS parallels Pharo's Slang -that is, the transpiler only needs to be able to compile the Smalltalk interpreter, which doesn't use the full power of Smalltalk. It was written relatively recently, so was written from the beginning to use ES6.

JSweet [16, 17]

There are over a dozen systems that transpile from Java, but perhaps the most relevant is JSweet. It has a similar model to PharoJS, in that it doesn't attempt to support traditional Java interaction models such as Swing, but rather focuses on clean interaction with native Javascript packages. Because Java has a simpler execution model (no class-side inheritance, no equivalent to class variables, no DNU) and because JSweet targets TypeScript, the transpilation is more straightforward. But, since Java has static typing, mapping to Javascript libraries becomes more complex. Since JSweet targets TypeScript, the use of JS5 or JS6 is left up to the Typescript transpiler. But, the simpler execution model means there would be less of a performance hit for staying with ES5.

Relationship of ES6 to ES5

Refactoring Legacy JavaScript Code to Use Classes: The Good, The Bad and The

Ugly [START_REF] Silva | Refactoring legacy javascript code to classes: The good, the bad and the ugly[END_REF] This paper discusses various mechanisms that were chosen to migrate ES5 code to ES6. They found some of the required changes difficult to implement. We did not experience these problems with our migration because we had our own idiosyncratic implementation of classes already, so it was easy to map them to ES6 constructs. This shows an advantage of writing code in a mature, stable language like Smalltalk, and then using a tool like PharoJS to generate Javascript code -nothing to do on the part of the application programmer beyond recompilation.

On the Usage of New JavaScript Features through Transpilers: The Babel Case [19, 20, 21]

Babel is a tool to provide JS backward compatability. It allows the programmer or transpiler to target ES6 features. Babel takes care of converting them to work with earlier levels of ECMAScript.

Lebab transpiles your ES5 code to ES6/ES7 [22]

Lebab is a tool to bring ES5 code up to date with newer standards. Theoretically we could have left PharoJS generating ES5 and letting Lebab translate to ES6. However the code generated by PharoJS ES5 is quite idiosyncratic, and Lebab is not terribly sophisticated with recognising non-standard code patterns.

Conclusion & Future Work

Summary. Prior to ECMAScript 6 (ES6), JavaScript had no explicit support for class-based Object Oriented Programming. PharoJS's previous version therefore had to generate code that emulated classes and their inheritance. We have discussed how we have converted the transpilation of the Pharo/Smalltalk class structure in Javascript from classic ECMAScript 5 (ES5) to more modern ES6 Javascript. This change has resulted in a more human-readable Javascript code. It also brought a nice size and speed benefit, as shown by benchmarks we have run. For those outside the Smalltalk world, this should provide a tutorial on how to represent a somewhat tricky class structure in ES5 or ES6 Javascript.

Even though ES6 provides support for classes and inheritance, transpiling Pharo classes to ES6 has it's own challenges. We discussed the rather frequent cases where we need to extend JS core or third party classes with extra methods. These class extensions allow JS objects to align with the Pharo/Smalltalk semantics, as well as to introduce application specific methods to built-in classes. We showed that the use of intermediate anonymous classes allows addressing the tricky case of methods that send messages to super. Future Work. One of the next stages in supporting Javascript development from a Smalltalk base would be to provide better debugging support. Debugging support at the level that is possible with Smalltalk would be onerous and have a very significant performance cost. One step that would support this would be to generate a source map that would tie Javascript code back to the Smalltalk source, and support the debuggers available in browsers or NodeJS. Another possible path would be to use the Debug Adapter Protocol [START_REF]What is the debug adapter protocol?[END_REF][START_REF] Chiarulli | Understanding debug adapters: Bridging ides and debuggers[END_REF].

We are also working to improve the inter-operation of PharoJS and standard Javascript libraries, in both directions. Currently it is fairly easy to access Javascript libraries from PharoJS, although the mapping from Smalltalk selectors to Javascript function names can be quite arbitrary. We also have limited support for building libraries with PharoJS that can be seamlessly accessed from other Javascript code, and we would like to improve this support.

c l a s s

  MyClass { s t a t i c cp$ = { X : null , Y : n u l l } ; } References to class variables result into the same JS code for both ES5 and ES6. For example, read and write accesses to X and Y , in instance or class methods are transpiled as following: 1-20 // Read class variables MyClass . cp$ . X ; MyClass . cp$ . Y ; // Write class variables MyClass . cp$ . X = 1 2 3 ; MyClass . cp$ . Y = 'Hello Pharo' ;

  _ _ p r o t o _ _ ; l e t c l a s s E x t e n s i o n = c l a s s extends e x i s t i n g J s S u p e r c l a s s { i n s t a n c e M e t h o d ( ) { /* ... */ } s t a t i c c l a s s M e t h o d ( ) { /* ... */ } s t a t i c cp$ = { X : null , Y : n u l l } } E x i s t i n g J s C l a s s . p r o t o t y p e . i n s t a n c e M e t h o d = c l a s s E x t e n s i o n .

  i f ( ! l o c a l S t o r a g e . i t e r a t i o n s C o u n t ) { l o c a l S t o r a g e . i t e r a t i o n s C o u n t = 1 0 0 ; l o c a l S t o r a g e . n e x t I t e r a t i o n = 1 ; } e l s e { l o c a l S t o r a g e . n e x t I t e r a t i o n = 1 + Number ( l o c a l S t o r a g e . n e x t I t e r a t i o n ) ; } i f ( Number ( l o c a l S t o r a g e . n e x t I t e r a t i o n ) < Number ( l o c a l S t o r a g e . i t e r a t i o n s C o u n t ) ) { window . s e t T i m e o u t ( ( ) = >{ window . l o c a t i o n . r e l o a d ( ) ; } ) ; } e l s e { c o n s o l e . l o g (" > > > P h a r o J S l o a d benchmark f i n i s h e d " ) ; l o c a l S t o r a g e . c l e a r ( ) ; }

•

  pharo10 : implements ECMAScript 6 style JS class generation for Pharo 10. • ecmascript5 : implements ECMAScript 5 style JS class generation for Pharo 10. The 2 PharoJS versions are loaded by evaluating the following expression in a playground, replacing <branchName> with the name of the desired branch. M e t a c e l l o new b a s e l i n e : ' P h a r o J S ' ; r e p o s i t o r y : ' g i t h u b : / / P h a r o J S / P h a r o J S : < branchName > ' ; l o a d Pharo images run on a Pharo 100 Darwin x86 64-bit VM updated on 2022-12-07 at 20:44:51. We run benchmarks on 2 different JS interpreters: • NodeJS v19.8.

.3. ECMAScript 6 Style Classes ES6

  introduced a set of statements to explicitly support class-based programming. Those are mostly syntactic sugar. Generated entities are very close to what we have in ES5. Appendix B provides a full example.

	2
	l a s s . p r o t o t y p e . i n s t a n c e M e t h o d = funct ion ( ) {
	S u b c l a s s . _ _ p r o t o _ _ . p r o t o t y p e . i n s t a n c e M e t h o d . a p p l y ( t h i s ) ;
	}

  As presented above, we use 2 different applications to compare differences between ES5 and ES6 styles. One is a NodeJS application, for which transpilation results in a JS code generated from 82 Pharo classes. The second is a single page web application, for which JS code is obtained from 85 Pharo classes. All metrics are summarized by tables in figure1. Summary of Benchmark ResultsWe can see that the numbers for ES6 are always smaller than the ones for ES5. Meaning that latest PharoJS version generating ES6 style JS code always out-performs the previous version with ES5.

	4.2. Benchmark Results and Discussion
			File Size (KB)
		PharoJS ES5	PharoJS ES6 Difference % (ES6 -ES5)
	NodeJS	274	252	-8.03%
	Web	311	285	-8.36%
		Median of 100 loads (ms)
		PharoJS ES5	PharoJS ES6 Difference % (ES6 -ES5)
	NodeJS	40.5	30.2	-25.52%
	Web	29.3	19.5	-33.23%
		Median of 100 exports (ms)
		PharoJS ES5	PharoJS ES6 Difference % (ES6 -ES5)
	NodeJS	484.0	460.0	-4.96%
	Web	473.0	467.5	-1.16%
		Delta Blue Benchmark 300 iterations median (ms)
		PharoJS ES5	PharoJS ES6 Difference % (ES6 -ES5)
	NodeJS	124.5	115.1	-7.53%
	Web	113.0	101.2	-10.44%
		Richards Benchmark 50 iterations median (ms)
		PharoJS ES5	PharoJS ES6 Difference % (ES6 -ES5)
	NodeJS	230.0	223.1	-2.98%
	Web	215.7	202.5	-6.13%
	Figure 1:	1
	• Web browser application on Google Chrome Version 111.0.5563.146 (Official Build)
	(x86_64)
	In both cases, the computer is a Mac Book Pro 2 with:
	• CPU: 8-Core Intel Core i9, clocking at 2.3 GHz
	• RAM: 32 GB 2667 MHz DDR4
	• OS: Mac OS X Ventura 13.2.1 (22D68)
	• Hard drive: 1 TB SSD, PCI-Express with APFS File System

Although __proto__ for these objects can have properties that will be inherited.

ES5 style JS code is smilar though obviously class declarations are different.

Appendix

A. Example of Class Definition in ECMAScript 5 Style


Laurent Freidel

Simone Speziale

From twistors to twisted geometries

In a previous paper we showed that the phase space of loop quantum gravity on a fixed graph can be parametrized in terms of twisted geometries, quantities describing the intrinsic and extrinsic discrete geometry of a cellular decomposition dual to the graph. Here we unravel the origin of the phase space from a geometric interpretation of twistors.

Introduction

The phase space of loop gravity on a fixed graph is given by holonomies of the gravitational connection and fluxes of the triad field. In [START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF], we introduced a parametrization of this phase space in terms of quantities describing the intrinsic and extrinsic discrete geometry of a cellular decomposition dual to the graph. The description provides a natural extension of Regge geometries allowing for discontinuous metrics [START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF] (See also [START_REF] Rovelli | On the geometry of loop quantum gravity on a graph[END_REF][START_REF] Dittrich | Phase space descriptions for simplicial 4d geometries[END_REF]). The name twisted was meant to stress this discontinuous nature, but also to imply the existence of a relation to twistors. In fact, as we show explicitly in this brief note, the parametrization can be derived from a geometric interpretation of twistors.

Twisted geometries from phase space reduction

Our starting point is the twistor space

T ≡ 2 × 2 , (1)
with coordinates (z A , zA ), A = 0, 1. We equip T with the standard Poisson algebra,

{z A , zB } = -iδ AB , {z A , zB } = -iδ AB . (2)
In each 2 space we introduce the 2-dimensional spinors |z ≡ (z 0 , z 1 ) and |z] = (-z 1 , z0 ). Both spinors can be used to construct a 4-dimensional future-pointing null vector X µ = (X 0 , X i ).

Choosing the first one, we have

|z z| = X 0 ½ + X i σ i , (3)
* lfreidel@perimeterinstitute.ca, simone.speziale@cpt.univ-mrs.fr † Unité Mixte de Recherche (UMR 6207) du CNRS et des Universites Aix-Marseille I, Aix-Marseille II et du Sud Toulon-Var. Laboratoire affilié à la FRUMAM (FR 2291).

where σ i are the Pauli matrices. In components,

X 0 = 1 2 (|z 0 | 2 + |z 1 | 2 ) ≡ 1 2 z|z , X + = z0 z 1 , X -= z 0 z1 , X 3 = 1 2 (|z 0 | 2 -|z 1 | 2 ), (4)
with X i ≡ Tr(Xσ i ), and1 σ ± = σ 1 ± iσ 2 . Notice that ( 4) is nothing but the classical version of the well-known Schwinger representation of the angular momentum in terms of two harmonic oscillators. We can then parametrize2 * = 2 \{ z|z = 0} in terms of the null vector X µ and a phase, ϕ ≡ arg(z 0 ) + arg(z 1 ) (which is well defined provided z|z = 0),

2 * = {(X i , ϕ)}. (5)
The induced algebra reads

{X i , X j } = ǫ ij k X k , ( 6a
)
{X 0 , ϕ} = 1, {X 3 , ϕ} = 0, {X ± , ϕ} = X 0 X ∓ . (6b)
Similarly, we denote Xµ the null vector built from zA as |z z| = X0 ½ + Xi σ i , and φ the left over phase. This leads to parametrize

T * = 2 * × 2 * as T * = {(X i , Xi , ϕ, φ)}, (7)
where both (X i , ϕ) and ( Xi , φ) satisfy the same algebra (6), while they commute with each other.

Consider now the constraint

H ≡ X 0 -X0 = 0, (8)
imposing the two spatial vectors to have the same norm. This constraint generates the following U(1) action on T,

{H, z A } = i 2 z A , {H, zA } = - i 2 zA , (|z , |z ) → (e i θ 2 |z , e -i θ 2 |z ), (9)
which leaves X i and Xi invariant, while it translates the angles,

ϕ → ϕ + θ, φ → φ -θ. ( 10
)
We claim that the symplectic reduction of the eight-dimensional twistor space T * by the constraint [START_REF] Conrady | Quantum geometry from phase space reduction[END_REF] gives the six-dimensional phase space of twisted geometries

P * ≡ S 2 j × S 2 j × T * S 1 = {(N, Ñ , j, ξ)}\{j = 0}, (11)
where N and Ñ are unit vectors parametrizing the two spheres of radius j ∈ Ê\{0}, and ξ is an angle. Let us make this statement more precise. Recall [START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF] that (11) is a symplectic space locally isomorphic to the cotangent bundle of SU(2), 2

P * / 2 ∼ = T * SU(2)\{|X| = 0}, ( 12
)
where the quotient by 2 corresponds to the identification (N, Ñ , j, ξ) ↔ (-N, -Ñ , -j, -ξ).

We can now make the following Proposition 1:

T * //U(1) ∼ = P * . (14)
Proof. To prove it, it suffices to consider one of the two branches j ≷ 0 identified by (13). We consider j > 0, but the proof is analogous for j < 0. Let us denote by j > 0 the common norm of the vectors X i and Xj ,

j ≡ 1 2 (X 0 + X0 ), (15a)
and introduce the unit vectors

N i = X i j , Ñ i = Xi j . (15b)
In order to make contact between the original variables z A and (15b), we need to parametrize the vectors on the sphere as N (z) in terms of the stereographic complex coordinate z. For instance using the conventions of [START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF],

N i (z) = 1 (1 + |z| 2 ) (1 -|z| 2 ), -2z, -2z , i = (3, -, +)
and the same for Ñ (z). Then taking (4), we see that (15b) is achieved through the Hopf maps z ≡ -z 1 /z 0 , z ≡ -z1 / z0 . The variables j, N i and Ñ i span a 5-dimensional subspace commuting with the constraint (8). Hence, it only remains to identify the sixth and last variable spanning the reduced phase space. To do so, we evaluate

{i ln z A zA , H} = 1, {i ln zA zA , H} = -1, {i ln z A zA , j} = 1 2 , {i ln zA zA , j} = 1 2 .
From these brackets it follows that if we define

ξ A ≡ i ln z A zA + ln zA zA , (15c)
we have

{ξ A , H} = 0, {ξ A , j} = 1. ( 16
)
That is, both ξ 0 and ξ 1 commute with the constraint, and furthermore are conjugated to j. They are thus equally valid choices for the reduced space, related by the canonical transformation

ξ 1 = ξ 0 + 2 arg(z) + 2 arg(z).
We conclude that the reduced phase space is spanned by (N (z), Ñ (z), j, ξ A ). Concerning its Poisson algebra, we have the right bracket of (16), as well as the brackets (6a) written in terms of (15b). It is also immediate to see that j commutes with both N and Ñ . The only remaining brackets to evaluate are

{ξ A , jN i } ≡ L i A (N ), (17)
which give, in cylindrical components (i = 3, -, +),

L i 0 N (z) = (1, -z, -z), L i 1 N (z) = (1, 1/z, 1/z). ( 18
)
Here L(N ) ≡ L 0 (N (z)) is precisely the Lagrangian introduced in [START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF], and

L 1 (N ) = L(N (-1/z)) = L(-N (z))
. From now on, we take ξ ≡ ξ 0 as the reduced variable. As explained in [START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF], the existence of canonical transformations which shift the ξ variable and the Lagrangian are related to changes of section in the Hopf map.

Collecting the brackets (6a), ( 16) and (17), we find

{jN i , jN j } = ǫ ij k jN k , {j Ñ i , j Ñ j } = ǫ ij k j Ñ k , {N i , Ñ j } = 0, (19a) {ξ, j} = 1, {N i , j} = 0, { Ñ i , j} = 0, (19b) {ξ, jN i } ≡ L i (N ), {ξ, j Ñ i } ≡ L i ( Ñ ), (19c)
which can be recognized as the algebra of twisted geometries, with both spheres positively oriented. 3The proof shows how the algebra (19) descends in a simple way from the canonical Poisson brackets on twistor space. We remark also that in the spirit of the Guillemin-Sternberg theorem [START_REF] Guillemin | Geometric Quantization and Multiplicities of Group Representations[END_REF], the symplectic quotient P * can be written as a complex quotient without imposing the constraints:

T * //U(1) ∼ = P * ∼ = T * / , (20)
where the action is given by:

(|z , |z ) → (λ|z , λ -1 |z ).
It is indeed trivial to show that we can always reach the constraint surface by choosing λ = z|z / z|z . Let us go back to the symplectomorphism (12), and notice that together with (14), it implies the symplectic reduction from twistor space to the cotangent bundle of SU [START_REF] Rovelli | On the geometry of loop quantum gravity on a graph[END_REF]. For completeness, we now give explicitly this alternative reduction.

Proposition 2:

T * //U (1) ∼ = T * SU(2)\{|X| = 0}. ( 21
)
Proof. Recall [START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF] that if we trivialize T * SU(2) ∼ = su(2) × SU(2) as (X, g) with right-invariant vector fields X, we have that

X ≡ -g -1 Xg (22)
is a left-invariant vector field and that the Poisson algebra on linear functions reads

{X i , X j } = ǫ ij k X k , { Xi , Xj } = ǫ ij k Xk , {X i , g} = -τ i g, { Xi , g} = gτ i . ( 23
)
The first two brackets hold automatically in the reduction of Ì * , since X i and Xi commute with H and satisfy (6a). It thus suffices to find g(z A , zA ) in Ì * such that (i) it is an SU(2) group element, (ii) it commutes with H, and (iii) it satisfies ( 22) and (23). It is not hard to see that

g(z A , zA ) ≡ |z [z| -|z] z| z|z z|z , (24)
fulfills (i -iii). Indeed, thanks to z|z] = 0, one can check that this map satisfies

g|z] = |z , g|z = -|z],
gg † = g † g = ½.

(

) 25
The commutation with H is straightforward. A less trivial calculation shows also that the matrix elements commute among themselves when H = 0 is satisfied. Finally, (22) follows from g|z][z|g † = |z z|, and (23) from the brackets (2) and the parametrization (4).

Null twistors

Thus far, we have connected the twisted geometries to pairs of spinors in 4 . We now show that our construction is effectively related to twistors, in particular to null twistors. To that end, let us briefly review some basic facts about twistors, refering the reader to the literature [START_REF] Penrose | Spinors and space-time[END_REF] for details. A twistor Z α ∈ 4 can be viewed as a pair of spinors Z α = (|ω , |π ), where |π defines a null direction p π = |π][π| in Minkowski space, while |ω defines a point x in complexified Minkowski space via |ω = ix|π . On twistor space there is a natural hermitian pairing given by Zα Z α = ω|π + π|ω , and the quantity s = Zα Z α /2 is called the helicity of the twistor. When a twistor is null, i.e. s = 0, the matrix x is Hermitian and thus identifies a point in real Minkowski space. However, x is defined only up to the addition of a null momentum p π , since p π |π = 0. The resulting null ray x + λp π can be explicitly reconstructed as

x(λ) = |ω ω| i ω|π + λ|π][π|, λ ∈ Ê. (26)
Hence, a null twistor defines a null generator p π and a null ray in Minkowski space. We call these data a "ruled" null ray, since the ray has a specific generator. The relation between twistors and twisted geometries is established through the map

|ω ≡ |z + |z], |π ≡ |z -|z]. ( 27
)
Under this map the twistor Hermitian pairing becomes

s = 1 2 ω|π + π|ω = z|z -[z|z]. ( 28
)
Then, the constraint H = 0 in ( 8) is equivalent to the statement that Z α (z, z) is a null twistor, and the U(1) action ( 9) translates into a global rescaling of Z α :

Z α = (|ω , |π ) → (e i θ 2 |ω , e i θ 2 |π ) = e i θ 2 Z α . ( 29
)
Therefore P * , which is the symplectic reduction of the space {(|z , |z )} by H = 0, can be interpreted as a phase space of null twistors TN up to a global phase,

P * = TN/U(1). ( 30
)
This is the connection between (null) twistors and twisted geometries. Notice that the U(1) rescaling (29) leaves invariant the ruled null ray x + λp π defined by Z α , thus (30) means that an element of P * defines a ruled null ray. The reverse is also true: Given a null ray in Minkowski space with a specific null generator, we can reconstruct uniquely a null twistor up to a global phase, and hence an element of the phase space P * .

This mathematical correspondence shows that we can think of an element of P * , the edge phase space of loop quantum gravity, as a ruled null ray in Minkowski space. Whether this is just a mathematical correspondence, or it has a deeper geometrical origin, is still a mystery for us, and a fascinating one.

Geometrical meaning of the constraints

To understand the geometrical meaning of the constraints H e , consider a cellular decomposition dual to the graph. A twisted geometry assigns to each face (dual to the edge e) its oriented area j e , the two unit normals N e and Ñe as seen from the two vertex frames sharing it, and an additional angle ξ e related to the extrinsic curvature between the frames. Working with 4 e = {(z A , zA )} e = {(N, Ñ , X 0 , X0 , ϕ, φ)} e corresponds to relaxing the uniqueness of the area, and assigning to each face two areas X 0 e and X0 e (and their conjugate variables ϕ e and φe ), one for each polyhedral frame. The constraints H e impose the matching of these areas (as well as reducing ϕ e and φe to a single ξ e ). This is the geometric meaning of the constraints H e = 0. What we have shown is that the phase space of loop quantum gravity on a fixed graph can be obtained starting from a geometric intepretation of twistors and imposing an area matching condition equivalent to say that the twistors are null.

Conclusions

Let us summarize. We unraveled a relation between the space P * of twisted geometries, isomorphic to T * SU(2), and null twistors in 4 . Since the phase space of loop quantum gravity on a fixed graph is just the Cartesian product × e T * SU(2), our results imply that it can be derived starting from the larger space × e 4 , and then imposing the area matching constraint (8) at each edge. The derivation can be done in both the usual holonomy-flux parametrization (g e , X e ) (Proposition 2), or in the twisted geometries parametrization (N e , Ñe , j e , ξ e ) (Proposition 1).

An interesting aspect of the twistor description is that it admits a complete factorization over the vertices, as opposed to the edges:

× e 4 = × v 2E(v) , (31)
where E(v) is the valency of the vertex v. This result follows straighforwardly once we use the orientation of the edges to uniquely assign |z to say the source vertex, and |z to the target one. The factorization over the vertices is an interesting spin-off of the twistor description, and can lead to useful applications (e.g. [START_REF] Freidel | U(N) Coherent States for Loop Quantum Gravity[END_REF]). Twistors and twisted geometries form natural spaces that can be associated to a graph. They admit simple geometric interpretations, and are related to loop gravity. Specifically, to the kinematical (i.e. prior to imposing the Gauss law implementing gauge-invariance) phase space of loop gravity on a fixed graph. For completeness, let us also recall [START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF] that gauge-invariance is implemented reducing the space of twisted geometries by the closure conditions

C v ≡ e∈v j e N e = 0 (32)
at each vertex. The resulting space of closed twisted geometries is isomorphic to the gaugeinvariant phase space of loop gravity, × e T * SU (2)//SU (2) V . The variables parametrize it as × e T * S 1 e × v S v , where T * S 1 is the cotangent bundle of a circle, and S v is the space of shapes of a polyhedron, introduced in [START_REF] Kapovich | The symplectic geometry of polygons in Euclidean space[END_REF] and studied in relation to loop gravity in [START_REF] Conrady | Quantum geometry from phase space reduction[END_REF][START_REF] Freidel | Holomorphic Factorization for a Quantum Tetrahedron[END_REF]. Closed twisted geometries define a local flat metric on each polyhedron. However, this metric is discontinuous: although each face has a unique area, it acquires a different shape when determined from the variables associated to the two polyhedra sharing it, since there is nothing enforcing a consistent matching of the faces. This discontinuity can be traced back to the fact that the normals carry both intrinsic and extrinsic geometry.

Finally, for graphs dual to triangulations, the space of closed twisted geometries can be related to the phase space of Regge calculus when one further imposes the gluing or shape matching conditions [START_REF] Dittrich | Area-angle variables for general relativity[END_REF]. For more discussions on the relation between loop gravity/twisted geometries and discrete gravity, see discussions in [START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF][START_REF] Rovelli | On the geometry of loop quantum gravity on a graph[END_REF][START_REF] Dittrich | Phase space descriptions for simplicial 4d geometries[END_REF].

The various phase spaces that can be associated to a graph, and their relations, are summarized by the following scheme: This scheme shows how twisted geometries fit into a larger hierarchy. From top to bottom, we move from larger and simpler spaces, with less intuitive geometrical meaning, to smaller and more constrained spaces, with clearer geometrical meaning. The results establish a path between twistors and Regge geometries, via loop gravity. 4 Furthermore, notice also that each phase space but the twistor one is related to a well-known representation of general relativity on a given graph, be it loop gravity or Regge calculus. This raises the intriguing question of whether such a representation can be given directly in terms of twistors. The possibility of defining a "twistor gravity" is a fascinating new direction opened by this new way of looking at loop quantum gravity.

  Closed twisted geometries ⇐⇒ gauge-invariant loop gravity ↓ shape matching reductionRegge phase space⇐⇒ Regge calculus

Our conventions imply that σ3 = σ 3 , σ-= σ + /2, σ+ = σ -/2, so that the scalar product in these components readsX 3 Y 3 + X + Y -/2 + X -Y + /2.

We parametrize T * SU(2) ∼ = su(2) × SU(2) with a pair (X, g). The isomorphism can be made global, i.e. including the configurations j = 0 and |X| = 0, taking an appropriate closure of P * , see[START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF] for details.

With respect to the opposite orientation taken in[START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF], this different choice affects the isomorphism with T * SU(2) in a minor way, see (34b) below.

For a different relation between twistors and (two-dimensional) Regge calculus, see[START_REF] Carfora | Triangulated surfaces in twistor space: A kinematical set up for open/closed string duality[END_REF].

This was absent in[START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF] because the spheres had opposite orientations.

Appendix

In this Appendix we give a direct derivation of (24) that uses explicitly the symplectomorphism (12). To that end, let us briefly review it, refering the reader to [START_REF] Freidel | Twisted geometries: A geometric parametrisation of SU(2) phase space[END_REF] for details. We first write the unit vectors as N = nτ 3 n -1 , Ñ = ñτ 3 ñ-1 , where n ≡ n(z) ∈ SU(2) is the Hopf section corresponding to the projection

Then the (2-to-1) isomorphism is given by

where ǫ = iσ 2 is the metric tensor in spinor space. 5 The form of g guarantees that X ≡ j Ñ = -g -1 Xg.

The Hopf section defines two families of SU(2) coherent states in the fundamental representation, |n = n(z)|-and |n] = -n(z)|+ , and allows us to bridge between these and spinors through the map z = -z 1 /z 0 ,

From these two expressions one immediately finds

where we used ξ ≡ ξ 0 = -2(arg(z 0 ) + arg(z 0 )), and ǫ = iσ 2 = |+ -| -|-+|.