From 2cd9e7f3df84ec743212110288e9a75fcab70252 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Wed, 30 Oct 2024 00:08:07 -0700 Subject: [PATCH 01/33] work on LaxMalloc --- .../java/org/teavm/runtime/LaxMalloc.java | 285 ++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 core/src/main/java/org/teavm/runtime/LaxMalloc.java diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java new file mode 100644 index 000000000..cb5b94e34 --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -0,0 +1,285 @@ +/* + * Copyright 2024 lax1dude. + * + * 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.Unmanaged; + +/** + * Linear memory allocator for creating "direct buffers" in WASM GC

+ * + * DO NOT USE IN LEGACY WASM BACKEND!!! Make a regular byte array, and use Address.ofData()

+ * + * Similar to dlmalloc and emmalloc (emscripten's malloc)

+ * + * bad things will happen if you free an address that was never allocated + * + * @author lax1dude + */ +@Unmanaged +public final class LaxMalloc { + + private LaxMalloc() { + } + + private static final int SIZEOF_PTR = 4; + private static final int SIZEOF_PTR_SH = 2; + + private static final int MIN_ALLOC_SIZE = 8; + + private static final int ADDR_HEAP_LIMIT = 4; // Address where we store the current heap limit (32 bit int) + private static final int ADDR_HEAP_BUCKETS_FREE_MASK = 8; // Address where we store the bitmask of free chunk lists (64 bit int) + private static final int ADDR_HEAP_BUCKETS_START = 16; // Address to the list of 64 pointers to the beginnings of the 64 buckets + private static final int ADDR_HEAP_DATA_START = 272; // Beginning of the first chunk of the heap + + /** + * malloc implementation + */ + public static Address laxMalloc(int sizeBytes) { + return laxAlloc(sizeBytes, false); + } + + /** + * calloc implementation (zeroed malloc) + */ + public static Address laxCalloc(int sizeBytes) { + return laxAlloc(sizeBytes, true); + } + + private static Address laxAlloc(int sizeBytes, boolean cleared) { + if(sizeBytes == 0) { + // Produce a null pointer if 0 size if requested + return Address.fromInt(0); + } + + // Allocation must be large enough to hold the two list pointers when the chunk becomes free again + if(sizeBytes < MIN_ALLOC_SIZE) { + sizeBytes = MIN_ALLOC_SIZE; + } + + // always between 0-63 + int bucket = getListBucket(sizeBytes); + + long bucketMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); + + // test the bucket mask if the bucket has any free chunks + if((bucketMask & (1L << bucket)) == 0l) { + // no more free chunks, let us first check if there is are any + // chunks in the larger buckets we can split + long largerBucketsMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << (bucket + 1))); + if(largerBucketsMask != 0l) { + // at least one larger chunk exists + int largerBucket = Long.numberOfTrailingZeros(largerBucketsMask); + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(largerBucket << SIZEOF_PTR_SH); + Address chunkPtr = bucketStartAddr.getAddress(); + //TODO: remove chunk from old bucket + int chunkSize = readChunkSize(chunkPtr); + int chunkOtherHalfNewSize = chunkSize - sizeBytes; + //TODO + //TODO + //TODO + + return null; //TODO + }else { + // No larger chunks already exist that we can split, + // time to sbrk + int sizePlusInts = sizeBytes + 8; // size + 2 ints + Address newChunk = growHeap(sizePlusInts); + + // Out of memory + if(newChunk.toInt() == 0) { + return Address.fromInt(0); //TODO + } + + // provision the new chunk + newChunk.putInt(sizePlusInts | 0x80000000); // size + in use flag + newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end + + // return the chunk, +4 bytes to skip size int + // we don't need to clear it because its new + return newChunk.add(4); + } + }else { + // At least one free chunk is available, get it from the bucket + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(bucket << SIZEOF_PTR_SH); + Address chunkPtr = bucketStartAddr.getAddress(); + + Address nextStart = readChunkNextFreeAddr(chunkPtr); + if(nextStart.toInt() != 0) { + // there is another free chunk in the bucket that comes after this chunk, + // make the next free chunk in the list the first free chunk + bucketStartAddr.putAddress(nextStart); + writeChunkPrevFreeAddr(nextStart, Address.fromInt(0)); + }else { + // there are no remaining free chunks in the bucket + // clear the bit in the bucket bitmask + bucketMask = (bucketMask ^ (1L << bucket)); + Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketMask); + + // set bucket start chunk pointer to null + bucketStartAddr.putAddress(Address.fromInt(0)); + } + + // mark the chunk in use + setChunkInUse(chunkPtr, true); + + // return the chunk we just removed from the list, +4 bytes to skip size + Address ret = chunkPtr.add(4); + + // clear if requested + if(cleared) { + Allocator.fillZero(ret, sizeBytes); + } + + return ret; + } + } + + /** + * free implementation

+ * + * bad things will happen if you free an address that was never allocated + */ + public static void laxFree(Address address) { + if(address.toInt() == 0) { + return; + } + + // chunk actually starts 4 bytes before + Address chunkPtr = address.add(-4); + + // set the chunk no longer in use + setChunkInUse(chunkPtr, false); + + // bring the size of the chunk into the stack + int chunkSize = chunkPtr.getInt(); + + if(Address.fromInt(ADDR_HEAP_DATA_START).isLessThan(chunkPtr)) { + // check if we can merge with the previous chunk, and move it to another bucket + Address prevChunkPtr = chunkPtr.add(-(chunkPtr.add(-4).getInt())); + int prevChunkSize = readChunkSizeStatus(prevChunkPtr); + if((prevChunkSize & 0x80000000) == 0) { + // previous chunk is not in use, merge! + + //TODO + } + } + + Address nextChunkPtr = chunkPtr.add(chunkSize); + if(Address.fromInt(ADDR_HEAP_LIMIT).getAddress().isLessThan(nextChunkPtr)) { + // check if we can merge with the next chunk as well + int nextChunkSize = readChunkSizeStatus(nextChunkPtr); + if((nextChunkSize & 0x80000000) == 0) { + // next chunk is not in use, merge! + + //TODO + } + } + + int bucket = getListBucket(chunkSize - 8); // size - 2 ints + + long bucketMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(bucket << SIZEOF_PTR_SH); + + // test the bucket mask if the bucket is empty + if((bucketMask & (1L << bucket)) == 0l) { + // bucket is empty, add the free chunk to the list + bucketStartAddr.putAddress(chunkPtr); + writeChunkPrevFreeAddr(chunkPtr, Address.fromInt(0)); + writeChunkNextFreeAddr(chunkPtr, Address.fromInt(0)); + // set the free bit in bucket mask + bucketMask |= (1L << bucket); + Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketMask); + }else { + // bucket is not empty, append to the bucket's existing free chunks list + Address otherBucketStart = bucketStartAddr.getAddress(); + writeChunkPrevFreeAddr(otherBucketStart, chunkPtr); + writeChunkNextFreeAddr(chunkPtr, otherBucketStart); + writeChunkPrevFreeAddr(chunkPtr, Address.fromInt(0)); + bucketStartAddr.putAddress(chunkPtr); + } + + } + + private static final int NUM_FREE_BUCKETS = 64; + + /** + * https://github.com/emscripten-core/emscripten/blob/16a0bf174cb85f88b6d9dcc8ee7fbca59390185b/system/lib/emmalloc.c#L241 + * (MIT License) + */ + private static int getListBucket(int allocSize) { + if (allocSize < 128) + return (allocSize >> 3) - 1; + + int clz = Integer.numberOfLeadingZeros(allocSize); + int bucketIndex = (clz > 19) ? 110 - (clz << 2) + ((allocSize >> (29 - clz)) ^ 4) + : min(71 - (clz << 1) + ((allocSize >> (30 - clz)) ^ 2), NUM_FREE_BUCKETS - 1); + + return bucketIndex; + } + + /** + * This is our sbrk + */ + private static Address growHeap(int amount) { + Address heapLimit = Address.fromInt(ADDR_HEAP_LIMIT).getAddress(); + Address.fromInt(ADDR_HEAP_LIMIT).putAddress(heapLimit.add(amount)); + //TODO: expand WebAssembly Memory + return heapLimit; + } + + private static int readChunkSizeStatus(Address chunkAddr) { + return chunkAddr.getInt(); + } + + private static int readChunkSize(Address chunkAddr) { + return chunkAddr.getInt() & 0x7FFFFFFF; + } + + private static boolean readChunkInUse(Address chunkAddr) { + return (chunkAddr.getInt() & 0x80000000) != 0; + } + + private static void writeChunkSizeStatus(Address chunkAddr, int sizeStatus) { + chunkAddr.putInt(sizeStatus); + } + + private static void setChunkInUse(Address chunkAddr, boolean inUse) { + int i = chunkAddr.getInt(); + chunkAddr.putInt(inUse ? (i | 0x80000000) : (i & 0x7FFFFFFF)); + } + + private static Address readChunkPrevFreeAddr(Address chunkAddr) { + return chunkAddr.add(4).getAddress(); + } + + private static void writeChunkPrevFreeAddr(Address chunkAddr, Address prevFree) { + chunkAddr.add(4).putAddress(prevFree); + } + + private static Address readChunkNextFreeAddr(Address chunkAddr) { + return chunkAddr.add(4 + (1 << SIZEOF_PTR_SH)).getAddress(); + } + + private static void writeChunkNextFreeAddr(Address chunkAddr, Address nextFree) { + chunkAddr.add(4 + (1 << SIZEOF_PTR_SH)).putAddress(nextFree); + } + + private static int min(int a, int b) { + return a < b ? a : b; + } + +} From 2b73f658d093adc778aea64a3cecd782b2cb1088 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Thu, 31 Oct 2024 00:13:40 -0700 Subject: [PATCH 02/33] work on LaxMalloc --- .../java/org/teavm/runtime/LaxMalloc.java | 152 ++++++++++++++++-- 1 file changed, 137 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index cb5b94e34..30be7a894 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -16,6 +16,7 @@ package org.teavm.runtime; import org.teavm.interop.Address; +import org.teavm.interop.StaticInit; import org.teavm.interop.Unmanaged; /** @@ -30,6 +31,7 @@ import org.teavm.interop.Unmanaged; * @author lax1dude */ @Unmanaged +@StaticInit public final class LaxMalloc { private LaxMalloc() { @@ -45,6 +47,13 @@ public final class LaxMalloc { private static final int ADDR_HEAP_BUCKETS_START = 16; // Address to the list of 64 pointers to the beginnings of the 64 buckets private static final int ADDR_HEAP_DATA_START = 272; // Beginning of the first chunk of the heap + static { + // zero out the control region + Allocator.fillZero(Address.fromInt(0), ADDR_HEAP_DATA_START); + // initialize heap limit + Address.fromInt(ADDR_HEAP_LIMIT).putInt(ADDR_HEAP_DATA_START); + } + /** * malloc implementation */ @@ -60,8 +69,8 @@ public final class LaxMalloc { } private static Address laxAlloc(int sizeBytes, boolean cleared) { - if(sizeBytes == 0) { - // Produce a null pointer if 0 size if requested + if(sizeBytes <= 0) { + // Produce a null pointer if 0 or invalid size is requested return Address.fromInt(0); } @@ -73,26 +82,61 @@ public final class LaxMalloc { // always between 0-63 int bucket = getListBucket(sizeBytes); + if(bucket == 63) { + // special bucket for the huge allocations + // uses a different function + return laxHugeAlloc(sizeBytes, cleared); + } + long bucketMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); // test the bucket mask if the bucket has any free chunks if((bucketMask & (1L << bucket)) == 0l) { - // no more free chunks, let us first check if there is are any + // no more free chunks, let us first check if there are any // chunks in the larger buckets we can split long largerBucketsMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << (bucket + 1))); if(largerBucketsMask != 0l) { // at least one larger chunk exists - int largerBucket = Long.numberOfTrailingZeros(largerBucketsMask); + // we can quickly find it using the bitmask + int largerBucket = numberOfTrailingZerosL(largerBucketsMask); + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(largerBucket << SIZEOF_PTR_SH); Address chunkPtr = bucketStartAddr.getAddress(); - //TODO: remove chunk from old bucket int chunkSize = readChunkSize(chunkPtr); - int chunkOtherHalfNewSize = chunkSize - sizeBytes; - //TODO - //TODO - //TODO - return null; //TODO + int chunkNewSize = chunkSize - sizeBytes - 8; // -size - 2 ints - 2 more ints + + // if the other half of the new chunk is too small, check an even larger bucket + // we should only need to look at an even larger bucket one more time before giving up + if(chunkNewSize - 8 < MIN_ALLOC_SIZE) { + //TODO + } + + // remove the large chunk from its bucket + unlinkChunkFromFreeList(chunkPtr, chunkSize); + + // provision the part of the large chunk we want + int sizePlusInts = sizeBytes + 8; // size + 2 ints + chunkPtr.putInt(sizePlusInts | 0x80000000); // size + in use flag + chunkPtr.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end + + // provision the other part of the chunk that we want to return to the free list + Address otherChunkPtr = chunkPtr.add(sizePlusInts); + otherChunkPtr.putInt(chunkNewSize); // size + otherChunkPtr.add(chunkNewSize - 4).putInt(chunkNewSize); // size (end) + + // return the other part of the chunk to the free chunks list + linkChunkInFreeList(otherChunkPtr, chunkNewSize); + + // +4 bytes to skip size + Address ret = chunkPtr.add(4); + + // clear if requested + if(cleared) { + Allocator.fillZero(ret, sizeBytes); + } + + return ret; }else { // No larger chunks already exist that we can split, // time to sbrk @@ -109,7 +153,7 @@ public final class LaxMalloc { newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end // return the chunk, +4 bytes to skip size int - // we don't need to clear it because its new + // we don't need to clear it because its new memory return newChunk.add(4); } }else { @@ -148,6 +192,10 @@ public final class LaxMalloc { } } + private static Address laxHugeAlloc(int sizeBytes, boolean cleared) { + return null; //TODO: bucket number 63 + } + /** * free implementation

* @@ -174,7 +222,14 @@ public final class LaxMalloc { if((prevChunkSize & 0x80000000) == 0) { // previous chunk is not in use, merge! - //TODO + // remove the previous chunk from its list + unlinkChunkFromFreeList(prevChunkPtr, prevChunkSize); + + // resize the current chunk to also contain the previous chunk + chunkPtr = prevChunkPtr; + chunkSize += prevChunkSize; + chunkPtr.putInt(chunkSize); + chunkPtr.add(chunkSize).putInt(chunkSize); } } @@ -184,11 +239,22 @@ public final class LaxMalloc { int nextChunkSize = readChunkSizeStatus(nextChunkPtr); if((nextChunkSize & 0x80000000) == 0) { // next chunk is not in use, merge! + + // remove the next chunk from its list + unlinkChunkFromFreeList(nextChunkPtr, nextChunkSize); - //TODO + // resize the current chunk to also contain the next chunk + chunkSize += nextChunkSize; + chunkPtr.putInt(chunkSize); + chunkPtr.add(chunkSize).putInt(chunkSize); } } + // add the final chunk to the free chunks list + linkChunkInFreeList(chunkPtr, chunkSize); + } + + private static void linkChunkInFreeList(Address chunkPtr, int chunkSize) { int bucket = getListBucket(chunkSize - 8); // size - 2 ints long bucketMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); @@ -211,7 +277,53 @@ public final class LaxMalloc { writeChunkPrevFreeAddr(chunkPtr, Address.fromInt(0)); bucketStartAddr.putAddress(chunkPtr); } - + } + + private static void unlinkChunkFromFreeList(Address chunkPtr, int chunkSize) { + Address prevChunkPtr = readChunkPrevFreeAddr(chunkPtr); + Address nextChunkPtr = readChunkNextFreeAddr(chunkPtr); + if((prevChunkPtr.toInt() | nextChunkPtr.toInt()) == 0) { + // chunk is the only one currently in its bucket + + int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints + + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); + bucketStartAddr.putAddress(Address.fromInt(0)); // remove chunk from the bucket + + // clear the bit in the free buckets bitmask + long bucketsFreeMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); + bucketsFreeMask &= ~(1L << chunkBucket); + Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketsFreeMask); + + }else { + // there are other chunks in this bucket + + if(prevChunkPtr.toInt() != 0) { + // previous chunk exists + + if(nextChunkPtr.toInt() != 0) { + // next chunk exits, link it to the previous chunk + writeChunkNextFreeAddr(prevChunkPtr, nextChunkPtr); + writeChunkPrevFreeAddr(nextChunkPtr, prevChunkPtr); + }else { + // there is no next chunk + writeChunkNextFreeAddr(prevChunkPtr, Address.fromInt(0)); + } + + }else { + // no previous chunk, this must be the first chunk in the bucket + + int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); + + // we already know the next chunk exists + // make the next chunk the first chunk in the bucket + bucketStartAddr.putAddress(nextChunkPtr); + + // unlink the next chunk from the current chunk + writeChunkPrevFreeAddr(nextChunkPtr, Address.fromInt(0)); + } + } } private static final int NUM_FREE_BUCKETS = 64; @@ -224,7 +336,7 @@ public final class LaxMalloc { if (allocSize < 128) return (allocSize >> 3) - 1; - int clz = Integer.numberOfLeadingZeros(allocSize); + int clz = numberOfLeadingZerosI(allocSize); int bucketIndex = (clz > 19) ? 110 - (clz << 2) + ((allocSize >> (29 - clz)) ^ 4) : min(71 - (clz << 1) + ((allocSize >> (30 - clz)) ^ 2), NUM_FREE_BUCKETS - 1); @@ -282,4 +394,14 @@ public final class LaxMalloc { return a < b ? a : b; } + private static int numberOfTrailingZerosL(long i) { + //TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! + return Long.numberOfTrailingZeros(i); + } + + private static int numberOfLeadingZerosI(int i) { + //TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! + return Integer.numberOfLeadingZeros(i); + } + } From 5830392f60db5b1509da40c79c84875effeb2bcf Mon Sep 17 00:00:00 2001 From: lax1dude Date: Thu, 31 Oct 2024 22:47:36 -0700 Subject: [PATCH 03/33] work on LaxMalloc --- .../java/org/teavm/runtime/LaxMalloc.java | 348 ++++++++++++------ 1 file changed, 234 insertions(+), 114 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 30be7a894..49c9315ad 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -84,52 +84,74 @@ public final class LaxMalloc { if(bucket == 63) { // special bucket for the huge allocations - // uses a different function + // uses a different slower function return laxHugeAlloc(sizeBytes, cleared); } + // load bitmask of buckets with free chunks long bucketMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); - // test the bucket mask if the bucket has any free chunks - if((bucketMask & (1L << bucket)) == 0l) { - // no more free chunks, let us first check if there are any - // chunks in the larger buckets we can split - long largerBucketsMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << (bucket + 1))); - if(largerBucketsMask != 0l) { - // at least one larger chunk exists - // we can quickly find it using the bitmask - int largerBucket = numberOfTrailingZerosL(largerBucketsMask); + // mask away the buckets that we know are too small for this allocation + bucketMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << bucket)); + + // there are no more buckets with free chunks + // need to sbrk + if(bucketMask == 0l) { + int sizePlusInts = sizeBytes + 8; // size + 2 ints + Address newChunk = growHeap(sizePlusInts); // sbrk + + // Out of memory + if(newChunk.toInt() == 0) { + return Address.fromInt(0); //TODO + } + + // provision the new chunk + newChunk.putInt(sizePlusInts | 0x80000000); // size + in use flag + newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end + + // return the chunk, +4 bytes to skip size int + // we don't need to clear it because its new memory + return newChunk.add(4); + } + + // at least one bucket exists containing a free chunk, + // quickly determine which bucket it is with bit hacks + int availableBucket = numberOfTrailingZerosL(bucketMask); + + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableBucket << SIZEOF_PTR_SH); + Address chunkPtr = bucketStartAddr.getAddress(); + int chunkSize = readChunkSize(chunkPtr); + boolean bucketHasMultipleChunks = false; + + // check if the first chunk in the bucket is large enough + if(chunkSize - 8 < sizeBytes) { // size - 2 ints + + // the chunk is not large enough, move the first chunk to the end of the list + // and then check in the next bucket (where the chunks are definitely large enough) + // this functionality is present in emmalloc (emscripten) + + Address chunkNextPtr = readChunkNextFreeAddr(chunkPtr); + if(chunkNextPtr.getInt() != chunkPtr.getInt()) { + bucketStartAddr.putAddress(chunkNextPtr); + chunkPtr = chunkNextPtr; + bucketHasMultipleChunks = true; + } + + // extend mask to the next bucket + bucketMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << (bucket + 1))); + + if(bucketMask != 0l) { + // there is a bucket with a larger chunk + int availableLargerBucket = numberOfTrailingZerosL(bucketMask); + Address largerBucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableLargerBucket << SIZEOF_PTR_SH); + Address largerChunkPtr = largerBucketStartAddr.getAddress(); + int largerChunkSize = readChunkSize(largerChunkPtr); - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(largerBucket << SIZEOF_PTR_SH); - Address chunkPtr = bucketStartAddr.getAddress(); - int chunkSize = readChunkSize(chunkPtr); + // this will remove the chunk from the free list + allocateMemoryFromChunk(largerChunkPtr, largerChunkSize, sizeBytes); - int chunkNewSize = chunkSize - sizeBytes - 8; // -size - 2 ints - 2 more ints - - // if the other half of the new chunk is too small, check an even larger bucket - // we should only need to look at an even larger bucket one more time before giving up - if(chunkNewSize - 8 < MIN_ALLOC_SIZE) { - //TODO - } - - // remove the large chunk from its bucket - unlinkChunkFromFreeList(chunkPtr, chunkSize); - - // provision the part of the large chunk we want - int sizePlusInts = sizeBytes + 8; // size + 2 ints - chunkPtr.putInt(sizePlusInts | 0x80000000); // size + in use flag - chunkPtr.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end - - // provision the other part of the chunk that we want to return to the free list - Address otherChunkPtr = chunkPtr.add(sizePlusInts); - otherChunkPtr.putInt(chunkNewSize); // size - otherChunkPtr.add(chunkNewSize - 4).putInt(chunkNewSize); // size (end) - - // return the other part of the chunk to the free chunks list - linkChunkInFreeList(otherChunkPtr, chunkNewSize); - - // +4 bytes to skip size - Address ret = chunkPtr.add(4); + // +4 bytes to skip size int + Address ret = largerChunkPtr.add(4); // clear if requested if(cleared) { @@ -137,50 +159,13 @@ public final class LaxMalloc { } return ret; - }else { - // No larger chunks already exist that we can split, - // time to sbrk - int sizePlusInts = sizeBytes + 8; // size + 2 ints - Address newChunk = growHeap(sizePlusInts); - - // Out of memory - if(newChunk.toInt() == 0) { - return Address.fromInt(0); //TODO - } - - // provision the new chunk - newChunk.putInt(sizePlusInts | 0x80000000); // size + in use flag - newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end - - // return the chunk, +4 bytes to skip size int - // we don't need to clear it because its new memory - return newChunk.add(4); } }else { - // At least one free chunk is available, get it from the bucket - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(bucket << SIZEOF_PTR_SH); - Address chunkPtr = bucketStartAddr.getAddress(); + // the first chunk in the bucket is large enough + // this will remove the chunk from the free list + allocateMemoryFromChunk(chunkPtr, chunkSize, sizeBytes); - Address nextStart = readChunkNextFreeAddr(chunkPtr); - if(nextStart.toInt() != 0) { - // there is another free chunk in the bucket that comes after this chunk, - // make the next free chunk in the list the first free chunk - bucketStartAddr.putAddress(nextStart); - writeChunkPrevFreeAddr(nextStart, Address.fromInt(0)); - }else { - // there are no remaining free chunks in the bucket - // clear the bit in the bucket bitmask - bucketMask = (bucketMask ^ (1L << bucket)); - Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketMask); - - // set bucket start chunk pointer to null - bucketStartAddr.putAddress(Address.fromInt(0)); - } - - // mark the chunk in use - setChunkInUse(chunkPtr, true); - - // return the chunk we just removed from the list, +4 bytes to skip size + // +4 bytes to skip size int Address ret = chunkPtr.add(4); // clear if requested @@ -190,10 +175,105 @@ public final class LaxMalloc { return ret; } + + if(bucketHasMultipleChunks) { + + // if we've reached this point, it means the first chunk in the bucket wasn't large enough + // and there weren't any chunks in the larger buckets we could split up + // so we need to look closer + + // iterate the (only) bucket of possibly large enough chunks + Address addrIterator = chunkPtr; + while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()) { + chunkSize = readChunkSize(addrIterator); + + // check if the chunk is large enough + if(chunkSize - 8 >= sizeBytes) { // size - 2 ints + // we've found a large enough chunk + // this will remove the chunk from the free list + allocateMemoryFromChunk(addrIterator, chunkSize, sizeBytes); + + // +4 bytes to skip size int + Address ret = chunkPtr.add(4); + + // clear if requested + if(cleared) { + Allocator.fillZero(ret, sizeBytes); + } + + return ret; + } + } + } + + // no other options, time to sbrk + + int sizePlusInts = sizeBytes + 8; // size + 2 ints + Address newChunk = growHeap(sizePlusInts); // sbrk + + // Out of memory + if(newChunk.toInt() == 0) { + return Address.fromInt(0); //TODO + } + + // provision the new chunk + newChunk.putInt(sizePlusInts | 0x80000000); // size + in use flag + newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end + + // return the chunk, +4 bytes to skip size int + // we don't need to clear it because its new memory + return newChunk.add(4); } private static Address laxHugeAlloc(int sizeBytes, boolean cleared) { - return null; //TODO: bucket number 63 + + // check the bucket mask if bucket 63 has any chunks + if((Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong() & 0x8000000000000000L) != 0) { + + // bucket 63 address + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(63 << SIZEOF_PTR_SH); + Address chunkPtr = bucketStartAddr.getAddress(); + + // iterate all free huge chunks + Address addrIterator = chunkPtr; + while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()) { + int chunkSize = readChunkSize(addrIterator); + + if(chunkSize - 8 >= sizeBytes) { // size - 2 ints + // we've found a large enough chunk + // this will remove the chunk from the free list + allocateMemoryFromChunk(addrIterator, chunkSize, sizeBytes); + + // +4 bytes to skip size int + Address ret = chunkPtr.add(4); + + // clear if requested + if(cleared) { + Allocator.fillZero(ret, sizeBytes); + } + + return ret; + } + } + } + + // no free huge chunks found, time to sbrk + + int sizePlusInts = sizeBytes + 8; // size + 2 ints + Address newChunk = growHeap(sizePlusInts); // sbrk + + // Out of memory + if(newChunk.toInt() == 0) { + return Address.fromInt(0); //TODO + } + + // provision the new chunk + newChunk.putInt(sizePlusInts | 0x80000000); // size + in use flag + newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end + + // return the chunk, +4 bytes to skip size int + // we don't need to clear it because its new memory + return newChunk.add(4); } /** @@ -229,7 +309,7 @@ public final class LaxMalloc { chunkPtr = prevChunkPtr; chunkSize += prevChunkSize; chunkPtr.putInt(chunkSize); - chunkPtr.add(chunkSize).putInt(chunkSize); + chunkPtr.add(chunkSize - 4).putInt(chunkSize); } } @@ -246,7 +326,7 @@ public final class LaxMalloc { // resize the current chunk to also contain the next chunk chunkSize += nextChunkSize; chunkPtr.putInt(chunkSize); - chunkPtr.add(chunkSize).putInt(chunkSize); + chunkPtr.add(chunkSize - 4).putInt(chunkSize); } } @@ -254,6 +334,44 @@ public final class LaxMalloc { linkChunkInFreeList(chunkPtr, chunkSize); } + /** + * Allocates memory from a free chunk, if the allocSize is smaller than the chunkSize by + * enough of a margin then the chunk is split into two smaller chunks, and the upper part + * of the chunk is returned to a bucket of free chunks + */ + private static void allocateMemoryFromChunk(Address chunkPtr, int chunkSize, int allocSize) { + // remove the chunk from its bucket + unlinkChunkFromFreeList(chunkPtr, chunkSize); + + int otherHalfSize = chunkSize - allocSize - 8; // -size - 2 ints + + // check if we can split the chunk into two smaller chunks + // chunk must be large enough to hold the 2 list pointers + if(otherHalfSize - (2 << SIZEOF_PTR_SH) >= MIN_ALLOC_SIZE) { + // chunk is large enough to split + + // provision the lower part of the chunk, the part we want to use + int sizePlusInts = allocSize + 8; // size + 2 ints + chunkPtr.putInt(sizePlusInts | 0x80000000); // size + in use flag + chunkPtr.add(allocSize + 4).putInt(sizePlusInts); // size integer at the end + + // provision the upper part of the chunk that we want to return to the free list + Address otherChunkPtr = chunkPtr.add(sizePlusInts); + otherChunkPtr.putInt(otherHalfSize); // size + otherChunkPtr.add(otherHalfSize - 4).putInt(otherHalfSize); // size (end) + + // return the upper part of the chunk to the free chunks list + linkChunkInFreeList(otherChunkPtr, otherHalfSize); + + }else { + // not large enough to split, just take the entire chunk + setChunkInUse(chunkPtr, true); + } + } + + /** + * Adds a free chunk to its corresponding bucket + */ private static void linkChunkInFreeList(Address chunkPtr, int chunkSize) { int bucket = getListBucket(chunkSize - 8); // size - 2 ints @@ -262,27 +380,43 @@ public final class LaxMalloc { // test the bucket mask if the bucket is empty if((bucketMask & (1L << bucket)) == 0l) { + // bucket is empty, add the free chunk to the list bucketStartAddr.putAddress(chunkPtr); - writeChunkPrevFreeAddr(chunkPtr, Address.fromInt(0)); - writeChunkNextFreeAddr(chunkPtr, Address.fromInt(0)); + writeChunkPrevFreeAddr(chunkPtr, chunkPtr); + writeChunkNextFreeAddr(chunkPtr, chunkPtr); + // set the free bit in bucket mask bucketMask |= (1L << bucket); Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketMask); + }else { + // bucket is not empty, append to the bucket's existing free chunks list Address otherBucketStart = bucketStartAddr.getAddress(); - writeChunkPrevFreeAddr(otherBucketStart, chunkPtr); + Address otherBucketPrev = readChunkPrevFreeAddr(otherBucketStart); + + // link new chunk to the existing chunks in the bucket + writeChunkPrevFreeAddr(chunkPtr, otherBucketPrev); writeChunkNextFreeAddr(chunkPtr, otherBucketStart); - writeChunkPrevFreeAddr(chunkPtr, Address.fromInt(0)); + + // link the existing chunks in the bucket to the new chunk + writeChunkPrevFreeAddr(otherBucketStart, chunkPtr); + writeChunkNextFreeAddr(otherBucketPrev, chunkPtr); + + // put the chunk in the bucket bucketStartAddr.putAddress(chunkPtr); + } } + /** + * Removes a free chunk from its corresponding bucket + */ private static void unlinkChunkFromFreeList(Address chunkPtr, int chunkSize) { Address prevChunkPtr = readChunkPrevFreeAddr(chunkPtr); Address nextChunkPtr = readChunkNextFreeAddr(chunkPtr); - if((prevChunkPtr.toInt() | nextChunkPtr.toInt()) == 0) { + if(prevChunkPtr.toInt() == nextChunkPtr.toInt()) { // chunk is the only one currently in its bucket int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints @@ -298,36 +432,22 @@ public final class LaxMalloc { }else { // there are other chunks in this bucket - if(prevChunkPtr.toInt() != 0) { - // previous chunk exists - - if(nextChunkPtr.toInt() != 0) { - // next chunk exits, link it to the previous chunk - writeChunkNextFreeAddr(prevChunkPtr, nextChunkPtr); - writeChunkPrevFreeAddr(nextChunkPtr, prevChunkPtr); - }else { - // there is no next chunk - writeChunkNextFreeAddr(prevChunkPtr, Address.fromInt(0)); - } - - }else { - // no previous chunk, this must be the first chunk in the bucket - - int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); - - // we already know the next chunk exists - // make the next chunk the first chunk in the bucket + // link the next chunk to the previous chunk + writeChunkNextFreeAddr(prevChunkPtr, nextChunkPtr); + writeChunkPrevFreeAddr(nextChunkPtr, prevChunkPtr); + + int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); + Address bucketStartChunk = bucketStartAddr.getAddress(); + + // chunk is the first in the bucket, so we also need to + // update the bucket to point to the next chunk instead + if(bucketStartChunk.toInt() == chunkPtr.toInt()) { bucketStartAddr.putAddress(nextChunkPtr); - - // unlink the next chunk from the current chunk - writeChunkPrevFreeAddr(nextChunkPtr, Address.fromInt(0)); } } } - private static final int NUM_FREE_BUCKETS = 64; - /** * https://github.com/emscripten-core/emscripten/blob/16a0bf174cb85f88b6d9dcc8ee7fbca59390185b/system/lib/emmalloc.c#L241 * (MIT License) @@ -338,7 +458,7 @@ public final class LaxMalloc { int clz = numberOfLeadingZerosI(allocSize); int bucketIndex = (clz > 19) ? 110 - (clz << 2) + ((allocSize >> (29 - clz)) ^ 4) - : min(71 - (clz << 1) + ((allocSize >> (30 - clz)) ^ 2), NUM_FREE_BUCKETS - 1); + : min(71 - (clz << 1) + ((allocSize >> (30 - clz)) ^ 2), 63); return bucketIndex; } From 30a61fd9dd01127e983f5d5a627fd3704bdca9b7 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Thu, 31 Oct 2024 22:49:52 -0700 Subject: [PATCH 04/33] fuck --- core/src/main/java/org/teavm/runtime/LaxMalloc.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 49c9315ad..688ca01a8 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -515,13 +515,13 @@ public final class LaxMalloc { } private static int numberOfTrailingZerosL(long i) { - //TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! - return Long.numberOfTrailingZeros(i); - } + // TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! + return Long.numberOfTrailingZeros(i); + } private static int numberOfLeadingZerosI(int i) { - //TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! - return Integer.numberOfLeadingZeros(i); - } + // TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! + return Integer.numberOfLeadingZeros(i); + } } From f266d21f585a250c12c3148c3692f606fed9668d Mon Sep 17 00:00:00 2001 From: lax1dude Date: Fri, 1 Nov 2024 00:15:24 -0700 Subject: [PATCH 05/33] work on LaxMalloc --- .../java/org/teavm/runtime/LaxMalloc.java | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 688ca01a8..644f8707f 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -16,6 +16,7 @@ package org.teavm.runtime; import org.teavm.interop.Address; +import org.teavm.interop.Import; import org.teavm.interop.StaticInit; import org.teavm.interop.Unmanaged; @@ -42,7 +43,8 @@ public final class LaxMalloc { private static final int MIN_ALLOC_SIZE = 8; - private static final int ADDR_HEAP_LIMIT = 4; // Address where we store the current heap limit (32 bit int) + private static final int ADDR_HEAP_OUTER_LIMIT = 0; // Address where we store the WebAssembly.Memory limit (32 bit int) + private static final int ADDR_HEAP_INNER_LIMIT = 4; // Address where we store the current heap limit (32 bit int) private static final int ADDR_HEAP_BUCKETS_FREE_MASK = 8; // Address where we store the bitmask of free chunk lists (64 bit int) private static final int ADDR_HEAP_BUCKETS_START = 16; // Address to the list of 64 pointers to the beginnings of the 64 buckets private static final int ADDR_HEAP_DATA_START = 272; // Beginning of the first chunk of the heap @@ -51,7 +53,8 @@ public final class LaxMalloc { // zero out the control region Allocator.fillZero(Address.fromInt(0), ADDR_HEAP_DATA_START); // initialize heap limit - Address.fromInt(ADDR_HEAP_LIMIT).putInt(ADDR_HEAP_DATA_START); + Address.fromInt(ADDR_HEAP_INNER_LIMIT).putInt(ADDR_HEAP_DATA_START); + Address.fromInt(ADDR_HEAP_OUTER_LIMIT).putInt(0x10000); } /** @@ -314,7 +317,7 @@ public final class LaxMalloc { } Address nextChunkPtr = chunkPtr.add(chunkSize); - if(Address.fromInt(ADDR_HEAP_LIMIT).getAddress().isLessThan(nextChunkPtr)) { + if(Address.fromInt(ADDR_HEAP_INNER_LIMIT).getAddress().isLessThan(nextChunkPtr)) { // check if we can merge with the next chunk as well int nextChunkSize = readChunkSizeStatus(nextChunkPtr); if((nextChunkSize & 0x80000000) == 0) { @@ -467,12 +470,28 @@ public final class LaxMalloc { * This is our sbrk */ private static Address growHeap(int amount) { - Address heapLimit = Address.fromInt(ADDR_HEAP_LIMIT).getAddress(); - Address.fromInt(ADDR_HEAP_LIMIT).putAddress(heapLimit.add(amount)); - //TODO: expand WebAssembly Memory - return heapLimit; + Address heapInnerLimit = Address.fromInt(ADDR_HEAP_INNER_LIMIT).getAddress(); + Address heapOuterLimit = Address.fromInt(ADDR_HEAP_OUTER_LIMIT).getAddress(); + Address newHeapInnerLimit = heapInnerLimit.add(amount); + if(heapOuterLimit.isLessThan(newHeapInnerLimit)) { + int bytesNeeded = newHeapInnerLimit.toInt() - heapOuterLimit.toInt(); + bytesNeeded = (bytesNeeded + 0xFFFF) & 0xFFFF0000; + if(growHeapOuter(bytesNeeded)) { + Address.fromInt(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); + Address.fromInt(ADDR_HEAP_OUTER_LIMIT).putAddress(heapOuterLimit.add(bytesNeeded)); + return newHeapInnerLimit; + }else { + return Address.fromInt(0); + } + }else { + Address.fromInt(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); + return newHeapInnerLimit; + } } + @Import(name = "teavm_growHeap") + private static native boolean growHeapOuter(int bytes); + private static int readChunkSizeStatus(Address chunkAddr) { return chunkAddr.getInt(); } From 9181299a6defad53cf36c1b429a4bbdc01be7dfb Mon Sep 17 00:00:00 2001 From: lax1dude Date: Fri, 1 Nov 2024 17:47:27 -0700 Subject: [PATCH 06/33] work on LaxMalloc --- .../java/org/teavm/runtime/LaxMalloc.java | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 644f8707f..f7f5c30b7 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -82,6 +82,9 @@ public final class LaxMalloc { sizeBytes = MIN_ALLOC_SIZE; } + // Make sure all allocations are at least a multiple of 4 to maintain alignment + sizeBytes = (sizeBytes + 3) & 0xFFFFFFFC; + // always between 0-63 int bucket = getListBucket(sizeBytes); @@ -124,7 +127,7 @@ public final class LaxMalloc { Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableBucket << SIZEOF_PTR_SH); Address chunkPtr = bucketStartAddr.getAddress(); int chunkSize = readChunkSize(chunkPtr); - boolean bucketHasMultipleChunks = false; + Address itrChunkStart = Address.fromInt(0); // check if the first chunk in the bucket is large enough if(chunkSize - 8 < sizeBytes) { // size - 2 ints @@ -136,8 +139,7 @@ public final class LaxMalloc { Address chunkNextPtr = readChunkNextFreeAddr(chunkPtr); if(chunkNextPtr.getInt() != chunkPtr.getInt()) { bucketStartAddr.putAddress(chunkNextPtr); - chunkPtr = chunkNextPtr; - bucketHasMultipleChunks = true; + itrChunkStart = chunkNextPtr; } // extend mask to the next bucket @@ -179,15 +181,15 @@ public final class LaxMalloc { return ret; } - if(bucketHasMultipleChunks) { + if(itrChunkStart.toInt() != 0) { // if we've reached this point, it means the first chunk in the bucket wasn't large enough // and there weren't any chunks in the larger buckets we could split up // so we need to look closer // iterate the (only) bucket of possibly large enough chunks - Address addrIterator = chunkPtr; - while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()) { + Address addrIterator = itrChunkStart; + do { chunkSize = readChunkSize(addrIterator); // check if the chunk is large enough @@ -206,7 +208,7 @@ public final class LaxMalloc { return ret; } - } + }while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()); } // no other options, time to sbrk @@ -292,11 +294,12 @@ public final class LaxMalloc { // chunk actually starts 4 bytes before Address chunkPtr = address.add(-4); - // set the chunk no longer in use - setChunkInUse(chunkPtr, false); - // bring the size of the chunk into the stack int chunkSize = chunkPtr.getInt(); + boolean sizeChanged = false; + + // set the chunk no longer in use + chunkSize &= 0x7FFFFFFF; if(Address.fromInt(ADDR_HEAP_DATA_START).isLessThan(chunkPtr)) { // check if we can merge with the previous chunk, and move it to another bucket @@ -311,8 +314,7 @@ public final class LaxMalloc { // resize the current chunk to also contain the previous chunk chunkPtr = prevChunkPtr; chunkSize += prevChunkSize; - chunkPtr.putInt(chunkSize); - chunkPtr.add(chunkSize - 4).putInt(chunkSize); + sizeChanged = true; } } @@ -328,11 +330,18 @@ public final class LaxMalloc { // resize the current chunk to also contain the next chunk chunkSize += nextChunkSize; - chunkPtr.putInt(chunkSize); - chunkPtr.add(chunkSize - 4).putInt(chunkSize); + sizeChanged = true; } } + // store the final chunk size (also clears the in use flag) + chunkPtr.putInt(chunkSize); + + if(sizeChanged) { + // if the size of the chunk changed, we also need to update the chunk's second size integer + chunkPtr.add(chunkSize - 4).putInt(chunkSize); + } + // add the final chunk to the free chunks list linkChunkInFreeList(chunkPtr, chunkSize); } @@ -368,7 +377,7 @@ public final class LaxMalloc { }else { // not large enough to split, just take the entire chunk - setChunkInUse(chunkPtr, true); + chunkPtr.putInt(chunkSize | 0x80000000); // sets the in use flag } } @@ -508,11 +517,6 @@ public final class LaxMalloc { chunkAddr.putInt(sizeStatus); } - private static void setChunkInUse(Address chunkAddr, boolean inUse) { - int i = chunkAddr.getInt(); - chunkAddr.putInt(inUse ? (i | 0x80000000) : (i & 0x7FFFFFFF)); - } - private static Address readChunkPrevFreeAddr(Address chunkAddr) { return chunkAddr.add(4).getAddress(); } From cd8066757b5c0a8d9a9090e0019a5a0cb9a59829 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Fri, 1 Nov 2024 17:49:21 -0700 Subject: [PATCH 07/33] fix hugeAlloc --- core/src/main/java/org/teavm/runtime/LaxMalloc.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index f7f5c30b7..088f4e9f4 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -241,7 +241,7 @@ public final class LaxMalloc { // iterate all free huge chunks Address addrIterator = chunkPtr; - while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()) { + do { int chunkSize = readChunkSize(addrIterator); if(chunkSize - 8 >= sizeBytes) { // size - 2 ints @@ -259,7 +259,7 @@ public final class LaxMalloc { return ret; } - } + }while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()); } // no free huge chunks found, time to sbrk From eb2cab3597336a4ca9f5eccbfab04ea7e8ec1bd1 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Fri, 1 Nov 2024 18:05:29 -0700 Subject: [PATCH 08/33] microoptimizations --- core/src/main/java/org/teavm/runtime/LaxMalloc.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 088f4e9f4..fbe3b33fb 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -126,7 +126,7 @@ public final class LaxMalloc { Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableBucket << SIZEOF_PTR_SH); Address chunkPtr = bucketStartAddr.getAddress(); - int chunkSize = readChunkSize(chunkPtr); + int chunkSize = readChunkSizeStatus(chunkPtr); Address itrChunkStart = Address.fromInt(0); // check if the first chunk in the bucket is large enough @@ -150,7 +150,7 @@ public final class LaxMalloc { int availableLargerBucket = numberOfTrailingZerosL(bucketMask); Address largerBucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableLargerBucket << SIZEOF_PTR_SH); Address largerChunkPtr = largerBucketStartAddr.getAddress(); - int largerChunkSize = readChunkSize(largerChunkPtr); + int largerChunkSize = readChunkSizeStatus(largerChunkPtr); // this will remove the chunk from the free list allocateMemoryFromChunk(largerChunkPtr, largerChunkSize, sizeBytes); @@ -190,7 +190,7 @@ public final class LaxMalloc { // iterate the (only) bucket of possibly large enough chunks Address addrIterator = itrChunkStart; do { - chunkSize = readChunkSize(addrIterator); + chunkSize = readChunkSizeStatus(addrIterator); // check if the chunk is large enough if(chunkSize - 8 >= sizeBytes) { // size - 2 ints @@ -242,7 +242,7 @@ public final class LaxMalloc { // iterate all free huge chunks Address addrIterator = chunkPtr; do { - int chunkSize = readChunkSize(addrIterator); + int chunkSize = readChunkSizeStatus(addrIterator); if(chunkSize - 8 >= sizeBytes) { // size - 2 ints // we've found a large enough chunk @@ -501,6 +501,9 @@ public final class LaxMalloc { @Import(name = "teavm_growHeap") private static native boolean growHeapOuter(int bytes); + /** + * Note that on a free chunk, this is the size, because the status bit is 0 + */ private static int readChunkSizeStatus(Address chunkAddr) { return chunkAddr.getInt(); } From 901a4a4c7d623d0a0e6af5b42f1604d00f3fd9a0 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Fri, 1 Nov 2024 18:10:16 -0700 Subject: [PATCH 09/33] convert to spaces --- .../java/org/teavm/runtime/LaxMalloc.java | 968 +++++++++--------- 1 file changed, 484 insertions(+), 484 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index fbe3b33fb..94d787c8e 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -35,519 +35,519 @@ import org.teavm.interop.Unmanaged; @StaticInit public final class LaxMalloc { - private LaxMalloc() { - } + private LaxMalloc() { + } - private static final int SIZEOF_PTR = 4; - private static final int SIZEOF_PTR_SH = 2; + private static final int SIZEOF_PTR = 4; + private static final int SIZEOF_PTR_SH = 2; - private static final int MIN_ALLOC_SIZE = 8; + private static final int MIN_ALLOC_SIZE = 8; - private static final int ADDR_HEAP_OUTER_LIMIT = 0; // Address where we store the WebAssembly.Memory limit (32 bit int) - private static final int ADDR_HEAP_INNER_LIMIT = 4; // Address where we store the current heap limit (32 bit int) - private static final int ADDR_HEAP_BUCKETS_FREE_MASK = 8; // Address where we store the bitmask of free chunk lists (64 bit int) - private static final int ADDR_HEAP_BUCKETS_START = 16; // Address to the list of 64 pointers to the beginnings of the 64 buckets - private static final int ADDR_HEAP_DATA_START = 272; // Beginning of the first chunk of the heap + private static final int ADDR_HEAP_OUTER_LIMIT = 0; // Address where we store the WebAssembly.Memory limit (32 bit int) + private static final int ADDR_HEAP_INNER_LIMIT = 4; // Address where we store the current heap limit (32 bit int) + private static final int ADDR_HEAP_BUCKETS_FREE_MASK = 8; // Address where we store the bitmask of free chunk lists (64 bit int) + private static final int ADDR_HEAP_BUCKETS_START = 16; // Address to the list of 64 pointers to the beginnings of the 64 buckets + private static final int ADDR_HEAP_DATA_START = 272; // Beginning of the first chunk of the heap - static { - // zero out the control region - Allocator.fillZero(Address.fromInt(0), ADDR_HEAP_DATA_START); - // initialize heap limit - Address.fromInt(ADDR_HEAP_INNER_LIMIT).putInt(ADDR_HEAP_DATA_START); - Address.fromInt(ADDR_HEAP_OUTER_LIMIT).putInt(0x10000); - } + static { + // zero out the control region + Allocator.fillZero(Address.fromInt(0), ADDR_HEAP_DATA_START); + // initialize heap limit + Address.fromInt(ADDR_HEAP_INNER_LIMIT).putInt(ADDR_HEAP_DATA_START); + Address.fromInt(ADDR_HEAP_OUTER_LIMIT).putInt(0x10000); + } - /** - * malloc implementation - */ - public static Address laxMalloc(int sizeBytes) { - return laxAlloc(sizeBytes, false); - } + /** + * malloc implementation + */ + public static Address laxMalloc(int sizeBytes) { + return laxAlloc(sizeBytes, false); + } - /** - * calloc implementation (zeroed malloc) - */ - public static Address laxCalloc(int sizeBytes) { - return laxAlloc(sizeBytes, true); - } + /** + * calloc implementation (zeroed malloc) + */ + public static Address laxCalloc(int sizeBytes) { + return laxAlloc(sizeBytes, true); + } - private static Address laxAlloc(int sizeBytes, boolean cleared) { - if(sizeBytes <= 0) { - // Produce a null pointer if 0 or invalid size is requested - return Address.fromInt(0); - } - - // Allocation must be large enough to hold the two list pointers when the chunk becomes free again - if(sizeBytes < MIN_ALLOC_SIZE) { - sizeBytes = MIN_ALLOC_SIZE; - } - - // Make sure all allocations are at least a multiple of 4 to maintain alignment - sizeBytes = (sizeBytes + 3) & 0xFFFFFFFC; - - // always between 0-63 - int bucket = getListBucket(sizeBytes); - - if(bucket == 63) { - // special bucket for the huge allocations - // uses a different slower function - return laxHugeAlloc(sizeBytes, cleared); - } - - // load bitmask of buckets with free chunks - long bucketMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); - - // mask away the buckets that we know are too small for this allocation - bucketMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << bucket)); - - // there are no more buckets with free chunks - // need to sbrk - if(bucketMask == 0l) { - int sizePlusInts = sizeBytes + 8; // size + 2 ints - Address newChunk = growHeap(sizePlusInts); // sbrk - - // Out of memory - if(newChunk.toInt() == 0) { - return Address.fromInt(0); //TODO - } - - // provision the new chunk - newChunk.putInt(sizePlusInts | 0x80000000); // size + in use flag - newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end - - // return the chunk, +4 bytes to skip size int - // we don't need to clear it because its new memory - return newChunk.add(4); - } - - // at least one bucket exists containing a free chunk, - // quickly determine which bucket it is with bit hacks - int availableBucket = numberOfTrailingZerosL(bucketMask); - - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableBucket << SIZEOF_PTR_SH); - Address chunkPtr = bucketStartAddr.getAddress(); - int chunkSize = readChunkSizeStatus(chunkPtr); - Address itrChunkStart = Address.fromInt(0); - - // check if the first chunk in the bucket is large enough - if(chunkSize - 8 < sizeBytes) { // size - 2 ints - - // the chunk is not large enough, move the first chunk to the end of the list - // and then check in the next bucket (where the chunks are definitely large enough) - // this functionality is present in emmalloc (emscripten) - - Address chunkNextPtr = readChunkNextFreeAddr(chunkPtr); - if(chunkNextPtr.getInt() != chunkPtr.getInt()) { - bucketStartAddr.putAddress(chunkNextPtr); - itrChunkStart = chunkNextPtr; - } - - // extend mask to the next bucket - bucketMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << (bucket + 1))); - - if(bucketMask != 0l) { - // there is a bucket with a larger chunk - int availableLargerBucket = numberOfTrailingZerosL(bucketMask); - Address largerBucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableLargerBucket << SIZEOF_PTR_SH); - Address largerChunkPtr = largerBucketStartAddr.getAddress(); - int largerChunkSize = readChunkSizeStatus(largerChunkPtr); - - // this will remove the chunk from the free list - allocateMemoryFromChunk(largerChunkPtr, largerChunkSize, sizeBytes); - - // +4 bytes to skip size int - Address ret = largerChunkPtr.add(4); - - // clear if requested - if(cleared) { - Allocator.fillZero(ret, sizeBytes); - } - - return ret; - } - }else { - // the first chunk in the bucket is large enough - // this will remove the chunk from the free list - allocateMemoryFromChunk(chunkPtr, chunkSize, sizeBytes); - - // +4 bytes to skip size int - Address ret = chunkPtr.add(4); - - // clear if requested - if(cleared) { - Allocator.fillZero(ret, sizeBytes); - } - - return ret; - } - - if(itrChunkStart.toInt() != 0) { - - // if we've reached this point, it means the first chunk in the bucket wasn't large enough - // and there weren't any chunks in the larger buckets we could split up - // so we need to look closer - - // iterate the (only) bucket of possibly large enough chunks - Address addrIterator = itrChunkStart; - do { - chunkSize = readChunkSizeStatus(addrIterator); - - // check if the chunk is large enough - if(chunkSize - 8 >= sizeBytes) { // size - 2 ints - // we've found a large enough chunk - // this will remove the chunk from the free list - allocateMemoryFromChunk(addrIterator, chunkSize, sizeBytes); - - // +4 bytes to skip size int - Address ret = chunkPtr.add(4); - - // clear if requested - if(cleared) { - Allocator.fillZero(ret, sizeBytes); - } - - return ret; - } - }while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()); - } - - // no other options, time to sbrk - - int sizePlusInts = sizeBytes + 8; // size + 2 ints - Address newChunk = growHeap(sizePlusInts); // sbrk - - // Out of memory - if(newChunk.toInt() == 0) { - return Address.fromInt(0); //TODO - } - - // provision the new chunk - newChunk.putInt(sizePlusInts | 0x80000000); // size + in use flag - newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end - - // return the chunk, +4 bytes to skip size int - // we don't need to clear it because its new memory - return newChunk.add(4); - } + private static Address laxAlloc(int sizeBytes, boolean cleared) { + if(sizeBytes <= 0) { + // Produce a null pointer if 0 or invalid size is requested + return Address.fromInt(0); + } + + // Allocation must be large enough to hold the two list pointers when the chunk becomes free again + if(sizeBytes < MIN_ALLOC_SIZE) { + sizeBytes = MIN_ALLOC_SIZE; + } + + // Make sure all allocations are at least a multiple of 4 to maintain alignment + sizeBytes = (sizeBytes + 3) & 0xFFFFFFFC; + + // always between 0-63 + int bucket = getListBucket(sizeBytes); + + if(bucket == 63) { + // special bucket for the huge allocations + // uses a different slower function + return laxHugeAlloc(sizeBytes, cleared); + } + + // load bitmask of buckets with free chunks + long bucketMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); + + // mask away the buckets that we know are too small for this allocation + bucketMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << bucket)); + + // there are no more buckets with free chunks + // need to sbrk + if(bucketMask == 0l) { + int sizePlusInts = sizeBytes + 8; // size + 2 ints + Address newChunk = growHeap(sizePlusInts); // sbrk + + // Out of memory + if(newChunk.toInt() == 0) { + return Address.fromInt(0); //TODO + } + + // provision the new chunk + newChunk.putInt(sizePlusInts | 0x80000000); // size + in use flag + newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end + + // return the chunk, +4 bytes to skip size int + // we don't need to clear it because its new memory + return newChunk.add(4); + } + + // at least one bucket exists containing a free chunk, + // quickly determine which bucket it is with bit hacks + int availableBucket = numberOfTrailingZerosL(bucketMask); + + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableBucket << SIZEOF_PTR_SH); + Address chunkPtr = bucketStartAddr.getAddress(); + int chunkSize = readChunkSizeStatus(chunkPtr); + Address itrChunkStart = Address.fromInt(0); + + // check if the first chunk in the bucket is large enough + if(chunkSize - 8 < sizeBytes) { // size - 2 ints + + // the chunk is not large enough, move the first chunk to the end of the list + // and then check in the next bucket (where the chunks are definitely large enough) + // this functionality is present in emmalloc (emscripten) + + Address chunkNextPtr = readChunkNextFreeAddr(chunkPtr); + if(chunkNextPtr.getInt() != chunkPtr.getInt()) { + bucketStartAddr.putAddress(chunkNextPtr); + itrChunkStart = chunkNextPtr; + } + + // extend mask to the next bucket + bucketMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << (bucket + 1))); + + if(bucketMask != 0l) { + // there is a bucket with a larger chunk + int availableLargerBucket = numberOfTrailingZerosL(bucketMask); + Address largerBucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableLargerBucket << SIZEOF_PTR_SH); + Address largerChunkPtr = largerBucketStartAddr.getAddress(); + int largerChunkSize = readChunkSizeStatus(largerChunkPtr); + + // this will remove the chunk from the free list + allocateMemoryFromChunk(largerChunkPtr, largerChunkSize, sizeBytes); + + // +4 bytes to skip size int + Address ret = largerChunkPtr.add(4); + + // clear if requested + if(cleared) { + Allocator.fillZero(ret, sizeBytes); + } + + return ret; + } + }else { + // the first chunk in the bucket is large enough + // this will remove the chunk from the free list + allocateMemoryFromChunk(chunkPtr, chunkSize, sizeBytes); + + // +4 bytes to skip size int + Address ret = chunkPtr.add(4); + + // clear if requested + if(cleared) { + Allocator.fillZero(ret, sizeBytes); + } + + return ret; + } + + if(itrChunkStart.toInt() != 0) { + + // if we've reached this point, it means the first chunk in the bucket wasn't large enough + // and there weren't any chunks in the larger buckets we could split up + // so we need to look closer + + // iterate the (only) bucket of possibly large enough chunks + Address addrIterator = itrChunkStart; + do { + chunkSize = readChunkSizeStatus(addrIterator); + + // check if the chunk is large enough + if(chunkSize - 8 >= sizeBytes) { // size - 2 ints + // we've found a large enough chunk + // this will remove the chunk from the free list + allocateMemoryFromChunk(addrIterator, chunkSize, sizeBytes); + + // +4 bytes to skip size int + Address ret = chunkPtr.add(4); + + // clear if requested + if(cleared) { + Allocator.fillZero(ret, sizeBytes); + } + + return ret; + } + }while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()); + } + + // no other options, time to sbrk + + int sizePlusInts = sizeBytes + 8; // size + 2 ints + Address newChunk = growHeap(sizePlusInts); // sbrk + + // Out of memory + if(newChunk.toInt() == 0) { + return Address.fromInt(0); //TODO + } + + // provision the new chunk + newChunk.putInt(sizePlusInts | 0x80000000); // size + in use flag + newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end + + // return the chunk, +4 bytes to skip size int + // we don't need to clear it because its new memory + return newChunk.add(4); + } - private static Address laxHugeAlloc(int sizeBytes, boolean cleared) { - - // check the bucket mask if bucket 63 has any chunks - if((Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong() & 0x8000000000000000L) != 0) { + private static Address laxHugeAlloc(int sizeBytes, boolean cleared) { + + // check the bucket mask if bucket 63 has any chunks + if((Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong() & 0x8000000000000000L) != 0) { - // bucket 63 address - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(63 << SIZEOF_PTR_SH); - Address chunkPtr = bucketStartAddr.getAddress(); - - // iterate all free huge chunks - Address addrIterator = chunkPtr; - do { - int chunkSize = readChunkSizeStatus(addrIterator); - - if(chunkSize - 8 >= sizeBytes) { // size - 2 ints - // we've found a large enough chunk - // this will remove the chunk from the free list - allocateMemoryFromChunk(addrIterator, chunkSize, sizeBytes); - - // +4 bytes to skip size int - Address ret = chunkPtr.add(4); - - // clear if requested - if(cleared) { - Allocator.fillZero(ret, sizeBytes); - } - - return ret; - } - }while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()); - } - - // no free huge chunks found, time to sbrk - - int sizePlusInts = sizeBytes + 8; // size + 2 ints - Address newChunk = growHeap(sizePlusInts); // sbrk - - // Out of memory - if(newChunk.toInt() == 0) { - return Address.fromInt(0); //TODO - } - - // provision the new chunk - newChunk.putInt(sizePlusInts | 0x80000000); // size + in use flag - newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end - - // return the chunk, +4 bytes to skip size int - // we don't need to clear it because its new memory - return newChunk.add(4); - } + // bucket 63 address + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(63 << SIZEOF_PTR_SH); + Address chunkPtr = bucketStartAddr.getAddress(); + + // iterate all free huge chunks + Address addrIterator = chunkPtr; + do { + int chunkSize = readChunkSizeStatus(addrIterator); + + if(chunkSize - 8 >= sizeBytes) { // size - 2 ints + // we've found a large enough chunk + // this will remove the chunk from the free list + allocateMemoryFromChunk(addrIterator, chunkSize, sizeBytes); + + // +4 bytes to skip size int + Address ret = chunkPtr.add(4); + + // clear if requested + if(cleared) { + Allocator.fillZero(ret, sizeBytes); + } + + return ret; + } + }while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()); + } + + // no free huge chunks found, time to sbrk + + int sizePlusInts = sizeBytes + 8; // size + 2 ints + Address newChunk = growHeap(sizePlusInts); // sbrk + + // Out of memory + if(newChunk.toInt() == 0) { + return Address.fromInt(0); //TODO + } + + // provision the new chunk + newChunk.putInt(sizePlusInts | 0x80000000); // size + in use flag + newChunk.add(sizeBytes + 4).putInt(sizePlusInts); // size integer at the end + + // return the chunk, +4 bytes to skip size int + // we don't need to clear it because its new memory + return newChunk.add(4); + } - /** - * free implementation

- * - * bad things will happen if you free an address that was never allocated - */ - public static void laxFree(Address address) { - if(address.toInt() == 0) { - return; - } - - // chunk actually starts 4 bytes before - Address chunkPtr = address.add(-4); - - // bring the size of the chunk into the stack - int chunkSize = chunkPtr.getInt(); - boolean sizeChanged = false; - - // set the chunk no longer in use - chunkSize &= 0x7FFFFFFF; - - if(Address.fromInt(ADDR_HEAP_DATA_START).isLessThan(chunkPtr)) { - // check if we can merge with the previous chunk, and move it to another bucket - Address prevChunkPtr = chunkPtr.add(-(chunkPtr.add(-4).getInt())); - int prevChunkSize = readChunkSizeStatus(prevChunkPtr); - if((prevChunkSize & 0x80000000) == 0) { - // previous chunk is not in use, merge! - - // remove the previous chunk from its list - unlinkChunkFromFreeList(prevChunkPtr, prevChunkSize); - - // resize the current chunk to also contain the previous chunk - chunkPtr = prevChunkPtr; - chunkSize += prevChunkSize; - sizeChanged = true; - } - } - - Address nextChunkPtr = chunkPtr.add(chunkSize); - if(Address.fromInt(ADDR_HEAP_INNER_LIMIT).getAddress().isLessThan(nextChunkPtr)) { - // check if we can merge with the next chunk as well - int nextChunkSize = readChunkSizeStatus(nextChunkPtr); - if((nextChunkSize & 0x80000000) == 0) { - // next chunk is not in use, merge! + /** + * free implementation

+ * + * bad things will happen if you free an address that was never allocated + */ + public static void laxFree(Address address) { + if(address.toInt() == 0) { + return; + } + + // chunk actually starts 4 bytes before + Address chunkPtr = address.add(-4); + + // bring the size of the chunk into the stack + int chunkSize = chunkPtr.getInt(); + boolean sizeChanged = false; + + // set the chunk no longer in use + chunkSize &= 0x7FFFFFFF; + + if(Address.fromInt(ADDR_HEAP_DATA_START).isLessThan(chunkPtr)) { + // check if we can merge with the previous chunk, and move it to another bucket + Address prevChunkPtr = chunkPtr.add(-(chunkPtr.add(-4).getInt())); + int prevChunkSize = readChunkSizeStatus(prevChunkPtr); + if((prevChunkSize & 0x80000000) == 0) { + // previous chunk is not in use, merge! + + // remove the previous chunk from its list + unlinkChunkFromFreeList(prevChunkPtr, prevChunkSize); + + // resize the current chunk to also contain the previous chunk + chunkPtr = prevChunkPtr; + chunkSize += prevChunkSize; + sizeChanged = true; + } + } + + Address nextChunkPtr = chunkPtr.add(chunkSize); + if(Address.fromInt(ADDR_HEAP_INNER_LIMIT).getAddress().isLessThan(nextChunkPtr)) { + // check if we can merge with the next chunk as well + int nextChunkSize = readChunkSizeStatus(nextChunkPtr); + if((nextChunkSize & 0x80000000) == 0) { + // next chunk is not in use, merge! - // remove the next chunk from its list - unlinkChunkFromFreeList(nextChunkPtr, nextChunkSize); - - // resize the current chunk to also contain the next chunk - chunkSize += nextChunkSize; - sizeChanged = true; - } - } - - // store the final chunk size (also clears the in use flag) - chunkPtr.putInt(chunkSize); - - if(sizeChanged) { - // if the size of the chunk changed, we also need to update the chunk's second size integer - chunkPtr.add(chunkSize - 4).putInt(chunkSize); - } - - // add the final chunk to the free chunks list - linkChunkInFreeList(chunkPtr, chunkSize); - } + // remove the next chunk from its list + unlinkChunkFromFreeList(nextChunkPtr, nextChunkSize); + + // resize the current chunk to also contain the next chunk + chunkSize += nextChunkSize; + sizeChanged = true; + } + } + + // store the final chunk size (also clears the in use flag) + chunkPtr.putInt(chunkSize); + + if(sizeChanged) { + // if the size of the chunk changed, we also need to update the chunk's second size integer + chunkPtr.add(chunkSize - 4).putInt(chunkSize); + } + + // add the final chunk to the free chunks list + linkChunkInFreeList(chunkPtr, chunkSize); + } - /** - * Allocates memory from a free chunk, if the allocSize is smaller than the chunkSize by - * enough of a margin then the chunk is split into two smaller chunks, and the upper part - * of the chunk is returned to a bucket of free chunks - */ - private static void allocateMemoryFromChunk(Address chunkPtr, int chunkSize, int allocSize) { - // remove the chunk from its bucket - unlinkChunkFromFreeList(chunkPtr, chunkSize); - - int otherHalfSize = chunkSize - allocSize - 8; // -size - 2 ints - - // check if we can split the chunk into two smaller chunks - // chunk must be large enough to hold the 2 list pointers - if(otherHalfSize - (2 << SIZEOF_PTR_SH) >= MIN_ALLOC_SIZE) { - // chunk is large enough to split - - // provision the lower part of the chunk, the part we want to use - int sizePlusInts = allocSize + 8; // size + 2 ints - chunkPtr.putInt(sizePlusInts | 0x80000000); // size + in use flag - chunkPtr.add(allocSize + 4).putInt(sizePlusInts); // size integer at the end - - // provision the upper part of the chunk that we want to return to the free list - Address otherChunkPtr = chunkPtr.add(sizePlusInts); - otherChunkPtr.putInt(otherHalfSize); // size - otherChunkPtr.add(otherHalfSize - 4).putInt(otherHalfSize); // size (end) + /** + * Allocates memory from a free chunk, if the allocSize is smaller than the chunkSize by + * enough of a margin then the chunk is split into two smaller chunks, and the upper part + * of the chunk is returned to a bucket of free chunks + */ + private static void allocateMemoryFromChunk(Address chunkPtr, int chunkSize, int allocSize) { + // remove the chunk from its bucket + unlinkChunkFromFreeList(chunkPtr, chunkSize); + + int otherHalfSize = chunkSize - allocSize - 8; // -size - 2 ints + + // check if we can split the chunk into two smaller chunks + // chunk must be large enough to hold the 2 list pointers + if(otherHalfSize - (2 << SIZEOF_PTR_SH) >= MIN_ALLOC_SIZE) { + // chunk is large enough to split + + // provision the lower part of the chunk, the part we want to use + int sizePlusInts = allocSize + 8; // size + 2 ints + chunkPtr.putInt(sizePlusInts | 0x80000000); // size + in use flag + chunkPtr.add(allocSize + 4).putInt(sizePlusInts); // size integer at the end + + // provision the upper part of the chunk that we want to return to the free list + Address otherChunkPtr = chunkPtr.add(sizePlusInts); + otherChunkPtr.putInt(otherHalfSize); // size + otherChunkPtr.add(otherHalfSize - 4).putInt(otherHalfSize); // size (end) - // return the upper part of the chunk to the free chunks list - linkChunkInFreeList(otherChunkPtr, otherHalfSize); + // return the upper part of the chunk to the free chunks list + linkChunkInFreeList(otherChunkPtr, otherHalfSize); - }else { - // not large enough to split, just take the entire chunk - chunkPtr.putInt(chunkSize | 0x80000000); // sets the in use flag - } - } + }else { + // not large enough to split, just take the entire chunk + chunkPtr.putInt(chunkSize | 0x80000000); // sets the in use flag + } + } - /** - * Adds a free chunk to its corresponding bucket - */ - private static void linkChunkInFreeList(Address chunkPtr, int chunkSize) { - int bucket = getListBucket(chunkSize - 8); // size - 2 ints - - long bucketMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(bucket << SIZEOF_PTR_SH); - - // test the bucket mask if the bucket is empty - if((bucketMask & (1L << bucket)) == 0l) { - - // bucket is empty, add the free chunk to the list - bucketStartAddr.putAddress(chunkPtr); - writeChunkPrevFreeAddr(chunkPtr, chunkPtr); - writeChunkNextFreeAddr(chunkPtr, chunkPtr); - - // set the free bit in bucket mask - bucketMask |= (1L << bucket); - Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketMask); - - }else { - - // bucket is not empty, append to the bucket's existing free chunks list - Address otherBucketStart = bucketStartAddr.getAddress(); - Address otherBucketPrev = readChunkPrevFreeAddr(otherBucketStart); - - // link new chunk to the existing chunks in the bucket - writeChunkPrevFreeAddr(chunkPtr, otherBucketPrev); - writeChunkNextFreeAddr(chunkPtr, otherBucketStart); - - // link the existing chunks in the bucket to the new chunk - writeChunkPrevFreeAddr(otherBucketStart, chunkPtr); - writeChunkNextFreeAddr(otherBucketPrev, chunkPtr); - - // put the chunk in the bucket - bucketStartAddr.putAddress(chunkPtr); - - } - } + /** + * Adds a free chunk to its corresponding bucket + */ + private static void linkChunkInFreeList(Address chunkPtr, int chunkSize) { + int bucket = getListBucket(chunkSize - 8); // size - 2 ints + + long bucketMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(bucket << SIZEOF_PTR_SH); + + // test the bucket mask if the bucket is empty + if((bucketMask & (1L << bucket)) == 0l) { + + // bucket is empty, add the free chunk to the list + bucketStartAddr.putAddress(chunkPtr); + writeChunkPrevFreeAddr(chunkPtr, chunkPtr); + writeChunkNextFreeAddr(chunkPtr, chunkPtr); + + // set the free bit in bucket mask + bucketMask |= (1L << bucket); + Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketMask); + + }else { + + // bucket is not empty, append to the bucket's existing free chunks list + Address otherBucketStart = bucketStartAddr.getAddress(); + Address otherBucketPrev = readChunkPrevFreeAddr(otherBucketStart); + + // link new chunk to the existing chunks in the bucket + writeChunkPrevFreeAddr(chunkPtr, otherBucketPrev); + writeChunkNextFreeAddr(chunkPtr, otherBucketStart); + + // link the existing chunks in the bucket to the new chunk + writeChunkPrevFreeAddr(otherBucketStart, chunkPtr); + writeChunkNextFreeAddr(otherBucketPrev, chunkPtr); + + // put the chunk in the bucket + bucketStartAddr.putAddress(chunkPtr); + + } + } - /** - * Removes a free chunk from its corresponding bucket - */ - private static void unlinkChunkFromFreeList(Address chunkPtr, int chunkSize) { - Address prevChunkPtr = readChunkPrevFreeAddr(chunkPtr); - Address nextChunkPtr = readChunkNextFreeAddr(chunkPtr); - if(prevChunkPtr.toInt() == nextChunkPtr.toInt()) { - // chunk is the only one currently in its bucket - - int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints - - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); - bucketStartAddr.putAddress(Address.fromInt(0)); // remove chunk from the bucket - - // clear the bit in the free buckets bitmask - long bucketsFreeMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); - bucketsFreeMask &= ~(1L << chunkBucket); - Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketsFreeMask); - - }else { - // there are other chunks in this bucket - - // link the next chunk to the previous chunk - writeChunkNextFreeAddr(prevChunkPtr, nextChunkPtr); - writeChunkPrevFreeAddr(nextChunkPtr, prevChunkPtr); - - int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); - Address bucketStartChunk = bucketStartAddr.getAddress(); - - // chunk is the first in the bucket, so we also need to - // update the bucket to point to the next chunk instead - if(bucketStartChunk.toInt() == chunkPtr.toInt()) { - bucketStartAddr.putAddress(nextChunkPtr); - } - } - } + /** + * Removes a free chunk from its corresponding bucket + */ + private static void unlinkChunkFromFreeList(Address chunkPtr, int chunkSize) { + Address prevChunkPtr = readChunkPrevFreeAddr(chunkPtr); + Address nextChunkPtr = readChunkNextFreeAddr(chunkPtr); + if(prevChunkPtr.toInt() == nextChunkPtr.toInt()) { + // chunk is the only one currently in its bucket + + int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints + + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); + bucketStartAddr.putAddress(Address.fromInt(0)); // remove chunk from the bucket + + // clear the bit in the free buckets bitmask + long bucketsFreeMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); + bucketsFreeMask &= ~(1L << chunkBucket); + Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketsFreeMask); + + }else { + // there are other chunks in this bucket + + // link the next chunk to the previous chunk + writeChunkNextFreeAddr(prevChunkPtr, nextChunkPtr); + writeChunkPrevFreeAddr(nextChunkPtr, prevChunkPtr); + + int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints + Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); + Address bucketStartChunk = bucketStartAddr.getAddress(); + + // chunk is the first in the bucket, so we also need to + // update the bucket to point to the next chunk instead + if(bucketStartChunk.toInt() == chunkPtr.toInt()) { + bucketStartAddr.putAddress(nextChunkPtr); + } + } + } - /** - * https://github.com/emscripten-core/emscripten/blob/16a0bf174cb85f88b6d9dcc8ee7fbca59390185b/system/lib/emmalloc.c#L241 - * (MIT License) - */ - private static int getListBucket(int allocSize) { - if (allocSize < 128) - return (allocSize >> 3) - 1; - - int clz = numberOfLeadingZerosI(allocSize); - int bucketIndex = (clz > 19) ? 110 - (clz << 2) + ((allocSize >> (29 - clz)) ^ 4) - : min(71 - (clz << 1) + ((allocSize >> (30 - clz)) ^ 2), 63); + /** + * https://github.com/emscripten-core/emscripten/blob/16a0bf174cb85f88b6d9dcc8ee7fbca59390185b/system/lib/emmalloc.c#L241 + * (MIT License) + */ + private static int getListBucket(int allocSize) { + if (allocSize < 128) + return (allocSize >> 3) - 1; + + int clz = numberOfLeadingZerosI(allocSize); + int bucketIndex = (clz > 19) ? 110 - (clz << 2) + ((allocSize >> (29 - clz)) ^ 4) + : min(71 - (clz << 1) + ((allocSize >> (30 - clz)) ^ 2), 63); + + return bucketIndex; + } - return bucketIndex; - } + /** + * This is our sbrk + */ + private static Address growHeap(int amount) { + Address heapInnerLimit = Address.fromInt(ADDR_HEAP_INNER_LIMIT).getAddress(); + Address heapOuterLimit = Address.fromInt(ADDR_HEAP_OUTER_LIMIT).getAddress(); + Address newHeapInnerLimit = heapInnerLimit.add(amount); + if(heapOuterLimit.isLessThan(newHeapInnerLimit)) { + int bytesNeeded = newHeapInnerLimit.toInt() - heapOuterLimit.toInt(); + bytesNeeded = (bytesNeeded + 0xFFFF) & 0xFFFF0000; + if(growHeapOuter(bytesNeeded)) { + Address.fromInt(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); + Address.fromInt(ADDR_HEAP_OUTER_LIMIT).putAddress(heapOuterLimit.add(bytesNeeded)); + return newHeapInnerLimit; + }else { + return Address.fromInt(0); + } + }else { + Address.fromInt(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); + return newHeapInnerLimit; + } + } - /** - * This is our sbrk - */ - private static Address growHeap(int amount) { - Address heapInnerLimit = Address.fromInt(ADDR_HEAP_INNER_LIMIT).getAddress(); - Address heapOuterLimit = Address.fromInt(ADDR_HEAP_OUTER_LIMIT).getAddress(); - Address newHeapInnerLimit = heapInnerLimit.add(amount); - if(heapOuterLimit.isLessThan(newHeapInnerLimit)) { - int bytesNeeded = newHeapInnerLimit.toInt() - heapOuterLimit.toInt(); - bytesNeeded = (bytesNeeded + 0xFFFF) & 0xFFFF0000; - if(growHeapOuter(bytesNeeded)) { - Address.fromInt(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); - Address.fromInt(ADDR_HEAP_OUTER_LIMIT).putAddress(heapOuterLimit.add(bytesNeeded)); - return newHeapInnerLimit; - }else { - return Address.fromInt(0); - } - }else { - Address.fromInt(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); - return newHeapInnerLimit; - } - } + @Import(name = "teavm_growHeap") + private static native boolean growHeapOuter(int bytes); - @Import(name = "teavm_growHeap") - private static native boolean growHeapOuter(int bytes); + /** + * Note that on a free chunk, this is the size, because the status bit is 0 + */ + private static int readChunkSizeStatus(Address chunkAddr) { + return chunkAddr.getInt(); + } - /** - * Note that on a free chunk, this is the size, because the status bit is 0 - */ - private static int readChunkSizeStatus(Address chunkAddr) { - return chunkAddr.getInt(); - } + private static int readChunkSize(Address chunkAddr) { + return chunkAddr.getInt() & 0x7FFFFFFF; + } - private static int readChunkSize(Address chunkAddr) { - return chunkAddr.getInt() & 0x7FFFFFFF; - } + private static boolean readChunkInUse(Address chunkAddr) { + return (chunkAddr.getInt() & 0x80000000) != 0; + } - private static boolean readChunkInUse(Address chunkAddr) { - return (chunkAddr.getInt() & 0x80000000) != 0; - } + private static void writeChunkSizeStatus(Address chunkAddr, int sizeStatus) { + chunkAddr.putInt(sizeStatus); + } - private static void writeChunkSizeStatus(Address chunkAddr, int sizeStatus) { - chunkAddr.putInt(sizeStatus); - } + private static Address readChunkPrevFreeAddr(Address chunkAddr) { + return chunkAddr.add(4).getAddress(); + } - private static Address readChunkPrevFreeAddr(Address chunkAddr) { - return chunkAddr.add(4).getAddress(); - } + private static void writeChunkPrevFreeAddr(Address chunkAddr, Address prevFree) { + chunkAddr.add(4).putAddress(prevFree); + } - private static void writeChunkPrevFreeAddr(Address chunkAddr, Address prevFree) { - chunkAddr.add(4).putAddress(prevFree); - } + private static Address readChunkNextFreeAddr(Address chunkAddr) { + return chunkAddr.add(4 + (1 << SIZEOF_PTR_SH)).getAddress(); + } - private static Address readChunkNextFreeAddr(Address chunkAddr) { - return chunkAddr.add(4 + (1 << SIZEOF_PTR_SH)).getAddress(); - } + private static void writeChunkNextFreeAddr(Address chunkAddr, Address nextFree) { + chunkAddr.add(4 + (1 << SIZEOF_PTR_SH)).putAddress(nextFree); + } - private static void writeChunkNextFreeAddr(Address chunkAddr, Address nextFree) { - chunkAddr.add(4 + (1 << SIZEOF_PTR_SH)).putAddress(nextFree); - } + private static int min(int a, int b) { + return a < b ? a : b; + } - private static int min(int a, int b) { - return a < b ? a : b; - } + private static int numberOfTrailingZerosL(long i) { + // TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! + return Long.numberOfTrailingZeros(i); + } - private static int numberOfTrailingZerosL(long i) { - // TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! - return Long.numberOfTrailingZeros(i); - } - - private static int numberOfLeadingZerosI(int i) { - // TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! - return Integer.numberOfLeadingZeros(i); - } + private static int numberOfLeadingZerosI(int i) { + // TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! + return Integer.numberOfLeadingZeros(i); + } } From acb4caa358cae003fb4649cb11e890fb04e692f2 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Fri, 1 Nov 2024 18:16:21 -0700 Subject: [PATCH 10/33] fix --- .../main/java/org/teavm/runtime/LaxMalloc.java | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 94d787c8e..f8a391873 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -122,7 +122,7 @@ public final class LaxMalloc { // at least one bucket exists containing a free chunk, // quickly determine which bucket it is with bit hacks - int availableBucket = numberOfTrailingZerosL(bucketMask); + int availableBucket = Long.numberOfTrailingZeros(bucketMask); Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableBucket << SIZEOF_PTR_SH); Address chunkPtr = bucketStartAddr.getAddress(); @@ -147,7 +147,7 @@ public final class LaxMalloc { if(bucketMask != 0l) { // there is a bucket with a larger chunk - int availableLargerBucket = numberOfTrailingZerosL(bucketMask); + int availableLargerBucket = Long.numberOfTrailingZeros(bucketMask); Address largerBucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableLargerBucket << SIZEOF_PTR_SH); Address largerChunkPtr = largerBucketStartAddr.getAddress(); int largerChunkSize = readChunkSizeStatus(largerChunkPtr); @@ -468,7 +468,7 @@ public final class LaxMalloc { if (allocSize < 128) return (allocSize >> 3) - 1; - int clz = numberOfLeadingZerosI(allocSize); + int clz = Integer.numberOfLeadingZeros(allocSize); int bucketIndex = (clz > 19) ? 110 - (clz << 2) + ((allocSize >> (29 - clz)) ^ 4) : min(71 - (clz << 1) + ((allocSize >> (30 - clz)) ^ 2), 63); @@ -540,14 +540,4 @@ public final class LaxMalloc { return a < b ? a : b; } - private static int numberOfTrailingZerosL(long i) { - // TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! - return Long.numberOfTrailingZeros(i); - } - - private static int numberOfLeadingZerosI(int i) { - // TODO: Intrinsify this, WASM has dedicated instructions for this operation!!! - return Integer.numberOfLeadingZeros(i); - } - } From 7d8f5fc9c44b17059c1770ae5305e0eb6aea724d Mon Sep 17 00:00:00 2001 From: lax1dude Date: Fri, 1 Nov 2024 20:19:37 -0700 Subject: [PATCH 11/33] DirectMalloc intrinsic --- .../intrinsics/gc/DirectMallocIntrinsic.java | 106 ++++++++++++++++++ .../java/org/teavm/runtime/LaxMalloc.java | 11 +- .../java/org/teavm/interop/DirectMalloc.java | 49 ++++++++ 3 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/DirectMallocIntrinsic.java create mode 100644 interop/core/src/main/java/org/teavm/interop/DirectMalloc.java diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/DirectMallocIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/DirectMallocIntrinsic.java new file mode 100644 index 000000000..121b822fe --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/DirectMallocIntrinsic.java @@ -0,0 +1,106 @@ +/* + * Copyright 2024 lax1dude. + * + * 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.backend.wasm.intrinsics.gc; + +import org.teavm.ast.InvocationExpr; +import org.teavm.backend.wasm.intrinsics.WasmIntrinsic; +import org.teavm.backend.wasm.intrinsics.WasmIntrinsicManager; +import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmCopy; +import org.teavm.backend.wasm.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmFill; +import org.teavm.backend.wasm.model.expression.WasmInt32Constant; +import org.teavm.backend.wasm.model.expression.WasmUnreachable; +import org.teavm.interop.Address; +import org.teavm.interop.DirectMalloc; +import org.teavm.model.MethodReference; +import org.teavm.runtime.LaxMalloc; + +public class DirectMallocIntrinsic implements WasmIntrinsic { + private static final MethodReference LAX_MALLOC = new MethodReference(LaxMalloc.class, "laxMalloc", int.class, + Address.class); + private static final MethodReference LAX_CALLOC = new MethodReference(LaxMalloc.class, "laxCalloc", int.class, + Address.class); + private static final MethodReference LAX_FREE = new MethodReference(LaxMalloc.class, "laxFree", Address.class, + void.class); + + @Override + public boolean isApplicable(MethodReference methodReference) { + if (!methodReference.getClassName().equals(DirectMalloc.class.getName())) { + return false; + } + + switch (methodReference.getName()) { + case "malloc": + case "calloc": + case "free": + case "memcpy": + case "memset": + case "zmemset": + return true; + default: + return false; + } + } + + @Override + public WasmExpression apply(InvocationExpr invocation, WasmIntrinsicManager manager) { + switch (invocation.getMethod().getName()) { + case "malloc": { + var function = manager.getFunctions().forStaticMethod(LAX_MALLOC); + var call = new WasmCall(function); + call.getArguments().add(manager.generate(invocation.getArguments().get(0))); + return call; + } + case "calloc": { + var function = manager.getFunctions().forStaticMethod(LAX_CALLOC); + var call = new WasmCall(function); + call.getArguments().add(manager.generate(invocation.getArguments().get(0))); + return call; + } + case "free": { + var function = manager.getFunctions().forStaticMethod(LAX_FREE); + var call = new WasmCall(function); + call.getArguments().add(manager.generate(invocation.getArguments().get(0))); + return call; + } + case "memcpy": { + var copy = new WasmCopy(); + copy.setDestinationIndex(manager.generate(invocation.getArguments().get(0))); + copy.setSourceIndex(manager.generate(invocation.getArguments().get(1))); + copy.setCount(manager.generate(invocation.getArguments().get(2))); + return copy; + } + case "memset": { + var fill = new WasmFill(); + fill.setIndex(manager.generate(invocation.getArguments().get(0))); + fill.setValue(manager.generate(invocation.getArguments().get(1))); + fill.setCount(manager.generate(invocation.getArguments().get(2))); + return fill; + } + case "zmemset": { + var fill = new WasmFill(); + fill.setIndex(manager.generate(invocation.getArguments().get(0))); + fill.setValue(new WasmInt32Constant(0)); + fill.setCount(manager.generate(invocation.getArguments().get(1))); + return fill; + } + default: + return new WasmUnreachable(); + } + } + +} diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index f8a391873..5aaec902e 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -16,6 +16,7 @@ package org.teavm.runtime; import org.teavm.interop.Address; +import org.teavm.interop.DirectMalloc; import org.teavm.interop.Import; import org.teavm.interop.StaticInit; import org.teavm.interop.Unmanaged; @@ -51,7 +52,7 @@ public final class LaxMalloc { static { // zero out the control region - Allocator.fillZero(Address.fromInt(0), ADDR_HEAP_DATA_START); + DirectMalloc.zmemset(Address.fromInt(0), ADDR_HEAP_DATA_START); // initialize heap limit Address.fromInt(ADDR_HEAP_INNER_LIMIT).putInt(ADDR_HEAP_DATA_START); Address.fromInt(ADDR_HEAP_OUTER_LIMIT).putInt(0x10000); @@ -160,7 +161,7 @@ public final class LaxMalloc { // clear if requested if(cleared) { - Allocator.fillZero(ret, sizeBytes); + DirectMalloc.zmemset(ret, sizeBytes); } return ret; @@ -175,7 +176,7 @@ public final class LaxMalloc { // clear if requested if(cleared) { - Allocator.fillZero(ret, sizeBytes); + DirectMalloc.zmemset(ret, sizeBytes); } return ret; @@ -203,7 +204,7 @@ public final class LaxMalloc { // clear if requested if(cleared) { - Allocator.fillZero(ret, sizeBytes); + DirectMalloc.zmemset(ret, sizeBytes); } return ret; @@ -254,7 +255,7 @@ public final class LaxMalloc { // clear if requested if(cleared) { - Allocator.fillZero(ret, sizeBytes); + DirectMalloc.zmemset(ret, sizeBytes); } return ret; diff --git a/interop/core/src/main/java/org/teavm/interop/DirectMalloc.java b/interop/core/src/main/java/org/teavm/interop/DirectMalloc.java new file mode 100644 index 000000000..f26ab0c49 --- /dev/null +++ b/interop/core/src/main/java/org/teavm/interop/DirectMalloc.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 lax1dude. + * + * 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.interop; + +/** + * Linear memory allocator for creating "direct buffers" in WASM GC

+ * + * DO NOT USE IN LEGACY WASM BACKEND!!! Make a regular byte array, and use Address.ofData()

+ * + * Similar to dlmalloc and emmalloc (emscripten's malloc)

+ * + * bad things will happen if you free an address that was never allocated + * + * @author lax1dude + */ +public class DirectMalloc { + + @UnsupportedOn({Platforms.JAVASCRIPT, Platforms.WEBASSEMBLY, Platforms.C}) + public static native Address malloc(int sizeBytes); + + @UnsupportedOn({Platforms.JAVASCRIPT, Platforms.WEBASSEMBLY, Platforms.C}) + public static native Address calloc(int sizeBytes); + + @UnsupportedOn({Platforms.JAVASCRIPT, Platforms.WEBASSEMBLY, Platforms.C}) + public static native void free(Address ptr); + + @UnsupportedOn({Platforms.JAVASCRIPT, Platforms.WEBASSEMBLY, Platforms.C}) + public static native void memcpy(Address dst, Address src, int count); + + @UnsupportedOn({Platforms.JAVASCRIPT, Platforms.WEBASSEMBLY, Platforms.C}) + public static native void memset(Address ptr, int val, int count); + + @UnsupportedOn({Platforms.JAVASCRIPT, Platforms.WEBASSEMBLY, Platforms.C}) + public static native void zmemset(Address ptr, int count); + +} From 0d8baace3d51f31aabca827ee443e3d618f1fa52 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Fri, 1 Nov 2024 21:06:05 -0700 Subject: [PATCH 12/33] Address intrinsic (partial) --- .../wasm/intrinsics/gc/AddressIntrinsic.java | 157 ++++++++++++++++++ .../intrinsics/gc/DirectMallocIntrinsic.java | 32 +--- .../wasm/intrinsics/gc/WasmGCIntrinsics.java | 46 +++++ 3 files changed, 208 insertions(+), 27 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/AddressIntrinsic.java diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/AddressIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/AddressIntrinsic.java new file mode 100644 index 000000000..f8aa3dd0f --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/AddressIntrinsic.java @@ -0,0 +1,157 @@ +/* + * Copyright 2024 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.backend.wasm.intrinsics.gc; + +import java.util.stream.Collectors; +import org.teavm.ast.InvocationExpr; +import org.teavm.backend.wasm.WasmRuntime; +import org.teavm.backend.wasm.model.WasmNumType; +import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmConversion; +import org.teavm.backend.wasm.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmInt32Constant; +import org.teavm.backend.wasm.model.expression.WasmInt32Subtype; +import org.teavm.backend.wasm.model.expression.WasmInt64Subtype; +import org.teavm.backend.wasm.model.expression.WasmIntBinary; +import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation; +import org.teavm.backend.wasm.model.expression.WasmIntType; +import org.teavm.backend.wasm.model.expression.WasmLoadFloat32; +import org.teavm.backend.wasm.model.expression.WasmLoadFloat64; +import org.teavm.backend.wasm.model.expression.WasmLoadInt32; +import org.teavm.backend.wasm.model.expression.WasmLoadInt64; +import org.teavm.backend.wasm.model.expression.WasmStoreFloat32; +import org.teavm.backend.wasm.model.expression.WasmStoreFloat64; +import org.teavm.backend.wasm.model.expression.WasmStoreInt32; +import org.teavm.backend.wasm.model.expression.WasmStoreInt64; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +public class AddressIntrinsic implements WasmGCIntrinsic { + + @Override + public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext manager) { + switch (invocation.getMethod().getName()) { + case "toInt": + case "toStructure": + return manager.generate(invocation.getArguments().get(0)); + case "toLong": { + WasmExpression value = manager.generate(invocation.getArguments().get(0)); + return new WasmConversion(WasmNumType.INT32, WasmNumType.INT64, false, value); + } + case "fromInt": + return manager.generate(invocation.getArguments().get(0)); + case "fromLong": { + WasmExpression value = manager.generate(invocation.getArguments().get(0)); + return new WasmConversion(WasmNumType.INT64, WasmNumType.INT32, false, value); + } + case "add": { + WasmExpression base = manager.generate(invocation.getArguments().get(0)); + if (invocation.getMethod().parameterCount() == 1) { + WasmExpression offset = manager.generate(invocation.getArguments().get(1)); + if (invocation.getMethod().parameterType(0) == ValueType.LONG) { + offset = new WasmConversion(WasmNumType.INT64, WasmNumType.INT32, false, offset); + } + return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, base, offset); + } else { + throw new IllegalArgumentException(invocation.getMethod().toString()); + } + } + case "getByte": + return new WasmLoadInt32(1, manager.generate(invocation.getArguments().get(0)), + WasmInt32Subtype.INT8); + case "getShort": + return new WasmLoadInt32(2, manager.generate(invocation.getArguments().get(0)), + WasmInt32Subtype.INT16); + case "getChar": + return new WasmLoadInt32(2, manager.generate(invocation.getArguments().get(0)), + WasmInt32Subtype.UINT16); + case "getAddress": + case "getInt": + return new WasmLoadInt32(4, manager.generate(invocation.getArguments().get(0)), + WasmInt32Subtype.INT32); + case "getLong": + return new WasmLoadInt64(8, manager.generate(invocation.getArguments().get(0)), + WasmInt64Subtype.INT64); + case "getFloat": + return new WasmLoadFloat32(4, manager.generate(invocation.getArguments().get(0))); + case "getDouble": + return new WasmLoadFloat64(8, manager.generate(invocation.getArguments().get(0))); + case "putByte": { + WasmExpression address = manager.generate(invocation.getArguments().get(0)); + WasmExpression value = manager.generate(invocation.getArguments().get(1)); + return new WasmStoreInt32(1, address, value, WasmInt32Subtype.INT8); + } + case "putShort": { + WasmExpression address = manager.generate(invocation.getArguments().get(0)); + WasmExpression value = manager.generate(invocation.getArguments().get(1)); + return new WasmStoreInt32(2, address, value, WasmInt32Subtype.INT16); + } + case "putChar": { + WasmExpression address = manager.generate(invocation.getArguments().get(0)); + WasmExpression value = manager.generate(invocation.getArguments().get(1)); + return new WasmStoreInt32(2, address, value, WasmInt32Subtype.UINT16); + } + case "putAddress": + case "putInt": { + WasmExpression address = manager.generate(invocation.getArguments().get(0)); + WasmExpression value = manager.generate(invocation.getArguments().get(1)); + return new WasmStoreInt32(4, address, value, WasmInt32Subtype.INT32); + } + case "putLong": { + WasmExpression address = manager.generate(invocation.getArguments().get(0)); + WasmExpression value = manager.generate(invocation.getArguments().get(1)); + return new WasmStoreInt64(8, address, value, WasmInt64Subtype.INT64); + } + case "putFloat": { + WasmExpression address = manager.generate(invocation.getArguments().get(0)); + WasmExpression value = manager.generate(invocation.getArguments().get(1)); + return new WasmStoreFloat32(4, address, value); + } + case "putDouble": { + WasmExpression address = manager.generate(invocation.getArguments().get(0)); + WasmExpression value = manager.generate(invocation.getArguments().get(1)); + return new WasmStoreFloat64(8, address, value); + } + case "sizeOf": + return new WasmInt32Constant(4); + case "align": { + MethodReference delegate = new MethodReference(WasmRuntime.class.getName(), + invocation.getMethod().getDescriptor()); + WasmCall call = new WasmCall(manager.functions().forStaticMethod(delegate)); + call.getArguments().addAll(invocation.getArguments().stream() + .map(arg -> manager.generate(arg)) + .collect(Collectors.toList())); + return call; + } + case "isLessThan": + return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.LT_UNSIGNED, + manager.generate(invocation.getArguments().get(0)), + manager.generate(invocation.getArguments().get(1))); + case "diff": { + WasmExpression result = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.SUB, + manager.generate(invocation.getArguments().get(0)), + manager.generate(invocation.getArguments().get(1)) + ); + result = new WasmConversion(WasmNumType.INT32, WasmNumType.INT64, true, result); + result.setLocation(invocation.getLocation()); + return result; + } + default: + throw new IllegalArgumentException(invocation.getMethod().toString()); + } + } + +} diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/DirectMallocIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/DirectMallocIntrinsic.java index 121b822fe..6383308af 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/DirectMallocIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/DirectMallocIntrinsic.java @@ -16,8 +16,6 @@ package org.teavm.backend.wasm.intrinsics.gc; import org.teavm.ast.InvocationExpr; -import org.teavm.backend.wasm.intrinsics.WasmIntrinsic; -import org.teavm.backend.wasm.intrinsics.WasmIntrinsicManager; import org.teavm.backend.wasm.model.expression.WasmCall; import org.teavm.backend.wasm.model.expression.WasmCopy; import org.teavm.backend.wasm.model.expression.WasmExpression; @@ -25,11 +23,10 @@ import org.teavm.backend.wasm.model.expression.WasmFill; import org.teavm.backend.wasm.model.expression.WasmInt32Constant; import org.teavm.backend.wasm.model.expression.WasmUnreachable; import org.teavm.interop.Address; -import org.teavm.interop.DirectMalloc; import org.teavm.model.MethodReference; import org.teavm.runtime.LaxMalloc; -public class DirectMallocIntrinsic implements WasmIntrinsic { +public class DirectMallocIntrinsic implements WasmGCIntrinsic { private static final MethodReference LAX_MALLOC = new MethodReference(LaxMalloc.class, "laxMalloc", int.class, Address.class); private static final MethodReference LAX_CALLOC = new MethodReference(LaxMalloc.class, "laxCalloc", int.class, @@ -38,41 +35,22 @@ public class DirectMallocIntrinsic implements WasmIntrinsic { void.class); @Override - public boolean isApplicable(MethodReference methodReference) { - if (!methodReference.getClassName().equals(DirectMalloc.class.getName())) { - return false; - } - - switch (methodReference.getName()) { - case "malloc": - case "calloc": - case "free": - case "memcpy": - case "memset": - case "zmemset": - return true; - default: - return false; - } - } - - @Override - public WasmExpression apply(InvocationExpr invocation, WasmIntrinsicManager manager) { + public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext manager) { switch (invocation.getMethod().getName()) { case "malloc": { - var function = manager.getFunctions().forStaticMethod(LAX_MALLOC); + var function = manager.functions().forStaticMethod(LAX_MALLOC); var call = new WasmCall(function); call.getArguments().add(manager.generate(invocation.getArguments().get(0))); return call; } case "calloc": { - var function = manager.getFunctions().forStaticMethod(LAX_CALLOC); + var function = manager.functions().forStaticMethod(LAX_CALLOC); var call = new WasmCall(function); call.getArguments().add(manager.generate(invocation.getArguments().get(0))); return call; } case "free": { - var function = manager.getFunctions().forStaticMethod(LAX_FREE); + var function = manager.functions().forStaticMethod(LAX_FREE); var call = new WasmCall(function); call.getArguments().add(manager.generate(invocation.getArguments().get(0))); return call; diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java index 00919cf13..b100d0552 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java @@ -25,6 +25,8 @@ import org.teavm.backend.wasm.model.expression.WasmIntType; import org.teavm.backend.wasm.runtime.StringInternPool; import org.teavm.backend.wasm.runtime.gc.WasmGCResources; import org.teavm.common.ServiceRepository; +import org.teavm.interop.Address; +import org.teavm.interop.DirectMalloc; import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; @@ -51,6 +53,8 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { fillArray(); fillString(); fillResources(); + fillDirectMalloc(); + fillAddress(); for (var entry : customIntrinsics.entrySet()) { add(entry.getKey(), entry.getValue()); } @@ -166,6 +170,48 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { add(new MethodReference(WasmGCResources.class, "readSingleByte", int.class, int.class), intrinsic); } + private void fillDirectMalloc() { + var intrinsic = new DirectMallocIntrinsic(); + add(new MethodReference(DirectMalloc.class, "malloc", int.class, Address.class), intrinsic); + add(new MethodReference(DirectMalloc.class, "calloc", int.class, Address.class), intrinsic); + add(new MethodReference(DirectMalloc.class, "free", Address.class, void.class), intrinsic); + add(new MethodReference(DirectMalloc.class, "memcpy", Address.class, Address.class, int.class, void.class), intrinsic); + add(new MethodReference(DirectMalloc.class, "memset", Address.class, int.class, int.class, void.class), intrinsic); + add(new MethodReference(DirectMalloc.class, "zmemset", Address.class, int.class, void.class), intrinsic); + } + + private void fillAddress() { + var intrinsic = new AddressIntrinsic(); + add(new MethodReference(Address.class, "add", int.class, Address.class), intrinsic); + add(new MethodReference(Address.class, "add", long.class, Address.class), intrinsic); + add(new MethodReference(Address.class, "isLessThan", Address.class, boolean.class), intrinsic); + add(new MethodReference(Address.class, "toInt", int.class), intrinsic); + add(new MethodReference(Address.class, "toLong", long.class), intrinsic); + //add(new MethodReference(Address.class, "toStructure", ?????), intrinsic); //TODO + add(new MethodReference(Address.class, "getByte", byte.class), intrinsic); + add(new MethodReference(Address.class, "putByte", byte.class, void.class), intrinsic); + add(new MethodReference(Address.class, "getChar", char.class), intrinsic); + add(new MethodReference(Address.class, "putChar", char.class, void.class), intrinsic); + add(new MethodReference(Address.class, "getShort", short.class), intrinsic); + add(new MethodReference(Address.class, "putShort", short.class, void.class), intrinsic); + add(new MethodReference(Address.class, "getInt", int.class), intrinsic); + add(new MethodReference(Address.class, "putInt", int.class, void.class), intrinsic); + add(new MethodReference(Address.class, "getLong", long.class), intrinsic); + add(new MethodReference(Address.class, "putLong", long.class, void.class), intrinsic); + add(new MethodReference(Address.class, "getFloat", float.class), intrinsic); + add(new MethodReference(Address.class, "putFloat", float.class, void.class), intrinsic); + add(new MethodReference(Address.class, "getDouble", double.class), intrinsic); + add(new MethodReference(Address.class, "putDouble", double.class, void.class), intrinsic); + add(new MethodReference(Address.class, "getAddress", Address.class), intrinsic); + add(new MethodReference(Address.class, "putAddress", Address.class, void.class), intrinsic); + add(new MethodReference(Address.class, "fromInt", int.class, Address.class), intrinsic); + add(new MethodReference(Address.class, "fromLong", long.class, Address.class), intrinsic); + add(new MethodReference(Address.class, "align", Address.class, int.class, Address.class), intrinsic); + add(new MethodReference(Address.class, "sizeOf", int.class), intrinsic); + add(new MethodReference(Address.class, "add", Class.class, int.class), intrinsic); + add(new MethodReference(Address.class, "diff", Address.class, long.class), intrinsic); + } + private void add(MethodReference methodRef, WasmGCIntrinsic intrinsic) { intrinsics.put(methodRef, new IntrinsicContainer(intrinsic)); } From f62a80a1d8fa3bb2ea85632c9d4653c485f40f08 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Fri, 1 Nov 2024 22:01:27 -0700 Subject: [PATCH 13/33] Make Address class compile to an i32 --- .../generate/gc/classes/WasmGCTypeMapper.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java index 220d9fdb5..63839993f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java @@ -24,6 +24,7 @@ import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmPackedType; import org.teavm.backend.wasm.model.WasmStorageType; import org.teavm.backend.wasm.model.WasmType; +import org.teavm.interop.Address; import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodDescriptor; import org.teavm.model.ValueType; @@ -127,12 +128,16 @@ public class WasmGCTypeMapper { } } if (result == null) { - var cls = classes.get(className); - if (cls == null) { - className = "java.lang.Object"; + if(className.equals(Address.class.getName())) { + typeCache.put(className, WasmType.INT32); + }else { + var cls = classes.get(className); + if (cls == null) { + className = "java.lang.Object"; + } + result = classInfoProvider.getClassInfo(className).getType(); + typeCache.put(className, result); } - result = classInfoProvider.getClassInfo(className).getType(); - typeCache.put(className, result); } } return result; From 5c25eac049390507fa288631849e160edfe7033f Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 2 Nov 2024 16:01:55 -0700 Subject: [PATCH 14/33] Work on intrinsics --- .../org/teavm/backend/wasm/WasmGCTarget.java | 43 ++++++++- .../backend/wasm/gc/WasmGCDependencies.java | 8 ++ .../intrinsics/gc/LaxMallocIntrinsic.java | 95 +++++++++++++++++++ .../wasm/intrinsics/gc/WasmGCIntrinsics.java | 18 ++++ .../java/org/teavm/runtime/LaxMalloc.java | 62 +++++++----- 5 files changed, 197 insertions(+), 29 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index 093d06221..682eb31c5 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -47,6 +47,7 @@ import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsics; import org.teavm.backend.wasm.model.WasmCustomSection; import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmLocal; +import org.teavm.backend.wasm.model.WasmMemorySegment; import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmTag; import org.teavm.backend.wasm.model.WasmType; @@ -99,6 +100,9 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { private List customGeneratorFactories = new ArrayList<>(); private EntryPointTransformation entryPointTransformation = new EntryPointTransformation(); private List classConsumers = new ArrayList<>(); + private boolean enableDirectMallocSupport = false; + private int directMallocMinHeapSize = 0x10000; + private int directMallocMaxHeapSize = 0x10000000; public void setObfuscated(boolean obfuscated) { this.obfuscated = obfuscated; @@ -128,6 +132,18 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { this.sourceMapLocation = sourceMapLocation; } + public void setEnableDirectMallocSupport(boolean enable) { + this.enableDirectMallocSupport = enable; + } + + public void setDirectMallocMinHeapSize(int minHeapSize) { + this.directMallocMinHeapSize = WasmRuntime.align(minHeapSize, WasmHeap.PAGE_SIZE); + } + + public void setDirectMallocMaxHeapSize(int maxHeapSize) { + this.directMallocMaxHeapSize = WasmRuntime.align(maxHeapSize, WasmHeap.PAGE_SIZE); + } + @Override public void addIntrinsicFactory(WasmGCIntrinsicFactory intrinsicFactory) { intrinsicFactories.add(intrinsicFactory); @@ -194,6 +210,9 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { var deps = new WasmGCDependencies(dependencyAnalyzer); deps.contribute(); deps.contributeStandardExports(); + if(enableDirectMallocSupport) { + deps.contributeDirectMalloc(); + } } @Override @@ -285,6 +304,17 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { moduleGenerator.generate(); customGenerators.contributeToModule(module); generateExceptionExports(declarationsGenerator); + if(enableDirectMallocSupport) { + var heapSegment = new WasmMemorySegment(); + if (!module.getSegments().isEmpty()) { + var lastSegment = module.getSegments().get(module.getSegments().size() - 1); + heapSegment.setOffset(WasmRuntime.align(lastSegment.getOffset() + lastSegment.getLength(), WasmHeap.PAGE_SIZE)); + } + heapSegment.setLength(directMallocMinHeapSize); + module.getSegments().add(heapSegment); + intrinsics.setupLaxMallocHeap(heapSegment.getOffset(), heapSegment.getOffset() + directMallocMinHeapSize, + heapSegment.getOffset() + directMallocMaxHeapSize); + } adjustModuleMemory(module); emitWasmFile(module, buildTarget, outputName, debugInfoBuilder); @@ -390,9 +420,16 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { return; } - var pages = (memorySize - 1) / WasmHeap.PAGE_SIZE + 1; - module.setMinMemorySize(pages); - module.setMaxMemorySize(pages); + if(enableDirectMallocSupport) { + var minPages = (memorySize - 1) / WasmHeap.PAGE_SIZE + 1; + var maxPages = (memorySize - directMallocMinHeapSize + directMallocMaxHeapSize - 1) / WasmHeap.PAGE_SIZE + 1; + module.setMinMemorySize(minPages); + module.setMaxMemorySize(maxPages); + }else { + var pages = (memorySize - 1) / WasmHeap.PAGE_SIZE + 1; + module.setMinMemorySize(pages); + module.setMaxMemorySize(pages); + } } private void emitWasmFile(WasmModule module, BuildTarget buildTarget, String outputName, diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java index 20ddf69d7..e491f256a 100644 --- a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java +++ b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java @@ -22,7 +22,9 @@ import org.teavm.backend.wasm.runtime.gc.WasmGCSupport; import org.teavm.dependency.AbstractDependencyListener; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyAnalyzer; +import org.teavm.interop.Address; import org.teavm.model.MethodReference; +import org.teavm.runtime.LaxMalloc; public class WasmGCDependencies { private DependencyAnalyzer analyzer; @@ -127,4 +129,10 @@ public class WasmGCDependencies { private void contributeString() { analyzer.addDependencyListener(new StringInternDependencySupport()); } + + public void contributeDirectMalloc() { + analyzer.linkMethod(new MethodReference(LaxMalloc.class, "laxMalloc", int.class, Address.class)).use(); + analyzer.linkMethod(new MethodReference(LaxMalloc.class, "laxCalloc", int.class, Address.class)).use(); + analyzer.linkMethod(new MethodReference(LaxMalloc.class, "laxFree", Address.class, void.class)).use(); + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java new file mode 100644 index 000000000..7bf4adce4 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java @@ -0,0 +1,95 @@ +/* + * Copyright 2024 lax1dude. + * + * 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.backend.wasm.intrinsics.gc; + +import java.util.ArrayList; +import java.util.List; + +import org.teavm.ast.InvocationExpr; +import org.teavm.backend.wasm.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmInt32Constant; +import org.teavm.backend.wasm.model.expression.WasmIntBinary; +import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation; +import org.teavm.backend.wasm.model.expression.WasmIntType; +import org.teavm.backend.wasm.model.expression.WasmMemoryGrow; + +public class LaxMallocIntrinsic implements WasmGCIntrinsic { + + private final List addressList = new ArrayList<>(); + private final List minAddrConstants = new ArrayList<>(); + private final List maxAddrConstants = new ArrayList<>(); + + @Override + public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { + switch (invocation.getMethod().getName()) { + case "addrHeap": { + WasmExpression value = context.generate(invocation.getArguments().get(0)); + if(value instanceof WasmInt32Constant) { + // if addrHeap is passed a constant i32, add the heap offset at compile time + final int memOffset = ((WasmInt32Constant)value).getValue(); + WasmInt32Constant ret = new WasmInt32Constant(0); + addressList.add((heapLoc) -> { + ret.setValue(heapLoc + memOffset); + }); + return ret; + }else { + WasmInt32Constant heapLocConst = new WasmInt32Constant(0); + WasmExpression calcOffset = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, heapLocConst, value); + addressList.add(heapLocConst::setValue); + return calcOffset; + } + } + case "growHeapOuter": { + return new WasmMemoryGrow(context.generate(invocation.getArguments().get(0))); + } + case "getHeapMinAddr": { + WasmInt32Constant ret = new WasmInt32Constant(0); + minAddrConstants.add(ret); + return ret; + } + case "getHeapMaxAddr": { + WasmInt32Constant ret = new WasmInt32Constant(0); + maxAddrConstants.add(ret); + return ret; + } + default: + throw new IllegalArgumentException(invocation.getMethod().toString()); + } + } + + public void setHeapLocation(int heapLoc) { + for(LaxMallocHeapMapper mapper : addressList) { + mapper.setHeapLocation(heapLoc); + } + } + + private static interface LaxMallocHeapMapper { + void setHeapLocation(int heapLoc); + } + + public void setHeapMinAddr(int heapSegmentMinAddr) { + for(WasmInt32Constant ct : minAddrConstants) { + ct.setValue(heapSegmentMinAddr); + } + } + + public void setHeapMaxAddr(int heapSegmentMaxAddr) { + for(WasmInt32Constant ct : maxAddrConstants) { + ct.setValue(heapSegmentMaxAddr); + } + } + +} diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java index b100d0552..a477d8d5b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java @@ -30,12 +30,14 @@ import org.teavm.interop.DirectMalloc; import org.teavm.model.ClassReaderSource; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; +import org.teavm.runtime.LaxMalloc; public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { private Map intrinsics = new HashMap<>(); private List factories; private ClassReaderSource classes; private ServiceRepository services; + private LaxMallocIntrinsic laxMallocIntrinsic; public WasmGCIntrinsics(ClassReaderSource classes, ServiceRepository services, List factories, Map customIntrinsics) { @@ -54,6 +56,7 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { fillString(); fillResources(); fillDirectMalloc(); + fillLaxMalloc(); fillAddress(); for (var entry : customIntrinsics.entrySet()) { add(entry.getKey(), entry.getValue()); @@ -180,6 +183,15 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { add(new MethodReference(DirectMalloc.class, "zmemset", Address.class, int.class, void.class), intrinsic); } + private void fillLaxMalloc() { + laxMallocIntrinsic = new LaxMallocIntrinsic(); + add(new MethodReference(LaxMalloc.class, "laxMalloc", int.class, Address.class), laxMallocIntrinsic); + add(new MethodReference(LaxMalloc.class, "laxCalloc", int.class, Address.class), laxMallocIntrinsic); + add(new MethodReference(LaxMalloc.class, "laxFree", Address.class, void.class), laxMallocIntrinsic); + add(new MethodReference(LaxMalloc.class, "getHeapMinSize", int.class), laxMallocIntrinsic); + add(new MethodReference(LaxMalloc.class, "getHeapMaxSize", int.class), laxMallocIntrinsic); + } + private void fillAddress() { var intrinsic = new AddressIntrinsic(); add(new MethodReference(Address.class, "add", int.class, Address.class), intrinsic); @@ -233,6 +245,12 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { return result.intrinsic; } + public void setupLaxMallocHeap(int heapAddr, int heapSegmentMinAddr, int heapSegmentMaxAddr) { + laxMallocIntrinsic.setHeapLocation(heapAddr); + laxMallocIntrinsic.setHeapMinAddr(heapSegmentMinAddr); + laxMallocIntrinsic.setHeapMaxAddr(heapSegmentMaxAddr); + } + static class IntrinsicContainer { final WasmGCIntrinsic intrinsic; diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 5aaec902e..78b762b78 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -50,12 +50,23 @@ public final class LaxMalloc { private static final int ADDR_HEAP_BUCKETS_START = 16; // Address to the list of 64 pointers to the beginnings of the 64 buckets private static final int ADDR_HEAP_DATA_START = 272; // Beginning of the first chunk of the heap + private static native Address addrHeap(int offset); // Intrinsic function to get an address in the heap segment + + private static native int growHeapOuter(int bytes); // Intrinsic function to grow the heap segment + + private static native Address getHeapMinAddr(); // Intrinsic function to get the minimum direct malloc heap segment ending address + + private static native Address getHeapMaxAddr(); // Intrinsic function to get the maximum direct malloc heap segment ending address + + @Import(name = "teavm_notifyHeapResized") + private static native void notifyHeapResized(); + static { // zero out the control region - DirectMalloc.zmemset(Address.fromInt(0), ADDR_HEAP_DATA_START); + DirectMalloc.zmemset(addrHeap(0), ADDR_HEAP_DATA_START); // initialize heap limit - Address.fromInt(ADDR_HEAP_INNER_LIMIT).putInt(ADDR_HEAP_DATA_START); - Address.fromInt(ADDR_HEAP_OUTER_LIMIT).putInt(0x10000); + addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(addrHeap(ADDR_HEAP_DATA_START)); + addrHeap(ADDR_HEAP_OUTER_LIMIT).putAddress(getHeapMinAddr()); } /** @@ -96,7 +107,7 @@ public final class LaxMalloc { } // load bitmask of buckets with free chunks - long bucketMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); + long bucketMask = addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); // mask away the buckets that we know are too small for this allocation bucketMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << bucket)); @@ -125,7 +136,7 @@ public final class LaxMalloc { // quickly determine which bucket it is with bit hacks int availableBucket = Long.numberOfTrailingZeros(bucketMask); - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableBucket << SIZEOF_PTR_SH); + Address bucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START).add(availableBucket << SIZEOF_PTR_SH); Address chunkPtr = bucketStartAddr.getAddress(); int chunkSize = readChunkSizeStatus(chunkPtr); Address itrChunkStart = Address.fromInt(0); @@ -149,7 +160,7 @@ public final class LaxMalloc { if(bucketMask != 0l) { // there is a bucket with a larger chunk int availableLargerBucket = Long.numberOfTrailingZeros(bucketMask); - Address largerBucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(availableLargerBucket << SIZEOF_PTR_SH); + Address largerBucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START).add(availableLargerBucket << SIZEOF_PTR_SH); Address largerChunkPtr = largerBucketStartAddr.getAddress(); int largerChunkSize = readChunkSizeStatus(largerChunkPtr); @@ -234,10 +245,10 @@ public final class LaxMalloc { private static Address laxHugeAlloc(int sizeBytes, boolean cleared) { // check the bucket mask if bucket 63 has any chunks - if((Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong() & 0x8000000000000000L) != 0) { + if((addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).getLong() & 0x8000000000000000L) != 0) { // bucket 63 address - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(63 << SIZEOF_PTR_SH); + Address bucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START).add(63 << SIZEOF_PTR_SH); Address chunkPtr = bucketStartAddr.getAddress(); // iterate all free huge chunks @@ -302,7 +313,7 @@ public final class LaxMalloc { // set the chunk no longer in use chunkSize &= 0x7FFFFFFF; - if(Address.fromInt(ADDR_HEAP_DATA_START).isLessThan(chunkPtr)) { + if(addrHeap(ADDR_HEAP_DATA_START).isLessThan(chunkPtr)) { // check if we can merge with the previous chunk, and move it to another bucket Address prevChunkPtr = chunkPtr.add(-(chunkPtr.add(-4).getInt())); int prevChunkSize = readChunkSizeStatus(prevChunkPtr); @@ -320,7 +331,7 @@ public final class LaxMalloc { } Address nextChunkPtr = chunkPtr.add(chunkSize); - if(Address.fromInt(ADDR_HEAP_INNER_LIMIT).getAddress().isLessThan(nextChunkPtr)) { + if(addrHeap(ADDR_HEAP_INNER_LIMIT).getAddress().isLessThan(nextChunkPtr)) { // check if we can merge with the next chunk as well int nextChunkSize = readChunkSizeStatus(nextChunkPtr); if((nextChunkSize & 0x80000000) == 0) { @@ -388,8 +399,8 @@ public final class LaxMalloc { private static void linkChunkInFreeList(Address chunkPtr, int chunkSize) { int bucket = getListBucket(chunkSize - 8); // size - 2 ints - long bucketMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(bucket << SIZEOF_PTR_SH); + long bucketMask = addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); + Address bucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START).add(bucket << SIZEOF_PTR_SH); // test the bucket mask if the bucket is empty if((bucketMask & (1L << bucket)) == 0l) { @@ -401,7 +412,7 @@ public final class LaxMalloc { // set the free bit in bucket mask bucketMask |= (1L << bucket); - Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketMask); + addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketMask); }else { @@ -434,13 +445,13 @@ public final class LaxMalloc { int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); + Address bucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); bucketStartAddr.putAddress(Address.fromInt(0)); // remove chunk from the bucket // clear the bit in the free buckets bitmask - long bucketsFreeMask = Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); + long bucketsFreeMask = addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); bucketsFreeMask &= ~(1L << chunkBucket); - Address.fromInt(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketsFreeMask); + addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketsFreeMask); }else { // there are other chunks in this bucket @@ -450,7 +461,7 @@ public final class LaxMalloc { writeChunkPrevFreeAddr(nextChunkPtr, prevChunkPtr); int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints - Address bucketStartAddr = Address.fromInt(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); + Address bucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START).add(chunkBucket << SIZEOF_PTR_SH); Address bucketStartChunk = bucketStartAddr.getAddress(); // chunk is the first in the bucket, so we also need to @@ -480,28 +491,27 @@ public final class LaxMalloc { * This is our sbrk */ private static Address growHeap(int amount) { - Address heapInnerLimit = Address.fromInt(ADDR_HEAP_INNER_LIMIT).getAddress(); - Address heapOuterLimit = Address.fromInt(ADDR_HEAP_OUTER_LIMIT).getAddress(); + Address heapInnerLimit = addrHeap(ADDR_HEAP_INNER_LIMIT).getAddress(); + Address heapOuterLimit = addrHeap(ADDR_HEAP_OUTER_LIMIT).getAddress(); Address newHeapInnerLimit = heapInnerLimit.add(amount); if(heapOuterLimit.isLessThan(newHeapInnerLimit)) { int bytesNeeded = newHeapInnerLimit.toInt() - heapOuterLimit.toInt(); bytesNeeded = (bytesNeeded + 0xFFFF) & 0xFFFF0000; - if(growHeapOuter(bytesNeeded)) { - Address.fromInt(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); - Address.fromInt(ADDR_HEAP_OUTER_LIMIT).putAddress(heapOuterLimit.add(bytesNeeded)); + Address newHeapOuterLimit = heapOuterLimit.add(bytesNeeded); + if(!getHeapMaxAddr().isLessThan(newHeapOuterLimit) && growHeapOuter(bytesNeeded >> 16) != -1) { + addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); + addrHeap(ADDR_HEAP_OUTER_LIMIT).putAddress(newHeapOuterLimit); + notifyHeapResized(); return newHeapInnerLimit; }else { return Address.fromInt(0); } }else { - Address.fromInt(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); + addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); return newHeapInnerLimit; } } - @Import(name = "teavm_growHeap") - private static native boolean growHeapOuter(int bytes); - /** * Note that on a free chunk, this is the size, because the status bit is 0 */ From 26df995f7c285e096aaaa1c9411f65a9c033482c Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 2 Nov 2024 16:03:59 -0700 Subject: [PATCH 15/33] fix wrong names --- .../teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java index a477d8d5b..2d47b68ca 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java @@ -188,8 +188,8 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { add(new MethodReference(LaxMalloc.class, "laxMalloc", int.class, Address.class), laxMallocIntrinsic); add(new MethodReference(LaxMalloc.class, "laxCalloc", int.class, Address.class), laxMallocIntrinsic); add(new MethodReference(LaxMalloc.class, "laxFree", Address.class, void.class), laxMallocIntrinsic); - add(new MethodReference(LaxMalloc.class, "getHeapMinSize", int.class), laxMallocIntrinsic); - add(new MethodReference(LaxMalloc.class, "getHeapMaxSize", int.class), laxMallocIntrinsic); + add(new MethodReference(LaxMalloc.class, "getHeapMinAddr", Address.class), laxMallocIntrinsic); + add(new MethodReference(LaxMalloc.class, "getHeapMaxAddr", Address.class), laxMallocIntrinsic); } private void fillAddress() { From c630fae2697b89278d8e3e27c94227d6bc38abd8 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 2 Nov 2024 17:10:33 -0700 Subject: [PATCH 16/33] work on adding direct malloc support option to tooling --- .../main/java/org/teavm/tooling/TeaVMTool.java | 10 ++++++++++ .../teavm/tooling/builder/BuildStrategy.java | 2 ++ .../tooling/builder/InProcessBuildStrategy.java | 7 +++++++ .../tooling/builder/RemoteBuildStrategy.java | 5 +++++ .../tooling/daemon/RemoteBuildRequest.java | 1 + .../main/java/org/teavm/gradle/TeaVMPlugin.java | 3 +++ .../gradle/api/TeaVMWasmGCConfiguration.java | 4 +++- .../teavm/gradle/tasks/GenerateWasmGCTask.java | 17 +++++++++++++++++ 8 files changed, 48 insertions(+), 1 deletion(-) diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index b30b994fe..ab613f8b3 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -119,6 +119,7 @@ public class TeaVMTool { private Set generatedFiles = new HashSet<>(); private int minHeapSize = 4 * (1 << 20); private int maxHeapSize = 128 * (1 << 20); + private boolean directMallocSupport = false; private ReferenceCache referenceCache; private boolean heapDump; private boolean shortFileNames; @@ -268,6 +269,10 @@ public class TeaVMTool { this.maxHeapSize = maxHeapSize; } + public void setDirectMallocSupport(boolean enableDirectMalloc) { + this.directMallocSupport = enableDirectMalloc; + } + public ClassLoader getClassLoader() { return classLoader; } @@ -411,6 +416,11 @@ public class TeaVMTool { target.setSourceMapBuilder(wasmSourceMapWriter); target.setSourceMapLocation(getResolvedTargetFileName() + ".map"); } + if(directMallocSupport) { + target.setEnableDirectMallocSupport(directMallocSupport); + target.setDirectMallocMinHeapSize(minHeapSize); + target.setDirectMallocMaxHeapSize(maxHeapSize); + } return target; } diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java index 2fe510467..f391bb036 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/BuildStrategy.java @@ -89,6 +89,8 @@ public interface BuildStrategy { void setWasmDebugInfoLocation(WasmDebugInfoLocation wasmDebugInfoLocation); + void setDirectMallocSupport(boolean enable); + void setMinHeapSize(int minHeapSize); void setMaxHeapSize(int maxHeapSize); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java index 9f790dfd1..ed94a59e8 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/InProcessBuildStrategy.java @@ -75,6 +75,7 @@ public class InProcessBuildStrategy implements BuildStrategy { private TeaVMToolLog log = new EmptyTeaVMToolLog(); private boolean shortFileNames; private boolean assertionsRemoved; + private boolean directMallocSupport; @Override public void init() { @@ -258,6 +259,11 @@ public class InProcessBuildStrategy implements BuildStrategy { this.assertionsRemoved = assertionsRemoved; } + @Override + public void setDirectMallocSupport(boolean enable) { + this.directMallocSupport = enable; + } + @Override public BuildResult build() throws BuildException { TeaVMTool tool = new TeaVMTool(); @@ -289,6 +295,7 @@ public class InProcessBuildStrategy implements BuildStrategy { tool.setWasmExceptionsUsed(wasmExceptionsUsed); tool.setWasmDebugInfoLevel(wasmDebugInfoLevel); tool.setWasmDebugInfoLocation(wasmDebugInfoLocation); + tool.setDirectMallocSupport(directMallocSupport); tool.setMinHeapSize(minHeapSize); tool.setMaxHeapSize(maxHeapSize); tool.setHeapDump(heapDump); diff --git a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java index bf71e65a6..106d10c0f 100644 --- a/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java +++ b/tools/core/src/main/java/org/teavm/tooling/builder/RemoteBuildStrategy.java @@ -214,6 +214,11 @@ public class RemoteBuildStrategy implements BuildStrategy { request.maxHeapSize = maxHeapSize; } + @Override + public void setDirectMallocSupport(boolean enable) { + request.directMallocSupport = enable; + } + @Override public void setHeapDump(boolean heapDump) { request.heapDump = heapDump; diff --git a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java index 78b40c20f..8fd6bbf3f 100644 --- a/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java +++ b/tools/core/src/main/java/org/teavm/tooling/daemon/RemoteBuildRequest.java @@ -59,4 +59,5 @@ public class RemoteBuildRequest implements Serializable { public boolean heapDump; public boolean shortFileNames; public boolean assertionsRemoved; + public boolean directMallocSupport; } diff --git a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java index c4f083fcc..5e8382f06 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/TeaVMPlugin.java @@ -231,6 +231,9 @@ public class TeaVMPlugin implements Plugin { task.getStrict().convention(wasmGC.getStrict()); task.getSourceMap().convention(wasmGC.getSourceMap()); task.getSourceFilePolicy().convention(wasmGC.getSourceFilePolicy()); + task.getDirectMallocSupport().convention(wasmGC.getDirectMallocSupport()); + task.getMinHeapSize().convention(wasmGC.getMinHeapSize()); + task.getMaxHeapSize().convention(wasmGC.getMaxHeapSize()); setupSources(task.getSourceFiles(), project); buildTask.dependsOn(task); }); diff --git a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java index 328d85643..356cd8d32 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java @@ -17,7 +17,7 @@ package org.teavm.gradle.api; import org.gradle.api.provider.Property; -public interface TeaVMWasmGCConfiguration extends TeaVMCommonConfiguration, TeaVMWebConfiguration { +public interface TeaVMWasmGCConfiguration extends TeaVMCommonConfiguration, TeaVMWebConfiguration, TeaVMNativeBaseConfiguration { Property getObfuscated(); Property getStrict(); @@ -37,4 +37,6 @@ public interface TeaVMWasmGCConfiguration extends TeaVMCommonConfiguration, TeaV Property getSourceFilePolicy(); Property getModularRuntime(); + + Property getDirectMallocSupport(); } diff --git a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java index d91c3189b..7e244fdf2 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java @@ -27,6 +27,8 @@ import org.teavm.tooling.TeaVMTargetType; import org.teavm.tooling.builder.BuildStrategy; public abstract class GenerateWasmGCTask extends TeaVMTask { + private static final int MB = 1024 * 1024; + public GenerateWasmGCTask() { getStrict().convention(true); getObfuscated().convention(true); @@ -58,6 +60,18 @@ public abstract class GenerateWasmGCTask extends TeaVMTask { @Optional public abstract Property getSourceFilePolicy(); + @Input + @Optional + public abstract Property getDirectMallocSupport(); + + @Input + @Optional + public abstract Property getMinHeapSize(); + + @Input + @Optional + public abstract Property getMaxHeapSize(); + @Override protected void setupBuilder(BuildStrategy builder) { builder.setStrict(getStrict().get()); @@ -83,5 +97,8 @@ public abstract class GenerateWasmGCTask extends TeaVMTask { builder.setTargetType(TeaVMTargetType.WEBASSEMBLY_GC); TaskUtils.applySourceFiles(getSourceFiles(), builder); TaskUtils.applySourceFilePolicy(getSourceFilePolicy(), builder); + builder.setDirectMallocSupport(getDirectMallocSupport().getOrElse(false)); + builder.setMinHeapSize(getMinHeapSize().getOrElse(0) * MB); + builder.setMaxHeapSize(getMaxHeapSize().getOrElse(0) * MB); } } From df6fc752b66951364c30f9d76b63305c1725c658 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 2 Nov 2024 17:34:37 -0700 Subject: [PATCH 17/33] fixes --- .../core/src/main/java/org/teavm/interop/DirectMalloc.java | 5 ++++- .../java/org/teavm/gradle/tasks/GenerateWasmGCTask.java | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/interop/core/src/main/java/org/teavm/interop/DirectMalloc.java b/interop/core/src/main/java/org/teavm/interop/DirectMalloc.java index f26ab0c49..b605abb92 100644 --- a/interop/core/src/main/java/org/teavm/interop/DirectMalloc.java +++ b/interop/core/src/main/java/org/teavm/interop/DirectMalloc.java @@ -26,7 +26,10 @@ package org.teavm.interop; * * @author lax1dude */ -public class DirectMalloc { +public final class DirectMalloc { + + private DirectMalloc() { + } @UnsupportedOn({Platforms.JAVASCRIPT, Platforms.WEBASSEMBLY, Platforms.C}) public static native Address malloc(int sizeBytes); diff --git a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java index 7e244fdf2..0ad674a00 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/tasks/GenerateWasmGCTask.java @@ -36,6 +36,9 @@ public abstract class GenerateWasmGCTask extends TeaVMTask { getDebugInfoLocation().convention(WasmDebugInfoLocation.EXTERNAL); getSourceMap().convention(false); getSourceFilePolicy().convention(SourceFilePolicy.LINK_LOCAL_FILES); + getDirectMallocSupport().convention(false); + getMinHeapSize().convention(1); + getMaxHeapSize().convention(16); } @Input @@ -98,7 +101,7 @@ public abstract class GenerateWasmGCTask extends TeaVMTask { TaskUtils.applySourceFiles(getSourceFiles(), builder); TaskUtils.applySourceFilePolicy(getSourceFilePolicy(), builder); builder.setDirectMallocSupport(getDirectMallocSupport().getOrElse(false)); - builder.setMinHeapSize(getMinHeapSize().getOrElse(0) * MB); - builder.setMaxHeapSize(getMaxHeapSize().getOrElse(0) * MB); + builder.setMinHeapSize(getMinHeapSize().get() * MB); + builder.setMaxHeapSize(getMaxHeapSize().get() * MB); } } From a5836dff2590b63e59ef7f152ac2ab38672cd829 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 2 Nov 2024 21:02:25 -0700 Subject: [PATCH 18/33] fix style violations --- .../org/teavm/backend/wasm/WasmGCTarget.java | 16 +++-- .../generate/gc/classes/WasmGCTypeMapper.java | 4 +- .../intrinsics/gc/LaxMallocIntrinsic.java | 15 +++-- .../java/org/teavm/runtime/LaxMalloc.java | 64 +++++++++++-------- .../java/org/teavm/tooling/TeaVMTool.java | 4 +- 5 files changed, 60 insertions(+), 43 deletions(-) diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index 682eb31c5..224a410df 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -100,7 +100,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { private List customGeneratorFactories = new ArrayList<>(); private EntryPointTransformation entryPointTransformation = new EntryPointTransformation(); private List classConsumers = new ArrayList<>(); - private boolean enableDirectMallocSupport = false; + private boolean enableDirectMallocSupport; private int directMallocMinHeapSize = 0x10000; private int directMallocMaxHeapSize = 0x10000000; @@ -210,7 +210,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { var deps = new WasmGCDependencies(dependencyAnalyzer); deps.contribute(); deps.contributeStandardExports(); - if(enableDirectMallocSupport) { + if (enableDirectMallocSupport) { deps.contributeDirectMalloc(); } } @@ -304,11 +304,12 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { moduleGenerator.generate(); customGenerators.contributeToModule(module); generateExceptionExports(declarationsGenerator); - if(enableDirectMallocSupport) { + if (enableDirectMallocSupport) { var heapSegment = new WasmMemorySegment(); if (!module.getSegments().isEmpty()) { var lastSegment = module.getSegments().get(module.getSegments().size() - 1); - heapSegment.setOffset(WasmRuntime.align(lastSegment.getOffset() + lastSegment.getLength(), WasmHeap.PAGE_SIZE)); + heapSegment.setOffset(WasmRuntime.align(lastSegment.getOffset() + + lastSegment.getLength(), WasmHeap.PAGE_SIZE)); } heapSegment.setLength(directMallocMinHeapSize); module.getSegments().add(heapSegment); @@ -420,12 +421,13 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { return; } - if(enableDirectMallocSupport) { + if (enableDirectMallocSupport) { var minPages = (memorySize - 1) / WasmHeap.PAGE_SIZE + 1; - var maxPages = (memorySize - directMallocMinHeapSize + directMallocMaxHeapSize - 1) / WasmHeap.PAGE_SIZE + 1; + var maxPages = (memorySize - directMallocMinHeapSize + directMallocMaxHeapSize - 1) + / WasmHeap.PAGE_SIZE + 1; module.setMinMemorySize(minPages); module.setMaxMemorySize(maxPages); - }else { + } else { var pages = (memorySize - 1) / WasmHeap.PAGE_SIZE + 1; module.setMinMemorySize(pages); module.setMaxMemorySize(pages); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java index 63839993f..3f9aeaa3b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java @@ -128,9 +128,9 @@ public class WasmGCTypeMapper { } } if (result == null) { - if(className.equals(Address.class.getName())) { + if (className.equals(Address.class.getName())) { typeCache.put(className, WasmType.INT32); - }else { + } else { var cls = classes.get(className); if (cls == null) { className = "java.lang.Object"; diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java index 7bf4adce4..9aa6f2ff6 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java @@ -37,17 +37,18 @@ public class LaxMallocIntrinsic implements WasmGCIntrinsic { switch (invocation.getMethod().getName()) { case "addrHeap": { WasmExpression value = context.generate(invocation.getArguments().get(0)); - if(value instanceof WasmInt32Constant) { + if (value instanceof WasmInt32Constant) { // if addrHeap is passed a constant i32, add the heap offset at compile time - final int memOffset = ((WasmInt32Constant)value).getValue(); + final int memOffset = ((WasmInt32Constant) value).getValue(); WasmInt32Constant ret = new WasmInt32Constant(0); - addressList.add((heapLoc) -> { + addressList.add(heapLoc -> { ret.setValue(heapLoc + memOffset); }); return ret; - }else { + } else { WasmInt32Constant heapLocConst = new WasmInt32Constant(0); - WasmExpression calcOffset = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, heapLocConst, value); + WasmExpression calcOffset = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, + heapLocConst, value); addressList.add(heapLocConst::setValue); return calcOffset; } @@ -81,13 +82,13 @@ public class LaxMallocIntrinsic implements WasmGCIntrinsic { } public void setHeapMinAddr(int heapSegmentMinAddr) { - for(WasmInt32Constant ct : minAddrConstants) { + for (WasmInt32Constant ct : minAddrConstants) { ct.setValue(heapSegmentMinAddr); } } public void setHeapMaxAddr(int heapSegmentMaxAddr) { - for(WasmInt32Constant ct : maxAddrConstants) { + for (WasmInt32Constant ct : maxAddrConstants) { ct.setValue(heapSegmentMaxAddr); } } diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 78b762b78..ac112014b 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -44,19 +44,32 @@ public final class LaxMalloc { private static final int MIN_ALLOC_SIZE = 8; - private static final int ADDR_HEAP_OUTER_LIMIT = 0; // Address where we store the WebAssembly.Memory limit (32 bit int) - private static final int ADDR_HEAP_INNER_LIMIT = 4; // Address where we store the current heap limit (32 bit int) - private static final int ADDR_HEAP_BUCKETS_FREE_MASK = 8; // Address where we store the bitmask of free chunk lists (64 bit int) - private static final int ADDR_HEAP_BUCKETS_START = 16; // Address to the list of 64 pointers to the beginnings of the 64 buckets - private static final int ADDR_HEAP_DATA_START = 272; // Beginning of the first chunk of the heap + // Address where we store the WebAssembly.Memory limit (32 bit int) + private static final int ADDR_HEAP_OUTER_LIMIT = 0; - private static native Address addrHeap(int offset); // Intrinsic function to get an address in the heap segment + // Address where we store the current heap limit (32 bit int) + private static final int ADDR_HEAP_INNER_LIMIT = 4; - private static native int growHeapOuter(int bytes); // Intrinsic function to grow the heap segment + // Address where we store the bitmask of free chunk lists (64 bit int) + private static final int ADDR_HEAP_BUCKETS_FREE_MASK = 8; - private static native Address getHeapMinAddr(); // Intrinsic function to get the minimum direct malloc heap segment ending address + // Address to the list of 64 pointers to the beginnings of the 64 buckets + private static final int ADDR_HEAP_BUCKETS_START = 16; - private static native Address getHeapMaxAddr(); // Intrinsic function to get the maximum direct malloc heap segment ending address + // Beginning of the first chunk of the heap + private static final int ADDR_HEAP_DATA_START = 272; + + // Intrinsic function to get an address in the heap segment + private static native Address addrHeap(int offset); + + // Intrinsic function to grow the heap segment + private static native int growHeapOuter(int bytes); + + // Intrinsic function to get the minimum direct malloc heap segment ending address + private static native Address getHeapMinAddr(); + + // Intrinsic function to get the maximum direct malloc heap segment ending address + private static native Address getHeapMaxAddr(); @Import(name = "teavm_notifyHeapResized") private static native void notifyHeapResized(); @@ -84,13 +97,13 @@ public final class LaxMalloc { } private static Address laxAlloc(int sizeBytes, boolean cleared) { - if(sizeBytes <= 0) { + if (sizeBytes <= 0) { // Produce a null pointer if 0 or invalid size is requested return Address.fromInt(0); } // Allocation must be large enough to hold the two list pointers when the chunk becomes free again - if(sizeBytes < MIN_ALLOC_SIZE) { + if (sizeBytes < MIN_ALLOC_SIZE) { sizeBytes = MIN_ALLOC_SIZE; } @@ -100,7 +113,7 @@ public final class LaxMalloc { // always between 0-63 int bucket = getListBucket(sizeBytes); - if(bucket == 63) { + if (bucket == 63) { // special bucket for the huge allocations // uses a different slower function return laxHugeAlloc(sizeBytes, cleared); @@ -110,16 +123,16 @@ public final class LaxMalloc { long bucketMask = addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); // mask away the buckets that we know are too small for this allocation - bucketMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << bucket)); + bucketMask = bucketMask & (0xFFFFFFFFFFFFFFFFL << bucket); // there are no more buckets with free chunks // need to sbrk - if(bucketMask == 0l) { + if (bucketMask == 0L) { int sizePlusInts = sizeBytes + 8; // size + 2 ints Address newChunk = growHeap(sizePlusInts); // sbrk // Out of memory - if(newChunk.toInt() == 0) { + if (newChunk.toInt() == 0) { return Address.fromInt(0); //TODO } @@ -142,22 +155,22 @@ public final class LaxMalloc { Address itrChunkStart = Address.fromInt(0); // check if the first chunk in the bucket is large enough - if(chunkSize - 8 < sizeBytes) { // size - 2 ints + if (chunkSize - 8 < sizeBytes) { // size - 2 ints // the chunk is not large enough, move the first chunk to the end of the list // and then check in the next bucket (where the chunks are definitely large enough) // this functionality is present in emmalloc (emscripten) Address chunkNextPtr = readChunkNextFreeAddr(chunkPtr); - if(chunkNextPtr.getInt() != chunkPtr.getInt()) { + if (chunkNextPtr.getInt() != chunkPtr.getInt()) { bucketStartAddr.putAddress(chunkNextPtr); itrChunkStart = chunkNextPtr; } // extend mask to the next bucket - bucketMask = (bucketMask & (0xFFFFFFFFFFFFFFFFL << (bucket + 1))); + bucketMask &= 0xFFFFFFFFFFFFFFFFL << (bucket + 1); - if(bucketMask != 0l) { + if (bucketMask != 0L) { // there is a bucket with a larger chunk int availableLargerBucket = Long.numberOfTrailingZeros(bucketMask); Address largerBucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START).add(availableLargerBucket << SIZEOF_PTR_SH); @@ -171,13 +184,13 @@ public final class LaxMalloc { Address ret = largerChunkPtr.add(4); // clear if requested - if(cleared) { + if (cleared) { DirectMalloc.zmemset(ret, sizeBytes); } return ret; } - }else { + } else { // the first chunk in the bucket is large enough // this will remove the chunk from the free list allocateMemoryFromChunk(chunkPtr, chunkSize, sizeBytes); @@ -186,14 +199,14 @@ public final class LaxMalloc { Address ret = chunkPtr.add(4); // clear if requested - if(cleared) { + if (cleared) { DirectMalloc.zmemset(ret, sizeBytes); } return ret; } - if(itrChunkStart.toInt() != 0) { + if (itrChunkStart.toInt() != 0) { // if we've reached this point, it means the first chunk in the bucket wasn't large enough // and there weren't any chunks in the larger buckets we could split up @@ -220,7 +233,8 @@ public final class LaxMalloc { return ret; } - }while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()); + addrIterator = readChunkNextFreeAddr(addrIterator); + } while (addrIterator.getInt() != chunkPtr.getInt()); } // no other options, time to sbrk @@ -229,7 +243,7 @@ public final class LaxMalloc { Address newChunk = growHeap(sizePlusInts); // sbrk // Out of memory - if(newChunk.toInt() == 0) { + if (newChunk.toInt() == 0) { return Address.fromInt(0); //TODO } diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java index ab613f8b3..1187c4ae5 100644 --- a/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -119,7 +119,7 @@ public class TeaVMTool { private Set generatedFiles = new HashSet<>(); private int minHeapSize = 4 * (1 << 20); private int maxHeapSize = 128 * (1 << 20); - private boolean directMallocSupport = false; + private boolean directMallocSupport; private ReferenceCache referenceCache; private boolean heapDump; private boolean shortFileNames; @@ -416,7 +416,7 @@ public class TeaVMTool { target.setSourceMapBuilder(wasmSourceMapWriter); target.setSourceMapLocation(getResolvedTargetFileName() + ".map"); } - if(directMallocSupport) { + if (directMallocSupport) { target.setEnableDirectMallocSupport(directMallocSupport); target.setDirectMallocMinHeapSize(minHeapSize); target.setDirectMallocMaxHeapSize(maxHeapSize); From fec6caa10f2f1c7545eadfab43284f5dbbfb4c8e Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 2 Nov 2024 21:05:25 -0700 Subject: [PATCH 19/33] fix style violations --- .../java/org/teavm/runtime/LaxMalloc.java | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index ac112014b..6f77791c4 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -218,7 +218,7 @@ public final class LaxMalloc { chunkSize = readChunkSizeStatus(addrIterator); // check if the chunk is large enough - if(chunkSize - 8 >= sizeBytes) { // size - 2 ints + if (chunkSize - 8 >= sizeBytes) { // size - 2 ints // we've found a large enough chunk // this will remove the chunk from the free list allocateMemoryFromChunk(addrIterator, chunkSize, sizeBytes); @@ -227,7 +227,7 @@ public final class LaxMalloc { Address ret = chunkPtr.add(4); // clear if requested - if(cleared) { + if (cleared) { DirectMalloc.zmemset(ret, sizeBytes); } @@ -259,7 +259,7 @@ public final class LaxMalloc { private static Address laxHugeAlloc(int sizeBytes, boolean cleared) { // check the bucket mask if bucket 63 has any chunks - if((addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).getLong() & 0x8000000000000000L) != 0) { + if ((addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).getLong() & 0x8000000000000000L) != 0) { // bucket 63 address Address bucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START).add(63 << SIZEOF_PTR_SH); @@ -270,7 +270,7 @@ public final class LaxMalloc { do { int chunkSize = readChunkSizeStatus(addrIterator); - if(chunkSize - 8 >= sizeBytes) { // size - 2 ints + if (chunkSize - 8 >= sizeBytes) { // size - 2 ints // we've found a large enough chunk // this will remove the chunk from the free list allocateMemoryFromChunk(addrIterator, chunkSize, sizeBytes); @@ -285,7 +285,8 @@ public final class LaxMalloc { return ret; } - }while((addrIterator = readChunkNextFreeAddr(addrIterator)).getInt() != chunkPtr.getInt()); + addrIterator = readChunkNextFreeAddr(addrIterator); + } while (addrIterator.getInt() != chunkPtr.getInt()); } // no free huge chunks found, time to sbrk @@ -294,7 +295,7 @@ public final class LaxMalloc { Address newChunk = growHeap(sizePlusInts); // sbrk // Out of memory - if(newChunk.toInt() == 0) { + if (newChunk.toInt() == 0) { return Address.fromInt(0); //TODO } @@ -313,7 +314,7 @@ public final class LaxMalloc { * bad things will happen if you free an address that was never allocated */ public static void laxFree(Address address) { - if(address.toInt() == 0) { + if (address.toInt() == 0) { return; } @@ -327,11 +328,11 @@ public final class LaxMalloc { // set the chunk no longer in use chunkSize &= 0x7FFFFFFF; - if(addrHeap(ADDR_HEAP_DATA_START).isLessThan(chunkPtr)) { + if (addrHeap(ADDR_HEAP_DATA_START).isLessThan(chunkPtr)) { // check if we can merge with the previous chunk, and move it to another bucket Address prevChunkPtr = chunkPtr.add(-(chunkPtr.add(-4).getInt())); int prevChunkSize = readChunkSizeStatus(prevChunkPtr); - if((prevChunkSize & 0x80000000) == 0) { + if ((prevChunkSize & 0x80000000) == 0) { // previous chunk is not in use, merge! // remove the previous chunk from its list @@ -345,10 +346,10 @@ public final class LaxMalloc { } Address nextChunkPtr = chunkPtr.add(chunkSize); - if(addrHeap(ADDR_HEAP_INNER_LIMIT).getAddress().isLessThan(nextChunkPtr)) { + if (addrHeap(ADDR_HEAP_INNER_LIMIT).getAddress().isLessThan(nextChunkPtr)) { // check if we can merge with the next chunk as well int nextChunkSize = readChunkSizeStatus(nextChunkPtr); - if((nextChunkSize & 0x80000000) == 0) { + if ((nextChunkSize & 0x80000000) == 0) { // next chunk is not in use, merge! // remove the next chunk from its list @@ -363,7 +364,7 @@ public final class LaxMalloc { // store the final chunk size (also clears the in use flag) chunkPtr.putInt(chunkSize); - if(sizeChanged) { + if (sizeChanged) { // if the size of the chunk changed, we also need to update the chunk's second size integer chunkPtr.add(chunkSize - 4).putInt(chunkSize); } @@ -385,7 +386,7 @@ public final class LaxMalloc { // check if we can split the chunk into two smaller chunks // chunk must be large enough to hold the 2 list pointers - if(otherHalfSize - (2 << SIZEOF_PTR_SH) >= MIN_ALLOC_SIZE) { + if (otherHalfSize - (2 << SIZEOF_PTR_SH) >= MIN_ALLOC_SIZE) { // chunk is large enough to split // provision the lower part of the chunk, the part we want to use @@ -401,7 +402,7 @@ public final class LaxMalloc { // return the upper part of the chunk to the free chunks list linkChunkInFreeList(otherChunkPtr, otherHalfSize); - }else { + } else { // not large enough to split, just take the entire chunk chunkPtr.putInt(chunkSize | 0x80000000); // sets the in use flag } @@ -417,7 +418,7 @@ public final class LaxMalloc { Address bucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START).add(bucket << SIZEOF_PTR_SH); // test the bucket mask if the bucket is empty - if((bucketMask & (1L << bucket)) == 0l) { + if ((bucketMask & (1L << bucket)) == 0l) { // bucket is empty, add the free chunk to the list bucketStartAddr.putAddress(chunkPtr); @@ -428,7 +429,7 @@ public final class LaxMalloc { bucketMask |= (1L << bucket); addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketMask); - }else { + } else { // bucket is not empty, append to the bucket's existing free chunks list Address otherBucketStart = bucketStartAddr.getAddress(); @@ -454,7 +455,7 @@ public final class LaxMalloc { private static void unlinkChunkFromFreeList(Address chunkPtr, int chunkSize) { Address prevChunkPtr = readChunkPrevFreeAddr(chunkPtr); Address nextChunkPtr = readChunkNextFreeAddr(chunkPtr); - if(prevChunkPtr.toInt() == nextChunkPtr.toInt()) { + if (prevChunkPtr.toInt() == nextChunkPtr.toInt()) { // chunk is the only one currently in its bucket int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints @@ -467,7 +468,7 @@ public final class LaxMalloc { bucketsFreeMask &= ~(1L << chunkBucket); addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketsFreeMask); - }else { + } else { // there are other chunks in this bucket // link the next chunk to the previous chunk @@ -480,19 +481,21 @@ public final class LaxMalloc { // chunk is the first in the bucket, so we also need to // update the bucket to point to the next chunk instead - if(bucketStartChunk.toInt() == chunkPtr.toInt()) { + if (bucketStartChunk.toInt() == chunkPtr.toInt()) { bucketStartAddr.putAddress(nextChunkPtr); } } } /** - * https://github.com/emscripten-core/emscripten/blob/16a0bf174cb85f88b6d9dcc8ee7fbca59390185b/system/lib/emmalloc.c#L241 + * https://github.com/emscripten-core/emscripten/blob/16a0bf174cb85f88b6d9dcc8ee7fbca59390185b/system/ + * lib/emmalloc.c#L241 * (MIT License) */ private static int getListBucket(int allocSize) { - if (allocSize < 128) + if (allocSize < 128) { return (allocSize >> 3) - 1; + } int clz = Integer.numberOfLeadingZeros(allocSize); int bucketIndex = (clz > 19) ? 110 - (clz << 2) + ((allocSize >> (29 - clz)) ^ 4) @@ -508,19 +511,19 @@ public final class LaxMalloc { Address heapInnerLimit = addrHeap(ADDR_HEAP_INNER_LIMIT).getAddress(); Address heapOuterLimit = addrHeap(ADDR_HEAP_OUTER_LIMIT).getAddress(); Address newHeapInnerLimit = heapInnerLimit.add(amount); - if(heapOuterLimit.isLessThan(newHeapInnerLimit)) { + if (heapOuterLimit.isLessThan(newHeapInnerLimit)) { int bytesNeeded = newHeapInnerLimit.toInt() - heapOuterLimit.toInt(); bytesNeeded = (bytesNeeded + 0xFFFF) & 0xFFFF0000; Address newHeapOuterLimit = heapOuterLimit.add(bytesNeeded); - if(!getHeapMaxAddr().isLessThan(newHeapOuterLimit) && growHeapOuter(bytesNeeded >> 16) != -1) { + if (!getHeapMaxAddr().isLessThan(newHeapOuterLimit) && growHeapOuter(bytesNeeded >> 16) != -1) { addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); addrHeap(ADDR_HEAP_OUTER_LIMIT).putAddress(newHeapOuterLimit); notifyHeapResized(); return newHeapInnerLimit; - }else { + } else { return Address.fromInt(0); } - }else { + } else { addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); return newHeapInnerLimit; } From 5c5c92e104cdbc51f851a71c7f522912e0a4ad94 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 2 Nov 2024 21:37:48 -0700 Subject: [PATCH 20/33] fix style violations --- .../backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java | 5 ++--- .../backend/wasm/intrinsics/gc/WasmGCIntrinsics.java | 6 ++++-- core/src/main/java/org/teavm/runtime/LaxMalloc.java | 9 +++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java index 9aa6f2ff6..21cc3a764 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/LaxMallocIntrinsic.java @@ -17,7 +17,6 @@ package org.teavm.backend.wasm.intrinsics.gc; import java.util.ArrayList; import java.util.List; - import org.teavm.ast.InvocationExpr; import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmInt32Constant; @@ -72,12 +71,12 @@ public class LaxMallocIntrinsic implements WasmGCIntrinsic { } public void setHeapLocation(int heapLoc) { - for(LaxMallocHeapMapper mapper : addressList) { + for (LaxMallocHeapMapper mapper : addressList) { mapper.setHeapLocation(heapLoc); } } - private static interface LaxMallocHeapMapper { + private interface LaxMallocHeapMapper { void setHeapLocation(int heapLoc); } diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java index 2d47b68ca..348ee01d0 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java @@ -178,8 +178,10 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { add(new MethodReference(DirectMalloc.class, "malloc", int.class, Address.class), intrinsic); add(new MethodReference(DirectMalloc.class, "calloc", int.class, Address.class), intrinsic); add(new MethodReference(DirectMalloc.class, "free", Address.class, void.class), intrinsic); - add(new MethodReference(DirectMalloc.class, "memcpy", Address.class, Address.class, int.class, void.class), intrinsic); - add(new MethodReference(DirectMalloc.class, "memset", Address.class, int.class, int.class, void.class), intrinsic); + add(new MethodReference(DirectMalloc.class, "memcpy", Address.class, Address.class, int.class, void.class), + intrinsic); + add(new MethodReference(DirectMalloc.class, "memset", Address.class, int.class, int.class, void.class), + intrinsic); add(new MethodReference(DirectMalloc.class, "zmemset", Address.class, int.class, void.class), intrinsic); } diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 6f77791c4..ea9480391 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -173,7 +173,8 @@ public final class LaxMalloc { if (bucketMask != 0L) { // there is a bucket with a larger chunk int availableLargerBucket = Long.numberOfTrailingZeros(bucketMask); - Address largerBucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START).add(availableLargerBucket << SIZEOF_PTR_SH); + Address largerBucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START) + .add(availableLargerBucket << SIZEOF_PTR_SH); Address largerChunkPtr = largerBucketStartAddr.getAddress(); int largerChunkSize = readChunkSizeStatus(largerChunkPtr); @@ -279,7 +280,7 @@ public final class LaxMalloc { Address ret = chunkPtr.add(4); // clear if requested - if(cleared) { + if (cleared) { DirectMalloc.zmemset(ret, sizeBytes); } @@ -418,7 +419,7 @@ public final class LaxMalloc { Address bucketStartAddr = addrHeap(ADDR_HEAP_BUCKETS_START).add(bucket << SIZEOF_PTR_SH); // test the bucket mask if the bucket is empty - if ((bucketMask & (1L << bucket)) == 0l) { + if ((bucketMask & (1L << bucket)) == 0L) { // bucket is empty, add the free chunk to the list bucketStartAddr.putAddress(chunkPtr); @@ -426,7 +427,7 @@ public final class LaxMalloc { writeChunkNextFreeAddr(chunkPtr, chunkPtr); // set the free bit in bucket mask - bucketMask |= (1L << bucket); + bucketMask |= 1L << bucket; addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).putLong(bucketMask); } else { From d86ab0884337153c366275d2dd6815e526bbe99a Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 2 Nov 2024 21:44:51 -0700 Subject: [PATCH 21/33] checkstyle my ass --- .../java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java index 356cd8d32..ff47d7381 100644 --- a/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java +++ b/tools/gradle/src/main/java/org/teavm/gradle/api/TeaVMWasmGCConfiguration.java @@ -17,7 +17,8 @@ package org.teavm.gradle.api; import org.gradle.api.provider.Property; -public interface TeaVMWasmGCConfiguration extends TeaVMCommonConfiguration, TeaVMWebConfiguration, TeaVMNativeBaseConfiguration { +public interface TeaVMWasmGCConfiguration extends TeaVMCommonConfiguration, TeaVMWebConfiguration, + TeaVMNativeBaseConfiguration { Property getObfuscated(); Property getStrict(); From e204a0ecd0cedf4cd68e9228a4fab358e246cab6 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sun, 3 Nov 2024 14:12:00 -0800 Subject: [PATCH 22/33] Got it to compile to WASM --- .../backend/wasm/generate/gc/classes/WasmGCTypeMapper.java | 3 ++- .../teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java index 3f9aeaa3b..6bc07335e 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java @@ -129,7 +129,8 @@ public class WasmGCTypeMapper { } if (result == null) { if (className.equals(Address.class.getName())) { - typeCache.put(className, WasmType.INT32); + result = WasmType.INT32; + typeCache.put(className, result); } else { var cls = classes.get(className); if (cls == null) { diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java index 348ee01d0..4276a79c0 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java @@ -187,9 +187,8 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { private void fillLaxMalloc() { laxMallocIntrinsic = new LaxMallocIntrinsic(); - add(new MethodReference(LaxMalloc.class, "laxMalloc", int.class, Address.class), laxMallocIntrinsic); - add(new MethodReference(LaxMalloc.class, "laxCalloc", int.class, Address.class), laxMallocIntrinsic); - add(new MethodReference(LaxMalloc.class, "laxFree", Address.class, void.class), laxMallocIntrinsic); + add(new MethodReference(LaxMalloc.class, "addrHeap", int.class, Address.class), laxMallocIntrinsic); + add(new MethodReference(LaxMalloc.class, "growHeapOuter", int.class, int.class), laxMallocIntrinsic); add(new MethodReference(LaxMalloc.class, "getHeapMinAddr", Address.class), laxMallocIntrinsic); add(new MethodReference(LaxMalloc.class, "getHeapMaxAddr", Address.class), laxMallocIntrinsic); } From 841e09cbd5a880fd1a0a3287913f2de056e082c4 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sun, 3 Nov 2024 15:26:00 -0800 Subject: [PATCH 23/33] Fix static init for LaxMalloc class --- .../org/teavm/backend/wasm/WasmGCTarget.java | 12 +++++- .../backend/wasm/gc/WasmGCDependencies.java | 1 + .../gc/LaxMallocInitializerContributor.java | 38 +++++++++++++++++++ .../gc/WasmGCDeclarationsGenerator.java | 4 ++ .../java/org/teavm/runtime/LaxMalloc.java | 3 ++ 5 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/generate/gc/LaxMallocInitializerContributor.java diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index 224a410df..e8e54d178 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -33,6 +33,7 @@ import org.teavm.backend.wasm.gc.TeaVMWasmGCHost; import org.teavm.backend.wasm.gc.WasmGCClassConsumer; import org.teavm.backend.wasm.gc.WasmGCClassConsumerContext; import org.teavm.backend.wasm.gc.WasmGCDependencies; +import org.teavm.backend.wasm.generate.gc.LaxMallocInitializerContributor; import org.teavm.backend.wasm.generate.gc.WasmGCDeclarationsGenerator; import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCCustomTypeMapperFactory; @@ -77,6 +78,7 @@ import org.teavm.model.transformation.BoundCheckInsertion; import org.teavm.model.transformation.NullCheckFilter; import org.teavm.model.transformation.NullCheckInsertion; import org.teavm.model.util.VariableCategoryProvider; +import org.teavm.runtime.LaxMalloc; import org.teavm.vm.BuildTarget; import org.teavm.vm.TeaVMTarget; import org.teavm.vm.TeaVMTargetController; @@ -301,6 +303,14 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { refQueueSupplyFunction.setExportName("teavm.reportGarbageCollectedValue"); } + if(enableDirectMallocSupport) { + var laxMallocClinitRef = new MethodReference(LaxMalloc.class, "", void.class); + if (controller.getDependencyInfo().getMethod(laxMallocClinitRef) != null) { + var laxMallocClinit = declarationsGenerator.functions().forStaticMethod(laxMallocClinitRef); + declarationsGenerator.addEarlyInitializerContributor(new LaxMallocInitializerContributor(laxMallocClinit)); + } + } + moduleGenerator.generate(); customGenerators.contributeToModule(module); generateExceptionExports(declarationsGenerator); @@ -311,7 +321,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { heapSegment.setOffset(WasmRuntime.align(lastSegment.getOffset() + lastSegment.getLength(), WasmHeap.PAGE_SIZE)); } - heapSegment.setLength(directMallocMinHeapSize); + heapSegment.setLength(WasmHeap.PAGE_SIZE); module.getSegments().add(heapSegment); intrinsics.setupLaxMallocHeap(heapSegment.getOffset(), heapSegment.getOffset() + directMallocMinHeapSize, heapSegment.getOffset() + directMallocMaxHeapSize); diff --git a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java index e491f256a..2e9957803 100644 --- a/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java +++ b/core/src/main/java/org/teavm/backend/wasm/gc/WasmGCDependencies.java @@ -131,6 +131,7 @@ public class WasmGCDependencies { } public void contributeDirectMalloc() { + analyzer.linkMethod(new MethodReference(LaxMalloc.class, "", void.class)).use(); analyzer.linkMethod(new MethodReference(LaxMalloc.class, "laxMalloc", int.class, Address.class)).use(); analyzer.linkMethod(new MethodReference(LaxMalloc.class, "laxCalloc", int.class, Address.class)).use(); analyzer.linkMethod(new MethodReference(LaxMalloc.class, "laxFree", Address.class, void.class)).use(); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/LaxMallocInitializerContributor.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/LaxMallocInitializerContributor.java new file mode 100644 index 000000000..5efac2b1a --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/LaxMallocInitializerContributor.java @@ -0,0 +1,38 @@ +/* + * Copyright 2024 lax1dude. + * + * 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.backend.wasm.generate.gc; + +import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.expression.WasmCall; + +public class LaxMallocInitializerContributor implements WasmGCInitializerContributor { + + private final WasmFunction clinit; + + public LaxMallocInitializerContributor(WasmFunction clinit) { + this.clinit = clinit; + } + + @Override + public void contributeToInitializerDefinitions(WasmFunction function) { + } + + @Override + public void contributeToInitializer(WasmFunction function) { + function.getBody().add(new WasmCall(clinit)); + } + +} diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java index 28433d47a..ca82f55f1 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java @@ -178,4 +178,8 @@ public class WasmGCDeclarationsGenerator { public void addToInitializer(Consumer contributor) { methodGenerator.getGenerationContext().addToInitializer(contributor); } + + public void addEarlyInitializerContributor(WasmGCInitializerContributor contributor) { + initializerContributors.add(contributor); + } } diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index ea9480391..c3867c258 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -80,6 +80,9 @@ public final class LaxMalloc { // initialize heap limit addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(addrHeap(ADDR_HEAP_DATA_START)); addrHeap(ADDR_HEAP_OUTER_LIMIT).putAddress(getHeapMinAddr()); + + //TODO: Need to handle error setting the heap to its initial size + growHeapOuter(getHeapMinAddr().toInt() >> 16); } /** From daf3a1c192be9c4dc06dd6b8410d51aa65734c23 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 9 Nov 2024 13:19:01 -0800 Subject: [PATCH 24/33] Fix null checks on Address objects (Unmanaged annotation) --- .../main/java/org/teavm/backend/wasm/WasmGCTarget.java | 8 +++++++- .../wasm/generate/gc/WasmGCDeclarationsGenerator.java | 3 +++ .../generate/gc/methods/WasmGCGenerationContext.java | 9 ++++++++- .../generate/gc/methods/WasmGCGenerationVisitor.java | 8 ++++++-- .../wasm/generate/gc/methods/WasmGCMethodGenerator.java | 7 ++++++- 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index e8e54d178..4c2ce9111 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -74,6 +74,8 @@ import org.teavm.model.ListableClassHolderSource; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.Program; +import org.teavm.model.lowlevel.Characteristics; +import org.teavm.model.lowlevel.LowLevelNullCheckFilter; import org.teavm.model.transformation.BoundCheckInsertion; import org.teavm.model.transformation.NullCheckFilter; import org.teavm.model.transformation.NullCheckInsertion; @@ -86,7 +88,8 @@ import org.teavm.vm.spi.TeaVMHostExtension; public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { private TeaVMTargetController controller; - private NullCheckInsertion nullCheckInsertion = new NullCheckInsertion(NullCheckFilter.EMPTY); + private Characteristics characteristics; + private NullCheckInsertion nullCheckInsertion; private BoundCheckInsertion boundCheckInsertion = new BoundCheckInsertion(); private boolean strict; private boolean obfuscated; @@ -179,6 +182,8 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { @Override public void setController(TeaVMTargetController controller) { this.controller = controller; + characteristics = new Characteristics(controller.getUnprocessedClassSource()); + nullCheckInsertion = new NullCheckInsertion(new LowLevelNullCheckFilter(characteristics)); } @Override @@ -267,6 +272,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { controller.getClassInitializerInfo(), controller.getDependencyInfo(), controller.getDiagnostics(), + characteristics, customGenerators, intrinsics, customTypeMapperFactories, diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java index ca82f55f1..aa8e64428 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java @@ -44,6 +44,7 @@ import org.teavm.model.analysis.ClassInitializerInfo; import org.teavm.model.analysis.ClassMetadataRequirements; import org.teavm.model.classes.TagRegistry; import org.teavm.model.classes.VirtualTableBuilder; +import org.teavm.model.lowlevel.Characteristics; public class WasmGCDeclarationsGenerator { public final ClassHierarchy hierarchy; @@ -61,6 +62,7 @@ public class WasmGCDeclarationsGenerator { ClassInitializerInfo classInitializerInfo, DependencyInfo dependencyInfo, Diagnostics diagnostics, + Characteristics characteristics, WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics, List customTypeMapperFactories, @@ -83,6 +85,7 @@ public class WasmGCDeclarationsGenerator { functionTypes, names, diagnostics, + characteristics, customGenerators, intrinsics, strict, diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java index 2a1c9395d..f3c5fa23e 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationContext.java @@ -43,6 +43,7 @@ import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.ListableClassReaderSource; import org.teavm.model.MethodReference; +import org.teavm.model.lowlevel.Characteristics; public class WasmGCGenerationContext implements BaseWasmGenerationContext { private WasmModule module; @@ -69,6 +70,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { private String entryPoint; private Consumer initializerContributors; private Diagnostics diagnostics; + private Characteristics characteristics; public WasmGCGenerationContext(WasmModule module, WasmGCVirtualTableProvider virtualTables, WasmGCTypeMapper typeMapper, WasmFunctionTypes functionTypes, ListableClassReaderSource classes, @@ -78,7 +80,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics, WasmGCNameProvider names, boolean strict, String entryPoint, Consumer initializerContributors, - Diagnostics diagnostics) { + Diagnostics diagnostics, Characteristics characteristics) { this.module = module; this.virtualTables = virtualTables; this.typeMapper = typeMapper; @@ -98,6 +100,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { this.entryPoint = entryPoint; this.initializerContributors = initializerContributors; this.diagnostics = diagnostics; + this.characteristics = characteristics; } public WasmGCClassInfoProvider classInfoProvider() { @@ -210,6 +213,10 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext { return diagnostics; } + public Characteristics characteristics() { + return characteristics; + } + public Collection getInterfaceImplementors(String className) { if (interfaceImplementors == null) { fillInterfaceImplementors(); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java index 64457d4e3..1bd528b1c 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java @@ -15,6 +15,8 @@ */ package org.teavm.backend.wasm.generate.gc.methods; +import static org.teavm.model.lowlevel.ExceptionHandlingUtil.isManagedMethodCall; + import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -97,6 +99,7 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { private WasmGCGenerationUtil generationUtil; private WasmType expectedType; private PreciseTypeInference types; + private boolean managed; public WasmGCGenerationVisitor(WasmGCGenerationContext context, MethodReference currentMethod, WasmFunction function, int firstVariable, boolean async, PreciseTypeInference types) { @@ -104,6 +107,7 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { this.context = context; generationUtil = new WasmGCGenerationUtil(context.classInfoProvider()); this.types = types; + managed = context.characteristics().isManaged(currentMethod); } @Override @@ -125,7 +129,7 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { @Override protected boolean isManaged() { - return true; + return managed; } @Override @@ -135,7 +139,7 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { @Override protected boolean isManagedCall(MethodReference method) { - return false; + return isManagedMethodCall(context.characteristics(), method); } @Override diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java index 6365d7cd1..2d946ad32 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCMethodGenerator.java @@ -61,6 +61,7 @@ import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; import org.teavm.model.analysis.ClassInitializerInfo; +import org.teavm.model.lowlevel.Characteristics; import org.teavm.model.util.RegisterAllocator; public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { @@ -74,6 +75,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { private WasmGCSupertypeFunctionProvider supertypeFunctions; public final WasmGCNameProvider names; private Diagnostics diagnostics; + private Characteristics characteristics; private WasmGCTypeMapper typeMapper; private WasmGCCustomGeneratorProvider customGenerators; private WasmGCIntrinsicProvider intrinsics; @@ -101,6 +103,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { WasmFunctionTypes functionTypes, WasmGCNameProvider names, Diagnostics diagnostics, + Characteristics characteristics, WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics, boolean strict, @@ -116,6 +119,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { this.functionTypes = functionTypes; this.names = names; this.diagnostics = diagnostics; + this.characteristics = characteristics; this.customGenerators = customGenerators; this.intrinsics = intrinsics; this.strict = strict; @@ -367,7 +371,8 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository { strict, entryPoint, initializerContributors, - diagnostics + diagnostics, + characteristics ); } return context; From a33f96b899555706d3bbd29047edc488cf4df6d5 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 9 Nov 2024 16:57:15 -0800 Subject: [PATCH 25/33] Fix initial heap grow --- core/src/main/java/org/teavm/runtime/LaxMalloc.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index c3867c258..88836caa1 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -63,7 +63,7 @@ public final class LaxMalloc { private static native Address addrHeap(int offset); // Intrinsic function to grow the heap segment - private static native int growHeapOuter(int bytes); + private static native int growHeapOuter(int chunks); // Intrinsic function to get the minimum direct malloc heap segment ending address private static native Address getHeapMinAddr(); @@ -81,8 +81,11 @@ public final class LaxMalloc { addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(addrHeap(ADDR_HEAP_DATA_START)); addrHeap(ADDR_HEAP_OUTER_LIMIT).putAddress(getHeapMinAddr()); - //TODO: Need to handle error setting the heap to its initial size - growHeapOuter(getHeapMinAddr().toInt() >> 16); + int initialGrowAmount = getHeapMinAddr().toInt() >>> 16 - 1; + if (initialGrowAmount > 0) { + //TODO: Need to handle error setting the heap to its initial size + growHeapOuter(initialGrowAmount); + } } /** From 851c09fecbd085a19c568cf0d638eb269cbf97c5 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 9 Nov 2024 17:31:38 -0800 Subject: [PATCH 26/33] Fix growHeap --- core/src/main/java/org/teavm/runtime/LaxMalloc.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 88836caa1..ebf329e03 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -526,13 +526,13 @@ public final class LaxMalloc { addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); addrHeap(ADDR_HEAP_OUTER_LIMIT).putAddress(newHeapOuterLimit); notifyHeapResized(); - return newHeapInnerLimit; + return heapInnerLimit; } else { return Address.fromInt(0); } } else { addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); - return newHeapInnerLimit; + return heapInnerLimit; } } From 07728cb10a617c0d298e81bc17c075cbde0ad906 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 9 Nov 2024 17:58:19 -0800 Subject: [PATCH 27/33] Fix defragmentation --- core/src/main/java/org/teavm/runtime/LaxMalloc.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index ebf329e03..483b5c512 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -335,7 +335,7 @@ public final class LaxMalloc { // set the chunk no longer in use chunkSize &= 0x7FFFFFFF; - if (addrHeap(ADDR_HEAP_DATA_START).isLessThan(chunkPtr)) { + if (!chunkPtr.isLessThan(addrHeap(ADDR_HEAP_DATA_START))) { // check if we can merge with the previous chunk, and move it to another bucket Address prevChunkPtr = chunkPtr.add(-(chunkPtr.add(-4).getInt())); int prevChunkSize = readChunkSizeStatus(prevChunkPtr); @@ -353,7 +353,7 @@ public final class LaxMalloc { } Address nextChunkPtr = chunkPtr.add(chunkSize); - if (addrHeap(ADDR_HEAP_INNER_LIMIT).getAddress().isLessThan(nextChunkPtr)) { + if (nextChunkPtr.isLessThan(addrHeap(ADDR_HEAP_INNER_LIMIT).getAddress())) { // check if we can merge with the next chunk as well int nextChunkSize = readChunkSizeStatus(nextChunkPtr); if ((nextChunkSize & 0x80000000) == 0) { From 73148353eb68ad9b5c7cf6302f0294414b64826d Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 9 Nov 2024 18:46:59 -0800 Subject: [PATCH 28/33] Fix corruption issue --- core/src/main/java/org/teavm/runtime/LaxMalloc.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 483b5c512..2549ab32b 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -231,7 +231,7 @@ public final class LaxMalloc { allocateMemoryFromChunk(addrIterator, chunkSize, sizeBytes); // +4 bytes to skip size int - Address ret = chunkPtr.add(4); + Address ret = addrIterator.add(4); // clear if requested if (cleared) { From 07293886db346a39cb956761d55dbca18a55e388 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 9 Nov 2024 19:40:42 -0800 Subject: [PATCH 29/33] Fix out of bounds access issue --- .../java/org/teavm/runtime/LaxMalloc.java | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index 2549ab32b..d6216e230 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -335,20 +335,22 @@ public final class LaxMalloc { // set the chunk no longer in use chunkSize &= 0x7FFFFFFF; - if (!chunkPtr.isLessThan(addrHeap(ADDR_HEAP_DATA_START))) { - // check if we can merge with the previous chunk, and move it to another bucket + if (addrHeap(ADDR_HEAP_DATA_START).isLessThan(chunkPtr)) { Address prevChunkPtr = chunkPtr.add(-(chunkPtr.add(-4).getInt())); - int prevChunkSize = readChunkSizeStatus(prevChunkPtr); - if ((prevChunkSize & 0x80000000) == 0) { - // previous chunk is not in use, merge! - - // remove the previous chunk from its list - unlinkChunkFromFreeList(prevChunkPtr, prevChunkSize); - - // resize the current chunk to also contain the previous chunk - chunkPtr = prevChunkPtr; - chunkSize += prevChunkSize; - sizeChanged = true; + if (!prevChunkPtr.isLessThan(addrHeap(ADDR_HEAP_DATA_START))) { + // check if we can merge with the previous chunk, and move it to another bucket + int prevChunkSize = readChunkSizeStatus(prevChunkPtr); + if ((prevChunkSize & 0x80000000) == 0) { + // previous chunk is not in use, merge! + + // remove the previous chunk from its list + unlinkChunkFromFreeList(prevChunkPtr, prevChunkSize); + + // resize the current chunk to also contain the previous chunk + chunkPtr = prevChunkPtr; + chunkSize += prevChunkSize; + sizeChanged = true; + } } } @@ -522,7 +524,7 @@ public final class LaxMalloc { int bytesNeeded = newHeapInnerLimit.toInt() - heapOuterLimit.toInt(); bytesNeeded = (bytesNeeded + 0xFFFF) & 0xFFFF0000; Address newHeapOuterLimit = heapOuterLimit.add(bytesNeeded); - if (!getHeapMaxAddr().isLessThan(newHeapOuterLimit) && growHeapOuter(bytesNeeded >> 16) != -1) { + if (!getHeapMaxAddr().isLessThan(newHeapOuterLimit) && growHeapOuter(bytesNeeded >>> 16) != -1) { addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); addrHeap(ADDR_HEAP_OUTER_LIMIT).putAddress(newHeapOuterLimit); notifyHeapResized(); From 2fcaaa531358bd83bcb179e6673b024440c8d8dd Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 9 Nov 2024 20:19:37 -0800 Subject: [PATCH 30/33] Fix max heap size and hugeAlloc --- core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java | 3 +-- core/src/main/java/org/teavm/runtime/LaxMalloc.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index 4c2ce9111..ec4d469da 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -439,8 +439,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { if (enableDirectMallocSupport) { var minPages = (memorySize - 1) / WasmHeap.PAGE_SIZE + 1; - var maxPages = (memorySize - directMallocMinHeapSize + directMallocMaxHeapSize - 1) - / WasmHeap.PAGE_SIZE + 1; + var maxPages = (memorySize + directMallocMaxHeapSize - WasmHeap.PAGE_SIZE - 1) / WasmHeap.PAGE_SIZE + 1; module.setMinMemorySize(minPages); module.setMaxMemorySize(maxPages); } else { diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index d6216e230..c924ebce8 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -283,7 +283,7 @@ public final class LaxMalloc { allocateMemoryFromChunk(addrIterator, chunkSize, sizeBytes); // +4 bytes to skip size int - Address ret = chunkPtr.add(4); + Address ret = addrIterator.add(4); // clear if requested if (cleared) { From 59f87c4f2cf3240cbfa67305a2033be8b9e956c4 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 9 Nov 2024 22:00:42 -0800 Subject: [PATCH 31/33] Fix a few more issues --- core/src/main/java/org/teavm/runtime/LaxMalloc.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index c924ebce8..e83ea2bda 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -71,7 +71,8 @@ public final class LaxMalloc { // Intrinsic function to get the maximum direct malloc heap segment ending address private static native Address getHeapMaxAddr(); - @Import(name = "teavm_notifyHeapResized") + // Function called to resize the JavaScript typed arrays wrapping the WebAssembly.Memory + @Import(name = "notifyHeapResized") private static native void notifyHeapResized(); static { @@ -129,7 +130,7 @@ public final class LaxMalloc { long bucketMask = addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); // mask away the buckets that we know are too small for this allocation - bucketMask = bucketMask & (0xFFFFFFFFFFFFFFFFL << bucket); + bucketMask &= 0xFFFFFFFFFFFFFFFFL << bucket; // there are no more buckets with free chunks // need to sbrk @@ -464,7 +465,7 @@ public final class LaxMalloc { private static void unlinkChunkFromFreeList(Address chunkPtr, int chunkSize) { Address prevChunkPtr = readChunkPrevFreeAddr(chunkPtr); Address nextChunkPtr = readChunkNextFreeAddr(chunkPtr); - if (prevChunkPtr.toInt() == nextChunkPtr.toInt()) { + if (prevChunkPtr.toInt() == chunkPtr.toInt() && nextChunkPtr.toInt() == chunkPtr.toInt()) { // chunk is the only one currently in its bucket int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints From f454f00ad4c046efbc67a417ca4b0523cdc865d2 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sat, 9 Nov 2024 22:02:05 -0800 Subject: [PATCH 32/33] Add heap dump function --- .../java/org/teavm/runtime/LaxMalloc.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index e83ea2bda..deffaffe6 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -578,4 +578,24 @@ public final class LaxMalloc { return a < b ? a : b; } +// @Import(name = "dumpHeapHelper") +// private static native void dumpHeapHelper(Address chunkStart, Address chunkEnd, int size, +// int free, Address endAddr); +// +// public static void heapDump() { +// Address curAddr = addrHeap(ADDR_HEAP_DATA_START); +// Address endAddr = addrHeap(ADDR_HEAP_INNER_LIMIT).getAddress(); +// while (curAddr.isLessThan(endAddr)) { +// int sizeStat = readChunkSizeStatus(curAddr); +// int size = sizeStat & 0x7FFFFFFF; +// int stat = sizeStat >>> 31; +// dumpHeapHelper(curAddr, curAddr.add(size), size, stat, endAddr); +// if (size == 0) { +// //NOTE: size 0 would be a bug +// return; +// } +// curAddr = curAddr.add(size); +// } +// } + } From f0e884113c68685bd2cb0a88b106ecbc9638e775 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Fri, 15 Nov 2024 00:39:47 -0800 Subject: [PATCH 33/33] fixes --- .../org/teavm/backend/wasm/WasmGCTarget.java | 14 ++--- .../java/org/teavm/runtime/LaxMalloc.java | 62 ++++++++++++++++--- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java index ec4d469da..94a6e92fe 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmGCTarget.java @@ -321,16 +321,14 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { customGenerators.contributeToModule(module); generateExceptionExports(declarationsGenerator); if (enableDirectMallocSupport) { - var heapSegment = new WasmMemorySegment(); + var heapSegmentStart = 0; if (!module.getSegments().isEmpty()) { var lastSegment = module.getSegments().get(module.getSegments().size() - 1); - heapSegment.setOffset(WasmRuntime.align(lastSegment.getOffset() - + lastSegment.getLength(), WasmHeap.PAGE_SIZE)); + heapSegmentStart = WasmRuntime.align(lastSegment.getOffset() + + lastSegment.getLength(), WasmHeap.PAGE_SIZE); } - heapSegment.setLength(WasmHeap.PAGE_SIZE); - module.getSegments().add(heapSegment); - intrinsics.setupLaxMallocHeap(heapSegment.getOffset(), heapSegment.getOffset() + directMallocMinHeapSize, - heapSegment.getOffset() + directMallocMaxHeapSize); + intrinsics.setupLaxMallocHeap(heapSegmentStart, heapSegmentStart + directMallocMinHeapSize, + heapSegmentStart + directMallocMaxHeapSize); } adjustModuleMemory(module); @@ -439,7 +437,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { if (enableDirectMallocSupport) { var minPages = (memorySize - 1) / WasmHeap.PAGE_SIZE + 1; - var maxPages = (memorySize + directMallocMaxHeapSize - WasmHeap.PAGE_SIZE - 1) / WasmHeap.PAGE_SIZE + 1; + var maxPages = minPages + (directMallocMaxHeapSize - 1) / WasmHeap.PAGE_SIZE + 1; module.setMinMemorySize(minPages); module.setMaxMemorySize(maxPages); } else { diff --git a/core/src/main/java/org/teavm/runtime/LaxMalloc.java b/core/src/main/java/org/teavm/runtime/LaxMalloc.java index deffaffe6..4f432e270 100644 --- a/core/src/main/java/org/teavm/runtime/LaxMalloc.java +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -76,17 +76,19 @@ public final class LaxMalloc { private static native void notifyHeapResized(); static { + int initialGrowAmount = getHeapMinAddr().toInt() >>> 16; + if (growHeapOuter(initialGrowAmount) == -1) { + initialGrowAmount = 1; + if (growHeapOuter(initialGrowAmount) == -1) { + //TODO: Handle failure to initialize fallback 64KiB heap + } + } + // zero out the control region DirectMalloc.zmemset(addrHeap(0), ADDR_HEAP_DATA_START); // initialize heap limit addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(addrHeap(ADDR_HEAP_DATA_START)); - addrHeap(ADDR_HEAP_OUTER_LIMIT).putAddress(getHeapMinAddr()); - - int initialGrowAmount = getHeapMinAddr().toInt() >>> 16 - 1; - if (initialGrowAmount > 0) { - //TODO: Need to handle error setting the heap to its initial size - growHeapOuter(initialGrowAmount); - } + addrHeap(ADDR_HEAP_OUTER_LIMIT).putAddress(addrHeap(initialGrowAmount << 16)); } /** @@ -136,7 +138,7 @@ public final class LaxMalloc { // need to sbrk if (bucketMask == 0L) { int sizePlusInts = sizeBytes + 8; // size + 2 ints - Address newChunk = growHeap(sizePlusInts); // sbrk + Address newChunk = growLastChunk(sizePlusInts); // Out of memory if (newChunk.toInt() == 0) { @@ -248,7 +250,7 @@ public final class LaxMalloc { // no other options, time to sbrk int sizePlusInts = sizeBytes + 8; // size + 2 ints - Address newChunk = growHeap(sizePlusInts); // sbrk + Address newChunk = growLastChunk(sizePlusInts); // Out of memory if (newChunk.toInt() == 0) { @@ -300,7 +302,7 @@ public final class LaxMalloc { // no free huge chunks found, time to sbrk int sizePlusInts = sizeBytes + 8; // size + 2 ints - Address newChunk = growHeap(sizePlusInts); // sbrk + Address newChunk = growLastChunk(sizePlusInts); // Out of memory if (newChunk.toInt() == 0) { @@ -514,6 +516,46 @@ public final class LaxMalloc { return bucketIndex; } + /** + * Removes the last chunk from the heap (if free), then grows the heap by amount minus + * the length of the last chunk, this shouldn't be called unless the program has failed + * to find a free chunk that is larger than the requested amount + */ + private static Address growLastChunk(int amount) { + Address lastAddr = addrHeap(ADDR_HEAP_INNER_LIMIT).getAddress(); + + // make sure it doesn't crash if the heap is empty + if (!addrHeap(ADDR_HEAP_DATA_START).isLessThan(lastAddr)) { + return growHeap(amount); + } + + // get the length and address of the last chunk + int lastLen = lastAddr.add(-4).getInt(); + Address lastChunk = lastAddr.add(-lastLen); + lastLen = lastChunk.getInt(); + + // check if the last chunk is free + if ((lastLen & 0x80000000) == 0) { + + // chunk is free, attempt to resize the heap first + // so errors can be handled + if (growHeap(amount - lastLen).toInt() == 0) { + // out of memory + return Address.fromInt(0); + } + + // unlink last chunk from free list + unlinkChunkFromFreeList(lastChunk, lastLen); + + // return the start of the last chunk + return lastChunk; + } else { + // no free chunk at the end of the heap + // just grow the heap by the full amount + return growHeap(amount); + } + } + /** * This is our sbrk */