From bc6a8aa2f9b44bdc266b4d0a449a135fa121a6a1 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 1 Jan 2025 15:57:01 +0300 Subject: [PATCH] Register type definitions lazily (#11938) close #10923 Changelog: - update: `registerTypeDefinitions` registers constructors lazily # Important Notes The improvement of time spent in the `registerTypeDefinitions` method (highlighted): #### before ![2024-12-23-192336_1193x245_scrot](https://github.com/user-attachments/assets/0bfd6784-61e6-4d8a-8e47-1972f58bb05c) #### after ![2024-12-23-192401_1197x253_scrot](https://github.com/user-attachments/assets/2fff5b28-f2fe-44c7-b92d-40a11a9f216d) --- .../enso/interpreter/runtime/data/Type.java | 35 ++-- .../runtime/data/atom/AtomConstructor.java | 195 ++++++++++++++---- .../runtime/util/CachingSupplier.java | 12 ++ .../interpreter/runtime/IrToTruffle.scala | 188 +++++++++-------- 4 files changed, 292 insertions(+), 138 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java index 754218d22988..76740d14916d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java @@ -26,6 +26,7 @@ import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.interpreter.runtime.scope.ModuleScope; +import org.enso.interpreter.runtime.util.CachingSupplier; import org.enso.pkg.QualifiedName; @ExportLibrary(TypesLibrary.class) @@ -217,21 +218,25 @@ public void generateGetters(EnsoLanguage language) { var roots = AtomConstructor.collectFieldAccessors(language, this); roots.forEach( (name, node) -> { - var schemaBldr = - FunctionSchema.newBuilder() - .argumentDefinitions( - new ArgumentDefinition( - 0, - Constants.Names.SELF_ARGUMENT, - null, - null, - ArgumentDefinition.ExecutionMode.EXECUTE)); - if (isProjectPrivate) { - schemaBldr.projectPrivate(); - } - var funcSchema = schemaBldr.build(); - var f = new Function(node.getCallTarget(), null, funcSchema); - definitionScope.registerMethod(this, name, f); + var functionSupplier = + CachingSupplier.wrap( + () -> { + var schemaBldr = + FunctionSchema.newBuilder() + .argumentDefinitions( + new ArgumentDefinition( + 0, + Constants.Names.SELF_ARGUMENT, + null, + null, + ArgumentDefinition.ExecutionMode.EXECUTE)); + if (isProjectPrivate) { + schemaBldr.projectPrivate(); + } + var funcSchema = schemaBldr.build(); + return new Function(node.getCallTarget(), null, funcSchema); + }); + definitionScope.registerMethod(this, name, functionSupplier); }); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/atom/AtomConstructor.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/atom/AtomConstructor.java index c14a9ff7d995..db9f212e5dfd 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/atom/AtomConstructor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/atom/AtomConstructor.java @@ -17,6 +17,7 @@ import java.util.TreeMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; import org.enso.compiler.context.LocalScope; import org.enso.interpreter.EnsoLanguage; import org.enso.interpreter.node.ExpressionNode; @@ -33,6 +34,7 @@ import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.interpreter.runtime.scope.ModuleScope; +import org.enso.interpreter.runtime.util.CachingSupplier; import org.enso.pkg.QualifiedName; /** @@ -47,10 +49,13 @@ public final class AtomConstructor extends EnsoObject { private final Module definitionModule; private final boolean builtin; private @CompilerDirectives.CompilationFinal Atom cachedInstance; + private @CompilerDirectives.CompilationFinal(dimensions = 1) String[] fieldNames; + private @CompilerDirectives.CompilationFinal Supplier constructorFunctionSupplier; private @CompilerDirectives.CompilationFinal Function constructorFunction; private @CompilerDirectives.CompilationFinal Function accessor; private final Lock layoutsLock = new ReentrantLock(); + private @CompilerDirectives.CompilationFinal Supplier boxedLayoutSupplier; private @CompilerDirectives.CompilationFinal Layout boxedLayout; private Layout[] unboxingLayouts = new Layout[0]; @@ -90,13 +95,102 @@ public AtomConstructor(String name, Module definitionModule, Type type, boolean * @return {@code true} if {@link #initializeFields} method has already been called */ public boolean isInitialized() { - return constructorFunction != null; + return accessor != null; } boolean isBuiltin() { return builtin; } + /** + * Create new builder required for initialization of the atom constructor. + * + * @param section the source section + * @param localScope the description of the local scope + * @param assignments the expressions that evaluate and assign constructor arguments to local vars + * @param varReads the expressions that read field values from local vars + * @param annotations the list of attached annotations + * @param args the list of argument definitions + */ + public static InitializationBuilder newInitializationBuilder( + SourceSection section, + LocalScope localScope, + ExpressionNode[] assignments, + ExpressionNode[] varReads, + Annotation[] annotations, + ArgumentDefinition[] args) { + return new InitializationBuilder(section, localScope, assignments, varReads, annotations, args); + } + + /** Builder required for initialization of the atom constructor. */ + public static final class InitializationBuilder { + + private final SourceSection section; + private final LocalScope localScope; + private final ExpressionNode[] assignments; + private final ExpressionNode[] varReads; + private final Annotation[] annotations; + private final ArgumentDefinition[] args; + + /** + * Create new builder required for initialization of the atom constructor. + * + * @param section the source section + * @param localScope the description of the local scope + * @param assignments the expressions that evaluate and assign constructor arguments to local + * vars + * @param varReads the expressions that read field values from local vars + * @param annotations the list of attached annotations + * @param args the list of argument definitions + */ + InitializationBuilder( + SourceSection section, + LocalScope localScope, + ExpressionNode[] assignments, + ExpressionNode[] varReads, + Annotation[] annotations, + ArgumentDefinition[] args) { + this.section = section; + this.localScope = localScope; + this.assignments = assignments; + this.varReads = varReads; + this.annotations = annotations; + this.args = args; + } + + private SourceSection getSection() { + return section; + } + + private LocalScope getLocalScope() { + return localScope; + } + + private ExpressionNode[] getAssignments() { + return assignments; + } + + private ExpressionNode[] getVarReads() { + return varReads; + } + + private Annotation[] getAnnotations() { + return annotations; + } + + private ArgumentDefinition[] getArgs() { + return args; + } + } + + /** + * The result of this atom constructor initialization. + * + * @param constructorFunction the atom constructor function + * @param layout the atom layout + */ + private record InitializationResult(Function constructorFunction, Layout layout) {} + /** * Generates a constructor function for this {@link AtomConstructor}. Note that such manually * constructed argument definitions must not have default arguments. @@ -106,49 +200,63 @@ boolean isBuiltin() { public AtomConstructor initializeFields( EnsoLanguage language, ModuleScope.Builder scopeBuilder, ArgumentDefinition... args) { ExpressionNode[] reads = new ExpressionNode[args.length]; + String[] fieldNames = new String[args.length]; for (int i = 0; i < args.length; i++) { reads[i] = ReadArgumentNode.build(i, null); + fieldNames[i] = args[i].getName(); } - return initializeFields( - language, - null, - LocalScope.empty(), - scopeBuilder, - new ExpressionNode[0], - reads, - new Annotation[0], - args); + + var builder = + newInitializationBuilder( + null, LocalScope.empty(), new ExpressionNode[0], reads, new Annotation[0], args); + return initializeFields(language, scopeBuilder, CachingSupplier.forValue(builder), fieldNames); } /** * Sets the fields of this {@link AtomConstructor} and generates a constructor function. * - * @param localScope a description of the local scope - * @param assignments the expressions that evaluate and assign constructor arguments to local vars + * @param language the language implementation * @param scopeBuilder the module scope's builder where the accessor should be registered at - * @param varReads the expressions that read field values from local vars + * @param initializationBuilderSupplier the function supplying the parts required for + * initialization + * @param fieldNames the argument names * @return {@code this}, for convenience */ public AtomConstructor initializeFields( EnsoLanguage language, - SourceSection section, - LocalScope localScope, ModuleScope.Builder scopeBuilder, - ExpressionNode[] assignments, - ExpressionNode[] varReads, - Annotation[] annotations, - ArgumentDefinition... args) { + Supplier initializationBuilderSupplier, + String[] fieldNames) { CompilerDirectives.transferToInterpreterAndInvalidate(); - assert boxedLayout == null : "Don't initialize twice: " + this.name; - if (args.length == 0) { + assert accessor == null : "Don't initialize twice: " + this.name; + this.fieldNames = fieldNames; + if (fieldNames.length == 0) { cachedInstance = BoxingAtom.singleton(this); } else { cachedInstance = null; } - boxedLayout = Layout.createBoxed(args); - this.constructorFunction = - buildConstructorFunction( - language, section, localScope, scopeBuilder, assignments, varReads, annotations, args); + CachingSupplier initializationResultSupplier = + CachingSupplier.wrap( + () -> { + var builder = initializationBuilderSupplier.get(); + var constructorFunction = + buildConstructorFunction( + language, + builder.getSection(), + builder.getLocalScope(), + scopeBuilder, + builder.getAssignments(), + builder.getVarReads(), + builder.getAnnotations(), + builder.getArgs()); + var layout = Layout.createBoxed(builder.getArgs()); + return new InitializationResult(constructorFunction, layout); + }); + this.boxedLayoutSupplier = + initializationResultSupplier.map(initializationResult -> initializationResult.layout); + this.constructorFunctionSupplier = + initializationResultSupplier.map( + initializationResult -> initializationResult.constructorFunction); this.accessor = generateQualifiedAccessor(language, scopeBuilder); return this; } @@ -244,7 +352,7 @@ public ModuleScope getDefinitionScope() { * @return the number of args expected by the constructor. */ public int getArity() { - return constructorFunction.getSchema().getArgumentsCount(); + return fieldNames.length; } /** @@ -279,6 +387,10 @@ public String toString() { * @return the constructor function of this constructor. */ public Function getConstructorFunction() { + if (constructorFunction == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + constructorFunction = constructorFunctionSupplier.get(); + } return constructorFunction; } @@ -323,9 +435,10 @@ public static Map collectFieldAccessors(EnsoLanguage language, // take just the first one. var moduleScope = constructors.iterator().next().getDefinitionScope(); for (var cons : constructors) { - for (var field : cons.getFields()) { - var items = names.computeIfAbsent(field.getName(), (k) -> new ArrayList<>()); - items.add(new GetFieldWithMatchNode.GetterPair(cons, field.getPosition())); + final var fieldNames = cons.getFieldNames(); + for (var i = 0; i < fieldNames.length; i++) { + var items = names.computeIfAbsent(fieldNames[i], (k) -> new ArrayList<>()); + items.add(new GetFieldWithMatchNode.GetterPair(cons, i)); } } for (var entry : names.entrySet()) { @@ -342,11 +455,10 @@ public static Map collectFieldAccessors(EnsoLanguage language, } } else if (constructors.size() == 1) { var cons = constructors.toArray(AtomConstructor[]::new)[0]; - for (var field : cons.getFields()) { - var node = - new GetFieldNode( - language, field.getPosition(), type, field.getName(), cons.getDefinitionScope()); - roots.put(field.getName(), node); + final var fieldNames = cons.getFieldNames(); + for (var i = 0; i < fieldNames.length; i++) { + var node = new GetFieldNode(language, i, type, fieldNames[i], cons.getDefinitionScope()); + roots.put(fieldNames[i], node); } } return roots; @@ -357,6 +469,10 @@ final Layout[] getUnboxingLayouts() { } final Layout getBoxedLayout() { + if (boxedLayout == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + boxedLayout = boxedLayoutSupplier.get(); + } return boxedLayout; } @@ -448,7 +564,16 @@ public QualifiedName getQualifiedTypeName() { * @return the fields defined by this constructor. */ public ArgumentDefinition[] getFields() { - return constructorFunction.getSchema().getArgumentInfos(); + return getConstructorFunction().getSchema().getArgumentInfos(); + } + + /** + * Names of this constructor fields. + * + * @return the field names defined by this constructor. + */ + public String[] getFieldNames() { + return fieldNames; } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/util/CachingSupplier.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/util/CachingSupplier.java index 4f5f17d287c6..feaa10d4de63 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/util/CachingSupplier.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/util/CachingSupplier.java @@ -1,6 +1,7 @@ package org.enso.interpreter.runtime.util; import com.oracle.truffle.api.CompilerDirectives; +import java.util.function.Function; import java.util.function.Supplier; public final class CachingSupplier implements Supplier { @@ -55,6 +56,17 @@ public T get() { } } + /** + * Transform the result of this supplier by applying a mapping function {@code f}. + * + * @param f the mapping function + * @return the supplier providing the value with the mapping function applied + * @param the result type + */ + public CachingSupplier map(Function f) { + return wrap(() -> f.apply(get())); + } + /** * Returns a supplier that always returns {@code null} when its {@link Supplier#get()} method is * called. 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 c77edf10c495..de8730c67113 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 @@ -1,6 +1,5 @@ package org.enso.interpreter.runtime -import java.util.logging.Level import com.oracle.truffle.api.source.{Source, SourceSection} import com.oracle.truffle.api.interop.InteropLibrary import org.enso.compiler.pass.analyse.FramePointer @@ -107,6 +106,9 @@ import org.enso.interpreter.runtime.scope.{ImportExportScope, ModuleScope} import org.enso.interpreter.{Constants, EnsoLanguage} import java.math.BigInteger +import java.util.function.Supplier +import java.util.logging.Level + import scala.annotation.tailrec import scala.collection.mutable import scala.collection.mutable.ArrayBuffer @@ -265,101 +267,111 @@ class IrToTruffle( atomCons: AtomConstructor, atomDefn: Definition.Data ): Unit = { - val scopeInfo = rootScopeInfo("atom definition", atomDefn) - - def dataflowInfo() = atomDefn.unsafeGetMetadata( - DataflowAnalysis, - "No dataflow information associated with an atom." - ) - def frameInfo() = atomDefn.unsafeGetMetadata( - FramePointerAnalysis, - "Method definition missing frame information." - ) - val localScope = new LocalScope( - None, - () => scopeInfo().graph, - () => scopeInfo().graph.rootScope, - dataflowInfo, - frameInfo - ) - - val argFactory = - new DefinitionArgumentProcessor( - scope = localScope, - initialName = "Type " + tpDef.name.name - ) - val argDefs = - new Array[ArgumentDefinition](atomDefn.arguments.size) - val argumentExpressions = - new ArrayBuffer[(RuntimeExpression, RuntimeExpression)] - - for (idx <- atomDefn.arguments.indices) { - val unprocessedArg = atomDefn.arguments(idx) - val checkNode = checkAsTypes(unprocessedArg) - val arg = argFactory.run(unprocessedArg, idx, checkNode) - val fp = unprocessedArg - .unsafeGetMetadata( + val initializationBuilderSupplier + : Supplier[AtomConstructor.InitializationBuilder] = + () => { + val scopeInfo = rootScopeInfo("atom definition", atomDefn) + + def dataflowInfo() = atomDefn.unsafeGetMetadata( + DataflowAnalysis, + "No dataflow information associated with an atom." + ) + def frameInfo() = atomDefn.unsafeGetMetadata( FramePointerAnalysis, - "No frame pointer on an argument definition." + "Method definition missing frame information." ) - .asInstanceOf[FramePointer] - val slotIdx = fp.frameSlotIdx() - argDefs(idx) = arg - val readArgNoCheck = - ReadArgumentNode.build( - idx, - arg.getDefaultValue.orElse(null) + val localScope = new LocalScope( + None, + () => scopeInfo().graph, + () => scopeInfo().graph.rootScope, + dataflowInfo, + frameInfo ) - val readArg = TypeCheckValueNode.wrap(readArgNoCheck, checkNode) - val assignmentArg = AssignmentNode.build(readArg, slotIdx) - val argRead = - ReadLocalVariableNode.build(new FramePointer(0, slotIdx)) - argumentExpressions.append((assignmentArg, argRead)) - } - val (assignments, reads) = argumentExpressions.unzip - // build annotations - val annotations = atomDefn.annotations.map { annotation => - val scopeElements = Seq( - tpDef.name.name, - atomDefn.name.name, - annotation.name - ) - val scopeName = - scopeElements.mkString(Constants.SCOPE_SEPARATOR) - val expressionProcessor = new ExpressionProcessor( - scopeName, - () => scopeInfo().graph, - () => scopeInfo().graph.rootScope, - dataflowInfo, - atomDefn.name.name, - frameInfo - ) - val expressionNode = - expressionProcessor.run(annotation.expression, true) - val closureName = s"" - val closureRootNode = ClosureRootNode.build( - language, - expressionProcessor.scope, - scopeBuilder.asModuleScope(), - expressionNode, - makeSection(scopeBuilder.getModule, annotation.location), - closureName, - true, - false - ) - new RuntimeAnnotation(annotation.name, closureRootNode) - } + val argFactory = + new DefinitionArgumentProcessor( + scope = localScope, + initialName = "Type " + tpDef.name.name + ) + val argDefs = + new Array[ArgumentDefinition](atomDefn.arguments.size) + val argumentExpressions = + new ArrayBuffer[(RuntimeExpression, RuntimeExpression)] + + for (idx <- atomDefn.arguments.indices) { + val unprocessedArg = atomDefn.arguments(idx) + val checkNode = checkAsTypes(unprocessedArg) + val arg = argFactory.run(unprocessedArg, idx, checkNode) + val fp = unprocessedArg + .unsafeGetMetadata( + FramePointerAnalysis, + "No frame pointer on an argument definition." + ) + .asInstanceOf[FramePointer] + val slotIdx = fp.frameSlotIdx() + argDefs(idx) = arg + val readArgNoCheck = + ReadArgumentNode.build( + idx, + arg.getDefaultValue.orElse(null) + ) + val readArg = TypeCheckValueNode.wrap(readArgNoCheck, checkNode) + val assignmentArg = AssignmentNode.build(readArg, slotIdx) + val argRead = + ReadLocalVariableNode.build(new FramePointer(0, slotIdx)) + argumentExpressions.append((assignmentArg, argRead)) + } + + val (assignments, reads) = argumentExpressions.unzip + // build annotations + val annotations = atomDefn.annotations.map { annotation => + val scopeElements = Seq( + tpDef.name.name, + atomDefn.name.name, + annotation.name + ) + val scopeName = + scopeElements.mkString(Constants.SCOPE_SEPARATOR) + val expressionProcessor = new ExpressionProcessor( + scopeName, + () => scopeInfo().graph, + () => scopeInfo().graph.rootScope, + dataflowInfo, + atomDefn.name.name, + frameInfo + ) + val expressionNode = + expressionProcessor.run(annotation.expression, true) + val closureName = s"" + val closureRootNode = ClosureRootNode.build( + language, + expressionProcessor.scope, + scopeBuilder.asModuleScope(), + expressionNode, + makeSection(scopeBuilder.getModule, annotation.location), + closureName, + true, + false + ) + new RuntimeAnnotation(annotation.name, closureRootNode) + } + + AtomConstructor.newInitializationBuilder( + makeSection(scopeBuilder.getModule, atomDefn.location), + localScope, + assignments.toArray, + reads.toArray, + annotations.toArray, + argDefs + ) + } if (!atomCons.isInitialized) { + val fieldNames = atomDefn.arguments.map(_.name.name).toArray atomCons.initializeFields( language, - makeSection(scopeBuilder.getModule, atomDefn.location), - localScope, scopeBuilder, - assignments.toArray, - reads.toArray, - annotations.toArray, - argDefs: _* + initializationBuilderSupplier, + fieldNames ) } }