Thomas Dupriez
email: thomas.dupriez@univ-lille.fr

Guillermo Polito
email: guillermo.polito@inria.fr

Steven Costiou
email: steven.costiou@inria.fr

Vincent Aranega
email: vincent.aranega@univ-lille.fr

Stéphane Ducasse
email: stephane.ducasse@inria.fr

Sindarin: A Versatile Scripting API for the Pharo Debugger

Keywords: debugging, object centrics, scripting, Pharo

Debugging is one of the most important and time consuming activities in software maintenance, yet mainstream debuggers are not well-adapted to several debugging scenarios. This has led to the research of new techniques covering specific families of complex bugs. Notably, recent research proposes to empower developers with scripting DSLs, plugin-based and moldable debuggers. However, these solutions are tailored to specific use-cases, or too costly for one-time-use scenarios.

In this paper we argue that exposing a debugging scripting interface in mainstream debuggers helps in solving many challenging debugging scenarios. For this purpose, we present Sindarin, a scripting API that eases the expression and automation of different strategies developers pursue during their debugging sessions. Sindarin provides a GDB-like API, augmented with AST-bytecode-source code mappings and objectcentric capabilities. To demonstrate the versatility of Sindarin, we reproduce several advanced breakpoints and non-trivial debugging mechanisms from the literature.

Introduction

Debugging is an important part of software development. Literature describes it as a difficult task, on which developers spend sometimes more than 50% of their development time [37-39, 41, 43]. Traditional debuggers, often called breakpoint-based or online debuggers, support interactive debug sessions via a graphical user interface. In such debuggers, the developer makes a hypothesis about the cwause of a bug and manually places breakpoints in the relevant pieces of the code. When the debugger stops in a breakpoint, the developer explores the subsequent execution by commanding a step-by-step execution on it.

DLS '19, October 20, 2019, Athens, Greece Studies and work on debugging acknowledge that mainstream debuggers are not well adapted to several debugging scenarios [START_REF] Bergel | Spy: A flexible code profiling framework[END_REF][START_REF] Lieberman | Introduction[END_REF][START_REF] Perscheid | Studying the advancement in debugging practice of professional software developers[END_REF]. This has led to the appearance of new debugging techniques proposing to augment traditional interactive debuggers with, e.g., stateful breakpoints [START_REF] Bodden | Stateful breakpoints: A practical approach to defining parameterized runtime monitors[END_REF], control-flow aware breakpoints [START_REF] Chern | Debugging with control-flow breakpoints[END_REF], object-centric breakpoints [START_REF] Corrodi | Towards efficient object-centric debugging with declarative breakpoints[END_REF][START_REF] Ressia | Object-centric debugging[END_REF], the automatic insertion of breakpoints based on dynamic executions [START_REF] Zhang | Bpgen: An automated breakpoint generator for debugging[END_REF], or declarative statements from the developer [START_REF] Ko | Debugging reinvented: Asking and answering why and why not questions about program behavior[END_REF]. A line of research has also started to study scripting APIs to empower developers to implement debugging scripts adapted to their needs. These scripting APIs help developers to e.g., insert breakpoints based on cross-cutting concerns [START_REF] Yin | Defusing the Debugging Scandal -Dedicated Debugging Technologies for Advanced Dispatching Languages[END_REF] or declaratively explore post-mortem executions [START_REF] Phang | Expositor: Scriptable timetravel debugging with first-class traces[END_REF]. Even further, the moldable debugger framework [START_REF] Chis | Practical domainspecific debuggers using the moldable debugger framework[END_REF] allows developers to create domain-specific debuggers with domain-specific views and operations. With the exception of the moldable debugger framework, all these solutions are dedicated tools and the developer is left alone to benefit from them in an integrated way.

Research questions. Our research question is:

• What is a debugger API that is powerful and versatile enough to allow developers to perform many debugging tasks that would normally require specific debugging tools or tedious manual operations? In this paper we present Sindarin 1 , a scriptable on-line debugger API for Pharo 2 [START_REF] Ducasse | Pharo by Example 5[END_REF], and we use it to reproduce several debugging scenarios and advanced breakpoints from the literature. To do this, Sindarin exposes stepping and introspection operations not always appearing in mainstream debuggers such as VisualStudio or GDB. In addition, it simplifies the creation of personalized debugging scripts by providing AST mappings, thus also proposing different stepping granularity over the debugging session. Finally Sindarin facilitates object reachability to use object-centric debugging [START_REF] Costiou | Collectors. In PX'18 -Programming Experience[END_REF][START_REF] Ressia | Object-centric debugging[END_REF].

Contributions. The contributions of this paper are:

• The identification of three main requirements that a debugger API must satisfy to allow developers to solve many debugging scenarios from the literature: step granularity at the expression level, full access to execution contexts (reading and modifying) and a bytecodeto-AST mapping. • The design of a debugger API through which developers can express several debugging scenarios and breakpoints from the literature, that would normally require specific tools.

Outline. The paper is structured as follows: Section 2 presents an overview of the Sindarin API and a usage example . Section 3 presents debugging scenarios from the literature and how they are solved with compact Sindarin scripts. Section 4 shows how to use Sindarin to express advanced breakpoints from the literature. Section 5 shows how Sindarin facilitates object-centric debugging. Section 6 analyses the requirements of debugging scenarios and compares them to mainstream debugger APIs. Section 7 gives a glance at the implementation of Sindarin in the Pharo debugger and its integration in the Integrated Development Environment. It also explains how Sindarin could be integrated in mainstream software development and discusses its limitations Section 8 exposes the related work. Finally, section 9 touches upon other aspects around Sindarin, like self debugging or the generation of persistent artifacts from Sindarin scripts.

Sindarin's Overview

Sindarin is a scripting API for the Pharo debugger, implemented as an internal DSL. In this section we introduce Sindarin through an example making use of several of its features.

We then document an extract of Sindarin's API, as it is used in the rest of the paper to illustrate more advanced debugging scenarios.

Sindarin by Example

To illustrate Sindarin, let's consider a program that operates on several files inside a directory. After some seconds running, the program tries to re-open an already open file and fails. The developer would like the program to stop when a file is opened for the second time and know for this specific file how and when it was opened the first time. The Sindarin script shown in Listing 1 solves this problem 3 . It (a) first detects 3 For readers unfamiliar with the Pharo/Smalltalk syntax:

• The message-send notation uses spaces instead of dots, and there are no parenthesis to specify arguments: dbg currentNode is equivalent to

dbg.currentNode()
when a file is opened and records the stack when this happens, then (b) it steps until an already open file is opened again. Listing 1. Sindarin illustrated.

Step until a file is open twice and return the stack that was saved when it was first opened.

This script continuously steps the execution (lines 4 and 13). After each step, it checks if the execution is about to send a message (line 5) to an instance of the File class (line 6). If the message is #open (line 7), it checks in a dictionary whether the file has already been opened (line 9-10). If the file is being opened for the first time, an entry is added in the dictionary with a copy of the current execution stack as value (line 12). If there is already an entry (line 11) the stack of interest is stored in result and the script terminates.

This example illustrates several aspects of the API:

Step Operations. Sindarin commands the execution through a step method performing a step-into (line 13). Stack frame access. The stack frame is queried multiple times during the script to get the receiver of a message before it is executed (lines 6, 10) and to obtain the current stack trace (line 12). AST Mapping. Sindarin's currentNode method maps the current program counter to the corresponding AST node, allowing high-level queries on the execution (lines 5, 7). Object-reachability. All objects created and used during the execution are reachable by the Sindarin script, easing the construction of object-centric debugging scripts (lines 6, 10).

Sindarin scripts are stateful: they can define and use variables.

Solving Debugging Scenarios with Sindarin

In this section we present debugging scenarios frequently encountered by developers in the literature and we show how they can be solved with Sindarin.

Monitoring Assignments to a Variable

In this scenario described originally in [START_REF] Yin | Defusing the Debugging Scandal -Dedicated Debugging Technologies for Advanced Dispatching Languages[END_REF], a developer spots that the foo instance variable of the class Bar has, at some point of the execution, the unexpected value of 42. To find the cause of the bug, she wants to know which assignments store 42 into the variable, for what she needs to manually search all writes to that variable and set a conditional breakpoint on them. Fig. 2 shows a Sindarin script solving this problem. This script steps the execution until an assignment with value 42 (line 3) is about to be performed on the #foo variable (line 4) on an instance of the Bar class (line 5). The assignment is detected in line 2 using AST comparison. This condition makes sure the script does not catch assignments to instance variables of the same name but from another class. With this script, the developer can quickly find the unexpected assignment and proceed from there to find the cause of the bug. Listing 2. Stopping on specific assignments with Sindarin.

Stopping Before an Exception

When debugging, developers often want to stop the execution just before a certain situation occurs, for example a specific exception being raised. To raise an exception in Pharo, the message signal must be sent to a subinstance of the class Exception, so this is what the script will look for.

Using Sindarin the developer can write the script shown in Listing 3, that steps the execution until it is about to raise an exception. Listing 3. Sindarin script to step until an exception is about to be signalled. This scenario is very close to the one proposed by Haihan et al., where a developer observes a NullPointerException in a chain of dereferences [START_REF] Yin | Defusing the Debugging Scandal -Dedicated Debugging Technologies for Advanced Dispatching Languages[END_REF], as illustrated by the following Java expression total.getObjects().addAll(current.getObjects()).

