Better way of registering builtin functions (#255)

This commit is contained in:
Marcin Kostrzewa 2019-10-23 16:18:27 +02:00 committed by GitHub
parent 3c4fd0c55b
commit 6569cc5cb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 137 additions and 82 deletions

View File

@ -32,7 +32,6 @@ import org.enso.interpreter.node.controlflow.DefaultFallbackNode;
import org.enso.interpreter.node.controlflow.FallbackNode; import org.enso.interpreter.node.controlflow.FallbackNode;
import org.enso.interpreter.node.controlflow.IfZeroNode; import org.enso.interpreter.node.controlflow.IfZeroNode;
import org.enso.interpreter.node.controlflow.MatchNode; import org.enso.interpreter.node.controlflow.MatchNode;
import org.enso.interpreter.node.expression.builtin.PrintNodeGen;
import org.enso.interpreter.node.expression.constant.ConstructorNode; import org.enso.interpreter.node.expression.constant.ConstructorNode;
import org.enso.interpreter.node.expression.constant.DynamicSymbolNode; import org.enso.interpreter.node.expression.constant.DynamicSymbolNode;
import org.enso.interpreter.node.expression.literal.IntegerLiteralNode; import org.enso.interpreter.node.expression.literal.IntegerLiteralNode;
@ -370,17 +369,6 @@ public class ExpressionFactory implements AstExpressionVisitor<ExpressionNode> {
return AssignmentNodeGen.create(expr.visit(this), slot); return AssignmentNodeGen.create(expr.visit(this), slot);
} }
/**
* Creates a runtime node representing a print expression.
*
* @param body an expression that computes the value to print
* @return a runtime node representing the print
*/
@Override
public ExpressionNode visitPrint(AstExpression body) {
return PrintNodeGen.create(body.visit(this));
}
/** /**
* Creates a runtime node representing a pattern match. * Creates a runtime node representing a pattern match.
* *

View File

@ -14,7 +14,7 @@ import org.enso.interpreter.runtime.type.TypesGen;
* *
* <p>Enso is an expression-oriented language, and hence doesn't have any statements. This means * <p>Enso is an expression-oriented language, and hence doesn't have any statements. This means
* that all expression execution will return a value, even if that is just the {@link * that all expression execution will return a value, even if that is just the {@link
* Builtins#UNIT} type. * Builtins#unit()} type.
* *
* <p>This class contains specialisations of the {@link #executeGeneric(VirtualFrame) * <p>This class contains specialisations of the {@link #executeGeneric(VirtualFrame)
* executeGeneric} method for various scenarios in order to improve performance. * executeGeneric} method for various scenarios in order to improve performance.

View File

@ -1,52 +1,62 @@
package org.enso.interpreter.node.expression.builtin; package org.enso.interpreter.node.expression.builtin;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.dsl.CachedContext; import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import org.enso.interpreter.Language; import org.enso.interpreter.Language;
import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.Builtins; import org.enso.interpreter.runtime.Builtins;
import org.enso.interpreter.runtime.Context; import org.enso.interpreter.runtime.Context;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.function.ArgumentSchema;
import org.enso.interpreter.runtime.callable.function.Function;
import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
/** This node allows for printing the result of an arbitrary expression to standard output. */ /** This node allows for printing the result of arbitrary values to standard output. */
@NodeInfo(shortName = "print", description = "Prints the value of child expression.") @NodeInfo(shortName = "print", description = "Root of the IO.println method.")
public abstract class PrintNode extends ExpressionNode { public abstract class PrintNode extends RootNode {
@Child private ExpressionNode expression;
/** /**
* Creates a node that prints the result of its expression. * Creates a root node for the builtin IO.println method.
* *
* @param expression the expression to print the result of * @param language the current {@link Language} instance.
*/ */
public PrintNode(ExpressionNode expression) { public PrintNode(Language language) {
this.expression = expression; super(language);
} }
/**
* Executes the print node.
*
* @param frame the stack frame for execution
* @return unit {@link Builtins#UNIT unit} type
*/
@Specialization @Specialization
public Object doPrint(VirtualFrame frame, @CachedContext(Language.class) Context ctx) { protected Object doPrint(VirtualFrame frame, @CachedContext(Language.class) Context ctx) {
doPrint(ctx.getOut(), expression.executeGeneric(frame)); doPrint(ctx.getOut(), Function.ArgumentsHelper.getPositionalArguments(frame.getArguments())[1]);
return Builtins.UNIT.newInstance(); return ctx.getUnit().newInstance();
} }
/**
* Prints the provided value to standard output.
*
* @param object the value to print
*/
@CompilerDirectives.TruffleBoundary @CompilerDirectives.TruffleBoundary
private void doPrint(PrintStream out, Object object) { private void doPrint(PrintStream out, Object object) {
out.println(object); out.println(object);
} }
/**
* Creates a {@link Function} object ignoring its first argument and printing the second to the
* standard output.
*
* @param language the current {@link Language} instance
* @return a {@link Function} object wrapping the behavior of this node
*/
public static Function toFunction(Language language) {
PrintNode node = PrintNodeGen.create(language);
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node);
ArgumentSchema schema =
new ArgumentSchema(
new ArgumentDefinition(0, "this", false), new ArgumentDefinition(1, "value", false));
return new Function(callTarget, null, schema);
}
} }

