From f48c24283cb7078e5dbfb9b7e932dc85a3200173 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 23 Jul 2024 20:50:05 +0200 Subject: [PATCH] wasm: implement transformation from TeaVM tree IR to Wasm GC --- .../java/org/teavm/backend/c/CTarget.java | 6 +- .../backend/javascript/JavaScriptTarget.java | 6 +- .../org/teavm/backend/wasm/WasmTarget.java | 6 +- .../wasm/generate/WasmGenerationVisitor.java | 59 ++- .../methods/BaseWasmGenerationVisitor.java | 55 ++- .../generate/gc/WasmGCGenerationContext.java | 130 ++++++ .../gc/classes/WasmGCClassGenerator.java | 26 +- .../generate/gc/classes/WasmGCClassInfo.java | 5 + .../gc/classes/WasmGCClassInfoProvider.java | 10 + .../gc/methods/WasmGCGenerationVisitor.java | 381 ++++++++++++++++++ .../backend/wasm/runtime/WasmGCSupport.java | 33 ++ .../model/analysis/BaseTypeInference.java | 9 + .../util/DefaultVariableCategoryProvider.java | 52 +++ .../org/teavm/model/util/GraphColorer.java | 16 +- .../teavm/model/util/RegisterAllocator.java | 38 +- .../model/util/VariableCategoryProvider.java | 23 ++ core/src/main/java/org/teavm/vm/TeaVM.java | 5 +- .../main/java/org/teavm/vm/TeaVMTarget.java | 3 +- 18 files changed, 768 insertions(+), 95 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCGenerationContext.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java create mode 100644 core/src/main/java/org/teavm/backend/wasm/runtime/WasmGCSupport.java create mode 100644 core/src/main/java/org/teavm/model/util/DefaultVariableCategoryProvider.java create mode 100644 core/src/main/java/org/teavm/model/util/VariableCategoryProvider.java diff --git a/core/src/main/java/org/teavm/backend/c/CTarget.java b/core/src/main/java/org/teavm/backend/c/CTarget.java index 04b98b967..dd981a6da 100644 --- a/core/src/main/java/org/teavm/backend/c/CTarget.java +++ b/core/src/main/java/org/teavm/backend/c/CTarget.java @@ -127,6 +127,8 @@ import org.teavm.model.transformation.BoundCheckInsertion; import org.teavm.model.transformation.ClassPatch; import org.teavm.model.transformation.NullCheckInsertion; import org.teavm.model.util.AsyncMethodFinder; +import org.teavm.model.util.DefaultVariableCategoryProvider; +import org.teavm.model.util.VariableCategoryProvider; import org.teavm.runtime.Allocator; import org.teavm.runtime.CallSite; import org.teavm.runtime.CallSiteLocation; @@ -259,8 +261,8 @@ public class CTarget implements TeaVMTarget, TeaVMCHost { } @Override - public boolean requiresRegisterAllocation() { - return true; + public VariableCategoryProvider variableCategoryProvider() { + return new DefaultVariableCategoryProvider(); } @Override diff --git a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java index 6efb07223..8f099844b 100644 --- a/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java +++ b/core/src/main/java/org/teavm/backend/javascript/JavaScriptTarget.java @@ -98,6 +98,8 @@ import org.teavm.model.instructions.StringConstantInstruction; import org.teavm.model.transformation.BoundCheckInsertion; import org.teavm.model.transformation.NullCheckFilter; import org.teavm.model.transformation.NullCheckInsertion; +import org.teavm.model.util.DefaultVariableCategoryProvider; +import org.teavm.model.util.VariableCategoryProvider; import org.teavm.vm.BuildTarget; import org.teavm.vm.RenderingException; import org.teavm.vm.TeaVMTarget; @@ -223,8 +225,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost { } @Override - public boolean requiresRegisterAllocation() { - return true; + public VariableCategoryProvider variableCategoryProvider() { + return new DefaultVariableCategoryProvider(); } public void setStackTraceIncluded(boolean stackTraceIncluded) { diff --git a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java index a15cb682b..9f9340c0b 100644 --- a/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java +++ b/core/src/main/java/org/teavm/backend/wasm/WasmTarget.java @@ -166,6 +166,8 @@ import org.teavm.model.transformation.BoundCheckInsertion; import org.teavm.model.transformation.ClassPatch; import org.teavm.model.transformation.NullCheckInsertion; import org.teavm.model.util.AsyncMethodFinder; +import org.teavm.model.util.DefaultVariableCategoryProvider; +import org.teavm.model.util.VariableCategoryProvider; import org.teavm.runtime.Allocator; import org.teavm.runtime.EventQueue; import org.teavm.runtime.ExceptionHandling; @@ -236,8 +238,8 @@ public class WasmTarget implements TeaVMTarget, TeaVMWasmHost { } @Override - public boolean requiresRegisterAllocation() { - return true; + public VariableCategoryProvider variableCategoryProvider() { + return new DefaultVariableCategoryProvider(); } @Override diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java index ac75ff4e2..cbcd60e8d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmGenerationVisitor.java @@ -146,8 +146,10 @@ public class WasmGenerationVisitor extends BaseWasmGenerationVisitor { } @Override - protected WasmExpression generateThrowNPE() { - return new WasmCall(context.functions().forStaticMethod(THROW_NPE_METHOD)); + protected void generateThrowNPE(TextLocation location, List target) { + var call = new WasmCall(context.functions().forStaticMethod(THROW_NPE_METHOD)); + call.setLocation(location); + target.add(call); } @Override @@ -174,8 +176,17 @@ public class WasmGenerationVisitor extends BaseWasmGenerationVisitor { } @Override - protected WasmExpression catchException() { - return new WasmCall(context.functions().forStaticMethod(CATCH_METHOD)); + protected void catchException(TextLocation location, List target, WasmLocal local) { + var call = new WasmCall(context.functions().forStaticMethod(CATCH_METHOD)); + if (local != null) { + var save = new WasmSetLocal(local, call); + save.setLocation(location); + target.add(save); + } else { + var drop = new WasmDrop(call); + drop.setLocation(location); + target.add(drop); + } } @Override @@ -494,8 +505,10 @@ public class WasmGenerationVisitor extends BaseWasmGenerationVisitor { } @Override - protected WasmExpression generateThrow(WasmExpression expression) { - return new WasmCall(context.functions().forStaticMethod(THROW_METHOD), result); + protected void generateThrow(WasmExpression expression, TextLocation location, List target) { + var call = new WasmCall(context.functions().forStaticMethod(THROW_METHOD), result); + call.setLocation(location); + target.add(call); } private class CallSiteIdentifierImpl extends CallSiteIdentifier @@ -745,18 +758,24 @@ public class WasmGenerationVisitor extends BaseWasmGenerationVisitor { } @Override - protected WasmExpression allocateObject(String className, TextLocation location) { + protected void allocateObject(String className, TextLocation location, WasmLocal local, + List target) { int tag = classGenerator.getClassPointer(ValueType.object(className)); var allocFunction = context.functions().forStaticMethod(new MethodReference(Allocator.class, "allocate", RuntimeClass.class, Address.class)); WasmCall call = new WasmCall(allocFunction); call.getArguments().add(new WasmInt32Constant(tag)); call.setLocation(location); - return call; + if (local != null) { + target.add(new WasmSetLocal(local, call)); + } else { + target.add(call); + } } @Override - protected WasmExpression allocateArray(ValueType itemType, WasmExpression length, TextLocation location) { + protected void allocateArray(ValueType itemType, WasmExpression length, TextLocation location, WasmLocal local, + List target) { int classPointer = classGenerator.getClassPointer(ValueType.arrayOf(itemType)); var allocFunction = context.functions().forStaticMethod(new MethodReference(Allocator.class, "allocateArray", RuntimeClass.class, int.class, Address.class)); @@ -764,7 +783,11 @@ public class WasmGenerationVisitor extends BaseWasmGenerationVisitor { call.getArguments().add(new WasmInt32Constant(classPointer)); call.getArguments().add(length); call.setLocation(location); - return call; + if (local != null) { + target.add(new WasmSetLocal(local, call)); + } else { + target.add(call); + } } @Override @@ -822,8 +845,10 @@ public class WasmGenerationVisitor extends BaseWasmGenerationVisitor { } @Override - protected WasmExpression generateThrowCCE() { - return new WasmCall(context.functions().forStaticMethod(THROW_CCE_METHOD)); + protected void generateThrowCCE(TextLocation location, List target) { + var call = new WasmCall(context.functions().forStaticMethod(THROW_CCE_METHOD)); + call.setLocation(location); + target.add(call); } @Override @@ -834,8 +859,8 @@ public class WasmGenerationVisitor extends BaseWasmGenerationVisitor { } @Override - protected boolean needsClassInitializer(String clasName) { - return classGenerator.hasClinit(clasName); + protected boolean needsClassInitializer(String className) { + return classGenerator.hasClinit(className); } @Override @@ -928,8 +953,10 @@ public class WasmGenerationVisitor extends BaseWasmGenerationVisitor { } @Override - protected WasmExpression generateThrowAIOOBE() { - return new WasmCall(context.functions().forStaticMethod(THROW_AIOOBE_METHOD)); + protected void generateThrowAIOOBE(TextLocation location, List target) { + var call = new WasmCall(context.functions().forStaticMethod(THROW_AIOOBE_METHOD)); + call.setLocation(location); + target.add(call); } private WasmIntrinsicManager intrinsicManager = new WasmIntrinsicManager() { diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java index 2fb2c0f29..f8079aa44 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java @@ -125,7 +125,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp private Map continueTargets = new HashMap<>(); private Set usedBlocks = new HashSet<>(); protected final TemporaryVariablePool tempVars; - private ExpressionCache exprCache; + protected final ExpressionCache exprCache; private boolean async; protected WasmExpression result; @@ -459,14 +459,14 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp var callSiteId = generateCallSiteId(location); callSiteId.generateRegister(block.getBody(), location); - block.getBody().add(generateThrowNPE()); + generateThrowNPE(location, block.getBody()); callSiteId.generateThrow(block.getBody(), location); cachedValue.release(); return block; } - protected abstract WasmExpression generateThrowNPE(); + protected abstract void generateThrowNPE(TextLocation location, List target); protected abstract WasmExpression generateArrayLength(WasmExpression array); @@ -852,8 +852,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp block.setType(resultType); var tmp = tempVars.acquire(resultType); - block.getBody().add(new WasmSetLocal(tmp, allocateObject(expr.getMethod().getClassName(), - expr.getLocation()))); + allocateObject(expr.getMethod().getClassName(), expr.getLocation(), tmp, block.getBody()); var function = context.functions().forInstanceMethod(expr.getMethod()); var call = new WasmCall(function); @@ -876,7 +875,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp accept(expr.getArguments().get(0)); var instance = result; var block = new WasmBlock(false); - block.setType(WasmGeneratorUtil.mapType(reference.getReturnType())); + block.setType(mapType(reference.getReturnType())); var instanceVar = tempVars.acquire(WasmType.INT32); block.getBody().add(new WasmSetLocal(instanceVar, instance)); @@ -961,7 +960,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp block.setType(mapType(ValueType.object(expr.getConstructedClass()))); var callSiteId = generateCallSiteId(expr.getLocation()); callSiteId.generateRegister(block.getBody(), expr.getLocation()); - block.getBody().add(allocateObject(expr.getConstructedClass(), expr.getLocation())); + allocateObject(expr.getConstructedClass(), expr.getLocation(), null, block.getBody()); if (block.getBody().size() == 1) { result = block.getBody().get(0); } else { @@ -969,7 +968,8 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp } } - protected abstract WasmExpression allocateObject(String className, TextLocation location); + protected abstract void allocateObject(String className, TextLocation location, WasmLocal local, + List target); @Override public void visit(NewArrayExpr expr) { @@ -981,8 +981,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp accept(expr.getLength()); var length = result; - var call = allocateArray(expr.getType(), length, expr.getLocation()); - block.getBody().add(call); + allocateArray(expr.getType(), length, expr.getLocation(), null, block.getBody()); if (block.getBody().size() == 1) { result = block.getBody().get(0); @@ -991,7 +990,8 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp } } - protected abstract WasmExpression allocateArray(ValueType itemType, WasmExpression length, TextLocation location); + protected abstract void allocateArray(ValueType itemType, WasmExpression length, TextLocation location, + WasmLocal local, List target); protected abstract WasmExpression allocateMultiArray(List target, ValueType itemType, List dimensions, TextLocation location); @@ -1035,9 +1035,8 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp callSiteId.generateRegister(block.getBody(), expr.getLocation()); var array = tempVars.acquire(wasmArrayType); - var allocation = allocateArray(expr.getType(), new WasmInt32Constant(expr.getData().size()), - expr.getLocation()); - block.getBody().add(new WasmSetLocal(array, allocation)); + allocateArray(expr.getType(), new WasmInt32Constant(expr.getData().size()), expr.getLocation(), array, + block.getBody()); for (int i = 0; i < expr.getData().size(); ++i) { expr.getData().get(i).acceptVisitor(this); @@ -1117,13 +1116,12 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp callSiteId.generateRegister(resultConsumer, statement.getLocation()); accept(statement.getException()); - var call = generateThrow(result); - call.setLocation(statement.getLocation()); - resultConsumer.add(call); + generateThrow(result, statement.getLocation(), resultConsumer); callSiteId.generateThrow(resultConsumer, statement.getLocation()); } - protected abstract WasmExpression generateThrow(WasmExpression expression); + protected abstract void generateThrow(WasmExpression expression, TextLocation location, + List target); @Override public void visit(CastExpr expr) { @@ -1149,7 +1147,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp var callSiteId = generateCallSiteId(expr.getLocation()); callSiteId.generateRegister(block.getBody(), expr.getLocation()); - block.getBody().add(generateThrowCCE()); + generateThrowCCE(expr.getLocation(), block.getBody()); callSiteId.generateThrow(block.getBody(), expr.getLocation()); valueToCast.release(); @@ -1168,7 +1166,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp } } - protected abstract boolean needsClassInitializer(String clasName); + protected abstract boolean needsClassInitializer(String className); protected abstract WasmExpression generateClassInitializer(String className, TextLocation location); @@ -1245,11 +1243,10 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp var catchBlock = catchBlocks.get(i); catchBlock.getBody().add(currentBlock); - var catchCall = catchException(); - var catchWrapper = tryCatch.getExceptionVariable() != null - ? new WasmSetLocal(localVar(tryCatch.getExceptionVariable()), catchCall) - : new WasmDrop(catchCall); - catchBlock.getBody().add(catchWrapper); + var catchLocal = tryCatch.getExceptionVariable() != null + ? localVar(tryCatch.getExceptionVariable()) + : null; + catchException(null, catchBlock.getBody(), catchLocal); visitMany(tryCatch.getHandler(), catchBlock.getBody()); if (!catchBlock.isTerminating() && catchBlock != outerCatchBlock) { catchBlock.getBody().add(new WasmBreak(outerCatchBlock)); @@ -1262,7 +1259,7 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp protected abstract WasmExpression peekException(); - protected abstract WasmExpression catchException(); + protected abstract void catchException(TextLocation location, List target, WasmLocal local); private void visitMany(List statements, List target) { var oldTarget = resultConsumer; @@ -1346,15 +1343,15 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp var callSiteId = generateCallSiteId(expr.getLocation()); callSiteId.generateRegister(block.getBody(), expr.getLocation()); - block.getBody().add(generateThrowAIOOBE()); + generateThrowAIOOBE(expr.getLocation(), block.getBody()); callSiteId.generateThrow(block.getBody(), expr.getLocation()); result = block; } - protected abstract WasmExpression generateThrowAIOOBE(); + protected abstract void generateThrowAIOOBE(TextLocation location, List target); - protected abstract WasmExpression generateThrowCCE(); + protected abstract void generateThrowCCE(TextLocation location, List target); private static WasmExpression negate(WasmExpression expr) { if (expr instanceof WasmIntBinary) { diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCGenerationContext.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCGenerationContext.java new file mode 100644 index 000000000..f203827dd --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCGenerationContext.java @@ -0,0 +1,130 @@ +/* + * 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.generate.gc; + +import org.teavm.backend.wasm.WasmFunctionRepository; +import org.teavm.backend.wasm.WasmFunctionTypes; +import org.teavm.backend.wasm.generate.common.methods.BaseWasmGenerationContext; +import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; +import org.teavm.backend.wasm.generate.gc.classes.WasmGCStandardClasses; +import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper; +import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider; +import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmGlobal; +import org.teavm.backend.wasm.model.WasmModule; +import org.teavm.backend.wasm.model.WasmTag; +import org.teavm.backend.wasm.model.expression.WasmNullConstant; +import org.teavm.backend.wasm.runtime.WasmGCSupport; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.MethodReference; +import org.teavm.model.classes.VirtualTableProvider; + +public class WasmGCGenerationContext implements BaseWasmGenerationContext { + private WasmModule module; + private WasmGCClassInfoProvider classInfoProvider; + private WasmGCStandardClasses standardClasses; + private WasmGCStringProvider strings; + private VirtualTableProvider virtualTables; + private WasmGCTypeMapper typeMapper; + private WasmFunction npeMethod; + private WasmFunction aaiobeMethod; + private WasmFunction cceMethod; + private WasmGlobal exceptionGlobal; + + public WasmGCGenerationContext(WasmModule module, WasmGCClassInfoProvider classInfoProvider, + WasmGCStandardClasses standardClasses, WasmGCStringProvider strings, VirtualTableProvider virtualTables, + WasmGCTypeMapper typeMapper) { + this.module = module; + this.classInfoProvider = classInfoProvider; + this.standardClasses = standardClasses; + this.strings = strings; + this.virtualTables = virtualTables; + this.typeMapper = typeMapper; + } + + public WasmGCClassInfoProvider classInfoProvider() { + return classInfoProvider; + } + + public WasmGCStandardClasses standardClasses() { + return standardClasses; + } + + public WasmGCStringProvider strings() { + return strings; + } + + public VirtualTableProvider virtualTables() { + return virtualTables; + } + + public WasmGCTypeMapper typeMapper() { + return typeMapper; + } + + @Override + public WasmFunctionRepository functions() { + return null; + } + + @Override + public WasmFunctionTypes functionTypes() { + return null; + } + + @Override + public WasmTag getExceptionTag() { + return null; + } + + @Override + public ClassReaderSource classSource() { + return null; + } + + public WasmFunction npeMethod() { + if (npeMethod == null) { + npeMethod = functions().forStaticMethod(new MethodReference(WasmGCSupport.class, "npe", + NullPointerException.class)); + } + return npeMethod; + } + + public WasmFunction aaiobeMethod() { + if (aaiobeMethod == null) { + aaiobeMethod = functions().forStaticMethod(new MethodReference(WasmGCSupport.class, "aaiobe", + ArrayIndexOutOfBoundsException.class)); + } + return aaiobeMethod; + } + + public WasmFunction cceMethod() { + if (cceMethod == null) { + cceMethod = functions().forStaticMethod(new MethodReference(WasmGCSupport.class, "cce", + ClassCastException.class)); + } + return cceMethod; + } + + public WasmGlobal exceptionGlobal() { + if (exceptionGlobal == null) { + var type = classInfoProvider.getClassInfo("java.lang.Throwable").getType(); + exceptionGlobal = new WasmGlobal("teavm_thrown_exception", type, new WasmNullConstant(type)); + module.globals.add(exceptionGlobal); + } + return exceptionGlobal; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java index f347fcf0a..d67642ad8 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java @@ -54,6 +54,7 @@ import org.teavm.model.FieldReference; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; +import org.teavm.model.analysis.ClassInitializerInfo; import org.teavm.model.analysis.ClassMetadataRequirements; import org.teavm.model.classes.TagRegistry; import org.teavm.model.classes.VirtualTable; @@ -61,7 +62,7 @@ import org.teavm.model.classes.VirtualTableProvider; import org.teavm.model.util.ReflectionUtil; public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInitializerContributor { - public static final int CLASS_FIELD_OFFSET = 0; + private static final MethodDescriptor CLINIT_METHOD_DESC = new MethodDescriptor("", ValueType.VOID); private final WasmModule module; private ClassReaderSource classSource; @@ -73,6 +74,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit private Map classInfoMap = new LinkedHashMap<>(); private ObjectIntMap fieldIndexes = new ObjectIntHashMap<>(); private ObjectIntMap methodIndexes = new ObjectIntHashMap<>(); + private ClassInitializerInfo classInitializerInfo; public final WasmGCStringPool strings; public final WasmGCStandardClasses standardClasses; @@ -95,7 +97,8 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit public WasmGCClassGenerator(WasmModule module, ClassReaderSource classSource, WasmFunctionTypes functionTypes, TagRegistry tagRegistry, ClassMetadataRequirements metadataRequirements, VirtualTableProvider virtualTables, - WasmGCFunctionProvider functionProvider, NameProvider names) { + WasmGCFunctionProvider functionProvider, NameProvider names, + ClassInitializerInfo classInitializerInfo) { this.module = module; this.classSource = classSource; this.functionTypes = functionTypes; @@ -104,6 +107,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit this.virtualTables = virtualTables; this.functionProvider = functionProvider; this.names = names; + this.classInitializerInfo = classInitializerInfo; standardClasses = new WasmGCStandardClasses(this); strings = new WasmGCStringPool(standardClasses, module, functionProvider); supertypeGenerator = new WasmGCSupertypeFunctionGenerator(module, this, names, tagRegistry, functionTypes); @@ -128,6 +132,13 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit new WasmFunctionReference(supertypeFunction))); function.getBody().add(setClassField(classInfo, CLASS_FIELD_OFFSET, new WasmGetGlobal(classClass.pointer))); + if (classInfo.initializerPointer != null) { + var className = ((ValueType.Object) classInfo.getValueType()).getClassName(); + var initFunction = functionProvider.getStaticFunction(new MethodReference(className, + CLINIT_METHOD_DESC)); + function.getBody().add(new WasmSetGlobal(classInfo.initializerPointer, + new WasmFunctionReference(initFunction))); + } } } @@ -224,6 +235,14 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit } private void initRegularClass(WasmGCClassInfo classInfo, WasmStructure classStructure, String name) { + var cls = classSource.get(name); + if (classInitializerInfo.isDynamicInitializer(name)) { + if (cls != null && cls.getMethod(CLINIT_METHOD_DESC) != null) { + var clinitType = functionTypes.of(null); + classInfo.initializerPointer = new WasmGlobal(null, clinitType.getReference(), + new WasmNullConstant(clinitType.getReference())); + } + } classInfo.initializer = target -> { var ranges = tagRegistry.getRanges(name); int tag = ranges.stream().mapToInt(range -> range.lower).min().orElse(0); @@ -233,7 +252,6 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit var namePtr = strings.getStringConstant(name).global; target.add(setClassField(classInfo, classNameOffset, new WasmGetGlobal(namePtr))); } - var cls = classSource.get(name); if (cls != null) { if (metadataReg.simpleName() && cls.getSimpleName() != null) { var namePtr = strings.getStringConstant(cls.getSimpleName()).global; @@ -334,6 +352,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit ); } + @Override public int getFieldIndex(FieldReference fieldRef) { var result = fieldIndexes.getOrDefault(fieldRef, -1); if (result < 0) { @@ -342,6 +361,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit return result; } + @Override public int getVirtualMethodIndex(MethodReference methodRef) { var result = methodIndexes.getOrDefault(methodRef, -1); if (result < 0) { diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfo.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfo.java index 7b1d28c8c..3ac9d0fbf 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfo.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfo.java @@ -28,6 +28,7 @@ public class WasmGCClassInfo { WasmStructure structure; WasmStructure virtualTableStructure; WasmGlobal pointer; + WasmGlobal initializerPointer; Consumer> initializer; WasmGCClassInfo(ValueType valueType) { @@ -53,4 +54,8 @@ public class WasmGCClassInfo { public WasmGlobal getPointer() { return pointer; } + + public WasmGlobal getInitializerPointer() { + return initializerPointer; + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java index b29f80015..ac8201d32 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java @@ -15,11 +15,21 @@ */ package org.teavm.backend.wasm.generate.gc.classes; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodReference; import org.teavm.model.ValueType; public interface WasmGCClassInfoProvider { + int CLASS_FIELD_OFFSET = 0; + int MONITOR_FIELD_OFFSET = 1; + int ARRAY_DATA_FIELD_OFFSET = 2; + WasmGCClassInfo getClassInfo(ValueType type); + int getFieldIndex(FieldReference fieldRef); + + int getVirtualMethodIndex(MethodReference methodRef); + default WasmGCClassInfo getClassInfo(String name) { return getClassInfo(ValueType.object(name)); } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java new file mode 100644 index 000000000..29133f3d6 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java @@ -0,0 +1,381 @@ +/* + * 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.generate.gc.methods; + +import java.util.List; +import org.teavm.ast.ArrayType; +import org.teavm.ast.Expr; +import org.teavm.ast.InvocationExpr; +import org.teavm.ast.QualificationExpr; +import org.teavm.ast.SubscriptExpr; +import org.teavm.ast.UnwrapArrayExpr; +import org.teavm.backend.wasm.generate.common.methods.BaseWasmGenerationVisitor; +import org.teavm.backend.wasm.generate.gc.WasmGCGenerationContext; +import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; +import org.teavm.backend.wasm.model.WasmArray; +import org.teavm.backend.wasm.model.WasmFunction; +import org.teavm.backend.wasm.model.WasmFunctionType; +import org.teavm.backend.wasm.model.WasmLocal; +import org.teavm.backend.wasm.model.WasmStructure; +import org.teavm.backend.wasm.model.WasmType; +import org.teavm.backend.wasm.model.expression.WasmArrayGet; +import org.teavm.backend.wasm.model.expression.WasmArrayLength; +import org.teavm.backend.wasm.model.expression.WasmArrayNewDefault; +import org.teavm.backend.wasm.model.expression.WasmArraySet; +import org.teavm.backend.wasm.model.expression.WasmBlock; +import org.teavm.backend.wasm.model.expression.WasmCall; +import org.teavm.backend.wasm.model.expression.WasmCallReference; +import org.teavm.backend.wasm.model.expression.WasmCast; +import org.teavm.backend.wasm.model.expression.WasmExpression; +import org.teavm.backend.wasm.model.expression.WasmGetGlobal; +import org.teavm.backend.wasm.model.expression.WasmGetLocal; +import org.teavm.backend.wasm.model.expression.WasmNullConstant; +import org.teavm.backend.wasm.model.expression.WasmSetGlobal; +import org.teavm.backend.wasm.model.expression.WasmSetLocal; +import org.teavm.backend.wasm.model.expression.WasmStructGet; +import org.teavm.backend.wasm.model.expression.WasmStructNewDefault; +import org.teavm.backend.wasm.model.expression.WasmStructSet; +import org.teavm.backend.wasm.model.expression.WasmThrow; +import org.teavm.backend.wasm.model.expression.WasmUnreachable; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodReference; +import org.teavm.model.TextLocation; +import org.teavm.model.ValueType; + +public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { + private WasmGCGenerationContext context; + + public WasmGCGenerationVisitor(WasmGCGenerationContext context, WasmFunction function, + int firstVariable, boolean async) { + super(context, function, firstVariable, async); + this.context = context; + } + + @Override + protected boolean isManaged() { + return false; + } + + @Override + protected boolean isManagedCall(MethodReference method) { + return false; + } + + @Override + protected void generateThrowNPE(TextLocation location, List target) { + generateThrow(new WasmCall(context.npeMethod()), location, target); + } + + @Override + protected void generateThrowAIOOBE(TextLocation location, List target) { + generateThrow(new WasmCall(context.aaiobeMethod()), location, target); + } + + @Override + protected void generateThrowCCE(TextLocation location, List target) { + generateThrow(new WasmCall(context.cceMethod()), location, target); + } + + @Override + protected void generateThrow(WasmExpression expression, TextLocation location, List target) { + var setThrowable = new WasmSetGlobal(context.exceptionGlobal(), expression); + setThrowable.setLocation(location); + target.add(setThrowable); + + var result = new WasmThrow(context.getExceptionTag()); + result.setLocation(location); + target.add(result); + } + + @Override + public void visit(UnwrapArrayExpr expr) { + accept(expr.getArray()); + result = unwrapArray(result); + result.setLocation(expr.getLocation()); + } + + private WasmExpression unwrapArray(WasmExpression array) { + array.acceptVisitor(typeInference); + var arrayType = (WasmType.CompositeReference) typeInference.getResult(); + var arrayStruct = (WasmStructure) arrayType.composite; + return new WasmStructGet(arrayStruct, array, WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET); + } + + @Override + protected WasmExpression generateArrayLength(WasmExpression array) { + return new WasmArrayLength(array); + } + + @Override + protected WasmExpression storeArrayItem(WasmExpression array, WasmExpression index, WasmExpression value, + ArrayType type) { + array.acceptVisitor(typeInference); + var arrayRefType = (WasmType.CompositeReference) typeInference.getResult(); + var arrayType = (WasmArray) arrayRefType.composite; + return new WasmArraySet(arrayType, array, index, value); + } + + @Override + protected void storeField(Expr qualified, FieldReference field, Expr value, TextLocation location) { + accept(qualified); + var target = result; + accept(value); + var wasmValue = result; + + target.acceptVisitor(typeInference); + var type = (WasmType.CompositeReference) typeInference.getResult(); + var struct = (WasmStructure) type.composite; + + var fieldIndex = context.classInfoProvider().getFieldIndex(field); + + var expr = new WasmStructSet(struct, target, fieldIndex, wasmValue); + expr.setLocation(location); + resultConsumer.add(expr); + } + + @Override + protected WasmExpression stringLiteral(String s) { + var stringConstant = context.strings().getStringConstant(s); + return new WasmGetGlobal(stringConstant.global); + } + + @Override + protected WasmExpression classLiteral(ValueType type) { + var classConstant = context.classInfoProvider().getClassInfo(type); + return new WasmGetGlobal(classConstant.getPointer()); + } + + @Override + protected CallSiteIdentifier generateCallSiteId(TextLocation location) { + return new SimpleCallSite(); + } + + @Override + protected WasmExpression generateVirtualCall(WasmLocal instance, MethodReference method, + List arguments) { + var vtable = context.virtualTables().lookup(method.getClassName()); + if (vtable != null) { + vtable = vtable.findMethodContainer(method.getDescriptor()); + } + if (vtable == null) { + return new WasmUnreachable(); + } + method = new MethodReference(vtable.getClassName(), method.getDescriptor()); + + arguments.get(0).acceptVisitor(typeInference); + var instanceType = (WasmType.CompositeReference) typeInference.getResult(); + var instanceStruct = (WasmStructure) instanceType.composite; + + WasmExpression classRef = new WasmStructGet(instanceStruct, new WasmGetLocal(instance), + WasmGCClassInfoProvider.CLASS_FIELD_OFFSET); + var index = context.classInfoProvider().getVirtualMethodIndex(method); + var vtableType = (WasmType.CompositeReference) instanceStruct.getFields() + .get(WasmGCClassInfoProvider.CLASS_FIELD_OFFSET).asUnpackedType(); + var vtableStruct = (WasmStructure) vtableType.composite; + var expectedVtableStruct = context.classInfoProvider().getClassInfo(vtable.getClassName()) + .getVirtualTableStructure(); + if (expectedVtableStruct != vtableStruct) { + classRef = new WasmCast(classRef, expectedVtableStruct.getReference()); + } + + var functionRef = new WasmStructGet(expectedVtableStruct, classRef, index); + var functionTypeRef = (WasmType.CompositeReference) expectedVtableStruct.getFields() + .get(index).asUnpackedType(); + var invoke = new WasmCallReference(functionRef, (WasmFunctionType) functionTypeRef.composite); + invoke.getArguments().addAll(arguments); + return invoke; + } + + @Override + protected void allocateObject(String className, TextLocation location, WasmLocal local, + List target) { + var classInfo = context.classInfoProvider().getClassInfo(className); + var block = new WasmBlock(false); + block.setType(classInfo.getType()); + var targetVar = local; + if (targetVar == null) { + targetVar = tempVars.acquire(classInfo.getType()); + } + + var structNew = new WasmSetLocal(targetVar, new WasmStructNewDefault(classInfo.getStructure())); + structNew.setLocation(location); + target.add(structNew); + + var initClassField = new WasmStructSet(classInfo.getStructure(), new WasmGetLocal(targetVar), + WasmGCClassInfoProvider.CLASS_FIELD_OFFSET, new WasmGetGlobal(classInfo.getPointer())); + initClassField.setLocation(location); + target.add(initClassField); + + if (local == null) { + var getLocal = new WasmGetLocal(targetVar); + getLocal.setLocation(location); + target.add(getLocal); + tempVars.release(targetVar); + } + } + + @Override + protected void allocateArray(ValueType itemType, WasmExpression length, TextLocation location, WasmLocal local, + List target) { + var classInfo = context.classInfoProvider().getClassInfo(ValueType.arrayOf(itemType)); + var block = new WasmBlock(false); + block.setType(classInfo.getType()); + var targetVar = local; + if (targetVar == null) { + targetVar = tempVars.acquire(classInfo.getType()); + } + + var structNew = new WasmSetLocal(targetVar, new WasmStructNewDefault(classInfo.getStructure())); + structNew.setLocation(location); + target.add(structNew); + + var initClassField = new WasmStructSet(classInfo.getStructure(), new WasmGetLocal(targetVar), + WasmGCClassInfoProvider.CLASS_FIELD_OFFSET, new WasmGetGlobal(classInfo.getPointer())); + initClassField.setLocation(location); + target.add(initClassField); + + var wasmArrayType = (WasmType.CompositeReference) classInfo.getStructure().getFields() + .get(WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET) + .asUnpackedType(); + var wasmArray = (WasmArray) wasmArrayType.composite; + var initArrayField = new WasmStructSet( + classInfo.getStructure(), + new WasmGetLocal(targetVar), + WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET, + new WasmArrayNewDefault(wasmArray, length) + ); + initArrayField.setLocation(location); + target.add(initArrayField); + + if (local == null) { + var getLocal = new WasmGetLocal(targetVar); + getLocal.setLocation(location); + target.add(getLocal); + tempVars.release(targetVar); + } + } + + @Override + protected WasmExpression allocateMultiArray(List target, ValueType itemType, + List dimensions, TextLocation location) { + return null; + } + + @Override + protected WasmExpression generateInstanceOf(WasmExpression expression, ValueType type) { + context.classInfoProvider().getClassInfo(type); + var supertypeCall = new WasmCall(context.functions().forSupertype(type)); + var classRef = new WasmStructGet( + context.standardClasses().objectClass().getStructure(), + expression, + WasmGCClassInfoProvider.CLASS_FIELD_OFFSET + ); + supertypeCall.getArguments().add(classRef); + return supertypeCall; + } + + @Override + protected WasmExpression generateCast(WasmExpression value, WasmType targetType) { + return new WasmCast(value, (WasmType.Reference) targetType); + } + + @Override + protected boolean needsClassInitializer(String className) { + return context.classInfoProvider().getClassInfo(className).getInitializerPointer() != null; + } + + @Override + protected WasmExpression generateClassInitializer(String className, TextLocation location) { + var pointer = context.classInfoProvider().getClassInfo(className).getInitializerPointer(); + var result = new WasmCallReference(new WasmGetGlobal(pointer), + context.functionTypes().of(null)); + result.setLocation(location); + return result; + } + + @Override + protected WasmExpression peekException() { + return new WasmGetGlobal(context.exceptionGlobal()); + } + + @Override + protected void catchException(TextLocation location, List target, WasmLocal local) { + var type = context.classInfoProvider().getClassInfo("java.lang.Throwable").getType(); + if (local != null) { + var save = new WasmSetLocal(local, new WasmGetGlobal(context.exceptionGlobal())); + save.setLocation(location); + target.add(save); + } + + var erase = new WasmSetGlobal(context.exceptionGlobal(), new WasmNullConstant(type)); + erase.setLocation(location); + target.add(erase); + } + + @Override + protected WasmType mapType(ValueType type) { + return context.typeMapper().mapType(type).asUnpackedType(); + } + + @Override + public void visit(SubscriptExpr expr) { + accept(expr.getArray()); + var arrayData = unwrapArray(result); + arrayData.acceptVisitor(typeInference); + var arrayTypeRef = (WasmType.CompositeReference) typeInference.getResult(); + var arrayType = (WasmArray) arrayTypeRef.composite; + + accept(expr.getIndex()); + var index = result; + + result = new WasmArrayGet(arrayType, arrayData, index); + result.setLocation(expr.getLocation()); + } + + @Override + public void visit(InvocationExpr expr) { + result = invocation(expr, null, false); + } + + @Override + public void visit(QualificationExpr expr) { + accept(expr.getQualified()); + var target = result; + + target.acceptVisitor(typeInference); + var type = (WasmType.CompositeReference) typeInference.getResult(); + var struct = (WasmStructure) type.composite; + + var fieldIndex = context.classInfoProvider().getFieldIndex(expr.getField()); + + result = new WasmStructGet(struct, target, fieldIndex); + result.setLocation(expr.getLocation()); + } + + private class SimpleCallSite extends CallSiteIdentifier { + @Override + public void generateRegister(List consumer, TextLocation location) { + } + + @Override + public void checkHandlerId(List target, TextLocation location) { + } + + @Override + public void generateThrow(List target, TextLocation location) { + } + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/WasmGCSupport.java b/core/src/main/java/org/teavm/backend/wasm/runtime/WasmGCSupport.java new file mode 100644 index 000000000..44d4237a8 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/WasmGCSupport.java @@ -0,0 +1,33 @@ +/* + * 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.runtime; + +public class WasmGCSupport { + private WasmGCSupport() { + } + + public static NullPointerException npe() { + return new NullPointerException(); + } + + public static ArrayIndexOutOfBoundsException aiiobe() { + return new ArrayIndexOutOfBoundsException(); + } + + public static ClassCastException cce() { + return new ClassCastException(); + } +} diff --git a/core/src/main/java/org/teavm/model/analysis/BaseTypeInference.java b/core/src/main/java/org/teavm/model/analysis/BaseTypeInference.java index 565e42ac7..51d1c8fa0 100644 --- a/core/src/main/java/org/teavm/model/analysis/BaseTypeInference.java +++ b/core/src/main/java/org/teavm/model/analysis/BaseTypeInference.java @@ -82,6 +82,15 @@ public abstract class BaseTypeInference { visitor.graphBuilder.addEdge(incoming.getValue().getIndex(), phi.getReceiver().getIndex()); } } + for (var tryCatch : block.getTryCatchBlocks()) { + var exceptionVar = tryCatch.getHandler().getExceptionVariable(); + if (exceptionVar != null) { + var exceptionType = tryCatch.getExceptionType() != null + ? ValueType.object(tryCatch.getExceptionType()) + : ValueType.object("java.lang.Throwable"); + visitor.type(exceptionVar, exceptionType); + } + } } graph = visitor.graphBuilder.build(); arrayGraph = visitor.arrayGraphBuilder.build(); diff --git a/core/src/main/java/org/teavm/model/util/DefaultVariableCategoryProvider.java b/core/src/main/java/org/teavm/model/util/DefaultVariableCategoryProvider.java new file mode 100644 index 000000000..3b08d178a --- /dev/null +++ b/core/src/main/java/org/teavm/model/util/DefaultVariableCategoryProvider.java @@ -0,0 +1,52 @@ +/* + * 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.model.util; + +import org.teavm.model.MethodReference; +import org.teavm.model.ProgramReader; + +public class DefaultVariableCategoryProvider implements VariableCategoryProvider { + @Override + public Object[] getCategories(ProgramReader program, MethodReference method) { + TypeInferer inferer = new TypeInferer(); + inferer.inferTypes(program, method); + var categories = new Object[program.variableCount()]; + for (int i = 0; i < program.variableCount(); ++i) { + categories[i] = getCategory(inferer.typeOf(i)); + } + return categories; + } + + private int getCategory(VariableType type) { + if (type == null) { + return 255; + } + switch (type) { + case INT: + return 0; + case LONG: + return 1; + case FLOAT: + return 2; + case DOUBLE: + return 3; + case OBJECT: + return 4; + default: + return 5; + } + } +} diff --git a/core/src/main/java/org/teavm/model/util/GraphColorer.java b/core/src/main/java/org/teavm/model/util/GraphColorer.java index e61de3aa5..06cd95f14 100644 --- a/core/src/main/java/org/teavm/model/util/GraphColorer.java +++ b/core/src/main/java/org/teavm/model/util/GraphColorer.java @@ -16,18 +16,20 @@ package org.teavm.model.util; import java.util.ArrayList; +import java.util.Arrays; import java.util.BitSet; import java.util.List; import org.teavm.common.Graph; -import org.teavm.common.IntegerArray; public class GraphColorer { public void colorize(Graph graph, int[] colors) { - colorize(graph, colors, new int[graph.size()], new String[graph.size()]); + var categories = new Object[graph.size()]; + Arrays.fill(categories, new Object()); + colorize(graph, colors, categories, new String[graph.size()]); } - public void colorize(Graph graph, int[] colors, int[] categories, String[] names) { - IntegerArray colorCategories = new IntegerArray(graph.size()); + public void colorize(Graph graph, int[] colors, Object[] categories, String[] names) { + var colorCategories = new ArrayList<>(graph.size()); List colorNames = new ArrayList<>(); for (int i = 0; i < colors.length; ++i) { int color = colors[i]; @@ -59,12 +61,12 @@ public class GraphColorer { while (true) { color = usedColors.nextClearBit(color); while (colorCategories.size() <= color) { - colorCategories.add(-1); + colorCategories.add(null); colorNames.add(null); } - int category = colorCategories.get(color); + var category = colorCategories.get(color); String name = colorNames.get(color); - if ((category < 0 || category == categories[v]) + if ((category == null || category.equals(categories[v])) && (name == null || names[v] == null || name.equals(names[v]))) { colors[v] = color; colorCategories.set(color, categories[v]); diff --git a/core/src/main/java/org/teavm/model/util/RegisterAllocator.java b/core/src/main/java/org/teavm/model/util/RegisterAllocator.java index 37df01266..0fe85ac5e 100644 --- a/core/src/main/java/org/teavm/model/util/RegisterAllocator.java +++ b/core/src/main/java/org/teavm/model/util/RegisterAllocator.java @@ -38,6 +38,12 @@ import org.teavm.model.instructions.AssignInstruction; import org.teavm.model.instructions.JumpInstruction; public class RegisterAllocator { + private VariableCategoryProvider variableCategoryProvider; + + public RegisterAllocator(VariableCategoryProvider variableCategoryProvider) { + this.variableCategoryProvider = variableCategoryProvider; + } + public void allocateRegisters(MethodReference method, Program program, boolean debuggerFriendly) { insertPhiArgumentsCopies(program); InterferenceGraphBuilder interferenceBuilder = new InterferenceGraphBuilder(); @@ -62,7 +68,7 @@ public class RegisterAllocator { for (int cls : classArray) { maxClass = Math.max(maxClass, cls + 1); } - int[] categories = getVariableCategories(program, method); + var categories = variableCategoryProvider.getCategories(program, method); String[] names = getVariableNames(program, debuggerFriendly); colorer.colorize(MutableGraphNode.toGraph(interferenceGraph), colors, categories, names); @@ -91,36 +97,6 @@ public class RegisterAllocator { } } - private int[] getVariableCategories(ProgramReader program, MethodReference method) { - TypeInferer inferer = new TypeInferer(); - inferer.inferTypes(program, method); - int[] categories = new int[program.variableCount()]; - for (int i = 0; i < program.variableCount(); ++i) { - categories[i] = getCategory(inferer.typeOf(i)); - } - return categories; - } - - private int getCategory(VariableType type) { - if (type == null) { - return 255; - } - switch (type) { - case INT: - return 0; - case LONG: - return 1; - case FLOAT: - return 2; - case DOUBLE: - return 3; - case OBJECT: - return 4; - default: - return 5; - } - } - private String[] getVariableNames(ProgramReader program, boolean debuggerFriendly) { String[] names = new String[program.variableCount()]; for (int i = 0; i < names.length; ++i) { diff --git a/core/src/main/java/org/teavm/model/util/VariableCategoryProvider.java b/core/src/main/java/org/teavm/model/util/VariableCategoryProvider.java new file mode 100644 index 000000000..ce988b8dc --- /dev/null +++ b/core/src/main/java/org/teavm/model/util/VariableCategoryProvider.java @@ -0,0 +1,23 @@ +/* + * 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.model.util; + +import org.teavm.model.MethodReference; +import org.teavm.model.ProgramReader; + +public interface VariableCategoryProvider { + Object[] getCategories(ProgramReader program, MethodReference method); +} diff --git a/core/src/main/java/org/teavm/vm/TeaVM.java b/core/src/main/java/org/teavm/vm/TeaVM.java index 4e6d999d3..e6ffab864 100644 --- a/core/src/main/java/org/teavm/vm/TeaVM.java +++ b/core/src/main/java/org/teavm/vm/TeaVM.java @@ -762,8 +762,9 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } while (changed); target.afterOptimizations(optimizedProgram, method); - if (target.requiresRegisterAllocation()) { - RegisterAllocator allocator = new RegisterAllocator(); + var categoryProvider = target.variableCategoryProvider(); + if (categoryProvider != null) { + var allocator = new RegisterAllocator(categoryProvider); allocator.allocateRegisters(method.getReference(), optimizedProgram, optimizationLevel == TeaVMOptimizationLevel.SIMPLE); } diff --git a/core/src/main/java/org/teavm/vm/TeaVMTarget.java b/core/src/main/java/org/teavm/vm/TeaVMTarget.java index 28486ac57..d0eb5d48e 100644 --- a/core/src/main/java/org/teavm/vm/TeaVMTarget.java +++ b/core/src/main/java/org/teavm/vm/TeaVMTarget.java @@ -27,6 +27,7 @@ import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.Program; import org.teavm.model.optimization.InliningFilterFactory; +import org.teavm.model.util.VariableCategoryProvider; import org.teavm.vm.spi.TeaVMHostExtension; public interface TeaVMTarget { @@ -38,7 +39,7 @@ public interface TeaVMTarget { List getHostExtensions(); - boolean requiresRegisterAllocation(); + VariableCategoryProvider variableCategoryProvider(); void contributeDependencies(DependencyAnalyzer dependencyAnalyzer);