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..94a6e92fe 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; @@ -47,6 +48,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; @@ -72,10 +74,13 @@ 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; 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; @@ -83,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; @@ -99,6 +105,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; + private int directMallocMinHeapSize = 0x10000; + private int directMallocMaxHeapSize = 0x10000000; public void setObfuscated(boolean obfuscated) { this.obfuscated = obfuscated; @@ -128,6 +137,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); @@ -161,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 @@ -194,6 +217,9 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { var deps = new WasmGCDependencies(dependencyAnalyzer); deps.contribute(); deps.contributeStandardExports(); + if (enableDirectMallocSupport) { + deps.contributeDirectMalloc(); + } } @Override @@ -246,6 +272,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost { controller.getClassInitializerInfo(), controller.getDependencyInfo(), controller.getDiagnostics(), + characteristics, customGenerators, intrinsics, customTypeMapperFactories, @@ -282,9 +309,27 @@ 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); + if (enableDirectMallocSupport) { + var heapSegmentStart = 0; + if (!module.getSegments().isEmpty()) { + var lastSegment = module.getSegments().get(module.getSegments().size() - 1); + heapSegmentStart = WasmRuntime.align(lastSegment.getOffset() + + lastSegment.getLength(), WasmHeap.PAGE_SIZE); + } + intrinsics.setupLaxMallocHeap(heapSegmentStart, heapSegmentStart + directMallocMinHeapSize, + heapSegmentStart + directMallocMaxHeapSize); + } adjustModuleMemory(module); emitWasmFile(module, buildTarget, outputName, debugInfoBuilder); @@ -390,9 +435,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 = minPages + (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..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 @@ -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,11 @@ public class WasmGCDependencies { private void contributeString() { analyzer.addDependencyListener(new StringInternDependencySupport()); } + + 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..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, @@ -178,4 +181,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/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java index 220d9fdb5..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 @@ -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,17 @@ public class WasmGCTypeMapper { } } if (result == null) { - var cls = classes.get(className); - if (cls == null) { - className = "java.lang.Object"; + if (className.equals(Address.class.getName())) { + result = WasmType.INT32; + typeCache.put(className, result); + } 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; 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; 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 new file mode 100644 index 000000000..6383308af --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/DirectMallocIntrinsic.java @@ -0,0 +1,84 @@ +/* + * 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.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.model.MethodReference; +import org.teavm.runtime.LaxMalloc; + +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, + Address.class); + private static final MethodReference LAX_FREE = new MethodReference(LaxMalloc.class, "laxFree", Address.class, + void.class); + + @Override + public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext manager) { + switch (invocation.getMethod().getName()) { + case "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.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.functions().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/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..21cc3a764 --- /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 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 00919cf13..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 @@ -25,15 +25,19 @@ 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; +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) { @@ -51,6 +55,9 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { fillArray(); fillString(); fillResources(); + fillDirectMalloc(); + fillLaxMalloc(); + fillAddress(); for (var entry : customIntrinsics.entrySet()) { add(entry.getKey(), entry.getValue()); } @@ -166,6 +173,58 @@ 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 fillLaxMalloc() { + laxMallocIntrinsic = new 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); + } + + 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)); } @@ -187,6 +246,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 new file mode 100644 index 000000000..4f432e270 --- /dev/null +++ b/core/src/main/java/org/teavm/runtime/LaxMalloc.java @@ -0,0 +1,643 @@ +/* + * 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.DirectMalloc; +import org.teavm.interop.Import; +import org.teavm.interop.StaticInit; +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 +@StaticInit +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; + + // Address where we store the WebAssembly.Memory limit (32 bit int) + private static final int ADDR_HEAP_OUTER_LIMIT = 0; + + // Address where we store the current heap limit (32 bit int) + private static final int ADDR_HEAP_INNER_LIMIT = 4; + + // Address where we store the bitmask of free chunk lists (64 bit int) + private static final int ADDR_HEAP_BUCKETS_FREE_MASK = 8; + + // Address to the list of 64 pointers to the beginnings of the 64 buckets + private static final int ADDR_HEAP_BUCKETS_START = 16; + + // 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 chunks); + + // 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(); + + // Function called to resize the JavaScript typed arrays wrapping the WebAssembly.Memory + @Import(name = "notifyHeapResized") + 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(addrHeap(initialGrowAmount << 16)); + } + + /** + * 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 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 = addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); + + // mask away the buckets that we know are too small for this allocation + 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 = growLastChunk(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); + } + + // at least one bucket exists containing a free chunk, + // quickly determine which bucket it is with bit hacks + int availableBucket = Long.numberOfTrailingZeros(bucketMask); + + 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); + + // 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 &= 0xFFFFFFFFFFFFFFFFL << (bucket + 1); + + 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 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) { + DirectMalloc.zmemset(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) { + DirectMalloc.zmemset(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 = addrIterator.add(4); + + // clear if requested + if (cleared) { + DirectMalloc.zmemset(ret, sizeBytes); + } + + return ret; + } + addrIterator = readChunkNextFreeAddr(addrIterator); + } while (addrIterator.getInt() != chunkPtr.getInt()); + } + + // no other options, time to sbrk + + int sizePlusInts = sizeBytes + 8; // size + 2 ints + Address newChunk = growLastChunk(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); + } + + 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) { + + // bucket 63 address + Address bucketStartAddr = addrHeap(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 = addrIterator.add(4); + + // clear if requested + if (cleared) { + DirectMalloc.zmemset(ret, sizeBytes); + } + + return ret; + } + addrIterator = readChunkNextFreeAddr(addrIterator); + } while (addrIterator.getInt() != chunkPtr.getInt()); + } + + // no free huge chunks found, time to sbrk + + int sizePlusInts = sizeBytes + 8; // size + 2 ints + Address newChunk = growLastChunk(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); + } + + /** + * 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 (addrHeap(ADDR_HEAP_DATA_START).isLessThan(chunkPtr)) { + Address prevChunkPtr = chunkPtr.add(-(chunkPtr.add(-4).getInt())); + 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; + } + } + } + + Address nextChunkPtr = chunkPtr.add(chunkSize); + 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) { + // 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); + } + + /** + * 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 + 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 = 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) { + + // 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; + addrHeap(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() == chunkPtr.toInt() && nextChunkPtr.toInt() == chunkPtr.toInt()) { + // chunk is the only one currently in its bucket + + int chunkBucket = getListBucket(chunkSize - 8); // size - 2 ints + + 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 = addrHeap(ADDR_HEAP_BUCKETS_FREE_MASK).getLong(); + bucketsFreeMask &= ~(1L << chunkBucket); + addrHeap(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 = 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 + // 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 = Integer.numberOfLeadingZeros(allocSize); + int bucketIndex = (clz > 19) ? 110 - (clz << 2) + ((allocSize >> (29 - clz)) ^ 4) + : min(71 - (clz << 1) + ((allocSize >> (30 - clz)) ^ 2), 63); + + 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 + */ + private static Address growHeap(int amount) { + 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; + 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 heapInnerLimit; + } else { + return Address.fromInt(0); + } + } else { + addrHeap(ADDR_HEAP_INNER_LIMIT).putAddress(newHeapInnerLimit); + return heapInnerLimit; + } + } + + /** + * 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 boolean readChunkInUse(Address chunkAddr) { + return (chunkAddr.getInt() & 0x80000000) != 0; + } + + private static void writeChunkSizeStatus(Address chunkAddr, int sizeStatus) { + chunkAddr.putInt(sizeStatus); + } + + 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; + } + +// @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); +// } +// } + +} 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..b605abb92 --- /dev/null +++ b/interop/core/src/main/java/org/teavm/interop/DirectMalloc.java @@ -0,0 +1,52 @@ +/* + * 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 final class DirectMalloc { + + private 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); + +} 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..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,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; 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..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 { +public interface TeaVMWasmGCConfiguration extends TeaVMCommonConfiguration, TeaVMWebConfiguration, + TeaVMNativeBaseConfiguration { Property getObfuscated(); Property getStrict(); @@ -37,4 +38,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..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 @@ -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); @@ -34,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 @@ -58,6 +63,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 +100,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().get() * MB); + builder.setMaxHeapSize(getMaxHeapSize().get() * MB); } }