diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala index 70adb2c4d855..929ccff13116 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala @@ -1,7 +1,6 @@ package org.enso.compiler.context import org.enso.scala.wrapper.ScalaConversions -import org.enso.compiler.pass.analyse.FrameAnalysisMeta import org.enso.compiler.pass.analyse.FramePointer import org.enso.compiler.pass.analyse.FrameVariableNames import org.enso.compiler.pass.analyse.DataflowAnalysis @@ -39,7 +38,7 @@ class LocalScope( final val aliasingGraph: () => AliasGraph, final private val scopeProvider: () => AliasGraph.Scope, final private val dataflowInfoProvider: () => DataflowAnalysis.Metadata, - final private val symbolsProvider: () => FrameAnalysisMeta = null, + final private val symbolsProvider: () => FrameVariableNames = null, final val flattenToParent: Boolean = false, private val parentFrameSlotIdxs: () => Map[AliasGraph.Id, Int] = () => Map() ) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/scope/LazyAssignmentNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/scope/LazyAssignmentNode.java new file mode 100644 index 000000000000..ae6f4d1ec24f --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/scope/LazyAssignmentNode.java @@ -0,0 +1,87 @@ +package org.enso.interpreter.node.scope; + +import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.NodeChild; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.Frame; +import com.oracle.truffle.api.frame.FrameSlotKind; +import com.oracle.truffle.api.frame.MaterializedFrame; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.NodeInfo; +import org.enso.interpreter.node.ExpressionNode; +import org.enso.interpreter.runtime.callable.function.Function; + +/** This node represents an assignment to a variable in a given scope. */ +@NodeInfo(shortName = "=", description = "Lazily assigns expression result to a variable.") +@NodeChild(value = "rhsNode", type = ExpressionNode.class) +public abstract class LazyAssignmentNode extends ExpressionNode { + + private final int parentLevel; + private final int frameSlotIdx; + + LazyAssignmentNode(int parentLevel, int frameSlotIdx) { + this.parentLevel = parentLevel; + this.frameSlotIdx = frameSlotIdx; + } + + /** + * Creates an instance of this node. + * + * @param expression the expression being assigned + * @param frameSlotIdx the slot index to which {@code expression} is being assigned + * @return a node representing an assignment + */ + public static LazyAssignmentNode build( + ExpressionNode expression, int parentLevel, int frameSlotIdx) { + return LazyAssignmentNodeGen.create(parentLevel, frameSlotIdx, expression); + } + + /** + * Writes a long value into the provided frame. + * + * @param frame the frame to write to + * @param value the value to write + * @return the unit type + */ + @Specialization(guards = "isLongOrIllegal(frame)") + protected Object writeLong(VirtualFrame frame, long value) { + var realFrame = getParentFrame(frame); + realFrame.getFrameDescriptor().setSlotKind(frameSlotIdx, FrameSlotKind.Long); + realFrame.setLong(frameSlotIdx, value); + return value; + } + + /** + * Writes an object value into the provided frame. + * + * @param frame the frame to write to + * @param value the value to write + * @return the unit type + */ + @Fallback + protected Object writeObject(VirtualFrame frame, Object value) { + var realFrame = getParentFrame(frame); + realFrame.getFrameDescriptor().setSlotKind(frameSlotIdx, FrameSlotKind.Object); + realFrame.setObject(frameSlotIdx, value); + + return value; + } + + boolean isLongOrIllegal(VirtualFrame frame) { + var realFrame = getParentFrame(frame); + FrameSlotKind kind = realFrame.getFrameDescriptor().getSlotKind(frameSlotIdx); + return kind == FrameSlotKind.Long || kind == FrameSlotKind.Illegal; + } + + private MaterializedFrame getParentFrame(Frame frame) { + return Function.ArgumentsHelper.getLocalScope(frame.getArguments()); + } + + private MaterializedFrame getProperFrame(Frame frame) { + MaterializedFrame currentFrame = getParentFrame(frame); + for (int i = 1; i < parentLevel; i++) { + currentFrame = getParentFrame(currentFrame); + } + return currentFrame; + } +} diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index 273599239603..82b4c2c7b733 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -78,7 +78,9 @@ import org.enso.interpreter.node.expression.builtin.BuiltinRootNode import org.enso.interpreter.node.expression.constant._ import org.enso.interpreter.node.expression.foreign.ForeignMethodCallNode import org.enso.interpreter.node.expression.literal.LiteralNode -import org.enso.interpreter.node.scope.{AssignmentNode, ReadLocalVariableNode} +import org.enso.interpreter.node.scope.LazyAssignmentNode +import org.enso.interpreter.node.scope.AssignmentNode +import org.enso.interpreter.node.scope.ReadLocalVariableNode import org.enso.interpreter.node.{ BaseNode, ClosureRootNode, @@ -255,10 +257,12 @@ private[runtime] class IrToTruffle( DataflowAnalysis, "Method definition missing dataflow information." ) - def frameInfo() = conversion.unsafeGetMetadata( - FramePointerAnalysis, - "Method definition missing frame information." - ) + def frameInfo() = conversion + .unsafeGetMetadata( + FramePointerAnalysis, + "Method definition missing frame information." + ) + .asInstanceOf[FrameVariableNames] val toType = conversion.methodReference.typePointer match { @@ -335,10 +339,12 @@ private[runtime] class IrToTruffle( DataflowAnalysis, "Method definition missing dataflow information." ) - def frameInfo() = method.unsafeGetMetadata( - FramePointerAnalysis, - "Method definition missing frame information." - ) + def frameInfo() = method + .unsafeGetMetadata( + FramePointerAnalysis, + "Method definition missing frame information." + ) + .asInstanceOf[FrameVariableNames] @tailrec def getContext(tp: Expression): Option[String] = tp match { @@ -447,10 +453,12 @@ private[runtime] class IrToTruffle( DataflowAnalysis, "No dataflow information associated with an atom." ) - def frameInfo() = atomDefn.unsafeGetMetadata( - FramePointerAnalysis, - "Method definition missing frame information." - ) + def frameInfo() = atomDefn + .unsafeGetMetadata( + FramePointerAnalysis, + "Method definition missing frame information." + ) + .asInstanceOf[FrameVariableNames] val localScope = new LocalScope( None, () => scopeInfo().graph, @@ -676,10 +684,12 @@ private[runtime] class IrToTruffle( scopeElements.init .mkString(Constants.SCOPE_SEPARATOR) ) - def frameInfo() = annotation.unsafeGetMetadata( - FramePointerAnalysis, - "Method definition missing frame information." - ) + def frameInfo() = annotation + .unsafeGetMetadata( + FramePointerAnalysis, + "Method definition missing frame information." + ) + .asInstanceOf[FrameVariableNames] val expressionProcessor = new ExpressionProcessor( scopeName, () => scopeInfo().graph, @@ -1182,7 +1192,7 @@ private[runtime] class IrToTruffle( scope: () => AliasScope, dataflowInfo: () => DataflowAnalysis.Metadata, initialName: String, - frameInfo: () => FramePointerAnalysis.Metadata = null + frameInfo: () => FrameVariableNames = null ) = { this( new LocalScope(None, graph, scope, dataflowInfo, frameInfo), @@ -1220,20 +1230,21 @@ private[runtime] class IrToTruffle( def run( ir: Expression, subjectToInstrumentation: Boolean - ): RuntimeExpression = run(ir, false, subjectToInstrumentation) + ): RuntimeExpression = run(ir, -1, subjectToInstrumentation) private def run( ir: Expression, - binding: Boolean, + bindingToIndex: Int, subjectToInstrumentation: Boolean ): RuntimeExpression = { var runtimeExpression = ir match { - case block: Expression.Block => processBlock(block) + case block: Expression.Block => processBlock(block, bindingToIndex) case literal: Literal => processLiteral(literal) case app: Application => processApplication(app, subjectToInstrumentation) - case name: Name => processName(name) - case function: Function => processFunction(function, binding) + case name: Name => processName(name) + case function: Function => + processFunction(function, bindingToIndex >= 0) case binding: Expression.Binding => processBinding(binding) case caseExpr: Case => processCase(caseExpr, subjectToInstrumentation) @@ -1245,7 +1256,7 @@ private[runtime] class IrToTruffle( asc.signature ) if (checkNode != null) { - val body = run(asc.typed, binding, subjectToInstrumentation) + val body = run(asc.typed, bindingToIndex, subjectToInstrumentation) TypeCheckValueNode.wrap(body, checkNode) } else { processType(asc) @@ -1304,8 +1315,11 @@ private[runtime] class IrToTruffle( * @param block the block to generate code for * @return the truffle nodes corresponding to `block` */ - private def processBlock(block: Expression.Block): RuntimeExpression = { - if (block.suspended) { + private def processBlock( + block: Expression.Block, + bindingToIndex: Int + ): RuntimeExpression = { + if (block.suspended && bindingToIndex >= 0) { val scopeInfo = childScopeInfo("block", block) def frameInfo() = block .unsafeGetMetadata( @@ -1322,13 +1336,15 @@ private[runtime] class IrToTruffle( ) val childScope = childFactory.scope - val blockNode = childFactory.processBlock(block.copy(suspended = false)) + val blockNode = childFactory.processBlock(block, -1) + val blockNodeWithAssignment = + LazyAssignmentNode.build(blockNode, 1, bindingToIndex) val defaultRootNode = ClosureRootNode.build( language, childScope, scopeBuilder.asModuleScope(), - blockNode, + blockNodeWithAssignment, makeSource(scopeBuilder.getModule), makeLocation(block.location), currentVarName, @@ -1843,8 +1859,9 @@ private[runtime] class IrToTruffle( currentVarName = binding.name.name val slotIdx = fp.frameSlotIdx() + val rhs = this.run(binding.expression, slotIdx, true) setLocation( - AssignmentNode.build(this.run(binding.expression, true, true), slotIdx), + AssignmentNode.build(rhs, slotIdx), binding.location ) } @@ -2209,7 +2226,7 @@ private[runtime] class IrToTruffle( argSlotIdxs ) case _ => - ExpressionProcessor.this.run(body, false, subjectToInstrumentation) + ExpressionProcessor.this.run(body, -1, subjectToInstrumentation) } if (typeCheck == null) { diff --git a/test/Base_Tests/src/Data/Vector_Spec.enso b/test/Base_Tests/src/Data/Vector_Spec.enso index 544626bcfec5..298a588bbee9 100644 --- a/test/Base_Tests/src/Data/Vector_Spec.enso +++ b/test/Base_Tests/src/Data/Vector_Spec.enso @@ -91,9 +91,9 @@ type_spec suite_builder name alter = suite_builder.group name group_builder-> Vector.new 100 (ix -> ix + 1) . fold 0 (+) . should_equal 5050 r = Ref.new 0 - next = + next _ = r.put r.get+1 - const = Vector.new 4 _->next + const = Vector.new 4 next const.should_equal [0, 1, 2, 3] group_builder.specify "should allow vector creation with a constant constructor" <| diff --git a/test/Base_Tests/src/Runtime/Lazy_Spec.enso b/test/Base_Tests/src/Runtime/Lazy_Spec.enso index 5075b9350159..c1b865c033f8 100644 --- a/test/Base_Tests/src/Runtime/Lazy_Spec.enso +++ b/test/Base_Tests/src/Runtime/Lazy_Spec.enso @@ -33,7 +33,7 @@ add_specs suite_builder = suite_builder.group "Lazy" group_builder-> compute . should_equal "Value" compute . should_equal "Value" - ref.get . should_equal 3 + ref.get . should_equal 1 group_builder.specify "should compute the result only once, even if copied" <| ref = Ref.new 0