Skip to content

Commit

Permalink
[WASM] Remove inplace Array resizing support from Wasm.
Browse files Browse the repository at this point in the history
This replaces ArrayList, ArrayHelper and PrimitiveLists to actually follow Java semantics of fixed length arrays so we can remove in place resizing logic from WasmArray.

This also means that we are removing length field and array.length will be directed to underlying array.len instruction.

PiperOrigin-RevId: 560002664
  • Loading branch information
gkdn authored and copybara-github committed Aug 25, 2023
1 parent a492687 commit 1e1ea94
Show file tree
Hide file tree
Showing 21 changed files with 309 additions and 206 deletions.
2 changes: 1 addition & 1 deletion jre/java/java/util/ArrayDeque.java
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ private void ensureCapacity() {
array = newArray;
head = 0;
} else {
ArrayHelper.setLength(array, newLength);
array = ArrayHelper.setLength(array, newLength);
}
tail = numElements;
}
Expand Down
188 changes: 188 additions & 0 deletions jre/java/super-wasm/java/util/ArrayList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package java.util;

import static javaemul.internal.InternalPreconditions.checkArgument;
import static javaemul.internal.InternalPreconditions.checkPositionIndex;
import static javaemul.internal.InternalPreconditions.checkPositionIndexes;

import javaemul.internal.ArrayHelper;

/**
* Resizeable array implementation of the List interface. See <a
* href="https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html">the official Java API
* doc</a> for details.
*
* @param <E> the element type.
*/
public class ArrayList<E> extends ArrayListBase<E> {

private int size;

public ArrayList() {
array = (E[]) new Object[10];
}

public ArrayList(Collection<? extends E> c) {
array = (E[]) c.toArray();
size = array.length;
}

public ArrayList(int initialCapacity) {
// Avoid calling overridable methods from constructors
checkArgument(initialCapacity >= 0, "Initial capacity must not be negative");
array = (E[]) new Object[initialCapacity];
}

@Override
public boolean add(E o) {
int nextIndex = size;
ensureCapacity(nextIndex + 1);
array[nextIndex] = o;
size = nextIndex + 1;
return true;
}

@Override
public void add(int index, E o) {
checkPositionIndex(index, size);
E[] array = insertGap(index, 1);
array[index] = o;
}

@Override
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index, size);
Object[] cArray = c.toArray();
int len = cArray.length;
if (len == 0) {
return false;
}
E[] array = insertGap(index, len);
ArrayHelper.copy(cArray, 0, array, index, len);
return true;
}

private E[] insertGap(int index, int count) {
E[] original = array;
int originalCapacity = original.length;

int originalSize = this.size;
int newSize = originalSize + count;

E[] target;
if (newSize > originalCapacity) {
// Ensure enough capacity.
target = (E[]) new Object[ArrayHelper.getNewCapacity(originalCapacity, newSize)];
// Only copy up to index since others will need to move anyway.
ArrayHelper.copy(original, 0, target, 0, index);
this.array = target;
} else {
target = original;
}

// Move the items. Note that we are not trying to clear existing values; callers will do that.
ArrayHelper.copy(original, index, target, index + count, originalSize - index);

this.size = newSize;

return target;
}

public Object clone() {
return new ArrayList<E>(this);
}

public void ensureCapacity(int minCapacity) {
if (minCapacity > array.length) {
array = ArrayHelper.grow(array, minCapacity);
}
}

@Override
void removeImpl(int index) {
int newSize = size - 1;
ArrayHelper.copy(array, index + 1, array, index, newSize - index);
array[newSize] = null;
this.size = newSize;
}

@Override
int sizeImpl() {
return size;
}

@Override
public Object[] toArray() {
return ArrayHelper.clone(array, 0, size);
}

/*
* Faster than the iterator-based implementation in AbstractCollection.
*/
@SuppressWarnings("unchecked")
@Override
public <T> T[] toArray(T[] out) {
int size = this.size;
if (out.length < size) {
out = ArrayHelper.createFrom(out, size);
}
for (int i = 0; i < size; ++i) {
out[i] = (T) array[i];
}
if (out.length > size) {
out[size] = null;
}
return out;
}

public void trimToSize() {
if (size == array.length) {
return;
}
array = ArrayHelper.clone(array, 0, size);
}

@Override
protected void removeRange(int fromIndex, int endIndex) {
checkPositionIndexes(fromIndex, endIndex, size);
int count = endIndex - fromIndex;
if (count == 0) {
return;
}

int oldSize = size;
// Copy the items after removal end, overwriting removed items.
ArrayHelper.copy(array, endIndex, array, fromIndex, oldSize - endIndex);
// Clear the end of the array.
Arrays.fill(array, oldSize - count, oldSize, null);

this.size = oldSize - count;
}

