Skip to content

Commit

Permalink
Roll forward of 7430c52: [WASM] Implement non-native JsEnum.
Browse files Browse the repository at this point in the history
Changes from original:
- Instead of having a global map for the boxed value cache, added a map to j.l.Class which is populated for each JsEnum class. See Class.java.

- Uses Enums.equals and Enums.compareTo logic to avoid boxing on "jsEnum.equals(otherJsEnum)" if they are the same type. See changes in RuntimeMethods.

PiperOrigin-RevId: 588886429
  • Loading branch information
Googler authored and copybara-github committed Dec 7, 2023
1 parent c8fde99 commit 5a16eea
Show file tree
Hide file tree
Showing 34 changed files with 4,347 additions and 545 deletions.
13 changes: 13 additions & 0 deletions jre/java/super-wasm/java/lang/Class.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import javaemul.internal.annotations.HasNoSideEffects;
import jsinterop.annotations.JsMethod;

Expand Down Expand Up @@ -43,6 +45,9 @@ public final class Class<T> implements Type, Serializable {
/** * Class objects for arrays of this type, created lazily. */
private Class<?>[] arrayTypes;

/** Cache of boxed JsEnum values, created lazily. */
private Map<Object, Object> jsEnumsCache;

private Class(
boolean isEnum,
boolean isInterface,
Expand Down Expand Up @@ -166,6 +171,14 @@ private String getClassName() {
@JsMethod(namespace = "j2wasm.StringUtils")
private static native String generateClassName();

@HasNoSideEffects
public Map<Object, Object> getJsEnumsCache() {
if (jsEnumsCache == null) {
jsEnumsCache = new HashMap<>();
}
return jsEnumsCache;
}

private static String repeatString(String str, int count) {
String rv = "";
for (int i = 0; i < count; i++) {
Expand Down
148 changes: 147 additions & 1 deletion jre/java/super-wasm/javaemul/internal/Enums.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@
package javaemul.internal;

import static javaemul.internal.InternalPreconditions.checkCriticalNotNull;
import static javaemul.internal.InternalPreconditions.checkNotNull;
import static javaemul.internal.InternalPreconditions.checkType;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import javaemul.internal.annotations.HasNoSideEffects;

class Enums {
/** Provides utilities to perform operations on enums. */
public final class Enums {

/**
* @param values An array containing all instances of a particular enum type.
Expand All @@ -43,4 +48,145 @@ public static <V> V getValueFromNameAndMap(String name, Map<String, V> map) {
return enumValue;
}

/** Base class for boxed representation of a JsEnum. */
private abstract static class BoxedLightEnum implements Serializable {
// TODO(b/295235576): Model JS constructor refs or find an alternative, change
// to allow features like `getClass()`. Use string for now.
final Class<?> clazz;

BoxedLightEnum(Class<?> clazz) {
this.clazz = clazz;
}
}

/** Boxes a JsEnum value that does not support comparable. */
private static BoxedIntegerEnum boxInteger(int value, Class<?> clazz) {
if (isNull(value)) {
return null;
}
return getCachedInteger(clazz, value);
}

/** Boxed representation of an int JsEnum. */
private static class BoxedIntegerEnum extends BoxedLightEnum {
final int value;

public BoxedIntegerEnum(int value, Class<?> clazz) {
super(clazz);
this.value = value;
}

public String toString() {
return Integer.toString(value);
}
}

/** Boxes a JsEnum value that supports {@link Enum#compareTo} and {@link Enum#ordinal}. */
public static BoxedComparableIntegerEnum boxComparableInteger(int value, Class<?> clazz) {
if (isNull(value)) {
return null;
}
return getCachedComparableInteger(clazz, value);
}

/** Boxed representation of a {@code Comparable<>} int JsEnum. */
private static class BoxedComparableIntegerEnum extends BoxedIntegerEnum
implements Comparable<BoxedComparableIntegerEnum> {
public BoxedComparableIntegerEnum(int value, Class<?> clazz) {
super(value, clazz);
}

@Override
public int compareTo(BoxedComparableIntegerEnum o) {
checkType(clazz.equals(o.clazz));
return Integer.compare(value, o.value);
}
}

public static int unboxInteger(Object boxedEnum, Class<?> clazz) {
if (boxedEnum == null) {
return Integer.MIN_VALUE;
}
checkType(isInstanceOf(boxedEnum, clazz));
return ((BoxedIntegerEnum) boxedEnum).value;
}

/** Boxes a JsEnum value that does not support comparable. */
public static BoxedStringEnum boxString(String value, Class<?> clazz) {
if (value == null) {
return null;
}
return getCachedString(clazz, value);
}

/** Boxed representation of a String JsEnum. */
public static class BoxedStringEnum extends BoxedLightEnum {
final String value;

public BoxedStringEnum(String value, Class<?> clazz) {
super(clazz);
this.value = value;
}

public String toString() {
return value;
}
}

public static String unboxString(Object boxedEnum, Class<?> clazz) {
if (boxedEnum == null) {
return null;
}
checkType(isInstanceOf(boxedEnum, clazz));
return ((BoxedStringEnum) boxedEnum).value;
}

public static boolean isInstanceOf(Object instance, Class<?> clazz) {
return instance instanceof BoxedLightEnum && ((BoxedLightEnum) instance).clazz.equals(clazz);
}

public static boolean equalsInteger(int instance, int other) {
checkNotNull(!isNull(instance));
return instance == other;
}

public static boolean equalsString(String instance, String other) {
checkNotNull(instance);
return instance == other;
}

public static int compareToInteger(int instance, int other) {
checkNotNull(!isNull(instance));
return Integer.compare(instance, other);
}

/** Returns {@code true} if the specified enum value is equivalent to null. */
public static boolean isNull(int enumValue) {
return enumValue == Integer.MIN_VALUE;
}

@HasNoSideEffects
private static BoxedIntegerEnum getCachedInteger(Class<?> enumClass, int value) {
return (BoxedIntegerEnum)
enumClass
.getJsEnumsCache()
.computeIfAbsent(value, key -> new BoxedIntegerEnum(value, enumClass));
}

@HasNoSideEffects
private static BoxedComparableIntegerEnum getCachedComparableInteger(
Class<?> enumClass, int value) {
return (BoxedComparableIntegerEnum)
enumClass
.getJsEnumsCache()
.computeIfAbsent(value, key -> new BoxedComparableIntegerEnum(value, enumClass));
}

@HasNoSideEffects
private static BoxedStringEnum getCachedString(Class<?> enumClass, String value) {
return (BoxedStringEnum)
enumClass
.getJsEnumsCache()
.computeIfAbsent(value, key -> new BoxedStringEnum(value, enumClass));
}
}
33 changes: 33 additions & 0 deletions transpiler/java/com/google/j2cl/transpiler/ast/AstUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,12 @@ public static boolean isJsEnumCustomValueField(MemberDescriptor memberDescriptor
&& memberDescriptor.getEnclosingTypeDescriptor().isJsEnum();
}

public static boolean isPrimitiveNonNativeJsEnum(TypeDescriptor typeDescriptor) {
return typeDescriptor.isJsEnum()
&& getJsEnumValueFieldType(((DeclaredTypeDescriptor) typeDescriptor).getTypeDeclaration())
.isPrimitive();
}

public static FieldDescriptor createJsEnumConstantFieldDescriptor(
FieldDescriptor fieldDescriptor) {
TypeDescriptor enumValueType =
Expand All @@ -1027,6 +1033,11 @@ public static FieldDescriptor createJsEnumConstantFieldDescriptor(
.build();
}

public static TypeDescriptor getJsEnumValueFieldType(TypeDescriptor typeDescriptor) {
checkState(typeDescriptor.isJsEnum());
return getJsEnumValueFieldType(((DeclaredTypeDescriptor) typeDescriptor).getTypeDeclaration());
}

/** Returns the value field for a JsEnum. */
public static TypeDescriptor getJsEnumValueFieldType(TypeDeclaration typeDeclaration) {
FieldDescriptor valueFieldDescriptor = getJsEnumValueFieldDescriptor(typeDeclaration);
Expand Down Expand Up @@ -1067,12 +1078,34 @@ public static boolean isNonNativeJsEnum(TypeDescriptor typeDescriptor) {
return typeDescriptor.isJsEnum() && !typeDescriptor.isNative();
}

/**
* Returns true if {@code typeDescriptor} is a non native JsEnum, i.e. a JsEnum that requires
* boxing.
*/
public static boolean isNonNativeJsEnum(TypeDeclaration typeDeclaration) {
return typeDeclaration.isJsEnum() && !typeDeclaration.isNative();
}

/** Returns true if {@code typeDescriptor} is a non native JsEnum array. */
public static boolean isNonNativeJsEnumArray(TypeDescriptor typeDescriptor) {
return typeDescriptor.isArray()
&& isNonNativeJsEnum(((ArrayTypeDescriptor) typeDescriptor).getLeafTypeDescriptor());
}

/** Gets the initial value for a field or variable for the be assigned to a Wasm variable. */
public static Expression getInitialValue(TypeDescriptor typeDescriptor) {
// JsEnum default values are special case.
// TODO(b/299984505): Is there a better way to do this?
if (isNonNativeJsEnum(typeDescriptor)) {
TypeDescriptor valueTypeDescriptor = getJsEnumValueFieldType(typeDescriptor);
return valueTypeDescriptor.isPrimitive()
? valueTypeDescriptor.toBoxedType().getFieldDescriptor("MIN_VALUE").getConstantValue()
: valueTypeDescriptor.getDefaultValue();
}

return typeDescriptor.getDefaultValue();
}

/** Returns a list of null values. */
public static List<Expression> createListOfNullValues(int size) {
return Collections.nCopies(size, TypeDescriptors.get().javaLangObject.getNullValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ public boolean isCompileTimeConstant() {
public boolean isReferenceComparison() {
return (getOperator() == BinaryOperator.EQUALS || getOperator() == BinaryOperator.NOT_EQUALS)
&& !getLeftOperand().getTypeDescriptor().isPrimitive()
&& !getRightOperand().getTypeDescriptor().isPrimitive();
&& !getRightOperand().getTypeDescriptor().isPrimitive()
// We use the value type in the case of JsEnums with the declaration descriptor to ignore
// specialization of type variables in case any boxing occurs.
&& !AstUtils.isPrimitiveNonNativeJsEnum(getLeftOperand().getDeclaredTypeDescriptor())
&& !AstUtils.isPrimitiveNonNativeJsEnum(getRightOperand().getDeclaredTypeDescriptor());
}

@Override
Expand Down
Loading

0 comments on commit 5a16eea

Please sign in to comment.