Speeding up "hello world" example by 16%

This commit is contained in:
Jaroslav Tulach 2023-11-19 16:38:31 +01:00 committed by GitHub
parent 5a7ad6bfe4
commit ba19813511
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 3219 additions and 703 deletions

View File

@ -254,6 +254,8 @@ lazy val buildNativeImage =
lazy val enso = (project in file("."))
.settings(version := "0.1")
.aggregate(
`persistance-dsl`,
`persistance`,
`interpreter-dsl`,
`interpreter-dsl-test`,
`json-rpc-server-test`,
@ -1074,6 +1076,35 @@ lazy val searcher = project
.dependsOn(testkit % Test)
.dependsOn(`polyglot-api`)
lazy val `persistance` = (project in file("lib/java/persistance"))
.settings(
version := "0.1",
frgaalJavaCompilerSetting,
Compile / javacOptions := ((Compile / javacOptions).value),
libraryDependencies ++= Seq(
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion,
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test
)
)
.dependsOn(`persistance-dsl` % Test)
lazy val `persistance-dsl` = (project in file("lib/java/persistance-dsl"))
.settings(
version := "0.1",
frgaalJavaCompilerSetting,
Compile / javacOptions := ((Compile / javacOptions).value ++
// Only run ServiceProvider processor and ignore those defined in META-INF, thus
// fixing incremental compilation setup
Seq(
"-processor",
"org.netbeans.modules.openide.util.ServiceProviderProcessor"
)),
libraryDependencies ++= Seq(
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided"
)
)
lazy val `interpreter-dsl` = (project in file("lib/scala/interpreter-dsl"))
.settings(
version := "0.1",
@ -1087,7 +1118,7 @@ lazy val `interpreter-dsl` = (project in file("lib/scala/interpreter-dsl"))
)),
libraryDependencies ++= Seq(
"org.apache.commons" % "commons-lang3" % commonsLangVersion,
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion,
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided",
"com.google.guava" % "guava" % guavaVersion exclude ("com.google.code.findbugs", "jsr305")
)
)
@ -1373,23 +1404,24 @@ lazy val runtime = (project in file("engine/runtime"))
scalacOptions += "-Ymacro-annotations",
scalacOptions ++= Seq("-Ypatmat-exhaust-depth", "off"),
libraryDependencies ++= jmh ++ jaxb ++ circe ++ GraalVM.langsPkgs ++ Seq(
"org.apache.commons" % "commons-lang3" % commonsLangVersion,
"org.apache.tika" % "tika-core" % tikaVersion,
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion % "provided",
"org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-tck" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-tck-common" % graalMavenPackagesVersion % "provided",
"org.scalacheck" %% "scalacheck" % scalacheckVersion % Test,
"org.scalactic" %% "scalactic" % scalacticVersion % Test,
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % Benchmark,
"org.typelevel" %% "cats-core" % catsVersion,
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
"org.hamcrest" % "hamcrest-all" % hamcrestVersion % Test,
"org.slf4j" % "slf4j-nop" % slf4jVersion % Benchmark
"org.apache.commons" % "commons-lang3" % commonsLangVersion,
"org.apache.tika" % "tika-core" % tikaVersion,
"org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion % "provided",
"org.graalvm.sdk" % "polyglot-tck" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-tck" % graalMavenPackagesVersion % "provided",
"org.graalvm.truffle" % "truffle-tck-common" % graalMavenPackagesVersion % "provided",
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided",
"org.scalacheck" %% "scalacheck" % scalacheckVersion % Test,
"org.scalactic" %% "scalactic" % scalacticVersion % Test,
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % Benchmark,
"org.typelevel" %% "cats-core" % catsVersion,
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
"org.hamcrest" % "hamcrest-all" % hamcrestVersion % Test,
"org.slf4j" % "slf4j-nop" % slf4jVersion % Benchmark
),
// Add all GraalVM packages with Runtime scope - we don't need them for compilation,
// just provide them at runtime (in module-path).
@ -1454,6 +1486,7 @@ lazy val runtime = (project in file("engine/runtime"))
.dependsOn(`common-polyglot-core-utils`)
.dependsOn(`edition-updater`)
.dependsOn(`interpreter-dsl`)
.dependsOn(`persistance-dsl` % "provided")
.dependsOn(`library-manager`)
.dependsOn(`logging-truffle-connector`)
.dependsOn(`polyglot-api`)
@ -1468,14 +1501,23 @@ lazy val `runtime-parser` =
.settings(
frgaalJavaCompilerSetting,
instrumentationSettings,
commands += WithDebugCommand.withDebug,
fork := true,
Test / javaOptions ++= Seq(
"-Dgraalvm.locatorDisabled=true",
s"--upgrade-module-path=${file("engine/runtime/build-cache/truffle-api.jar").absolutePath}"
),
libraryDependencies ++= Seq(
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
"org.scalatest" %% "scalatest" % scalatestVersion % Test
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided"
)
)
.dependsOn(syntax)
.dependsOn(`syntax-rust-definition`)
.dependsOn(`persistance`)
.dependsOn(`persistance-dsl` % "provided")
lazy val `runtime-compiler` =
(project in file("engine/runtime-compiler"))
@ -1483,16 +1525,18 @@ lazy val `runtime-compiler` =
frgaalJavaCompilerSetting,
instrumentationSettings,
libraryDependencies ++= Seq(
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
"com.lihaoyi" %% "fansi" % fansiVersion
"junit" % "junit" % junitVersion % Test,
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
"org.scalatest" %% "scalatest" % scalatestVersion % Test,
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided",
"com.lihaoyi" %% "fansi" % fansiVersion
)
)
.dependsOn(`runtime-parser`)
.dependsOn(pkg)
.dependsOn(`polyglot-api`)
.dependsOn(editions)
.dependsOn(`persistance-dsl` % "provided")
lazy val `runtime-instrument-common` =
(project in file("engine/runtime-instrument-common"))

View File

@ -13,6 +13,7 @@ import project.Extensions.Prefix_Name.Prefix_Name
import project.Internal.Fan_Out
import project.Internal.Java_Exports
import project.Internal.Java_Problems
import project.Internal.Widget_Helpers
from project.Internal.Java_Exports import make_inferred_builder
## PRIVATE

View File

