com.oracle.truffle.sl.SLLanguage Maven / Gradle / Ivy
Show all versions of truffle-sl Show documentation
/*
* 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.
*
* 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.
*
*
* Types:
*
* - 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.
*
- Boolean: implemented as the Java primitive type {@code boolean}.
*
- String: implemented as the Java standard type {@link String}.
*
- Function: implementation type {@link SLFunction}.
*
- Object: efficient implementation using the object model provided by Truffle. The
* implementation type of objects is a subclass of {@link DynamicObject}.
*
- Null (with only one value {@code null}): implemented as the singleton
* {@link SLNull#SINGLETON}.
*
* The class {@link SLTypes} lists these types for the Truffle DSL, i.e., for type-specialized
* operations that are specified using Truffle DSL annotations.
*
*
* Language concepts:
*
* - Literals for {@link SLBigIntegerLiteralNode numbers} , {@link SLStringLiteralNode strings},
* and {@link SLFunctionLiteralNode functions}.
*
- 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 ≤}, >, ≥.
*
- 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.
*
- Basic control flow statements: {@link SLBlockNode blocks}, {@link SLIfNode if},
* {@link SLWhileNode while} with {@link SLBreakNode break} and {@link SLContinueNode continue},
* {@link SLReturnNode return}.
*
- Debugging control: {@link SLDebuggerNode debugger} statement uses
* {@link DebuggerTags.AlwaysHalt} tag to halt the execution when run under the debugger.
*
- Function calls: {@link SLInvokeNode invocations} are efficiently implemented with
* {@link SLFunction polymorphic inline caches}.
*
- Object access: {@link SLReadPropertyNode} and {@link SLWritePropertyNode} use a cached
* {@link DynamicObjectLibrary} as the polymorphic inline cache for property reads and writes,
* respectively.
*
*
*
* Syntax and parsing:
* 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}.
*
*
* Builtin functions:
* 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
*
* - {@link SLReadlnBuiltin readln}: Read a String from the {@link SLContext#getInput() standard
* input}.
*
- {@link SLPrintlnBuiltin println}: Write a value to the {@link SLContext#getOutput() standard
* output}.
*
- {@link SLNanoTimeBuiltin nanoTime}: Returns the value of a high-resolution time, in
* nanoseconds.
*
- {@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.
*
- {@link SLStackTraceBuiltin stckTrace}: Print all function activations with all local
* variables.
*
*/
@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 {
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, RootCallTarget> builtinTargets = new ConcurrentHashMap<>();
private final Map 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 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 REFERENCE = LanguageReference.create(SLLanguage.class);
public static SLLanguage get(Node node) {
return REFERENCE.get(node);
}
private static final List> 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();
}
}