In Java, when the exception is raised, the context where the problem happened and its intermediate expressions is lost: only the line number where the error was raised is available. If the developer wants to find the problematic sub-expression, she needs to rewrite the expression with one statement per line, or use a complex chaining of step into operations. However, even if the two problematics are, somehow, equivalent (a specific exception is raised), this time the developer wants to stop before the call is performed, and not before the exception is sent.

A Sindarin script solving this problem is illustrated in Listing 4. This script steps the execution until a message is about to be sent to a receiver with value nil. When the script finishes, the debugger is in the state desired by the developer: the execution is suspended in the problematic sub-expression. Listing 4. Catching message-sends to nil.

Placing Breakpoints on a Family of Methods

In this scenario, the execution opens the same file multiple times, through different file-opening methods whose names conform to a given regex pattern (e.g.,'.*open.*File.*'). The developer wants to set breakpoints in all the methods that call one of these file-opening methods to open the given file (e.g.,'myFile.txt'). With Sindarin, the developer writes the script shown in Listing 5. This way of expressing a location in the code using an expression is close to pointcuts definitions in Aspect-Oriented Programming [START_REF] Kiczales | Aspect-Oriented Programming[END_REF]. Steps the execution until the predicate is true.

dbg skipWith: obj Skips the execution of the current instruction, and puts the object obj on the execution stack.

dbg skip

Skips the execution of the current instruction, and puts nil on the execution stack.

dbg continue

Steps the execution until a breakpoint is hit. Returns a reification of the breakpoint hit.

Stack Access dbg isExecutionFinished

Returns whether the debugged execution is finished. Returns the selector of the given context.

ctx method

Returns the method of the given context.

ctx arguments

Returns the arguments of the given context.

ctx temporaries

Returns the temporary variable of the given context.

Stack Modification ctx push: aValue

Pushes aValue into the stack-frame's value stack.

ctx pop

Pops a value from the stack-frame's value stack and returns it.

AST and AST Mapping dbg currentNode

Returns the node that corresponds to the current program counter.

ast accept: visitor

Visits the current node using a visitor pattern.

ast is*Node

Returns true if the receiver is a node of the specified kind (for example: ast isMessageNode). dbg haltOnWrite: obj field: iv Breaks next time the instance variable iv of the object obj is written.

Object-Centric Debugging

Breakpoints dbg setBreakpoint

Sets a breakpoint on the current node, returns an object reifying the breakpoint.

dbg setBreakpointOn: T Sets a breakpoint on T (a node or a compiled method), returns an object reifying the breakpoint.

Stack Access Helpers dbg receiver

Returns the receiver of the current stack-frame.

dbg selector

Returns the selector of the current stack-frame.

dbg method

Returns the method of the current stack-frame.

dbg arguments

Returns the arguments of the current stack-frame.

dbg temporaries

Returns the temporary variables of the current stack-frame.

dbg messageReceiver

Returns the receiver of the message about to be sent, if the current node is a message node.

dbg messageSelector

Returns the selector of the message about to be sent, if the current node is a message node.

dbg messageArguments

Returns the arguments of the message about to be sent, if the current node is a message node.

dbg assignmentValue

Returns the value about to be assigned, if the current node is an assignment node. dbg assignmentVariableName Returns the variable name about to be assigned to, if the current node is an assignment node. This script steps through the whole execution (line 1). When the selector of the current method matches the pattern (line 2) and the argument of the current method is the file the developer is interested in (line 3), the script places a breakpoint in the method that called the current method (line 4). Once all the breakpoints are set, the developer can launch again his program and start the debugging session knowing she will stop each time the file myFile.txt is opened.

Expressing Advanced Breakpoints

In this section, we show how to use Sindarin to express more advanced breakpoints.

Control-Flow Breakpoints

In [START_REF] Chern | Debugging with control-flow breakpoints[END_REF], Chern and De Volder present a breakpoint-definition language to define breakpoints based on control-flow aspects of the execution. Their motivational example is that a method ProjectBrowser»#trySaveAs: that they want to debug is called by multiple other methods during the execution. They want the execution to stop in this method, but not if it is called by ActionSaveProjectAs»#actionPerformed: because they identified that situation to be uninteresting.

To express this, a developer using Sindarin writes the script shown in Listing 6. This script steps the execution until the current method is the desired one (line 2) and the method of the sender context is not the undesired one (lines 3-4). This scenario is slightly more complicated if the ActionSave-ProjectAs»#actionPerformed: method is not the direct sender but is lower in the stack. The developer wants to stop the execution in the ProjectBrowser»#trySaveAs: method, but not if the ActionSaveProjectAs»#actionPerformed: method is present anywhere in the stack. To solve this extended scenario, the developer replaces line 3 of the original script to look up the entire stack, as shown in Listing 7. Listing 7. Sindarin script for a control-flow breakpoint (extended scenario).

Chaining Pitons

Some bugs are only reproducible deep inside an execution. Long loops and recursive structures are examples of common programming patterns that harm debugging. Debugging in these scenarios is not only time consuming, but also requires patience and discipline from the developer. On the one hand, manual stepping is error prone: an extra step can go over the cause of the bug, invalidating the debug session and forcing the developer to restart the debug session. On the other hand, a breakpoint inside a complex computation could be triggered hundreds of times before the bug actually appears.

Pitons 4 , also called stateful breakpoints [START_REF] Bodden | Stateful breakpoints: A practical approach to defining parameterized runtime monitors[END_REF], are a sequence of breakpoints that trigger only if they are activated in the right order. That is, given a sequence of breakpoints (b 1 , b 2 ...b n ), b n will only trigger if b n-1 already triggered, and b n-1 will trigger only if b n-2 already triggered, and so on.

Building such a breakpoint with Sindarin boils down to a sequence of instructions to step the execution until the pitons are reached. Listing 8 shows a Sindarin script passing through pitons method1, method2,... up to methodN. Listing 9. Refactored Sindarin script stepping through pitons.

Divergence Breakpoints

Introducing a minor change in the source code can cause a program to behave very differently. When facing this situation, developers are left to manually step multiple executions with and without the changes, and compare them manually [START_REF] Zeller | Simplifying and isolating failure-inducing input[END_REF].

The Pillar bug Scenario. Pillar 5 is an open-source document generator. It takes a source document written in a markup syntax, and generates an output document (e.g., a pdf). The Pillar test suite is made up of more than 3000 unit tests. One of the tests failed after developers introduced an instance variable and its accessor in a Pillar class named Configuration [START_REF] Costiou | Unanticipated behavior adaptation : application to the debugging of running programs[END_REF][START_REF] Dupriez | Analysis and exploration for new generation debuggers[END_REF]. This bug was difficult to solve because the symptoms of the bug (a failing test) had no relation with the modification (introducing an accessor) and its supposed impact.

Finding Execution Divergence with Sindarin. To solve this scenario, we command two executions side-by-side and them until they diverge i.e., until both programs stack-traces differ. First, we copied the Configuration class and the failing test using this class. One test (TestOriginalClass) executes the original behavior with the original class. This test passes. The second test (TestCopyClass) uses the copy class, in which we added an instance variable and its accessor method named #disabledPhases. This test fails. Listing 10 shows a script that commands the executions of the two tests until they step in different methods. Lines 1-2, we create the two debug sessions for the two tests. Line 3-4, we start a stepping loop, in which the two sessions are stepped side-by-side until the current method of both executions are different. Analysis. At first, one execution uses an instance of the original Configuration class, and the second execution uses an instance of its modified counterpart, with the initialized new instance variable and its accessor. At some point of the execution, those instances are composed with new uninitialized instances of their own class, named sub-configuration. When a sub-configuration, instance of the modified Configuration class, receives the #disabledPhases message, it answers an uninitialized value. When a sub-configuration, instance of the original Configuration class, receives the same message, it raises a #doesNotUnderstand: exception, which starts a hidden developer-defined lookup to retrieve the value from the original Configuration instance. We are immediately able to see where the two executions diverge, and notice that adding an accessor prevents the hidden lookup to start, thus producing our bug.

Domain Specific Breakpoints

Generic debugging operations such as step or stepOver are useful because they are applicable to executions of many different domains. However, when using generic stepping operations, debugging high-level libraries and internal DSLs such as Roassal [START_REF] Bergel | Agile Visualization[END_REF] or Glamour becomes a repetitive and error prone task. Indeed, the steps are too small and go through a lot of places that the developer knows to be uninteresting. Let's take for example the internal iterators of the Pharo Collections library such as #collect:6 , whose code is shown in Listing 11. This iterator receives a block closure as argument, executes it over each element of the collection and returns a new collection with the collected elements. This iterator is defined using the #do: iterator, which applies a block closure to every element of a collection. The #do: iterator is implemented differently for each collection. When encountering a call to #collect:, the developer usually does not want to step inside the implementation of #collect: (and even less inside the one of #do:). Instead, she would like to jump through the Collection library's code to when her block closure is executed on the elements of the collection. Listing 12 illustrates how the developer writes a script that steps until the 173 th iteration of a #collect: on a large collection. Line 1 steps inside the #collect:. Line 2 captures the first argument of the invoked method: the block closure. We then call repeatedly a custom stepping function #stepToNextIteration that will step until the next invocation of the block. This stepping detects the invocation of the block when the block is used as method. Also, it recognizes a new invocation as a context it has not seen before.

Easing Object-Centric Debugging