@ -12,84 +12,82 @@ One of the largest pain points for users of Enso at the moment is the fact that
it has to precompile the entire standard library on every project load. This is,
in essence, due to the fact that the current parser is abysmally slow, and
incredibly demanding. The obvious solution to improve this is to take the parser
out of the equation in its entirety, by serialising the parser's output.
out of the equation in its entirety, by serializing the parser's output.
To that end, we want to serialise the Enso IR to a format that can later be read
To that end, we want to serialize the Enso IR to a format that can later be read
back in, bypassing the parser entirely. Furthermore, we can move the boundary at
which this serialisation takes place to the end of the compiler pipeline,
which this serialization takes place to the end of the compiler pipeline,
thereby bypassing doing most of the compilation work, and further improving
startup performance.
<!-- MarkdownTOC levels="2,3" autolink="true" -->
- [Serialising the IR](#serialising-the-ir)
- [Serializing the IR](#serializing-the-ir)
- [Breaking Links](#breaking-links)
- [Storing the IR](#storing-the-ir)
- [Metadata Format](#metadata-format)
- [Portability Guarantees](#portability-guarantees)
- [Portability and Versioning](#portability-and-versioning)
- [Loading the IR](#loading-the-ir)
- [Integrity Checking](#integrity-checking)
- [Error Handling](#error-handling)
- [Imports](#imports)
- [Testing the Serialisation](#testing-the-serialisation)
- [Testing the Serialization](#testing-the-serialization)
- [Future Directions](#future-directions)
<!-- /MarkdownTOC -->
## Serialising the IR
## Serializing the IR
As the serialised IR doesn't need to be read by anything other than Enso, we
need not use a representation that is portable between platforms. As a result,
we have picked the `Serializable` infrastructure that is _already present_ on
the JVM. It has the following benefits:
Using classical Java Serialization turned out to be unsuitably slow. Rather than
switching to other serialization framework that does the same, but faster we
desided in [PR-8207](https://github.com/enso-org/enso/pull/8207) to create _own
persistance framework_ that radically changes the way we can read the caches.
Rather than loading all the megabytes of stored data, it reads them _lazily on
demand_.
- It is able to serialise arbitrary object graphs while maintaining object
identity and tracking references. This cannot be disabled for `Serializable`,
but that is fine as we want it.
- It is built into the JVM and is hence guaranteed to be portable between
instances of the same JVM.
- It copes fine with highly-nested scala types, like our IR.
Use following command to generate the Javadoc for the `org.enso.persist`
package:
In order to maximise the benefits of this process, we want to serialise the IR
as _late_ in the compiler pipeline as possible. This means serialising it just
```bash
enso$ find lib/java/persistance/src/main/java/ | grep java$ | xargs ~/bin/graalvm-21/bin/javadoc -d target/javadoc/ --snippet-path lib/java/persistance/src/test/java/
enso$ links target/javadoc/index.html
```
In order to maximize the benefits of this process, we want to serialize the IR
as _late_ in the compiler pipeline as possible. This means serializing it just
before the code generation step that generates Truffle nodes (before the
`RuntimeStubsGenerator` and `IrToTruffle` run).
This serialisation should take place in an _offloaded thread_ so that it doesn't
This serialization should take place in an _offloaded thread_ so that it doesn't
block the compiler from continuing.
### Breaking Links
Doing this naïvely, however, means that we can inadvertently end up serialising
Doing this naïvely, however, means that we can inadvertently end up serializing
the entire module graph. This is due to the `BindingsMap`, which contains a
reference to the associated `runtime.Module`, from which there is a reference to
the `ModuleScope`. The `ModuleScope` may then reference other `runtime.Module`s
which all contain `IR.Module`s. Therefore, done in a silly fashion, we end up
serialising the entire reachable module graph. This is not what we want.
serializing the entire reachable module graph. This is not what we want.
While the ideal way of solving this problem would be to customise the
serialisation and deserialisation process for the `BindingsMap`, the JVM's
`Serializable` does not provide the ability to customise it enough to solve this
problem. Instead, we solve it using a preprocessing step:
The `Persistance.write` method contains additional `writeReplace` function which
our cache system uses to perform following modification just before
`ProcessingPass.Metadata` are stored down:
- We can modify `BindingsMap` and its child types to be able to contain an
unlinked module pointer
`case class ModulePointer(qualifiedName: List[String])` in place of a
`Module`.
- As the `MetadataStorage` type that holds the `BindingsMap` is mutable it can
be updated in place without having to reassemble the entire IR graph.
- Hence, we can traverse all the nodes in the `ir.preorder` that have metadata
consisting of either the `BindingsMap` or `ResolvedName` types (provided by
the following passes: `BindingAnalysis`, `MethodDefinitions`, `GlobalNames`,
`VectorLiterals`, `Patterns`), and perform a replacement.
- modify `BindingsMap` and its child types to be able to contain an unlinked
module pointer `case class ModulePointer(qualifiedName: List[String])` in
place of a `Module`.
- As the `MetadataStorage` type that holds the `BindingsMap` is mutable it might
be tempting to update it in place, but relying on `writeReplace` mechanism is
safer as it only changes the format of object being written down, rather than
modifying objects of live `IR` - potentially shared with other parts of the
system.
Having done this, we have broken any links that the IR may hold between modules,
and can serialise each module individually.
and can serialize each module individually.
This serialisation must take place _after_ codegen has happened as it modifies
the IR in place. The compiler can handle giving it to the offloaded
serialisation thread. It _may_ be necessary to `duplicate` the IR before handing
it to this thread, but this should be checked during development.
It _may_ be safer to `duplicate` the IR before handing it to serialization, but
it shouldn't be necessary if the `writeReplace` function is written correctly.
## Storing the IR
@ -146,12 +144,28 @@ All hashes are encoded in SHA1 format, for performance reasons. The engine
version is encoded in the cache path, and hence does not need to be explicitly
specified in the metadata.
### Portability Guarantees
### Portability and Versioning
As part of this design we provide only the following portability guarantees:
These are two static methods in `Persistance` class to help creating a `byte[]`
from a single object and then read it back. The array is identified with
following header:
- The serialised IR must be able to be deserialised by _the same version of
Enso_ that wrote the original blob.
- 4 bytes fixed header
- 4 bytes describing the version
- 4 bytes to locate the beginning of the object (the objects aren't written
linearly)
E.g. 12 bytes overhead before the actual data start. Following versioning is
recommended when making a change:
- when you change something really core in the `Persitance` implementation -
change the builtin header first four bytes
- when you add or remove a Persistance implementation the version changes (as it
is computed from all the IDs present in the system)
- when you change format of some `Persitance.writeObject` method - change its ID
That way the same version of Enso will recognize its `.ir` files. Different
versions of Enso will realize that the files aren't in suitable form.
## Loading the IR
@ -165,10 +179,12 @@ checking on the loaded cache. It works as follows.
2. **Check Integrity:** Check the module's [metadata](#metadata-format) for
validity according to the [integrity rules](#integrity-checking).
3. **Load:** If the cache passes the integrity check, load the `.ir` file. If
deserialisation fails in any way, immediately fall back to parsing the source
deserialization fails in any way, immediately fall back to parsing the source
file.
4. **Re-Link:** If loading completed successfully, re-link the `BindingsMap`
metadata to the proper modules in question.
4. **Re-Link:** Relinking is part of **Load**. When using `Persistance.read`
provide own `readResolve` function. Such a function gets a chance to change
and replace each object read-in with appropriate variant respecting the whole
compiler environment.
The main subtlety here is handling the dependencies between modules. We need to
ensure that, when loading multiple cached libraries, we properly handle them
@ -177,8 +193,8 @@ setting `AFTER_STATIC_PASSES` as the compilation state after loading the module.
This will tie into the current `ImportsResolver` and `ExportsResolver` which are
run in an un-gated fashion in `Compiler::run`.
In order to prevent the execution of malicious code when deserialising we should
employ a deserialisation filter as built into the JDK.
Unlike classical Java deserialization nly registered `Persistance` subclasses
may participate in deserialization making it much safer and less vulnerable.
### Integrity Checking
@ -195,8 +211,8 @@ ignored if it is in a read-only location.
It is important, as part of this, that we fail under all circumstances into a
working state. This means that:
- If serialisation fails, we report a low-priority error message and continue.
- If deserialisation fails, we fall back to loading and parsing the original
- If serialization fails, we report a low-priority error message and continue.
- If deserialization fails, we fall back to loading and parsing the original
source file.
At no point should this mechanism be exposed to the user in any visible way,
@ -215,12 +231,15 @@ until a complete cache invalidation was forced.
Therefore, the compiler performs an additional check by invalidating module's
cache if any of its imported modules have been invalidated.
## Testing the Serialisation
## Testing the Serialization
There are two main elements that need to be tested as part of this feature.
- Firstly, we need to test the serialisation and deserialisation process,
including the rewrite of `BindingsMap` to work properly.
- `persistance` project comes with its own unit tests
- `runtime-parser` project adds tests of various core classes used during `IR`
serialization - like Scala `List` or checks of the _laziness_ of Scala `Seq`
- We need to test the serialization and deserialization process, including the
rewrite of `BindingsMap` to work properly.
- We also need to test the discovery of cache locations on the filesystem and
cache eviction strategies. The best way to do this is to set `$ENSO_DATA` to a
temporary directory and then directly interact with the filesystem. Caching
@ -243,16 +262,23 @@ library in a single pass.
The bindings are serialized along with the library caches in a file with a
`.bindings` suffix.
Further more the storage of `.ir` files contains usage of _lazy_ `Seq`
references to separate the general part of the `IR` tree from elements
representing method bodies. As such the compiler can process the structure of
`.ir` files, but avoid loading in `IR` for methods that aren't being executed.
## Future Directions
Due to the less than ideal platform situation we're in, we're limited to using
Java's `Serializable`. It is not as performant as other options.
The `Persistance` framework gives us _laziness_ opportunities and we should use
them more:
- [FST](https://github.com/RuedigerMoeller/fast-serialization) is around 10x
faster than the JVM's serialization, and is a drop-in replacement.
- However, the version that supports Java 11 utilises reflection that trips
warnings that will be disallowed with Java 17 (the next LTS version for
GraalVM).
- The version that fixes this relies on the foreign memory API which is
available in Java 17. I recommend that once we're on Java 17 builds the
serialization is updated to work using FST.
- have a single _blob_ with all `IR`s per a library and read only the parts that
are needed
- experiement with GC - being able to release parts of unused `IR` once they
were used (for code generation or co.)
- make the `.ir` files smaller where possible
The use of `Persistance` has already sped up the execution time of simple
`IO.println "Hello!"` by 16% - let's use it to speed things up even more.

View File

@ -572,7 +572,8 @@ object Main {
topScope.compile(shouldCompileDependencies)
exitSuccess()
} catch {
case _: Throwable =>
case t: Throwable =>
logger.error("Unexpected internal error", t)
exitFail()
} finally {
context.context.close()

View File

@ -81,8 +81,6 @@ public interface CompilerContext extends CompilerStub {
boolean wasLoadedFromCache(Module module);
boolean hasCrossModuleLinks(Module module);
org.enso.compiler.core.ir.Module getIr(Module module);
CompilationStage getCompilationStage(Module module);
@ -108,8 +106,6 @@ public interface CompilerContext extends CompilerStub {
void loadedFromCache(boolean b);
void hasCrossModuleLinks(boolean b);
void resetScope();
void invalidateCache();
@ -134,8 +130,6 @@ public interface CompilerContext extends CompilerStub {
public abstract boolean isSynthetic();
public abstract boolean hasCrossModuleLinks();
public abstract org.enso.compiler.core.ir.Module getIr();
public abstract boolean isPrivate();

View File

@ -0,0 +1,155 @@
package org.enso.compiler.pass.analyse;
import java.io.IOException;
import org.enso.compiler.pass.analyse.AliasAnalysis.Graph;
import org.enso.compiler.pass.resolve.DocumentationComments;
import org.enso.compiler.pass.resolve.FullyQualifiedNames;
import org.enso.compiler.pass.resolve.IgnoredBindings;
import org.enso.compiler.pass.resolve.ModuleAnnotations;
import org.enso.compiler.pass.resolve.TypeSignatures;
import org.enso.persist.Persistable;
import org.enso.persist.Persistance;
import org.openide.util.lookup.ServiceProvider;
import scala.Option;
@Persistable(clazz = CachePreferenceAnalysis.WeightInfo.class, id = 1111)
@Persistable(clazz = DataflowAnalysis.DependencyInfo.class, id = 1112)
@Persistable(clazz = DataflowAnalysis.DependencyMapping.class, id = 1113)
@Persistable(clazz = GatherDiagnostics.DiagnosticsMeta.class, id = 1114)
@Persistable(clazz = DocumentationComments.Doc.class, id = 1115)
@Persistable(clazz = AliasAnalysis$Info$Occurrence.class, id = 1116)
@Persistable(clazz = TypeSignatures.Signature.class, id = 1117)
@Persistable(clazz = ModuleAnnotations.Annotations.class, id = 1118)
@Persistable(clazz = AliasAnalysis$Info$Scope$Root.class, id = 1120)
@Persistable(clazz = DataflowAnalysis$DependencyInfo$Type$Static.class, id = 1121)
@Persistable(clazz = DataflowAnalysis$DependencyInfo$Type$Dynamic.class, id = 1122)
@Persistable(clazz = AliasAnalysis$Info$Scope$Child.class, id = 1123)
@Persistable(clazz = AliasAnalysis$Graph$Occurrence$Use.class, id = 1125)
@Persistable(clazz = AliasAnalysis$Graph$Occurrence$Def.class, id = 1126)
@Persistable(clazz = AliasAnalysis$Graph$Link.class, id = 1127)
@Persistable(clazz = FullyQualifiedNames.FQNResolution.class, id = 1128)
@Persistable(clazz = FullyQualifiedNames.ResolvedLibrary.class, id = 1129)
@Persistable(clazz = FullyQualifiedNames.ResolvedModule.class, id = 1130)
public final class PassPersistance {
private PassPersistance() {}
@ServiceProvider(service = Persistance.class)
public static final class PersistState extends Persistance<IgnoredBindings.State> {
public PersistState() {
super(IgnoredBindings.State.class, true, 1101);
}
@Override
protected void writeObject(IgnoredBindings.State obj, Output out) throws IOException {
out.writeBoolean(obj.isIgnored());
}
@Override
protected IgnoredBindings.State readObject(Input in)
throws IOException, ClassNotFoundException {
var b = in.readBoolean();
return b
? org.enso.compiler.pass.resolve.IgnoredBindings$State$Ignored$.MODULE$
: org.enso.compiler.pass.resolve.IgnoredBindings$State$NotIgnored$.MODULE$;
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistTail extends Persistance<TailCall.TailPosition> {
public PersistTail() {
super(TailCall.TailPosition.class, true, 1102);
}
@Override
protected void writeObject(TailCall.TailPosition obj, Output out) throws IOException {
out.writeBoolean(obj.isTail());
}
@Override
protected TailCall.TailPosition readObject(Input in)
throws IOException, ClassNotFoundException {
var b = in.readBoolean();
return b
? org.enso.compiler.pass.analyse.TailCall$TailPosition$Tail$.MODULE$
: org.enso.compiler.pass.analyse.TailCall$TailPosition$NotTail$.MODULE$;
}
}
@org.openide.util.lookup.ServiceProvider(service = Persistance.class)
public static final class PersistAliasAnalysisGraphScope
extends Persistance<org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope> {
public PersistAliasAnalysisGraphScope() {
super(org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope.class, false, 1124);
}
@Override
@SuppressWarnings("unchecked")
protected org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope readObject(Input in)
throws IOException {
var childScopes = in.readInline(scala.collection.immutable.List.class);
var occurrences = (scala.collection.immutable.Set) in.readObject();
var allDefinitions = in.readInline(scala.collection.immutable.List.class);
var parent =
new org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope(
childScopes, occurrences, allDefinitions);
var optionParent = Option.apply(parent);
childScopes.forall(
(object) -> {
var ch = (org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope) object;
ch.parent_$eq(optionParent);
return null;
});
return parent;
}
@Override
@SuppressWarnings("unchecked")
protected void writeObject(
org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope obj, Output out)
throws IOException {
out.writeInline(scala.collection.immutable.List.class, obj.childScopes());
out.writeObject(obj.occurrences());
out.writeInline(scala.collection.immutable.List.class, obj.allDefinitions());
}
}
@org.openide.util.lookup.ServiceProvider(service = Persistance.class)
public static final class PersistAliasAnalysisGraph extends Persistance<Graph> {
public PersistAliasAnalysisGraph() {
super(Graph.class, false, 1119);
}
@SuppressWarnings("unchecked")
protected Graph readObject(Input in) throws IOException {
var g = new Graph();
var rootScope = (AliasAnalysis$Graph$Scope) in.readObject();
assignParents(rootScope);
g.rootScope_$eq(rootScope);
var links =
(scala.collection.immutable.Set) in.readInline(scala.collection.immutable.Set.class);
g.links_$eq(links);
return g;
}
@SuppressWarnings("unchecked")
@Override
protected void writeObject(Graph obj, Output out) throws IOException {
out.writeObject(obj.rootScope());
out.writeInline(scala.collection.immutable.Set.class, obj.links());
}
private static void assignParents(AliasAnalysis$Graph$Scope scope) {
var option = Option.apply(scope);
scope
.childScopes()
.foreach(
(ch) -> {
assignParents(ch);
ch.parent_$eq(option);
return null;
});
}
}
}

View File

@ -257,7 +257,7 @@ class Compiler(
}
)
var requiredModules = modules.flatMap { module =>
val requiredModules = modules.flatMap { module =>
val importedModules = runImportsAndExportsResolution(module, generateCode)
val isLoadedFromSource =
(m: Module) => !context.wasLoadedFromCache(m) && !context.isSynthetic(m)
@ -286,47 +286,11 @@ class Compiler(
}
}.distinct
var hasInvalidModuleRelink = false
if (irCachingEnabled) {
requiredModules.foreach { module =>
ensureParsed(module)
if (!context.hasCrossModuleLinks(module)) {
val flags =
context
.getIr(module)
.preorder
.map(_.passData.restoreFromSerialization(this.context))
if (!flags.contains(false)) {
context.log(
Compiler.defaultLogLevel,
"Restored links (late phase) for module [{0}].",
context.getModuleName(module)
)
} else {
hasInvalidModuleRelink = true
context.log(
Compiler.defaultLogLevel,
"Failed to restore links (late phase) for module [{0}].",
context.getModuleName(module)
)
uncachedParseModule(module, isGenDocs = false)
}
}
}
}
if (hasInvalidModuleRelink) {
context.log(
Compiler.defaultLogLevel,
s"Some modules failed to relink. Re-running import and " +
s"export resolution."
)
requiredModules =
modules.flatMap(runImportsAndExportsResolution(_, generateCode))
}
requiredModules.foreach { module =>
if (
!context
@ -629,7 +593,6 @@ class Compiler(
u.ir(discoveredModule)
u.compilationStage(CompilationStage.AFTER_PARSING)
u.loadedFromCache(false)
u.hasCrossModuleLinks(true)
}
)
}
@ -892,7 +855,9 @@ class Compiler(
): Unit = {
if (config.isStrictErrors) {
val diagnostics = modules.flatMap { module =>
val errors = gatherDiagnostics(module)
val errors =
if (context.wasLoadedFromCache(module)) List()
else gatherDiagnostics(module)
List((module, errors))
}
if (reportDiagnostics(diagnostics)) {

View File

@ -84,7 +84,10 @@ class LocalScope(
* @return the frame slot index for `id`.
*/
def getVarSlotIdx(id: Graph.Id): Int = {
assert(localFrameSlotIdxs.contains(id))
assert(
localFrameSlotIdxs.contains(id),
"Cannot find " + id + " in " + localFrameSlotIdxs
)
localFrameSlotIdxs(id)
}

View File

@ -11,6 +11,7 @@ import org.enso.compiler.core.ir.{
Function,
IdentifiedLocation,
Literal,
Location,
Name,
Type
}
@ -28,7 +29,6 @@ import org.enso.compiler.pass.resolve.{
import org.enso.pkg.QualifiedName
import org.enso.polyglot.Suggestion
import org.enso.polyglot.data.{Tree, TypeGraph}
import org.enso.syntax.text.Location
import org.enso.text.editing.IndexedSource
import java.util.UUID

View File

@ -8,6 +8,7 @@ import org.enso.compiler.core.ir.{
Empty,
Expression,
Function,
IdentifiedLocation,
Literal,
Module,
Name,
@ -185,9 +186,10 @@ case object DemandAnalysis extends IRPass {
} else {
name match {
case lit: Name.Literal if isDefined(lit) =>
val forceLocation = name.location
val newNameLocation = name.location.map(l => l.copy(id = None))
val newName = lit.copy(location = newNameLocation)
val forceLocation = name.location
val newNameLocation =
name.location.map(l => new IdentifiedLocation(l.location()))
val newName = lit.copy(location = newNameLocation)
Application.Force(newName, forceLocation)
case _ => name
}

View File

@ -111,7 +111,7 @@ case object FunctionBinding extends IRPass {
val lambda = args
.map(_.mapExpressions(desugarExpression))
.foldRight(desugarExpression(body))((arg, body) =>
Function.Lambda(List(arg), body, None)
new Function.Lambda(List(arg), body, None)
)
.asInstanceOf[Function.Lambda]
.copy(canBeTCO = canBeTCO, location = location)
@ -155,10 +155,10 @@ case object FunctionBinding extends IRPass {
val newBody = args
.map(_.mapExpressions(desugarExpression))
.foldRight(desugarExpression(body))((arg, body) =>
Function.Lambda(List(arg), body, None)
new Function.Lambda(List(arg), body, None)
)
definition.Method.Explicit(
new definition.Method.Explicit(
methRef,
newBody,
loc,
@ -245,7 +245,7 @@ case object FunctionBinding extends IRPass {
val newBody = (requiredArgs ::: remainingArgs)
.map(_.mapExpressions(desugarExpression))
.foldRight(desugarExpression(body))((arg, body) =>
Function.Lambda(List(arg), body, None)
new Function.Lambda(List(arg), body, None)
)
Right(
definition.Method.Conversion(

View File

@ -244,7 +244,7 @@ case object GenerateMethodBodies extends IRPass {
expr: Expression,
funName: Name
): Expression = {
Function.Lambda(
new Function.Lambda(
arguments =
if (funName.name == MAIN_FUNCTION_NAME) Nil
else genSyntheticSelf() :: Nil,

View File

@ -7,6 +7,7 @@ import org.enso.compiler.core.ir.{
DefinitionArgument,
Expression,
Function,
IdentifiedLocation,
Module,
Name,
Type
@ -142,7 +143,7 @@ case object LambdaShorthandToLambda extends IRPass {
case blank: Name.Blank =>
val newName = supply.newName()
Function.Lambda(
new Function.Lambda(
List(
DefinitionArgument.Specified(
name = Name.Literal(
@ -221,12 +222,12 @@ case object LambdaShorthandToLambda extends IRPass {
// arg
val appResult =
actualDefArgs.foldRight(processedApp: Expression)((arg, body) =>
Function.Lambda(List(arg), body, None)
new Function.Lambda(List(arg), body, None)
)
// If the function is shorthand, do the same
val resultExpr = if (functionIsShorthand) {
Function.Lambda(
new Function.Lambda(
List(
DefinitionArgument.Specified(
Name
@ -267,8 +268,9 @@ case object LambdaShorthandToLambda extends IRPass {
name
case it => desugarExpression(it, freshNameSupply)
}
val newVec = vector.copy(newItems)
val locWithoutId = newVec.location.map(_.copy(id = None))
val newVec = vector.copy(newItems)
val locWithoutId =
newVec.location.map(l => new IdentifiedLocation(l.location()))
bindings.foldLeft(newVec: Expression) { (body, bindingName) =>
val defArg = DefinitionArgument.Specified(
bindingName,
@ -277,7 +279,7 @@ case object LambdaShorthandToLambda extends IRPass {
suspended = false,
location = None
)
Function.Lambda(List(defArg), body, locWithoutId)
new Function.Lambda(List(defArg), body, locWithoutId)
}
case tSet @ Application.Typeset(expr, _, _, _) =>
tSet.copy(expression = expr.map(desugarExpression(_, freshNameSupply)))
@ -422,7 +424,7 @@ case object LambdaShorthandToLambda extends IRPass {
branches = newBranches
)
Function.Lambda(
new Function.Lambda(
List(lambdaArg),
newCaseExpr,
caseExpr.location,

View File

@ -132,13 +132,13 @@ case object SectionsToBinOp extends IRPass {
diagnostics
)
val rightLam = Function.Lambda(
val rightLam = new Function.Lambda(
List(rightDefArg),
opCall,
None
)
Function.Lambda(
new Function.Lambda(
List(leftDefArg),
rightLam,
loc
@ -187,13 +187,13 @@ case object SectionsToBinOp extends IRPass {
diagnostics
)
val rightLambda = Function.Lambda(
val rightLambda = new Function.Lambda(
List(rightDefArg),
opCall,
None
)
Function.Lambda(
new Function.Lambda(
List(leftDefArg),
rightLambda,
loc
@ -253,13 +253,13 @@ case object SectionsToBinOp extends IRPass {
diagnostics
)
val leftLam = Function.Lambda(
val leftLam = new Function.Lambda(
List(leftDefArg),
opCall,
None
)
Function.Lambda(
new Function.Lambda(
List(rightDefArg),
leftLam,
loc
@ -274,7 +274,7 @@ case object SectionsToBinOp extends IRPass {
diagnostics
)
Function.Lambda(
new Function.Lambda(
List(leftDefArg),
opCall,
loc

View File

@ -9,6 +9,7 @@ import org.enso.compiler.core.ir.{
Expression,
Function,
IdentifiedLocation,
Location,
Module,
Name
}
@ -23,7 +24,6 @@ import org.enso.compiler.pass.analyse.{
}
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.resolve.IgnoredBindings
import org.enso.syntax.text.Location
import java.util.UUID
@ -171,8 +171,8 @@ case object LambdaConsolidate extends IRPass {
val newLocation = chainedLambdas.head.location match {
case Some(location) =>
Some(
IdentifiedLocation(
Location(
IdentifiedLocation.create(
new Location(
location.start,
chainedLambdas.last.location.getOrElse(location).location.end
),

View File

@ -5,6 +5,7 @@ import org.enso.compiler.core.Implicits.AsDiagnostics
import org.enso.compiler.core.ir.{
Expression,
IdentifiedLocation,
Location,
Module,
Pattern
}
@ -19,7 +20,6 @@ import org.enso.compiler.pass.analyse.{
}
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.resolve.{DocumentationComments, IgnoredBindings}
import org.enso.syntax.text.Location
import scala.annotation.unused
@ -146,8 +146,8 @@ case object UnreachableMatchBranches extends IRPass {
branch.location match {
case Some(branchLoc) =>
Some(
IdentifiedLocation(
Location(loc.start, branchLoc.end),
IdentifiedLocation.create(
new Location(loc.start, branchLoc.end),
loc.id
)
)

View File

@ -101,7 +101,7 @@ case object MethodDefinitions extends IRPass {
if canGenerateStaticWrappers(tp) =>
val dup = method.duplicate()
val static = dup.copy(body =
Function.Lambda(
new Function.Lambda(
List(
DefinitionArgument
.Specified(

View File

@ -128,9 +128,6 @@ case object ModuleAnnotations extends IRPass {
/** @inheritdoc */
override def prepareForSerialization(compiler: Compiler): Annotations = {
annotations.foreach(ir =>
ir.preorder.foreach(_.passData.prepareForSerialization(compiler))
)
this
}
@ -138,14 +135,6 @@ case object ModuleAnnotations extends IRPass {
override def restoreFromSerialization(
compiler: Compiler
): Option[IRPass.IRMetadata] = {
annotations.foreach { ann =>
ann.preorder.foreach { ir =>
if (!ir.passData.restoreFromSerialization(compiler)) {
return None
}
}
}
Some(this)
}
}

View File

@ -58,7 +58,7 @@ class ImportResolver(compiler: Compiler) {
.getCompilationStage(current)
.isBefore(
CompilationStage.AFTER_IMPORT_RESOLUTION
) || !context.hasCrossModuleLinks(current)
)
) {
val importedModules: List[
(Import, Option[BindingsMap.ResolvedImport])

View File

@ -12,11 +12,11 @@ import org.enso.compiler.core.{
}
import org.enso.compiler.core.Implicits.AsMetadata
import org.enso.compiler.core.ir.Literal
import org.enso.compiler.core.ir.Location
import org.enso.compiler.core.ir.Name
import org.enso.compiler.core.ir.module.scope.definition
import org.enso.compiler.pass.analyse.DataflowAnalysis
import org.enso.interpreter.instrument.execution.model.PendingEdit
import org.enso.syntax.text.Location
import org.enso.text.editing.model.TextEdit
import org.enso.text.editing.{IndexedSource, TextEditor}
@ -288,7 +288,7 @@ object ChangesetBuilder {
* @return the node with a new location
*/
def shift(offset: Int): Node = {
val newLocation = location.copy(
val newLocation = new Location(
start = location.start + offset,
end = location.end + offset
)
@ -359,7 +359,7 @@ object ChangesetBuilder {
val nodeBetweenPreviousPositionAndNextNode =
Node(
NodeId(currentIr),
Location(previousPosition, nextNode.location.start),
new Location(previousPosition, nextNode.location.start),
false
)
acc += nodeBetweenPreviousPositionAndNextNode
@ -378,7 +378,7 @@ object ChangesetBuilder {
if (hasRemainingTextAfterLastChild) {
val nodeAfterLastChild = Node(
NodeId(currentIr),
Location(lastCoveredPosition, endOfNonLeafIr),
new Location(lastCoveredPosition, endOfNonLeafIr),
false
)
acc += nodeAfterLastChild
@ -491,7 +491,7 @@ object ChangesetBuilder {
edit: TextEdit,
source: A
): Location = {
Location(
new Location(
IndexedSource[A].toIndex(edit.range.start, source),
IndexedSource[A].toIndex(edit.range.end, source)
)

View File

@ -2,9 +2,9 @@ package org.enso.interpreter.instrument.execution
import com.oracle.truffle.api.source.SourceSection
import org.enso.compiler.core.{ExternalID, IR, Identifier}
import org.enso.compiler.core.ir.Location
import org.enso.compiler.core.ir.IdentifiedLocation
import org.enso.interpreter.runtime.Module
import org.enso.syntax.text.Location
import org.enso.text.editing.{model, IndexedSource}
import java.util.UUID
@ -121,7 +121,7 @@ object LocationResolver {
source: A
): Location = {
val range = sectionToRange(section)
Location(
new Location(
IndexedSource[A].toIndex(range.start, source),
IndexedSource[A].toIndex(range.end, source)
)

View File

@ -12,6 +12,7 @@ import org.enso.compiler.core.ir.Empty;
import org.enso.compiler.core.ir.Expression;
import org.enso.compiler.core.ir.Function;
import org.enso.compiler.core.ir.Literal;
import org.enso.compiler.core.ir.Location;
import org.enso.compiler.core.ir.Name;
import org.enso.compiler.core.ir.MetadataStorage;
import org.enso.compiler.core.ir.Module;
@ -29,7 +30,6 @@ import org.enso.compiler.core.ir.module.scope.definition.Method;
import org.enso.compiler.core.ir.module.scope.Export;
import org.enso.compiler.core.ir.module.scope.Import;
import org.enso.compiler.core.ir.module.scope.imports.Polyglot;
import org.enso.syntax.text.Location;
import org.enso.syntax2.ArgumentDefinition;
import org.enso.syntax2.Base;
import org.enso.syntax2.DocComment;
@ -41,6 +41,7 @@ import org.enso.syntax2.Tree;
import org.enso.syntax2.Tree.Invalid;
import org.enso.syntax2.Tree.Private;
import scala.Option;
import scala.collection.immutable.LinearSeq;
import scala.collection.immutable.List;
@ -121,7 +122,7 @@ final class TreeToIr {
locations.get(1).start(),
locations.get(locations.size() - 1).end()
),
Option.empty()
null
)
);
}
@ -842,7 +843,7 @@ final class TreeToIr {
var locationWithANewLine = getIdentifiedLocation(body, 0, 0, null);
if (last != null && last.location().isDefined() && last.location().get().end() != locationWithANewLine.get().end()) {
var patched = new Location(last.location().get().start(), locationWithANewLine.get().end() - 1);
var id = new IdentifiedLocation(patched, last.location().get().id());
var id = IdentifiedLocation.create(patched, last.location().get().id());
last = last.setLocation(Option.apply(id));
}
yield new Expression.Block(list, last, locationWithANewLine, false, meta(), diag());
@ -1693,7 +1694,7 @@ final class TreeToIr {
Math.min(en.start(), in.start()),
Math.max(en.end(), in.end())
);
return Option.apply(new IdentifiedLocation(loc, en.id()));
return Option.apply(IdentifiedLocation.create(loc, en.id()));
} else {
return encapsulating;
}
@ -1716,7 +1717,7 @@ final class TreeToIr {
default -> {
var begin = castToInt(ast.getStartCode()) + b;
var end = castToInt(ast.getEndCode()) + e;
yield new IdentifiedLocation(new Location(begin, end), someId);
yield IdentifiedLocation.create(new Location(begin, end), someId);
}
});
}
@ -1751,7 +1752,7 @@ final class TreeToIr {
end = ast.getPattern().getEndCode();
}
int end_ = castToInt(end);
return Option.apply(new IdentifiedLocation(new Location(begin_, end_), Option.empty()));
return Option.apply(IdentifiedLocation.create(new Location(begin_, end_), Option.empty()));
}
private Option<IdentifiedLocation> getIdentifiedLocation(Token ast) {
@ -1763,7 +1764,7 @@ final class TreeToIr {
default -> {
int begin = castToInt(ast.getStartCode());
int end = castToInt(ast.getEndCode());
var id = Option.apply(generateId ? UUID.randomUUID() : null);
var id = generateId ? UUID.randomUUID() : null;
yield new IdentifiedLocation(new Location(begin, end), id);
}
});

View File

@ -0,0 +1,45 @@
package org.enso.compiler.core.ir;
import java.util.UUID;
import scala.Option;
public record IdentifiedLocation(Location location, UUID uuid) {
public IdentifiedLocation(Location location) {
this(location, (UUID)null);
}
/**
* Creates new location from an optional UUID.
*/
public static IdentifiedLocation create(Location location, Option<UUID> uuid) {
return new IdentifiedLocation(location, uuid.isEmpty() ? null : uuid.get());
}
/** @return the character index of the start of this source location.
*/
public int start() {
return location().start();
}
/** @return the character index of the end of this source location.
*/
public int end() {
return location().end();
}
/** @return the length in characters of this location.
*/
public int length() {
return location().length();
}
/** @return option with/out UUID */
public Option<UUID> id() {
return Option.apply(uuid());
}
@Override
public String toString() {
return "IdentifiedLocation[location=" + this.location() + ", uuid="+ id() + "]";
}
}

View File

@ -0,0 +1,69 @@
package org.enso.compiler.core.ir;
import java.util.NoSuchElementException;
import org.enso.persist.Persistance;
import scala.collection.Iterator;
import scala.collection.SeqFactory;
import scala.collection.immutable.AbstractSeq;
final class IrLazySeq extends AbstractSeq {
private final Persistance.Reference<?>[] arr;
private final int size;
IrLazySeq(Persistance.Reference<?>[] arr, int size) {
this.arr = arr;
this.size = size;
}
@Override
public Object apply(int i) throws IndexOutOfBoundsException {
return arr[i].get(Object.class);
}
@Override
public int length() {
return size;
}
@Override
public boolean isDefinedAt(int idx) {
return 0 <= idx && idx < size;
}
@Override
public boolean isDefinedAt(Object idx) {
throw new IllegalStateException();
}
@Override
public Object apply(Object i) throws IndexOutOfBoundsException {
throw new IllegalStateException();
}
@Override
public SeqFactory iterableFactory() {
return super.iterableFactory();
}
@Override
public Iterator iterator() {
return new IrIter();
}
private final class IrIter implements Iterator {
private int at;
@Override
public boolean hasNext() {
return at < size;
}
@Override
public Object next() throws NoSuchElementException {
if (at >= size) {
throw new NoSuchElementException();
}
return apply(at++);
}
}
}

View File

@ -0,0 +1,439 @@
package org.enso.compiler.core.ir;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.enso.compiler.core.ir.expression.Application;
import org.enso.compiler.core.ir.expression.Case;
import org.enso.compiler.core.ir.expression.Foreign;
import org.enso.compiler.core.ir.expression.warnings.Unused;
import org.enso.compiler.core.ir.module.scope.Definition;
import org.enso.compiler.core.ir.module.scope.Export;
import org.enso.compiler.core.ir.module.scope.Import;
import org.enso.compiler.core.ir.module.scope.definition.Method;
import org.enso.compiler.core.ir.module.scope.imports.Polyglot;
import org.enso.compiler.core.ir.type.Set;
import org.enso.persist.Persistable;
import org.enso.persist.Persistance;
import org.openide.util.lookup.ServiceProvider;
import scala.Option;
import scala.Tuple2;
import scala.collection.immutable.List;
import scala.collection.immutable.Seq;
@Persistable(clazz = Module.class, id = 201)
@Persistable(clazz = Name.Literal.class, id = 351)
@Persistable(clazz = Import.Module.class, id = 342)
@Persistable(clazz = Polyglot.class, id = 343)
@Persistable(clazz = Export.Module.class, id = 344)
@Persistable(clazz = Name.Qualified.class, id = 352)
@Persistable(clazz = Method.Explicit.class, id = 361)
@Persistable(clazz = Name.MethodReference.class, id = 362)
@Persistable(clazz = Function.Lambda.class, id = 363)
@Persistable(clazz = Polyglot.Java.class, id = 703)
@Persistable(clazz = DefinitionArgument.Specified.class, id = 704)
@Persistable(clazz = Name.Self.class, id = 705)
@Persistable(clazz = Literal.Number.class, id = 706)
@Persistable(clazz = Literal.Text.class, id = 707)
@Persistable(clazz = CallArgument.Specified.class, id = 708)
@Persistable(clazz = Definition.Type.class, id = 709)
@Persistable(clazz = Definition.Data.class, id = 710)
@Persistable(clazz = Name.Blank.class, id = 711)
@Persistable(clazz = Name.GenericAnnotation.class, id = 712)
@Persistable(clazz = Name.SelfType.class, id = 713)
@Persistable(clazz = Expression.Block.class, id = 751)
@Persistable(clazz = Expression.Binding.class, id = 752)
@Persistable(clazz = Application.Prefix.class, id = 753)
@Persistable(clazz = Application.Force.class, id = 754)
@Persistable(clazz = Application.Sequence.class, id = 755)
@Persistable(clazz = Case.Expr.class, id = 761)
@Persistable(clazz = Case.Branch.class, id = 762)
@Persistable(clazz = Pattern.Constructor.class, id = 763)
@Persistable(clazz = Pattern.Name.class, id = 764)
@Persistable(clazz = Pattern.Literal.class, id = 765)
@Persistable(clazz = Pattern.Type.class, id = 766)
@Persistable(clazz = Method.Conversion.class, id = 771)
@Persistable(clazz = Set.Union.class, id = 772)
@Persistable(clazz = Set.Intersection.class, id = 773)
@Persistable(clazz = Foreign.Definition.class, id = 781)
@Persistable(clazz = Type.Function.class, id = 782)
@Persistable(clazz = Name.BuiltinAnnotation.class, id = 783)
@Persistable(clazz = Type.Error.class, id = 784)
@Persistable(clazz = Unused.Binding.class, id = 785)
public final class IrPersistance {
private IrPersistance() {}
@ServiceProvider(service = Persistance.class)
public static final class PersistIdentifiedLocation extends Persistance<IdentifiedLocation> {
public PersistIdentifiedLocation() {
super(IdentifiedLocation.class, false, 2);
}
@Override
protected void writeObject(IdentifiedLocation obj, Output out) throws IOException {
out.writeInline(Location.class, obj.location());
out.writeInline(Option.class, obj.id());
}
@Override
@SuppressWarnings("unchecked")
protected IdentifiedLocation readObject(Input in) throws IOException, ClassNotFoundException {
var obj = in.readInline(Location.class);
var id = in.readInline(Option.class);
return IdentifiedLocation.create((Location) obj, id);
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistUUID extends Persistance<UUID> {
public PersistUUID() {
super(UUID.class, false, 73);
}
@Override
protected void writeObject(UUID obj, Output out) throws IOException {
out.writeLong(obj.getLeastSignificantBits());
out.writeLong(obj.getMostSignificantBits());
}
@Override
protected UUID readObject(Input in) throws IOException, ClassNotFoundException {
var least = in.readLong();
var most = in.readLong();
return new UUID(most, least);
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistScalaOption extends Persistance<Option> {
public PersistScalaOption() {
super(Option.class, true, 4431);
}
@Override
protected void writeObject(Option obj, Output out) throws IOException {
out.writeObject(obj.isEmpty() ? null : obj.get());
}
@Override
protected Option readObject(Input in) throws IOException, ClassNotFoundException {
var obj = in.readObject();
return Option.apply(obj);
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistString extends Persistance<String> {
public PersistString() {
super(String.class, true, 4437);
}
@Override
protected void writeObject(String obj, Output out) throws IOException {
out.writeUTF(obj);
}
@Override
protected String readObject(Input in) throws IOException, ClassNotFoundException {
var obj = in.readUTF();
return obj;
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistLong extends Persistance<Long> {
public PersistLong() {
super(Long.class, true, 4438);
}
@Override
protected void writeObject(Long obj, Output out) throws IOException {
out.writeLong(obj);
}
@Override
protected Long readObject(Input in) throws IOException, ClassNotFoundException {
var obj = in.readLong();
return obj;
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistDouble extends Persistance<Double> {
public PersistDouble() {
super(Double.class, true, 4439);
}
@Override
protected void writeObject(Double obj, Output out) throws IOException {
out.writeDouble(obj);
}
@Override
protected Double readObject(Input in) throws IOException, ClassNotFoundException {
var obj = in.readDouble();
return obj;
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistScalaList extends Persistance<List> {
public PersistScalaList() {
super(List.class, true, 4432);
}
@Override
protected void writeObject(List list, Output out) throws IOException {
var size = list.size();
out.writeInt(size);
var l = list.reverse();
for (var i = 0; i < size; i++) {
out.writeObject(l.head());
l = (List) l.tail();
}
}
@Override
@SuppressWarnings("unchecked")
protected List readObject(Input in) throws IOException, ClassNotFoundException {
var size = in.readInt();
List list = scala.collection.immutable.Nil$.MODULE$;
for (var i = 0; i < size; i++) {
var elem = in.readObject();
list = scala.collection.immutable.$colon$colon$.MODULE$.apply(elem, list);
}
return list;
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistScalaMap extends Persistance<scala.collection.immutable.Map> {
public PersistScalaMap() {
super(scala.collection.immutable.Map.class, true, 4444);
}
@Override
@SuppressWarnings("unchecked")
protected void writeObject(scala.collection.immutable.Map map, Output out) throws IOException {
var size = map.size();
out.writeInt(size);
var it = map.iterator();
while (size-- > 0) {
var tuple = (Tuple2) it.next();
out.writeObject(tuple._1());
out.writeObject(tuple._2());
}
}
@Override
@SuppressWarnings("unchecked")
protected scala.collection.immutable.Map readObject(Input in)
throws IOException, ClassNotFoundException {
var size = in.readInt();
var map = scala.collection.immutable.Map$.MODULE$.empty();
for (var i = 0; i < size; i++) {
var key = in.readObject();
var value = in.readObject();
map = map.$plus(new Tuple2(key, value));
}
return map;
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistScalaMutableMap
extends Persistance<scala.collection.mutable.Map> {
public PersistScalaMutableMap() {
super(scala.collection.mutable.Map.class, true, 4949);
}
@Override
@SuppressWarnings("unchecked")
protected void writeObject(scala.collection.mutable.Map map, Output out) throws IOException {
var size = map.size();
out.writeInt(size);
var it = map.iterator();
while (it.hasNext()) {
var tuple = (Tuple2) it.next();
out.writeObject(tuple._1());
out.writeObject(tuple._2());
}
}
@Override
@SuppressWarnings("unchecked")
protected scala.collection.mutable.Map readObject(Input in)
throws IOException, ClassNotFoundException {
var size = in.readInt();
var map = scala.collection.mutable.Map$.MODULE$.empty();
for (var i = 0; i < size; i++) {
var key = in.readObject();
var value = in.readObject();
map.put(key, value);
}
return map;
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistScalaSet extends Persistance<scala.collection.immutable.Set> {
public PersistScalaSet() {
super(scala.collection.immutable.Set.class, true, 4445);
}
@Override
@SuppressWarnings("unchecked")
protected void writeObject(scala.collection.immutable.Set set, Output out) throws IOException {
var size = set.size();
out.writeInt(size);
var it = set.iterator();
while (it.hasNext()) {
var obj = it.next();
out.writeObject(obj);
}
}
@Override
@SuppressWarnings("unchecked")
protected scala.collection.immutable.Set readObject(Input in)
throws IOException, ClassNotFoundException {
var size = in.readInt();
var map = scala.collection.immutable.Set$.MODULE$.empty();
for (var i = 0; i < size; i++) {
var elem = in.readObject();
map = map.$plus(elem);
}
return map;
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistMap extends Persistance<HashMap> {
public PersistMap() {
super(HashMap.class, true, 4440);
}
@Override
protected void writeObject(HashMap m, Output out) throws IOException {
var size = m.size();
out.writeInt(size);
var it = m.entrySet().iterator();
while (it.hasNext()) {
var entry = (Map.Entry) it.next();
out.writeObject(entry.getKey());
out.writeObject(entry.getValue());
}
}
@Override
@SuppressWarnings("unchecked")
protected HashMap readObject(Input in) throws IOException, ClassNotFoundException {
var size = in.readInt();
var map = new HashMap<Object, Object>();
for (var i = 0; i < size; i++) {
var key = in.readObject();
var value = in.readObject();
map.put(key, value);
}
return map;
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistScalaSeq extends Persistance<Seq> {
public PersistScalaSeq() {
super(Seq.class, true, 4433);
}
@Override
protected void writeObject(Seq list, Output out) throws IOException {
var size = list.size();
out.writeInt(size);
for (var i = 0; i < size; i++) {
out.writeObject(list.apply(i));
}
}
@Override
@SuppressWarnings("unchecked")
protected Seq readObject(Input in) throws IOException, ClassNotFoundException {
var size = in.readInt();
Reference<?>[] arr = new Reference<?>[size];
for (var i = 0; i < size; i++) {
arr[i] = in.readReference(Object.class);
}
return new IrLazySeq(arr, size);
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistMetadataStorage extends Persistance<MetadataStorage> {
public PersistMetadataStorage() {
super(MetadataStorage.class, false, 301);
}
@Override
@SuppressWarnings("unchecked")
protected void writeObject(MetadataStorage obj, Output out) throws IOException {
var map =
obj.map(
(processingPass, data) -> {
var t = new Tuple2<>(processingPass.getClass().getName(), data);
return t;
});
out.writeInline(scala.collection.immutable.Map.class, map);
}
@Override
@SuppressWarnings("unchecked")
protected MetadataStorage readObject(Input in) throws IOException, ClassNotFoundException {
var storage = new MetadataStorage(nil());
var map = in.readInline(scala.collection.immutable.Map.class);
var it = map.iterator();
while (it.hasNext()) {
var obj = (Tuple2<String, ProcessingPass.Metadata>) it.next();
try {
var pass = (ProcessingPass) Class.forName(obj._1()).getField("MODULE$").get(null);
var data = obj._2();
storage.update(pass, data);
} catch (ReflectiveOperationException ex) {
throw new IOException(ex);
}
}
return storage;
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistDiagnosticStorage extends Persistance<DiagnosticStorage> {
public PersistDiagnosticStorage() {
super(DiagnosticStorage.class, false, 302);
}
@Override
protected void writeObject(DiagnosticStorage obj, Output out) throws IOException {}
@Override
@SuppressWarnings("unchecked")
protected DiagnosticStorage readObject(Input in) throws IOException, ClassNotFoundException {
return new DiagnosticStorage(
(scala.collection.immutable.List) scala.collection.immutable.Nil$.MODULE$);
}
}
@SuppressWarnings("unchecked")
private static <T> scala.collection.immutable.List<T> nil() {
return (scala.collection.immutable.List<T>) scala.collection.immutable.Nil$.MODULE$;
}
private static <T> scala.collection.immutable.List<T> join(
T head, scala.collection.immutable.List<T> tail) {
return scala.collection.immutable.$colon$colon$.MODULE$.apply(head, tail);
}
@SuppressWarnings("unchecked")
private static <E extends Throwable> E raise(Class<E> clazz, Throwable t) throws E {
throw (E) t;
}
}

View File

@ -0,0 +1,10 @@
package org.enso.compiler.core.ir;
import org.enso.persist.Persistable;
@Persistable(clazz=Location.class, id=1)
public record Location(int start, int end) {
public int length() {
return end - start;
}
}

View File

@ -4,7 +4,7 @@ package org.enso.compiler.core.ir
*
* @param initDiagnostics the initial diagnostics
*/
sealed class DiagnosticStorage(initDiagnostics: Seq[Diagnostic] = Seq())
final class DiagnosticStorage(initDiagnostics: Seq[Diagnostic] = Seq())
extends Serializable {
private var diagnostics: List[Diagnostic] = initDiagnostics.toList

View File

@ -45,14 +45,25 @@ object Function {
*/
sealed case class Lambda(
override val arguments: List[DefinitionArgument],
override val body: Expression,
bodySeq: Seq[Expression],
location: Option[IdentifiedLocation],
override val canBeTCO: Boolean = true,
passData: MetadataStorage = MetadataStorage(),
diagnostics: DiagnosticStorage = DiagnosticStorage()
override val canBeTCO: Boolean,
passData: MetadataStorage,
diagnostics: DiagnosticStorage
) extends Function
with IRKind.Primitive {
def this(
arguments: List[DefinitionArgument],
body: Expression,
location: Option[IdentifiedLocation],
canBeTCO: Boolean = true,
passData: MetadataStorage = new MetadataStorage(),
diagnostics: DiagnosticStorage = new DiagnosticStorage()
) = {
this(arguments, Seq(body), location, canBeTCO, passData, diagnostics)
}
var id: UUID @Identifier = randomId
override lazy val body = bodySeq.head
/** Creates a copy of `this`.
*
@ -75,7 +86,7 @@ object Function {
id: UUID @Identifier = id
): Lambda = {
val res =
Lambda(arguments, body, location, canBeTCO, passData, diagnostics)
Lambda(arguments, Seq(body), location, canBeTCO, passData, diagnostics)
res.id = id
res
}
@ -150,6 +161,29 @@ object Function {
}
}
object Lambda {
def unapply(l: Lambda): Some[
(
List[DefinitionArgument],
Expression,
Option[IdentifiedLocation],
Boolean,
MetadataStorage,
DiagnosticStorage
)
] =
Some(
(
l.arguments,
l.body,
l.location,
l.canBeTCO,
l.passData,
l.diagnostics
)
)
}
/** A representation of the syntactic sugar for defining functions.
*
* @param name the name of the function

View File

@ -1,36 +0,0 @@
package org.enso.compiler.core.ir
import org.enso.syntax.text.Location
import java.util.UUID
/** Couples a location with a possible source identifier.
*
* @param location the code location.
* @param id the identifier for the location.
*/
case class IdentifiedLocation(location: Location, id: Option[UUID]) {
/** @return the character index of the start of this source location.
*/
def start: Int = location.start
/** @return the character index of the end of this source location.
*/
def end: Int = location.end
/** @return the length in characters of this location.
*/
def length: Int = location.length
}
object IdentifiedLocation {
/** Utility constructor, building a location without an ID.
*
* @param location the code location.
* @return an [[IdentifiedLocation]] corresponding to the input location.
*/
def apply(location: Location): IdentifiedLocation =
IdentifiedLocation(location, None)
}

View File

@ -11,7 +11,7 @@ import org.enso.compiler.core.CompilerStub
* storage with
*/
//noinspection DuplicatedCode
class MetadataStorage(
final class MetadataStorage(
startingMeta: Seq[MetadataPair[_]] = Seq()
) extends Serializable {
private var metadata: Map[ProcessingPass, Any] = Map(

View File

@ -23,7 +23,7 @@ import java.util.UUID
@SerialVersionUID(
8160L // Use BindingsMap
) // prevents reading broken caches, see PR-3692 for details
sealed case class Module(
final case class Module(
imports: List[Import],
exports: List[Export],
bindings: List[Definition],

View File

@ -3,7 +3,6 @@ package org.enso.compiler.core.ir
import org.enso.compiler.core.{ConstantsNames, IR, Identifier}
import org.enso.compiler.core.IR.randomId
import org.enso.compiler.core.Implicits.{ShowPassData, ToStringHelper}
import org.enso.syntax.text.Location
import java.util.UUID
@ -45,7 +44,7 @@ object Name {
* @param passData the pass metadata associated with this node
* @param diagnostics compiler diagnostics for this node
*/
sealed case class MethodReference(
final case class MethodReference(
typePointer: Option[Name],
methodName: Name,
location: Option[IdentifiedLocation],
@ -183,8 +182,8 @@ object Name {
(identLoc, segment) => {
identLoc.flatMap(loc => {
Some(
IdentifiedLocation(
Location(
new IdentifiedLocation(
new Location(
loc.location.start,
segment.location
.flatMap(l => Some(l.location.end))
@ -206,7 +205,7 @@ object Name {
* @param diagnostics compiler diagnostics for this node
* @return a copy of `this`, updated with the specified values
*/
sealed case class Qualified(
final case class Qualified(
parts: List[Name],
location: Option[IdentifiedLocation],
passData: MetadataStorage = MetadataStorage(),

View File

@ -49,13 +49,34 @@ object Method {
*/
sealed case class Explicit(
override val methodReference: Name.MethodReference,
override val body: Expression,
val bodySeq: Seq[Expression],
val isStatic: Boolean,
val isStaticWrapperForInstanceMethod: Boolean,
override val location: Option[IdentifiedLocation],
override val passData: MetadataStorage = MetadataStorage(),
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
override val passData: MetadataStorage,
override val diagnostics: DiagnosticStorage
) extends Method
with IRKind.Primitive {
def this(
methodReference: Name.MethodReference,
body: Expression,
location: Option[IdentifiedLocation],
passData: MetadataStorage = MetadataStorage(),
diagnostics: DiagnosticStorage = DiagnosticStorage()
) = {
this(
methodReference,
Seq(body),
Explicit.computeIsStatic(body),
Explicit.computeIsStaticWrapperForInstanceMethod(body),
location,
passData,
diagnostics
);
}
var id: UUID @Identifier = randomId
lazy val body = bodySeq.head
/** Creates a copy of `this`.
*
@ -70,14 +91,19 @@ object Method {
def copy(
methodReference: Name.MethodReference = methodReference,
body: Expression = body,
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
diagnostics: DiagnosticStorage = diagnostics,
id: UUID @Identifier = id
isStatic: Boolean = Explicit.computeIsStatic(body),
isStaticWrapperForInstanceMethod: Boolean =
Explicit.computeIsStaticWrapperForInstanceMethod(body),
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
diagnostics: DiagnosticStorage = diagnostics,
id: UUID @Identifier = id
): Explicit = {
val res = Explicit(
methodReference,
body,
List(body),
isStatic,
isStaticWrapperForInstanceMethod,
location,
passData,
diagnostics
@ -156,8 +182,21 @@ object Method {
s"${methodReference.showCode(indent)} = $exprStr"
}
}
def isStatic: Boolean = body match {
object Explicit {
def unapply(m: Explicit): Option[
(
Name.MethodReference,
Expression,
Option[IdentifiedLocation],
MetadataStorage,
DiagnosticStorage
)
] = {
Some((m.methodReference, m.body, m.location, m.passData, m.diagnostics))
}
private def computeIsStatic(body: IR): Boolean = body match {
case function: Function.Lambda =>
function.arguments.headOption.map(_.name) match {
case Some(Name.Self(_, true, _, _)) => true
@ -167,21 +206,21 @@ object Method {
true // if it's not a function, it has no arguments, therefore no `self`
}
def isStaticWrapperForInstanceMethod: Boolean = body match {
case function: Function.Lambda =>
function.arguments.map(_.name) match {
case Name.Self(_, true, _, _) :: Name.Self(
_,
false,
_,
_
) :: _ =>
true
case _ => false
}
case _ => false
}
private def computeIsStaticWrapperForInstanceMethod(body: IR): Boolean =
body match {
case function: Function.Lambda =>
function.arguments.map(_.name) match {
case Name.Self(_, true, _, _) :: Name.Self(
_,
false,
_,
_
) :: _ =>
true
case _ => false
}
case _ => false
}
}
/** The definition of a method for a given constructor using sugared

View File

@ -21,7 +21,11 @@ public class EnsoParserTest {
@BeforeClass
public static void initEnsoParser() {
ensoCompiler = new EnsoParser();
try {
ensoCompiler = new EnsoParser();
} catch (LinkageError e) {
throw new AssertionError(e);
}
}
@AfterClass
@ -1252,7 +1256,7 @@ public class EnsoParserTest {
""",
"""
# Comment with empty line
private
"""
);
@ -1345,29 +1349,13 @@ public class EnsoParserTest {
* @return string representation of the IR
*/
private static String simplifyIR(IR ir, boolean noIds, boolean noLocations, boolean lessDocs) {
if (noLocations) {
ir = ir.duplicate(false, true, true, true);
}
String txt = ir.pretty();
if (noIds) {
txt = txt.replaceAll("[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\\-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]\\-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]\\-[0-9a-f][0-9a-f][0-9a-f][0-9a-f]\\-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]", "_");
}
if (noLocations) {
for (;;) {
final String pref = " Location(";
int at = txt.indexOf(pref);
if (at == -1) {
break;
}
int to = at + pref.length();
int depth = 1;
while (depth > 0) {
switch (txt.charAt(to)) {
case '(' -> depth++;
case ')' -> depth--;
}
to++;
}
txt = txt.substring(0, at) + "Location[_]" + txt.substring(to);
}
}
if (lessDocs) {
for (;;) {
final String pref = "Comment.Documentation(";

View File

@ -0,0 +1,377 @@
package org.enso.compiler.core;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import org.enso.compiler.core.ir.DiagnosticStorage;
import org.enso.compiler.core.ir.IdentifiedLocation;
import org.enso.compiler.core.ir.Location;
import org.enso.compiler.core.ir.MetadataStorage;
import org.enso.compiler.core.ir.Module;
import org.enso.persist.Persistable;
import org.enso.persist.Persistance;
import org.junit.Test;
import org.openide.util.lookup.ServiceProvider;
import scala.Option;
import scala.Tuple2;
import scala.collection.immutable.List;
import scala.collection.immutable.Seq;
public class IrPersistanceTest {
@Test
public void locationTest() throws Exception {
var l = new Location(12, 33);
var n = serde(Location.class, l, 8);
assertEquals(12, n.start());
assertEquals(33, n.end());
assertEquals(l.length(), n.length());
}
@Test
public void identifiedLocation() throws Exception {
var il = new IdentifiedLocation(new Location(5, 19), null);
var in = serde(IdentifiedLocation.class, il, 12);
assertEquals(il, in);
}
@Test
public void identifiedLocationNoUUID() throws Exception {
var il = new IdentifiedLocation(new Location(5, 19), UUID.randomUUID());
var in = serde(IdentifiedLocation.class, il, 32);
assertEquals("UUIDs are serialized at the moment", il, in);
}
@Test
@SuppressWarnings("unchecked")
public void scalaMap() throws Exception {
var idLoc1 = new IdentifiedLocation(new Location(1, 5));
var in = scala.collection.immutable.Map$.MODULE$.empty().$plus(new Tuple2("Hi", idLoc1));
var out = serde(scala.collection.immutable.Map.class, in, 36);
assertEquals("One element", 1, out.size());
assertEquals(in, out);
}
@Test
@SuppressWarnings("unchecked")
public void scalaHashMap() throws Exception {
var idLoc1 = new IdentifiedLocation(new Location(1, 5));
var immutable = join(new Tuple2("Hi", idLoc1), nil());
var in =
(scala.collection.mutable.HashMap)
scala.collection.mutable.HashMap$.MODULE$.apply(immutable);
var out = serde(scala.collection.mutable.Map.class, in, 36);
assertEquals("One element", 1, out.size());
assertEquals(in, out);
}
@Test
@SuppressWarnings("unchecked")
public void scalaSet() throws Exception {
var idLoc1 = new IdentifiedLocation(new Location(1, 5));
var in = scala.collection.immutable.Set$.MODULE$.empty().$plus(idLoc1);
var out = serde(scala.collection.immutable.Set.class, in, 24);
assertEquals("One element", 1, out.size());
assertEquals(in, out);
}
@Test
public void scalaList() throws Exception {
var idLoc1 = new IdentifiedLocation(new Location(1, 5));
var idLoc2 = new IdentifiedLocation(new Location(2, 4), UUID.randomUUID());
var in = join(idLoc2, join(idLoc1, nil()));
var out = serde(List.class, in, 64);
assertEquals("Two elements", 2, out.size());
assertEquals("UUIDs are serialized at the moment", idLoc2, out.head());
assertEquals("Tail is the same", idLoc1, out.last());
}
@Test
public void scalaListSharedRef() throws Exception {
var idLoc1 = new IdentifiedLocation(new Location(1, 5));
var in = join(idLoc1, join(idLoc1, nil()));
var out = serde(List.class, in, 32);
assertEquals("Two elements", 2, out.size());
assertEquals("Head is equal to original", idLoc1, out.head());
assertEquals("Tail is equal to original", idLoc1, out.last());
assertSame("Head and tail are the same", out.head(), out.last());
}
@Test
@SuppressWarnings("unchecked")
public void scalaListSharedRefAtDepth() throws Exception {
var idLoc1 = Singleton.INSTANCE;
var in = join(Option.apply(idLoc1), join(Option.apply(idLoc1), nil()));
var out = serde(List.class, in, -1);
assertEquals("Two elements", 2, out.size());
var readHead = (Option<Singleton>) out.head();
var readTail = (Option<Singleton>) out.last();
assertEquals("Head is equal to original", idLoc1, readHead.get());
assertEquals("Tail is equal to original", idLoc1, readTail.get());
assertNotSame("Head and tail are different", readHead, readTail);
assertSame("Head and tail are the same", readHead.get(), readTail.get());
}
@Test
public void lazyScalaSequence() throws Exception {
var s1 = new LazySeq("Hello");
var s2 = new LazySeq("World");
var second = new boolean[1];
@SuppressWarnings("unchecked")
var in =
(Seq<String>)
Seq.fill(
2,
() -> {
if (second[0]) {
return s2;
}
second[0] = true;
return s1;
});
assertEquals("Seq with two elements created", 2, in.length());
LazySeq.forbidden = true;
var out = serde(Seq.class, in, -1);
assertEquals("Two elements", 2, out.size());
LazySeq.forbidden = false;
assertEquals("Lazily deserialized s2", s2, out.head());
assertNotSame("Lazily deserialized s2", s2, out.head());
assertEquals("Lazily deserialized s1", s1, out.last());
assertNotSame("Lazily deserialized s1", s1, out.head());
}
@Test
public void serializeModule() throws Exception {
var meta = new MetadataStorage(nil());
var diag = new DiagnosticStorage(nil());
var m = new Module(nil(), nil(), nil(), true, Option.empty(), meta, diag);
var out = serde(Module.class, m, -1);
assertEquals("Same", m, out);
}
@Test
public void hashMap() throws Exception {
var map = new HashMap<String, String>();
map.put("one", "uno");
map.put("two", "duo");
map.put("ten", "tre");
var out = serde(HashMap.class, map, -1);
assertEquals("Same", map, out);
}
@Test
public void readResolve() throws Exception {
var in = new Service(5);
var arr = Persistance.write(in, (Function<Object, Object>) null);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Remains five", 5, plain.get(Service.class).value());
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
assertEquals("Multiplied on read", 15, multiOnRead.get(Service.class).value());
}
@Test
public void writeReplace() throws Exception {
var in = new Service(5);
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Multiplied on write", 15, plain.get(Service.class).value());
}
@Test
public void readResolveInline() throws Exception {
var in = new ServiceSupply(new Service(5));
var arr = Persistance.write(in, (Function<Object, Object>) null);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Remains five", 5, plain.get(ServiceSupply.class).supply().value());
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
assertEquals("Multiplied on read", 15, multiOnRead.get(ServiceSupply.class).supply().value());
}
@Test
public void writeReplaceInline() throws Exception {
var in = new ServiceSupply(new Service(5));
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Multiplied on write", 15, plain.get(ServiceSupply.class).supply().value());
}
@Test
public void readResolveReference() throws Exception {
var in = new IntegerSupply(new Service(5));
var arr = Persistance.write(in, (Function<Object, Object>) null);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Remains five", 5, (int) plain.get(IntegerSupply.class).supply().get());
assertEquals("Remains five 2", 5, (int) plain.get(IntegerSupply.class).supply().get());
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
assertEquals("Multiplied on read", 15, (int) multiOnRead.get(IntegerSupply.class).supply().get());
}
@Test
public void writeReplaceReference() throws Exception {
var in = new IntegerSupply(new Service(5));
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Multiplied on write", 15, (int) plain.get(IntegerSupply.class).supply().get());
}
private static <T> T serde(Class<T> clazz, T l, int expectedSize) throws IOException {
var arr = Persistance.write(l, (Function<Object, Object>) null);
if (expectedSize >= 0) {
assertEquals(expectedSize, arr.length - 12);
}
var ref = Persistance.read(arr, (Function<Object, Object>) null);
return ref.get(clazz);
}
@SuppressWarnings("unchecked")
private static final <T> scala.collection.immutable.List<T> nil() {
return (scala.collection.immutable.List<T>) scala.collection.immutable.Nil$.MODULE$;
}
private static final <T> scala.collection.immutable.List<T> join(
T head, scala.collection.immutable.List<T> tail) {
return scala.collection.immutable.$colon$colon$.MODULE$.apply(head, tail);
}
private static class LazySeq implements CharSequence {
private static boolean forbidden;
private final String value;
public LazySeq(String value) {
if (forbidden) {
throw new IllegalStateException("Cannot create LazySeq right now!");
}
this.value = value;
}
public char charAt(int index) {
return value.charAt(index);
}
public int length() {
return value.length();
}
public CharSequence subSequence(int beginIndex, int endIndex) {
return value.subSequence(beginIndex, endIndex);
}
@Override
public int hashCode() {
int hash = 5;
hash = 53 * hash + Objects.hashCode(this.value);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final LazySeq other = (LazySeq) obj;
return Objects.equals(this.value, other.value);
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistLazySeq extends Persistance<LazySeq> {
public PersistLazySeq() {
super(LazySeq.class, false, 432432);
}
@Override
protected void writeObject(LazySeq obj, Output out) throws IOException {
out.writeUTF(obj.value);
}
@Override
protected LazySeq readObject(Input in) throws IOException, ClassNotFoundException {
var s = in.readUTF();
return new LazySeq(s);
}
}
public static final class Singleton {
public static final Singleton INSTANCE = new Singleton();
private Singleton() {}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistSingleton extends Persistance<Singleton> {
public PersistSingleton() {
super(Singleton.class, false, 432433);
}
@Override
protected void writeObject(Singleton obj, Output out) throws IOException {}
@Override
protected Singleton readObject(Input in) throws IOException, ClassNotFoundException {
return Singleton.INSTANCE;
}
}
@Persistable(clazz=Service.class, id=432434)
public record Service(int value) implements Supplier<Integer> {
@Override
public Integer get() {
return value;
}
}
@Persistable(clazz=IntegerSupply.class, id=432435)
public record IntegerSupply(Supplier<Integer> supply) {}
@Persistable(clazz=ServiceSupply.class, id=432436)
public record ServiceSupply(Service supply) {}
}

View File

@ -217,7 +217,7 @@ public abstract class Cache<T, M extends Cache.Metadata> {
}
logger.log(logLevel, "Unable to load a cache [" + logName + "]");
} catch (IOException e) {
} catch (Exception e) {
logger.log(
Level.WARNING,
"Unable to load a cache [" + logName + "]: " + e.getMessage(),
@ -271,14 +271,13 @@ public abstract class Cache<T, M extends Cache.Metadata> {
invalidateCache(cacheRoot, logger);
return Optional.empty();
}
} catch (IOException ioe) {
} catch (ClassNotFoundException | IOException ex) {
logger.log(
logLevel, "`" + logName + "` failed to load (caused by: " + ioe.getMessage() + ").");
Level.WARNING,
"`" + logName + "` in " + dataPath + " failed to load: " + ex.getMessage());
logger.log(logLevel, "`" + logName + "` failed to load.", ex);
invalidateCache(cacheRoot, logger);
return Optional.empty();
} catch (ClassNotFoundException e) {
logger.log(Level.WARNING, logName + " appears to be corrupted", e);
return Optional.empty();
}
} else {
logger.log(logLevel, "One or more digests did not match for the cache for [{0}].", logName);

View File

@ -1,21 +1,21 @@
package org.enso.interpreter.caches;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import org.apache.commons.lang3.StringUtils;
import org.enso.persist.Persistance;
import org.enso.persist.Persistable;
import org.enso.compiler.data.BindingsMap;
import org.enso.compiler.data.BindingsMap.DefinedEntity;
import org.enso.compiler.data.BindingsMap.ModuleReference;
import org.enso.editions.LibraryName;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.pkg.QualifiedName;
import org.enso.pkg.SourceFile;
import org.openide.util.lookup.ServiceProvider;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
@ -24,8 +24,11 @@ import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLogger;
import buildinfo.Info;
import scala.Option;
import scala.Tuple2;
import scala.collection.immutable.Map;
@Persistable(clazz = QualifiedName.class, id = 30300)
public final class ImportExportCache extends Cache<ImportExportCache.CachedBindings, ImportExportCache.Metadata> {
private final LibraryName libraryName;
@ -49,13 +52,9 @@ public final class ImportExportCache extends Cache<ImportExportCache.CachedBindi
@Override
protected CachedBindings deserialize(EnsoContext context, byte[] data, Metadata meta, TruffleLogger logger) throws ClassNotFoundException, IOException, ClassNotFoundException {
try (var stream = new ObjectInputStream(new ByteArrayInputStream(data))) {
if (stream.readObject() instanceof MapToBindings bindings) {
return new CachedBindings(libraryName, bindings, Optional.empty());
} else {
throw new ClassNotFoundException("Expected ImportExportCache.FileToBindings, got " + data.getClass());
}
}
var ref = Persistance.read(data, null);
var bindings = ref.get(MapToBindings.class);
return new CachedBindings(libraryName, bindings, Optional.empty());
}
@Override
@ -107,25 +106,58 @@ public final class ImportExportCache extends Cache<ImportExportCache.CachedBindi
@Override
protected byte[] serialize(EnsoContext context, CachedBindings entry) throws IOException {
var byteStream = new ByteArrayOutputStream();
try (ObjectOutputStream stream = new ObjectOutputStream(byteStream)) {
stream.writeObject(entry.bindings());
}
return byteStream.toByteArray();
var arr = Persistance.write(entry.bindings(), null);
return arr;
}
public static final class MapToBindings implements Serializable {
private final Map<QualifiedName, BindingsMap> _entries;
public static final class MapToBindings {
private final Map<QualifiedName, Persistance.Reference<BindingsMap>> entries;
public MapToBindings(Map<QualifiedName, BindingsMap> entries) {
this._entries = entries;
public MapToBindings(Map<QualifiedName, Persistance.Reference<BindingsMap>> entries) {
this.entries = entries;
}
public Map<QualifiedName, BindingsMap> entries() {
return _entries;
public Option<BindingsMap> findForModule(QualifiedName moduleName) {
var ref = entries.get(moduleName);
if (ref.isEmpty()) {
return Option.empty();
}
return Option.apply(ref.get().get(BindingsMap.class));
}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistMapToBindings extends Persistance<MapToBindings> {
public PersistMapToBindings() {
super(MapToBindings.class, false, 364);
}
@Override
protected void writeObject(MapToBindings obj, Output out) throws IOException {
out.writeInt(obj.entries.size());
var it = obj.entries.iterator();
while (it.hasNext()) {
var e = it.next();
out.writeInline(QualifiedName.class, e._1());
out.writeObject(e._2().get(BindingsMap.class));
}
}
@Override
@SuppressWarnings("unchecked")
protected MapToBindings readObject(Input in) throws IOException, ClassNotFoundException {
var size = in.readInt();
var b = Map.newBuilder();
b.sizeHint(size);
while (size-- > 0) {
var name = in.readInline(QualifiedName.class);
var value = in.readReference(BindingsMap.class);
b.addOne(Tuple2.apply(name, value));
}
return new MapToBindings((Map) b.result());
}
}
public static record CachedBindings(LibraryName libraryName, MapToBindings bindings, Optional<List<SourceFile<TruffleFile>>> sources) {
}
@ -139,4 +171,57 @@ public final class ImportExportCache extends Cache<ImportExportCache.CachedBindi
private final static ObjectMapper objectMapper = new ObjectMapper();
@Persistable(clazz=BindingsMap.PolyglotSymbol.class, id=33006)
@Persistable(clazz=org.enso.compiler.data.BindingsMap$ModuleReference$Abstract.class, id=33007)
@Persistable(clazz=BindingsMap.ModuleMethod.class, id=33008)
@Persistable(clazz=BindingsMap.Type.class, id=33009)
@Persistable(clazz=BindingsMap.ResolvedImport.class, id=33010)
@Persistable(clazz=BindingsMap.Cons.class, id=33011)
@Persistable(clazz=BindingsMap.ResolvedModule.class, id=33012)
@Persistable(clazz=BindingsMap.ResolvedType.class, id=33013)
@Persistable(clazz=BindingsMap.ResolvedMethod.class, id=33014)
@Persistable(clazz=BindingsMap.ExportedModule.class, id=33015)
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$Only.class, id=33016)
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$Union.class, id=33017)
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$Intersect.class, id=33018)
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$AllowedResolution.class, id=33019)
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$All$.class, id=33020)
@Persistable(clazz=org.enso.compiler.data.BindingsMap$SymbolRestriction$Hiding.class, id=33021)
@Persistable(clazz=BindingsMap.Resolution.class, id=33029)
@Persistable(clazz=BindingsMap.ResolvedConstructor.class, id=33030)
@Persistable(clazz=BindingsMap.ResolvedPolyglotSymbol.class, id=33031)
@Persistable(clazz=BindingsMap.ResolvedPolyglotField.class, id=33032)
@ServiceProvider(service = Persistance.class)
public static final class PersistBindingsMap extends Persistance<BindingsMap> {
public PersistBindingsMap() {
super(BindingsMap.class, false, 33005);
}
@Override
protected void writeObject(BindingsMap obj, Output out) throws IOException {
out.writeObject(obj.definedEntities());
out.writeObject(obj.currentModule());
out.writeInline(scala.collection.immutable.List.class, obj.resolvedImports());
out.writeInline(scala.collection.immutable.List.class, obj.resolvedExports());
out.writeInline(scala.collection.immutable.Map.class, obj.exportedSymbols());
}
@Override
@SuppressWarnings("unchecked")
protected BindingsMap readObject(Input in) throws IOException, ClassNotFoundException {
var de = (scala.collection.immutable.List<DefinedEntity>) in.readObject();
var cm = (ModuleReference) in.readObject();
var imp = in.readInline(scala.collection.immutable.List.class);
var exp = in.readInline(scala.collection.immutable.List.class);
var sym = in.readInline(scala.collection.immutable.Map.class);
var map = new BindingsMap(de, cm);
map.resolvedImports_$eq(imp);
map.resolvedExports_$eq(exp);
map.exportedSymbols_$eq(sym);
return map;
}
}
}

View File

@ -1,28 +1,28 @@
package org.enso.interpreter.caches;
import buildinfo.Info;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.source.Source;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;
import org.enso.compiler.core.ir.Module;
import org.enso.compiler.core.ir.ProcessingPass;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.polyglot.CompilationStage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import java.util.logging.Level;
import org.enso.persist.Persistance;
public final class ModuleCache extends Cache<ModuleCache.CachedModule, ModuleCache.Metadata> {
private final org.enso.interpreter.runtime.Module module;
@ -46,17 +46,19 @@ public final class ModuleCache extends Cache<ModuleCache.CachedModule, ModuleCac
@Override
protected CachedModule deserialize(EnsoContext context, byte[] data, Metadata meta, TruffleLogger logger) throws ClassNotFoundException, IOException, ClassNotFoundException {
try (var stream = new ObjectInputStream(new ByteArrayInputStream(data))) {
if (stream.readObject() instanceof Module ir) {
try {
return new CachedModule(ir,CompilationStage.valueOf(meta.compilationStage()), module.getSource());
} catch (IOException ioe) {
throw new ClassNotFoundException(ioe.getMessage());
}
var ref = Persistance.read(data, (obj) -> switch (obj) {
case ProcessingPass.Metadata metadata -> {
var option = metadata.restoreFromSerialization(context.getCompiler().context());
if (option.nonEmpty()) {
yield option.get();
} else {
throw new ClassNotFoundException("Expected Module, got " + data.getClass());
throw raise(RuntimeException.class, new IOException("Cannot convert " + metadata));
}
}
default -> obj;
});
var mod = ref.get(Module.class);
return new CachedModule(mod, CompilationStage.valueOf(meta.compilationStage()), module.getSource());
}
@Override
@ -142,37 +144,11 @@ public final class ModuleCache extends Cache<ModuleCache.CachedModule, ModuleCac
@Override
protected byte[] serialize(EnsoContext context, CachedModule entry) throws IOException {
var byteStream = new ByteArrayOutputStream();
boolean noUUIDs = false;
for (var p : context.getPackageRepository().getLoadedPackagesJava()) {
if ("Standard".equals(p.namespace())) {
for (var s : p.listSourcesJava()) {
if (s.file().getPath().equals(entry.source().getPath())) {
noUUIDs = true;
break;
}
}
}
}
try (var stream = new ObjectOutputStream(byteStream) {
void filterUUIDs() {
enableReplaceObject(true);
}
@Override
protected Object replaceObject(Object obj) throws IOException {
if (obj instanceof UUID) {
return null;
}
return obj;
}
}) {
if (noUUIDs) {
stream.filterUUIDs();
}
stream.writeObject(entry.moduleIR());
}
return byteStream.toByteArray();
var arr = Persistance.write(entry.moduleIR(), (obj) -> switch (obj) {
case ProcessingPass.Metadata metadata -> metadata.prepareForSerialization(context.getCompiler().context());
default -> obj;
});
return arr;
}
public record CachedModule(Module moduleIR, CompilationStage compilationStage, Source source) {
@ -189,4 +165,8 @@ public final class ModuleCache extends Cache<ModuleCache.CachedModule, ModuleCac
private final static ObjectMapper objectMapper = new ObjectMapper();
@SuppressWarnings("unchecked")
private static <T extends Exception> T raise(Class<T> cls, Exception e) throws T {
throw (T)e;
}
}

View File

@ -62,7 +62,6 @@ public final class Module implements EnsoObject {
private QualifiedName name;
private final ModuleCache cache;
private boolean wasLoadedFromCache;
private boolean hasCrossModuleLinks;
private final boolean synthetic;
/**
* This list is filled in case there is a directory with the same name as this module. The
@ -88,7 +87,6 @@ public final class Module implements EnsoObject {
this.name = name;
this.cache = new ModuleCache(this);
this.wasLoadedFromCache = false;
this.hasCrossModuleLinks = false;
this.synthetic = false;
}
@ -106,7 +104,6 @@ public final class Module implements EnsoObject {
this.name = name;
this.cache = new ModuleCache(this);
this.wasLoadedFromCache = false;
this.hasCrossModuleLinks = false;
this.patchedValues = new PatchedModuleValues(this);
this.synthetic = false;
}
@ -125,7 +122,6 @@ public final class Module implements EnsoObject {
this.name = name;
this.cache = new ModuleCache(this);
this.wasLoadedFromCache = false;
this.hasCrossModuleLinks = false;
this.patchedValues = new PatchedModuleValues(this);
this.synthetic = false;
}
@ -147,7 +143,6 @@ public final class Module implements EnsoObject {
this.compilationStage = synthetic ? CompilationStage.INITIAL : CompilationStage.AFTER_CODEGEN;
this.cache = new ModuleCache(this);
this.wasLoadedFromCache = false;
this.hasCrossModuleLinks = false;
this.synthetic = synthetic;
}
@ -516,19 +511,6 @@ public final class Module implements EnsoObject {
this.wasLoadedFromCache = wasLoadedFromCache;
}
/**
* @return {@code true} if the module has had its cross-module links restored, otherwise {@code
* false}
*/
public boolean hasCrossModuleLinks() {
return hasCrossModuleLinks;
}
/** @param hasCrossModuleLinks whether or not the module has cross-module links restored */
void setHasCrossModuleLinks(boolean hasCrossModuleLinks) {
this.hasCrossModuleLinks = hasCrossModuleLinks;
}
/**
* Turns this module into appropriate {@link CompilerContext} wrapper.
*

View File

@ -150,11 +150,6 @@ final class TruffleCompilerContext implements CompilerContext {
return ((Module)module).unsafeModule().wasLoadedFromCache();
}
@Override
public boolean hasCrossModuleLinks(CompilerContext.Module module) {
return ((Module)module).unsafeModule().hasCrossModuleLinks();
}
@Override
public org.enso.compiler.core.ir.Module getIr(CompilerContext.Module module) {
return module.getIr();
@ -225,17 +220,9 @@ final class TruffleCompilerContext implements CompilerContext {
);
} else {
builtins.initializeBuiltinsIr(this, freshNameSupply, passes);
updateModule(
builtinsModule,
u -> u.hasCrossModuleLinks(true)
);
}
} else {
builtins.initializeBuiltinsIr(this, freshNameSupply, passes);
updateModule(
builtinsModule,
u -> u.hasCrossModuleLinks(true)
);
}
if (irCachingEnabled && !wasLoadedFromCache(builtinsModule)) {
@ -288,7 +275,6 @@ final class TruffleCompilerContext implements CompilerContext {
private org.enso.compiler.core.ir.Module ir;
private CompilationStage stage;
private Boolean loadedFromCache;
private Boolean hasCrossModuleLinks;
private boolean resetScope;
private boolean invalidateCache;
@ -316,11 +302,6 @@ final class TruffleCompilerContext implements CompilerContext {
this.loadedFromCache = b;
}
@Override
public void hasCrossModuleLinks(boolean b) {
this.hasCrossModuleLinks = b;
}
@Override
public void resetScope() {
this.resetScope = true;
@ -348,9 +329,6 @@ final class TruffleCompilerContext implements CompilerContext {
if (loadedFromCache != null) {
module.module.setLoadedFromCache(loadedFromCache);
}
if (hasCrossModuleLinks != null) {
module.module.setHasCrossModuleLinks(hasCrossModuleLinks);
}
if (resetScope) {
module.module.ensureScopeExists();
module.module.getScope().reset();
@ -429,11 +407,6 @@ final class TruffleCompilerContext implements CompilerContext {
return module.isSynthetic();
}
@Override
public boolean hasCrossModuleLinks() {
return module.hasCrossModuleLinks();
}
@Override
public org.enso.compiler.core.ir.Module getIr() {
return module.getIr();

View File

@ -10,6 +10,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.enso.interpreter.runtime.Module;
import org.enso.interpreter.runtime.callable.function.Function;
@ -24,7 +25,7 @@ public final class ModuleScope implements EnsoObject {
private final Module module;
private Map<String, Object> polyglotSymbols;
private Map<String, Type> types;
private Map<Type, Map<String, Function>> methods;
private Map<Type, Map<String, Supplier<Function>>> methods;
private Map<Type, Map<Type, Function>> conversions;
private Set<ModuleScope> imports;
private Set<ModuleScope> exports;
@ -56,7 +57,7 @@ public final class ModuleScope implements EnsoObject {
Type associatedType,
Map<String, Object> polyglotSymbols,
Map<String, Type> types,
Map<Type, Map<String, Function>> methods,
Map<Type, Map<String, Supplier<Function>>> methods,
Map<Type, Map<Type, Function>> conversions,
Set<ModuleScope> imports,
Set<ModuleScope> exports) {
@ -95,14 +96,14 @@ public final class ModuleScope implements EnsoObject {
* @param type the type for which method map is requested
* @return a map containing all the defined methods by name
*/
private Map<String, Function> ensureMethodMapFor(Type type) {
private Map<String, Supplier<Function>> ensureMethodMapFor(Type type) {
Type tpeKey = type == null ? noTypeKey : type;
return methods.computeIfAbsent(tpeKey, k -> new HashMap<>());
}
private Map<String, Function> getMethodMapFor(Type type) {
private Map<String, Supplier<Function>> getMethodMapFor(Type type) {
Type tpeKey = type == null ? noTypeKey : type;
Map<String, Function> result = methods.get(type);
Map<String, Supplier<Function>> result = methods.get(type);
if (result == null) {
return new HashMap<>();
}
@ -117,14 +118,55 @@ public final class ModuleScope implements EnsoObject {
* @param function the {@link Function} associated with this definition
*/
public void registerMethod(Type type, String method, Function function) {
Map<String, Function> methodMap = ensureMethodMapFor(type);
Map<String, Supplier<Function>> methodMap = ensureMethodMapFor(type);
// Builtin types will have double definition because of
// BuiltinMethod and that's OK
if (methodMap.containsKey(method) && !type.isBuiltin()) {
throw new RedefinedMethodException(type.getName(), method);
} else {
methodMap.put(method, function);
methodMap.put(method, new CachingSupplier<>(function));
}
}
/**
* Registers a lazily constructed method defined for a given type.
*
* @param type the type the method was defined for
* @param method method name
* @param supply provider of the {@link Function} associated with this definition
*/
public void registerMethod(Type type, String method, Supplier<Function> supply) {
Map<String, Supplier<Function>> methodMap = ensureMethodMapFor(type);
// Builtin types will have double definition because of
// BuiltinMethod and that's OK
if (methodMap.containsKey(method) && !type.isBuiltin()) {
throw new RedefinedMethodException(type.getName(), method);
} else {
methodMap.put(method, new CachingSupplier<>(supply));
}
}
private static final class CachingSupplier<T> implements Supplier<T> {
private final Supplier<T> supply;
private T memo;
CachingSupplier(Supplier<T> supply) {
this.supply = supply;
}
CachingSupplier(T memo) {
this.supply = null;
this.memo = memo;
}
@Override
public T get() {
if (memo == null) {
memo = supply.get();
}
return memo;
}
}
@ -185,14 +227,14 @@ public final class ModuleScope implements EnsoObject {
*/
@TruffleBoundary
public Function lookupMethodDefinition(Type type, String name) {
Function definedWithAtom = type.getDefinitionScope().getMethodMapFor(type).get(name);
var definedWithAtom = type.getDefinitionScope().getMethodMapFor(type).get(name);
if (definedWithAtom != null) {
return definedWithAtom;
return definedWithAtom.get();
}
Function definedHere = getMethodMapFor(type).get(name);
var definedHere = getMethodMapFor(type).get(name);
if (definedHere != null) {
return definedHere;
return definedHere.get();
}
return imports.stream()
@ -226,13 +268,14 @@ public final class ModuleScope implements EnsoObject {
}
private Function getExportedMethod(Type type, String name) {
Function here = getMethodMapFor(type).get(name);
var here = getMethodMapFor(type).get(name);
if (here != null) {
return here;
return here.get();
}
return exports.stream()
.map(scope -> scope.getMethodMapFor(type).get(name))
.filter(Objects::nonNull)
.map(s -> s.get())
.findFirst()
.orElse(null);
}
@ -281,8 +324,12 @@ public final class ModuleScope implements EnsoObject {
/** @return a method for the given type */
public Function getMethodForType(Type tpe, String name) {
Type tpeKey = tpe == null ? noTypeKey : tpe;
Map<String, Function> allTpeMethods = methods.get(tpeKey);
return allTpeMethods == null ? null : allTpeMethods.get(name);
var allTpeMethods = methods.get(tpeKey);
if (allTpeMethods == null) {
return null;
}
var supply = allTpeMethods.get(name);
return supply == null ? null : supply.get();
}
/**
@ -293,7 +340,7 @@ public final class ModuleScope implements EnsoObject {
*/
public Set<String> getMethodNamesForType(Type tpe) {
Type tpeKey = tpe == null ? noTypeKey : tpe;
Map<String, Function> allTpeMethods = methods.get(tpeKey);
var allTpeMethods = methods.get(tpeKey);
return allTpeMethods == null ? null : allTpeMethods.keySet();
}
@ -305,7 +352,7 @@ public final class ModuleScope implements EnsoObject {
*/
public void registerAllMethodsOfTypeToScope(Type tpe, ModuleScope scope) {
Type tpeKey = tpe == null ? noTypeKey : tpe;
Map<String, Function> allTypeMethods = methods.get(tpeKey);
var allTypeMethods = methods.get(tpeKey);
if (allTypeMethods != null) {
allTypeMethods.forEach((name, fun) -> scope.registerMethod(tpeKey, name, fun));
}
@ -313,7 +360,10 @@ public final class ModuleScope implements EnsoObject {
/** @return methods for all registered types */
public List<Function> getAllMethods() {
return methods.values().stream().flatMap(e -> e.values().stream()).collect(Collectors.toList());
return methods.values().stream()
.flatMap(e -> e.values().stream())
.map(s -> s.get())
.collect(Collectors.toList());
}
/** @return the raw conversions held by this module */
@ -346,7 +396,7 @@ public final class ModuleScope implements EnsoObject {
public ModuleScope withTypes(List<String> typeNames) {
Map<String, Object> polyglotSymbols = new HashMap<>(this.polyglotSymbols);
Map<String, Type> requestedTypes = new HashMap<>(this.types);
Map<Type, Map<String, Function>> methods = new ConcurrentHashMap<>();
Map<Type, Map<String, Supplier<Function>>> methods = new ConcurrentHashMap<>();
Map<Type, Map<Type, Function>> conversions = new ConcurrentHashMap<>();
Set<ModuleScope> imports = new HashSet<>(this.imports);
Set<ModuleScope> exports = new HashSet<>(this.exports);

View File

@ -586,7 +586,7 @@ private class DefaultPackageRepository(
}
loadedLibraryBindings.get(libraryName)
}
cache.flatMap(_.bindings.entries().get(moduleName))
cache.flatMap(_.bindings.findForModule(moduleName))
}
private def loadDependencies(pkg: Package[TruffleFile]): Unit = {

View File

@ -438,191 +438,203 @@ class IrToTruffle(
dataflowInfo
)
val function = methodDef.body match {
case fn: Function if isBuiltinMethod(fn.body) =>
// For builtin types that own the builtin method we only check that
// the method has been registered during the initialization of builtins
// and not attempt to register it in the scope (can't redefined methods).
// For non-builtin types (or modules) that own the builtin method
// we have to look up the function and register it in the scope.
// Static wrappers for instance methods have to be registered always.
val fullMethodName = methodDef.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Literal.Text]
moduleScope.registerMethod(
cons,
methodDef.methodName.name,
() => {
val function = methodDef.body match {
case fn: Function if isBuiltinMethod(fn.body) =>
// For builtin types that own the builtin method we only check that
// the method has been registered during the initialization of builtins
// and not attempt to register it in the scope (can't redefined methods).
// For non-builtin types (or modules) that own the builtin method
// we have to look up the function and register it in the scope.
// Static wrappers for instance methods have to be registered always.
val fullMethodName = methodDef.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Literal.Text]
val builtinNameElements = fullMethodName.text.split('.')
if (builtinNameElements.length != 2) {
throw new CompilerError(
s"Unknown builtin method ${fullMethodName.text}, probably should be '$fullMethodDefName?'"
)
}
val methodName = builtinNameElements(1)
val methodOwnerName = builtinNameElements(0)
val staticWrapper = methodDef.isStaticWrapperForInstanceMethod
val builtinFunction = context.getBuiltins
.getBuiltinFunction(
methodOwnerName,
methodName,
language,
staticWrapper
)
builtinFunction.toScala
.map(Some(_))
.toRight(
new CompilerError(
s"Unable to find Truffle Node for method ${cons.getName}.${methodDef.methodName.name}"
)
)
.left
.flatMap { l =>
// Builtin Types Number and Integer have methods only for documentation purposes
val number = context.getBuiltins.number()
val ok =
staticWrapper && (cons == number.getNumber.getEigentype || cons == number.getInteger.getEigentype) ||
!staticWrapper && (cons == number.getNumber || cons == number.getInteger)
if (ok) Right(None)
else Left(l)
}
.map(fOpt =>
fOpt.map { m =>
if (m.isAutoRegister) {
val irFunctionArgumentsCount = fn.arguments.length
val builtinArgumentsCount =
m.getFunction.getSchema.getArgumentsCount
if (irFunctionArgumentsCount != builtinArgumentsCount) {
val irFunctionArguments =
fn.arguments.map(_.name.name).mkString(",")
val builtinArguments =
m.getFunction.getSchema.getArgumentInfos
.map(_.getName)
.mkString(",")
throw new CompilerError(
s"Wrong number of arguments provided in the definition of builtin function ${cons.getName}.${methodDef.methodName.name}. " +
s"[$irFunctionArguments] vs [$builtinArguments]"
)
}
val bodyBuilder =
new expressionProcessor.BuildFunctionBody(
fn.arguments,
fn.body,
effectContext,
true
)
val builtinRootNode =
m.getFunction.getCallTarget.getRootNode
.asInstanceOf[BuiltinRootNode]
builtinRootNode.setModuleName(moduleScope.getModule.getName)
builtinRootNode.setTypeName(cons.getQualifiedName)
new RuntimeFunction(
m.getFunction.getCallTarget,
null,
new FunctionSchema(
new Array[RuntimeAnnotation](0),
bodyBuilder.args(): _*
)
)
} else {
m.getFunction
}
val builtinNameElements = fullMethodName.text.split('.')
if (builtinNameElements.length != 2) {
throw new CompilerError(
s"Unknown builtin method ${fullMethodName.text}, probably should be '$fullMethodDefName?'"
)
}
)
case fn: Function =>
val bodyBuilder =
new expressionProcessor.BuildFunctionBody(
fn.arguments,
fn.body,
effectContext,
true
)
val rootNode = MethodRootNode.build(
language,
expressionProcessor.scope,
moduleScope,
() => bodyBuilder.bodyNode(),
makeSection(moduleScope, methodDef.location),
cons,
methodDef.methodName.name
)
val callTarget = rootNode.getCallTarget
val arguments = bodyBuilder.args()
// build annotations
val annotations =
methodDef.getMetadata(GenericAnnotations).toVector.flatMap {
meta =>
meta.annotations
.collect { case annotation: Name.GenericAnnotation =>
val scopeElements = Seq(
cons.getName,
methodDef.methodName.name,
annotation.name
)
val scopeName =
scopeElements.mkString(Constants.SCOPE_SEPARATOR)
val scopeInfo = annotation
.unsafeGetMetadata(
AliasAnalysis,
s"Missing scope information for annotation " +
s"${annotation.name} of method " +
scopeElements.init.mkString(Constants.SCOPE_SEPARATOR)
)
.unsafeAs[AliasAnalysis.Info.Scope.Root]
val dataflowInfo = annotation.unsafeGetMetadata(
DataflowAnalysis,
"Missing dataflow information for annotation " +
s"${annotation.name} of method " +
scopeElements.init.mkString(Constants.SCOPE_SEPARATOR)
)
val expressionProcessor = new ExpressionProcessor(
scopeName,
scopeInfo.graph,
scopeInfo.graph.rootScope,
dataflowInfo
)
val expressionNode =
expressionProcessor.run(annotation.expression, true)
val closureName =
s"<default::${expressionProcessor.scopeName}>"
val closureRootNode = ClosureRootNode.build(
language,
expressionProcessor.scope,
moduleScope,
expressionNode,
makeSection(moduleScope, annotation.location),
closureName,
true,
false
)
new RuntimeAnnotation(annotation.name, closureRootNode)
}
}
val methodName = builtinNameElements(1)
val methodOwnerName = builtinNameElements(0)
Right(
Some(
new RuntimeFunction(
callTarget,
null,
new FunctionSchema(annotations.toArray, arguments: _*)
val staticWrapper = methodDef.isStaticWrapperForInstanceMethod
val builtinFunction = context.getBuiltins
.getBuiltinFunction(
methodOwnerName,
methodName,
language,
staticWrapper
)
builtinFunction.toScala
.map(Some(_))
.toRight(
new CompilerError(
s"Unable to find Truffle Node for method ${cons.getName}.${methodDef.methodName.name}"
)
)
.left
.flatMap { l =>
// Builtin Types Number and Integer have methods only for documentation purposes
val number = context.getBuiltins.number()
val ok =
staticWrapper && (cons == number.getNumber.getEigentype || cons == number.getInteger.getEigentype) ||
!staticWrapper && (cons == number.getNumber || cons == number.getInteger)
if (ok) Right(None)
else Left(l)
}
.map(fOpt =>
fOpt.map { m =>
if (m.isAutoRegister) {
val irFunctionArgumentsCount = fn.arguments.length
val builtinArgumentsCount =
m.getFunction.getSchema.getArgumentsCount
if (irFunctionArgumentsCount != builtinArgumentsCount) {
val irFunctionArguments =
fn.arguments.map(_.name.name).mkString(",")
val builtinArguments =
m.getFunction.getSchema.getArgumentInfos
.map(_.getName)
.mkString(",")
throw new CompilerError(
s"Wrong number of arguments provided in the definition of builtin function ${cons.getName}.${methodDef.methodName.name}. " +
s"[$irFunctionArguments] vs [$builtinArguments]"
)
}
val bodyBuilder =
new expressionProcessor.BuildFunctionBody(
fn.arguments,
fn.body,
effectContext,
true
)
val builtinRootNode =
m.getFunction.getCallTarget.getRootNode
.asInstanceOf[BuiltinRootNode]
builtinRootNode
.setModuleName(moduleScope.getModule.getName)
builtinRootNode.setTypeName(cons.getQualifiedName)
new RuntimeFunction(
m.getFunction.getCallTarget,
null,
new FunctionSchema(
new Array[RuntimeAnnotation](0),
bodyBuilder.args(): _*
)
)
} else {
m.getFunction
}
}
)
case fn: Function =>
val bodyBuilder =
new expressionProcessor.BuildFunctionBody(
fn.arguments,
fn.body,
effectContext,
true
)
val rootNode = MethodRootNode.build(
language,
expressionProcessor.scope,
moduleScope,
() => bodyBuilder.bodyNode(),
makeSection(moduleScope, methodDef.location),
cons,
methodDef.methodName.name
)
)
)
case _ =>
Left(
new CompilerError(
"Method bodies must be functions at the point of codegen."
)
)
}
function match {
case Left(failure) =>
throw failure
case Right(Some(fun)) =>
moduleScope.registerMethod(cons, methodDef.methodName.name, fun)
case _ =>
// Don't register dummy function nodes
}
val callTarget = rootNode.getCallTarget
val arguments = bodyBuilder.args()
// build annotations
val annotations =
methodDef.getMetadata(GenericAnnotations).toVector.flatMap {
meta =>
meta.annotations
.collect { case annotation: Name.GenericAnnotation =>
val scopeElements = Seq(
cons.getName,
methodDef.methodName.name,
annotation.name
)
val scopeName =
scopeElements.mkString(Constants.SCOPE_SEPARATOR)
val scopeInfo = annotation
.unsafeGetMetadata(
AliasAnalysis,
s"Missing scope information for annotation " +
s"${annotation.name} of method " +
scopeElements.init
.mkString(Constants.SCOPE_SEPARATOR)
)
.unsafeAs[AliasAnalysis.Info.Scope.Root]
val dataflowInfo = annotation.unsafeGetMetadata(
DataflowAnalysis,
"Missing dataflow information for annotation " +
s"${annotation.name} of method " +
scopeElements.init
.mkString(Constants.SCOPE_SEPARATOR)
)
val expressionProcessor = new ExpressionProcessor(
scopeName,
scopeInfo.graph,
scopeInfo.graph.rootScope,
dataflowInfo
)
val expressionNode =
expressionProcessor.run(annotation.expression, true)
val closureName =
s"<default::${expressionProcessor.scopeName}>"
val closureRootNode = ClosureRootNode.build(
language,
expressionProcessor.scope,
moduleScope,
expressionNode,
makeSection(moduleScope, annotation.location),
closureName,
true,
false
)
new RuntimeAnnotation(
annotation.name,
closureRootNode
)
}
}
Right(
Some(
new RuntimeFunction(
callTarget,
null,
new FunctionSchema(annotations.toArray, arguments: _*)
)
)
)
case _ =>
Left(
new CompilerError(
"Method bodies must be functions at the point of codegen."
)
)
}
function match {
case Left(failure) =>
throw failure
case Right(Some(fun)) =>
fun
case x =>
throw new IllegalStateException("Wrong state: " + x)
}
}
)
}
})

View File

@ -119,10 +119,6 @@ final class SerializationManager(private val context: TruffleCompilerContext) {
module.getIr,
module.getIr.duplicate(keepIdentifiers = true)
)
duplicatedIr.preorder.foreach(
_.passData.prepareForSerialization(compiler.context)
)
val task = doSerializeModule(
getCache(module),
duplicatedIr,
@ -215,7 +211,10 @@ final class SerializationManager(private val context: TruffleCompilerContext) {
)
val abstractBindings =
bindings.prepareForSerialization(compiler.context)
(module.getName, abstractBindings)
(
module.getName,
org.enso.persist.Persistance.Reference.of(abstractBindings)
)
}
.toMap
),
@ -401,6 +400,7 @@ final class SerializationManager(private val context: TruffleCompilerContext) {
compiler: Compiler,
module: Module
): Option[Boolean] = {
compiler.getClass()
if (isWaitingForSerialization(module)) {
abort(module)
None
@ -411,9 +411,6 @@ final class SerializationManager(private val context: TruffleCompilerContext) {
context.loadCache(getCache(module)).toScala match {
case Some(loadedCache) =>
val relinkedIrChecks =
loadedCache.moduleIR.preorder
.map(_.passData.restoreFromSerialization(compiler.context))
context.updateModule(
module,
{ u =>
@ -428,24 +425,7 @@ final class SerializationManager(private val context: TruffleCompilerContext) {
module.getName,
loadedCache.compilationStage
)
if (!relinkedIrChecks.contains(false)) {
context.updateModule(module, _.hasCrossModuleLinks(true))
context.logSerializationManager(
debugLogLevel,
"Restored links (early phase) in module [{0}].",
module.getName
)
Some(true)
} else {
context.logSerializationManager(
debugLogLevel,
"Could not restore links (early phase) in module [{0}].",
module.getName
)
context.updateModule(module, _.hasCrossModuleLinks(false))
Some(false)
}
Some(true)
case None =>
context.logSerializationManager(
debugLogLevel,

View File

@ -38,7 +38,7 @@ public abstract class CompilerTest {
return ir;
}
static void assertIR(String msg, Module old, Module now) throws IOException {
public static void assertIR(String msg, Module old, Module now) throws IOException {
Function<IR, String> filter = f -> simplifyIR(f, true, true, false);
String ir1 = filter.apply(old);
String ir2 = filter.apply(now);
@ -129,6 +129,15 @@ public abstract class CompilerTest {
int to = txt.indexOf("reason =", at + pref.length());
txt = txt.substring(0, at) + "errors.Syntax (" + txt.substring(to);
}
for (;;) {
final String pref = "List(";
int at = txt.indexOf(pref);
if (at == -1) {
break;
}
int to = at + pref.length();
txt = txt.substring(0, at) + "Seq(" + txt.substring(to);
}
return txt;
}
}

View File

@ -1,8 +1,8 @@
package org.enso.compiler;
import org.enso.compiler.core.ir.Location;
import org.enso.compiler.core.ir.Module;
import org.enso.compiler.core.ir.expression.errors.Syntax;
import org.enso.syntax.text.Location;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;

View File

@ -0,0 +1,58 @@
package org.enso.interpreter.caches;
import org.enso.compiler.CompilerTest;
import org.enso.compiler.core.IR;
import org.enso.interpreter.Constants;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.interpreter.test.TestBase;
import org.enso.interpreter.util.ScalaConversions;
import org.enso.polyglot.CompilationStage;
import org.enso.polyglot.LanguageInfo;
import org.enso.polyglot.MethodNames;
import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.BeforeClass;
public class ModuleCacheTest extends TestBase {
private static Context ctx;
public ModuleCacheTest() {
}
@BeforeClass
public static void initializeContext() throws Exception {
ctx = defaultContextBuilder()
.option(RuntimeOptions.DISABLE_IR_CACHES, "true")
.build();
}
@Test
public void testCompareList() throws Exception {
var ensoCtx = (EnsoContext) ctx.getBindings(LanguageInfo.ID).invokeMember(MethodNames.TopScope.LEAK_CONTEXT).asHostObject();
var name = "Standard.Base.Data.List";
var v = ctx.eval("enso", """
import Standard.Base.Data.List
empty = List.List.Nil
""").invokeMember(MethodNames.Module.EVAL_EXPRESSION, "empty");
assertEquals("List", v.getMetaObject().getMetaSimpleName());
var option = ensoCtx.findModule(name);
assertTrue("Module found", option.isPresent());
var module = option.get();
var ir = module.getIr().duplicate(true, true, true, true);
var cm = new ModuleCache.CachedModule(ir, CompilationStage.AFTER_CODEGEN, module.getSource());
byte[] arr = module.getCache().serialize(ensoCtx, cm);
var meta = new ModuleCache.Metadata("hash", "code", CompilationStage.AFTER_CODEGEN.toString());
var cachedIr = module.getCache().deserialize(ensoCtx, arr, meta, null);
assertNotNull("IR read", cachedIr);
CompilerTest.assertIR(name, ir, cachedIr.moduleIR());
}
}

View File

@ -150,7 +150,7 @@ trait CompilerRunner {
* @return a method containing `ir` as its body
*/
def asMethod: definition.Method = {
definition.Method.Explicit(
new definition.Method.Explicit(
Name.MethodReference(
Some(
Name.Qualified(

View File

@ -31,7 +31,7 @@ class GatherDiagnosticsTest extends CompilerTest {
hasDefaultsSuspended = false,
None
)
val lam = Function.Lambda(
val lam = new Function.Lambda(
List(
DefinitionArgument
.Specified(
@ -94,10 +94,8 @@ class GatherDiagnosticsTest extends CompilerTest {
List(),
None
),
definition.Method
.Explicit(method1Ref, lam, None),
definition.Method
.Explicit(method2Ref, error3, None)
new definition.Method.Explicit(method1Ref, lam, None),
new definition.Method.Explicit(method2Ref, error3, None)
),
false,
None

View File

@ -5,12 +5,12 @@ import org.enso.compiler.core.ir.{
Empty,
Expression,
IdentifiedLocation,
Location,
Name
}
import org.enso.compiler.core.ir.expression.{Application, Operator}
import org.enso.compiler.pass.desugar.OperatorToFunction
import org.enso.compiler.test.CompilerTest
import org.enso.syntax.text.Location
class OperatorToFunctionTest extends CompilerTest {
@ -31,7 +31,7 @@ class OperatorToFunctionTest extends CompilerTest {
left: Expression,
right: Expression
): (Operator.Binary, Application.Prefix) = {
val loc = IdentifiedLocation(Location(1, 33))
val loc = new IdentifiedLocation(new Location(1, 33))
val leftArg = CallArgument.Specified(None, left, left.location)
val rightArg = CallArgument.Specified(None, right, right.location)

View File

@ -226,42 +226,41 @@ class LambdaConsolidateTest extends CompilerTest {
"collapse lambdas with multiple parameters" in {
implicit val inlineContext: InlineContext = mkContext
val ir: Function.Lambda = Function
.Lambda(
val ir: Function.Lambda = new Function.Lambda(
List(
DefinitionArgument
.Specified(
Name
.Literal("a", isMethod = false, None),
None,
None,
suspended = false,
None
),
DefinitionArgument.Specified(
Name.Literal("b", isMethod = false, None),
None,
None,
suspended = false,
None
)
),
new Function.Lambda(
List(
DefinitionArgument
.Specified(
Name
.Literal("a", isMethod = false, None),
None,
None,
suspended = false,
None
),
DefinitionArgument.Specified(
Name.Literal("b", isMethod = false, None),
Name
.Literal("c", isMethod = false, None),
None,
None,
suspended = false,
None
)
),
Function.Lambda(
List(
DefinitionArgument.Specified(
Name
.Literal("c", isMethod = false, None),
None,
None,
suspended = false,
None
)
),
Name.Literal("c", isMethod = false, None),
None
),
Name.Literal("c", isMethod = false, None),
None
)
),
None
)
.runPasses(passManager, inlineContext)
.optimise
.asInstanceOf[Function.Lambda]

View File

@ -1,13 +1,14 @@
package org.enso.compiler.test.semantic
import org.enso.compiler.core.Implicits.AsMetadata
import org.enso.compiler.core.ir.{Module, Warning}
import org.enso.compiler.core.ir.{Module, ProcessingPass, Warning}
import org.enso.compiler.core.ir.expression.errors
import org.enso.compiler.core.ir.module.scope.Import
import org.enso.compiler.data.BindingsMap
import org.enso.compiler.pass.analyse.BindingAnalysis
import org.enso.interpreter.runtime
import org.enso.interpreter.runtime.EnsoContext
import org.enso.persist.Persistance
import org.enso.pkg.QualifiedName
import org.enso.polyglot.{LanguageInfo, MethodNames, RuntimeOptions}
import org.graalvm.polyglot.{Context, Engine}
@ -15,9 +16,10 @@ import org.scalatest.BeforeAndAfter
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import java.io.{ByteArrayOutputStream, ObjectOutputStream}
import java.io.ByteArrayOutputStream
import java.nio.file.Paths
import java.util.logging.Level
import java.io.IOException
/** Tests a single package with multiple modules for import/export resolution.
* Checks whether the exported symbols and defined entities metadata of the modules
@ -908,13 +910,17 @@ class ImportExportTest
.diagnostics
.collect({ case w: Warning.DuplicatedImport => w })
warn.size shouldEqual 1
val baos = new ByteArrayOutputStream()
val stream = new ObjectOutputStream(baos)
mainIr.preorder.foreach(
_.passData.prepareForSerialization(langCtx.getCompiler.context)
)
stream.writeObject(mainIr)
baos.toByteArray should not be empty
val arr = Persistance.write(
mainIr,
{
case metadata: ProcessingPass.Metadata =>
metadata.prepareForSerialization(
langCtx.getCompiler.context.asInstanceOf[metadata.Compiler]
);
case obj => obj
}
);
arr should not be empty
}
"serialize ambiguous import error" in {
@ -940,13 +946,24 @@ class ImportExportTest
.reason
.asInstanceOf[errors.ImportExport.AmbiguousImport]
ambiguousImport.symbolName shouldEqual "A_Type"
val baos = new ByteArrayOutputStream()
val stream = new ObjectOutputStream(baos)
mainIr.preorder.foreach(
_.passData.prepareForSerialization(langCtx.getCompiler.context)
)
stream.writeObject(mainIr)
baos.toByteArray should not be empty
try {
val arr = Persistance.write(
mainIr,
{
case metadata: ProcessingPass.Metadata =>
metadata.prepareForSerialization(
langCtx.getCompiler.context.asInstanceOf[metadata.Compiler]
);
case obj => obj
}
);
fail("Shouldn't return anything when there is an error" + arr)
} catch {
case ex: IOException =>
ex.getMessage should equal(
"No persistance for org.enso.compiler.core.ir.expression.errors.ImportExport"
)
}
}
}

View File

@ -0,0 +1,282 @@
package org.enso.persist.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor9;
import javax.tools.Diagnostic.Kind;
import org.openide.util.lookup.ServiceProvider;
/**
* Processes the {@code Persistable} annotation. See its javadoc for
* proper usage. A new subclass of {@code Persistance} class is generated per each
* {@code Persistable} annotation.
*/
@SupportedAnnotationTypes({
"org.enso.persist.Persistable",
"org.enso.persist.Persistable.Group"
})
@ServiceProvider(service=Processor.class)
public class PersistableProcessor extends AbstractProcessor {
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
var ok = true;
var eu = processingEnv.getElementUtils();
var Persistable = eu.getTypeElement("org.enso.persist.Persistable");
var PersistableGroup = eu.getTypeElement("org.enso.persist.Persistable.Group");
try {
for (var elem : roundEnv.getElementsAnnotatedWith(Persistable)) {
var anno = getAnnotation(elem, Persistable);
ok &= generatePersistance(elem, anno);
}
for (var elem : roundEnv.getElementsAnnotatedWith(PersistableGroup)) {
var group = getAnnotation(elem, PersistableGroup);
for (var anno : readAnnoArray(group, "value")) {
ok &= generatePersistance(elem, anno);
}
}
} catch (IOException e) {
ok = false;
processingEnv.getMessager().printMessage(Kind.ERROR, e.getMessage());
}
return ok;
}
private String findFqn(Element e) {
var inPackage = findNameInPackage(e);
var pkg = processingEnv.getElementUtils().getPackageOf(e);
return pkg.getQualifiedName() + "." + inPackage;
}
private static String findNameInPackage(Element e) {
var sb = new StringBuilder();
while (e != null && !(e instanceof PackageElement)) {
if (!sb.isEmpty()) {
sb.insert(0, ".");
}
sb.insert(0, e.getSimpleName());
e = e.getEnclosingElement();
}
return sb.toString();
}
private boolean generatePersistance(Element orig, AnnotationMirror anno) throws IOException {
var eu = processingEnv.getElementUtils();
var tu = processingEnv.getTypeUtils();
String typeElemName = readAnnoValue(anno, "clazz");
if (typeElemName == null) {
typeElemName = ((TypeElement) orig).getQualifiedName().toString();
}
var typeElem = eu.getTypeElement(typeElemName);
if (typeElem == null) {
processingEnv.getMessager().printMessage(Kind.ERROR, "Cannot find type for " + typeElemName);
return false;
}
var richerConstructor = new Comparator<Object>() {
@Override
public int compare(Object a, Object b) {
var ea = (ExecutableElement)a;
var eb = (ExecutableElement)b;
var diff = eb.getParameters().size() - ea.getParameters().size();
if (diff == 0) {
diff = countSeq(eb.getParameters()) - countSeq(ea.getParameters());
}
return diff;
}
};
var constructors = typeElem.getEnclosedElements().stream()
.filter(
e -> e.getModifiers().contains(Modifier.PUBLIC) && e.getKind() == ElementKind.CONSTRUCTOR
)
.sorted(richerConstructor)
.toList();
if (constructors.isEmpty()) {
processingEnv.getMessager().printMessage(Kind.ERROR, "There should be exactly one constructor in " + typeElem, orig);
return false;
}
var cons = (ExecutableElement) constructors.get(0);
if (constructors.size() > 1) {
var snd = (ExecutableElement) constructors.get(1);
if (richerConstructor.compare(cons, snd) == 0) {
processingEnv.getMessager().printMessage(Kind.ERROR, "There should be exactly one 'richest' constructor in " + typeElem, orig);
return false;
}
}
var pkgName = eu.getPackageOf(orig).getQualifiedName().toString();
var className = "Persist" + findNameInPackage(typeElem).replace(".", "_");
var fo = processingEnv.getFiler().createSourceFile(pkgName + "." + className, orig);
try (var w = fo.openWriter()) {
w.append("package ").append(pkgName).append(";\n");
w.append("import java.io.IOException;\n");
w.append("import org.enso.persist.Persistance;\n");
w.append("@org.openide.util.lookup.ServiceProvider(service=Persistance.class)\n");
w.append("public final class ").append(className).append(" extends Persistance<").append(typeElemName).append("> {\n");
w.append(" public ").append(className).append("() {\n");
var id = readAnnoValue(anno, "id").toString(); // Integer.toString(anno.id());
w.append(" super(").append(typeElemName).append(".class, false, ").append(id).append(");\n");
w.append(" }\n");
w.append(" @SuppressWarnings(\"unchecked\")\n");
w.append(" protected ").append(typeElemName).append(" readObject(Input in) throws IOException {\n");
for (var v : cons.getParameters()) {
if (tu.isSameType(eu.getTypeElement("java.lang.String").asType(), v.asType())) {
w.append(" var ").append(v.getSimpleName()).append(" = in.readUTF();\n");
} else if (!v.asType().getKind().isPrimitive()) {
var type = tu.erasure(v.asType());
var elem = (TypeElement) tu.asElement(type);
var name = findFqn(elem);
if (shouldInline(elem)) {
w.append(" var ").append(v.getSimpleName()).append(" = in.readInline(").append(name).append(".class);\n");
} else {
w.append(" var ").append(v.getSimpleName()).append(" = (").append(name).append(") in.readObject();\n");
}
} else switch (v.asType().getKind()) {
case BOOLEAN ->
w.append(" var ").append(v.getSimpleName()).append(" = in.readBoolean();\n");
case INT ->
w.append(" var ").append(v.getSimpleName()).append(" = in.readInt();\n");
default ->
processingEnv.getMessager().printMessage(Kind.ERROR, "Unsupported primitive type: " + v.asType().getKind());
}
}
w.append(" return new ").append(typeElemName).append("(\n");
w.append(" ");
{
var sep = "";
for (var v : cons.getParameters()) {
w.append(sep);
w.append(v.getSimpleName());
sep = ", ";
}
}
w.append("\n");
w.append(" );\n");
w.append(" }\n");
w.append(" @SuppressWarnings(\"unchecked\")\n");
w.append(" protected void writeObject(").append(typeElemName).append(" obj, Output out) throws IOException {\n");
for (var v : cons.getParameters()) {
if (tu.isSameType(eu.getTypeElement("java.lang.String").asType(), v.asType())) {
w.append(" out.writeUTF(obj.").append(v.getSimpleName()).append("());\n");
} else if (!v.asType().getKind().isPrimitive()) {
var type = tu.erasure(v.asType());
var elem = (TypeElement) tu.asElement(type);
var name = findFqn(elem);
if (shouldInline(elem)) {
w.append(" out.writeInline(").append(name).append(".class, obj.").append(v.getSimpleName()).append("());\n");
} else {
w.append(" out.writeObject(obj.").append(v.getSimpleName()).append("());\n");
}
} else switch (v.asType().getKind()) {
case BOOLEAN ->
w.append(" out.writeBoolean(obj.").append(v.getSimpleName()).append("());\n");
case INT ->
w.append(" out.writeInt(obj.").append(v.getSimpleName()).append("());\n");
default ->
processingEnv.getMessager().printMessage(Kind.ERROR, "Unsupported primitive type: " + v.asType().getKind());
}
}
w.append(" }\n");
w.append("}\n");
}
return true;
}
private int countSeq(List<? extends VariableElement> parameters) {
var tu = processingEnv.getTypeUtils();
var cnt = 0;
for (var p : parameters) {
var type = tu.asElement(tu.erasure(p.asType()));
if (type != null && type.getSimpleName().toString().equals("Seq")) {
cnt++;
}
}
return cnt;
}
private boolean shouldInline(TypeElement elem) {
var inline = switch (findFqn(elem)) {
case "scala.collection.immutable.Seq" -> true;
default -> false;
} || !elem.getKind().isInterface();
return inline;
}
private AnnotationMirror getAnnotation(Element elem, TypeElement annoType) {
var tu = processingEnv.getTypeUtils();
for (var m : elem.getAnnotationMirrors()) {
var realType = m.getAnnotationType();
if (tu.isSameType(realType, annoType.asType())) {
return m;
}
}
return null;
}
private String readAnnoValue(AnnotationMirror mirror, String name) {
for (var entry : mirror.getElementValues().entrySet()) {
if (name.equals(entry.getKey().getSimpleName().toString())) {
return entry.getValue().accept(new SimpleAnnotationValueVisitor9<String, Object>() {
@Override
public String visitInt(int i, Object p) {
return Integer.toString(i);
}
@Override
public String visitType(TypeMirror t, Object p) {
var e = (TypeElement) processingEnv.getTypeUtils().asElement(t);
return e.getQualifiedName().toString();
}
}, null);
}
}
return null;
}
private List<AnnotationMirror> readAnnoArray(AnnotationMirror mirror, String name) {
for (var entry : mirror.getElementValues().entrySet()) {
if (name.equals(entry.getKey().getSimpleName().toString())) {
return entry.getValue().accept(new SimpleAnnotationValueVisitor9<List<AnnotationMirror>, List<AnnotationMirror>>() {
@Override
public List<AnnotationMirror> visitArray(List<? extends AnnotationValue> vals, List<AnnotationMirror> p) {
for (var v : vals) {
v.accept(this, p);
}
return p;
}
@Override
public List<AnnotationMirror> visitAnnotation(AnnotationMirror a, List<AnnotationMirror> p) {
p.add(a);
return p;
}
}, new ArrayList<>());
}
}
throw new IllegalArgumentException();
}
}

View File

@ -0,0 +1,38 @@
package org.enso.persist;
import org.enso.persist.PerInputImpl.InputCache;
import org.enso.persist.Persistance.Reference;
final class PerBufferReference<T> extends Persistance.Reference<T> {
private final Persistance<T> p;
private final PerInputImpl.InputCache cache;
private final int offset;
private PerBufferReference(Persistance<T> p, PerInputImpl.InputCache buffer, int offset) {
this.p = p;
this.cache = buffer;
this.offset = offset;
}
@SuppressWarnings(value = "unchecked")
final <T> T readObject(Class<T> clazz) {
if (p != null) {
if (clazz.isAssignableFrom(p.clazz)) {
clazz = (Class) p.clazz;
} else {
throw new ClassCastException();
}
}
org.enso.persist.PerInputImpl in = new PerInputImpl(cache, offset);
T obj = in.readInline(clazz);
return obj;
}
static <V> Reference<V> from(InputCache buffer, int offset) {
return from(null, buffer, offset);
}
static <V> Reference<V> from(Persistance<V> p, InputCache buffer, int offset) {
return new PerBufferReference<>(p, buffer, offset);
}
}

View File

@ -0,0 +1,107 @@
package org.enso.persist;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.Function;
final class PerGenerator {
static final byte[] HEADER = new byte[] {0x0a, 0x0d, 0x02, 0x0f};
private final OutputStream main;
private final Map<Object, Integer> knownObjects = new IdentityHashMap<>();
final Function<Object, Object> writeReplace;
private int position;
private PerGenerator(OutputStream out, int position, Function<Object, Object> writeReplace) {
this.main = out;
this.writeReplace = writeReplace == null ? Function.identity() : writeReplace;
this.position = position;
}
static byte[] writeObject(Object obj, Function<Object, Object> writeReplace) throws IOException {
var out = new ByteArrayOutputStream();
var data = new DataOutputStream(out);
data.write(PerGenerator.HEADER);
data.writeInt(PerMap.DEFAULT.versionStamp);
data.write(new byte[4]); // space
data.flush();
var g = new PerGenerator(out, 12, writeReplace);
var at = g.writeObject(obj);
var arr = out.toByteArray();
arr[8] = (byte) ((at >> 24) & 0xff);
arr[9] = (byte) ((at >> 16) & 0xff);
arr[10] = (byte) ((at >> 8) & 0xff);
arr[11] = (byte) (at & 0xff);
return arr;
}
final <T> int writeObject(T t) throws IOException {
if (t == null) {
return -1;
}
java.lang.Object obj = writeReplace.apply(t);
java.lang.Integer found = knownObjects.get(obj);
if (found == null) {
org.enso.persist.Persistance<?> p = PerMap.DEFAULT.forType(obj.getClass());
java.io.ByteArrayOutputStream os = new ByteArrayOutputStream();
p.writeInline(obj, new ReferenceOutput(this, os));
found = this.position;
byte[] arr = os.toByteArray();
main.write(arr);
this.position += arr.length;
knownObjects.put(obj, found);
}
return found;
}
final void writeIndirect(Object obj, Persistance.Output out) throws IOException {
if (obj == null) {
out.writeInt(-1);
return;
}
obj = writeReplace.apply(obj);
org.enso.persist.Persistance<?> p = PerMap.DEFAULT.forType(obj.getClass());
java.lang.Integer found = knownObjects.get(obj);
if (found == null) {
var os = new ByteArrayOutputStream();
var osData = new ReferenceOutput(this, os);
p.writeInline(obj, osData);
found = position;
if (os.size() == 0) {
os.write(0);
}
byte[] arr = os.toByteArray();
main.write(arr);
position += arr.length;
knownObjects.put(obj, found);
}
out.writeInt(found);
out.writeInt(p.id);
}
private static final class ReferenceOutput extends DataOutputStream
implements Persistance.Output {
private final PerGenerator generator;
ReferenceOutput(PerGenerator g, ByteArrayOutputStream out) {
super(out);
this.generator = g;
}
@Override
public <T> void writeInline(Class<T> clazz, T t) throws IOException {
var obj = generator.writeReplace.apply(t);
var p = PerMap.DEFAULT.forType(clazz);
p.writeInline(obj, this);
}
@Override
public void writeObject(Object obj) throws IOException {
this.generator.writeIndirect(obj, this);
}
}
}

View File

@ -0,0 +1,230 @@
package org.enso.persist;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import static org.enso.persist.PerUtils.raise;
import org.enso.persist.Persistance.Input;
import org.enso.persist.Persistance.Reference;
final class PerInputImpl implements Input {
private final InputCache cache;
private final ByteBuffer buf;
private int at;
PerInputImpl(InputCache cache, int at) {
this.cache = cache;
this.buf = cache.buf();
this.at = at;
}
static <T> Reference<T> readObject(byte[] arr, Function<Object, Object> readResolve) throws IOException {
for (var i = 0; i < PerGenerator.HEADER.length; i++) {
if (arr[i] != PerGenerator.HEADER[i]) {
throw new IOException("Wrong header");
}
}
var buf = ByteBuffer.wrap(arr);
var version = buf.getInt(4);
if (version != PerMap.DEFAULT.versionStamp) {
throw new IOException("Incompatible version " + version + " != " + PerMap.DEFAULT.versionStamp);
}
var at = buf.getInt(8);
var cache = new InputCache(buf, readResolve);
return PerBufferReference.from(cache, at);
}
@Override
public <T> T readInline(Class<T> clazz) {
Persistance<T> p = PerMap.DEFAULT.forType(clazz);
T res = p.readWith(this);
var resolve = cache.readResolve().apply(res);
return clazz.cast(resolve);
}
@Override
public Object readObject() throws IOException {
var obj = readIndirect(cache, PerMap.DEFAULT, this);
return obj;
}
@Override
public <T> Persistance.Reference<T> readReference(Class<T> clazz) throws IOException {
var obj = readIndirectAsReference(cache, PerMap.DEFAULT, this, clazz);
return obj;
}
public int read() throws IOException {
return buf.get(at++);
}
public int read(byte[] b) throws IOException {
buf.get(at, b);
at += b.length;
return b.length;
}
public int read(byte[] b, int off, int len) throws IOException {
buf.get(at, b, off, len);
at += len;
return len;
}
public long skip(long n) throws IOException {
at += Math.toIntExact(n);
return at;
}
public int available() throws IOException {
return buf.limit() - at;
}
public void close() throws IOException {
}
@Override
public void readFully(byte[] b) throws IOException {
read(b);
}
@Override
public void readFully(byte[] b, int off, int len) throws IOException {
read(b, off, len);
}
@Override
public int skipBytes(int n) throws IOException {
at += n;
return at;
}
@Override
public boolean readBoolean() throws IOException {
int b = read();
return b != 0;
}
@Override
public byte readByte() throws IOException {
return buf.get(at++);
}
@Override
public int readUnsignedByte() throws IOException {
byte b = readByte();
return b >= 0 ? b : b + 256;
}
@Override
public short readShort() throws IOException {
short s = buf.getShort(at);
at += 2;
return s;
}
@Override
public int readUnsignedShort() throws IOException {
short s = readShort();
return s >= 0 ? s : s + 256 * 256;
}
@Override
public char readChar() throws IOException {
return (char) readShort();
}
@Override
public int readInt() throws IOException {
int i = buf.getInt(at);
at += 4;
return i;
}
@Override
public long readLong() throws IOException {
long l = buf.getLong(at);
at += 8;
return l;
}
@Override
public float readFloat() throws IOException {
float f = buf.getFloat(at);
at += 4;
return f;
}
@Override
public double readDouble() throws IOException {
double d = buf.getDouble(at);
at += 8;
return d;
}
@Override
public String readLine() throws IOException {
throw new IOException("No readLine in buffer");
}
@Override
public String readUTF() throws IOException {
return DataInputStream.readUTF(this);
}
@Override
public String toString() {
return "Input[at=" + at + " of " + this.buf.limit() + "]";
}
static Object readIndirect(InputCache cache, PerMap map, Input in) throws IOException {
var at = in.readInt();
if (at < 0) {
return null;
}
var id = in.readInt();
var p = map.forId(id);
if (cache.cache().get(at) instanceof Object res) {
return p.clazz.cast(res);
}
var inData = new PerInputImpl(cache, at);
var res = p.readWith(inData);
res = cache.readResolve().apply(res);
var prev = cache.cache().put(at, res);
if (prev != null) {
throw raise(RuntimeException.class, new IOException("Adding at " + at + " object: " + res.getClass().getName() + " but there already is " + prev.getClass().getName()));
}
return res;
}
@SuppressWarnings("unchecked")
static <T> Reference<T> readIndirectAsReference(InputCache buffer, PerMap map, Input in, Class<T> clazz) throws IOException {
var at = in.readInt();
if (at < 0) {
return null;
}
var id = in.readInt();
var p = map.forId(id);
if (clazz.isAssignableFrom(p.clazz)) {
return PerBufferReference.from((Persistance<T>) p, buffer, at);
} else {
throw new IOException("Expecting " + clazz.getName() + " but found " + p.clazz.getName());
}
}
static final record InputCache(
ByteBuffer buf,
Function<Object, Object> readResolve,
Map<Integer, Object> cache
) {
InputCache(ByteBuffer buf, Function<Object, Object> readResolve) {
this(buf, readResolve == null ? Function.identity() : readResolve, new HashMap<>());
}
}
}

View File

@ -0,0 +1,85 @@
package org.enso.persist;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.openide.util.lookup.Lookups;
final class PerMap {
static final PerMap DEFAULT = new PerMap();
private final Map<Integer, Persistance<?>> ids = new HashMap<>();
private final Map<Class<?>, Persistance<?>> types = new HashMap<>();
final int versionStamp;
private PerMap() {
int hash = 0;
var loader = getClass().getClassLoader();
for (var p : Lookups.metaInfServices(loader).lookupAll(Persistance.class)) {
org.enso.persist.Persistance<?> prevId = ids.put(p.id, p);
if (prevId != null) {
throw new IllegalStateException(
"Multiple registrations for ID " + p.id + " " + prevId + " != " + p);
}
hash += p.id;
org.enso.persist.Persistance<?> prevType = types.put(p.clazz, p);
if (prevType != null) {
throw new IllegalStateException(
"Multiple registrations for " + p.clazz.getName() + " " + prevId + " != " + p);
}
}
versionStamp = hash;
}
@SuppressWarnings(value = "unchecked")
private synchronized <T> Persistance<T> searchSupertype(String name, Class<T> type) {
// synchronized as it mutes the types map
// however over time the types map gets saturated and
// the synchronization will get less frequent
// please note that Persistance as well as Class (as a key) have all fields final =>
// as soon as they become visible from other threads, they have to look consistent
NOT_FOUND:
if (type != null) {
org.enso.persist.Persistance<?> p = types.get(type);
if (p != null) {
if (!p.includingSubclasses) {
break NOT_FOUND;
}
} else {
for (java.lang.Class<?> in : type.getInterfaces()) {
p = searchSupertype(name, in);
if (p != null) {
break;
}
}
if (p == null && !type.isInterface()) {
p = searchSupertype(name, type.getSuperclass());
}
types.put(type, p);
}
return (Persistance<T>) p;
}
if (type == null) {
throw PerUtils.raise(RuntimeException.class, new IOException("No persistance for " + name));
} else {
return null;
}
}
@SuppressWarnings(value = "unchecked")
final <T> Persistance<T> forType(Class<T> type) {
org.enso.persist.Persistance<?> p = types.get(type);
if (p == null) {
p = searchSupertype(type.getName(), type);
}
return (Persistance<T>) p;
}
final Persistance<?> forId(int id) {
org.enso.persist.Persistance<?> p = ids.get(id);
if (p == null) {
throw PerUtils.raise(RuntimeException.class, new IOException("No persistance for " + id));
}
return p;
}
}

View File

@ -0,0 +1,14 @@
package org.enso.persist;
final class PerMemoryReference<T> extends Persistance.Reference<T> {
static final Persistance.Reference<?> NULL = new PerMemoryReference<>(null);
private final T value;
PerMemoryReference(T obj) {
this.value = obj;
}
final T value() {
return value;
}
}

View File

@ -0,0 +1,10 @@
package org.enso.persist;
final class PerUtils {
private PerUtils() {}
@SuppressWarnings("unchecked")
static <E extends Throwable> E raise(Class<E> clazz, Throwable t) throws E {
throw (E) t;
}
}

View File

@ -0,0 +1,51 @@
package org.enso.persist;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Target;
/**
* Annotation for an automatic persistance of a class. Use to generate implementation and
* registration of {@link Persistance} subclass to read and write simple records and case classes:
*
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="annotation"}
*/
@Target(ElementType.TYPE)
@Repeatable(Persistable.Group.class)
public @interface Persistable {
/**
* The class to generate {@link Persistance} for. If the value is omitted then the code is
* generated for the class that is annotated by this annotation. Example of multiple
* {@code @Persistable} annotations on a single element.
*
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="annotation"}
*
* <p>Example of self annotated class:
*
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="self-annotation"}
*
* @return the class to generate read/write persistance code for
*/
Class<?> clazz() default Object.class;
/**
* ID of the class. Each registered {@link Persistance} sub class must have a unique ID. This
* attribute specifies it for the generated code. When serialization format changes (for example
* by changing the number or type of constructor arguments), change also the ID.
*
* @return unique ID for the persisted {@link #clazz()}
*/
int id();
/** Multiple {@link Persistable} annotations. */
@Target(ElementType.TYPE)
public @interface Group {
/**
* The array of {@link Persistable} annotations.
*
* @return the array of annotations.
*/
Persistable[] value();
}
}

View File

@ -0,0 +1,220 @@
package org.enso.persist;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.function.Function;
import static org.enso.persist.PerUtils.raise;
/** Central persistance class. Use static {@link
* Persistance#write write} method to turn a graph of JVM objects into a {@code byte[]}.
*
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="write"}
*
* <p>Use sibling static {@link Persistance#read readO} method to read the byte buffer back into
* their memory representation.
*
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="read"}
*
* <h2>Manual Persistance</h2>
*
* Unlike typical Java serialization (which tries to make things automatic), this framework requires
* one to implement the persistance manually. For each class that one wants to support, one has to
* implement subclass {@link Persistance} and implement its {@link Persistance#writeObject} and
* {@link Persistance#readObject} method.
*
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="manual"}
*
* There is a semi-automatic way to generate such subclasses of {@link Persistance} via
* the {@link Persistable @Persistable} annotation.
* @param <T>
*/
public abstract class Persistance<T> {
final Class<T> clazz;
final boolean includingSubclasses;
final int id;
/**
* Constructor for subclasses to register persistance for certain
* {@code clazz}. Sample registration:
*
* <p>
* {@snippet file="org/enso/persist/PersistanceTest.java" region="manual"}
*
* Each persistance requires unique ID. A stream created by
* {@link #write(Object, Function<Object, Object>)} and read by
* {@link #read(byte[], Function<Object, Object>)} contains a header derived
* from the all the IDs present in the system. When versioning the protocol
* and implementation:
* <ul>
* <li>when you change something really core in the Persitance itself -
* change the header</li>
* <li>when you add or remove a Persistance implementation the version
* changes (computed from all the IDs)</li>
* <li>when you change format of some Persitance.writeObject method - change
* its ID</li>
* </ul>
*
* @param clazz the class persistance is written for
* @param includingSubclasses should the persistance also apply to
* subclasses
* @param id unique ID
*/
protected Persistance(Class<T> clazz, boolean includingSubclasses, int id) {
this.clazz = clazz;
this.includingSubclasses = includingSubclasses;
this.id = id;
}
protected abstract void writeObject(T obj, Output out) throws IOException;
protected abstract T readObject(Input in) throws IOException, ClassNotFoundException;
/** Prints the {@code clazz} and {@code id} values.
*/
@Override
public final String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Persistance{");
sb.append("clazz=").append(clazz.getName());
sb.append(", id=").append(id);
sb.append('}');
return sb.toString();
}
/** Extended output interface for {@link #writeObject(Object)} method.
*/
public static interface Output extends DataOutput {
/** Writes an object "inline" - as a value.
*
* @param <T> the type of the class
* @param clazz the class to use to locate {@link Persistance} implementation
* @param obj the object to write down
* @throws IOException when an I/O problem happens
*/
public abstract <T> void writeInline(Class<T> clazz, T obj) throws IOException;
/** Writes an object as a "reference". Objects written by by this method
* are shared - each {@code obj} is stored only once and referenced from
* all the locations it is used.
*
* @param obj the object to write down
* @throws IOException when an I/O problem happens
*/
public abstract void writeObject(Object obj) throws IOException;
}
/** Extended input interface for the {@link #writeObject(T, Output)} method.
*/
public static interface Input extends DataInput {
/** Reads objects written down by {@link Output#writeInline}.
*
* @param <T> the type to read
* @param clazz class that identifies {@link Persistance} to use for reading
* @return the read in object
* @throws IOException when an I/O problem happens
*/
public abstract <T> T readInline(Class<T> clazz) throws IOException;
/** Reads a reference to object written down by {@link Output#writeObject(Object)}.
*
* @return the read in object
* @throws IOException when an I/O problem happens
*/
public abstract Object readObject() throws IOException;
/** * Reads a reference to an object written down by {@link Output#writeObject(Object)}
* but without reading the object itself. The object can then be obtained
* <em>"later"</em> via the {@link Reference#get(Class)} method.
*
* @param <T> the type to read
* @param clazz the expected type of the object to read
* @return a reference allowing to read the object later
* @throws IOException when an I/O problem happens
*/
public <T> Reference<T> readReference(Class<T> clazz) throws IOException;
}
final void writeInline(Object obj, Output out) throws IOException {
writeObject(clazz.cast(obj), out);
}
final T readWith(Input in) {
try {
return readObject(in);
} catch (IOException | ClassNotFoundException ex) {
throw raise(RuntimeException.class, ex);
}
}
/** Read object written down by {@link #write} from an array.
*
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="read"}
*
* @param <T> expected type of object
* @param arr the stored bytes
* @param readResolve either {@code null} or function to call for each object being stored to provide a replacement
* @return the read object
* @throws java.io.IOException when an I/O problem happens
*/
public static <T> Reference<T> read(byte[] arr, Function<Object, Object> readResolve) throws IOException {
return PerInputImpl.readObject(arr, readResolve);
}
/** Writes down an object into an array of bytes. Use {@link #read} method to convert the
* array back to object.
*
* @param obj the object to persist
* @param writeReplace {@code null} or a function that allows to convert each object before storing it down
* @return the array of bytes
* @throws IOException when an I/O problem happens
*/
public static byte[] write(Object obj, Function<Object, Object> writeReplace) throws IOException {
return PerGenerator.writeObject(obj, writeReplace);
}
/** Reference to an object. Either created directly or obtained from {@link Input#readReference}
* method.
*
* @see Input#readReference
*/
public static sealed abstract class Reference<T> permits PerBufferReference, PerMemoryReference {
Reference() {
}
/** Reference to {@code null} object.
* @param <T> the type of the reference
* @return reference to {@code null}
*/
@SuppressWarnings("unchecked")
public static final <T> Reference<T> none() {
return (Reference<T>) PerMemoryReference.NULL;
}
/** Extract object from the reference. Multiple calls to this method
* may return the same or another instance of object depending on the
* type of the reference and <em>laziness policy</em>.
*
* @param <V> the type of the object to expect
* @param expectedType the expected clazz of the object
* @return the referenced object
* @throws ClassCastException if the object isn't of the expected type
*/
public <V> V get(Class<V> expectedType) {
var value = switch (this) {
case PerMemoryReference m -> m.value();
case PerBufferReference<T> b -> b.readObject(expectedType);
};
return expectedType.cast(value);
}
/** Creates a reference to existing object.
*
* @param <V> the type of the object
* @param obj the object to "reference"
* @return reference pointing to the provided object
*/
public static <V> Reference<V> of(V obj) {
return new PerMemoryReference<>(obj);
}
}
}

View File

@ -0,0 +1,37 @@
/**
* Framework for persisting Java objects and reading them <em>"lazily"</em>. Use static {@link
* Persistance#write write} method to turn a graph of JVM objects into a {@code byte[]}.
*
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="write"}
*
* <p>Use sibling static {@link Persistance#read readO} method to read the byte buffer back into
* their memory representation.
*
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="read"}
*
* <h2>Laziness</h2>
*
* The major benefit of this framework is the ability to read only the part of the stored graph that
* is actually needed. One may {@linkplain Persistance.Input#readReference obtain a reference} to an
* object and turn that {@link Persistance.Reference} into actual object <b>later</b>, when needed.
*
* <h2>Manual Persistance</h2>
*
* Unlike typical Java serialization (which tries to make things automatic), this framework requires
* one to implement the persistance manually. For each class that one wants to support, one has to
* implement subclass {@link Persistance} and implement its {@link Persistance#writeObject} and
* {@link Persistance#readObject} method.
*
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="manual"}
*
* <h2>Semi-automatic Persistance</h2>
*
* Writing serialization and deserialization implementation manually is a boring, tedious and
* errorprone process. That's why there is a {@link Persistable} annotation and associated
* annotation processor that generates the necessary {@link Persistance} subclass based on the
* "richest" constructor in the class to be persisted. This approach seems to work well for Java
* records and Scala case classes.
*
* <p>{@snippet file="org/enso/persist/PersistanceTest.java" region="annotation"}
*/
package org.enso.persist;

View File

@ -0,0 +1,167 @@
package org.enso.compiler.core;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import org.enso.persist.Persistable;
import org.enso.persist.Persistance;
import org.junit.Test;
import org.openide.util.lookup.ServiceProvider;
public class PersistanceTest {
@Test
public void testUUIDPersistance() throws Exception {
// @start region="write"
var obj = UUID.randomUUID();
var buffer = Persistance.write(obj, null);
assertNotNull("Byte array is returned", buffer);
assertNotEquals("It has non-zero length", 0, buffer.length);
// @end region="write"
// @start region="read"
var ref = Persistance.read(buffer, null);
var loaded = ref.get(UUID.class);
assertEquals("The same object was recreated", obj, loaded);
// @end region="read"
}
@Test
public void readResolve() throws Exception {
var in = new Service(5);
var arr = Persistance.write(in, (Function<Object, Object>) null);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Remains five", 5, plain.get(Service.class).value());
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
assertEquals("Multiplied on read", 15, multiOnRead.get(Service.class).value());
}
@Test
public void writeReplace() throws Exception {
var in = new Service(5);
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Multiplied on write", 15, plain.get(Service.class).value());
}
@Test
public void readResolveInline() throws Exception {
var in = new ServiceSupply(new Service(5));
var arr = Persistance.write(in, (Function<Object, Object>) null);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Remains five", 5, plain.get(ServiceSupply.class).supply().value());
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
assertEquals("Multiplied on read", 15, multiOnRead.get(ServiceSupply.class).supply().value());
}
@Test
public void writeReplaceInline() throws Exception {
var in = new ServiceSupply(new Service(5));
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Multiplied on write", 15, plain.get(ServiceSupply.class).supply().value());
}
@Test
public void readResolveReference() throws Exception {
var in = new IntegerSupply(new Service(5));
var arr = Persistance.write(in, (Function<Object, Object>) null);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Remains five", 5, (int) plain.get(IntegerSupply.class).supply().get());
assertEquals("Remains five 2", 5, (int) plain.get(IntegerSupply.class).supply().get());
var multiOnRead = Persistance.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
assertEquals("Multiplied on read", 15, (int) multiOnRead.get(IntegerSupply.class).supply().get());
}
@Test
public void writeReplaceReference() throws Exception {
var in = new IntegerSupply(new Service(5));
var arr = Persistance.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj);
var plain = Persistance.read(arr, (Function<Object, Object>) null);
assertEquals("Multiplied on write", 15, (int) plain.get(IntegerSupply.class).supply().get());
}
private static <T> T serde(Class<T> clazz, T l, int expectedSize) throws IOException {
var arr = Persistance.write(l, (Function<Object, Object>) null);
if (expectedSize >= 0) {
assertEquals(expectedSize, arr.length - 12);
}
var ref = Persistance.read(arr, (Function<Object, Object>) null);
return ref.get(clazz);
}
// @start region="manual"
@ServiceProvider(service = Persistance.class)
public static final class PersistUUID extends Persistance<UUID> {
public PersistUUID() {
super(UUID.class, false, 328439);
}
@Override
protected void writeObject(UUID obj, Output out) throws IOException {
out.writeLong(obj.getMostSignificantBits());
out.writeLong(obj.getLeastSignificantBits());
}
@Override
protected UUID readObject(Input in) throws IOException, ClassNotFoundException {
var most = in.readLong();
var least = in.readLong();
return new UUID(most, least);
}
}
// @end region="manual"
public static final class Singleton {
public static final Singleton INSTANCE = new Singleton();
private Singleton() {}
}
@ServiceProvider(service = Persistance.class)
public static final class PersistSingleton extends Persistance<Singleton> {
public PersistSingleton() {
super(Singleton.class, false, 432433);
}
@Override
protected void writeObject(Singleton obj, Output out) throws IOException {}
@Override
protected Singleton readObject(Input in) throws IOException, ClassNotFoundException {
return Singleton.INSTANCE;
}
}
// @start region="annotation"
@Persistable(clazz=Service.class, id=432434)
@Persistable(clazz=IntegerSupply.class, id=432435)
public record Service(int value) implements Supplier<Integer> {
@Override
public Integer get() {
return value;
}
}
public record IntegerSupply(Supplier<Integer> supply) {}
// @end region="annotation"
// @start region="self-annotation"
@Persistable(id=432436)
public record ServiceSupply(Service supply) {}
// @end region="self-annotation"
}

View File

@ -1,9 +1,9 @@
package org.enso.refactoring
import org.enso.syntax.text.Location
import org.enso.text.editing.model
import org.enso.text.editing.{IndexedSource, TextEditor}
import org.enso.text.editing.model.TextEdit
import org.enso.compiler.core.ir.Location
object RenameUtils {

View File

@ -1,6 +1,6 @@
package org.enso.refactoring;
import org.enso.syntax.text.Location;
import org.enso.compiler.core.ir.Location;
import org.enso.text.buffer.Rope;
import org.enso.text.editing.IndexedSource;
import org.enso.text.editing.IndexedSource$;
@ -11,6 +11,7 @@ import org.enso.text.editing.TextEditor;
import org.enso.text.editing.model;
import org.junit.Assert;
import org.junit.Test;
import scala.collection.immutable.Seq;
import scala.collection.immutable.VectorBuilder;
import scala.util.Either;

View File

@ -1,15 +0,0 @@
package org.enso.syntax.text
////////////////////////////////////////////////////////////////////////////////
//// AbsolutePosition //////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
/** Represents an expression's absolute positioning in a source file.
* @param start the inclusive, 0-indexed position of the beginning
* of the expression
* @param end the exclusive, 0-indexed position of the end of
* the expression
*/
case class Location(start: Int, end: Int) {
def length: Int = end - start
}

View File

@ -1,2 +1,2 @@
/FasterXML/jackson-dataformats-binary/blob/2.16/LICENSE
#license
/FasterXML/jackson-dataformats-binary/blob/2.17/LICENSE