Custom type conversion to double from large long (#4099)

When a large long would be passed to a host call expecting a double, it would crash with a
```
Cannot convert '<some long>'(language: Java, type: java.lang.Long) to Java type 'double': Invalid or lossy primitive coercion
```

That is unlikely to be expected by users. It also came up in the Statistics examples during Sum. One could workaround it by forcing the conversion manually with `.to_decimal` but it is not a permanent solution.

Instead this change adds a custom type mapping from Long to Double that will do it behind the scenes with no user interaction. The mapping kicks in only for really large longs.

# Important Notes
Note that the _safe_ range is hardcoded in Truffle and it is not accessible in enso packages. Therefore a simple c&p for that max safe long value was necessary.
This commit is contained in:
Hubert Plociniczak 2023-01-31 16:13:00 +01:00 committed by GitHub
parent 945aa2b2d7
commit d3b350f460
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 3 deletions

View File

@ -547,6 +547,7 @@
- [Resolve Fully Qualified Names][4056] - [Resolve Fully Qualified Names][4056]
- [Optimize Atom storage layouts][3862] - [Optimize Atom storage layouts][3862]
- [Make instance methods callable like statics for builtin types][4077] - [Make instance methods callable like statics for builtin types][4077]
- [Convert large longs to doubles, safely, for host calls][4099]
[3227]: https://github.com/enso-org/enso/pull/3227 [3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248 [3248]: https://github.com/enso-org/enso/pull/3248
@ -636,6 +637,7 @@
[4049]: https://github.com/enso-org/enso/pull/4049 [4049]: https://github.com/enso-org/enso/pull/4049
[4056]: https://github.com/enso-org/enso/pull/4056 [4056]: https://github.com/enso-org/enso/pull/4056
[4077]: https://github.com/enso-org/enso/pull/4077 [4077]: https://github.com/enso-org/enso/pull/4077
[4099]: https://github.com/enso-org/enso/pull/4099
# Enso 2.0.0-alpha.18 (2021-10-12) # Enso 2.0.0-alpha.18 (2021-10-12)

View File

@ -43,7 +43,7 @@ import org.enso.librarymanager.published.PublishedLibraryCache
import org.enso.lockmanager.server.LockManagerService import org.enso.lockmanager.server.LockManagerService
import org.enso.logger.masking.Masking import org.enso.logger.masking.Masking
import org.enso.loggingservice.{JavaLoggingLogHandler, LogLevel} import org.enso.loggingservice.{JavaLoggingLogHandler, LogLevel}
import org.enso.polyglot.{RuntimeOptions, RuntimeServerInfo} import org.enso.polyglot.{HostAccessFactory, RuntimeOptions, RuntimeServerInfo}
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo} import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo}
import org.enso.text.{ContentBasedVersioning, Sha3_224VersionCalculator} import org.enso.text.{ContentBasedVersioning, Sha3_224VersionCalculator}
import org.graalvm.polyglot.Context import org.graalvm.polyglot.Context
@ -270,6 +270,7 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
val context = Context val context = Context
.newBuilder() .newBuilder()
.allowAllAccess(true) .allowAllAccess(true)
.allowHostAccess(new HostAccessFactory().allWithTypeMapping())
.allowExperimentalOptions(true) .allowExperimentalOptions(true)
.option(RuntimeServerInfo.ENABLE_OPTION, "true") .option(RuntimeServerInfo.ENABLE_OPTION, "true")
.option(RuntimeOptions.INTERACTIVE_MODE, "true") .option(RuntimeOptions.INTERACTIVE_MODE, "true")

View File