View File

@ -1,5 +1,6 @@
package org.enso.interpreter.node.scope; package org.enso.interpreter.node.scope;
import com.oracle.truffle.api.dsl.CachedContext;
import com.oracle.truffle.api.dsl.NodeChild; import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeField; import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
@ -7,8 +8,10 @@ import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.FrameSlotKind; import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo; import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.Builtins; import org.enso.interpreter.runtime.Builtins;
import org.enso.interpreter.runtime.Context;
/** This node represents an assignment to a variable in a given scope. */ /** This node represents an assignment to a variable in a given scope. */
@NodeInfo(shortName = "=", description = "Assigns expression result to a variable.") @NodeInfo(shortName = "=", description = "Assigns expression result to a variable.")
@ -20,14 +23,16 @@ public abstract class AssignmentNode extends ExpressionNode {
* *
* @param frame the frame to write to * @param frame the frame to write to
* @param value the value to write * @param value the value to write
* @return the {@link Builtins#UNIT unit} type * @param ctx language context for global values access
* @return the unit type
*/ */
@Specialization @Specialization
protected Object writeLong(VirtualFrame frame, long value) { protected Object writeLong(
VirtualFrame frame, long value, @CachedContext(Language.class) Context ctx) {
frame.getFrameDescriptor().setFrameSlotKind(getFrameSlot(), FrameSlotKind.Long); frame.getFrameDescriptor().setFrameSlotKind(getFrameSlot(), FrameSlotKind.Long);
frame.setLong(getFrameSlot(), value); frame.setLong(getFrameSlot(), value);
return Builtins.UNIT.newInstance(); return ctx.getUnit().newInstance();
} }
/** /**
@ -35,14 +40,16 @@ public abstract class AssignmentNode extends ExpressionNode {
* *
* @param frame the frame to write to * @param frame the frame to write to
* @param value the value to write * @param value the value to write
* @return the {@link Builtins#UNIT unit} type * @param ctx language context for global values access
* @return the unit type
*/ */
@Specialization @Specialization
protected Object writeObject(VirtualFrame frame, Object value) { protected Object writeObject(
VirtualFrame frame, Object value, @CachedContext(Language.class) Context ctx) {
frame.getFrameDescriptor().setFrameSlotKind(getFrameSlot(), FrameSlotKind.Object); frame.getFrameDescriptor().setFrameSlotKind(getFrameSlot(), FrameSlotKind.Object);
frame.setObject(getFrameSlot(), value); frame.setObject(getFrameSlot(), value);
return Builtins.UNIT.newInstance(); return ctx.getUnit().newInstance();
} }
/** /**

View File

@ -1,24 +1,55 @@
package org.enso.interpreter.runtime; package org.enso.interpreter.runtime;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.expression.builtin.PrintNode;
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.interpreter.runtime.scope.ModuleScope;
/** Container class for static predefined atoms and their containing scope. */ /** Container class for static predefined atoms, methods, and their containing scope. */
public class Builtins { public class Builtins {
public static final ModuleScope BUILTIN_SCOPE = new ModuleScope(); private final ModuleScope scope;
public static final AtomConstructor UNIT = private final AtomConstructor unit;
new AtomConstructor("Unit", BUILTIN_SCOPE).initializeFields();
public static final AtomConstructor NIL =
new AtomConstructor("Nil", BUILTIN_SCOPE).initializeFields();
public static final AtomConstructor CONS =
new AtomConstructor("Cons", BUILTIN_SCOPE)
.initializeFields(
new ArgumentDefinition(0, "head", false), new ArgumentDefinition(1, "rest", false));
static { /**
BUILTIN_SCOPE.registerConstructor(Builtins.CONS); * Creates an instance with builtin methods installed.
BUILTIN_SCOPE.registerConstructor(Builtins.NIL); *
BUILTIN_SCOPE.registerConstructor(Builtins.UNIT); * @param language the current {@link Language} instance
*/
public Builtins(Language language) {
scope = new ModuleScope();
unit = new AtomConstructor("Unit", scope).initializeFields();
AtomConstructor nil = new AtomConstructor("Nil", scope).initializeFields();
AtomConstructor cons =
new AtomConstructor("Cons", scope)
.initializeFields(
new ArgumentDefinition(0, "head", false), new ArgumentDefinition(1, "rest", false));
AtomConstructor io = new AtomConstructor("IO", scope).initializeFields();
scope.registerConstructor(cons);
scope.registerConstructor(nil);
scope.registerConstructor(unit);
scope.registerConstructor(io);
scope.registerMethod(io, "println", PrintNode.toFunction(language));
}
/**
* Returns the {@code Unit} atom constructor.
*
* @return the {@code Unit} atom constructor
*/
public AtomConstructor unit() {
return unit;
}
/**
* Returns the builtin module scope.
*
* @return the builtin module scope
*/
public ModuleScope getScope() {
return scope;
} }
} }

View File

@ -20,6 +20,7 @@ import org.enso.interpreter.Language;
import org.enso.interpreter.builder.ModuleScopeExpressionFactory; import org.enso.interpreter.builder.ModuleScopeExpressionFactory;
import org.enso.interpreter.node.EnsoRootNode; import org.enso.interpreter.node.EnsoRootNode;
import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.error.ModuleDoesNotExistException; import org.enso.interpreter.runtime.error.ModuleDoesNotExistException;
import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.interpreter.runtime.scope.ModuleScope;
import org.enso.interpreter.util.ScalaConversions; import org.enso.interpreter.util.ScalaConversions;
@ -36,6 +37,7 @@ public class Context {
private final Env environment; private final Env environment;
private final Map<String, Module> knownFiles; private final Map<String, Module> knownFiles;
private final PrintStream out; private final PrintStream out;
private final Builtins builtins;
/** /**
* Creates a new Enso context. * Creates a new Enso context.
@ -47,6 +49,7 @@ public class Context {
this.language = language; this.language = language;
this.environment = environment; this.environment = environment;
this.out = new PrintStream(environment.out()); this.out = new PrintStream(environment.out());
this.builtins = new Builtins(language);
List<File> packagePaths = RuntimeOptions.getPackagesPaths(environment); List<File> packagePaths = RuntimeOptions.getPackagesPaths(environment);
// TODO [MK] Replace getTruffleFile with getInternalTruffleFile when Graal 19.3.0 comes out. // TODO [MK] Replace getTruffleFile with getInternalTruffleFile when Graal 19.3.0 comes out.
@ -86,7 +89,7 @@ public class Context {
* @return a call target which execution corresponds to the toplevel executable bits in the module * @return a call target which execution corresponds to the toplevel executable bits in the module
*/ */
public CallTarget parse(Source source) { public CallTarget parse(Source source) {
return parse(source, new ModuleScope()); return parse(source, createScope());
} }
/** /**
@ -131,4 +134,29 @@ public class Context {
public PrintStream getOut() { public PrintStream getOut() {
return out; return out;
} }
/**
* Creates a new module scope that automatically imports all the builtin types and methods.
*
* @return a new module scope with automatic builtins dependency.
*/
public ModuleScope createScope() {
ModuleScope moduleScope = new ModuleScope();
moduleScope.addImport(getBuiltins().getScope());
return moduleScope;
}
private Builtins getBuiltins() {
return builtins;
}
/**
* Returns the atom constructor corresponding to the {@code Unit} type, for builtin constructs
* that need to return an atom of this type.
*
* @return the builtin {@code Unit} atom constructor
*/
public AtomConstructor getUnit() {
return getBuiltins().unit();
}
} }

View File

@ -28,7 +28,7 @@ public class Module {
*/ */
public ModuleScope requestParse(Context context) throws IOException { public ModuleScope requestParse(Context context) throws IOException {
if (cachedScope == null) { if (cachedScope == null) {
cachedScope = new ModuleScope(); cachedScope = context.createScope();
context.parse(file, cachedScope); context.parse(file, cachedScope);
} }
return cachedScope; return cachedScope;

View File

@ -48,7 +48,7 @@ public class ArgumentSchema {
* *
* @param argumentInfos Definition site arguments information * @param argumentInfos Definition site arguments information
*/ */
public ArgumentSchema(ArgumentDefinition[] argumentInfos) { public ArgumentSchema(ArgumentDefinition... argumentInfos) {
this(argumentInfos, new boolean[argumentInfos.length], new CallArgumentInfo[0]); this(argumentInfos, new boolean[argumentInfos.length], new CallArgumentInfo[0]);
} }

View File

@ -1,6 +1,7 @@
package org.enso.interpreter.runtime.scope; package org.enso.interpreter.runtime.scope;
import org.enso.interpreter.runtime.Builtins; import org.enso.interpreter.runtime.Builtins;
import org.enso.interpreter.runtime.Module;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.Function;
@ -14,11 +15,6 @@ public class ModuleScope {
private final Set<ModuleScope> imports = new HashSet<>(); private final Set<ModuleScope> imports = new HashSet<>();
private final Set<ModuleScope> transitiveImports = new HashSet<>(); private final Set<ModuleScope> transitiveImports = new HashSet<>();
/** Creates a new scope. Every scope implicitly imports the builtin scope. */
public ModuleScope() {
imports.add(Builtins.BUILTIN_SCOPE);
}
/** /**
* Adds an Atom constructor definition to the module scope. * Adds an Atom constructor definition to the module scope.
* *
@ -81,9 +77,13 @@ public class ModuleScope {
*/ */
public Function lookupMethodDefinition(AtomConstructor atom, String name) { public Function lookupMethodDefinition(AtomConstructor atom, String name) {
Function definedWithAtom = atom.getDefinitionScope().getMethodMapFor(atom).get(name); Function definedWithAtom = atom.getDefinitionScope().getMethodMapFor(atom).get(name);
if (definedWithAtom != null) { return definedWithAtom; } if (definedWithAtom != null) {
return definedWithAtom;
}
Function definedHere = getMethodMapFor(atom).get(name); Function definedHere = getMethodMapFor(atom).get(name);
if (definedHere != null) { return definedHere; } if (definedHere != null) {
return definedHere;
}
return transitiveImports.stream() return transitiveImports.stream()
.map(scope -> scope.getMethodMapFor(atom).get(name)) .map(scope -> scope.getMethodMapFor(atom).get(name))
.filter(Objects::nonNull) .filter(Objects::nonNull)

View File

@ -41,8 +41,6 @@ trait AstExpressionVisitor[+T] {
def visitAssignment(varName: String, expr: AstExpression): T def visitAssignment(varName: String, expr: AstExpression): T
def visitPrint(body: AstExpression): T
def visitMatch( def visitMatch(
target: AstExpression, target: AstExpression,
branches: java.util.List[AstCase], branches: java.util.List[AstCase],
@ -203,11 +201,6 @@ case class AstAssignment(name: String, body: AstExpression)
visitor.visitAssignment(name, body) visitor.visitAssignment(name, body)
} }
case class AstPrint(body: AstExpression) extends AstExpression {
override def visit[T](visitor: AstExpressionVisitor[T]): T =
visitor.visitPrint(body)
}
case class AstIfZero( case class AstIfZero(
cond: AstExpression, cond: AstExpression,
ifTrue: AstExpression, ifTrue: AstExpression,
@ -293,7 +286,7 @@ class EnsoParserInternal extends JavaTokenParsers {
} }
def expression: Parser[AstExpression] = def expression: Parser[AstExpression] =
desuspend | print | ifZero | matchClause | arith | function desuspend | ifZero | matchClause | arith | function
def functionCall: Parser[AstApply] = def functionCall: Parser[AstApply] =
"@" ~> expression ~ (argList ?) ~ defaultSuspend ^^ { "@" ~> expression ~ (argList ?) ~ defaultSuspend ^^ {
@ -313,8 +306,6 @@ class EnsoParserInternal extends JavaTokenParsers {
case v ~ exp => AstAssignment(v, exp) case v ~ exp => AstAssignment(v, exp)
} }
def print: Parser[AstPrint] = "print:" ~> expression ^^ AstPrint
def ifZero: Parser[AstIfZero] = def ifZero: Parser[AstIfZero] =
"ifZero:" ~> "[" ~> (expression ~ ("," ~> expression ~ ("," ~> expression))) <~ "]" ^^ { "ifZero:" ~> "[" ~> (expression ~ ("," ~> expression ~ ("," ~> expression))) <~ "]" ^^ {
case cond ~ (ift ~ iff) => AstIfZero(cond, ift, iff) case cond ~ (ift ~ iff) => AstIfZero(cond, ift, iff)

View File

@ -8,7 +8,7 @@ class LazyArgumentsTest extends LanguageTest {
""" """
|@{ |@{
| foo = { |i, $x, $y| ifZero: [i, $x, $y] }; | foo = { |i, $x, $y| ifZero: [i, $x, $y] };
| @foo [1, (print: 1), (print: 2)] | @foo [1, @println [@IO, 1], @println [@IO, 2]]
|} |}
|""".stripMargin |""".stripMargin
noException should be thrownBy parse(code) noException should be thrownBy parse(code)
@ -53,9 +53,9 @@ class LazyArgumentsTest extends LanguageTest {
|Bar.method = { |x| 10 } |Bar.method = { |x| 10 }
| |
|@{ |@{
| @method [@Foo, (print: 1)]; | @method [@Foo, @println [@IO, 1]];
| @method [@Bar, (print: 2)]; | @method [@Bar, @println [@IO, 2]];
| @method [@Foo, (print: 3)] | @method [@Foo, @println [@IO, 3]]
|} |}
|""".stripMargin |""".stripMargin
eval(code) eval(code)
@ -68,8 +68,8 @@ class LazyArgumentsTest extends LanguageTest {
|@{ |@{
| if = { |c, $ifT, $ifF| ifZero: [c, $ifT, $ifF] }; | if = { |c, $ifT, $ifF| ifZero: [c, $ifT, $ifF] };
| foo = { |c| @if [c] }; | foo = { |c| @if [c] };
| @foo [0, (print: 1), (print: 2)]; | @foo [0, @println [@IO, 1], @println [@IO, 2]];
| @foo [1, (print: 3), (print: 4)] | @foo [1, @println [@IO, 3], @println [@IO, 4]]
|} |}
|""".stripMargin |""".stripMargin
eval(code) eval(code)