mirror of
https://github.com/enso-org/enso.git
synced 2024-11-30 05:35:09 +03:00
Performance improvements for Comparators (#5687)
Critical performance improvements after #4067 # Important Notes - Replace if-then-else expressions in `Any.==` with case expressions. - Fix caching in `EqualsNode`. - This includes fixing specializations, along with fallback guard.
This commit is contained in:
parent
6dddc530b6
commit
58c7ca5401
@ -110,9 +110,13 @@ type Any
|
|||||||
# host or polyglot values, so we just compare them with the default comparator.
|
# host or polyglot values, so we just compare them with the default comparator.
|
||||||
eq_self = Panic.catch No_Such_Conversion (Comparable.from self) _-> Default_Unordered_Comparator
|
eq_self = Panic.catch No_Such_Conversion (Comparable.from self) _-> Default_Unordered_Comparator
|
||||||
eq_that = Panic.catch No_Such_Conversion (Comparable.from that) _-> Default_Unordered_Comparator
|
eq_that = Panic.catch No_Such_Conversion (Comparable.from that) _-> Default_Unordered_Comparator
|
||||||
if Meta.is_same_object eq_self Incomparable then False else
|
case Meta.is_same_object eq_self Incomparable of
|
||||||
|
True -> False
|
||||||
|
False ->
|
||||||
similar_type = Meta.is_same_object eq_self eq_that
|
similar_type = Meta.is_same_object eq_self eq_that
|
||||||
if similar_type.not then False else
|
case similar_type of
|
||||||
|
False -> False
|
||||||
|
True ->
|
||||||
case eq_self.is_ordered of
|
case eq_self.is_ordered of
|
||||||
True ->
|
True ->
|
||||||
# Comparable.equals_builtin is a hack how to directly access EqualsNode from the
|
# Comparable.equals_builtin is a hack how to directly access EqualsNode from the
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import project.Data.Ordering.Ordering
|
import project.Data.Ordering.Ordering
|
||||||
import project.Data.Ordering.Comparable
|
import project.Data.Ordering.Comparable
|
||||||
|
import project.Data.Ordering.Incomparable
|
||||||
import project.Data.Ordering.Default_Ordered_Comparator
|
import project.Data.Ordering.Default_Ordered_Comparator
|
||||||
import project.Data.Text.Text
|
import project.Data.Text.Text
|
||||||
import project.Data.Locale.Locale
|
import project.Data.Locale.Locale
|
||||||
@ -940,7 +941,10 @@ type Integer
|
|||||||
|
|
||||||
parse_builtin text radix = @Builtin_Method "Integer.parse"
|
parse_builtin text radix = @Builtin_Method "Integer.parse"
|
||||||
|
|
||||||
Comparable.from (_:Number) = Default_Ordered_Comparator
|
Comparable.from (that:Number) =
|
||||||
|
case that.is_nan of
|
||||||
|
True -> Incomparable
|
||||||
|
False -> Default_Ordered_Comparator
|
||||||
|
|
||||||
## UNSTABLE
|
## UNSTABLE
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import project.Error.Unimplemented.Unimplemented
|
|||||||
import project.Nothing
|
import project.Nothing
|
||||||
import project.Meta
|
import project.Meta
|
||||||
import project.Meta.Atom
|
import project.Meta.Atom
|
||||||
|
import project.Panic.Panic
|
||||||
from project.Data.Boolean import all
|
from project.Data.Boolean import all
|
||||||
|
|
||||||
## Provides custom ordering, equality check and hash code for types that need it.
|
## Provides custom ordering, equality check and hash code for types that need it.
|
||||||
@ -165,9 +166,15 @@ type Default_Ordered_Comparator
|
|||||||
## Handles only primitive types, not atoms or vectors.
|
## Handles only primitive types, not atoms or vectors.
|
||||||
compare : Any -> Any -> Ordering
|
compare : Any -> Any -> Ordering
|
||||||
compare x y =
|
compare x y =
|
||||||
if Comparable.less_than_builtin x y then Ordering.Less else
|
case Comparable.less_than_builtin x y of
|
||||||
if Comparable.equals_builtin x y then Ordering.Equal else
|
True -> Ordering.Less
|
||||||
if Comparable.less_than_builtin y x then Ordering.Greater
|
False ->
|
||||||
|
case Comparable.equals_builtin x y of
|
||||||
|
True -> Ordering.Equal
|
||||||
|
False ->
|
||||||
|
case Comparable.less_than_builtin y x of
|
||||||
|
True -> Ordering.Greater
|
||||||
|
False -> Panic.throw "Unreachable"
|
||||||
|
|
||||||
hash : Number -> Integer
|
hash : Number -> Integer
|
||||||
hash x = Comparable.hash_builtin x
|
hash x = Comparable.hash_builtin x
|
||||||
|
@ -47,7 +47,7 @@ get key = @Builtin_Method "State.get"
|
|||||||
- key: The key with which to associate the new state.
|
- key: The key with which to associate the new state.
|
||||||
- new_state: The new state to store.
|
- new_state: The new state to store.
|
||||||
|
|
||||||
Returns an uninitialized state error if the user tries to read from an
|
Returns an uninitialized state error if the user tries to put into an
|
||||||
uninitialized slot.
|
uninitialized slot.
|
||||||
|
|
||||||
> Example
|
> Example
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package org.enso.interpreter.node.expression.builtin.meta;
|
package org.enso.interpreter.node.expression.builtin.meta;
|
||||||
|
|
||||||
import com.ibm.icu.text.Normalizer;
|
import com.ibm.icu.text.Normalizer;
|
||||||
|
import com.oracle.truffle.api.CompilerAsserts;
|
||||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||||
import com.oracle.truffle.api.dsl.Cached;
|
import com.oracle.truffle.api.dsl.Cached;
|
||||||
import com.oracle.truffle.api.dsl.Fallback;
|
|
||||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||||
import com.oracle.truffle.api.dsl.Specialization;
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
import com.oracle.truffle.api.interop.ArityException;
|
import com.oracle.truffle.api.interop.ArityException;
|
||||||
@ -15,18 +15,19 @@ import com.oracle.truffle.api.interop.UnknownKeyException;
|
|||||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||||
import com.oracle.truffle.api.interop.UnsupportedTypeException;
|
import com.oracle.truffle.api.interop.UnsupportedTypeException;
|
||||||
import com.oracle.truffle.api.library.CachedLibrary;
|
import com.oracle.truffle.api.library.CachedLibrary;
|
||||||
|
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||||
import com.oracle.truffle.api.nodes.Node;
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
import com.oracle.truffle.api.profiles.ConditionProfile;
|
import com.oracle.truffle.api.profiles.ConditionProfile;
|
||||||
import com.oracle.truffle.api.profiles.LoopConditionProfile;
|
import java.math.BigInteger;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
|
||||||
import org.enso.interpreter.dsl.AcceptsError;
|
import org.enso.interpreter.dsl.AcceptsError;
|
||||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||||
import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode;
|
import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode;
|
||||||
import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode;
|
import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode;
|
||||||
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
|
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
|
||||||
|
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
|
||||||
import org.enso.interpreter.node.expression.builtin.ordering.HasCustomComparatorNode;
|
import org.enso.interpreter.node.expression.builtin.ordering.HasCustomComparatorNode;
|
||||||
import org.enso.interpreter.runtime.EnsoContext;
|
import org.enso.interpreter.runtime.EnsoContext;
|
||||||
import org.enso.interpreter.runtime.Module;
|
import org.enso.interpreter.runtime.Module;
|
||||||
@ -78,38 +79,43 @@ public abstract class EqualsNode extends Node {
|
|||||||
|
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
boolean equalsBoolean(boolean self, boolean other) {
|
boolean equalsBoolBool(boolean self, boolean other) {
|
||||||
return self == other;
|
return self == other;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
boolean equalsBytes(byte self, byte other) {
|
boolean equalsBoolDouble(boolean self, double other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBoolLong(boolean self, long other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBoolBigInt(boolean self, EnsoBigInteger other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBoolText(boolean self, Text other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsByteByte(byte self, byte other) {
|
||||||
return self == other;
|
return self == other;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
boolean equalsLong(long self, long other) {
|
boolean equalsLongLong(long self, long other) {
|
||||||
return self == other;
|
return self == other;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
boolean equalsDouble(double self, double other) {
|
boolean equalsLongBool(long self, boolean other) {
|
||||||
return self == other;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsLongDouble(long self, double other) {
|
|
||||||
return (double) self == other;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsDoubleLong(double self, long other) {
|
|
||||||
return self == (double) other;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsIntLong(int self, long other) {
|
|
||||||
return (long) self == other;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
@ -118,10 +124,34 @@ public abstract class EqualsNode extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
boolean equalsIntDouble(int self, double other) {
|
boolean equalsLongDouble(long self, double other) {
|
||||||
return (double) self == other;
|
return (double) self == other;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsLongText(long self, Text other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsDoubleDouble(double self, double other) {
|
||||||
|
if (Double.isNaN(self) || Double.isNaN(other)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return self == other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsDoubleLong(double self, long other) {
|
||||||
|
return self == (double) other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsDoubleBool(double self, boolean other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
boolean equalsDoubleInt(double self, int other) {
|
boolean equalsDoubleInt(double self, int other) {
|
||||||
return self == (double) other;
|
return self == (double) other;
|
||||||
@ -129,7 +159,33 @@ public abstract class EqualsNode extends Node {
|
|||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
boolean equalsBigInt(EnsoBigInteger self, EnsoBigInteger otherBigInt) {
|
boolean equalsDoubleBigInt(double self, EnsoBigInteger other) {
|
||||||
|
return self == other.doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsDoubleText(double self, Text other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsIntInt(int self, int other) {
|
||||||
|
return self == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsIntLong(int self, long other) {
|
||||||
|
return (long) self == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsIntDouble(int self, double other) {
|
||||||
|
return (double) self == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
@TruffleBoundary
|
||||||
|
boolean equalsBigIntBigInt(EnsoBigInteger self, EnsoBigInteger otherBigInt) {
|
||||||
return self.equals(otherBigInt);
|
return self.equals(otherBigInt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,8 +197,63 @@ public abstract class EqualsNode extends Node {
|
|||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
boolean equalsDoubleBigInt(double self, EnsoBigInteger other) {
|
boolean equalsBigIntLong(EnsoBigInteger self, long other) {
|
||||||
return self == other.doubleValue();
|
if (BigIntegerOps.fitsInLong(self.getValue())) {
|
||||||
|
return self.getValue().compareTo(BigInteger.valueOf(other)) == 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBigIntBool(EnsoBigInteger self, boolean other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBigIntText(EnsoBigInteger self, Text other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
@TruffleBoundary
|
||||||
|
boolean equalsLongBigInt(long self, EnsoBigInteger other) {
|
||||||
|
if (BigIntegerOps.fitsInLong(other.getValue())) {
|
||||||
|
return BigInteger.valueOf(self).compareTo(other.getValue()) == 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization(limit = "3")
|
||||||
|
boolean equalsTextText(Text selfText, Text otherText,
|
||||||
|
@CachedLibrary("selfText") InteropLibrary selfInterop,
|
||||||
|
@CachedLibrary("otherText") InteropLibrary otherInterop) {
|
||||||
|
if (selfText.is_normalized() && otherText.is_normalized()) {
|
||||||
|
return selfText.toString().compareTo(otherText.toString()) == 0;
|
||||||
|
} else {
|
||||||
|
return equalsStrings(selfText, otherText, selfInterop, otherInterop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsTextBool(Text self, boolean other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsTextLong(Text selfText, long otherLong) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsTextDouble(Text selfText, double otherDouble) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsTextBigInt(Text self, EnsoBigInteger other) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -225,17 +336,6 @@ public abstract class EqualsNode extends Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(limit = "3")
|
|
||||||
boolean equalsTexts(Text selfText, Text otherText,
|
|
||||||
@CachedLibrary("selfText") InteropLibrary selfInterop,
|
|
||||||
@CachedLibrary("otherText") InteropLibrary otherInterop) {
|
|
||||||
if (selfText.is_normalized() && otherText.is_normalized()) {
|
|
||||||
return selfText.toString().compareTo(otherText.toString()) == 0;
|
|
||||||
} else {
|
|
||||||
return equalsStrings(selfText, otherText, selfInterop, otherInterop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Interop libraries **/
|
/** Interop libraries **/
|
||||||
|
|
||||||
@Specialization(guards = {
|
@Specialization(guards = {
|
||||||
@ -249,7 +349,6 @@ public abstract class EqualsNode extends Node {
|
|||||||
return selfInterop.isNull(selfNull) && otherInterop.isNull(otherNull);
|
return selfInterop.isNull(selfNull) && otherInterop.isNull(otherNull);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Specialization(guards = {
|
@Specialization(guards = {
|
||||||
"selfInterop.isBoolean(selfBoolean)",
|
"selfInterop.isBoolean(selfBoolean)",
|
||||||
"otherInterop.isBoolean(otherBoolean)"
|
"otherInterop.isBoolean(otherBoolean)"
|
||||||
@ -268,12 +367,8 @@ public abstract class EqualsNode extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(guards = {
|
@Specialization(guards = {
|
||||||
"!selfInterop.isDate(selfTimeZone)",
|
"isTimeZone(selfTimeZone, selfInterop)",
|
||||||
"!selfInterop.isTime(selfTimeZone)",
|
"isTimeZone(otherTimeZone, otherInterop)",
|
||||||
"selfInterop.isTimeZone(selfTimeZone)",
|
|
||||||
"!otherInterop.isDate(otherTimeZone)",
|
|
||||||
"!otherInterop.isTime(otherTimeZone)",
|
|
||||||
"otherInterop.isTimeZone(otherTimeZone)"
|
|
||||||
}, limit = "3")
|
}, limit = "3")
|
||||||
boolean equalsTimeZones(Object selfTimeZone, Object otherTimeZone,
|
boolean equalsTimeZones(Object selfTimeZone, Object otherTimeZone,
|
||||||
@CachedLibrary("selfTimeZone") InteropLibrary selfInterop,
|
@CachedLibrary("selfTimeZone") InteropLibrary selfInterop,
|
||||||
@ -289,12 +384,8 @@ public abstract class EqualsNode extends Node {
|
|||||||
|
|
||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
@Specialization(guards = {
|
@Specialization(guards = {
|
||||||
"selfInterop.isDate(selfZonedDateTime)",
|
"isZonedDateTime(selfZonedDateTime, selfInterop)",
|
||||||
"selfInterop.isTime(selfZonedDateTime)",
|
"isZonedDateTime(otherZonedDateTime, otherInterop)",
|
||||||
"selfInterop.isTimeZone(selfZonedDateTime)",
|
|
||||||
"otherInterop.isDate(otherZonedDateTime)",
|
|
||||||
"otherInterop.isTime(otherZonedDateTime)",
|
|
||||||
"otherInterop.isTimeZone(otherZonedDateTime)"
|
|
||||||
}, limit = "3")
|
}, limit = "3")
|
||||||
boolean equalsZonedDateTimes(Object selfZonedDateTime, Object otherZonedDateTime,
|
boolean equalsZonedDateTimes(Object selfZonedDateTime, Object otherZonedDateTime,
|
||||||
@CachedLibrary("selfZonedDateTime") InteropLibrary selfInterop,
|
@CachedLibrary("selfZonedDateTime") InteropLibrary selfInterop,
|
||||||
@ -318,12 +409,8 @@ public abstract class EqualsNode extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(guards = {
|
@Specialization(guards = {
|
||||||
"selfInterop.isDate(selfDateTime)",
|
"isDateTime(selfDateTime, selfInterop)",
|
||||||
"selfInterop.isTime(selfDateTime)",
|
"isDateTime(otherDateTime, otherInterop)",
|
||||||
"!selfInterop.isTimeZone(selfDateTime)",
|
|
||||||
"otherInterop.isDate(otherDateTime)",
|
|
||||||
"otherInterop.isTime(otherDateTime)",
|
|
||||||
"!otherInterop.isTimeZone(otherDateTime)"
|
|
||||||
}, limit = "3")
|
}, limit = "3")
|
||||||
boolean equalsDateTimes(Object selfDateTime, Object otherDateTime,
|
boolean equalsDateTimes(Object selfDateTime, Object otherDateTime,
|
||||||
@CachedLibrary("selfDateTime") InteropLibrary selfInterop,
|
@CachedLibrary("selfDateTime") InteropLibrary selfInterop,
|
||||||
@ -344,12 +431,8 @@ public abstract class EqualsNode extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(guards = {
|
@Specialization(guards = {
|
||||||
"selfInterop.isDate(selfDate)",
|
"isDate(selfDate, selfInterop)",
|
||||||
"!selfInterop.isTime(selfDate)",
|
"isDate(otherDate, otherInterop)",
|
||||||
"!selfInterop.isTimeZone(selfDate)",
|
|
||||||
"otherInterop.isDate(otherDate)",
|
|
||||||
"!otherInterop.isTime(otherDate)",
|
|
||||||
"!otherInterop.isTimeZone(otherDate)"
|
|
||||||
}, limit = "3")
|
}, limit = "3")
|
||||||
boolean equalsDates(Object selfDate, Object otherDate,
|
boolean equalsDates(Object selfDate, Object otherDate,
|
||||||
@CachedLibrary("selfDate") InteropLibrary selfInterop,
|
@CachedLibrary("selfDate") InteropLibrary selfInterop,
|
||||||
@ -364,12 +447,8 @@ public abstract class EqualsNode extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(guards = {
|
@Specialization(guards = {
|
||||||
"!selfInterop.isDate(selfTime)",
|
"isTime(selfTime, selfInterop)",
|
||||||
"selfInterop.isTime(selfTime)",
|
"isTime(otherTime, otherInterop)",
|
||||||
"!selfInterop.isTimeZone(selfTime)",
|
|
||||||
"!otherInterop.isDate(otherTime)",
|
|
||||||
"otherInterop.isTime(otherTime)",
|
|
||||||
"!otherInterop.isTimeZone(otherTime)"
|
|
||||||
}, limit = "3")
|
}, limit = "3")
|
||||||
boolean equalsTimes(Object selfTime, Object otherTime,
|
boolean equalsTimes(Object selfTime, Object otherTime,
|
||||||
@CachedLibrary("selfTime") InteropLibrary selfInterop,
|
@CachedLibrary("selfTime") InteropLibrary selfInterop,
|
||||||
@ -505,21 +584,8 @@ public abstract class EqualsNode extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(guards = {
|
@Specialization(guards = {
|
||||||
"!isAtom(selfObject)",
|
"isObjectWithMembers(selfObject, interop)",
|
||||||
"!isAtom(otherObject)",
|
"isObjectWithMembers(otherObject, interop)",
|
||||||
"!isHostObject(selfObject)",
|
|
||||||
"!isHostObject(otherObject)",
|
|
||||||
"interop.hasMembers(selfObject)",
|
|
||||||
"interop.hasMembers(otherObject)",
|
|
||||||
"!interop.isDate(selfObject)",
|
|
||||||
"!interop.isDate(otherObject)",
|
|
||||||
"!interop.isTime(selfObject)",
|
|
||||||
"!interop.isTime(otherObject)",
|
|
||||||
// Objects with types are handled in `equalsTypes` specialization, so we have to
|
|
||||||
// negate the guards of that specialization here - to make the specializations
|
|
||||||
// disjunctive.
|
|
||||||
"!typesLib.hasType(selfObject)",
|
|
||||||
"!typesLib.hasType(otherObject)",
|
|
||||||
})
|
})
|
||||||
boolean equalsInteropObjectWithMembers(Object selfObject, Object otherObject,
|
boolean equalsInteropObjectWithMembers(Object selfObject, Object otherObject,
|
||||||
@CachedLibrary(limit = "10") InteropLibrary interop,
|
@CachedLibrary(limit = "10") InteropLibrary interop,
|
||||||
@ -584,32 +650,32 @@ public abstract class EqualsNode extends Node {
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@Specialization(guards = {
|
||||||
|
"selfCtorCached == self.getConstructor()"
|
||||||
|
}, limit = "10")
|
||||||
|
@ExplodeLoop
|
||||||
boolean equalsAtoms(
|
boolean equalsAtoms(
|
||||||
Atom self,
|
Atom self,
|
||||||
Atom other,
|
Atom other,
|
||||||
@Cached LoopConditionProfile loopProfile,
|
@Cached("self.getConstructor()") AtomConstructor selfCtorCached,
|
||||||
@Cached(value = "createEqualsNodes(equalsNodeCountForFields)", allowUncached = true) EqualsNode[] fieldEqualsNodes,
|
@Cached(value = "selfCtorCached.getFields().length", allowUncached = true) int fieldsLenCached,
|
||||||
@Cached ConditionProfile enoughEqualNodesForFieldsProfile,
|
@Cached(value = "createEqualsNodes(fieldsLenCached)", allowUncached = true) EqualsNode[] fieldEqualsNodes,
|
||||||
@Cached ConditionProfile constructorsNotEqualProfile,
|
@Cached ConditionProfile constructorsNotEqualProfile,
|
||||||
@CachedLibrary(limit = "3") StructsLibrary selfStructs,
|
|
||||||
@CachedLibrary(limit = "3") StructsLibrary otherStructs,
|
|
||||||
@Cached HasCustomComparatorNode hasCustomComparatorNode,
|
@Cached HasCustomComparatorNode hasCustomComparatorNode,
|
||||||
@Cached InvokeAnyEqualsNode invokeAnyEqualsNode
|
@Cached InvokeAnyEqualsNode invokeAnyEqualsNode,
|
||||||
|
@CachedLibrary(limit = "5") StructsLibrary structsLib
|
||||||
) {
|
) {
|
||||||
if (constructorsNotEqualProfile.profile(
|
if (constructorsNotEqualProfile.profile(
|
||||||
self.getConstructor() != other.getConstructor()
|
self.getConstructor() != other.getConstructor()
|
||||||
)) {
|
)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var selfFields = selfStructs.getFields(self);
|
var selfFields = structsLib.getFields(self);
|
||||||
var otherFields = otherStructs.getFields(other);
|
var otherFields = structsLib.getFields(other);
|
||||||
assert selfFields.length == otherFields.length;
|
assert selfFields.length == otherFields.length : "Constructors are same, atoms should have the same number of fields";
|
||||||
|
|
||||||
int fieldsSize = selfFields.length;
|
CompilerAsserts.partialEvaluationConstant(fieldsLenCached);
|
||||||
if (enoughEqualNodesForFieldsProfile.profile(fieldsSize <= equalsNodeCountForFields)) {
|
for (int i = 0; i < fieldsLenCached; i++) {
|
||||||
loopProfile.profileCounted(fieldsSize);
|
|
||||||
for (int i = 0; loopProfile.inject(i < fieldsSize); i++) {
|
|
||||||
boolean fieldsAreEqual;
|
boolean fieldsAreEqual;
|
||||||
// We don't check whether `other` has the same type of comparator, that is checked in
|
// We don't check whether `other` has the same type of comparator, that is checked in
|
||||||
// `Any.==` that we invoke here anyway.
|
// `Any.==` that we invoke here anyway.
|
||||||
@ -620,21 +686,29 @@ public abstract class EqualsNode extends Node {
|
|||||||
// custom comparators. EqualsNode cannot deal with custom comparators.
|
// custom comparators. EqualsNode cannot deal with custom comparators.
|
||||||
fieldsAreEqual = invokeAnyEqualsNode.execute(selfAtomField, otherAtomField);
|
fieldsAreEqual = invokeAnyEqualsNode.execute(selfAtomField, otherAtomField);
|
||||||
} else {
|
} else {
|
||||||
fieldsAreEqual = fieldEqualsNodes[i].execute(selfFields[i], otherFields[i]);
|
fieldsAreEqual = fieldEqualsNodes[i].execute(
|
||||||
|
selfFields[i],
|
||||||
|
otherFields[i]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!fieldsAreEqual) {
|
if (!fieldsAreEqual) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return equalsAtomsFieldsUncached(selfFields, otherFields);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
private static boolean equalsAtomsFieldsUncached(Object[] selfFields, Object[] otherFields) {
|
@Specialization(replaces = "equalsAtoms")
|
||||||
assert selfFields.length == otherFields.length;
|
boolean equalsAtomsUncached(Atom self, Atom other) {
|
||||||
|
if (!equalsAtomConstructors(self.getConstructor(), other.getConstructor())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Object[] selfFields = StructsLibrary.getUncached().getFields(self);
|
||||||
|
Object[] otherFields = StructsLibrary.getUncached().getFields(other);
|
||||||
|
if (selfFields.length != otherFields.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
for (int i = 0; i < selfFields.length; i++) {
|
for (int i = 0; i < selfFields.length; i++) {
|
||||||
boolean areFieldsSame;
|
boolean areFieldsSame;
|
||||||
if (selfFields[i] instanceof Atom selfFieldAtom
|
if (selfFields[i] instanceof Atom selfFieldAtom
|
||||||
@ -683,17 +757,136 @@ public abstract class EqualsNode extends Node {
|
|||||||
return equalsNode.execute(selfFuncStrRepr, otherFuncStrRepr);
|
return equalsNode.execute(selfFuncStrRepr, otherFuncStrRepr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Fallback
|
@Specialization(guards = "fallbackGuard(left, right, interop)")
|
||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
boolean equalsGeneric(Object left, Object right,
|
boolean equalsGeneric(Object left, Object right,
|
||||||
@CachedLibrary(limit = "5") InteropLibrary interop,
|
@CachedLibrary(limit = "10") InteropLibrary interop,
|
||||||
@CachedLibrary(limit = "5") TypesLibrary typesLib) {
|
@CachedLibrary(limit = "10") TypesLibrary typesLib) {
|
||||||
return left == right
|
return left == right
|
||||||
|| interop.isIdentical(left, right, interop)
|
|| interop.isIdentical(left, right, interop)
|
||||||
|| left.equals(right)
|
|| left.equals(right)
|
||||||
|| (isNullOrNothing(left, typesLib, interop) && isNullOrNothing(right, typesLib, interop));
|
|| (isNullOrNothing(left, typesLib, interop) && isNullOrNothing(right, typesLib, interop));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We have to manually specify negation of guards of other specializations, because
|
||||||
|
// we cannot use @Fallback here. Note that this guard is not precisely the negation of
|
||||||
|
// all the other guards on purpose.
|
||||||
|
boolean fallbackGuard(Object left, Object right, InteropLibrary interop) {
|
||||||
|
if (isPrimitive(left) && isPrimitive(right)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isHostObject(left) && isHostObject(right)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isHostFunction(left) && isHostFunction(right)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (left instanceof Atom && right instanceof Atom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (interop.isNull(left) && interop.isNull(right)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (interop.isString(left) && interop.isString(right)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (interop.hasArrayElements(left) && interop.hasArrayElements(right)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (interop.hasHashEntries(left) && interop.hasHashEntries(right)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isObjectWithMembers(left, interop) && isObjectWithMembers(right, interop)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isTimeZone(left, interop) && isTimeZone(right, interop)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isZonedDateTime(left, interop) && isZonedDateTime(right, interop)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isDateTime(left, interop) && isDateTime(right, interop)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isDate(left, interop) && isDate(right, interop)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isTime(left, interop) && isTime(right, interop)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (interop.isDuration(left) && interop.isDuration(right)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// For all other cases, fall through to the generic specialization
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true iff object is a primitive value used in some of the specializations
|
||||||
|
* guard. By primitive value we mean any value that can be present in Enso, so,
|
||||||
|
* for example, not Integer, as that cannot be present in Enso.
|
||||||
|
* All the primitive types should be handled in their corresponding specializations.
|
||||||
|
* See {@link org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode}.
|
||||||
|
*/
|
||||||
|
private static boolean isPrimitive(Object object) {
|
||||||
|
return object instanceof Boolean ||
|
||||||
|
object instanceof Long ||
|
||||||
|
object instanceof Double ||
|
||||||
|
object instanceof EnsoBigInteger ||
|
||||||
|
object instanceof Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isTimeZone(Object object, InteropLibrary interop) {
|
||||||
|
return
|
||||||
|
!interop.isTime(object) &&
|
||||||
|
!interop.isDate(object) &&
|
||||||
|
interop.isTimeZone(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isZonedDateTime(Object object, InteropLibrary interop) {
|
||||||
|
return
|
||||||
|
interop.isTime(object) &&
|
||||||
|
interop.isDate(object) &&
|
||||||
|
interop.isTimeZone(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isDateTime(Object object, InteropLibrary interop) {
|
||||||
|
return
|
||||||
|
interop.isTime(object) &&
|
||||||
|
interop.isDate(object) &&
|
||||||
|
!interop.isTimeZone(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isDate(Object object, InteropLibrary interop) {
|
||||||
|
return
|
||||||
|
!interop.isTime(object) &&
|
||||||
|
interop.isDate(object) &&
|
||||||
|
!interop.isTimeZone(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isTime(Object object, InteropLibrary interop) {
|
||||||
|
return
|
||||||
|
interop.isTime(object) &&
|
||||||
|
!interop.isDate(object) &&
|
||||||
|
!interop.isTimeZone(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isObjectWithMembers(Object object, InteropLibrary interop) {
|
||||||
|
if (object instanceof Atom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isHostObject(object)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (interop.isDate(object)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (interop.isTime(object)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return interop.hasMembers(object);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isNullOrNothing(Object object, TypesLibrary typesLib, InteropLibrary interop) {
|
private boolean isNullOrNothing(Object object, TypesLibrary typesLib, InteropLibrary interop) {
|
||||||
if (typesLib.hasType(object)) {
|
if (typesLib.hasType(object)) {
|
||||||
return typesLib.getType(object) == EnsoContext.get(this).getNothing();
|
return typesLib.getType(object) == EnsoContext.get(this).getNothing();
|
||||||
@ -734,11 +927,11 @@ public abstract class EqualsNode extends Node {
|
|||||||
@Cached(value = "getAnyEqualsMethod()", allowUncached = true) Function anyEqualsFunc,
|
@Cached(value = "getAnyEqualsMethod()", allowUncached = true) Function anyEqualsFunc,
|
||||||
@Cached(value = "buildInvokeFuncNodeForAnyEquals()", allowUncached = true) InvokeFunctionNode invokeAnyEqualsNode,
|
@Cached(value = "buildInvokeFuncNodeForAnyEquals()", allowUncached = true) InvokeFunctionNode invokeAnyEqualsNode,
|
||||||
@CachedLibrary(limit = "3") InteropLibrary interop) {
|
@CachedLibrary(limit = "3") InteropLibrary interop) {
|
||||||
// TODO: Shouldn't Comparable type be the very first argument? (synthetic self)?
|
|
||||||
Object ret = invokeAnyEqualsNode.execute(
|
Object ret = invokeAnyEqualsNode.execute(
|
||||||
anyEqualsFunc,
|
anyEqualsFunc,
|
||||||
null,
|
null,
|
||||||
State.create(EnsoContext.get(this)),
|
State.create(EnsoContext.get(this)),
|
||||||
|
// TODO: Shouldn't Any type be the very first argument? (synthetic self)?
|
||||||
new Object[]{selfAtom, thatAtom}
|
new Object[]{selfAtom, thatAtom}
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
|
@ -2,6 +2,7 @@ package org.enso.interpreter.node.expression.builtin.meta;
|
|||||||
|
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.ibm.icu.text.Normalizer2;
|
import com.ibm.icu.text.Normalizer2;
|
||||||
|
import com.oracle.truffle.api.CompilerAsserts;
|
||||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||||
import com.oracle.truffle.api.dsl.Cached;
|
import com.oracle.truffle.api.dsl.Cached;
|
||||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||||
@ -14,6 +15,7 @@ import com.oracle.truffle.api.interop.UnknownIdentifierException;
|
|||||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||||
import com.oracle.truffle.api.interop.UnsupportedTypeException;
|
import com.oracle.truffle.api.interop.UnsupportedTypeException;
|
||||||
import com.oracle.truffle.api.library.CachedLibrary;
|
import com.oracle.truffle.api.library.CachedLibrary;
|
||||||
|
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||||
import com.oracle.truffle.api.nodes.Node;
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
import com.oracle.truffle.api.profiles.ConditionProfile;
|
import com.oracle.truffle.api.profiles.ConditionProfile;
|
||||||
import com.oracle.truffle.api.profiles.LoopConditionProfile;
|
import com.oracle.truffle.api.profiles.LoopConditionProfile;
|
||||||
@ -103,7 +105,10 @@ public abstract class HashCodeNode extends Node {
|
|||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
long hashCodeForDouble(double d) {
|
long hashCodeForDouble(double d) {
|
||||||
if (d % 1.0 != 0 || BigIntegerOps.fitsInLong(d)) {
|
if (Double.isNaN(d)) {
|
||||||
|
// NaN is Incomparable, just return a "random" constant
|
||||||
|
return 456879;
|
||||||
|
} else if (d % 1.0 != 0 || BigIntegerOps.fitsInLong(d)) {
|
||||||
return Double.hashCode(d);
|
return Double.hashCode(d);
|
||||||
} else {
|
} else {
|
||||||
return bigDoubleHash(d);
|
return bigDoubleHash(d);
|
||||||
@ -127,6 +132,7 @@ public abstract class HashCodeNode extends Node {
|
|||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
long hashCodeForAtomConstructor(AtomConstructor atomConstructor) {
|
long hashCodeForAtomConstructor(AtomConstructor atomConstructor) {
|
||||||
|
// AtomConstructors are singletons, we take system hash code explicitly.
|
||||||
return System.identityHashCode(atomConstructor);
|
return System.identityHashCode(atomConstructor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,23 +187,23 @@ public abstract class HashCodeNode extends Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** How many {@link HashCodeNode} nodes should be created for fields in atoms. */
|
|
||||||
static final int hashCodeNodeCountForFields = 10;
|
|
||||||
|
|
||||||
static HashCodeNode[] createHashCodeNodes(int size) {
|
static HashCodeNode[] createHashCodeNodes(int size) {
|
||||||
HashCodeNode[] nodes = new HashCodeNode[size];
|
HashCodeNode[] nodes = new HashCodeNode[size];
|
||||||
Arrays.fill(nodes, HashCodeNode.build());
|
Arrays.fill(nodes, HashCodeNode.build());
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@Specialization(guards = {
|
||||||
|
"atomCtorCached == atom.getConstructor()"
|
||||||
|
}, limit = "5")
|
||||||
|
@ExplodeLoop
|
||||||
long hashCodeForAtom(
|
long hashCodeForAtom(
|
||||||
Atom atom,
|
Atom atom,
|
||||||
@Cached(value = "createHashCodeNodes(hashCodeNodeCountForFields)", allowUncached = true)
|
@Cached("atom.getConstructor()") AtomConstructor atomCtorCached,
|
||||||
|
@Cached("atomCtorCached.getFields().length") int fieldsLenCached,
|
||||||
|
@Cached(value = "createHashCodeNodes(fieldsLenCached)", allowUncached = true)
|
||||||
HashCodeNode[] fieldHashCodeNodes,
|
HashCodeNode[] fieldHashCodeNodes,
|
||||||
@Cached ConditionProfile isHashCodeCached,
|
@Cached ConditionProfile isHashCodeCached,
|
||||||
@Cached ConditionProfile enoughHashCodeNodesForFields,
|
|
||||||
@Cached LoopConditionProfile loopProfile,
|
|
||||||
@CachedLibrary(limit = "10") StructsLibrary structs,
|
@CachedLibrary(limit = "10") StructsLibrary structs,
|
||||||
@Cached HasCustomComparatorNode hasCustomComparatorNode,
|
@Cached HasCustomComparatorNode hasCustomComparatorNode,
|
||||||
@Cached HashCallbackNode hashCallbackNode) {
|
@Cached HashCallbackNode hashCallbackNode) {
|
||||||
@ -208,22 +214,18 @@ public abstract class HashCodeNode extends Node {
|
|||||||
Object[] fields = structs.getFields(atom);
|
Object[] fields = structs.getFields(atom);
|
||||||
int fieldsCount = fields.length;
|
int fieldsCount = fields.length;
|
||||||
|
|
||||||
|
CompilerAsserts.partialEvaluationConstant(fieldsLenCached);
|
||||||
// hashes stores hash codes for all fields, and for constructor.
|
// hashes stores hash codes for all fields, and for constructor.
|
||||||
int[] hashes = new int[fieldsCount + 1];
|
int[] hashes = new int[fieldsCount + 1];
|
||||||
if (enoughHashCodeNodesForFields.profile(fieldsCount <= hashCodeNodeCountForFields)) {
|
for (int i = 0; i < fieldsLenCached; i++) {
|
||||||
loopProfile.profileCounted(fieldsCount);
|
|
||||||
for (int i = 0; loopProfile.inject(i < fieldsCount); i++) {
|
|
||||||
if (fields[i] instanceof Atom atomField && hasCustomComparatorNode.execute(atomField)) {
|
if (fields[i] instanceof Atom atomField && hasCustomComparatorNode.execute(atomField)) {
|
||||||
hashes[i] = (int) hashCallbackNode.execute(atomField);
|
hashes[i] = (int) hashCallbackNode.execute(atomField);
|
||||||
} else {
|
} else {
|
||||||
hashes[i] = (int) fieldHashCodeNodes[i].execute(fields[i]);
|
hashes[i] = (int) fieldHashCodeNodes[i].execute(fields[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
hashCodeForAtomFieldsUncached(fields, hashes);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ctorHashCode = System.identityHashCode(atom.getConstructor());
|
int ctorHashCode = (int) hashCodeForAtomConstructor(atom.getConstructor());
|
||||||
hashes[hashes.length - 1] = ctorHashCode;
|
hashes[hashes.length - 1] = ctorHashCode;
|
||||||
|
|
||||||
int atomHashCode = Arrays.hashCode(hashes);
|
int atomHashCode = Arrays.hashCode(hashes);
|
||||||
@ -232,15 +234,29 @@ public abstract class HashCodeNode extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
private void hashCodeForAtomFieldsUncached(Object[] fields, int[] fieldHashes) {
|
@Specialization(replaces = "hashCodeForAtom")
|
||||||
|
long hashCodeForAtomUncached(Atom atom) {
|
||||||
|
if (atom.getHashCode() != null) {
|
||||||
|
return atom.getHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] fields = StructsLibrary.getUncached().getFields(atom);
|
||||||
|
int[] hashes = new int[fields.length + 1];
|
||||||
for (int i = 0; i < fields.length; i++) {
|
for (int i = 0; i < fields.length; i++) {
|
||||||
if (fields[i] instanceof Atom atomField
|
if (fields[i] instanceof Atom atomField
|
||||||
&& HasCustomComparatorNode.getUncached().execute(atomField)) {
|
&& HasCustomComparatorNode.getUncached().execute(atomField)) {
|
||||||
fieldHashes[i] = (int) HashCallbackNode.getUncached().execute(atomField);
|
hashes[i] = (int) HashCallbackNode.getUncached().execute(atomField);
|
||||||
} else {
|
} else {
|
||||||
fieldHashes[i] = (int) HashCodeNodeGen.getUncached().execute(fields[i]);
|
hashes[i] = (int) HashCodeNodeGen.getUncached().execute(fields[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ctorHashCode = (int) hashCodeForAtomConstructor(atom.getConstructor());
|
||||||
|
hashes[hashes.length - 1] = ctorHashCode;
|
||||||
|
|
||||||
|
int atomHashCode = Arrays.hashCode(hashes);
|
||||||
|
atom.setHashCode(atomHashCode);
|
||||||
|
return atomHashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(
|
@Specialization(
|
||||||
@ -434,7 +450,10 @@ public abstract class HashCodeNode extends Node {
|
|||||||
* Two maps are considered equal, if they have the same entries. Note that we do not care about
|
* Two maps are considered equal, if they have the same entries. Note that we do not care about
|
||||||
* ordering.
|
* ordering.
|
||||||
*/
|
*/
|
||||||
@Specialization(guards = "interop.hasHashEntries(selfMap)")
|
@Specialization(guards = {
|
||||||
|
"interop.hasHashEntries(selfMap)",
|
||||||
|
"!interop.hasArrayElements(selfMap)",
|
||||||
|
})
|
||||||
long hashCodeForMap(
|
long hashCodeForMap(
|
||||||
Object selfMap,
|
Object selfMap,
|
||||||
@CachedLibrary(limit = "5") InteropLibrary interop,
|
@CachedLibrary(limit = "5") InteropLibrary interop,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.enso.interpreter.runtime.data;
|
package org.enso.interpreter.runtime.data;
|
||||||
|
|
||||||
import com.oracle.truffle.api.CompilerDirectives;
|
import com.oracle.truffle.api.CompilerDirectives;
|
||||||
|
import com.oracle.truffle.api.dsl.Cached;
|
||||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||||
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
||||||
import com.oracle.truffle.api.interop.TruffleObject;
|
import com.oracle.truffle.api.interop.TruffleObject;
|
||||||
@ -9,6 +10,7 @@ import com.oracle.truffle.api.library.CachedLibrary;
|
|||||||
import com.oracle.truffle.api.library.ExportLibrary;
|
import com.oracle.truffle.api.library.ExportLibrary;
|
||||||
import com.oracle.truffle.api.library.ExportMessage;
|
import com.oracle.truffle.api.library.ExportMessage;
|
||||||
import com.oracle.truffle.api.nodes.Node;
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
|
import com.oracle.truffle.api.profiles.BranchProfile;
|
||||||
import org.enso.interpreter.dsl.Builtin;
|
import org.enso.interpreter.dsl.Builtin;
|
||||||
import org.enso.interpreter.runtime.EnsoContext;
|
import org.enso.interpreter.runtime.EnsoContext;
|
||||||
import org.enso.interpreter.runtime.error.Warning;
|
import org.enso.interpreter.runtime.error.Warning;
|
||||||
@ -25,7 +27,7 @@ import org.enso.interpreter.runtime.error.WithWarnings;
|
|||||||
@Builtin(pkg = "mutable", stdlibName = "Standard.Base.Data.Array.Array")
|
@Builtin(pkg = "mutable", stdlibName = "Standard.Base.Data.Array.Array")
|
||||||
public final class Array implements TruffleObject {
|
public final class Array implements TruffleObject {
|
||||||
private final Object[] items;
|
private final Object[] items;
|
||||||
private @CompilerDirectives.CompilationFinal Boolean withWarnings;
|
private Boolean withWarnings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new array
|
* Creates a new array
|
||||||
@ -75,14 +77,20 @@ public final class Array implements TruffleObject {
|
|||||||
* @throws InvalidArrayIndexException when the index is out of bounds.
|
* @throws InvalidArrayIndexException when the index is out of bounds.
|
||||||
*/
|
*/
|
||||||
@ExportMessage
|
@ExportMessage
|
||||||
public Object readArrayElement(long index, @CachedLibrary(limit = "3") WarningsLibrary warnings)
|
public Object readArrayElement(
|
||||||
|
long index,
|
||||||
|
@CachedLibrary(limit = "3") WarningsLibrary warnings,
|
||||||
|
@Cached BranchProfile errProfile,
|
||||||
|
@Cached BranchProfile hasWarningsProfile)
|
||||||
throws InvalidArrayIndexException, UnsupportedMessageException {
|
throws InvalidArrayIndexException, UnsupportedMessageException {
|
||||||
if (index >= items.length || index < 0) {
|
if (index >= items.length || index < 0) {
|
||||||
|
errProfile.enter();
|
||||||
throw InvalidArrayIndexException.create(index);
|
throw InvalidArrayIndexException.create(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
var v = items[(int) index];
|
var v = items[(int) index];
|
||||||
if (this.hasWarnings(warnings)) {
|
if (this.hasWarnings(warnings)) {
|
||||||
|
hasWarningsProfile.enter();
|
||||||
Warning[] extracted = this.getWarnings(null, warnings);
|
Warning[] extracted = this.getWarnings(null, warnings);
|
||||||
if (warnings.hasWarnings(v)) {
|
if (warnings.hasWarnings(v)) {
|
||||||
v = warnings.removeWarnings(v);
|
v = warnings.removeWarnings(v);
|
||||||
|
@ -11,7 +11,9 @@ import java.time.ZonedDateTime;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
|
||||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||||
|
import org.enso.interpreter.node.expression.builtin.meta.EqualsNodeGen;
|
||||||
import org.graalvm.polyglot.Context;
|
import org.graalvm.polyglot.Context;
|
||||||
import org.graalvm.polyglot.Value;
|
import org.graalvm.polyglot.Value;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
@ -28,19 +30,21 @@ public class EqualsTest extends TestBase {
|
|||||||
private static Context context;
|
private static Context context;
|
||||||
private static EqualsNode equalsNode;
|
private static EqualsNode equalsNode;
|
||||||
private static TestRootNode testRootNode;
|
private static TestRootNode testRootNode;
|
||||||
|
private static HostValueToEnsoNode hostValueToEnsoNode;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void initContextAndData() {
|
public static void initContextAndData() {
|
||||||
context = createDefaultContext();
|
context = createDefaultContext();
|
||||||
unwrappedValues = fetchAllUnwrappedValues();
|
|
||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
testRootNode = new TestRootNode();
|
testRootNode = new TestRootNode();
|
||||||
equalsNode = EqualsNode.build();
|
equalsNode = EqualsNode.build();
|
||||||
testRootNode.insertChildren(equalsNode);
|
hostValueToEnsoNode = HostValueToEnsoNode.build();
|
||||||
|
testRootNode.insertChildren(equalsNode, hostValueToEnsoNode);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
unwrappedValues = fetchAllUnwrappedValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
@ -74,6 +78,7 @@ public class EqualsTest extends TestBase {
|
|||||||
try {
|
try {
|
||||||
return values.stream()
|
return values.stream()
|
||||||
.map(value -> unwrapValue(context, value))
|
.map(value -> unwrapValue(context, value))
|
||||||
|
.map(unwrappedValue -> hostValueToEnsoNode.execute(unwrappedValue))
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
.toArray(new Object[] {});
|
.toArray(new Object[] {});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -105,6 +110,21 @@ public class EqualsTest extends TestBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Theory
|
||||||
|
public void equalsNodeCachedIsConsistentWithUncached(Object firstVal, Object secondVal) {
|
||||||
|
executeInContext(
|
||||||
|
context,
|
||||||
|
() -> {
|
||||||
|
boolean uncachedRes = EqualsNodeGen.getUncached().execute(firstVal, secondVal);
|
||||||
|
boolean cachedRes = equalsNode.execute(firstVal, secondVal);
|
||||||
|
assertEquals(
|
||||||
|
"Result from uncached EqualsNode should be the same as result from its cached variant",
|
||||||
|
uncachedRes,
|
||||||
|
cachedRes);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** Test for some specific values, for which we know that they are equal. */
|
/** Test for some specific values, for which we know that they are equal. */
|
||||||
@Test
|
@Test
|
||||||
public void testDateEquality() {
|
public void testDateEquality() {
|
||||||
|
@ -7,8 +7,10 @@ import com.oracle.truffle.api.interop.InteropLibrary;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
|
||||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeNode;
|
import org.enso.interpreter.node.expression.builtin.meta.HashCodeNode;
|
||||||
|
import org.enso.interpreter.node.expression.builtin.meta.HashCodeNodeGen;
|
||||||
import org.graalvm.polyglot.Context;
|
import org.graalvm.polyglot.Context;
|
||||||
import org.graalvm.polyglot.Value;
|
import org.graalvm.polyglot.Value;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
@ -26,20 +28,22 @@ public class HashCodeTest extends TestBase {
|
|||||||
|
|
||||||
private static HashCodeNode hashCodeNode;
|
private static HashCodeNode hashCodeNode;
|
||||||
private static EqualsNode equalsNode;
|
private static EqualsNode equalsNode;
|
||||||
|
private static HostValueToEnsoNode hostValueToEnsoNode;
|
||||||
private static TestRootNode testRootNode;
|
private static TestRootNode testRootNode;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void initContextAndData() {
|
public static void initContextAndData() {
|
||||||
context = createDefaultContext();
|
context = createDefaultContext();
|
||||||
// Initialize datapoints here, to make sure that it is initialized just once.
|
|
||||||
unwrappedValues = fetchAllUnwrappedValues();
|
|
||||||
executeInContext(context, () -> {
|
executeInContext(context, () -> {
|
||||||
hashCodeNode = HashCodeNode.build();
|
hashCodeNode = HashCodeNode.build();
|
||||||
equalsNode = EqualsNode.build();
|
equalsNode = EqualsNode.build();
|
||||||
|
hostValueToEnsoNode = HostValueToEnsoNode.build();
|
||||||
testRootNode = new TestRootNode();
|
testRootNode = new TestRootNode();
|
||||||
testRootNode.insertChildren(hashCodeNode, equalsNode);
|
testRootNode.insertChildren(hashCodeNode, equalsNode, hostValueToEnsoNode);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
// Initialize datapoints here, to make sure that it is initialized just once.
|
||||||
|
unwrappedValues = fetchAllUnwrappedValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
@ -79,6 +83,7 @@ public class HashCodeTest extends TestBase {
|
|||||||
return values
|
return values
|
||||||
.stream()
|
.stream()
|
||||||
.map(value -> unwrapValue(context, value))
|
.map(value -> unwrapValue(context, value))
|
||||||
|
.map(unwrappedValue -> hostValueToEnsoNode.execute(unwrappedValue))
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
.toArray(new Object[]{});
|
.toArray(new Object[]{});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -132,4 +137,18 @@ public class HashCodeTest extends TestBase {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Theory
|
||||||
|
public void hashCodeCachedNodeIsConsistentWithUncached(Object value) {
|
||||||
|
executeInContext(context, () -> {
|
||||||
|
long uncachedRes = HashCodeNodeGen.getUncached().execute(value);
|
||||||
|
long cachedRes = hashCodeNode.execute(value);
|
||||||
|
assertEquals(
|
||||||
|
"Result from cached HashCodeNode should be the same as from its uncached variant",
|
||||||
|
uncachedRes,
|
||||||
|
cachedRes
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
import org.enso.polyglot.MethodNames.Module;
|
||||||
import org.graalvm.polyglot.Context;
|
import org.graalvm.polyglot.Context;
|
||||||
import org.graalvm.polyglot.PolyglotException;
|
import org.graalvm.polyglot.PolyglotException;
|
||||||
import org.graalvm.polyglot.Value;
|
import org.graalvm.polyglot.Value;
|
||||||
@ -74,6 +75,32 @@ class ValuesGenerator {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts expressions into values of type described by {@code typeDefs} by concatenating
|
||||||
|
* everything into a single source.
|
||||||
|
*
|
||||||
|
* This method exists so that there are no multiple definitions of a single type.
|
||||||
|
*
|
||||||
|
* @param typeDefs Type definitions.
|
||||||
|
* @param expressions List of expressions - every expression will be converted to a {@link Value}.
|
||||||
|
* @return List of values converted from the given expressions.
|
||||||
|
*/
|
||||||
|
private List<Value> createValuesOfCustomType(String typeDefs, List<String> expressions) {
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.append(typeDefs);
|
||||||
|
sb.append("\n");
|
||||||
|
for (int i = 0; i < expressions.size(); i++) {
|
||||||
|
sb.append("var_").append(i).append(" = ").append(expressions.get(i)).append("\n");
|
||||||
|
}
|
||||||
|
Value module = ctx.eval("enso", sb.toString());
|
||||||
|
List<Value> values = new ArrayList<>(expressions.size());
|
||||||
|
for (int i = 0; i < expressions.size(); i++) {
|
||||||
|
Value val = module.invokeMember(Module.EVAL_EXPRESSION, "var_" + i);
|
||||||
|
values.add(val);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
public Value typeAny() {
|
public Value typeAny() {
|
||||||
return v("typeAny", """
|
return v("typeAny", """
|
||||||
import Standard.Base.Any.Any
|
import Standard.Base.Any.Any
|
||||||
@ -521,7 +548,7 @@ class ValuesGenerator {
|
|||||||
Nil
|
Nil
|
||||||
Value value
|
Value value
|
||||||
""";
|
""";
|
||||||
for (var expr : List.of(
|
var exprs = List.of(
|
||||||
"Node.C2 Node.Nil (Node.Value 42)",
|
"Node.C2 Node.Nil (Node.Value 42)",
|
||||||
"Node.C2 (Node.Value 42) Node.Nil",
|
"Node.C2 (Node.Value 42) Node.Nil",
|
||||||
"Node.Nil",
|
"Node.Nil",
|
||||||
@ -536,9 +563,8 @@ class ValuesGenerator {
|
|||||||
"Node.C2 (Node.C2 (Node.C1 Node.Nil) (Node.C1 (Node.C1 Node.Nil))) (Node.C2 (Node.C3 (Node.Nil) (Node.Value 22) (Node.Nil)) (Node.C2 (Node.Value 22) (Node.Nil)))",
|
"Node.C2 (Node.C2 (Node.C1 Node.Nil) (Node.C1 (Node.C1 Node.Nil))) (Node.C2 (Node.C3 (Node.Nil) (Node.Value 22) (Node.Nil)) (Node.C2 (Node.Value 22) (Node.Nil)))",
|
||||||
"Node.C2 (Node.C2 (Node.C1 Node.Nil) (Node.C1 Node.Nil)) (Node.C2 (Node.C3 (Node.Nil) (Node.Value 22) (Node.Nil)) (Node.C2 (Node.Value 22) (Node.Nil)))",
|
"Node.C2 (Node.C2 (Node.C1 Node.Nil) (Node.C1 Node.Nil)) (Node.C2 (Node.C3 (Node.Nil) (Node.Value 22) (Node.Nil)) (Node.C2 (Node.Value 22) (Node.Nil)))",
|
||||||
"Node.C2 (Node.C2 (Node.C1 Node.Nil) (Node.C1 Node.Nil)) (Node.C2 (Node.C3 (Node.Nil) (Node.Nil) (Node.Value 22)) (Node.C2 (Node.Value 22) (Node.Nil)))"
|
"Node.C2 (Node.C2 (Node.C1 Node.Nil) (Node.C1 Node.Nil)) (Node.C2 (Node.C3 (Node.Nil) (Node.Nil) (Node.Value 22)) (Node.C2 (Node.Value 22) (Node.Nil)))"
|
||||||
)) {
|
);
|
||||||
collect.add(v(null, nodeTypeDef, expr).type());
|
collect.addAll(createValuesOfCustomType(nodeTypeDef, exprs));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return collect;
|
return collect;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user