@ -5,7 +5,7 @@ import org.enso.libraryupload.DependencyExtractor
import org.enso.loggingservice.{JavaLoggingLogHandler, LogLevel} import org.enso.loggingservice.{JavaLoggingLogHandler, LogLevel}
import org.enso.pkg.Package import org.enso.pkg.Package
import org.enso.pkg.SourceFile import org.enso.pkg.SourceFile
import org.enso.polyglot.{PolyglotContext, RuntimeOptions} import org.enso.polyglot.{HostAccessFactory, PolyglotContext, RuntimeOptions}
import org.graalvm.polyglot.Context import org.graalvm.polyglot.Context
import java.io.File import java.io.File
@ -55,6 +55,7 @@ class CompilerBasedDependencyExtractor(logLevel: LogLevel)
.newBuilder() .newBuilder()
.allowExperimentalOptions(true) .allowExperimentalOptions(true)
.allowAllAccess(true) .allowAllAccess(true)
.allowHostAccess(new HostAccessFactory().allWithTypeMapping())
.option(RuntimeOptions.PROJECT_ROOT, pkg.root.getCanonicalPath) .option(RuntimeOptions.PROJECT_ROOT, pkg.root.getCanonicalPath)
.option("js.foreign-object-prototype", "true") .option("js.foreign-object-prototype", "true")
.option( .option(

View File

@ -0,0 +1,35 @@
package org.enso.polyglot;
import org.graalvm.polyglot.HostAccess;
/** Utility class for creating HostAccess object. */
public class HostAccessFactory {
// Copied from com.oracle.truffle.api.interop.NumberUtils
// since the latter is inaccessible
private static final long LONG_MAX_SAFE_DOUBLE = 9007199254740991L; // 2 ** 53 - 1
public HostAccess allWithTypeMapping() {
return HostAccess.newBuilder()
.allowPublicAccess(true)
.allowAllImplementations(true)
.allowAllClassImplementations(true)
.allowArrayAccess(true)
.allowListAccess(true)
.allowBufferAccess(true)
.allowIterableAccess(true)
.allowIteratorAccess(true)
.allowMapAccess(true)
.allowAccessInheritance(true)
.targetTypeMapping(
Long.class,
Double.class,
(v) -> v != null && !longInSafeDoubleRange(v, LONG_MAX_SAFE_DOUBLE),
(v) -> Double.longBitsToDouble(v))
.build();
}
private boolean longInSafeDoubleRange(Long v, Long max) {
return v >= -max && v <= max;
}
}

View File

@ -5,7 +5,7 @@ import org.enso.polyglot.debugger.{
DebugServerInfo, DebugServerInfo,
DebuggerSessionManagerEndpoint DebuggerSessionManagerEndpoint
} }
import org.enso.polyglot.{PolyglotContext, RuntimeOptions} import org.enso.polyglot.{HostAccessFactory, PolyglotContext, RuntimeOptions}
import org.graalvm.polyglot.Context import org.graalvm.polyglot.Context
import java.io.{InputStream, OutputStream} import java.io.{InputStream, OutputStream}
@ -46,6 +46,7 @@ class ContextFactory {
.newBuilder() .newBuilder()
.allowExperimentalOptions(true) .allowExperimentalOptions(true)
.allowAllAccess(true) .allowAllAccess(true)
.allowHostAccess(new HostAccessFactory().allWithTypeMapping())
.option(RuntimeOptions.PROJECT_ROOT, projectRoot) .option(RuntimeOptions.PROJECT_ROOT, projectRoot)
.option(RuntimeOptions.STRICT_ERRORS, strictErrors.toString) .option(RuntimeOptions.STRICT_ERRORS, strictErrors.toString)
.option(RuntimeOptions.WAIT_FOR_PENDING_SERIALIZATION_JOBS, "true") .option(RuntimeOptions.WAIT_FOR_PENDING_SERIALIZATION_JOBS, "true")

View File

@ -12,6 +12,7 @@ polyglot java import java.lang.StringBuilder as Java_String_Builder
polyglot java import java.util.ArrayList polyglot java import java.util.ArrayList
polyglot java import java.time.LocalDate polyglot java import java.time.LocalDate
polyglot java import java.time.LocalTime polyglot java import java.time.LocalTime
polyglot java import org.enso.base.statistics.Moments
Any.test_me self x = x.is_nothing Any.test_me self x = x.is_nothing
@ -43,6 +44,12 @@ spec =
x = Integer.valueOf 1 x = Integer.valueOf 1
x.test_me x . should_equal False x.test_me x . should_equal False
Test.group "Numeric values" <|
Test.specify "can be passed in host calls without lossy coercion exception" <|
large_long = 6907338656278321365
moments = Moments.new 1
moments.add large_long
Test.group "Java/Enso Date" <| Test.group "Java/Enso Date" <|
Test.specify "Java date has Enso properties" <| Test.specify "Java date has Enso properties" <|
april1st = LocalDate.of 2022 04 01 april1st = LocalDate.of 2022 04 01