-
Notifications
You must be signed in to change notification settings - Fork 200
/
Copy pathSLLanguage.java
427 lines (396 loc) · 20.3 KB
/
SLLanguage.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
/*
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.sl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLanguage.ContextPolicy;
import com.oracle.truffle.api.debug.DebuggerTags;
import com.oracle.truffle.api.dsl.NodeFactory;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.instrumentation.AllocationReporter;
import com.oracle.truffle.api.instrumentation.ProvidedTags;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.DynamicObjectLibrary;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.sl.builtins.SLBuiltinNode;
import com.oracle.truffle.sl.builtins.SLDefineFunctionBuiltin;
import com.oracle.truffle.sl.builtins.SLNanoTimeBuiltin;
import com.oracle.truffle.sl.builtins.SLPrintlnBuiltin;
import com.oracle.truffle.sl.builtins.SLReadlnBuiltin;
import com.oracle.truffle.sl.builtins.SLStackTraceBuiltin;
import com.oracle.truffle.sl.nodes.SLEvalRootNode;
import com.oracle.truffle.sl.nodes.SLExpressionNode;
import com.oracle.truffle.sl.nodes.SLRootNode;
import com.oracle.truffle.sl.nodes.SLTypes;
import com.oracle.truffle.sl.nodes.SLUndefinedFunctionRootNode;
import com.oracle.truffle.sl.nodes.controlflow.SLBlockNode;
import com.oracle.truffle.sl.nodes.controlflow.SLBreakNode;
import com.oracle.truffle.sl.nodes.controlflow.SLContinueNode;
import com.oracle.truffle.sl.nodes.controlflow.SLDebuggerNode;
import com.oracle.truffle.sl.nodes.controlflow.SLIfNode;
import com.oracle.truffle.sl.nodes.controlflow.SLReturnNode;
import com.oracle.truffle.sl.nodes.controlflow.SLWhileNode;
import com.oracle.truffle.sl.nodes.expression.SLAddNode;
import com.oracle.truffle.sl.nodes.expression.SLBigIntegerLiteralNode;
import com.oracle.truffle.sl.nodes.expression.SLDivNode;
import com.oracle.truffle.sl.nodes.expression.SLEqualNode;
import com.oracle.truffle.sl.nodes.expression.SLFunctionLiteralNode;
import com.oracle.truffle.sl.nodes.expression.SLInvokeNode;
import com.oracle.truffle.sl.nodes.expression.SLLessOrEqualNode;
import com.oracle.truffle.sl.nodes.expression.SLLessThanNode;
import com.oracle.truffle.sl.nodes.expression.SLLogicalAndNode;
import com.oracle.truffle.sl.nodes.expression.SLLogicalOrNode;
import com.oracle.truffle.sl.nodes.expression.SLMulNode;
import com.oracle.truffle.sl.nodes.expression.SLReadPropertyNode;
import com.oracle.truffle.sl.nodes.expression.SLStringLiteralNode;
import com.oracle.truffle.sl.nodes.expression.SLSubNode;
import com.oracle.truffle.sl.nodes.expression.SLWritePropertyNode;
import com.oracle.truffle.sl.nodes.local.SLReadArgumentNode;
import com.oracle.truffle.sl.nodes.local.SLReadLocalVariableNode;
import com.oracle.truffle.sl.nodes.local.SLWriteLocalVariableNode;
import com.oracle.truffle.sl.parser.SLNodeFactory;
import com.oracle.truffle.sl.parser.SimpleLanguageLexer;
import com.oracle.truffle.sl.parser.SimpleLanguageParser;
import com.oracle.truffle.sl.runtime.SLBigInteger;
import com.oracle.truffle.sl.runtime.SLContext;
import com.oracle.truffle.sl.runtime.SLFunction;
import com.oracle.truffle.sl.runtime.SLFunctionRegistry;
import com.oracle.truffle.sl.runtime.SLLanguageView;
import com.oracle.truffle.sl.runtime.SLNull;
import com.oracle.truffle.sl.runtime.SLObject;
import com.oracle.truffle.sl.runtime.SLStrings;
/**
* SL is a simple language to demonstrate and showcase features of Truffle. The implementation is as
* simple and clean as possible in order to help understanding the ideas and concepts of Truffle.
* The language has first class functions, and objects are key-value stores.
* <p>
* SL is dynamically typed, i.e., there are no type names specified by the programmer. SL is
* strongly typed, i.e., there is no automatic conversion between types. If an operation is not
* available for the types encountered at run time, a type error is reported and execution is
* stopped. For example, {@code 4 - "2"} results in a type error because subtraction is only defined
* for numbers.
*
* <p>
* <b>Types:</b>
* <ul>
* <li>Number: arbitrary precision integer numbers. The implementation uses the Java primitive type
* {@code long} to represent numbers that fit into the 64 bit range, and {@link SLBigInteger} for
* numbers that exceed the range. Using a primitive type such as {@code long} is crucial for
* performance.
* <li>Boolean: implemented as the Java primitive type {@code boolean}.
* <li>String: implemented as the Java standard type {@link String}.
* <li>Function: implementation type {@link SLFunction}.
* <li>Object: efficient implementation using the object model provided by Truffle. The
* implementation type of objects is a subclass of {@link DynamicObject}.
* <li>Null (with only one value {@code null}): implemented as the singleton
* {@link SLNull#SINGLETON}.
* </ul>
* The class {@link SLTypes} lists these types for the Truffle DSL, i.e., for type-specialized
* operations that are specified using Truffle DSL annotations.
*
* <p>
* <b>Language concepts:</b>
* <ul>
* <li>Literals for {@link SLBigIntegerLiteralNode numbers} , {@link SLStringLiteralNode strings},
* and {@link SLFunctionLiteralNode functions}.
* <li>Basic arithmetic, logical, and comparison operations: {@link SLAddNode +}, {@link SLSubNode
* -}, {@link SLMulNode *}, {@link SLDivNode /}, {@link SLLogicalAndNode logical and},
* {@link SLLogicalOrNode logical or}, {@link SLEqualNode ==}, !=, {@link SLLessThanNode <},
* {@link SLLessOrEqualNode ≤}, >, ≥.
* <li>Local variables: local variables must be defined (via a {@link SLWriteLocalVariableNode
* write}) before they can be used (by a {@link SLReadLocalVariableNode read}). Local variables are
* not visible outside of the block where they were first defined.
* <li>Basic control flow statements: {@link SLBlockNode blocks}, {@link SLIfNode if},
* {@link SLWhileNode while} with {@link SLBreakNode break} and {@link SLContinueNode continue},
* {@link SLReturnNode return}.
* <li>Debugging control: {@link SLDebuggerNode debugger} statement uses
* {@link DebuggerTags#AlwaysHalt} tag to halt the execution when run under the debugger.
* <li>Function calls: {@link SLInvokeNode invocations} are efficiently implemented with
* {@link SLDispatchNode polymorphic inline caches}.
* <li>Object access: {@link SLReadPropertyNode} and {@link SLWritePropertyNode} use a cached
* {@link DynamicObjectLibrary} as the polymorphic inline cache for property reads and writes,
* respectively.
* </ul>
*
* <p>
* <b>Syntax and parsing:</b><br>
* The syntax is described as an attributed grammar. The {@link SimpleLanguageParser} and
* {@link SimpleLanguageLexer} are automatically generated by ANTLR 4. The grammar contains semantic
* actions that build the AST for a method. To keep these semantic actions short, they are mostly
* calls to the {@link SLNodeFactory} that performs the actual node creation. All functions found in
* the SL source are added to the {@link SLFunctionRegistry}, which is accessible from the
* {@link SLContext}.
*
* <p>
* <b>Builtin functions:</b><br>
* Library functions that are available to every SL source without prior definition are called
* builtin functions. They are added to the {@link SLFunctionRegistry} when the {@link SLContext} is
* created. Some of the current builtin functions are
* <ul>
* <li>{@link SLReadlnBuiltin readln}: Read a String from the {@link SLContext#getInput() standard
* input}.
* <li>{@link SLPrintlnBuiltin println}: Write a value to the {@link SLContext#getOutput() standard
* output}.
* <li>{@link SLNanoTimeBuiltin nanoTime}: Returns the value of a high-resolution time, in
* nanoseconds.
* <li>{@link SLDefineFunctionBuiltin defineFunction}: Parses the functions provided as a String
* argument and adds them to the function registry. Functions that are already defined are replaced
* with the new version.
* <li>{@link SLStackTraceBuiltin stckTrace}: Print all function activations with all local
* variables.
* </ul>
*/
@TruffleLanguage.Registration(id = SLLanguage.ID, name = "SL", defaultMimeType = SLLanguage.MIME_TYPE, characterMimeTypes = SLLanguage.MIME_TYPE, contextPolicy = ContextPolicy.SHARED, fileTypeDetectors = SLFileDetector.class, //
website = "https://www.graalvm.org/graalvm-as-a-platform/implement-language/")
@ProvidedTags({StandardTags.CallTag.class, StandardTags.StatementTag.class, StandardTags.RootTag.class, StandardTags.RootBodyTag.class, StandardTags.ExpressionTag.class, DebuggerTags.AlwaysHalt.class,
StandardTags.ReadVariableTag.class, StandardTags.WriteVariableTag.class})
public final class SLLanguage extends TruffleLanguage<SLContext> {
public static volatile int counter;
public static final String ID = "sl";
public static final String MIME_TYPE = "application/x-sl";
private static final Source BUILTIN_SOURCE = Source.newBuilder(SLLanguage.ID, "", "SL builtin").build();
public static final TruffleString.Encoding STRING_ENCODING = TruffleString.Encoding.UTF_16;
private final Assumption singleContext = Truffle.getRuntime().createAssumption("Single SL context.");
private final Map<NodeFactory<? extends SLBuiltinNode>, RootCallTarget> builtinTargets = new ConcurrentHashMap<>();
private final Map<TruffleString, RootCallTarget> undefinedFunctions = new ConcurrentHashMap<>();
private final Shape rootShape;
public SLLanguage() {
counter++;
this.rootShape = Shape.newBuilder().layout(SLObject.class).build();
}
@Override
protected SLContext createContext(Env env) {
return new SLContext(this, env, new ArrayList<>(EXTERNAL_BUILTINS));
}
@Override
protected boolean patchContext(SLContext context, Env newEnv) {
context.patchContext(newEnv);
return true;
}
public RootCallTarget getOrCreateUndefinedFunction(TruffleString name) {
RootCallTarget target = undefinedFunctions.get(name);
if (target == null) {
target = new SLUndefinedFunctionRootNode(this, name).getCallTarget();
RootCallTarget other = undefinedFunctions.putIfAbsent(name, target);
if (other != null) {
target = other;
}
}
return target;
}
public RootCallTarget lookupBuiltin(NodeFactory<? extends SLBuiltinNode> factory) {
RootCallTarget target = builtinTargets.get(factory);
if (target != null) {
return target;
}
/*
* The builtin node factory is a class that is automatically generated by the Truffle DSL.
* The signature returned by the factory reflects the signature of the @Specialization
*
* methods in the builtin classes.
*/
int argumentCount = factory.getExecutionSignature().size();
SLExpressionNode[] argumentNodes = new SLExpressionNode[argumentCount];
/*
* Builtin functions are like normal functions, i.e., the arguments are passed in as an
* Object[] array encapsulated in SLArguments. A SLReadArgumentNode extracts a parameter
* from this array.
*/
for (int i = 0; i < argumentCount; i++) {
argumentNodes[i] = new SLReadArgumentNode(i);
}
/* Instantiate the builtin node. This node performs the actual functionality. */
SLBuiltinNode builtinBodyNode = factory.createNode((Object) argumentNodes);
builtinBodyNode.addRootTag();
/* The name of the builtin function is specified via an annotation on the node class. */
TruffleString name = SLStrings.fromJavaString(lookupNodeInfo(builtinBodyNode.getClass()).shortName());
builtinBodyNode.setUnavailableSourceSection();
/* Wrap the builtin in a RootNode. Truffle requires all AST to start with a RootNode. */
SLRootNode rootNode = new SLRootNode(this, new FrameDescriptor(), builtinBodyNode, BUILTIN_SOURCE.createUnavailableSection(), name);
/*
* Register the builtin function in the builtin registry. Call targets for builtins may be
* reused across multiple contexts.
*/
RootCallTarget newTarget = rootNode.getCallTarget();
RootCallTarget oldTarget = builtinTargets.putIfAbsent(factory, newTarget);
if (oldTarget != null) {
return oldTarget;
}
return newTarget;
}
public static NodeInfo lookupNodeInfo(Class<?> clazz) {
if (clazz == null) {
return null;
}
NodeInfo info = clazz.getAnnotation(NodeInfo.class);
if (info != null) {
return info;
} else {
return lookupNodeInfo(clazz.getSuperclass());
}
}
@Override
protected CallTarget parse(ParsingRequest request) throws Exception {
Source source = request.getSource();
Map<TruffleString, RootCallTarget> functions;
/*
* Parse the provided source. At this point, we do not have a SLContext yet. Registration of
* the functions with the SLContext happens lazily in SLEvalRootNode.
*/
if (request.getArgumentNames().isEmpty()) {
functions = SimpleLanguageParser.parseSL(this, source);
} else {
StringBuilder sb = new StringBuilder();
sb.append("function main(");
String sep = "";
for (String argumentName : request.getArgumentNames()) {
sb.append(sep);
sb.append(argumentName);
sep = ",";
}
sb.append(") { return ");
sb.append(source.getCharacters());
sb.append(";}");
String language = source.getLanguage() == null ? ID : source.getLanguage();
Source decoratedSource = Source.newBuilder(language, sb.toString(), source.getName()).build();
functions = SimpleLanguageParser.parseSL(this, decoratedSource);
}
RootCallTarget main = functions.get(SLStrings.MAIN);
RootNode evalMain;
if (main != null) {
/*
* We have a main function, so "evaluating" the parsed source means invoking that main
* function. However, we need to lazily register functions into the SLContext first, so
* we cannot use the original SLRootNode for the main function. Instead, we create a new
* SLEvalRootNode that does everything we need.
*/
evalMain = new SLEvalRootNode(this, main, functions);
} else {
/*
* Even without a main function, "evaluating" the parsed source needs to register the
* functions into the SLContext.
*/
evalMain = new SLEvalRootNode(this, null, functions);
}
return evalMain.getCallTarget();
}
/**
* SLLanguage specifies the {@link ContextPolicy#SHARED} in
* {@link Registration#contextPolicy()}. This means that a single {@link TruffleLanguage}
* instance can be reused for multiple language contexts. Before this happens the Truffle
* framework notifies the language by invoking {@link #initializeMultipleContexts()}. This
* allows the language to invalidate certain assumptions taken for the single context case. One
* assumption SL takes for single context case is located in {@link SLEvalRootNode}. There
* functions are only tried to be registered once in the single context case, but produce a
* boundary call in the multi context case, as function registration is expected to happen more
* than once.
*
* Value identity caches should be avoided and invalidated for the multiple contexts case as no
* value will be the same. Instead, in multi context case, a language should only use types,
* shapes and code to speculate.
*
* For a new language it is recommended to start with {@link ContextPolicy#EXCLUSIVE} and as the
* language gets more mature switch to {@link ContextPolicy#SHARED}.
*/
@Override
protected void initializeMultipleContexts() {
singleContext.invalidate();
}
public boolean isSingleContext() {
return singleContext.isValid();
}
@Override
protected Object getLanguageView(SLContext context, Object value) {
return SLLanguageView.create(value);
}
@Override
protected boolean isVisible(SLContext context, Object value) {
return !InteropLibrary.getFactory().getUncached(value).isNull(value);
}
@Override
protected Object getScope(SLContext context) {
return context.getFunctionRegistry().getFunctionsObject();
}
public Shape getRootShape() {
return rootShape;
}
/**
* Allocate an empty object. All new objects initially have no properties. Properties are added
* when they are first stored, i.e., the store triggers a shape change of the object.
*/
public SLObject createObject(AllocationReporter reporter) {
reporter.onEnter(null, 0, AllocationReporter.SIZE_UNKNOWN);
SLObject object = new SLObject(rootShape);
reporter.onReturnValue(object, 0, AllocationReporter.SIZE_UNKNOWN);
return object;
}
private static final LanguageReference<SLLanguage> REFERENCE = LanguageReference.create(SLLanguage.class);
public static SLLanguage get(Node node) {
return REFERENCE.get(node);
}
private static final List<NodeFactory<? extends SLBuiltinNode>> EXTERNAL_BUILTINS = Collections.synchronizedList(new ArrayList<>());
public static void installBuiltin(NodeFactory<? extends SLBuiltinNode> builtin) {
EXTERNAL_BUILTINS.add(builtin);
}
@Override
protected void exitContext(SLContext context, ExitMode exitMode, int exitCode) {
/*
* Runs shutdown hooks during explicit exit triggered by TruffleContext#closeExit(Node, int)
* or natural exit triggered during natural context close.
*/
context.runShutdownHooks();
}
}