From ae5d701aaca445f7a976f7f3dd2856b28217aacc Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Thu, 8 Sep 2016 16:36:10 +0300 Subject: [PATCH] Implementing simple mark&sweep GC --- .../java/org/teavm/runtime/Allocator.java | 29 +-- .../java/org/teavm/runtime/FreeChunk.java | 20 +++ .../org/teavm/runtime/FreeChunkHolder.java | 22 +++ core/src/main/java/org/teavm/runtime/GC.java | 169 ++++++++++++++++++ .../java/org/teavm/runtime/MarkQueue.java | 54 ++++++ .../main/java/org/teavm/runtime/Mutator.java | 43 +++++ .../main/java/org/teavm/interop/Address.java | 6 + 7 files changed, 322 insertions(+), 21 deletions(-) create mode 100644 core/src/main/java/org/teavm/runtime/FreeChunk.java create mode 100644 core/src/main/java/org/teavm/runtime/FreeChunkHolder.java create mode 100644 core/src/main/java/org/teavm/runtime/GC.java create mode 100644 core/src/main/java/org/teavm/runtime/MarkQueue.java create mode 100644 core/src/main/java/org/teavm/runtime/Mutator.java diff --git a/core/src/main/java/org/teavm/runtime/Allocator.java b/core/src/main/java/org/teavm/runtime/Allocator.java index 924b71b53..f528be033 100644 --- a/core/src/main/java/org/teavm/runtime/Allocator.java +++ b/core/src/main/java/org/teavm/runtime/Allocator.java @@ -16,6 +16,7 @@ package org.teavm.runtime; import org.teavm.interop.Address; +import org.teavm.interop.NoGC; import org.teavm.interop.StaticInit; import org.teavm.interop.Structure; @@ -24,24 +25,17 @@ public final class Allocator { private Allocator() { } - static Address address = initialize(); - - private static native Address initialize(); - public static Address allocate(RuntimeClass tag) { - Address result = address; - address = result.add(tag.size); - fillZero(result, tag.size); - RuntimeObject object = result.toStructure(); + RuntimeObject object = GC.alloc(tag.size); + fillZero(object.toAddress(), tag.size); object.classReference = tag.toAddress().toInt() >> 3; - return result; + return object.toAddress(); } public static Address allocateArray(RuntimeClass tag, int size) { - Address result = address; int sizeInBytes = tag.itemType.size * size + Structure.sizeOf(RuntimeArray.class); sizeInBytes = Address.align(Address.fromInt(sizeInBytes), 4).toInt(); - address = result.add(sizeInBytes); + Address result = GC.alloc(sizeInBytes).toAddress(); fillZero(result, sizeInBytes); RuntimeArray array = result.toStructure(); @@ -51,19 +45,12 @@ public final class Allocator { return result; } + @NoGC public static native void fillZero(Address address, int count); + @NoGC public static native void moveMemoryBlock(Address source, Address target, int count); + @NoGC public static native boolean isInitialized(Class cls); - - public static native void allocStack(int size); - - public static native void registerGcRoot(int index, Object object); - - public static native void removeGcRoot(int index); - - public static native void releaseStack(int size); - - public static native Address getStaticGcRoots(); } diff --git a/core/src/main/java/org/teavm/runtime/FreeChunk.java b/core/src/main/java/org/teavm/runtime/FreeChunk.java new file mode 100644 index 000000000..829ebc46f --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/FreeChunk.java @@ -0,0 +1,20 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * 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 org.teavm.runtime; + +class FreeChunk extends RuntimeObject { + int size; +} diff --git a/core/src/main/java/org/teavm/runtime/FreeChunkHolder.java b/core/src/main/java/org/teavm/runtime/FreeChunkHolder.java new file mode 100644 index 000000000..e639d67cd --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/FreeChunkHolder.java @@ -0,0 +1,22 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * 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 org.teavm.runtime; + +import org.teavm.interop.Structure; + +class FreeChunkHolder extends Structure { + FreeChunk value; +} diff --git a/core/src/main/java/org/teavm/runtime/GC.java b/core/src/main/java/org/teavm/runtime/GC.java new file mode 100644 index 000000000..af9ae69df --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/GC.java @@ -0,0 +1,169 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * 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 org.teavm.runtime; + +import org.teavm.interop.Address; +import org.teavm.interop.NoGC; +import org.teavm.interop.StaticInit; +import org.teavm.interop.Structure; + +@NoGC +@StaticInit +public final class GC { + private GC() { + } + + static Address currentChunkLimit; + static FreeChunk currentChunk; + static FreeChunkHolder currentChunkPointer; + static int freeChunks; + + private static native Address gcStorageAddress(); + + private static native Address heapAddress(); + + private static native Region regionsAddress(); + + private static native int regionMaxCount(); + + private static native int availableBytes(); + + private static native int regionSize(); + + static { + currentChunk = heapAddress().toStructure(); + currentChunk.classReference = 0; + currentChunk.size = availableBytes(); + currentChunkLimit = currentChunk.toAddress().add(currentChunk.size); + currentChunkPointer = gcStorageAddress().toStructure(); + currentChunkPointer.value = currentChunk; + freeChunks = 1; + getAvailableChunkIfPossible(0); + } + + public static RuntimeObject alloc(int size) { + FreeChunk current = currentChunk; + Address next = currentChunk.toAddress().add(size); + if (!next.add(Structure.sizeOf(FreeChunk.class) + 1).isLessThan(currentChunkLimit)) { + getAvailableChunk(size); + } + int oldSize = current.size; + Address result = current.toAddress(); + currentChunk = next.toStructure(); + currentChunk.classReference = 0; + currentChunk.size = oldSize - size; + return result.toStructure(); + } + + private static void getAvailableChunk(int size) { + if (getAvailableChunkIfPossible(size)) { + return; + } + collectGarbage(size); + getAvailableChunkIfPossible(size); + } + + private static boolean getAvailableChunkIfPossible(int size) { + while (!currentChunk.toAddress().add(size).isLessThan(currentChunkLimit)) { + if (--size == 0) { + return false; + } + currentChunkPointer = currentChunkPointer.toAddress().add(FreeChunkHolder.class, 1).toStructure(); + currentChunk = currentChunkPointer.value; + currentChunkLimit = currentChunk.toAddress().add(currentChunk.size); + } + return false; + } + + private static boolean collectGarbage(int size) { + mark(); + return false; + } + + private static void mark() { + MarkQueue.init(); + Allocator.fillZero(regionsAddress().toAddress(), regionMaxCount() * Structure.sizeOf(Region.class)); + + Address staticRoots = Mutator.getStaticGcRoots(); + int staticCount = staticRoots.getInt(); + staticRoots.add(8); + while (staticCount-- > 0) { + RuntimeObject object = staticRoots.getAddress().toStructure(); + if (object != null) { + mark(object); + } + } + + for (Address stackRoots = Mutator.getStackGcRoots(); stackRoots != null; + stackRoots = Mutator.getNextStackRoots(stackRoots)) { + int count = Mutator.getStackRootCount(stackRoots); + Address stackRootsPtr = Mutator.getStackRootPointer(stackRoots); + while (count-- > 0) { + RuntimeObject obj = stackRootsPtr.getAddress().toStructure(); + mark(obj); + stackRootsPtr = stackRootsPtr.add(Address.sizeOf()); + } + } + } + + private static void mark(RuntimeObject object) { + if (object == null || isMarked(object)) { + return; + } + + MarkQueue.enqueue(object); + while (!MarkQueue.isEmpty()) { + object = MarkQueue.dequeue(); + if (isMarked(object)) { + continue; + } + object.classReference |= RuntimeObject.GC_MARKED; + + long offset = object.toAddress().toLong() - heapAddress().toLong(); + Region region = regionsAddress().toAddress().add(Region.class, (int) (offset / regionSize())) + .toStructure(); + short relativeOffset = (short) (offset % regionSize() + 1); + if (region.start == 0 || region.start > relativeOffset) { + region.start = relativeOffset; + } + + RuntimeClass cls = RuntimeClass.getClass(object); + while (cls != null) { + Address layout = cls.layout; + if (layout != null) { + short fieldCount = layout.getShort(); + while (fieldCount-- > 0) { + layout = layout.add(2); + int fieldOffset = layout.getShort(); + RuntimeObject reference = object.toAddress().add(fieldOffset).toStructure(); + if (reference != null && !isMarked(reference)) { + MarkQueue.enqueue(reference); + } + } + } + cls = cls.parent; + } + } + } + + private static boolean isMarked(RuntimeObject object) { + return (object.classReference & RuntimeObject.GC_MARKED) != 0; + } + + static class Region extends Structure { + short start; + } +} diff --git a/core/src/main/java/org/teavm/runtime/MarkQueue.java b/core/src/main/java/org/teavm/runtime/MarkQueue.java new file mode 100644 index 000000000..3712d7cea --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/MarkQueue.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * 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 org.teavm.runtime; + +import org.teavm.interop.Address; + +final class MarkQueue { + private MarkQueue() { + } + + private static int head; + private static int tail; + + static void init() { + head = 0; + tail = 0; + } + + private static native Address getBase(); + + private static native int getSize(); + + static void enqueue(RuntimeObject object) { + getBase().add(Address.sizeOf() * tail).putAddress(object.toAddress()); + if (++tail >= getSize()) { + tail = 0; + } + } + + static RuntimeObject dequeue() { + Address result = getBase().add(Address.sizeOf() * head).getAddress(); + if (++head >= getSize()) { + head = 0; + } + return result.toStructure(); + } + + static boolean isEmpty() { + return head == tail; + } +} diff --git a/core/src/main/java/org/teavm/runtime/Mutator.java b/core/src/main/java/org/teavm/runtime/Mutator.java new file mode 100644 index 000000000..769f44dea --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/Mutator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * 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 org.teavm.runtime; + +import org.teavm.interop.Address; +import org.teavm.interop.NoGC; + +@NoGC +public final class Mutator { + private Mutator() { + } + + public static native void allocStack(int size); + + public static native void registerGcRoot(int index, Object object); + + public static native void removeGcRoot(int index); + + public static native void releaseStack(int size); + + public static native Address getStaticGcRoots(); + + public static native Address getStackGcRoots(); + + public static native Address getNextStackRoots(Address address); + + public static native int getStackRootCount(Address address); + + public static native Address getStackRootPointer(Address address); +} diff --git a/interop/core/src/main/java/org/teavm/interop/Address.java b/interop/core/src/main/java/org/teavm/interop/Address.java index 04347b1af..87298d245 100644 --- a/interop/core/src/main/java/org/teavm/interop/Address.java +++ b/interop/core/src/main/java/org/teavm/interop/Address.java @@ -22,6 +22,8 @@ public final class Address { public native Address add(long offset); + public native boolean isLessThan(Address other); + public native int toInt(); public native long toLong(); @@ -56,6 +58,10 @@ public final class Address { public native void putDouble(double value); + public native Address getAddress(); + + public native void putAddress(Address value); + public static native Address fromInt(int value); public static native Address fromLong(long value);