mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-21 16:04:09 -08:00
Merge branch 'wasm-gc-linear-memory' into eagler-r1
This commit is contained in:
commit
0d4da6e99a
|
@ -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<WasmGCCustomGeneratorFactory> customGeneratorFactories = new ArrayList<>();
|
||||
private EntryPointTransformation entryPointTransformation = new EntryPointTransformation();
|
||||
private List<WasmGCClassConsumer> 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, "<clinit>", 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,10 +435,17 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
|
|||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
GCDebugInfoBuilder debugInfoBuilder) throws IOException {
|
||||
|
|
|
@ -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, "<clinit>", 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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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<WasmGCCustomTypeMapperFactory> 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<WasmFunction> contributor) {
|
||||
methodGenerator.getGenerationContext().addToInitializer(contributor);
|
||||
}
|
||||
|
||||
public void addEarlyInitializerContributor(WasmGCInitializerContributor contributor) {
|
||||
initializerContributors.add(contributor);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,6 +128,10 @@ public class WasmGCTypeMapper {
|
|||
}
|
||||
}
|
||||
if (result == null) {
|
||||
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";
|
||||
|
@ -135,6 +140,7 @@ public class WasmGCTypeMapper {
|
|||
typeCache.put(className, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<WasmGCInitializerContributor> 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<WasmGCInitializerContributor> 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<String> getInterfaceImplementors(String className) {
|
||||
if (interfaceImplementors == null) {
|
||||
fillInterfaceImplementors();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<LaxMallocHeapMapper> addressList = new ArrayList<>();
|
||||
private final List<WasmInt32Constant> minAddrConstants = new ArrayList<>();
|
||||
private final List<WasmInt32Constant> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<MethodReference, IntrinsicContainer> intrinsics = new HashMap<>();
|
||||
private List<WasmGCIntrinsicFactory> factories;
|
||||
private ClassReaderSource classes;
|
||||
private ServiceRepository services;
|
||||
private LaxMallocIntrinsic laxMallocIntrinsic;
|
||||
|
||||
public WasmGCIntrinsics(ClassReaderSource classes, ServiceRepository services,
|
||||
List<WasmGCIntrinsicFactory> factories, Map<MethodReference, WasmGCIntrinsic> 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;
|
||||
|
||||
|
|
643
core/src/main/java/org/teavm/runtime/LaxMalloc.java
Normal file
643
core/src/main/java/org/teavm/runtime/LaxMalloc.java
Normal file
|
@ -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<br><br>
|
||||
*
|
||||
* DO NOT USE IN LEGACY WASM BACKEND!!! Make a regular byte array, and use Address.ofData()<br><br>
|
||||
*
|
||||
* Similar to dlmalloc and emmalloc (emscripten's malloc)<br><br>
|
||||
*
|
||||
* 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<br><br>
|
||||
*
|
||||
* 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);
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
|
@ -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<br><br>
|
||||
*
|
||||
* DO NOT USE IN LEGACY WASM BACKEND!!! Make a regular byte array, and use Address.ofData()<br><br>
|
||||
*
|
||||
* Similar to dlmalloc and emmalloc (emscripten's malloc)<br><br>
|
||||
*
|
||||
* 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);
|
||||
|
||||
}
|
|
@ -119,6 +119,7 @@ public class TeaVMTool {
|
|||
private Set<File> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -89,6 +89,8 @@ public interface BuildStrategy {
|
|||
|
||||
void setWasmDebugInfoLocation(WasmDebugInfoLocation wasmDebugInfoLocation);
|
||||
|
||||
void setDirectMallocSupport(boolean enable);
|
||||
|
||||
void setMinHeapSize(int minHeapSize);
|
||||
|
||||
void setMaxHeapSize(int maxHeapSize);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -59,4 +59,5 @@ public class RemoteBuildRequest implements Serializable {
|
|||
public boolean heapDump;
|
||||
public boolean shortFileNames;
|
||||
public boolean assertionsRemoved;
|
||||
public boolean directMallocSupport;
|
||||
}
|
||||
|
|
|
@ -231,6 +231,9 @@ public class TeaVMPlugin implements Plugin<Project> {
|
|||
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);
|
||||
});
|
||||
|
|
|
@ -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<Boolean> getObfuscated();
|
||||
|
||||
Property<Boolean> getStrict();
|
||||
|
@ -37,4 +38,6 @@ public interface TeaVMWasmGCConfiguration extends TeaVMCommonConfiguration, TeaV
|
|||
Property<SourceFilePolicy> getSourceFilePolicy();
|
||||
|
||||
Property<Boolean> getModularRuntime();
|
||||
|
||||
Property<Boolean> getDirectMallocSupport();
|
||||
}
|
||||
|
|
|
@ -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<SourceFilePolicy> getSourceFilePolicy();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<Boolean> getDirectMallocSupport();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<Integer> getMinHeapSize();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<Integer> 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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user