Skip to content

Latest commit

 

History

History

part-10

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 

Part 10 - arrays, read-only properties

In this part of the series, we add support for arrays to the language. To use arrays effectively, we also add the ability to read their length property.

Array expressions

We support array literals, reading an array index, and writing to an array index. These new expressions are added to the ANTLR grammar for EasyScript.

The Nodes implementing those expressions are ArrayLiteralExprNode, ArrayIndexReadExprNode and ArrayIndexWriteExprNode, respectively. Accessing of the array elements is performed through a Truffle library, InteropLibrary, which we've already seen in previous chapters. However, we now use it directly, getting an instance of it with the @CachedLibrary annotation. Using @CachedLibrary forces you to provide the limit attribute of the @Specialization annotation. It's good practice to always set limit equal to 2 when using @CachedLibrary in a @Specialization method, as setting it to 1 sometimes makes the code slower for some unknown reason.

The implementations of those Nodes use the ArrayObject class. This is a Truffle Dynamic Object, which means it implicitly implements the TruffleObject interface. It exports the appropriate messages for dealing with arrays, like getArraySize(), readArrayElement() and writeArrayElement().

The Shape for arrays, cached in a field of the TruffleLanguage class for this chapter and then passed to the parser class, is created by passing the ArrayObject.class to the layout() method of Shape.Builder. There is a field in ArrayObject annotated with the @DynamicField annotation that tells the Truffle object system that this dynamic object always has the length property.

The logic inside ArrayObject stores the length property directly in the object instance using a different Truffle library, DynamicObjectLibrary. To get a reference to an instance of the library, we again use the @CachedLibrary annotation, which can be placed not only on parameters of @Specialization methods, but also of @ExportMessage methods.

We also extract a TruffleObject class called MemberNamesObject, that simply encapsulates an array of property names, and we use it to implement the getMembers() message of ArrayObject. We will use this class for the same purpose in other TruffleObjects below.

There are some unit tests validating that the array functionality works as expected.

Reading properties

In order to allow reading the length property of arrays, we need to change how we handle property reads. In previous parts of the series, we implemented references like Math.abs as two identifiers, separated by a .. But now, we need to allow arbitrary expressions to be targets of property reads.

So, we add a new rule, PropertyReadExpr5, to the ANTLR grammar. We implement that expression in the PropertyReadExprNode Node. We again use the InteropLibrary, this time the readMember() method message.

Math static object

To still support references like Math.abs in our language, we need to change the Math object. Since we know exactly what properties Math has, and we don't support property assignments in this chapter yet, we will implement it using the opposite of Dynamic Object, Static Object.

The code is in the MathObject class. The part dealing with static objects is in the create() static factory method -- there, we initialize a StaticShape with two StaticProperty instances, corresponding to the abs and pow properties of Math. Finally, we create a new object from a factory retrieved from the Shape, and save it, and the properties, as instance fields of the Math object. Then, the actual logic of reading the properties is implemented with messages from the InteropLibrary, like readMember().

Global scope as a Dynamic Object

Now that we know how to use the DynamicObject library, we can fix a problem we had up to this point in the series with our interpreter. The GlobalScopeObject uses a HashMap to store the names and values of the global variables. However, a HashMap is not optimized for partial evaluation, which causes performance problems when JIT compiling it. For that reason, the general rule in Truffle is to avoid using Java collections like Map or List, and prefer using arrays instead, which are much easier for Graal to optimize. However, arrays are only appropriate for list-like sequences, and for key-value pairs like Map, we can use dynamic objects.

So, we change the GlobalScopeObject class to be a DynamicObject, similar to what we did in ArrayObject. We implement reading and writing the variables using InteropLibrary messages, similarly like we did in ArrayObject.

This change to GlobalScopeObject means we need to adjust the Nodes that interact with global variables: GlobalVarDeclStmtNode, FuncDeclStmtNode, GlobalVarReferenceExprNode and GlobalVarAssignmentExprNode.

We use the flags parameter to save whether a given variable is a constant or not, and we check that when performing assignment.

In order to be able to use the @CachedLibrary annotation with the GlobalScopeObject, we create a special Node, GlobalScopeObjectExprNode, that just returns the global scope object from the TruffleLanguage Context using the currentLanguageContext() method defined in the base class of all Nodes. We then add GlobalScopeObjectExprNode as the first child to of each Node that deals with global variables. This way, they can receive the GlobalScopeObject instance as the first argument to their @Specialization methods, and use it in the @CachedLibrary annotation.

Performance results

Thanks to these improvements, we can roll back the changes made in the last part of the series that turned FunctionObject mutable, and remove caching the resolved function we added to GlobalVarReferenceExprNode (which prevents code like function f() {}; f = 3 from working correctly).

As it turns out, this simplified code is twice as fast on the Fibonacci benchmark than the complicated one that implemented GlobalScopeObject with a HashMap in the previous part of the series:

Benchmark                              Mode  Cnt   Score   Error  Units
FibonacciBenchmark.recursive_eval_ezs  avgt    5  49.806 ± 0.835  us/op
FibonacciBenchmark.recursive_eval_js   avgt    5  72.937 ± 2.110  us/op
FibonacciBenchmark.recursive_eval_sl   avgt    5  52.396 ± 0.964  us/op
FibonacciBenchmark.recursive_java      avgt    5  35.726 ± 0.497  us/op