Merge branch 'wasm-gc-linear-memory' into eagler-r1

This commit is contained in:
lax1dude 2024-11-30 12:56:35 -08:00
commit 0d4da6e99a
22 changed files with 1289 additions and 14 deletions

View File

@ -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 {

View File

@ -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();
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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();

View File

@ -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

View File

@ -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;

View File

@ -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());
}
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;

View 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);
// }
// }
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -89,6 +89,8 @@ public interface BuildStrategy {
void setWasmDebugInfoLocation(WasmDebugInfoLocation wasmDebugInfoLocation);
void setDirectMallocSupport(boolean enable);
void setMinHeapSize(int minHeapSize);
void setMaxHeapSize(int maxHeapSize);

View File

@ -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);

View File

@ -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;

View File

@ -59,4 +59,5 @@ public class RemoteBuildRequest implements Serializable {
public boolean heapDump;
public boolean shortFileNames;
public boolean assertionsRemoved;
public boolean directMallocSupport;
}

View File

@ -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);
});

View File

@ -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();
}

View File

@ -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);
}
}