diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/MultiValueBenchmarks.java b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/MultiValueBenchmarks.java new file mode 100644 index 000000000000..1403fbedd5a1 --- /dev/null +++ b/engine/runtime-benchmarks/src/main/java/org/enso/interpreter/bench/benchmarks/semantic/MultiValueBenchmarks.java @@ -0,0 +1,155 @@ +package org.enso.interpreter.bench.benchmarks.semantic; + +import java.util.concurrent.TimeUnit; +import org.enso.common.MethodNames; +import org.enso.compiler.benchmarks.Utils; +import org.graalvm.polyglot.Value; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.BenchmarkParams; +import org.openjdk.jmh.infra.Blackhole; + +/** + * These benchmarks compare performance of {@link EnsoMultiValue}. They create a vector in a certain + * configuration representing numbers and then they perform {@code sum} operation on it. + */ +@BenchmarkMode(Mode.AverageTime) +@Fork(1) +@Warmup(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +public class MultiValueBenchmarks { + private Value arrayOfNumbers; + private Value sum; + private Value self; + private final long length = 100000; + + @Setup + public void initializeBenchmark(BenchmarkParams params) throws Exception { + var ctx = Utils.createDefaultContextBuilder().build(); + var code = + """ + from Standard.Base import Vector, Float, Number, Integer + + type Complex + private Number re:Float im:Float + + Complex.from (that:Number) = Complex.Number that 0 + + sum arr = + go acc i = if i >= arr.length then acc else + v = arr.at i + sum = acc + v + @Tail_Call go sum i+1 + go 0 0 + + sum_re arr = + go acc i = if i >= arr.length then acc else + v = arr.at i . re + sum = acc + v + @Tail_Call go sum i+1 + go 0 0 + + make_vector typ n = + Vector.new n i-> + r = 3 + 5*i + case typ of + 0 -> r:Complex + 1 -> r:Integer + 2 -> r:Float + 3 -> + c = r:Complex&Float + c:Float + 4 -> + c = r:Float&Complex + c:Float + 5 -> r:Complex&Float + 6 -> r:Float&Complex + """; + var benchmarkName = SrcUtil.findName(params); + var src = SrcUtil.source(benchmarkName, code); + int type = Integer.parseInt(benchmarkName.substring(benchmarkName.length() - 1)); + + var module = ctx.eval(src); + this.self = module.invokeMember(MethodNames.Module.GET_ASSOCIATED_TYPE); + var makeVector = module.invokeMember("get_method", self, "make_vector"); + this.arrayOfNumbers = makeVector.execute(self, type, length); + this.sum = + module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, type == 0 ? "sum_re" : "sum"); + } + + /** + * The base benchmark for this suite. Measures how much it takes to access an Atom in a + * Vector, read {@code re:Float} field out of it and sum all of them together. + */ + @Benchmark + public void sumOverComplexBaseBenchmark0(Blackhole matter) { + performBenchmark(matter); + } + + /** + * Working with {@code Integer} should be the fastest. The plus operation on integer are faster + * than those on {@code Float} and moreover the {@code Vector} has a special representation when + * full of {@code long} values. + */ + @Benchmark + public void sumOverInteger1(Blackhole matter) { + performBenchmark(matter); + } + + /** + * Working with {@code Float} should be also fast. The {@code Vector} has a special representation + * when full of {@code double} values that increases cache locality. + */ + @Benchmark + public void sumOverFloat2(Blackhole matter) { + performBenchmark(matter); + } + + // + // Following benchmarks shall catch up with the base benchmark + // + + @Benchmark + public void sumOverComplexFloatRecastedToFloat3(Blackhole matter) { + performBenchmark(matter); + } + + @Benchmark + public void sumOverFloatComplexRecastedToFloat4(Blackhole matter) { + performBenchmark(matter); + } + + @Benchmark + public void sumOverComplexAndFloat5(Blackhole matter) { + performBenchmark(matter); + } + + @Benchmark + public void sumOverFloatAndComplex6(Blackhole matter) { + performBenchmark(matter); + } + + private void performBenchmark(Blackhole matter) throws AssertionError { + var resultValue = sum.execute(arrayOfNumbers); + if (!resultValue.fitsInLong()) { + throw new AssertionError("Shall be a long: " + resultValue); + } + long result = resultValue.asLong(); + long expectedResult = length * 3L + (5L * (length * (length - 1L) / 2L)); + boolean isResultCorrect = result == expectedResult; + if (!isResultCorrect) { + throw new AssertionError("Expecting " + expectedResult + " but was " + result); + } + matter.consume(result); + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeMultiValueTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeMultiValueTest.java index b82ec6b2acf9..86be83d8710a 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeMultiValueTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeMultiValueTest.java @@ -91,17 +91,24 @@ private static void registerValue( if (rawValue instanceof EnsoMultiValue) { return; } + var rawInt = (Type) ContextUtils.unwrapValue(ctx(), g.typeInteger()); var rawType = ContextUtils.unwrapValue(ctx(), t); if (rawType instanceof Type type) { - var singleMultiValue = EnsoMultiValue.create(new Type[] {type}, 1, new Object[] {rawValue}); + if (rawType == rawInt) { + return; + } + var singleMultiValue = + EnsoMultiValue.NewNode.getUncached() + .newValue(new Type[] {type}, 1, new Object[] {rawValue}); var n = t.getMetaSimpleName(); data.add(new Object[] {singleMultiValue, n, 0}); - var rawInt = (Type) ContextUtils.unwrapValue(ctx(), g.typeInteger()); var secondMultiValue = - EnsoMultiValue.create(new Type[] {rawInt, type}, 2, new Object[] {5L, rawValue}); + EnsoMultiValue.NewNode.getUncached() + .newValue(new Type[] {rawInt, type}, 2, new Object[] {5L, rawValue}); data.add(new Object[] {secondMultiValue, n, 1}); var firstMultiValue = - EnsoMultiValue.create(new Type[] {type, rawInt}, 2, new Object[] {rawValue, 6L}); + EnsoMultiValue.NewNode.getUncached() + .newValue(new Type[] {type, rawInt}, 2, new Object[] {rawValue, 6L}); data.add(new Object[] {firstMultiValue, n, 0}); } else { if (!t.isHostObject()) { diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeValueTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeValueTest.java index 3c22ef041617..128d2993f16a 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeValueTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeValueTest.java @@ -93,7 +93,9 @@ public void multiValueWithHiddenType() { new Type[] { ensoCtx.getBuiltins().number().getInteger(), ensoCtx.getBuiltins().text() }; - var multi = EnsoMultiValue.create(types, 1, new Object[] {42L, "Meaning"}); + var multi = + EnsoMultiValue.NewNode.getUncached() + .newValue(types, 1, new Object[] {42L, "Meaning"}); var arr = (Object[]) testTypesCall.call(multi, true); var allTypes = (Type[]) arr[1]; assertEquals("Two types", 2, allTypes.length); diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/typecheck/TypeCheckValueTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/typecheck/TypeCheckValueTest.java index 7a4fbdd1e028..e732d1dcbcd0 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/typecheck/TypeCheckValueTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/typecheck/TypeCheckValueTest.java @@ -39,10 +39,11 @@ public void avoidDoubleWrappingOfEnsoMultiValue() { () -> { var builtins = ContextUtils.leakContext(ctx).getBuiltins(); var m1 = - EnsoMultiValue.create( - new Type[] {builtins.text(), builtins.number().getInteger()}, - 2, - new Object[] {"Hi", 42}); + EnsoMultiValue.NewNode.getUncached() + .newValue( + new Type[] {builtins.text(), builtins.number().getInteger()}, + 2, + new Object[] {"Hi", 42}); assertEquals("Text & Integer", m1.toDisplayString(true)); var res = convert.call(m1); diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/AnyToTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/AnyToTest.java index 20311946ad6e..50be393c32f9 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/AnyToTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/AnyToTest.java @@ -60,7 +60,9 @@ public void multiValueToInteger() throws Exception { """; var conv = ContextUtils.evalModule(ctx, Source.newBuilder("enso", code, "conv.enso").build(), "conv"); - var both = EnsoMultiValue.create(types, types.length, new Object[] {2L, Text.create("Two")}); + var both = + EnsoMultiValue.NewNode.getUncached() + .newValue(types, types.length, new Object[] {2L, Text.create("Two")}); var eq = ContextUtils.executeInContext( ctx, @@ -104,7 +106,9 @@ private void multiValueToText(int dispatchLength) throws Exception { """; var conv = ContextUtils.evalModule(ctx, Source.newBuilder("enso", code, "conv.enso").build(), "conv"); - var both = EnsoMultiValue.create(types, dispatchLength, new Object[] {2L, Text.create("Two")}); + var both = + EnsoMultiValue.NewNode.getUncached() + .newValue(types, dispatchLength, new Object[] {2L, Text.create("Two")}); var eq = ContextUtils.executeInContext( ctx, diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/EnsoMultiValueInteropTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/EnsoMultiValueInteropTest.java index 530122ceb651..2cddf7cd230a 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/EnsoMultiValueInteropTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/EnsoMultiValueInteropTest.java @@ -64,7 +64,12 @@ private static void registerValue( if (r2 instanceof EnsoMultiValue) { return; } - var both = EnsoMultiValue.create(new Type[] {typ1, typ2}, 2, new Object[] {r1, r2}); + if (typ1 == typ2) { + return; + } + var both = + EnsoMultiValue.NewNode.getUncached() + .newValue(new Type[] {typ1, typ2}, 2, new Object[] {r1, r2}); data.add(new Object[] {both}); } } diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/EqualsMultiValueTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/EqualsMultiValueTest.java index 90b001ebd16f..39d6d6ae9468 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/EqualsMultiValueTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/EqualsMultiValueTest.java @@ -55,8 +55,9 @@ public void testEqualityIntegerAndMultiValue() { var intType = builtins.number().getInteger(); var textText = builtins.text(); var fourExtraText = - EnsoMultiValue.create( - new Type[] {intType, textText}, 1, new Object[] {4L, Text.create("Hi")}); + EnsoMultiValue.NewNode.getUncached() + .newValue( + new Type[] {intType, textText}, 1, new Object[] {4L, Text.create("Hi")}); assertTrue("4 == 4t", equalityCheck(4L, fourExtraText)); assertFalse("5 != 4t", equalityCheck(5L, fourExtraText)); @@ -85,7 +86,7 @@ public void testEqualityTextAndExtraIntegerMultiValue() { // x = _ : (Text & Integer) : Text // e.g. multi value with Text and Integer, casted to Text only // - var multiV = EnsoMultiValue.create(bothTypes, 1, text, integer); + var multiV = EnsoMultiValue.NewNode.getUncached().newValue(bothTypes, 1, text, integer); assertTrue("'Hi' == multiV", equalityCheck(text, multiV)); assertFalse("'Ahoj' != multiV", equalityCheck(ahoj, multiV)); @@ -112,11 +113,14 @@ public void testEqualityIntegerAndMultiValueWithBoth() { var textText = builtins.text(); var hi = Text.create("Hi"); var textFour = - EnsoMultiValue.create(new Type[] {textText, intType}, 2, new Object[] {hi, 4L}); + EnsoMultiValue.NewNode.getUncached() + .newValue(new Type[] {textText, intType}, 2, new Object[] {hi, 4L}); var textFive = - EnsoMultiValue.create(new Type[] {textText, intType}, 2, new Object[] {hi, 5L}); + EnsoMultiValue.NewNode.getUncached() + .newValue(new Type[] {textText, intType}, 2, new Object[] {hi, 5L}); var fourText = - EnsoMultiValue.create(new Type[] {intType, textText}, 2, new Object[] {4L, hi}); + EnsoMultiValue.NewNode.getUncached() + .newValue(new Type[] {intType, textText}, 2, new Object[] {4L, hi}); assertFalse("4 != t", equalityCheck(4L, hi)); assertFalse("4 != 4t", equalityCheck(4L, textFour)); @@ -143,8 +147,9 @@ public void testEqualityIntegerAndMultiValueWithIntText() { var intType = builtins.number().getInteger(); var textText = builtins.text(); var fourExtraText = - EnsoMultiValue.create( - new Type[] {intType, textText}, 2, new Object[] {4L, Text.create("Hi")}); + EnsoMultiValue.NewNode.getUncached() + .newValue( + new Type[] {intType, textText}, 2, new Object[] {4L, Text.create("Hi")}); assertFalse("4 != 4t", equalityCheck(4L, fourExtraText)); assertFalse("5 != 4t", equalityCheck(5L, fourExtraText)); @@ -164,14 +169,17 @@ public void twoMultiValues() { var intType = builtins.number().getInteger(); var textText = builtins.text(); var fourExtraText = - EnsoMultiValue.create( - new Type[] {intType, textText}, 1, new Object[] {4L, Text.create("Hi")}); + EnsoMultiValue.NewNode.getUncached() + .newValue( + new Type[] {intType, textText}, 1, new Object[] {4L, Text.create("Hi")}); var fourExtraText2 = - EnsoMultiValue.create( - new Type[] {intType, textText}, 1, new Object[] {4L, Text.create("Hi")}); + EnsoMultiValue.NewNode.getUncached() + .newValue( + new Type[] {intType, textText}, 1, new Object[] {4L, Text.create("Hi")}); var fiveExtraText = - EnsoMultiValue.create( - new Type[] {intType, textText}, 1, new Object[] {5L, Text.create("Hi")}); + EnsoMultiValue.NewNode.getUncached() + .newValue( + new Type[] {intType, textText}, 1, new Object[] {5L, Text.create("Hi")}); assertFalse("!= for sure #1", equalityCheck(fiveExtraText, fourExtraText)); assertFalse("!= for sure #2", equalityCheck(fourExtraText, fiveExtraText)); diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/ValuesGenerator.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/ValuesGenerator.java index a163a184655e..b35789f41133 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/ValuesGenerator.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/ValuesGenerator.java @@ -900,7 +900,7 @@ public List numbersMultiText() { private void addMultiToCollect( List collect, Type[] types, int dispatchTypes, Object... values) { - var raw = EnsoMultiValue.create(types, dispatchTypes, values); + var raw = EnsoMultiValue.NewNode.getUncached().newValue(types, dispatchTypes, values); var wrap = ctx.asValue(raw); collect.add(wrap); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/BinaryOperatorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/BinaryOperatorNode.java index cd076538fca9..19825dbea699 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/BinaryOperatorNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/BinaryOperatorNode.java @@ -1,6 +1,7 @@ package org.enso.interpreter.node; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.GenerateUncached; @@ -24,6 +25,7 @@ import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.interpreter.runtime.state.State; +import org.graalvm.collections.Pair; final class BinaryOperatorNode extends ExpressionNode { private @Child ExpressionNode left; @@ -140,7 +142,48 @@ final Object doThatConversionCached( return doDispatch(frame, self, that, selfType, thatType, symbolFn, convertNode, invokeNode); } - @Specialization(replaces = "doThatConversionCached") + @Specialization( + limit = "5", + guards = {"multi.getDispatchId() == cachedDispatchId"}) + final Object doThatConversionWithMulti( + VirtualFrame frame, + String symbol, + Object self, + EnsoMultiValue multi, + @Shared("typeOf") @Cached TypeOfNode typeOfNode, + @Cached("multi.getDispatchId()") Object cachedDispatchId, + @Cached("findFnForMulti(typeOfNode, multi, symbol)") Pair fnAndType, + @Shared("convert") @Cached InteropConversionCallNode convertNode, + @Shared("invoke") @Cached(allowUncached = true, value = "buildWithArity(2)") + InvokeFunctionNode invokeNode) { + var selfType = findType(typeOfNode, self); + if (fnAndType != null) { + var fn = fnAndType.getLeft(); + var thatType = fnAndType.getRight(); + var result = + doDispatch(frame, self, multi, selfType, thatType, fn, convertNode, invokeNode); + if (result != null) { + return result; + } + } + return null; + } + + @TruffleBoundary + final Pair findFnForMulti( + TypeOfNode typeOfNode, EnsoMultiValue multi, String symbol) { + var all = typeOfNode.findAllTypesOrNull(multi, false); + Function fn = null; + for (var thatType : all) { + fn = findSymbol(symbol, thatType); + if (fn != null) { + return Pair.create(fn, thatType); + } + } + return null; + } + + @Specialization(replaces = {"doThatConversionCached", "doThatConversionWithMulti"}) final Object doThatConversionUncached( VirtualFrame frame, String symbol, @@ -152,16 +195,14 @@ final Object doThatConversionUncached( InvokeFunctionNode invokeNode) { var selfType = findType(typeOfNode, self); if (that instanceof EnsoMultiValue multi) { - var all = typeOfNode.findAllTypesOrNull(multi, false); - assert all != null; - for (var thatType : all) { - var fn = findSymbol(symbol, thatType); - if (fn != null) { - var result = - doDispatch(frame, self, multi, selfType, thatType, fn, convertNode, invokeNode); - if (result != null) { - return result; - } + var fnAndType = findFnForMulti(typeOfNode, multi, symbol); + if (fnAndType != null) { + var fn = fnAndType.getLeft(); + var thatType = fnAndType.getRight(); + var result = + doDispatch(frame, self, multi, selfType, thatType, fn, convertNode, invokeNode); + if (result != null) { + return result; } } } else { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AbstractTypeCheckNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AbstractTypeCheckNode.java index 1246a3f89b90..87d1ecafe16d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AbstractTypeCheckNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AbstractTypeCheckNode.java @@ -34,12 +34,12 @@ abstract Object executeCheckOrConversion( @ExplodeLoop final boolean isAllTypes() { Node p = this; - CompilerAsserts.compilationConstant(p); + CompilerAsserts.partialEvaluationConstant(p); for (; ; ) { if (p instanceof TypeCheckValueNode vn) { - CompilerAsserts.compilationConstant(vn); + CompilerAsserts.partialEvaluationConstant(vn); var allTypes = vn.isAllTypes(); - CompilerAsserts.compilationConstant(allTypes); + CompilerAsserts.partialEvaluationConstant(allTypes); return allTypes; } p = p.getParent(); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AllOfTypesCheckNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AllOfTypesCheckNode.java index 5b23be5dbe06..d9b7aaa5bd97 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AllOfTypesCheckNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/AllOfTypesCheckNode.java @@ -1,10 +1,12 @@ package org.enso.interpreter.node.typecheck; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.ExplodeLoop; import java.util.Arrays; import java.util.stream.Collectors; import org.enso.interpreter.node.ExpressionNode; +import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.data.EnsoMultiValue; import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; @@ -13,11 +15,13 @@ final class AllOfTypesCheckNode extends AbstractTypeCheckNode { @Children private AbstractTypeCheckNode[] checks; @Child private TypesLibrary types; + @Child private EnsoMultiValue.NewNode newNode; AllOfTypesCheckNode(String name, AbstractTypeCheckNode[] checks) { super(name); this.checks = checks; this.types = TypesLibrary.getFactory().createDispatched(checks.length); + this.newNode = EnsoMultiValue.NewNode.create(); } AbstractTypeCheckNode[] getChecks() { @@ -35,12 +39,26 @@ Object executeCheckOrConversion(VirtualFrame frame, Object value, ExpressionNode var values = new Object[checks.length]; var valueTypes = new Type[checks.length]; var at = 0; + var integers = 0; + var floats = 0; for (var n : checks) { var result = n.executeCheckOrConversion(frame, value, expr); if (result == null) { return null; } - valueTypes[at] = types.getType(result); + var t = types.getType(result); + var ctx = EnsoContext.get(this); + if (ctx.getBuiltins().number().getInteger() == t) { + if (++integers > 1) { + continue; + } + } + if (ctx.getBuiltins().number().getFloat() == t) { + if (++floats > 1) { + continue; + } + } + valueTypes[at] = t; if (result instanceof EnsoMultiValue emv) { result = EnsoMultiValue.CastToNode.getUncached() @@ -52,7 +70,18 @@ Object executeCheckOrConversion(VirtualFrame frame, Object value, ExpressionNode values[at] = result; at++; } - return EnsoMultiValue.create(valueTypes, valueTypes.length, values); + if (at != checks.length) { + // request for Number & Integer may yield two integers collision + // request for Number & Float may yield two floats collision + // request for Number & Integer & Float must yield one collision + // + // people shouldn't be doing such things but the code must be + // ready for that - switching to interpreter without optimization + CompilerDirectives.transferToInterpreter(); + values = Arrays.copyOf(values, at); + valueTypes = Arrays.copyOf(valueTypes, at); + } + return newNode.newValue(valueTypes, valueTypes.length, values); } @Override diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/SingleTypeCheckNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/SingleTypeCheckNode.java index cb5f26b10249..39cc53a3e31d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/SingleTypeCheckNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/typecheck/SingleTypeCheckNode.java @@ -170,23 +170,14 @@ ApplicationNode findConversionNode(ExpressionNode valueNode, Type[] allTypes) { return null; } - Type[] findType(TypeOfNode typeOfNode, Object v) { + final Type[] findType(TypeOfNode typeOfNode, Object v) { return findType(typeOfNode, v, null); } - Type[] findType(TypeOfNode typeOfNode, Object v, Type[] previous) { + final Type[] findType(TypeOfNode typeOfNode, Object v, Type[] previous) {; if (v instanceof EnsoMultiValue multi) { - var all = typeOfNode.findAllTypesOrNull(multi, isAllTypes()); - assert all != null; - var to = 0; - // only consider methodDispatchTypes and not "all types" of the multi value - for (var i = 0; i < all.length; i++) { - var typeOrNull = EnsoMultiValue.CastToNode.getUncached().findTypeOrNull(all[i], multi, false, false); - if (typeOrNull != null) { - all[to++] = all[i]; - } - } - return Arrays.copyOf(all, to); + var all = typeOfNode.findAllTypesOrNull(multi, false); + return all; } if (v instanceof UnresolvedConstructor) { return null; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java index 1f823c6c2463..783d48bfcd5a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java @@ -66,6 +66,7 @@ public ModuleScope getScope() { * @param type the type for which this symbol should be resolved * @return the resolved function definition and type it was resolved in, or null if not found */ + @TruffleBoundary public Pair resolveFor(Node node, Type type) { if (type != null) { for (var current : type.allTypes(EnsoContext.get(node))) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiType.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiType.java new file mode 100644 index 000000000000..e531ed8f6fdf --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiType.java @@ -0,0 +1,182 @@ +package org.enso.interpreter.runtime.data; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.NeverDefault; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.nodes.Node; +import java.util.Arrays; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; +import org.enso.interpreter.runtime.EnsoContext; + +/** + * Internal representation of {@code Type[]} that supports identity comparision with {@code ==} to + * support inline caching of {@link EnsoMultiValue}. This is a separate and hidden concept from + * {@link Type} which is used thru out the Enso codebase. + * + *

Think twice before opening this type to public! + */ +final class EnsoMultiType { + private static final ConcurrentHashMap ALL_TYPES = + new ConcurrentHashMap<>(); + + @CompilerDirectives.CompilationFinal(dimensions = 1) + private final Type[] types; + + private EnsoMultiType(Type[] types) { + this.types = types; + } + + @CompilerDirectives.TruffleBoundary + static EnsoMultiType findOrCreateSlow(Type[] types, int from, int to) { + var mt = new EnsoMultiType(Arrays.copyOfRange(types, from, to)); + return ALL_TYPES.computeIfAbsent(mt, java.util.function.Function.identity()); + } + + final int typesLength() { + return types.length; + } + + final Type firstType() { + return types[0]; + } + + private int find(EnsoContext ctx, Type type) { + for (var i = 0; i < types.length; i++) { + for (var t : types[i].allTypes(ctx)) { + if (t == type) { + return i; + } + } + } + return -1; + } + + @Override + @CompilerDirectives.TruffleBoundary + public int hashCode() { + int hash = 7; + hash = 89 * hash + Arrays.deepHashCode(this.types); + return hash; + } + + @Override + @CompilerDirectives.TruffleBoundary + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EnsoMultiType other = (EnsoMultiType) obj; + return Arrays.deepEquals(this.types, other.types); + } + + @Override + @CompilerDirectives.TruffleBoundary + public String toString() { + return "MultiType{" + "types=" + Arrays.toString(types) + '}'; + } + + @CompilerDirectives.TruffleBoundary + final boolean hasIntersectionWith(EnsoMultiType other) { + var my = new HashSet<>(Arrays.asList(types)); + var their = Arrays.asList(other.types); + my.removeAll(their); + return my.size() < types.length; + } + + @GenerateUncached + abstract static class FindIndexNode extends Node { + private static final String INLINE_CACHE_LIMIT = "5"; + + abstract int executeFindIndex(Type type, EnsoMultiType mt); + + @NeverDefault + public static FindIndexNode create() { + return EnsoMultiTypeFactory.FindIndexNodeGen.create(); + } + + @NeverDefault + public static FindIndexNode getUncached() { + return EnsoMultiTypeFactory.FindIndexNodeGen.getUncached(); + } + + @Specialization( + guards = {"type == cachedType", "mt == cachedMt"}, + limit = INLINE_CACHE_LIMIT) + int findsCachedIndexOfAType( + Type type, + EnsoMultiType mt, + @Cached("type") Type cachedType, + @Cached("mt") EnsoMultiType cachedMt, + @Cached(allowUncached = true, value = "findsAnIndexOfAType(type, mt)") int cachedIndex) { + return cachedIndex; + } + + @Specialization(replaces = "findsCachedIndexOfAType") + int findsAnIndexOfAType(Type type, EnsoMultiType mt) { + var ctx = EnsoContext.get(this); + var index = mt.find(ctx, type); + return index; + } + } + + @GenerateUncached + abstract static class AllTypesWith extends Node { + private static final String INLINE_CACHE_LIMIT = "5"; + + @NeverDefault + static AllTypesWith getUncached() { + return EnsoMultiTypeFactory.AllTypesWithNodeGen.getUncached(); + } + + @NeverDefault + static AllTypesWith create() { + return EnsoMultiTypeFactory.AllTypesWithNodeGen.create(); + } + + /** + * Don't modify the return value. It maybe cached! + * + * @param first first set of types + * @param second second set of types + * @return union of both types + */ + abstract Type[] executeAllTypes(EnsoMultiType first, EnsoMultiType second); + + @Specialization( + limit = INLINE_CACHE_LIMIT, + guards = { + "self == cachedSelf", + "nextOrNull == cachedNextOrNull", + }) + Type[] optimizeForTypes( + EnsoMultiType self, + EnsoMultiType nextOrNull, + @Cached("self") EnsoMultiType cachedSelf, + @Cached("nextOrNull") EnsoMultiType cachedNextOrNull, + @Cached("slowlyComputeTypes(self, nextOrNull)") Type[] result) { + return result; + } + + @Specialization(replaces = "optimizeForTypes") + Type[] slowlyComputeTypes(EnsoMultiType self, EnsoMultiType nextOrNull) { + if (nextOrNull == null || nextOrNull.types.length == 0) { + return self.types.clone(); + } else { + var next = nextOrNull; + var arr = new Type[self.types.length + next.types.length]; + System.arraycopy(self.types, 0, arr, 0, self.types.length); + System.arraycopy(next.types, 0, arr, self.types.length, next.types.length); + return arr; + } + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java index 22f31f090bd1..69cff2aeef74 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/EnsoMultiValue.java @@ -1,7 +1,9 @@ package org.enso.interpreter.runtime.data; +import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.NeverDefault; @@ -15,6 +17,7 @@ import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.Node; import java.math.BigInteger; import java.time.Duration; @@ -29,6 +32,7 @@ import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.data.EnsoMultiType.AllTypesWith; import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.graalvm.collections.Pair; @@ -36,40 +40,109 @@ @ExportLibrary(TypesLibrary.class) @ExportLibrary(InteropLibrary.class) public final class EnsoMultiValue extends EnsoObject { - - @CompilationFinal(dimensions = 1) - private final Type[] types; - - @CompilationFinal private final int methodDispatchTypes; + private final EnsoMultiType dispatch; + private final EnsoMultiType extra; @CompilationFinal(dimensions = 1) private final Object[] values; - private EnsoMultiValue(Type[] types, int dispatchTypes, Object[] values) { - this.types = types; - this.methodDispatchTypes = dispatchTypes; - assert types.length == values.length; + private EnsoMultiValue(EnsoMultiType dispatch, EnsoMultiType extra, Object[] values) { + assert !dispatch.hasIntersectionWith(extra) + : "Dispatch (" + dispatch + " and extra " + extra + " should be disjoin!"; + this.dispatch = dispatch; + this.extra = extra; this.values = values; - assert !Stream.of(values).anyMatch(v -> v instanceof EnsoMultiValue) - : "Avoid double wrapping " + Arrays.toString(values); + } + + /** Creates new instance of EnsoMultiValue from provided information. */ + @GenerateUncached + public abstract static class NewNode extends Node { + private static final String INLINE_CACHE_LIMIT = "5"; + + @NeverDefault + public static NewNode create() { + return EnsoMultiValueFactory.NewNodeGen.create(); + } + + @NeverDefault + public static NewNode getUncached() { + return EnsoMultiValueFactory.NewNodeGen.getUncached(); + } + + /** + * Creates new multi value from provided information. + * + * @param types all the types this value can be {@link CastToNode cast to} + * @param dispatchTypes the (subset of) types that the value is cast to currently - bigger than + * {@code 0} and at most {@code type.length} + * @param values value of each of the provided {@code types} + * @return non-{@code null} multi value instance + */ + @NeverDefault + public EnsoMultiValue newValue( + @NeverDefault Type[] types, + @NeverDefault int dispatchTypes, + @NeverDefault Object... values) { + assert dispatchTypes > 0; + assert dispatchTypes <= types.length; + assert types.length == values.length; + assert !Stream.of(values).anyMatch(v -> v instanceof EnsoMultiValue) + : "Avoid double wrapping " + Arrays.toString(values); + var dt = executeTypes(types, 0, dispatchTypes); + var et = executeTypes(types, dispatchTypes, types.length); + return new EnsoMultiValue(dt, et, values); + } + + abstract EnsoMultiType executeTypes(Type[] types, int from, int to); + + @Specialization( + guards = {"compareTypes(cachedTypes, types, from, to)"}, + limit = INLINE_CACHE_LIMIT) + final EnsoMultiType cachedMultiType( + Type[] types, + int from, + int to, + @Cached(value = "clone(types, from, to)", dimensions = 1) Type[] cachedTypes, + @Cached("createMultiType(types, from, to)") EnsoMultiType result) { + return result; + } + + @Specialization(replaces = "cachedMultiType") + final EnsoMultiType createMultiType(Type[] types, int from, int to) { + return EnsoMultiType.findOrCreateSlow(types, from, to); + } + + @TruffleBoundary + static final Type[] clone(Type[] types, int from, int to) { + return Arrays.copyOfRange(types, from, to); + } + + @ExplodeLoop + static final boolean compareTypes(Type[] cached, Type[] arr, int from, int to) { + CompilerAsserts.partialEvaluationConstant(cached); + if (cached.length != to - from) { + return false; + } + CompilerAsserts.partialEvaluationConstant(cached.length); + for (var i = 0; i < cached.length; i++) { + CompilerAsserts.partialEvaluationConstant(cached[i]); + if (cached[i] != arr[from++]) { + return false; + } + } + return true; + } } /** - * Creates new instance of EnsoMultiValue from provided information. + * The "dispatch identity" of the multi value. If two multivalues have the same identity, they are + * going to resolve methods the same way. * - * @param types all the types this value can be {@link CastToNode cast to} - * @param dispatchTypes the (subset of) types that the value is cast to currently - bigger than - * {@code 0} and at most {@code type.length} - * @param values value of each of the provided {@code types} - * @return non-{@code null} multi value instance + * @return an opaque object that can be used for caching dispatch logic */ - @NeverDefault - public static EnsoMultiValue create( - @NeverDefault Type[] types, @NeverDefault int dispatchTypes, @NeverDefault Object... values) { - assert dispatchTypes > 0; - assert dispatchTypes <= types.length; - assert types.length == values.length; - return new EnsoMultiValue(types, dispatchTypes, values); + public final Object getDispatchId() { + // intentionally typed as Object to avoid exposing EnsoMultiType + return dispatch; } @ExportMessage @@ -84,15 +157,16 @@ boolean hasSpecialDispatch() { @ExportMessage final Type getType() { - return types[0]; + return dispatch.firstType(); } @ExportMessage - final Type[] allTypes(boolean includeExtraTypes) { - if (includeExtraTypes || methodDispatchTypes == types.length) { - return types.clone(); + final Type[] allTypes( + boolean includeExtraTypes, @Cached EnsoMultiType.AllTypesWith allTypesWith) { + if (!includeExtraTypes) { + return allTypesWith.executeAllTypes(dispatch, null); } else { - return Arrays.copyOf(types, methodDispatchTypes); + return allTypesWith.executeAllTypes(dispatch, extra); } } @@ -151,16 +225,20 @@ static Value find(Object[] values, int max, InteropLibrary iop) { } } + private InteropType.Value findInteropTypeValue(InteropLibrary iop) { + return InteropType.find(values, dispatch.typesLength(), iop); + } + @ExportMessage boolean isBoolean(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.BOOLEAN; } @ExportMessage boolean asBoolean(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.BOOLEAN) { return iop.asBoolean(both.value()); } @@ -169,14 +247,14 @@ boolean asBoolean(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary @ExportMessage boolean isString(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.STRING; } @ExportMessage String asString(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.STRING) { return iop.asString(both.value()); } @@ -185,50 +263,50 @@ String asString(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary i @ExportMessage boolean isNumber(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.NUMBER; } @ExportMessage boolean fitsInByte(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.NUMBER && iop.fitsInByte(both.value()); } @ExportMessage boolean fitsInShort(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.NUMBER && iop.fitsInShort(both.value()); } @ExportMessage boolean fitsInInt(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.NUMBER && iop.fitsInInt(both.value()); } @ExportMessage boolean fitsInLong(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.NUMBER && iop.fitsInLong(both.value()); } @ExportMessage boolean fitsInFloat(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.NUMBER && iop.fitsInFloat(both.value()); } @ExportMessage boolean fitsInDouble(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.NUMBER && iop.fitsInDouble(both.value()); } @ExportMessage byte asByte(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.NUMBER) { return iop.asByte(both.value()); } @@ -238,7 +316,7 @@ byte asByte(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) @ExportMessage short asShort(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.NUMBER) { return iop.asShort(both.value()); } @@ -248,7 +326,7 @@ short asShort(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop @ExportMessage int asInt(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.NUMBER) { return iop.asInt(both.value()); } @@ -258,7 +336,7 @@ int asInt(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) @ExportMessage long asLong(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.NUMBER) { return iop.asLong(both.value()); } @@ -268,7 +346,7 @@ long asLong(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) @ExportMessage float asFloat(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.NUMBER) { return iop.asFloat(both.value()); } @@ -278,7 +356,7 @@ float asFloat(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop @ExportMessage double asDouble(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.NUMBER) { return iop.asDouble(both.value()); } @@ -287,14 +365,14 @@ float asFloat(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop @ExportMessage boolean fitsInBigInteger(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.NUMBER && iop.fitsInBigInteger(both.value()); } @ExportMessage BigInteger asBigInteger(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.NUMBER) { return iop.asBigInteger(both.value()); } @@ -303,14 +381,14 @@ BigInteger asBigInteger(@Shared("interop") @CachedLibrary(limit = "10") InteropL @ExportMessage boolean isTime(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.DATE_TIME_ZONE && iop.isTime(both.value()); } @ExportMessage LocalTime asTime(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.DATE_TIME_ZONE) { return iop.asTime(both.value()); } @@ -319,14 +397,14 @@ LocalTime asTime(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary @ExportMessage boolean isDate(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.DATE_TIME_ZONE && iop.isDate(both.value()); } @ExportMessage LocalDate asDate(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.DATE_TIME_ZONE) { return iop.asDate(both.value()); } @@ -335,14 +413,14 @@ LocalDate asDate(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary @ExportMessage boolean isTimeZone(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.DATE_TIME_ZONE && iop.isTimeZone(both.value()); } @ExportMessage ZoneId asTimeZone(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.DATE_TIME_ZONE) { return iop.asTimeZone(both.value()); } @@ -351,14 +429,14 @@ ZoneId asTimeZone(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary @ExportMessage boolean isDuration(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); return both.type() == InteropType.DURATION; } @ExportMessage Duration asDuration(@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) throws UnsupportedMessageException { - var both = InteropType.find(values, methodDispatchTypes, iop); + var both = findInteropTypeValue(iop); if (both.type() == InteropType.DURATION) { return iop.asDuration(both.value()); } @@ -375,7 +453,7 @@ boolean hasMembers() { Object getMembers( boolean includeInternal, @Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { var names = new TreeSet(); - for (var i = 0; i < methodDispatchTypes; i++) { + for (var i = 0; i < dispatch.typesLength(); i++) { try { var members = iop.getMembers(values[i]); var len = iop.getArraySize(members); @@ -392,7 +470,7 @@ Object getMembers( @ExportMessage boolean isMemberInvocable( String name, @Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) { - for (var i = 0; i < methodDispatchTypes; i++) { + for (var i = 0; i < dispatch.typesLength(); i++) { if (iop.isMemberInvocable(values[i], name)) { return true; } @@ -409,7 +487,7 @@ Object invokeMember( ArityException, UnsupportedTypeException, UnknownIdentifierException { - for (var i = 0; i < methodDispatchTypes; i++) { + for (var i = 0; i < dispatch.typesLength(); i++) { if (iop.isMemberInvocable(values[i], name)) { return iop.invokeMember(values[i], name, args); } @@ -420,12 +498,39 @@ Object invokeMember( @TruffleBoundary @Override public String toString() { - return Arrays.stream(types).map(t -> t.getName()).collect(Collectors.joining(" & ")); + var both = EnsoMultiType.AllTypesWith.getUncached().executeAllTypes(dispatch, extra); + return Stream.of(both).map(t -> t.getName()).collect(Collectors.joining(" & ")); } /** Casts {@link EnsoMultiValue} to requested type effectively. */ - @GenerateUncached - public abstract static class CastToNode extends Node { + public static final class CastToNode extends Node { + private static final CastToNode UNCACHED = + new CastToNode( + EnsoMultiType.FindIndexNode.getUncached(), + NewNode.getUncached(), + AllTypesWith.getUncached()); + @Child private EnsoMultiType.FindIndexNode findNode; + @Child private NewNode newNode; + @Child private AllTypesWith allTypesWith; + + private CastToNode(EnsoMultiType.FindIndexNode f, NewNode n, AllTypesWith a) { + this.findNode = f; + this.newNode = n; + this.allTypesWith = a; + } + + @NeverDefault + public static CastToNode create() { + return new CastToNode( + EnsoMultiType.FindIndexNode.create(), NewNode.create(), AllTypesWith.create()); + } + + @NeverDefault + @TruffleBoundary + public static CastToNode getUncached() { + return UNCACHED; + } + /** * Casts value in a multi value into specific type. * @@ -438,44 +543,32 @@ public abstract static class CastToNode extends Node { */ public final Object findTypeOrNull( Type type, EnsoMultiValue mv, boolean reorderOnly, boolean allTypes) { - return executeCast(type, mv, reorderOnly, allTypes); - } - - abstract Object executeCast( - Type type, EnsoMultiValue mv, boolean reorderOnly, boolean allTypes); - - @NeverDefault - public static CastToNode create() { - return EnsoMultiValueFactory.CastToNodeGen.create(); - } - - @NeverDefault - public static CastToNode getUncached() { - return EnsoMultiValueFactory.CastToNodeGen.getUncached(); - } - - @Specialization - Object castsToAType(Type type, EnsoMultiValue mv, boolean reorderOnly, boolean allTypes) { - var ctx = EnsoContext.get(this); - var max = allTypes ? mv.types.length : mv.methodDispatchTypes; - for (var i = 0; i < max; i++) { - for (var t : mv.types[i].allTypes(ctx)) { - if (t == type) { - if (reorderOnly) { - var copyTypes = mv.types.clone(); - var copyValues = mv.values.clone(); - copyTypes[i] = mv.types[0]; - copyValues[i] = mv.values[0]; - copyTypes[0] = mv.types[i]; - copyValues[0] = mv.values[i]; - return EnsoMultiValue.create(copyTypes, 1, copyValues); - } else { - return mv.values[i]; - } + var dispatch = mv.dispatch; + var i = findNode.executeFindIndex(type, dispatch); + if (i == -1 && allTypes) { + var extraIndex = findNode.executeFindIndex(type, mv.extra); + i = extraIndex == -1 ? -1 : dispatch.typesLength() + extraIndex; + } + if (i != -1) { + if (reorderOnly) { + var copyTypes = allTypesWith.executeAllTypes(dispatch, mv.extra); + if (i == 0 && dispatch.typesLength() == 1) { + return newNode.newValue(copyTypes, 1, mv.values); + } else { + copyTypes = copyTypes.clone(); + var copyValues = mv.values.clone(); + copyTypes[0] = copyTypes[i]; + copyValues[0] = copyValues[i]; + copyTypes[i] = dispatch.firstType(); + copyValues[i] = mv.values[0]; + return newNode.newValue(copyTypes, 1, copyValues); } + } else { + return mv.values[i]; } + } else { + return null; } - return null; } } @@ -490,11 +583,10 @@ public final Pair resolveSymbol( MethodResolverNode node, UnresolvedSymbol symbol) { var ctx = EnsoContext.get(node); Pair foundAnyMethod = null; - for (var i = 0; i < methodDispatchTypes; i++) { - var t = types[i]; + for (var t : EnsoMultiType.AllTypesWith.getUncached().executeAllTypes(dispatch, null)) { var fnAndType = node.execute(t, symbol); if (fnAndType != null) { - if (methodDispatchTypes == 1 || fnAndType.getRight() != ctx.getBuiltins().any()) { + if (dispatch.typesLength() == 1 || fnAndType.getRight() != ctx.getBuiltins().any()) { return Pair.create(fnAndType.getLeft(), t); } foundAnyMethod = fnAndType; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java index 468bbbbffa5f..e1a5a55ae42a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/PanicException.java @@ -55,10 +55,7 @@ public PanicException(Object payload, Node location) { */ public PanicException(Object payload, Throwable cause, Node location) { super(null, cause, UNLIMITED_STACK_TRACE, location); - if (!InteropLibrary.isValidValue(payload)) { - CompilerDirectives.transferToInterpreter(); - throw new IllegalArgumentException("Only interop values are supported: " + payload); - } + assert InteropLibrary.isValidValue(payload) : "Only interop values are supported: " + payload; this.payload = payload; }