void setSize(int newSize) {
int oldSize = size;
if (newSize == oldSize) {
return;
}
if (newSize > oldSize) {
ArrayHelper.grow(array, newSize);
} else {
Arrays.fill(array, newSize, oldSize, null);
}
this.size = newSize;
}
}
55 changes: 14 additions & 41 deletions jre/java/super-wasm/javaemul/internal/ArrayHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@
/** Provides utilities to perform operations on Arrays. */
public final class ArrayHelper {

public static <T> T clone(T array) {
return (T) cloneImpl(array, 0, getLength(array));
}

public static <T> T clone(T array, int fromIndex, int toIndex) {
return (T) cloneImpl(array, fromIndex, toIndex);
}
Expand All @@ -37,8 +33,9 @@ public static <T> T unsafeClone(Object array, int fromIndex, int toIndex) {

private static Object cloneImpl(Object array, int fromIndex, int toIndex) {
int newLength = toIndex - fromIndex;
Object targetArray = asWasmArray(array).newArray(newLength);
int endIndex = Math.min(getLength(array), toIndex);
WasmArray wasmArray = asWasmArray(array);
Object targetArray = wasmArray.newArray(newLength);
int endIndex = Math.min(wasmArray.getLength(), toIndex);
copy(array, fromIndex, targetArray, 0, endIndex - fromIndex);
return targetArray;
}
Expand All @@ -55,33 +52,22 @@ public static int getLength(Object array) {
return asWasmArray(array).getLength();
}

public static <T> T setLength(T array, int length) {
asWasmArray(array).setLength(length);
return array;
public static <T> T setLength(T array, int newLength) {
return getLength(array) == newLength ? array : clone(array, 0, newLength);
}

/**
* Resize the array to accommodate requested length. For Wasm, the size is increased in larger
* chunks to amortize cost of growing similar to JavaScript.
*/
public static <T> T grow(T array, int length) {
return setLength(array, length);
}

public static void push(Object[] array, Object o) {
((WasmArray.OfObject) asWasmArray(array)).push(o);
}

public static void push(byte[] array, byte o) {
((WasmArray.OfByte) asWasmArray(array)).push(o);
return clone(array, 0, getNewCapacity(getLength(array), length));
}

public static void push(int[] array, int o) {
((WasmArray.OfInt) asWasmArray(array)).push(o);
}

public static void push(long[] array, long o) {
((WasmArray.OfLong) asWasmArray(array)).push(o);
}

public static void push(double[] array, double o) {
((WasmArray.OfDouble) asWasmArray(array)).push(o);
public static int getNewCapacity(int originalCapacity, int requestedCapacity) {
// Grow roughly with 1.5x rate at minimum.
int minCapacity = originalCapacity + (originalCapacity >> 1) + 1;
return Math.max(minCapacity, requestedCapacity);
}

public static void fill(int[] array, int value) {
Expand Down Expand Up @@ -183,19 +169,6 @@ public static <T> void fill(T[] array, T value, int fromIndex, int toIndex) {
@Wasm("array.fill $java.lang.Object.array")
private static native void nativeFill(Object[] array, int offset, Object value, int size);

public static void removeFrom(Object[] array, int index, int deleteCount) {
// Copy the items after deletion end, overwriting deleted items.
int copyFrom = index + deleteCount;
copy(array, copyFrom, array, index, array.length - copyFrom);
// Trim the end array.
setLength(array, array.length - deleteCount);
}

public static void insertTo(Object[] array, int index, Object value) {
((WasmArray.OfObject) asWasmArray(array))
.insertFrom(index, (WasmArray.OfObject) asWasmArray(new Object[] {value}));
}

public static void copy(Object array, int srcOfs, Object dest, int destOfs, int len) {
asWasmArray(dest).copyFrom(destOfs, asWasmArray(array), srcOfs, len);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,20 @@
static class %ARRAY_TYPE_NAME% extends WasmArray {

%TYPE_NAME%[] elements;

%ARRAY_TYPE_NAME%(int length) {
super(length);
static %ARRAY_TYPE_NAME% newWithLength(int length) {
// Do explicit construction of 0 length arrays because those are optimized to preallocated
// singletons.
this.elements = (length == 0) ? new %TYPE_NAME%[0] : new %TYPE_NAME%[length];
return new %ARRAY_TYPE_NAME%((length == 0) ? new %TYPE_NAME%[0] : new %TYPE_NAME%[length]);
}

final %TYPE_NAME%[] elements;

%ARRAY_TYPE_NAME%(%TYPE_NAME%[] initialValues) {
super(initialValues.length);
this.elements = initialValues;
}

@Override
Object newArray(int length) {
return new WasmArray.%ARRAY_TYPE_NAME%(length);
}

@Override
void setLength(int newLength) {
ensureCapacity(newLength);
if (newLength < length) {
// Clear to outside contents
for (int i = newLength; i < length; i++) {
elements[i] = %DEFAULT_VALUE%;
}
}
length = newLength;
}

public void push(%TYPE_NAME% o) {
int newLength = length + 1;
ensureCapacity(newLength);
elements[length] = o;
length = newLength;
}

private void ensureCapacity(int newLength) {
%TYPE_NAME%[] original = elements;
if (newLength > original.length) {
// Not enough capacity, increase it.
elements = new %TYPE_NAME%[getNewCapacity(length, newLength)];
copy(elements, 0, original, 0, length);
}
return newWithLength(length);
}

#IF OfByte OfShort OfInt OfLong OfChar OfFloat OfDouble
Expand Down Expand Up @@ -180,29 +150,4 @@
@JsMethod // Keep JsInteropRestrictionsChecker happy.
@Wasm("array.copy $%TYPE_NAME%.array $%TYPE_NAME%.array ")
private static native void copy(Object dest, int destOfs, Object src, int srcOfs, int len);

#IF OfObject
public void insertFrom(int insertIndex, WasmArray.%ARRAY_TYPE_NAME% values) {
%TYPE_NAME%[] original = elements;
int newLength = length + values.length;

// Ensure enough capacity.
if (newLength > elements.length) {
elements = new %TYPE_NAME%[getNewCapacity(elements.length, newLength)];
// Copy only up to index since the other will be moved anyway.
copy(this.elements, 0, original, 0, insertIndex);
}

// Make room for the values that will be inserted by moving the existing elements to the
// end so that they are not overwritten.
int insertEndIndex = insertIndex + values.length;
copy(this.elements, insertEndIndex, original, insertIndex, newLength - insertEndIndex);

// Copy new values into the insert location.
copy(this.elements, insertIndex, values.elements, 0, values.length);

// Adjust the final size to cover all copied items
length = newLength;
}
#ENDIF
}
Loading

0 comments on commit 1e1ea94

Please sign in to comment.