Object-centric debugging focuses on debugging operations at the level of objects rather than static method representations and execution stacks [START_REF] Ressia | Object-centric debugging[END_REF]. For example, this includes stopping the execution when a specific object receives a message, or when the state of this particular object is accessed or modified.

The first step to debug an object is to find that object but object-centric debuggers do not provide dedicated means to find objects. We first give an example of application for object-centric debugging, then, we show how scripting the debugger with Sindarin eases acquiring objects for objectcentric debugging.

Why Object-centric Debugging: an Illustration

Imagine AtomViewer, an incarnation of the legendary Self [START_REF] Hölzle | The SELF Manual[END_REF] graphical application, displaying lots of random small shapes called atoms. Such atoms can be of different shapes (squares, dots, circles, stars, etc.). Listing 13 shows the method called by the display loop for each atom: Each atom is instance of the Atom class. Every time the display is refreshed, each atom is redrawn through the display-Atom: method. It takes the atom instance to display, and passes a random drawer object to it (line 2). This drawer knows how to display atoms, as atoms have different shapes. Each drawer uses specific display options to draw the atoms' shapes (like motion blur, glow, blend...). To that end, drawers implement different display methods, and know which method to call depending on the display options.

Imagine a bug that sometimes occurs when circular atoms that have a radius higher than 50 pixels are rendered. It is difficult to debug since we do not know which drawer is erratic, and drawers are randomly chosen at display time. In addition, circular atoms (spheres, torus, circles...) all call a different rendering behavior from drawers (lines 4, 6, 8). To avoid putting conditional breakpoints everywhere, we want to put an object-centric breakpoint on the drawer object. We also want to set this breakpoint before the drawer is passed to an atom with a problematic radius, so that the drawer halts each time it receives a rendering message. However, we cannot easily access the drawer object. It is returned by a call to the randomAtomDrawer method, used immediately as a parameter to the acceptVisitor: message, then discarded and garbage collected.

Easing Object-centric Debugging with Sindarin

With Sindarin, we control the execution when breakpoints are hit: we navigate the execution to reach the right context, from which we extract and capture objects of interest. We apply object-centric debugging operations to those objects, then continue the execution. It gives us a systematic and automatic means to capture objects for object-centric debugging.

In Listing 14, we capture the object returned by the self randomAtomDrawer message in Listing 13, before it is used as parameter to the renderWith: method. To achieve this, we set a breakpoint on the displayAtom: method (line 1). When the breakpoint is hit, we first do a stepOver. It executes the self randomAtomDrawer expression and puts the result on the value stack. At this point, the debugger is about to send the render-With: message, with anAtom as receiver, and the object that has been put on the stack as argument (the atom drawer). We use the execution introspection API to recover both objects (lines 4-5) and check conditions (line 6) before applying an objectcentric breakpoint to the drawer (line 8) and removing the original breakpoint (line 9).

Replaying Objects

Bugs can be hard to reproduce when programs contain nondeterministic aspects. For instance, in our example from Listing 13, the drawer object is chosen randomly. To replay the bug, we re-inject the problematic drawer in the control flow using the skipWith: operation (illustrated in Figure 1). Figure 1. Removing non-determinism: skipWith: anObject steps over a node about to be executed (underlined) but without executing it, and pushes anObject on the stack instead.

We show in Listing 15 how to achieve this with Sindarin. Each time we break, we capture the node whose execution creates a random drawer (line 3), and the drawer it returns (line 4-5). If conditions for replay are satisfied (line 6), we remove the original breakpoint and configure a replay breakpoint on the drawer node (line 8). When this replay breakpoint is hit (line 9), we instruct the debugger to skip over the current node (the drawer creation node) and use the captured drawer object instead (line 10).

Evaluation

We have already shown throughout the paper that Sindarin is a versatile scripting DSL that covers different debugging scenarios from the literature. In this section we Sindarin with respect to mainstream debugger APIs such as GDB or JDI. We first identify the different features required by Sindarin to implement each debugging scenario described in this paper. We then evaluate how those features are supported in several mainstream debuggers with debugger APIs.

Sindarin Debugger Requirements

Each of the debugging scenarios used throughout this paper requires a different set of features from Sindarin. We analyzed and categorized those requirements into three different axis:

Step Granularity. The granularity required for the step operation i.e., the minimal amount of code that is executed between two step operations. In our debugging scenarios, we observed the need for two different granularities: a fine-grained expression granularity and a coarse-grained method granularity. Context Access. The access provided to the execution stack. We have categorized context access in two main operations. Basic access refers to the access to the list of stack frames and their methods, receiver and argument objects, and their temporary variables. Full access refers to the access to the intermediate values of expressions during the execution (Stack access), and the ability to modify the current stack to e.g., pop or push a value into it (Stack modification). AST Mappings. Whether the scenario requires AST mappings or not to identify code of interest dynamically.

Table 2 summarizes our analysis for each of our debugging scenarios. We observed that several scenarios require expression step granularity to operate at the level of expressions instead of methods. All scenarios requiring expression step granularity also require AST mappings to identify the interesting expressions, and stack access to access intermediate results of those expressions stored in the stack. For instance, although it is possible, to some extent, to implement objectcentric breakpoints with those debuggers, the object capture scenario is mandatory for their application. This scenario is one example requiring stack access to access intermediate results. Finally, replaying objects is the only scenario in our scenario collection to require stack modification. Replaying an object instead of an expression requires to skip the expression and push the object to replay in the stack.

Comparison with Debugger APIs

Several debugger implementations provide an API or protocol to connect to it and drive the debugging of a program. Examples of these are GDB [START_REF] Richard Stallman | Debugging with GDB[END_REF] and Java's debugger interface through JDI and JVMTI [START_REF]JVM tool interface (JVMTI)[END_REF][START_REF]Java debug interface[END_REF]. In this section we take several mainstream debuggers and compare their APIs to Sindarin's, to analyze which debugging scenarios are applicable to them.

The summary of our analysis is reflected in Table 3. We identify five families of debugger APIs: (1) Unmanaged-code debuggers (UCD) covers debuggers for non-managed runtimes, for which we analyzed GDB, LLDB, WinDBG, (2) Python's basic debugger functions (BDB) [START_REF] Coetzee | Combining reverse debugging and live programming towards visual thinking in computer programming[END_REF][START_REF] Foundation | bdb -debugger framework[END_REF], (3) NodeJS debugging API, (4) Java's debugger interface through JDI and JVMTI [START_REF]JVM tool interface (JVMTI)[END_REF][START_REF]Java debug interface[END_REF] and (5) Microsoft's CLR EnvDTE debugger interface. (1) Unmanaged Code Debuggers: GDB, WINDBG, LLDB. (2) A line stepping operation at the machine instruction level is available. It matches the source code, but developers must know when to start and stop stepping to achieve an expression level granularity, as many assembly instructions/bytecodes can be related to a single instruction in source code.

(3) Possible by reading and writing directly into the memory of the program.

