Skip to content

Commit

Permalink
Register type definitions lazily (#11938)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
4e6 authored Jan 1, 2025
1 parent ea2d4e5 commit bc6a8aa
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand All @@ -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<Function> constructorFunctionSupplier;
private @CompilerDirectives.CompilationFinal Function constructorFunction;
private @CompilerDirectives.CompilationFinal Function accessor;

private final Lock layoutsLock = new ReentrantLock();
private @CompilerDirectives.CompilationFinal Supplier<Layout> boxedLayoutSupplier;
private @CompilerDirectives.CompilationFinal Layout boxedLayout;
private Layout[] unboxingLayouts = new Layout[0];

Expand Down Expand Up @@ -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.
Expand All @@ -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<InitializationBuilder> 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<InitializationResult> 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;
}
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -323,9 +435,10 @@ public static Map<String, RootNode> 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()) {
Expand All @@ -342,11 +455,10 @@ public static Map<String, RootNode> 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;
Expand All @@ -357,6 +469,10 @@ final Layout[] getUnboxingLayouts() {
}

final Layout getBoxedLayout() {
if (boxedLayout == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
boxedLayout = boxedLayoutSupplier.get();
}
return boxedLayout;
}

Expand Down Expand Up @@ -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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T> implements Supplier<T> {
Expand Down Expand Up @@ -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 <R> the result type
*/
public <R> CachingSupplier<R> map(Function<T, R> f) {
return wrap(() -> f.apply(get()));
}

/**
* Returns a supplier that always returns {@code null} when its {@link Supplier#get()} method is
* called.
Expand Down
Loading

0 comments on commit bc6a8aa

Please sign in to comment.