mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 07:12:20 +03:00
Removing Unsafe.set_atom_field (#4023)
Introducing `Meta.atom_with_hole` to create an `Atom` _with a hole_ that is then _safely_ filled in later.
This commit is contained in:
parent
3379ce51f2
commit
41b2aac39f
@ -498,6 +498,7 @@
|
|||||||
- [IGV can jump to JMH sources & more][4008]
|
- [IGV can jump to JMH sources & more][4008]
|
||||||
- [Basic support of VSCode integration][4014]
|
- [Basic support of VSCode integration][4014]
|
||||||
- [Sync language server with file system after VCS restore][4020]
|
- [Sync language server with file system after VCS restore][4020]
|
||||||
|
- [Introducing Meta.atom_with_hole][4023]
|
||||||
- [Report failures in name resolution in type signatures][4030]
|
- [Report failures in name resolution in type signatures][4030]
|
||||||
|
|
||||||
[3227]: https://github.com/enso-org/enso/pull/3227
|
[3227]: https://github.com/enso-org/enso/pull/3227
|
||||||
@ -580,6 +581,7 @@
|
|||||||
[4008]: https://github.com/enso-org/enso/pull/4008
|
[4008]: https://github.com/enso-org/enso/pull/4008
|
||||||
[4014]: https://github.com/enso-org/enso/pull/4014
|
[4014]: https://github.com/enso-org/enso/pull/4014
|
||||||
[4020]: https://github.com/enso-org/enso/pull/4020
|
[4020]: https://github.com/enso-org/enso/pull/4020
|
||||||
|
[4023]: https://github.com/enso-org/enso/pull/4023
|
||||||
[4030]: https://github.com/enso-org/enso/pull/4030
|
[4030]: https://github.com/enso-org/enso/pull/4030
|
||||||
|
|
||||||
# Enso 2.0.0-alpha.18 (2021-10-12)
|
# Enso 2.0.0-alpha.18 (2021-10-12)
|
||||||
|
@ -7,7 +7,7 @@ import project.Data.Vector.Vector
|
|||||||
import project.Error.Error
|
import project.Error.Error
|
||||||
import project.Function.Function
|
import project.Function.Function
|
||||||
import project.Nothing.Nothing
|
import project.Nothing.Nothing
|
||||||
import project.Runtime.Unsafe
|
import project.Meta
|
||||||
|
|
||||||
from project.Data.Boolean import Boolean, True, False
|
from project.Data.Boolean import Boolean, True, False
|
||||||
|
|
||||||
@ -185,13 +185,19 @@ type List
|
|||||||
filter : (Filter_Condition | (Any -> Boolean)) -> Vector Any
|
filter : (Filter_Condition | (Any -> Boolean)) -> Vector Any
|
||||||
filter self filter = case filter of
|
filter self filter = case filter of
|
||||||
_ : Filter_Condition -> self.filter filter.to_predicate
|
_ : Filter_Condition -> self.filter filter.to_predicate
|
||||||
predicate : Function ->
|
predicate : Function -> case self of
|
||||||
go_filter list = case list of
|
|
||||||
Nil -> Nil
|
Nil -> Nil
|
||||||
Cons h t ->
|
Cons x xs -> if predicate x . not then @Tail_Call xs.filter filter else
|
||||||
rest = go_filter t
|
go list fill = case list of
|
||||||
if predicate h then Cons h rest else rest
|
Nil -> fill Nil
|
||||||
go_filter self
|
Cons h t -> if predicate h . not then @Tail_Call go t fill else
|
||||||
|
res = Meta.atom_with_hole (Cons h _)
|
||||||
|
fill res.value
|
||||||
|
@Tail_Call go t res.fill
|
||||||
|
|
||||||
|
res = Meta.atom_with_hole (Cons x _)
|
||||||
|
go xs res.fill
|
||||||
|
res.value
|
||||||
|
|
||||||
## Applies a function to each element of the list, returning the list of
|
## Applies a function to each element of the list, returning the list of
|
||||||
results.
|
results.
|
||||||
@ -209,9 +215,18 @@ type List
|
|||||||
map self f = case self of
|
map self f = case self of
|
||||||
Nil -> Nil
|
Nil -> Nil
|
||||||
Cons h t ->
|
Cons h t ->
|
||||||
res = Cons (f h) Nil
|
go : List -> Any -> (Any -> Any) -> Nothing
|
||||||
map_helper t res f
|
go list fill = case list of
|
||||||
res
|
Nil -> fill Nil
|
||||||
|
Cons h t ->
|
||||||
|
v = f h
|
||||||
|
res = Meta.atom_with_hole (Cons v _)
|
||||||
|
fill res.value
|
||||||
|
@Tail_Call go t res.fill
|
||||||
|
v = f h
|
||||||
|
res = Meta.atom_with_hole (Cons v _)
|
||||||
|
go t res.fill
|
||||||
|
res.value
|
||||||
|
|
||||||
## Applies a function to each element of the list.
|
## Applies a function to each element of the list.
|
||||||
|
|
||||||
@ -281,7 +296,16 @@ type List
|
|||||||
take_start : Integer -> List
|
take_start : Integer -> List
|
||||||
take_start self count = if count <= 0 then Nil else case self of
|
take_start self count = if count <= 0 then Nil else case self of
|
||||||
Nil -> Nil
|
Nil -> Nil
|
||||||
Cons a b -> Cons a (b.take_start count-1)
|
Cons a b ->
|
||||||
|
go c l fill = if c <= 0 then fill Nil else case l of
|
||||||
|
Nil -> fill Nil
|
||||||
|
Cons a b ->
|
||||||
|
res = Meta.atom_with_hole (Cons a _)
|
||||||
|
fill res.value
|
||||||
|
@Tail_Call go c-1 b res.fill
|
||||||
|
res = Meta.atom_with_hole (Cons a _)
|
||||||
|
go count-1 b res.fill
|
||||||
|
res.value
|
||||||
|
|
||||||
## Get the first element from the list.
|
## Get the first element from the list.
|
||||||
|
|
||||||
@ -319,12 +343,21 @@ type List
|
|||||||
example_init = Examples.list.init
|
example_init = Examples.list.init
|
||||||
init : List ! Empty_Error
|
init : List ! Empty_Error
|
||||||
init self =
|
init self =
|
||||||
init_fn x y = case y of
|
|
||||||
Nil -> Nil
|
|
||||||
Cons a b -> Cons x (init_fn a b)
|
|
||||||
case self of
|
case self of
|
||||||
Nil -> Error.throw Empty_Error
|
Nil -> Error.throw Empty_Error
|
||||||
Cons a b -> init_fn a b
|
Cons _ Nil -> Nil
|
||||||
|
Cons a b ->
|
||||||
|
go l fill = case l of
|
||||||
|
Cons x xs -> case xs of
|
||||||
|
Nil -> fill Nil
|
||||||
|
Cons _ _ ->
|
||||||
|
res = Meta.atom_with_hole (Cons x _)
|
||||||
|
fill res.value
|
||||||
|
@Tail_Call go xs res.fill
|
||||||
|
|
||||||
|
res = Meta.atom_with_hole (Cons a _)
|
||||||
|
go b res.fill
|
||||||
|
res.value
|
||||||
|
|
||||||
## Get the last element of the list.
|
## Get the last element of the list.
|
||||||
|
|
||||||
@ -369,6 +402,13 @@ type List
|
|||||||
builder.append elem
|
builder.append elem
|
||||||
builder.to_vector
|
builder.to_vector
|
||||||
|
|
||||||
|
to_text : Text
|
||||||
|
to_text self =
|
||||||
|
go l t = case l of
|
||||||
|
Nil -> t + "Nil"
|
||||||
|
Cons x xs -> @Tail_Call go xs (t + "Cons " + x.to_text + " ")
|
||||||
|
go self ""
|
||||||
|
|
||||||
## UNSTABLE
|
## UNSTABLE
|
||||||
|
|
||||||
An error representing that the list is empty.
|
An error representing that the list is empty.
|
||||||
@ -379,21 +419,3 @@ type Empty_Error
|
|||||||
to_display_text : Text
|
to_display_text : Text
|
||||||
to_display_text self = "The List is empty."
|
to_display_text self = "The List is empty."
|
||||||
|
|
||||||
## PRIVATE
|
|
||||||
A helper for the `map` function.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- list: The list to map over.
|
|
||||||
- cons: The current field to set.
|
|
||||||
- f: The function to apply to the value.
|
|
||||||
|
|
||||||
Uses unsafe field mutation under the hood, to rewrite `map` in
|
|
||||||
a tail-recursive manner. The mutation is purely internal and does not leak
|
|
||||||
to the user-facing API.
|
|
||||||
map_helper : List -> Any -> (Any -> Any) -> Nothing
|
|
||||||
map_helper list cons f = case list of
|
|
||||||
Nil -> Unsafe.set_atom_field cons 1 Nil
|
|
||||||
Cons h t ->
|
|
||||||
res = Cons (f h) Nil
|
|
||||||
Unsafe.set_atom_field cons 1 res
|
|
||||||
@Tail_Call map_helper t res f
|
|
||||||
|
@ -273,6 +273,17 @@ get_constructor_name atom_constructor = @Builtin_Method "Meta.get_constructor_na
|
|||||||
new_atom : Any -> Array -> Atom
|
new_atom : Any -> Array -> Atom
|
||||||
new_atom constructor fields = @Builtin_Method "Meta.new_atom"
|
new_atom constructor fields = @Builtin_Method "Meta.new_atom"
|
||||||
|
|
||||||
|
## PRIVATE
|
||||||
|
|
||||||
|
Constructs a new atom with a "hole". Returns an object with `value` and
|
||||||
|
`fill` properties. Value contains the created atom and `fill` holds a
|
||||||
|
function to "fill the hole" later.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- factory: a function that takes the "hole" element and returns newly created atom
|
||||||
|
atom_with_hole : (Any -> Atom) -> Any
|
||||||
|
atom_with_hole factory = @Builtin_Method "Meta.atom_with_hole_builtin"
|
||||||
|
|
||||||
## UNSTABLE
|
## UNSTABLE
|
||||||
ADVANCED
|
ADVANCED
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
## Unsafe operations.
|
|
||||||
A container for unsafe operations that operate based on implementation
|
|
||||||
details of the language.
|
|
||||||
|
|
||||||
import project.Any.Any
|
|
||||||
import project.Data.Numbers.Integer
|
|
||||||
import project.Meta.Atom
|
|
||||||
|
|
||||||
## PRIVATE
|
|
||||||
|
|
||||||
Sets the atom field at the provided index to have the provided value.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
- atom: The atom to set the field in.
|
|
||||||
- index: The index of the field to set (zero-based).
|
|
||||||
- value: The value to set the field at index to.
|
|
||||||
set_atom_field : Atom -> Integer -> Any -> Atom
|
|
||||||
set_atom_field atom index value = @Builtin_Method "Unsafe.set_atom_field"
|
|
@ -0,0 +1,107 @@
|
|||||||
|
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import org.graalvm.polyglot.Context;
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@Fork(1)
|
||||||
|
@Warmup(iterations = 3)
|
||||||
|
@Measurement(iterations = 5)
|
||||||
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
public class ListBenchmarks {
|
||||||
|
private final int LENGTH_OF_EXPERIMENT = 1_000_000;
|
||||||
|
private Value list;
|
||||||
|
private Value plusOne;
|
||||||
|
private Value self;
|
||||||
|
private Value sum;
|
||||||
|
private Value oldSum;
|
||||||
|
|
||||||
|
@Setup
|
||||||
|
public void initializeBenchmark(BenchmarkParams params) throws Exception {
|
||||||
|
var ctx = Context.newBuilder()
|
||||||
|
.allowExperimentalOptions(true)
|
||||||
|
.allowIO(true)
|
||||||
|
.allowAllAccess(true)
|
||||||
|
.logHandler(new ByteArrayOutputStream())
|
||||||
|
.option(
|
||||||
|
"enso.languageHomeOverride",
|
||||||
|
Paths.get("../../distribution/component").toFile().getAbsolutePath()
|
||||||
|
).build();
|
||||||
|
|
||||||
|
var benchmarkName = params.getBenchmark().replaceFirst(".*\\.", "");
|
||||||
|
var code = """
|
||||||
|
from Standard.Base.Data.List.List import Cons, Nil
|
||||||
|
import Standard.Base.IO
|
||||||
|
|
||||||
|
plus_one list = list.map (x -> x + 1)
|
||||||
|
|
||||||
|
sum list acc =
|
||||||
|
case list of
|
||||||
|
Nil -> acc
|
||||||
|
Cons x xs -> @Tail_Call sum xs acc+x
|
||||||
|
|
||||||
|
generator n =
|
||||||
|
go x v l = if x > n then l else
|
||||||
|
@Tail_Call go x+1 v+1 (Cons v l)
|
||||||
|
go 1 1 Nil
|
||||||
|
""";
|
||||||
|
|
||||||
|
var module = ctx.eval(SrcUtil.source(benchmarkName, code));
|
||||||
|
|
||||||
|
this.self = module.invokeMember("get_associated_type");
|
||||||
|
Function<String,Value> getMethod = (name) -> module.invokeMember("get_method", self, name);
|
||||||
|
|
||||||
|
Value longList = getMethod.apply("generator").execute(self, LENGTH_OF_EXPERIMENT);
|
||||||
|
|
||||||
|
this.plusOne = getMethod.apply("plus_one");
|
||||||
|
this.sum = getMethod.apply("sum");
|
||||||
|
|
||||||
|
switch (benchmarkName) {
|
||||||
|
case "mapOverList": {
|
||||||
|
this.list = longList;
|
||||||
|
this.oldSum = sum.execute(self, longList, 0);
|
||||||
|
if (!this.oldSum.fitsInLong()) {
|
||||||
|
throw new AssertionError("Expecting a number " + this.oldSum);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unexpected benchmark: " + params.getBenchmark());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public void mapOverList(Blackhole matter) {
|
||||||
|
performBenchmark(matter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performBenchmark(Blackhole hole) throws AssertionError {
|
||||||
|
var newList = plusOne.execute(self, list);
|
||||||
|
var newSum = sum.execute(self, newList, 0);
|
||||||
|
|
||||||
|
var result = newSum.asLong() - oldSum.asLong();
|
||||||
|
if (result != LENGTH_OF_EXPERIMENT) {
|
||||||
|
throw new AssertionError("Unexpected result " + result);
|
||||||
|
}
|
||||||
|
hole.consume(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
@ -0,0 +1,196 @@
|
|||||||
|
package org.enso.interpreter.node.expression.builtin.meta;
|
||||||
|
|
||||||
|
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||||
|
import org.enso.interpreter.runtime.EnsoContext;
|
||||||
|
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition;
|
||||||
|
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||||
|
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||||
|
import org.enso.interpreter.runtime.callable.function.Function;
|
||||||
|
import org.enso.interpreter.runtime.callable.function.FunctionSchema;
|
||||||
|
import org.enso.interpreter.runtime.data.Array;
|
||||||
|
import org.enso.interpreter.runtime.data.Vector;
|
||||||
|
import org.enso.interpreter.runtime.error.PanicException;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.CompilerDirectives;
|
||||||
|
import com.oracle.truffle.api.dsl.Cached;
|
||||||
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
|
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||||
|
import com.oracle.truffle.api.interop.TruffleObject;
|
||||||
|
import com.oracle.truffle.api.interop.UnknownIdentifierException;
|
||||||
|
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||||
|
import com.oracle.truffle.api.library.ExportLibrary;
|
||||||
|
import com.oracle.truffle.api.library.ExportMessage;
|
||||||
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
|
import com.oracle.truffle.api.nodes.RootNode;
|
||||||
|
import com.oracle.truffle.api.profiles.ValueProfile;
|
||||||
|
import org.enso.interpreter.node.callable.InvokeCallableNode;
|
||||||
|
import org.enso.interpreter.runtime.state.State;
|
||||||
|
|
||||||
|
@BuiltinMethod(
|
||||||
|
type = "Meta",
|
||||||
|
name = "atom_with_hole_builtin",
|
||||||
|
description = "Creates a new atom with given constructor and fields.",
|
||||||
|
autoRegister = false)
|
||||||
|
public abstract class AtomWithAHoleNode extends Node {
|
||||||
|
|
||||||
|
static AtomWithAHoleNode build() {
|
||||||
|
return AtomWithAHoleNodeGen.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract Object execute(VirtualFrame frame, Object factory);
|
||||||
|
|
||||||
|
static InvokeCallableNode callWithHole() {
|
||||||
|
return InvokeCallableNode.build(
|
||||||
|
new CallArgumentInfo[] {new CallArgumentInfo()},
|
||||||
|
InvokeCallableNode.DefaultsExecutionMode.EXECUTE,
|
||||||
|
InvokeCallableNode.ArgumentsExecutionMode.PRE_EXECUTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
Object doExecute(
|
||||||
|
VirtualFrame frame,
|
||||||
|
Object factory,
|
||||||
|
@Cached("callWithHole()") InvokeCallableNode iop,
|
||||||
|
@Cached SwapAtomFieldNode swapNode
|
||||||
|
) {
|
||||||
|
var ctx = EnsoContext.get(this);
|
||||||
|
var lazy = new HoleInAtom();
|
||||||
|
var result = iop.execute(factory, frame, State.create(ctx), new Object[] { lazy });
|
||||||
|
if (result instanceof Atom atom) {
|
||||||
|
var index = swapNode.findHoleIndex(atom, lazy);
|
||||||
|
if (index >= 0) {
|
||||||
|
var function = swapNode.createFn(lazy);
|
||||||
|
lazy.init(atom, index, function);
|
||||||
|
return lazy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new PanicException(ctx.getBuiltins().error().makeUninitializedStateError(result), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExportLibrary(InteropLibrary.class)
|
||||||
|
static final class HoleInAtom implements TruffleObject {
|
||||||
|
Atom result;
|
||||||
|
int index;
|
||||||
|
Function function;
|
||||||
|
|
||||||
|
HoleInAtom() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(Atom result, int index, Function function) {
|
||||||
|
this.result = result;
|
||||||
|
this.index = index;
|
||||||
|
this.function = function;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExportMessage boolean hasMembers() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExportMessage boolean isMemberReadable(String member) {
|
||||||
|
return switch (member) {
|
||||||
|
case "value", "fill" -> true;
|
||||||
|
default -> false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExportMessage Object getMembers(boolean includeInternal) throws UnsupportedMessageException {
|
||||||
|
return new Array("value", "fill");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExportMessage
|
||||||
|
Object readMember(String name) throws UnknownIdentifierException {
|
||||||
|
if ("value".equals(name)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if ("fill".equals(name)) {
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
throw UnknownIdentifierException.create(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExportMessage
|
||||||
|
String toDisplayString(boolean pure) {
|
||||||
|
return "Meta.atom_with_hole";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static final class SwapAtomFieldNode extends RootNode {
|
||||||
|
private final FunctionSchema schema;
|
||||||
|
private final ValueProfile sameAtom = ValueProfile.createClassProfile();
|
||||||
|
@CompilerDirectives.CompilationFinal
|
||||||
|
private int lastIndex = -1;
|
||||||
|
|
||||||
|
private SwapAtomFieldNode() {
|
||||||
|
super(null);
|
||||||
|
this.schema = new FunctionSchema(FunctionSchema.CallerFrameAccess.NONE, new ArgumentDefinition[]{
|
||||||
|
new ArgumentDefinition(0, "lazy", ArgumentDefinition.ExecutionMode.EXECUTE),
|
||||||
|
new ArgumentDefinition(1, "value", ArgumentDefinition.ExecutionMode.EXECUTE)
|
||||||
|
}, new boolean[]{
|
||||||
|
true, false
|
||||||
|
}, new CallArgumentInfo[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static SwapAtomFieldNode create() {
|
||||||
|
return new SwapAtomFieldNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
int findHoleIndex(Atom atom, HoleInAtom lazy) {
|
||||||
|
var arr = atom.getFields();
|
||||||
|
if (lastIndex >= 0 && lastIndex < arr.length) {
|
||||||
|
if (arr[lastIndex] == lazy) {
|
||||||
|
return lastIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int index = findHoleIndexLoop(arr, lazy);
|
||||||
|
if (index == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (lastIndex == -1) {
|
||||||
|
lastIndex = index;
|
||||||
|
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||||
|
return index;
|
||||||
|
} else {
|
||||||
|
if (lastIndex != -2) {
|
||||||
|
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||||
|
lastIndex = -2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@CompilerDirectives.TruffleBoundary
|
||||||
|
private int findHoleIndexLoop(Object[] arr, HoleInAtom lazy) {
|
||||||
|
for (int i = 0; i < arr.length; i++) {
|
||||||
|
if (arr[i] == lazy) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Function createFn(HoleInAtom lazy) {
|
||||||
|
var preArgs = new Object[]{lazy, null};
|
||||||
|
return new Function(
|
||||||
|
getCallTarget(),
|
||||||
|
null,
|
||||||
|
schema,
|
||||||
|
preArgs,
|
||||||
|
new Object[]{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object execute(VirtualFrame frame) {
|
||||||
|
var args = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments());
|
||||||
|
if (args[0] instanceof HoleInAtom lazy) {
|
||||||
|
var fields = lazy.result.getFields();
|
||||||
|
var newValue = args[1];
|
||||||
|
if (fields[lazy.index] == lazy) {
|
||||||
|
fields[lazy.index] = newValue;
|
||||||
|
}
|
||||||
|
return newValue;
|
||||||
|
}
|
||||||
|
return EnsoContext.get(this).getBuiltins().nothing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +0,0 @@
|
|||||||
package org.enso.interpreter.node.expression.builtin.unsafe;
|
|
||||||
|
|
||||||
import com.oracle.truffle.api.nodes.Node;
|
|
||||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
|
||||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
|
||||||
|
|
||||||
@BuiltinMethod(
|
|
||||||
type = "Unsafe",
|
|
||||||
name = "set_atom_field",
|
|
||||||
description = "Unsafely, in place, sets the value of an atom field by index.",
|
|
||||||
autoRegister = false)
|
|
||||||
public class SetAtomFieldNode extends Node {
|
|
||||||
Atom execute(Atom atom, long index, Object value) {
|
|
||||||
atom.getFields()[(int) index] = value;
|
|
||||||
return atom;
|
|
||||||
}
|
|
||||||
}
|
|
@ -613,69 +613,6 @@ public class DebuggingEnsoTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO[PM]: Re-enable (https://www.pivotaltracker.com/story/show/183854585)
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void unsafeRecursiveAtom() throws Exception {
|
|
||||||
Engine eng = Engine.newBuilder()
|
|
||||||
.allowExperimentalOptions(true)
|
|
||||||
.option(
|
|
||||||
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
|
|
||||||
Paths.get("../../test/micro-distribution/component").toFile().getAbsolutePath()
|
|
||||||
).build();
|
|
||||||
Context ctx = Context.newBuilder()
|
|
||||||
.engine(eng)
|
|
||||||
.allowIO(true)
|
|
||||||
.allowHostClassLoading(true)
|
|
||||||
.allowHostClassLookup((c) -> true)
|
|
||||||
.build();
|
|
||||||
final Map<String, Language> langs = ctx.getEngine().getLanguages();
|
|
||||||
org.junit.Assert.assertNotNull("Enso found: " + langs, langs.get("enso"));
|
|
||||||
|
|
||||||
final URI onceUri = new URI("memory://once.enso");
|
|
||||||
final Source onesSrc = Source.newBuilder("enso", """
|
|
||||||
import Standard.Base.Runtime.Unsafe
|
|
||||||
|
|
||||||
type Gen
|
|
||||||
Empty
|
|
||||||
Generator a:Int tail:Gen
|
|
||||||
|
|
||||||
ones : Gen
|
|
||||||
ones =
|
|
||||||
g = Gen.Generator 1 Gen.Empty
|
|
||||||
Unsafe.set_atom_field g 1 g
|
|
||||||
g
|
|
||||||
|
|
||||||
next g = case g of
|
|
||||||
Gen.Generator a tail -> a
|
|
||||||
Gen.Empty -> -1
|
|
||||||
""", "ones.enso")
|
|
||||||
.uri(onceUri)
|
|
||||||
.buildLiteral();
|
|
||||||
|
|
||||||
var module = ctx.eval(onesSrc);
|
|
||||||
var ones = module.invokeMember("eval_expression", "ones");
|
|
||||||
var next = module.invokeMember("eval_expression", "next");
|
|
||||||
|
|
||||||
|
|
||||||
final var dbg = Debugger.find(eng);
|
|
||||||
final var values = new HashSet<String>();
|
|
||||||
try (var session = dbg.startSession((event) -> {
|
|
||||||
final DebugValue gVariable = findDebugValue(event, "g");
|
|
||||||
if (gVariable != null) {
|
|
||||||
final String str = gVariable.toDisplayString(false);
|
|
||||||
assertNotNull("The string shall always be computed for " + gVariable, str);
|
|
||||||
values.add(str);
|
|
||||||
}
|
|
||||||
event.getSession().suspendNextExecution();
|
|
||||||
})) {
|
|
||||||
session.suspendNextExecution();
|
|
||||||
var one = next.execute(ones);
|
|
||||||
Assert.assertEquals("First element from list of ones", 1, one.asInt());
|
|
||||||
}
|
|
||||||
Assert.assertEquals("Some values of g variable found: " + values, 1, values.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DebugValue findDebugValue(SuspendedEvent event, final String n) throws DebugException {
|
private static DebugValue findDebugValue(SuspendedEvent event, final String n) throws DebugException {
|
||||||
for (var v : event.getTopStackFrame().getScope().getDeclaredValues()) {
|
for (var v : event.getTopStackFrame().getScope().getDeclaredValues()) {
|
||||||
if (v.getName().contains(n)) {
|
if (v.getName().contains(n)) {
|
||||||
|
@ -0,0 +1,165 @@
|
|||||||
|
package org.enso.interpreter.test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.enso.polyglot.RuntimeOptions;
|
||||||
|
import org.graalvm.polyglot.Context;
|
||||||
|
import org.graalvm.polyglot.Language;
|
||||||
|
import org.graalvm.polyglot.Source;
|
||||||
|
import org.graalvm.polyglot.Value;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ListTest {
|
||||||
|
private Context ctx;
|
||||||
|
private final int size = 100_000;
|
||||||
|
private Value generator;
|
||||||
|
private Value plusOne;
|
||||||
|
private Value evenOnes;
|
||||||
|
private Value taken;
|
||||||
|
private Value init;
|
||||||
|
private Value asVector;
|
||||||
|
private Value asText;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void prepareCtx() throws Exception {
|
||||||
|
this.ctx = Context.newBuilder()
|
||||||
|
.allowExperimentalOptions(true)
|
||||||
|
.allowIO(true)
|
||||||
|
.allowAllAccess(true)
|
||||||
|
.logHandler(new ByteArrayOutputStream())
|
||||||
|
.option(
|
||||||
|
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
|
||||||
|
Paths.get("../../distribution/component").toFile().getAbsolutePath()
|
||||||
|
).build();
|
||||||
|
|
||||||
|
final Map<String, Language> langs = ctx.getEngine().getLanguages();
|
||||||
|
assertNotNull("Enso found: " + langs, langs.get("enso"));
|
||||||
|
|
||||||
|
final String code = """
|
||||||
|
from Standard.Base.Data.List.List import Cons, Nil
|
||||||
|
|
||||||
|
init list = list.init
|
||||||
|
|
||||||
|
taken list n = list.take_start n
|
||||||
|
|
||||||
|
even_ones list = list.filter (x -> x % 2 == 0)
|
||||||
|
|
||||||
|
plus_one list = list.map (x -> x + 1)
|
||||||
|
|
||||||
|
as_vector list = list.to_vector
|
||||||
|
|
||||||
|
as_text list = list.to_text
|
||||||
|
|
||||||
|
generator n =
|
||||||
|
go x v l = if x > n then l else
|
||||||
|
@Tail_Call go x+1 v+1 (Cons v l)
|
||||||
|
go 1 1 Nil
|
||||||
|
""";
|
||||||
|
|
||||||
|
generator = evalCode(code, "generator");
|
||||||
|
plusOne = evalCode(code, "plus_one");
|
||||||
|
evenOnes = evalCode(code, "even_ones");
|
||||||
|
taken = evalCode(code, "taken");
|
||||||
|
init = evalCode(code, "init");
|
||||||
|
asVector = evalCode(code, "as_vector");
|
||||||
|
asText = evalCode(code, "as_text");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapPlusOneAndIterate() throws Exception {
|
||||||
|
var list = generator.execute(size);
|
||||||
|
assertEquals("Size is OK", size, list.invokeMember("length").asInt());
|
||||||
|
|
||||||
|
var values = new ArrayList<BigInteger>();
|
||||||
|
{
|
||||||
|
var it = plusOne.execute(list);
|
||||||
|
|
||||||
|
for (long i = 0; i < size; i++) {
|
||||||
|
var e = it.getMember("x");
|
||||||
|
assertTrue("All numbers are numbers, but " + e + " is not", e.isNumber());
|
||||||
|
var n = e.as(Number.class);
|
||||||
|
assertNotNull("All numbers can be seen as java.lang.Number", n);
|
||||||
|
var b = new BigInteger(n.toString());
|
||||||
|
assertNotNull("Each Enso number can be parsed as big integer", b);
|
||||||
|
assertEquals("Textual values are the same", n.toString(), b.toString());
|
||||||
|
values.add(b);
|
||||||
|
it = it.getMember("xs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals("Two hundred numbers collected", size, values.size());
|
||||||
|
for (int i = 1; i < values.size(); i++) {
|
||||||
|
var prev = values.get(i - 1);
|
||||||
|
var next = values.get(i);
|
||||||
|
|
||||||
|
assertEquals("Each value is accurate", next.add(BigInteger.ONE), prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterEvenOnes() throws Exception {
|
||||||
|
var list = generator.execute(size);
|
||||||
|
assertLength("Generated", size, list);
|
||||||
|
var even = evenOnes.execute(list);
|
||||||
|
assertLength("Half the size", size / 2, even);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void takeHalf() throws Exception {
|
||||||
|
var list = generator.execute(size);
|
||||||
|
assertLength("Generated", size, list);
|
||||||
|
var even = taken.execute(list, size / 2);
|
||||||
|
assertLength("Half the size", size / 2, even);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void initAllButLast() throws Exception {
|
||||||
|
var list = generator.execute(size);
|
||||||
|
assertLength("Generated all", size, list);
|
||||||
|
var shorterByOne = init.execute(list);
|
||||||
|
assertLength("Last element is gone", size - 1, shorterByOne);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toVector() throws Exception {
|
||||||
|
var list = generator.execute(size);
|
||||||
|
assertLength("Generated all", size, list);
|
||||||
|
var vec = asVector.execute(list);
|
||||||
|
assertEquals("It's vector", "Vector", vec.getMetaObject().getMetaSimpleName());
|
||||||
|
assertTrue("And an array like object", vec.hasArrayElements());
|
||||||
|
assertEquals("The same size remains", size, vec.getArraySize());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toText() throws Exception {
|
||||||
|
var list = generator.execute(size);
|
||||||
|
assertLength("Generated all", size, list);
|
||||||
|
var str = asText.execute(list);
|
||||||
|
assertTrue("It is a string", str.isString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Value evalCode(final String code, final String methodName) throws URISyntaxException {
|
||||||
|
final var testName = "test.enso";
|
||||||
|
final URI testUri = new URI("memory://" + testName);
|
||||||
|
final Source src = Source.newBuilder("enso", code, testName)
|
||||||
|
.uri(testUri)
|
||||||
|
.buildLiteral();
|
||||||
|
var module = ctx.eval(src);
|
||||||
|
var powers = module.invokeMember("eval_expression", methodName);
|
||||||
|
return powers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertLength(String msg, long expected, Value list) {
|
||||||
|
var actual = list.invokeMember("length");
|
||||||
|
assertTrue("Size fits into number", actual.fitsInLong());
|
||||||
|
assertEquals(msg, expected, actual.asLong());
|
||||||
|
}
|
||||||
|
}
|
@ -118,7 +118,11 @@ object FrgaalJavaCompiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val (withTarget, noTarget) = sources0.partition(checkTarget)
|
val (withTarget, noTarget) = sources0.partition(checkTarget)
|
||||||
val in = findUnder(3, noTarget.tail.fold(asPath(noTarget.head))(asCommon).asInstanceOf[Path])
|
val in = if (noTarget.isEmpty) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(findUnder(3, noTarget.tail.fold(asPath(noTarget.head))(asCommon).asInstanceOf[Path]))
|
||||||
|
}
|
||||||
val generated = if (withTarget.isEmpty) {
|
val generated = if (withTarget.isEmpty) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@ -133,8 +137,9 @@ object FrgaalJavaCompiler {
|
|||||||
def storeArray(name: String, values : Seq[String]) = {
|
def storeArray(name: String, values : Seq[String]) = {
|
||||||
values.zipWithIndex.foreach { case (value, idx) => ensoProperties.setProperty(s"$name.$idx", value) }
|
values.zipWithIndex.foreach { case (value, idx) => ensoProperties.setProperty(s"$name.$idx", value) }
|
||||||
}
|
}
|
||||||
|
if (in.isDefined) {
|
||||||
ensoProperties.setProperty("input", in.toString())
|
ensoProperties.setProperty("input", in.get.toString())
|
||||||
|
}
|
||||||
if (generated.isDefined) {
|
if (generated.isDefined) {
|
||||||
ensoProperties.setProperty("generated", generated.get.toString())
|
ensoProperties.setProperty("generated", generated.get.toString())
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,10 @@ spec = Test.group "List" <|
|
|||||||
Test.specify "should allow getting the tail of the list with `.tail`" <|
|
Test.specify "should allow getting the tail of the list with `.tail`" <|
|
||||||
l.tail . should_equal (List.Cons 2 (List.Cons 3 List.Nil))
|
l.tail . should_equal (List.Cons 2 (List.Cons 3 List.Nil))
|
||||||
empty.tail.should_fail_with Empty_Error
|
empty.tail.should_fail_with Empty_Error
|
||||||
|
Test.specify "single element list.init yields Nil" <|
|
||||||
|
(List.Cons 1 List.Nil).init . should_equal List.Nil
|
||||||
|
Test.specify "two element list.init yields one element" <|
|
||||||
|
(List.Cons 1 (List.Cons 2 List.Nil)).init . should_equal (List.Cons 1 List.Nil)
|
||||||
Test.specify "should allow getting the init of the list with `.init`" <|
|
Test.specify "should allow getting the init of the list with `.init`" <|
|
||||||
l.init . should_equal (List.Cons 1 (List.Cons 2 List.Nil))
|
l.init . should_equal (List.Cons 1 (List.Cons 2 List.Nil))
|
||||||
empty.init.should_fail_with Empty_Error
|
empty.init.should_fail_with Empty_Error
|
||||||
|
@ -11,6 +11,7 @@ polyglot java import java.util.Locale as JavaLocale
|
|||||||
|
|
||||||
from Standard.Test import Test, Test_Suite
|
from Standard.Test import Test, Test_Suite
|
||||||
import Standard.Test.Extensions
|
import Standard.Test.Extensions
|
||||||
|
from Standard.Base.Error.Common import Uninitialized_State
|
||||||
|
|
||||||
type My_Type
|
type My_Type
|
||||||
Value foo bar baz
|
Value foo bar baz
|
||||||
@ -20,7 +21,8 @@ My_Type.my_method self = self.foo + self.bar + self.baz
|
|||||||
type Test_Type
|
type Test_Type
|
||||||
Value x
|
Value x
|
||||||
|
|
||||||
spec = Test.group "Meta-Value Manipulation" <|
|
spec =
|
||||||
|
Test.group "Meta-Value Manipulation" <|
|
||||||
Test.specify "should allow manipulating unresolved symbols" <|
|
Test.specify "should allow manipulating unresolved symbols" <|
|
||||||
sym = .does_not_exist
|
sym = .does_not_exist
|
||||||
meta_sym = Meta.meta sym
|
meta_sym = Meta.meta sym
|
||||||
@ -170,4 +172,71 @@ spec = Test.group "Meta-Value Manipulation" <|
|
|||||||
(Test_Type.Value a)==(Test_Type.Value b) . should_be_true
|
(Test_Type.Value a)==(Test_Type.Value b) . should_be_true
|
||||||
(Test_Type.Value a)==(Test_Type.Value c) . should_be_false
|
(Test_Type.Value a)==(Test_Type.Value c) . should_be_false
|
||||||
|
|
||||||
|
Test.group "Atom with holes" <|
|
||||||
|
Test.specify "construct and fill" <|
|
||||||
|
pair = Meta.atom_with_hole (e -> My_Type.Value 1 e 3)
|
||||||
|
|
||||||
|
atom = pair.value
|
||||||
|
fill = pair.fill
|
||||||
|
|
||||||
|
Meta.is_atom atom . should_be_true
|
||||||
|
|
||||||
|
atom.foo . should_equal 1
|
||||||
|
atom.baz . should_equal 3
|
||||||
|
case atom.bar of
|
||||||
|
n : Number -> Test.fail "Shouldn't be number yet: "+n
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
fill 2
|
||||||
|
atom.bar . should_equal 2
|
||||||
|
|
||||||
|
fill 10 # no change
|
||||||
|
atom.bar . should_equal 2
|
||||||
|
|
||||||
|
Test.specify "fail if atom_with_hole isn't used" <|
|
||||||
|
key = Panic.catch Uninitialized_State.Error handler=(caught_panic-> caught_panic.payload.key) <|
|
||||||
|
Meta.atom_with_hole (_ -> My_Type.Value 1 2 3)
|
||||||
|
case key of
|
||||||
|
t : My_Type ->
|
||||||
|
t.foo . should_equal 1
|
||||||
|
t.bar . should_equal 2
|
||||||
|
t.baz . should_equal 3
|
||||||
|
|
||||||
|
Test.specify "fail if non-atom is created" <|
|
||||||
|
key = Panic.catch Uninitialized_State.Error handler=(caught_panic-> caught_panic.payload.key) <|
|
||||||
|
Meta.atom_with_hole (_ -> 2)
|
||||||
|
case key of
|
||||||
|
t : Number ->
|
||||||
|
t . should_equal 2
|
||||||
|
|
||||||
|
Test.specify "only one atom_with_hole is used" <|
|
||||||
|
pair = Meta.atom_with_hole (e -> My_Type.Value e e e)
|
||||||
|
atom = pair.value
|
||||||
|
fill = pair.fill
|
||||||
|
|
||||||
|
Meta.is_atom atom . should_be_true
|
||||||
|
|
||||||
|
case atom.foo of
|
||||||
|
n : Number -> Test.fail "Shouldn't be number yet: "+n
|
||||||
|
_ -> Nothing
|
||||||
|
case atom.baz of
|
||||||
|
n : Number -> Test.fail "Shouldn't be number yet: "+n
|
||||||
|
_ -> Nothing
|
||||||
|
case atom.bar of
|
||||||
|
n : Number -> Test.fail "Shouldn't be number yet: "+n
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
fill 2
|
||||||
|
atom.foo . should_equal 2
|
||||||
|
|
||||||
|
fill 10 # no change
|
||||||
|
atom.foo . should_equal 2
|
||||||
|
|
||||||
|
case atom.baz of
|
||||||
|
n : Number -> Test.fail "Not changed to number: "+n
|
||||||
|
_ -> Nothing
|
||||||
|
case atom.bar of
|
||||||
|
n : Number -> Test.fail "Not changed to number: "+n
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
main = Test_Suite.run_main spec
|
main = Test_Suite.run_main spec
|
||||||
|
@ -1 +0,0 @@
|
|||||||
set_atom_field atom index value = @Builtin_Method "Unsafe.set_atom_field"
|
|
Loading…
Reference in New Issue
Block a user