(4) Common Language Runtime debuggers ( C#, J#, .NET).

From our analysis we observe that most debuggers provide a rather coarse grained granularity for stepping of an entire line of code, which prevents a straight forward implementation of the scenarios discussed in this paper. Indeed, this granularity does not allow developers to differentiate between different expressions (and sub-expressions) within a single line. Unmanaged code debuggers, on the other hand, provide by default a step operation that operates at the line level while a second stepping operation does it at the machine instruction level.

Another point of divergence is the fact that not all debugger APIs allow full stack manipulation, preventing advanced scenarios such as object-replay. We consider that unmanaged code debuggers support full stack manipulation since they can modify the execution stack and the objects pointed by it as any other memory region.

Finally, no mainstream debugger provides high level mappings between instructions and AST such as Sindarin's AST mappings.

Implementation

We implemented Sindarin for the Pharo programming language. In this section we briefly describe the infrastructure of our implementation, how it integrates within the Integrated Development Environment, how it could be integrated into mainstream software development, as well as its limitations. Starting a Sindarin session. Sindarin drives a scriptable debugger instance and configures it for a dedicated piece of code that will be executed. Figure 2 shows the creation process of a scriptable debugger instance. The developer first provides a block closure of the code she wants to debug [START_REF] Barr | Time-travel debugging for javascript/node[END_REF]. The scriptable debugger then creates a process to run the closure (2), and a DebugSession to step and access the execution being debugged (3). Finally, the developer sends commands written using the Sindarin API to the scriptable debugger to control and/or inspect the debugged execution (4).

Contexts. The contexts exposed by our Sindarin implementation are of the same class as the ones returned by the Smalltalk thisContext pseudo-variable.

Step Granularity. Our implementation of Sindarin runs in Pharo. Pharo code is compiled into bytecode, that is interpreted by a virtual machine. Through reflection, Pharo programs can access the bytecode interpreter of the virtual machine and step it to progress a frozen execution. The dbg step operation of Sindarin is implemented using this feature to provide a stepping granularity at the expression level.

Context Access. Sindarin provides full access to the contexts of the debugged execution through instances of the Context class. Context objects are automatically reified by the Pharo virtual machine when needed for reflection purposes.

Mapping AST to bytecode. The Pharo compiler keeps a mapping between the AST and the bytecode, as well as a mapping between the AST and the source code. By default, the granularity of each step is at the level of expressions. Sindarin makes use of this infrastructure to provide fine grained control over stepping, without losing the bytecode abstraction. This granularity eases the ability to set breakpoints inside statements. The direct access and handling of the AST also enables to express complex conditions for breakpoints.

Breakpoints. The imperative design of Sindarin means that users will often set breakpoints on the AST node the debugged execution is currently at. Typical implementation of breakpoints modify the bytecode of the method to insert the breakpoint behaviour. This approach does not fit the use-case of Sindarin, because modifying methods that are already on the call stack is hard [START_REF] Tesone | Dynamic software update from development to production[END_REF]. To go around this limitation, the breakpoints set by Sindarin are "virtual" in the sense that no code modification is performed. Instead, Sindarin remembers the AST nodes on which breakpoints were set, and stops debugged execution that would step through these nodes.

IDE Integration

We modified the Pharo debugger to make it scriptable through the Sindarin API. The developer writes Sindarin scripts in an editor or directly in the debugger (Figure 3). When scripts are run in the debugger, the dbg variable is automatically bound to the current debugger.

Integration into Mainstream Software Development

We highlighted a few existing debugger APIs from mainstream languages in section 6.2. These could be used as a basis to implement the Sindarin API in these languages. The three main challenges of such implementations are those outlined in section 6.2: step granularity, context access and AST mapping.

Implementing expression-level step granularity in the languages that do not natively support it can be achieved with automatic program rewriting [START_REF] Kume | Cutting java expressions into lines for detecting their evaluation at runtime[END_REF], to rewrite the expressions over multiple lines, or AOP weaving [START_REF] Yin | Defusing the Debugging Scandal -Dedicated Debugging Technologies for Advanced Dispatching Languages[END_REF].

From Table 3, UCD already provides full context access through direct memory access. For Python, Python code can interact in an indirect way with the Python virtual machine to access/modify the context stack of the execution. An alternative method is probably achievable for Java. Of note is that these solutions require significant engineering effort.

AST mapping is not present in any of the debugging APIs we considered. It could maybe be achieved by modifying the compiler of the host language to keep the AST that is generated during the compilation, and build a binding between the bytecode generated by the compiler and this AST.

Limitations

A limitation of Sindarin is its low-level aspect. It offers a lot of possibilities, but lacks a higher-level layer that would offer a quick and expressive way for developers to express higherlevel debugging operations. This limitation is mitigated by the ability to write and re-use debugging scripts. Another limitation is that effectively using Sindarin to debug requires understanding and manipulating the concepts of contexts, AST nodes and stack.

Related work

We present in the following related work on debugger customization and means to control debugging sessions.

Domain Specific Debugging Operations

Kompos [START_REF] Marr | Kompos: A platform for debugging complex concurrent applications[END_REF] is a concurrency-agnostic debugger protocol, which decouples the debugger from the concurrency models employed by the target application. As a result, the underlying language run-time can define custom breakpoints, stepping operations, and execution events for each concurrency model it supports, and a debugger can expose them without having to be specifically adapted. With Sindarin, it is possible to place standard breakpoints and to step in concurrent code. We plan to introduce additional support for concurrent debugging, including specific breakpoints like Kompos'.

The Moldable Debugger framework [START_REF] Chis | Practical domainspecific debuggers using the moldable debugger framework[END_REF] allows developers to create domain-specific debuggers, by defining and combining domain-specific debugging operations and views. The moldable debugger adapts itself to a domain by selecting at run-time the appropriate appropriate debugging operations and views. Debugging operations executes the program until a debugging predicate is matched, or performs an action every time a debugging predicate is matched. Debugging predicates are either primitive predicates like attribute reads or method calls, or combinations of these. A key difference with Sindarin is the "immediacy". With Sindarin, developers write a script on the fly to help with the particular bug under investigation. By contrast, the moldable debugger framework is geared towards investing time into creating a debugger able to assist the debugging of a specific domain, rather than a specific bug. Both approaches are not incompatible, we believe that Sindarin can be used to build moldable debuggers and to script their specialized parts.

Expositor [START_REF] Phang | Expositor: Scriptable timetravel debugging with first-class traces[END_REF], inspired by the work on MzTake [START_REF] Marceau | A dataflow language for scriptable debugging[END_REF], combines scripting and time-travel debugging. The fundamental abstraction provided by Expositor is the execution trace, which is a time-indexed sequence of program state snapshots. Developers can manipulate traces as if they were simple lists with operations such as map and filter. From that perspective Expositor has a conceptual interface close to the Pharo thisContext pseudo-variable that reifies stack on demand.

Barr et al., describe a time-traveling debugger for JavaScript/Node.js [START_REF] Barr | Time-travel debugging for javascript/node[END_REF]. In particular, they describe three time-travel operations: reverse-step (rs) to step to the previously executed line in the current stack frame, reverse-step dynamic (rsd) to step back in time to the previously executed statement in the any frame, and reverse to callback origin (rcbo): a more specific operation to step back from the currently executing callback to the point in time when the callback was registered. These three operations are good candidates to extend the Sindarin API with time-traveling capabilities.

CBD is a Control-flow Breakpoint Debugger [START_REF] Chern | Debugging with control-flow breakpoints[END_REF]. CBD uses a dynamic pointcut language to characterise controlflow breakpoints. These breakpoints are conditions on the control-flow, through which they were reached. As such, CBD leverages aspect-oriented programming to assist debugging operations. The difference between CBD and Sindarin is the versatility. CBD is specialised in expressing control-flow breakpoints, and as a result expresses them in a concise fashion. Sindarin allows developers to express the same breakpoints as CBD, and to combine these breakpoints with other features such as object-centric debugging.

Bugloo [START_REF] Ciabrini | Bugloo: A source level debugger for scheme programs compiled into jvm bytecode[END_REF] is a source level debugger for Scheme programs, that are compiled into JVM bytecode by the Bigloo compiler. Bugloo traces methods that are entered, and the source code location where a given variable is read/written. The debugging commands are recorded, which allows developers to replay their debugging sessions. A command line is available for interactive debugger control.

Other solutions focus on exposing bugs through execution traces. PTQL [START_REF] Goldsmith | Relational queries over program traces[END_REF] is a query language, through which developers express what part of their program they want to trace. Queries are automatically translated to instrumentation. ange et al., [START_REF] Lange | Interactive visualization of design patterns can help in framework understanding[END_REF] combines Prolog-like queries on program traces and visualization to understand program execution. Working on traces implies a post-mortem approach, such works do not support controlling step by step execution of the debugged program, the precise queries of an execution moment nor object-centric facilities.

Querying Objects

Finding objects of interest for debugging is difficult. Using traditional debugging tools, developers have to put breakpoints to halt the execution and manually select objects [START_REF] Corrodi | Towards efficient object-centric debugging with declarative breakpoints[END_REF][START_REF] Ressia | Object-centric debugging[END_REF]. This approach requires much interaction from developers and forbids any automation, e.g. breakpoints in loops break the execution at each loop iteration.

Developers need systematic ways to obtain objects, for object-centric debugging or to provide more details about the debugged execution. Fox [START_REF] Potanin | Snapshot query-based debugging[END_REF] is a language to perform queries over the object graph of an execution snapshot. Bugloo [START_REF] Ciabrini | Bugloo: A source level debugger for scheme programs compiled into jvm bytecode[END_REF] provides a heap inspector allows some access to the object graph of the execution. Query-based debugging [START_REF] Lencevicius | Query-based debugging of object-oriented programs[END_REF][START_REF] Lencevicius | Dynamic query-based debugging[END_REF] refers to debuggers which build collections of objects from userdefined queries, written in a dedicated query language. Reactive object queries [START_REF] Lehmann | Reactive object queries[END_REF] are user-defined requests on a program, which results create and maintain dynamic collections of objects. Both solutions automatically search the object space to update their collections of objects. However they cannot express conditions regarding the context from which an object is obtained [START_REF] Costiou | Collectors. In PX'18 -Programming Experience[END_REF]. These solutions are good candidates for extending Sindarin. Sindarin allows the developer to bring executions to points of interest, from where developers could perform queries over the object space.

Aspect-Oriented Programming

Aspect-oriented programming (AOP) [START_REF] Colyer | Aspect-oriented programming with aspectj[END_REF][START_REF] Fabry | Phantom: a modern aspect language for pharo smalltalk[END_REF][START_REF] Kiczales | Aspect-Oriented Programming[END_REF] is a programming paradigm where the developer can add cross-cutting behavior (an "advice") to existing code without modifying the latter, by specifying which code is modified via a "pointcut" specification. AOP can be used for debugging purposes [START_REF] Yin | Defusing the Debugging Scandal -Dedicated Debugging Technologies for Advanced Dispatching Languages[END_REF] and is able to solve some of the scenarios we described in this paper. AOP cannot directly tackle the Divergence Breakpoints scenario, as it requires two executions to be ran side-byside. The Capturing Objects and Replaying Objects scenarios could be implemented with AOP. This would however require additional work to define a model for storing and re-using objects across executions. AOP differs from Sindarin in the way debugging operations are expressed. Sindarin is imperative while AOP is declarative. The imperative paradigm is closer to the interactions developers have with mainstream debuggers: for example repeatedly clicking on the "step" button until the desired state is reached. We could use AOP to implement the Sindarin API in other languages. We leave as future work a comparison between the imperative (Sindarin) and declarative (AOP) styles in terms of learning curve and convenience for developers.

Discussion

In this section, we discuss a few points of note about Sindarin, namely the advantages of implementing it as an internal DSL, the ability and limits of debugging a Sindarin implementation with itself, and ideas about how Sindarin scripts written for debugging purposes could be leveraged as persistent artifacts. 9.1 Internal DSL Our implementation of Sindarin is an internal DSL of Pharo. This brings advantages in terms of usability, as a developer debugging Pharo code will already be familiar with the general syntax of Sindarin scripts (e.g. how to define a variable, block closures...) and have access to the standard libraries. In addition, this makes Sindarin directly usable within the Pharo IDE, with no additional integration work. 9.2 Self-debugging Since Sindarin is an internal DSL, a debugging session using Sindarin is a program execution in the same host language. As such, Sindarin can be used to debug itself. This is in contrast to other debugging tools that cannot debug themselves. For example, Aspect-oriented programming is unable to debug itself in this fashion, as aspects cannot be added to other aspects [START_REF] Yin | Defusing the Debugging Scandal -Dedicated Debugging Technologies for Advanced Dispatching Languages[END_REF]. A limit to the self-debugging ability of Sindarin is when a core feature is impacted, for example the step operation itself. Indeed, if step itself does not work, the buggy execution of step cannot be stepped to be debugged. This limitations echoes that of Kansas [START_REF] Smith | From kansas to oz: collaborative debugging when a shared world breaks[END_REF]: a reflective system where developers interact with objects in a world. When a Kansas world is broken, another one is created from which the first world can be repaired. However, when a core feature like the ability to create new worlds is broken, Kansas cannot be debugged from within itself.

Artifact Generation

By debugging with Sindarin, a developer generates debugging scripts. These scripts constitute valuable artifacts that can be harvested and used for other purposes. For example, a script written to reach a certain point in the execution and check a property on a variable can be turned into a test. A script defining a particular domain-specific stepping operation (like in our DSL Stepping scenario) can be kept to assist future developers in debugging the same section of the code. A script written to reach a point in the execution that is relevant for a given bug can be shared on the issue tracker. This allows other developers to immediately reach the relevant point without requiring a tedious textual description of which buttons to click in the debugger. These alternative, long-term uses for Sindarin scripts give additional value to the time spent by developers to write them during debugging sessions.

Additionally, debugging scripts could be automatically generated from a developer's interaction with a mainstream debugger. For example the sequence of buttons she clicked.

Conclusion

In this paper we presented Sindarin, a versatile API for scripting an online debugger. Sindarin supports the definition of advanced stepping operations: It offers different interfaces such as AST or runtime stack introspection letting the developer use the abstractions she needs. Sindarin supports objectreachability in the sense that all the objects accessed during execution can be manipulated. This eases the construction of object-centric debugging scripts. Sindarin will be shipped with Pharo 80 as a central part of the new debugging solution.

As a future work we will add support for concurrent programming debugging, back-in-time debugging, and provide ways to support the debugging based on multiple execution stacks. In addition, we would like to perform a user-study with experimented developers to assess multiple characteristics of Sindarin like its learning-curve and how much it helps with debugging scenarios that are impractical to tackle without the support of specialized debugging tools. This study could also compare Sindarin to aspect-oriented-programming techniques on these two points to evaluate the difference between the imperative and declarative styles of debugging.

1 result

 1

3

  and: [ (dbg assignmentValue== 42)

4

  and: [ (dbg assignmentVariableName = #foo) 5 and: [ dbg receiver isKindOf: Bar ]]] 6 ]

3

  and: [ (dbg messageSelector = #signal) 4 and: [ dbg messageReceiver isKindOf: Exception ]]]

3

  and: [ dbg messageReceiver = nil ]]

  of the current stack-frame. dbg stack Returns a list of context objects representing the current call stack. ctx pc Returns the current program counter of the given context.

  ctx senderReturns the sender context of the given context.

  ctx receiverReturns the receiver of the given context.

  dbg haltOnCall: objBreaks next time the object obj receives any message.

  dbg haltOnCall: obj for: m Breaks next time the object obj receives the message m. dbg haltOnWrite: obj Breaks next time any instance variable of the object obj is written.

  bp whenHit: aBlock Defines a sequence of debugging operations to perform when a breakpoint bp is hit.bp removeRemoves the breakpoint bp.bp onceConfigures the Breakpoint bp to remove itself the next time it is hit. Returns bp.

1 [ 2 ( 3 [Listing 5 .

 1235 dbg isExecutionFinished ] whileFalse: [ ( '. * open. * File. * ' match: dbg selector ) and: (dbg context arguments at: 1) = 'myFile.txt' ]) 4 ifTrue: [ dbg setBreakpointOn: dbg context sender method ]. Setting breakpoints on a family of methods.

1 dbg stepUntil: [ 2 ( 3 [Listing 6 .

 1236 dbg method = ProjectBrowser>>#trySaveAs:) and: (dbg context sender method = 4 ActionSaveProjectAs>>#actionPerformed:) not ]] Sindarin script for a control-flow breakpoint

1 dbg stepUntil: [ 2 (

 12 dbg method = ProjectBrowser>>#trySaveAs:) and: 3 [ (dbg stack anySatisfy: [:ctx | ctx method = ActionSaveProjectAs>>#actionPerformed:) not ]]

1

  dbg stepUntil: [ dbg method = ClassA>>#method1 ].

2

  dbg stepUntil: [ dbg method = ClassB>>#method2 ].

4

  dbg stepUntil: [ dbg method = ClassC>>#methodN ] Listing 8. Sindarin script stepping through pitons. This script can be further enhanced by extracting common behaviour into a new stepping function that receives as argument a collection of piton methods (Listing 9). The stepping function iterates over the collection of pitons and steps until it has found all of them during the execution. 1 self stepThroughPitons: {ClassA>>#method1. ClassB>> #method2. ... ClassC>>#methodN}.

2 3

 2 MyScript >> stepThroughPitons: anArray4 anArray do: [:aMethod | 5 dbg stepUntil: [ dbg method = aMethod ]]

1 dbg1:

 1 = ScriptableDebugger debug: [ TestOriginalClass run ].

2

  dbg2 := ScriptableDebugger debug: [ TestCopyClass run ].

3 [

 3 dbg1 method = dbg2 method ] 4 whileFalse:[ dbg1 step. dbg2 step ] Listing 10. Script to debug the two executions side-byside.

1 "

 1 Usage example" 2 self bigCollection collect: [ :each | each double ].

  self species new. 8 self do: [:each | newCollection add: (aBlock value: each)]. 9 ˆnewCollection Listing 11. The #collect: internal iterator from Pharo collections.

1

  dbg stepUntil: [ dbg method = (Collection >> #collect:) ].

2 6 7

 6 blockClosure := dbg arguments first. "step to the third iteration" 5 173 timesRepeat: [ self stepToNextIteration ] MyScript >> stepToNextIteration 8 dbg stepUntil: [ :ctx | lastCtx ~~ctx and: [ ctx method = blockClosure ] ].

9 lastCtx := dbg currentContext Listing 12 .

 912 The #collect: internal iterator from Pharo collections.

1 AtomViewerListing 13 .

 113 How to debug atom drawers?

1

  bpoint := dbg setBreakpointOn: AtomViewer>> #displayAtom:.

4 atom:

 4 = dbg messageReceiver.

5 drawer := dbg messageArguments first. 6 (

 56 atom shape isCircular and: [atom shape radius > 50])

Listing 14 .

 14 Capture example: capturing an atom drawer.

1 bpoint

 1

5 drawer := dbg messageArguments first. 6 "

 56 condition for replay" ifTrue: [ 7 bpoint remove.

Listing 15 .

 15 Replaying an atom drawer after an exception

Figure 2 .

 2 Figure 2. Creation process of a scriptable debugger instance

Figure 3 .

 3 Figure 3. Command line implementing Sindarin integrated into the Pharo debugger

Table 1 .

 1 Expression-level Step Operations. Basic step operations like step into and step over. Unlike in most other debugging APIs, these operations work at the expression level rather than the line level. The notations used in the table are as follows: dbg stands for scriptable debugger, ctx stands for a context (a.k.a. stack frame), ast stands for an AST node and bp stands for breakpoint.

	Full Context Access. Access to, and manipulation of the
	stack trace and its stack frames.
	AST Mapping. A bytecode-AST mapping, mapping the
	debugged program's program counter to the AST node
	currently executed.
	Object-Centric Debugging Operations. Scope break-
	points to specific objects.
	Breakpoint-related Operations. To set, customize and
	remove breakpoints.
	Sindarin's API is further described in

2.2 Sindarin's API

Sindarin's internal DSL exposes among others:

Table 1 .

 1 The Sindarin debugging API Executes the next instruction. If the instruction is a message-send, step inside it.Executes over the next instruction. If the instruction is a message-send, step it until it returns.

	Stepping
	dbg step
	dbg stepOver
	dbg stepUntil: aPredicate

Table 2 .

 2 Feature Requirements of the Debugging Scenarios

	Scenario	Step Granularity Context Access	AST Mapping
	Monitoring Assignments to a Variable	Expression	Stack Access	✓
	Stopping before an Exception	Expression	Stack Access	✓
	Placing Breakpoints on a Family of Methods	Method	Basic Access	✗
	Chaining Pitons	Method	Basic Access	✗
	Control-Flow Breakpoints Method	Basic Access	✗
	Divergence Breakpoints	Method	Basic Access	✗
	DSL Stepping	Method	Basic Access	✗
	Capturing Objects	Expression	Stack Access	✓
	Replaying Objects	Expression	Stack Modification	✓

Table 3 .

 3 Mainstream Debugging APIs.

	Feature \ API	UCD 1	BDB	NodeJS JDI CLR 4 Sindarin
	Step Granularity line 2 expression	line	line	line expression
	Context Access	full 3	basic	basic	basic basic	full
	AST Mapping	✗	✗	✗	✗	✗	✓

Pitons in alpinism are sticks placed one by one to secure the path of an alpinist. An alpinist carefully moves its secure rope from one piton to the next one.

https://github.com/pillar-markup/pillar

In Pharo, the #collect: method implements the well-known map function from functional programming.

 [START_REF] Barr | Time-travel debugging for javascript/node[END_REF]This work was supported by Ministry of Higher Education and Research, Nord-Pas de Calais Regional Council, CPER Nord-Pas de Calais/FEDER DATA Advanced data science and technologies 2015-2020. The work is supported by I-Site ERC-Generator Multi project 2018-2022. We gratefully acknowledge the financial support of the Métropole Européenne de Lille. 2 Pharo is a pure object-oriented dynamically typed programming language inspired by Smalltalk -http://www.pharo.org.


Jean-François Petiot
email: jean-francois.petiot@ec-nantes.fr

Killian Legeay

Mathieu Lagrange

OPTIMIZATION OF THE SOUND OF ELECTRIC VEHICLES ACCORDING TO UNPLEASANTNESS AND DETECTABILITY

Keywords: Multiobjective optimization, masking effect, sound quality, interactive optimization Optimization, User centred design, Participatory

Electric Vehicles (EVs) are very quite at low speed, which can be hazardous for pedestrians. It is necessary to add warning sounds but this can represent an annoyance if they are poorly designed. On the other hand, they can be not enough detectable because of the masking effect due to the background noise. In this paper, we propose a method for the design of EV sounds that takes into account in the same time detectability and unpleasantness. It is based on user tests and implements Interactive Genetic Algorithms (IGA) for the optimization of the sounds. Synthesized EV sounds, based on additive synthesis and filtering, are proposed to a set of participants during a hearing test. An experimental protocol is proposed for the assessment of the detectability and the unpleasantness of the EV sounds. After the convergence of the method, sounds obtained with the IGA are compared to different sound design proposals. Results show that the quality of the sounds designed by the IGA method is significantly higher than the design proposals, validating the relevance of the approach.

INTRODUCTION

Electric vehicles (EVs) and hybrid vehicles are becoming a serious alternative to internal combustion engine cars (Gillibrand et al., 2011). However, at low speed (under 40km/h), EVs are very quiet and may be dangerous for pedestrian or visually impaired peoples who have to rely on auditory cues when intending to cross a road [START_REF] Parizet | Auditory warnings for electric vehicles: Detectability in normal-vision and visually-impaired listeners[END_REF]. Regulation concerning the sounds of electric cars is still under study. Nevertheless, several countries such as Japan and the United States of America already decided that adding artificial sounds to EVs is compulsory [START_REF] Konet | Development of Approaching Vehicle Sound for Pedestrians (VSP) for Quiet Electric Vehicles[END_REF]. Car manufacturers started to design warning sound generator devices, named for instance AVAS (Approaching vehicle Audible System), VSP (Vehicle Sound for Pedestrian), VPNS (Vehicle Proximity Notification Systems)… Many studies have been conducted to recommend design guidance for warning sounds and external sound generation systems (Senselab, 2011), [START_REF] Robart | eVADER: A perceptual approach to finding minimum warning sound requirements for quiet cars[END_REF], [START_REF] Singh | Toward a methodology for assessing electric vehicle exterior sounds[END_REF]. The sonification of EVs is a complex design problem, with many constraints and stakeholders involved. Stakeholders include cyclists and pedestrians some of whom might have difficulties hearing warning sounds in a urban environment, drivers who expect audio-feedback on the performance of the car, and other third parties who prefer not to be disturbed by additional sounds [START_REF] Petiot | How should an electric vehicle sound? User and expert perception[END_REF]. The main difficulty in the design concerns the tradeoff between detectability and acceptance of the EV sound [START_REF] Lee | Objective evaluation of the sound quality of the warning sound of electric vehicles with a consideration of the masking effect: annoyance and detectability[END_REF]. It is indeed clear that EV sounds may be masked by the background noise of the environment, making them hard to detect. And a naïve solution consisting in a simple increase of the sound level to reduce the masking effect may have dramatic consequences on the sound pollution of cities. There is then clearly a conflict between detectability and annoyance for the perception of EV sounds. Different studies addressed this problem (Campillo-Davo and Rassili, 2016), [START_REF] Lee | Objective evaluation of the sound quality of the warning sound of electric vehicles with a consideration of the masking effect: annoyance and detectability[END_REF], [START_REF] Parizet | Auditory warnings for electric vehicles: Detectability in normal-vision and visually-impaired listeners[END_REF]. All these studies are based on hearing tests of a predefined set of sound stimuli. They produced interesting results to give recommendations for the design of sounds by the fitting of a model between the perceptual dimensions and the sounds parameters. This data modeling stage may constitute a limitation for the optimization of sounds, given the number of parameters of the sounds and the possible interaction between them. To address this problem, we propose in this paper an interactive optimization of EV sounds based on Interactive Genetic Algorithms (IGA). This model-free approach is efficient to improve design solutions during interactive assessments [START_REF] Poirson | Eliciting User Perceptions Using Assessment Tests Based on an Interactive Genetic Algorithm[END_REF], and to take into account subtle perceptual phenomenon [START_REF] Wakefield | Genetic Algorithms for Adaptive Psychophysical Procedures: Recipient-Directed Design of Speech-Processor MAPs[END_REF]. The first objective is to define a design method for the optimization of the tradeoff detectability/unpleasantness of EV sounds. It is based on hearing tests of synthesized EV sounds by a panel of participants, and uses Interactive Genetic Algorithms (IGA) for the optimization of quality (or fitness) of the sounds. A second objective of the paper is to assess the efficiency of the design solutions compared to different designs and current proposals of a designer. The remainder of the paper is organized as follows. Section 2 presents a short background on interactive Genetic Algorithms (IGA) and their use in product design. Section 3 presents the material and methods for the experiment. It starts with a presentation of the sound synthesis method and the associated parameters, the tasks given to the participants, the sound stimuli used, and the method used for the analysis of the results. Results are presented in section 4. The concluding section provides implications for sound design and perspectives.

BACKGROUND ON IGA

Principles

Genetic Algorithms (GA) are evolutionary optimization methods (Goldberg, 1989) The principle of GA is based on iterative generations of population of individuals, converging step by step toward solutions, which are adapted to the problem. Based on the principle of Darwin's natural evolution theory, the algorithm proceeds to a selection of parents, which will spread their genetic dominant heritage in the next generation, suitable to a desired objective. Classically, the fitness evaluation of the individuals is calculated numerically with a mathematical function known beforehand. A particular category of GA, Interactive Genetic Algorithms (IGA) introduces the user in the optimization loop to assess the fitness. During each iteration the user selects solutions (products) that he/she considers as the most interesting for the desired objective. After a number of iterations, the method may converge towards solutions that fulfill the user's objective. These algorithms are used for example to explore design spaces and to encourage creativity (Kim and Cho, 2006), [START_REF] Qian | Joint pricing and platform configuration in product family design with genetic algorithm[END_REF]. Since the user decides the individual fitness, there is no need for a prior and unique formulation of the fitness function. For some applications, such as exploring semantic dimensions [START_REF] Poirson | Eliciting User Perceptions Using Assessment Tests Based on an Interactive Genetic Algorithm[END_REF] or integrating complex perceptual processes [START_REF] Wakefield | Genetic Algorithms for Adaptive Psychophysical Procedures: Recipient-Directed Design of Speech-Processor MAPs[END_REF] [START_REF] Lee | Stimulating designers' creativity based on a creative evolutionary system and collective intelligence in product design[END_REF], this advantage is crucial.

Implementation of IGA

After a definition of the variables of the product and their corresponding levels, a coding of the designs, represented by a chromosome, is proposed. Our implementation uses a binary coding and discrete-valued variables. A more complete description of the implementation of our IGA can be found in [START_REF] Poirson | Eliciting User Perceptions Using Assessment Tests Based on an Interactive Genetic Algorithm[END_REF]. The IGA creates an initial population of designs by randomly generating the chromosomes and presents them to the user (e.g. pictures or sounds). Based on personal criteria, and according to the instructions given to the user for the experiment, the user has to select a subset of these individuals (1 or 2), or to rate each individual according to its "fitness". A new population of individuals is then created using one of three different operations on each individual from the previous population. The efficiency of our IGA is ruled by its 3 operators: crossover, mutation, and selection, chosen randomly for each individual of the population, and controlled by the crossover rate (𝑐 ! ), the mutation rate (𝑚 ! ) and the selection rate (𝑠 ! ). These values are chosen between 0 and 1 in such a way that 𝑐 ! + 𝑚 ! + 𝑠 ! = 1. An indicator, rand(i), is randomly chosen between 0 and 1 for each individual i with a uniform distribution.

-If rand(i) < 𝑐 ! , the operation is a crossover (single point crossover -the second parent is chosen randomly in the population) -If 𝑐 ! ≤ rand(i) ≤ 𝑐 ! + 𝑚 ! , the operation is a mutation (random mutation of one variable) -If rand(i) > 𝑐 ! + 𝑚 ! , the operation is a selection (simple duplication of the individual) A fourth important parameter of the IGA is the roulette wheel 𝑤 ! . In the crossover operation, the probability that an individual will be a parent in the crossover operation is increased by the weight 𝑤 ! >1. An automatic process was implemented to tune the different parameters of our IGA [START_REF] Poirson | Eliciting User Perceptions Using Assessment Tests Based on an Interactive Genetic Algorithm[END_REF]. This process uses simulated "virtual" users and a "target" product in the design space (defined by target values of the design variables). To simulate the choices of a virtual user, a distance function between the individuals of the population and the target is computed. By launching several simulations in the same conditions (Monte Carlo method), an average estimate of the convergence rates of the IGA is computed, given the value of the parameters. This process allows the experimenter to determine the "optimal" tuning of the parameters, given a maximum number of generations.

MATERIAL AND METHODS

EV Sound synthesis

Components of the sound

The EV sounds were synthesized using the mathematical modeling software Matlab and the additive synthesis technique. In order to generate different but plausible sounds for an electric car, after an analysis of current sounds of different carmakers [START_REF] Misdariis | Do electric cars have to make noise? An emblematic opportunity for designing sounds and soundscapes[END_REF] and personal propositions [START_REF] Petiot | How should an electric vehicle sound? User and expert perception[END_REF], four main components of the sound were considered. The components are also named design factors in this paper.

• Component C1 "A thermic motor sound". This component synthesizes the first harmonics of a classical 4-stroke internal combustion engine (H0.5, H1, H1.5, H2, H4, H6), • Component C2 "A Harmonic Sound". This component synthesizes different musical 'notes', harmonic, that constitute a chord (chord with 2, 3, or 4 notes), • Component C3 and C4: "A broad band Noise" (granular synthesis). These components synthesize two filtered noises. The final temporal signal 𝑠 𝑡 is simply a weighted sum of the different components (equation 1).

𝑠 𝑡 = 𝑎 !! . 𝐶1 𝑡 + 𝑎 !! . 𝐶2 𝑡 + 𝑎 !! . 𝐶3 𝑡 + 𝑎 !! . 𝐶4 𝑡 (1)
In addition to this, different parametric filters were applied to the final sound: (1) a sweeping filter (envelop filter), which changes the harmonics amplitude in function of time -(2) a flanging filter (swept comb filter effect), which produces time variations of the frequency spectrum.

Since it is out of the scope of this paper to describe all the parameters of the synthesizer (there are more than 70 independent parameters to define a sound), we can mention that all the frequencies and amplitudes of the components are adjustable, to create credible and original sounds, as well as the filters parameters. The sound is not constant but 'played' by a control parameter of the car: the speed.

To make the sound evolve with the speed of the car, we choose to adjust the frequencies and the amplitudes of the different components according to the speed with parameterized patterns.

Design variables of the EV sound

Among the different synthesis parameters of the sounds, it is necessary to define which one are manipulated by the IGA and coded in the genome (space of exploration of the genetic code). After several experiments, the following 6 variables, and their corresponding levels, were chosen as factors to get a large diversity of sounds (table 1). The four first factors (C1, C2, C3, C4) control the frequencies of the components C1, C2, C3, C4, the factor Amp control the relative amplitude of the different components, and the Filter controls the use of different filters to alter the global sound. 4 levels were chosen for each factor. The setting of the levels of the factors required many adjustments (not reported here) to obtain audible differences between sounds, but with still convenient sounds.

𝑎 !! = 2 𝑎 !! = 1 𝑎 !! = 0.75 𝑎 !! = 0 𝑎 !! = 0.5 𝑎 !! = 0.75 𝑎 !! = 1.5 𝑎 !! = 0.33 𝑎 !! = 0.25 𝑎 !! = 0.5 𝑎 !! = 0.1 𝑎 !! = 0.25 𝑎 !! = 0 𝑎 !! = 0.1 𝑎 !! = 0.1 𝑎 !! = 0.5

Filter Type of filter None Sweeping filter Flanger Sweep+flanger

With these six factors and four level, the design space counts 4 ! = 4096 possible designs (all the possible combinations of the full factorial).

Scenario and Sound stimuli

Given that the objective of the test is to assess the reaction time associated to the detection of the EV sound, the sound must be incorporated in a background noise (masking signal). The background noise considered in the study corresponds to a two-lane street in downtown. The soundscape was synthesized with the SimScene software1 [START_REF] Rossignol | SimScene: a web-based acoustic scenes simulator[END_REF], from real recordings in a city. To be used as background noise, the soundscape must not contain any strong emergent event (horns, car passing, …) [START_REF] Kerber | Prediction of perceptibility of vehicle exterior noise in background noise[END_REF]. The level of the background noise was adjusted to a convenient level and kept constant for all the stimuli proposed in the hearing test. To avoid the potential fatigue of the participant due to the repetition of the same background noise during the test, the part of the audio file selected (15 seconds) was randomly chosen in the total background noise (duration 1mn). The scenario chosen for the test corresponds to the following situation [START_REF] Misdariis | Detectability study of warning signals in urban background noises: A first step for designing the sound of electric vehicles[END_REF]): a pedestrian located on the sidewalk of a street waits before crossing (Figure 1). An EV may pass by, coming either from the right or from the left. The listener is static, and must indicate when he/she detects the EV. We can mention that for experimental reasons related to the test duration, the attenuation function of the EV sound is asymmetrical, as in [START_REF] Misdariis | Detectability study of warning signals in urban background noises: A first step for designing the sound of electric vehicles[END_REF] (the attenuation is more fast than the increase of the sound level). Of course this aspect does not affect the detectability of the EV sound, which always occurs in the approach phase.

Experiment

The objective of the experiment is to assess the detectability and the unpleasantness of EV sounds. 15 participants (12 males), with no reported auditory deficiencies, performed the experiments. The audio stimuli were presented with the same hardware desktop configuration, sound card and software, as well as Beyerdynamics DT-990 headphones in a quiet environment. The same sound output level was set by the experimenter for all computers. Instructions were given to the participants at the beginning of the experiment, mainly to explain how to assess the detection time.

After selecting a sound ("select" button Figure 3), participants had to strike the "space bar" to start playing the stimuli (t = 0), and next strike the "a" key as soon as they detect the EV coming from the left, or the "e" key if it is coming from the right. This allows the definition of the detection time 𝑡 ! . The detection duration 𝐷 ! is then given by (equation 2).

𝐷 ! = 𝑡 ! -𝑡 ! (2)
In case the participant did not strike any key, or strike a key before 𝑡 ! (starting time of the EV), the detection duration was arbitrarily set to the maximum value, and a warning was recorded. In case the participant struck the wrong key (mistake in the direction of the EV), the detection duration was still computed, but a warning was recorded. The change in the direction of the car in the protocol is very important to be able to detect "false alarm" cases, where the participants strike the key before detecting the car. Furthermore, to avoid habituation of the participant in the detection time, the starting time 𝑡 ! of the EV sound in the mixture (figure 2) was not always the same, and was randomly chosen in the interval [1, 3] seconds.

After assessing the detectability of the sound, participants were asked to rate the unpleasantness 𝑈𝑛 ! of the sound on a structured semantic scale (figure 3 -from "0": not at all unpleasant to "10": very unpleasant)). To explain this semantic dimension unpleasantness, the following information was given to the participants « If the car passed by your house during a calm moment, how unpleasant would the sound be? ». They were able to play again the stimuli, but of course it was not possible to assess again the detectability (given that they already heard the sound and knew the direction of the car).

IGA test

The assessment of the EV sound according to detectability and unpleasantness has been included in an IGA iterative experiment. The problem of IGA tests is to manage a good balance between convergence properties and fatigue of the participant. After different tests, we considered that the assessment of 11 populations of 9 sounds was enough to not fatigue the participant. This corresponds to a test duration of around 35mn. An automatic process was implemented to tune the different parameters of our IGA et al., 2010). The optimal tuning parameters of the IGA are as follows:

• Wheelrate: 𝑤 ! = 14 • Crossrate: 𝑐 ! = 0.65 •
Mutation rate: 𝑚 ! = 0.3 The fitness of the individuals was computed from the detectability and unpleasantness assessments (equation 3). The following form was defined, according to the detection duration 𝐷 ! and the unpleasantness rating score 𝑈𝑛 ! :

𝑓𝑖𝑡𝑛𝑒𝑠𝑠 = 𝑎 1 - ! ! ! ! !! ! + ! ! (10 -𝑈𝑛 ! ) (3)
The higher the unpleasantness, and the higher the detection duration, the lower the fitness. The parameter 𝑎 represents the relative contribution of the detection time to the fitness. After several tests, it was set to 𝑎 = 5 to give a similar importance to detectability and unpleasantness given the variance in the population. At the end of the IGA test, the individual with the highest fitness 𝐼𝐺𝐴 !"# in the last population can be defined for each participant.

Assessment of sounds proposals

At the end of the IGA test, participants were asked to assess the detectability and unpleasantness of six different EV sound proposals (interface given in Figure 3). Four EV Sounds (𝑟𝑎𝑛𝑑1, 𝑟𝑎𝑛𝑑2, 𝑟𝑎𝑛𝑑3, 𝑟𝑎𝑛𝑑4) were randomly defined in the experimental space, whereas two EV sounds (𝑆𝑜𝑢𝑛𝑑1, 𝑆𝑜𝑢𝑛𝑑2) were designed by a sound designer with instructions for good detectability and good acceptance. In total, each participant had to assess 105 sound stimuli (11*9 for the IGA + 6 design proposals). For each participant, the detection time, unpleasantness, fitness of his/her best individual 𝐼𝐺𝐴 !"# was next compared to those of the different sound proposals. In addition to this, the detection time was converted into the distance to pedestrian, i.e. the distance of the EV to the participant at the instant of detection.

RESULTS

Concerning the detection of the car direction, one participant made more than 50% or errors in the detection direction (problem with the headphone). He was withdrawn of the panel. The other 14 participants made very few errors (less than 5 errors for the 105 stimuli), errors only in the direction of the car due to careless mistakes (strike of the wrong key). Their data were considered as valid.

IGA convergence

The average fitness of the population according to the generations for the whole panel of participants is presented in Figure 4. An improvement of the fitness is noticed over the generations, sign of the reliability of the experimental protocol for the assessment of the detection time and the unpleasantness, and a correct tuning of the IGA parameters.

Analysis of the best IGA sounds

Table 2 shows the occurrences of each level of the variables in the set of 𝐼𝐺𝐴 !"# sounds. For example, for the fundamental frequency of the motor sound (C1), 6 participants chose the level 3 (130Hz), 3 the level 2 (100Hz) or level 4 (160 Hz), and 2 the level 1 (70Hz) for their final sound. To define the variables subjected to the most consensual choice their levels, a multinomial goodness of fit test of the distribution of the occurrences was carried out. Results are presented in Table 2. Only one variable (Amp) obtains occurrences significantly different from a random distribution at the 1% level. For the amplitude of the components of the sounds, the level 1 (strong presence of the thermic motor sound) is by far the most represented. This over-representation could be explained by the "naturalness" of the thermic motor sound in the street, and the habit of people to be surrounded by such noises.

In conclusion, for the whole group, the presence of the thermic motor sound is important for the detectability and the acceptance of the EV sounds. For the other variables (C1, C2, C3, C4, Filter), there was no significant consensus, mainly because of the small size of the panel of participants.

Additional studies are needed to identify the sound that better fits in average the requirement of all participants. It is clear on Figure 5 that the 𝐼𝐺𝐴 !"# sound is Pareto efficient. In average, the 𝐼𝐺𝐴 !"# sound obtains the best performances both for detectability and unpleasantness. The first proposal of the sound designer, Sound1, is still not unpleasant, but less detectable than 𝐼𝐺𝐴 !"# . The second proposal, Sound2, is more detectable, but the price to pay is the unpleasantness, that is the largest. The randomly generated sounds (rand1 to rand4) obtain average performances: they are rather unpleasant, and with large detection times.

To investigate the differences in the performances of the sounds, the average scores with their standard errors are presented in Figure 6 To study the differences in the average score, a one-tailed paired t-test is carried out for every pair of sounds (significant threshold: p = 0.05). The results are presented with bold lines connecting the sounds in the figures. When sounds are connected, pairs are not significantly different, whereas they are when not connected.

According to detection time, there is no significant difference between two groups of sounds (Figure 6 left): (rand2, Sound2, IGAopt) and (rand4,rand1,Sound1,rand3,rand2,Sound2). With detection time only, the 𝐼𝐺𝐴 !"# sound does not outrank two other proposals, particularly the sound designer proposal Sound2. Concerning the distance to pedestrian, figure 6 (right) shows that some proposals (rand4, rand1) are detected under the stopping distance of the vehicle (considered as 11m at 30km/h). These sounds do not allow the EV to stay in a safety zone with regard to a blind detectability. According to Unpleasantness (Figure 7 left), there is no significant difference between three groups of sounds. Again the 𝐼𝐺𝐴 !"# sound does not outrank all the proposals, particularly the sound designer proposal Sound1. When global fitness is considered (Figure 7 right), 𝐼𝐺𝐴 !"# is then significantly different of all the other proposals. This result shows that the IGA allows a conjoint optimization of the two conflicting criteria. These results are average results for the whole panel. At the individual level, the IGA sounds got the best fitness for 7 participants out of 14. The IGA procedure is interesting to define convenient EV sounds, optimized for unpleasantness and detectability. It allows ones to uncover tradeoffs between detectability and unpleasantness. We are of course aware that there is a bias in the evaluation process because the same experimental protocol is used for the optimization and for the evaluation. A different experiment should be proposed, why not in real environment, to prove with an independent experiment that the IGA process is efficient to design EV sounds. This will be conducted in future experiments. The positive results are nevertheless encouraging given the small number of studies on the interactive optimization of sounds.

CONCLUSION

This paper presented an experiment aiming at designing EV sounds that are detectable but not unpleasant. An Interactive Genetic Algorithm was implemented in order to optimize the fitness of synthesized sounds. The designed experimental protocol provided a realistic assessment of the detection time. With hearing tests, and inclusions of the EV sounds in a background sound, assessments of the detection time and the unpleasantness of the sounds were provided. First results show the potential of the method: the IGA algorithm is effective for the design of efficient sounds. Comparisons between sound proposals showed that in average, the sound provided by the IGA was significantly more efficient. Several perspectives can be drawn for this project. We are of course aware that our study would need more participants to confirm these first encouraging results. A next stage of the project will be to explain the performances in detectability and unpleasantness with sound parameters (spectral or spectro-temporal). This will be important to be able to draw recommendations to a sound designer and to improve the design of sounds for EVs.

Campillo-Davo N. and Rassili A. (2016), "NVH Analysis Techniques for Design and Optimization of Hybrid and Electric Vehicles". ISBN 978-3-8440-4356-3, Shaker Verlag Publications. Gillibrand, A., Suffield, I., Vinamata, X., Williams, R. and Brückmann, A. (2011)

Figure 1 .

 1 Figure 1. Passing-by scenario for the hearing test: pedestrian located on the sidewalk of a streetTo obtain a pseudo-realistic passing-by scenario, the following properties have been implemented:• The sound level of the EV is modulated according to the vehicle/listener distance. The model used, based on acoustic theory, considers the EV as a monopole and provides a sound level inversely proportional to the distance to the listener (1/r) (see figure2)[START_REF] Misdariis | Detectability study of warning signals in urban background noises: A first step for designing the sound of electric vehicles[END_REF],[START_REF] Lee | Objective evaluation of the sound quality of the warning sound of electric vehicles with a consideration of the masking effect: annoyance and detectability[END_REF]).•The speed of the EV is considered as constant and equal to 30km/h • The duration of the sound stimuli is 15 seconds •The Doppler effect (shifting in frequency due to the speed of the source) is taken into account for a more realistic experience, •The direction of the car (from the right or from the left) is randomly chosen •The panning of the EV sound is managed in such a way that the source goes progressively from one canal (left or right, depending of the direction of the EV) to the other (right or left) according to the position of the vehicle Figure2describes the assembly of the background and the EV sounds and their respective sound level evolution.

Figure 2 .

 2 Figure 2. Timeline of the assembly of the background and the EV sound, with their respective level evolution (the x axis represents indifferently the time or the distance of the EV, given that the speed of the vehicle is constant)

Figure 3 .

 3 Figure 3. Interface for the assessment of the detectability and the unpleasantness of a sound stimulus (structured rating scale).

Figure 4 .

 4 Figure 4. Average fitness of the populations over the generations.

  , for the panel of 14 participants, the average performances of the sounds proposals and of the best sound 𝐼𝐺𝐴 !"# according to the two criteria detection time and unpleasantness. The standard error is also reported for information.

Figure 5 .

 5 Figure 5. Scatterplot of the average performances of the different EV sounds (detection time vs. unpleasantness) with their standard error.

  left (Detection time), Figure 6 right (distance to pedestrian), Figure 7 left (Unpleasantness), and Figure 7 right (Fitness).

Figure 6 .

 6 Figure 6. Bar graph of the average value of the Detection time (left) and distance to pedestrian (right) for the different EV sounds. Non significant differences between pairs of sounds (p>.05) are linked with an horizontal line (paired t-test).

Figure 7 .

 7 Figure 7. Bar graph of the average value of the Unpleasantness (left) and Fitness (right) for the different EV sounds. Non significant differences between pairs of sounds (p>.05) are linked with an horizontal line (paired t-test).

Table 1 .

 1 Definition of the 6 factors (design variables) and their levels

	Factor Variable	Level 1	Level 2	Level 3	Level 4
	C1	Fundamental	70Hz	100Hz	130Hz	160Hz
		frequency of C1
	C2	Fundamental	100Hz	150Hz	200Hz	250Hz
		frequency of C2
	C3	Central frequency of	100Hz	200Hz	300Hz	400Hz
		C3
	C4	Central frequency of 500Hz	600Hz	700Hz	800Hz

Table 2 :

 2 Occurrences of the levels of the variables in the IGA final choices of the participants(IGA test)

		C1	C2	C3	C4	Amp	Filter
	Level 1	2	2	3	5	8	1
	Level 2	3	4	4	4	0	4
	Level 3	6	3	3	2	3	3
	Level 4	3	5	4	2	2	5
	Multinomial	N.S.	N.S.	N.S.	N.S.	***	N.S.
	test Signif.
		***: p<0.01		N.S.: not significant

  , "An Initial Study to Develop Appropriate Warning Sound for a Luxury Vehicle Using an Exterior Sound Simulator". SAE International, 2011-01-1727. Goldberg, D.E. (1989), "Genetic Algorithms in search, optimization & machine learning", Addison-Wesley Publishing Company. Kim H. S. and Cho S. B. (2006), "Application of Interactive Genetic Algorithm to Fashion Design". Engineering Design 38, 224-237.

Open-source project available at: https://bitbucket.org/ mlagrange/simscene