Skip to content

Commit

Permalink
8323746: Add PathElement hashCode and equals
Browse files Browse the repository at this point in the history
Reviewed-by: mcimadamore
  • Loading branch information
minborg committed Feb 8, 2024
1 parent 917838e commit b58d73b
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 76 deletions.
48 changes: 14 additions & 34 deletions src/java.base/share/classes/java/lang/foreign/MemoryLayout.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -32,7 +32,6 @@
import java.util.stream.Stream;

import jdk.internal.foreign.LayoutPath;
import jdk.internal.foreign.LayoutPath.PathElementImpl.PathKind;
import jdk.internal.foreign.Utils;
import jdk.internal.foreign.layout.MemoryLayoutUtil;
import jdk.internal.foreign.layout.PaddingLayoutImpl;
Expand Down Expand Up @@ -847,7 +846,13 @@ public sealed interface MemoryLayout
*
* @since 22
*/
sealed interface PathElement permits LayoutPath.PathElementImpl {
sealed interface PathElement
permits LayoutPath.DereferenceElement,
LayoutPath.GroupElementByIndex,
LayoutPath.GroupElementByName,
LayoutPath.SequenceElement,
LayoutPath.SequenceElementByIndex,
LayoutPath.SequenceElementByRange {

/**
* {@return a path element which selects a member layout with the given name in a
Expand All @@ -862,10 +867,7 @@ sealed interface PathElement permits LayoutPath.PathElementImpl {
* @param name the name of the member layout to be selected
*/
static PathElement groupElement(String name) {
Objects.requireNonNull(name);
return new LayoutPath.PathElementImpl(PathKind.GROUP_ELEMENT,
path -> path.groupElement(name),
"groupElement(\"" + name + "\")");
return new LayoutPath.GroupElementByName(name);
}

/**
Expand All @@ -876,12 +878,7 @@ static PathElement groupElement(String name) {
* @throws IllegalArgumentException if {@code index < 0}
*/
static PathElement groupElement(long index) {
if (index < 0) {
throw new IllegalArgumentException("Index < 0");
}
return new LayoutPath.PathElementImpl(PathKind.GROUP_ELEMENT,
path -> path.groupElement(index),
"groupElement(" + index + ")");
return new LayoutPath.GroupElementByIndex(index);
}

/**
Expand All @@ -892,12 +889,7 @@ static PathElement groupElement(long index) {
* @throws IllegalArgumentException if {@code index < 0}
*/
static PathElement sequenceElement(long index) {
if (index < 0) {
throw new IllegalArgumentException("Index must be positive: " + index);
}
return new LayoutPath.PathElementImpl(PathKind.SEQUENCE_ELEMENT_INDEX,
path -> path.sequenceElement(index),
"sequenceElement(" + index + ")");
return new LayoutPath.SequenceElementByIndex(index);
}

/**
Expand All @@ -923,15 +915,7 @@ static PathElement sequenceElement(long index) {
* @throws IllegalArgumentException if {@code start < 0}, or {@code step == 0}
*/
static PathElement sequenceElement(long start, long step) {
if (start < 0) {
throw new IllegalArgumentException("Start index must be positive: " + start);
}
if (step == 0) {
throw new IllegalArgumentException("Step must be != 0: " + step);
}
return new LayoutPath.PathElementImpl(PathKind.SEQUENCE_RANGE,
path -> path.sequenceElement(start, step),
"sequenceElement(" + start + ", " + step + ")");
return new LayoutPath.SequenceElementByRange(start, step);
}

/**
Expand All @@ -943,19 +927,15 @@ static PathElement sequenceElement(long start, long step) {
* {@code 0 <= I < C}.
*/
static PathElement sequenceElement() {
return new LayoutPath.PathElementImpl(PathKind.SEQUENCE_ELEMENT,
LayoutPath::sequenceElement,
"sequenceElement()");
return LayoutPath.SequenceElement.instance();
}

/**
* {@return a path element that dereferences an address layout as its
* {@linkplain AddressLayout#targetLayout() target layout} (where set)}
*/
static PathElement dereferenceElement() {
return new LayoutPath.PathElementImpl(PathKind.DEREF_ELEMENT,
LayoutPath::derefElement,
"dereferenceElement()");
return LayoutPath.DereferenceElement.instance();
}
}

Expand Down
147 changes: 117 additions & 30 deletions src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -363,54 +363,141 @@ private String breadcrumbs() {
.collect(joining(", selected from: "));
}

/**
* This class provides an immutable implementation for the {@code PathElement} interface. A path element implementation
* is simply a pointer to one of the selector methods provided by the {@code LayoutPath} class.
*/
public static final class PathElementImpl implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> {
public record GroupElementByName(String name)
implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> {

public enum PathKind {
SEQUENCE_ELEMENT("unbound sequence element"),
SEQUENCE_ELEMENT_INDEX("bound sequence element"),
SEQUENCE_RANGE("sequence range"),
GROUP_ELEMENT("group element"),
DEREF_ELEMENT("dereference element");
// Assert invariants
public GroupElementByName {
Objects.requireNonNull(name);
}

@Override
public LayoutPath apply(LayoutPath layoutPath) {
return layoutPath.groupElement(name);
}

final String description;
@Override
public String toString() {
return "groupElement(\"" + name + "\")";
}
}

PathKind(String description) {
this.description = description;
public record GroupElementByIndex(long index)
implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> {

// Assert invariants
public GroupElementByIndex {
if (index < 0) {
throw new IllegalArgumentException("Index < 0");
}
}

@Override
public LayoutPath apply(LayoutPath layoutPath) {
return layoutPath.groupElement(index);
}

public String description() {
return description;
@Override
public String toString() {
return "groupElement(" + index + ")";
}

}

public record SequenceElementByIndex(long index)
implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> {

// Assert invariants
public SequenceElementByIndex {
if (index < 0) {
throw new IllegalArgumentException("Index < 0");
}
}

final PathKind kind;
final UnaryOperator<LayoutPath> pathOp;
final String stringRepresentation;
@Override
public LayoutPath apply(LayoutPath layoutPath) {
return layoutPath.sequenceElement(index);
}

public PathElementImpl(PathKind kind,
UnaryOperator<LayoutPath> pathOp,
String stringRepresentation) {
this.kind = kind;
this.pathOp = pathOp;
this.stringRepresentation = stringRepresentation;
@Override
public String toString() {
return "sequenceElement(" + index + ")";
}

}

public record SequenceElementByRange(long start, long step)
implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> {

// Assert invariants
public SequenceElementByRange {
if (start < 0) {
throw new IllegalArgumentException("Start index must be positive: " + start);
}
if (step == 0) {
throw new IllegalArgumentException("Step must be != 0: " + step);
}
}

@Override
public LayoutPath apply(LayoutPath layoutPath) {
return layoutPath.sequenceElement(start, step);
}

@Override
public String toString() {
return "sequenceElement(" + start + ", " + step + ")";
}

}

public record SequenceElement()
implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> {

private static final SequenceElement INSTANCE = new SequenceElement();

@Override
public LayoutPath apply(LayoutPath layoutPath) {
return layoutPath.sequenceElement();
}

@Override
public String toString() {
return "sequenceElement()";
}

public static MemoryLayout.PathElement instance() {
return INSTANCE;
}

}

public record DereferenceElement()
implements MemoryLayout.PathElement, UnaryOperator<LayoutPath> {

private static final DereferenceElement INSTANCE = new DereferenceElement();

@Override
public LayoutPath apply(LayoutPath layoutPath) {
return pathOp.apply(layoutPath);
return layoutPath.derefElement();
}

public PathKind kind() {
return kind;
// Overriding here will ensure DereferenceElement will have a hash code
// that is different from the hash code of SequenceElement.
@Override
public int hashCode() {
return 31;
}

@Override
public String toString() {
return stringRepresentation;
return "dereferenceElement()";
}

public static MemoryLayout.PathElement instance() {
return INSTANCE;
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -26,7 +26,6 @@
package jdk.internal.foreign.layout;

import jdk.internal.foreign.LayoutPath;
import jdk.internal.foreign.LayoutPath.PathElementImpl.PathKind;
import jdk.internal.foreign.Utils;

import java.lang.foreign.GroupLayout;
Expand All @@ -40,11 +39,11 @@
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.function.UnaryOperator;

public abstract sealed class AbstractLayout<L extends AbstractLayout<L> & MemoryLayout>
permits AbstractGroupLayout, PaddingLayoutImpl, SequenceLayoutImpl, ValueLayouts.AbstractValueLayout {
Expand Down Expand Up @@ -174,12 +173,14 @@ class Holder {

public long byteOffset(PathElement... elements) {
return computePathOp(LayoutPath.rootPath((MemoryLayout) this), LayoutPath::offset,
EnumSet.of(PathKind.SEQUENCE_ELEMENT, PathKind.SEQUENCE_RANGE, PathKind.DEREF_ELEMENT), elements);
Set.of(LayoutPath.SequenceElement.class, LayoutPath.SequenceElementByRange.class, LayoutPath.DereferenceElement.class),
elements);
}

public MethodHandle byteOffsetHandle(PathElement... elements) {
return computePathOp(LayoutPath.rootPath((MemoryLayout) this), LayoutPath::offsetHandle,
EnumSet.of(PathKind.DEREF_ELEMENT), elements);
Set.of(LayoutPath.DereferenceElement.class),
elements);
}

public VarHandle varHandle(PathElement... elements) {
Expand All @@ -197,23 +198,27 @@ public VarHandle arrayElementVarHandle(PathElement... elements) {

public MethodHandle sliceHandle(PathElement... elements) {
return computePathOp(LayoutPath.rootPath((MemoryLayout) this), LayoutPath::sliceHandle,
Set.of(PathKind.DEREF_ELEMENT), elements);
Set.of(LayoutPath.DereferenceElement.class),
elements);
}

public MemoryLayout select(PathElement... elements) {
return computePathOp(LayoutPath.rootPath((MemoryLayout) this), LayoutPath::layout,
EnumSet.of(PathKind.SEQUENCE_ELEMENT_INDEX, PathKind.SEQUENCE_RANGE, PathKind.DEREF_ELEMENT), elements);
Set.of(LayoutPath.SequenceElementByIndex.class, LayoutPath.SequenceElementByRange.class, LayoutPath.DereferenceElement.class),
elements);
}

private static <Z> Z computePathOp(LayoutPath path, Function<LayoutPath, Z> finalizer,
Set<PathKind> badKinds, PathElement... elements) {
Set<Class<?>> badTypes, PathElement... elements) {
Objects.requireNonNull(elements);
for (PathElement e : elements) {
LayoutPath.PathElementImpl pathElem = (LayoutPath.PathElementImpl)Objects.requireNonNull(e);
if (badKinds.contains(pathElem.kind())) {
throw new IllegalArgumentException(String.format("Invalid %s selection in layout path", pathElem.kind().description()));
Objects.requireNonNull(e);
if (badTypes.contains(e.getClass())) {
throw new IllegalArgumentException("Invalid selection in layout path: " + e);
}
path = pathElem.apply(path);
@SuppressWarnings("unchecked")
UnaryOperator<LayoutPath> pathOp = (UnaryOperator<LayoutPath>) e;
path = pathOp.apply(path);
}
return finalizer.apply(path);
}
Expand Down
7 changes: 7 additions & 0 deletions test/jdk/java/foreign/TestLayoutPaths.java
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,13 @@ public void testOffsetHandle(MemoryLayout layout, PathElement[] pathElements, lo
assertEquals(actualByteOffset, expectedByteOffset);
}

@Test
public void testHashCodeCollision() {
PathElement sequenceElement = PathElement.sequenceElement();
PathElement dereferenceElement = PathElement.dereferenceElement();
assertNotEquals(sequenceElement.hashCode(), dereferenceElement.hashCode());
}

@Test
public void testGroupElementIndexToString() {
PathElement e = PathElement.groupElement(2);
Expand Down

1 comment on commit b58d73b

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.