wasm gc: support exporting declarations as JS entities from module

This commit is contained in:
Alexey Andreev 2024-10-06 21:00:17 +02:00
parent 73dda91d35
commit 0dcc25d66b
42 changed files with 1061 additions and 324 deletions

View File

@ -25,7 +25,9 @@ import org.teavm.interop.NoSideEffects;
import org.teavm.interop.Platforms;
import org.teavm.interop.Unmanaged;
import org.teavm.interop.UnsupportedOn;
import org.teavm.jso.JSObject;
import org.teavm.jso.core.JSDate;
import org.teavm.jso.impl.JS;
public class TDate implements TComparable<TDate> {
private long value;
@ -420,7 +422,7 @@ public class TDate implements TComparable<TDate> {
} else if (PlatformDetector.isWebAssembly()) {
return toStringWebAssembly(value);
} else if (PlatformDetector.isWebAssemblyGC()) {
return toStringWebAssemblyGC(value);
return JS.unwrapString(toStringWebAssemblyGC(value));
} else {
return JSDate.create(value).stringValue();
}
@ -435,7 +437,7 @@ public class TDate implements TComparable<TDate> {
private static native String toStringWebAssembly(double date);
@Import(module = "teavmDate", name = "dateToString")
private static native String toStringWebAssemblyGC(double date);
private static native JSObject toStringWebAssemblyGC(double date);
@Deprecated
public String toLocaleString() {

View File

@ -18,7 +18,6 @@ package org.teavm.backend.wasm;
import org.teavm.backend.wasm.generate.gc.WasmGCDeclarationsGenerator;
import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.backend.wasm.runtime.gc.WasmGCSupport;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
@ -34,47 +33,6 @@ public class WasmGCModuleGenerator {
createInitializer();
}
public WasmFunction generateMainFunction(String entryPoint) {
return declarationsGenerator.functions().forStaticMethod(new MethodReference(entryPoint,
"main", ValueType.parse(String[].class), ValueType.VOID));
}
public WasmFunction generateCreateStringBuilderFunction() {
return declarationsGenerator.functions().forStaticMethod(new MethodReference(
WasmGCSupport.class, "createStringBuilder", StringBuilder.class));
}
public WasmFunction generateCreateStringArrayFunction() {
return declarationsGenerator.functions().forStaticMethod(new MethodReference(
WasmGCSupport.class, "createStringArray", int.class, String[].class));
}
public WasmFunction generateAppendCharFunction() {
return declarationsGenerator.functions().forInstanceMethod(new MethodReference(
StringBuilder.class, "append", char.class, StringBuilder.class));
}
public WasmFunction generateBuildStringFunction() {
return declarationsGenerator.functions().forInstanceMethod(new MethodReference(
StringBuilder.class, "toString", String.class));
}
public WasmFunction generateSetToStringArrayFunction() {
return declarationsGenerator.functions().forStaticMethod(new MethodReference(
WasmGCSupport.class, "setToStringArray", String[].class, int.class, String.class, void.class));
}
public WasmFunction generateStringLengthFunction() {
return declarationsGenerator.functions().forInstanceMethod(new MethodReference(
String.class, "length", int.class));
}
public WasmFunction generateCharAtFunction() {
return declarationsGenerator.functions().forInstanceMethod(new MethodReference(
String.class, "charAt", int.class, char.class));
}
public WasmFunction generateReportGarbageCollectedStringFunction() {
var entryType = ValueType.object(StringInternPool.class.getName() + "$Entry");
return declarationsGenerator.functions().forStaticMethod(new MethodReference(

View File

@ -21,20 +21,28 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.teavm.backend.wasm.debug.DebugLines;
import org.teavm.backend.wasm.debug.ExternalDebugFile;
import org.teavm.backend.wasm.debug.GCDebugInfoBuilder;
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.WasmGCDeclarationsGenerator;
import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider;
import org.teavm.backend.wasm.generate.gc.classes.WasmGCCustomTypeMapperFactory;
import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper;
import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider;
import org.teavm.backend.wasm.generators.gc.WasmGCCustomGenerator;
import org.teavm.backend.wasm.generators.gc.WasmGCCustomGeneratorFactory;
import org.teavm.backend.wasm.generators.gc.WasmGCCustomGenerators;
import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsic;
import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicFactory;
import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsics;
import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.WasmTag;
import org.teavm.backend.wasm.optimization.WasmUsageCounter;
import org.teavm.backend.wasm.render.WasmBinaryRenderer;
import org.teavm.backend.wasm.render.WasmBinaryStatsCollector;
@ -43,10 +51,12 @@ import org.teavm.backend.wasm.render.WasmBinaryWriter;
import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.backend.wasm.transformation.gc.BaseClassesTransformation;
import org.teavm.backend.wasm.transformation.gc.ClassLoaderResourceTransformation;
import org.teavm.backend.wasm.transformation.gc.EntryPointTransformation;
import org.teavm.dependency.DependencyAnalyzer;
import org.teavm.dependency.DependencyListener;
import org.teavm.interop.Platforms;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListableClassHolderSource;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
@ -73,6 +83,8 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
private List<WasmGCCustomTypeMapperFactory> customTypeMapperFactories = new ArrayList<>();
private Map<MethodReference, WasmGCCustomGenerator> customCustomGenerators = new HashMap<>();
private List<WasmGCCustomGeneratorFactory> customGeneratorFactories = new ArrayList<>();
private EntryPointTransformation entryPointTransformation = new EntryPointTransformation();
private List<WasmGCClassConsumer> classConsumers = new ArrayList<>();
public void setObfuscated(boolean obfuscated) {
this.obfuscated = obfuscated;
@ -115,11 +127,22 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
customTypeMapperFactories.add(customTypeMapperFactory);
}
@Override
public void addClassConsumer(WasmGCClassConsumer consumer) {
classConsumers.add(consumer);
}
@Override
public void setController(TeaVMTargetController controller) {
this.controller = controller;
}
@Override
public void setEntryPoint(String entryPoint, String name) {
entryPointTransformation.setEntryPoint(entryPoint);
entryPointTransformation.setEntryPointName(name);
}
@Override
public VariableCategoryProvider variableCategoryProvider() {
return null;
@ -134,7 +157,8 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
public List<ClassHolderTransformer> getTransformers() {
return List.of(
new BaseClassesTransformation(),
new ClassLoaderResourceTransformation()
new ClassLoaderResourceTransformation(),
entryPointTransformation
);
}
@ -181,6 +205,7 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
@Override
public void emit(ListableClassHolderSource classes, BuildTarget buildTarget, String outputName) throws IOException {
var module = new WasmModule();
module.memoryExportName = "teavm.memory";
var customGenerators = new WasmGCCustomGenerators(classes, controller.getServices(),
customGeneratorFactories, customCustomGenerators,
controller.getProperties());
@ -198,47 +223,30 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
intrinsics,
customTypeMapperFactories,
controller::isVirtual,
strict
strict,
controller.getEntryPoint()
);
declarationsGenerator.setFriendlyToDebugger(controller.isFriendlyToDebugger());
var moduleGenerator = new WasmGCModuleGenerator(declarationsGenerator);
var mainFunction = moduleGenerator.generateMainFunction(controller.getEntryPoint());
mainFunction.setExportName(controller.getEntryPointName());
mainFunction.setName(controller.getEntryPointName());
var stringBuilderFunction = moduleGenerator.generateCreateStringBuilderFunction();
stringBuilderFunction.setExportName("createStringBuilder");
var createStringArrayFunction = moduleGenerator.generateCreateStringArrayFunction();
createStringArrayFunction.setExportName("createStringArray");
var appendCharFunction = moduleGenerator.generateAppendCharFunction();
appendCharFunction.setExportName("appendChar");
var buildStringFunction = moduleGenerator.generateBuildStringFunction();
buildStringFunction.setExportName("buildString");
var setArrayFunction = moduleGenerator.generateSetToStringArrayFunction();
setArrayFunction.setExportName("setToStringArray");
var stringLengthFunction = moduleGenerator.generateStringLengthFunction();
stringLengthFunction.setExportName("stringLength");
var charAtFunction = moduleGenerator.generateCharAtFunction();
charAtFunction.setExportName("charAt");
var classConsumerContext = createClassConsumerContext(classes, declarationsGenerator);
for (var cls : classes.getClassNames()) {
for (var consumer : classConsumers) {
consumer.accept(classConsumerContext, cls);
}
}
var internMethod = controller.getDependencyInfo().getMethod(new MethodReference(String.class,
"intern", String.class));
if (internMethod != null && internMethod.isUsed()) {
var removeStringEntryFunction = moduleGenerator.generateReportGarbageCollectedStringFunction();
removeStringEntryFunction.setExportName("reportGarbageCollectedString");
removeStringEntryFunction.setExportName("teavm.reportGarbageCollectedString");
}
var exceptionMessageRef = new MethodReference(Throwable.class, "getMessage", Throwable.class);
if (controller.getDependencyInfo().getMethod(exceptionMessageRef) != null) {
var exceptionMessageFunction = declarationsGenerator.functions().forInstanceMethod(exceptionMessageRef);
exceptionMessageFunction.setExportName("exceptionMessage");
exceptionMessageFunction.setExportName("teavm.exceptionMessage");
}
moduleGenerator.generate();
@ -248,6 +256,63 @@ public class WasmGCTarget implements TeaVMTarget, TeaVMWasmGCHost {
emitWasmFile(module, buildTarget, outputName, debugInfoBuilder);
}
private WasmGCClassConsumerContext createClassConsumerContext(
ClassReaderSource classes,
WasmGCDeclarationsGenerator generator
) {
return new WasmGCClassConsumerContext() {
@Override
public ClassReaderSource classes() {
return classes;
}
@Override
public WasmModule module() {
return generator.module;
}
@Override
public WasmFunctionTypes functionTypes() {
return generator.functionTypes;
}
@Override
public BaseWasmFunctionRepository functions() {
return generator.functions();
}
@Override
public WasmGCNameProvider names() {
return generator.names();
}
@Override
public WasmGCStringProvider strings() {
return generator.strings();
}
@Override
public WasmGCTypeMapper typeMapper() {
return generator.typeMapper();
}
@Override
public WasmTag exceptionTag() {
return generator.exceptionTag();
}
@Override
public String entryPoint() {
return controller.getEntryPoint();
}
@Override
public void addToInitializer(Consumer<WasmFunction> initializerContributor) {
generator.addToInitializer(initializerContributor);
}
};
}
private void adjustModuleMemory(WasmModule module) {
var memorySize = 0;
for (var segment : module.getSegments()) {

View File

@ -33,4 +33,6 @@ public interface TeaVMWasmGCHost extends TeaVMHostExtension {
void addGenerator(MethodReference method, WasmGCCustomGenerator generator);
void addCustomTypeMapperFactory(WasmGCCustomTypeMapperFactory customTypeMapperFactory);
void addClassConsumer(WasmGCClassConsumer consumer);
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2024 konsoletyper.
*
* 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.gc;
public interface WasmGCClassConsumer {
void accept(WasmGCClassConsumerContext context, String className);
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2024 konsoletyper.
*
* 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.gc;
import java.util.function.Consumer;
import org.teavm.backend.wasm.BaseWasmFunctionRepository;
import org.teavm.backend.wasm.WasmFunctionTypes;
import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider;
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.WasmModule;
import org.teavm.backend.wasm.model.WasmTag;
import org.teavm.model.ClassReaderSource;
public interface WasmGCClassConsumerContext {
ClassReaderSource classes();
WasmModule module();
WasmFunctionTypes functionTypes();
BaseWasmFunctionRepository functions();
WasmGCNameProvider names();
WasmGCStringProvider strings();
WasmGCTypeMapper typeMapper();
WasmTag exceptionTag();
String entryPoint();
void addToInitializer(Consumer<WasmFunction> initializerContributor);
}

View File

@ -17,6 +17,7 @@ package org.teavm.backend.wasm.generate.gc;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.teavm.backend.wasm.BaseWasmFunctionRepository;
import org.teavm.backend.wasm.WasmFunctionTypes;
@ -29,8 +30,10 @@ import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper;
import org.teavm.backend.wasm.generate.gc.methods.WasmGCCustomGeneratorProvider;
import org.teavm.backend.wasm.generate.gc.methods.WasmGCIntrinsicProvider;
import org.teavm.backend.wasm.generate.gc.methods.WasmGCMethodGenerator;
import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider;
import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.WasmTag;
import org.teavm.dependency.DependencyInfo;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassHierarchy;
@ -62,7 +65,8 @@ public class WasmGCDeclarationsGenerator {
WasmGCIntrinsicProvider intrinsics,
List<WasmGCCustomTypeMapperFactory> customTypeMapperFactories,
Predicate<MethodReference> isVirtual,
boolean strict
boolean strict,
String entryPoint
) {
this.module = module;
hierarchy = new ClassHierarchy(classes);
@ -82,6 +86,7 @@ public class WasmGCDeclarationsGenerator {
customGenerators,
intrinsics,
strict,
entryPoint,
initializerContributors::add
);
var tags = new TagRegistry(classes, hierarchy);
@ -157,4 +162,20 @@ public class WasmGCDeclarationsGenerator {
public WasmFunction dummyInitializer() {
return methodGenerator.getDummyInitializer();
}
public WasmGCNameProvider names() {
return methodGenerator.names;
}
public WasmGCStringProvider strings() {
return classGenerator.strings;
}
public WasmTag exceptionTag() {
return methodGenerator.getGenerationContext().getExceptionTag();
}
public void addToInitializer(Consumer<WasmFunction> contributor) {
methodGenerator.getGenerationContext().addToInitializer(contributor);
}
}

View File

@ -65,6 +65,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
private Map<String, Set<String>> interfaceImplementors;
private WasmGCNameProvider names;
private boolean strict;
private String entryPoint;
private Consumer<WasmGCInitializerContributor> initializerContributors;
public WasmGCGenerationContext(WasmModule module, WasmGCVirtualTableProvider virtualTables,
@ -73,7 +74,8 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
WasmGCSupertypeFunctionProvider supertypeFunctions, WasmGCClassInfoProvider classInfoProvider,
WasmGCStandardClasses standardClasses, WasmGCStringProvider strings,
WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics,
WasmGCNameProvider names, boolean strict, Consumer<WasmGCInitializerContributor> initializerContributors) {
WasmGCNameProvider names, boolean strict, String entryPoint,
Consumer<WasmGCInitializerContributor> initializerContributors) {
this.module = module;
this.virtualTables = virtualTables;
this.typeMapper = typeMapper;
@ -90,6 +92,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
this.intrinsics = intrinsics;
this.names = names;
this.strict = strict;
this.entryPoint = entryPoint;
this.initializerContributors = initializerContributors;
}
@ -109,6 +112,10 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
return strings;
}
public String entryPoint() {
return entryPoint;
}
public WasmGCVirtualTableProvider virtualTables() {
return virtualTables;
}
@ -136,7 +143,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
if (exceptionTag == null) {
exceptionTag = new WasmTag(functionTypes.of(null,
classInfoProvider.getClassInfo("java.lang.Throwable").getStructure().getReference()));
exceptionTag.setExportName("javaException");
exceptionTag.setExportName("teavm.javaException");
module.tags.add(exceptionTag);
}
return exceptionTag;

View File

@ -844,6 +844,11 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor {
return context.getExceptionTag();
}
@Override
public String entryPoint() {
return context.entryPoint();
}
@Override
public void addToInitializer(Consumer<WasmFunction> initializerContributor) {
context.addToInitializer(initializerContributor);

View File

@ -72,7 +72,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
private ClassInitializerInfo classInitInfo;
private WasmFunctionTypes functionTypes;
private WasmGCSupertypeFunctionProvider supertypeFunctions;
private WasmGCNameProvider names;
public final WasmGCNameProvider names;
private Diagnostics diagnostics;
private WasmGCTypeMapper typeMapper;
private WasmGCCustomGeneratorProvider customGenerators;
@ -88,6 +88,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
private WasmGCStandardClasses standardClasses;
private WasmGCStringProvider strings;
private boolean strict;
private String entryPoint;
private Consumer<WasmGCInitializerContributor> initializerContributors;
public WasmGCMethodGenerator(
@ -103,6 +104,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
WasmGCCustomGeneratorProvider customGenerators,
WasmGCIntrinsicProvider intrinsics,
boolean strict,
String entryPoint,
Consumer<WasmGCInitializerContributor> initializerContributors
) {
this.module = module;
@ -117,6 +119,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
this.customGenerators = customGenerators;
this.intrinsics = intrinsics;
this.strict = strict;
this.entryPoint = entryPoint;
this.initializerContributors = initializerContributors;
}
@ -343,7 +346,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
return decompiler;
}
private WasmGCGenerationContext getGenerationContext() {
public WasmGCGenerationContext getGenerationContext() {
if (context == null) {
context = new WasmGCGenerationContext(
module,
@ -362,6 +365,7 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
intrinsics,
names,
strict,
entryPoint,
initializerContributors
);
}
@ -434,6 +438,11 @@ public class WasmGCMethodGenerator implements BaseWasmFunctionRepository {
return context.strings();
}
@Override
public String entryPoint() {
return context.entryPoint();
}
@Override
public void addToInitializer(Consumer<WasmFunction> initializerContributor) {
context.addToInitializer(initializerContributor);

View File

@ -51,5 +51,7 @@ public interface WasmGCCustomGeneratorContext {
WasmGCStringProvider strings();
String entryPoint();
void addToInitializer(Consumer<WasmFunction> initializerContributor);
}

View File

@ -61,5 +61,7 @@ public interface WasmGCIntrinsicContext {
WasmTag exceptionTag();
String entryPoint();
void addToInitializer(Consumer<WasmFunction> initializerContributor);
}

View File

@ -23,6 +23,7 @@ public class WasmGlobal extends WasmEntity {
private WasmType type;
private WasmExpression initialValue;
private boolean immutable;
private String exportName;
public WasmGlobal(String name, WasmType type, WasmExpression initialValue) {
this.name = name;
@ -57,4 +58,12 @@ public class WasmGlobal extends WasmEntity {
public void setImmutable(boolean immutable) {
this.immutable = immutable;
}
public String getExportName() {
return exportName;
}
public void setExportName(String exportName) {
this.exportName = exportName;
}
}

View File

@ -40,6 +40,7 @@ public class WasmModule {
public final WasmCollection<WasmGlobal> globals = new WasmCollection<>();
public final WasmCollection<WasmCompositeType> types = new WasmCollection<>();
public final WasmCollection<WasmTag> tags = new WasmCollection<>();
public String memoryExportName = "memory";
public void add(WasmCustomSection customSection) {
if (customSections.containsKey(customSection.getName())) {

View File

@ -49,6 +49,7 @@ public class WasmBinaryRenderer {
private static final int EXTERNAL_KIND_FUNCTION = 0;
private static final int EXTERNAL_KIND_MEMORY = 2;
private static final int EXTERNAL_KIND_GLOBAL = 3;
private static final int EXTERNAL_KIND_TAG = 4;
private WasmBinaryWriter output;
@ -241,7 +242,11 @@ public class WasmBinaryRenderer {
.filter(tag -> tag.getExportName() != null)
.collect(Collectors.toList());
section.writeLEB(functions.size() + tags.size() + 1);
var globals = module.globals.stream()
.filter(global -> global.getExportName() != null)
.collect(Collectors.toList());
section.writeLEB(functions.size() + tags.size() + globals.size() + 1);
for (var function : functions) {
int functionIndex = module.functions.indexOf(function);
@ -257,9 +262,16 @@ public class WasmBinaryRenderer {
section.writeByte(EXTERNAL_KIND_TAG);
section.writeLEB(tagIndex);
}
for (var global : globals) {
var index = module.globals.indexOf(global);
section.writeAsciiString(global.getExportName());
section.writeByte(EXTERNAL_KIND_GLOBAL);
section.writeLEB(index);
}
// We also need to export the memory to make it accessible
section.writeAsciiString("memory");
section.writeAsciiString(module.memoryExportName);
section.writeByte(EXTERNAL_KIND_MEMORY);
section.writeLEB(0);

View File

@ -0,0 +1,50 @@
/*
* 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.transformation.gc;
import org.teavm.model.AnnotationHolder;
import org.teavm.model.AnnotationValue;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.MethodDescriptor;
public class EntryPointTransformation implements ClassHolderTransformer {
private static final MethodDescriptor MAIN_METHOD = new MethodDescriptor("main", String[].class, void.class);
private String entryPoint;
private String entryPointName;
public void setEntryPoint(String entryPoint) {
this.entryPoint = entryPoint;
}
public void setEntryPointName(String entryPointName) {
this.entryPointName = entryPointName;
}
@Override
public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) {
if (cls.getName().equals(entryPoint)) {
var mainMethod = cls.getMethod(MAIN_METHOD);
if (mainMethod != null) {
mainMethod.getAnnotations().add(new AnnotationHolder("org.teavm.jso.JSExport"));
var methodAnnot = new AnnotationHolder("org.teavm.jso.JSMethod");
methodAnnot.getValues().put("value", new AnnotationValue(entryPointName));
}
}
}
}

View File

@ -381,7 +381,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
return;
}
processEntryPoint();
target.setEntryPoint(entryPoint, entryPointName);
dependencyAnalyzer.setAsyncSupported(target.isAsyncSupported());
dependencyAnalyzer.setInterruptor(() -> {
int progress = dependencyAnalyzer.getReachableClasses().size();
@ -390,6 +390,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
});
target.contributeDependencies(dependencyAnalyzer);
dependencyAnalyzer.initDependencies();
processEntryPoint();
if (target.needsSystemArrayCopyOptimization()) {
dependencyAnalyzer.addDependencyListener(new StdlibDependencyListener());
}

View File

@ -35,6 +35,9 @@ public interface TeaVMTarget {
List<DependencyListener> getDependencyListeners();
default void setEntryPoint(String entryPoint, String name) {
}
void setController(TeaVMTargetController controller);
List<TeaVMHostExtension> getHostExtensions();

View File

@ -15,9 +15,14 @@
*/
var TeaVM = TeaVM || {};
TeaVM.wasm = function() {
if (window && window.TeaVM === undefined) {
window.TeaVM = TeaVM;
}
TeaVM.wasmGC = TeaVM.wasmGC || function() {
let exports;
let globalsCache = new Map();
let stackDeobfuscator = null;
let chromeExceptionRegex = / *at .+\.wasm:wasm-function\[[0-9]+]:0x([0-9a-f]+).*/;
let getGlobalName = function(name) {
let result = globalsCache.get(name);
if (typeof result === "undefined") {
@ -45,11 +50,11 @@ TeaVM.wasm = function() {
this[javaExceptionSymbol] = javaException;
}
get message() {
let exceptionMessage = exports.exceptionMessage;
let exceptionMessage = exports["teavm.exceptionMessage"];
if (typeof exceptionMessage === "function") {
let message = exceptionMessage(this[javaExceptionSymbol]);
if (message != null) {
return stringToJava(message);
return message;
}
}
return "(could not fetch message)";
@ -59,8 +64,8 @@ TeaVM.wasm = function() {
function dateImports(imports) {
imports.teavmDate = {
currentTimeMillis: () => new Date().getTime(),
dateToString: timestamp => stringToJava(new Date(timestamp).toString()),
getYear: timestamp =>new Date(timestamp).getFullYear(),
dateToString: timestamp => new Date(timestamp).toString(),
getYear: timestamp => new Date(timestamp).getFullYear(),
setYear(timestamp, year) {
let date = new Date(timestamp);
date.setFullYear(year);
@ -108,12 +113,16 @@ TeaVM.wasm = function() {
function coreImports(imports) {
let finalizationRegistry = new FinalizationRegistry(heldValue => {
if (typeof exports.reportGarbageCollectedValue === "function") {
exports.reportGarbageCollectedValue(heldValue)
let report = exports["teavm.reportGarbageCollectedValue"];
if (typeof report === "function") {
report(heldValue)
}
});
let stringFinalizationRegistry = new FinalizationRegistry(heldValue => {
exports.reportGarbageCollectedString(heldValue);
let report = exports["teavm.reportGarbageCollectedString"];
if (typeof report === "function") {
report(heldValue);
}
});
imports.teavm = {
createWeakRef(value, heldValue) {
@ -129,7 +138,28 @@ TeaVM.wasm = function() {
stringFinalizationRegistry.register(value, heldValue)
return weakRef;
},
stringDeref: weakRef => weakRef.deref()
stringDeref: weakRef => weakRef.deref(),
currentStackTrace() {
if (stackDeobfuscator) {
return;
}
let reportCallFrame = exports["teavm.reportCallFrame"];
if (typeof reportCallFrame !== "function") {
return;
}
let stack = new Error().stack;
for (let line in stack.split("\n")) {
let match = chromeExceptionRegex.exec(line);
if (match !== null) {
let address = parseInt(match.groups[1], 16);
let frames = stackDeobfuscator(address);
for (let frame of frames) {
let line = frame.line;
reportCallFrame(file, method, cls, line);
}
}
}
}
};
}
@ -137,6 +167,7 @@ TeaVM.wasm = function() {
let javaObjectSymbol = Symbol("javaObject");
let functionsSymbol = Symbol("functions");
let functionOriginSymbol = Symbol("functionOrigin");
let wrapperCallMarkerSymbol = Symbol("wrapperCallMarker");
let jsWrappers = new WeakMap();
let javaWrappers = new WeakMap();
@ -182,7 +213,7 @@ TeaVM.wasm = function() {
}
function javaExceptionToJs(e) {
if (e instanceof WebAssembly.Exception) {
let tag = exports["javaException"];
let tag = exports["teavm.javaException"];
if (e.is(tag)) {
let javaException = e.getArg(tag, 0);
let extracted = extractException(javaException);
@ -226,6 +257,22 @@ TeaVM.wasm = function() {
rethrowJsAsJava(e);
}
}
function defineFunction(fn) {
let params = [];
for (let i = 0; i < fn.length; ++i) {
params.push("p" + i);
}
let paramsAsString = params.length === 0 ? "" : params.join(", ");
return new Function("rethrowJavaAsJs", "fn", `
return function(${paramsAsString}) {
try {
return fn(${paramsAsString});
} catch (e) {
rethrowJavaAsJs(e);
}
};
`)(rethrowJavaAsJs, fn);
}
imports.teavmJso = {
emptyString: () => "",
stringFromCharCode: code => String.fromCharCode(code),
@ -247,16 +294,68 @@ TeaVM.wasm = function() {
rethrowJsAsJava(e);
}
},
createClass(name) {
let fn = new Function(
"javaObjectSymbol",
"functionsSymbol",
`return function JavaClass_${sanitizeName(name)}(javaObject) {
this[javaObjectSymbol] = javaObject;
this[functionsSymbol] = null;
};`
);
return fn(javaObjectSymbol, functionsSymbol, functionOriginSymbol);
createClass(name, parent, constructor) {
name = sanitizeName(name);
if (parent === null) {
let fn = new Function(
"javaObjectSymbol",
"functionsSymbol",
"wrapperCallMarker",
"constructor",
"rethrowJavaAsJs",
`let fn;
fn = function ${name}(marker, javaObject) {
if (marker === wrapperCallMarker) {
this[javaObjectSymbol] = javaObject;
this[functionsSymbol] = null;
} else if (constructor === null) {
throw new Error("This class can't be instantiated directly");
} else {
try {
return fn(wrapperCallMarker, constructor(arguments));
} catch (e) {
rethrowJavaAsJs(e);
}
}
};
let boundFn = function(javaObject) { return fn.call(this, wrapperCallMarker, javaObject); };
boundFn[wrapperCallMarker] = fn;
boundFn.prototype = fn.prototype;
return boundFn;`
);
return fn(javaObjectSymbol, functionsSymbol, wrapperCallMarkerSymbol, constructor, rethrowJavaAsJs);
} else {
let fn = new Function(
"parent",
"wrapperCallMarker",
"constructor",
"rethrowJavaAsJs",
`let fn
fn = function ${name}(marker, javaObject) {
if (marker === wrapperCallMarker) {
parent.call(this, javaObject);
} else if (constructor === null) {
throw new Error("This class can't be instantiated directly");
} else {
try {
return fn(wrapperCallMarker, constructor(arguments));
} catch (e) {
rethrowJavaAsJs(e);
}
}
};
fn.prototype = Object.create(parent);
fn.prototype.constructor = parent;
let boundFn = function(javaObject) { return fn.call(this, wrapperCallMarker, javaObject); };
boundFn[wrapperCallMarker] = fn;
boundFn.prototype = fn.prototype;
return fn;`
);
return fn(parent, wrapperCallMarkerSymbol, constructor, rethrowJavaAsJs);
}
},
exportClass(cls) {
return cls[wrapperCallMarkerSymbol];
},
defineMethod(cls, name, fn) {
let params = [];
@ -274,6 +373,10 @@ TeaVM.wasm = function() {
};
`)(rethrowJavaAsJs, fn);
},
defineStaticMethod(cls, name, fn) {
cls[name] = defineFunction(fn);
},
defineFunction: defineFunction,
defineProperty(cls, name, getFn, setFn) {
let descriptor = {
get() {
@ -295,6 +398,27 @@ TeaVM.wasm = function() {
}
Object.defineProperty(cls.prototype, name, descriptor);
},
defineStaticProperty(cls, name, getFn, setFn) {
let descriptor = {
get() {
try {
return getFn();
} catch (e) {
rethrowJavaAsJs(e);
}
}
};
if (setFn !== null) {
descriptor.set = function(value) {
try {
setFn(value);
} catch (e) {
rethrowJavaAsJs(e);
}
}
}
Object.defineProperty(cls, name, descriptor);
},
javaObjectToJS(instance, cls) {
let existing = jsWrappers.get(instance);
if (typeof existing != "undefined") {
@ -474,41 +598,26 @@ TeaVM.wasm = function() {
options.installImports(importObj);
}
return WebAssembly.instantiateStreaming(fetch(path), importObj).then((obj => {
let teavm = {};
teavm.main = createMain(obj.instance);
teavm.instance = obj.instance;
return teavm;
}));
}
function stringToJava(str) {
let sb = exports.createStringBuilder();
for (let i = 0; i < str.length; ++i) {
exports.appendChar(sb, str.charCodeAt(i));
}
return exports.buildString(sb);
}
function createMain(instance) {
return args => {
if (typeof args === "undefined") {
args = [];
}
return new Promise((resolve, reject) => {
exports = instance.exports;
let javaArgs = exports.createStringArray(args.length);
for (let i = 0; i < args.length; ++i) {
exports.setToStringArray(javaArgs, i, stringToJava(args[i]));
}
try {
exports.main(javaArgs);
} catch (e) {
reject(e);
return WebAssembly.instantiateStreaming(fetch(path), importObj)
.then(r => {
exports = r.instance.exports;
let userExports = {};
let teavm = {
exports: userExports,
instance: r.instance,
module: r.module
};
for (let key in r.instance.exports) {
let exportObj = r.instance.exports[key];
if (exportObj instanceof WebAssembly.Global) {
Object.defineProperty(userExports, key, {
get: () => exportObj.value
});
}
}
return teavm;
});
}
}
return { load };
return { load, defaults };
}();

View File

@ -72,7 +72,7 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
var hasExportedMembers = false;
hasExportedMembers |= exportClassInstanceMembers(classReader);
if (!className.equals(context.getEntryPoint())) {
var name = "$rt_export_class_ " + getClassAliasName(classReader) + "_" + lastExportIndex++;
var name = "$rt_export_class_" + getClassAliasName(classReader) + "_" + lastExportIndex++;
hasExportedMembers |= exportClassStaticMembers(classReader, name);
if (hasExportedMembers) {
exportedNamesByClass.put(className, name);
@ -213,10 +213,12 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
}
writer.append(")").ws().appendBlockStart();
if (method != null) {
writer.appendClass(cls.getName()).append(".call(this);").softNewLine();
writer.appendMethod(method).append("(this");
writer.append("return ").appendMethod(method).append("(");
for (var i = 0; i < method.parameterCount(); ++i) {
writer.append(",").ws().append("p" + i);
if (i > 0) {
writer.append(",").ws();
}
writer.append("p" + i);
}
writer.append(");").softNewLine();
} else {

View File

@ -0,0 +1,19 @@
/*
* 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.jso.impl;
public @interface JSClassObjectToExpose {
}

View File

@ -161,7 +161,6 @@ public final class JSMethods {
public static final String JS_MARSHALLABLE = JSMarshallable.class.getName();
public static final MethodDescriptor MARSHALL_TO_JS = new MethodDescriptor("marshallToJs", JS_OBJECT);
public static final MethodReference MARSHALL_TO_JS_REF = new MethodReference(JS_MARSHALLABLE, MARSHALL_TO_JS);
static {
for (int i = 0; i < INVOKE_METHODS.length; ++i) {

View File

@ -50,6 +50,7 @@ import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ConstructInstruction;
import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.IntegerConstantInstruction;
import org.teavm.model.instructions.InvocationType;
@ -120,11 +121,14 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
}
exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod);
exportStaticMethods(cls, context.getDiagnostics());
var hasStaticMethods = exportStaticMethods(cls, context.getDiagnostics());
if (isJavaScriptImplementation(cls) || !exposedClass.methods.isEmpty()) {
cls.getAnnotations().add(new AnnotationHolder(JSClassToExpose.class.getName()));
}
if (isJavaScriptImplementation(cls) || !exposedClass.methods.isEmpty() || hasStaticMethods) {
cls.getAnnotations().add(new AnnotationHolder(JSClassObjectToExpose.class.getName()));
}
if (wasmGC && (!exposedClass.methods.isEmpty() || isJavaScriptClass(cls))) {
var createWrapperMethod = new MethodHolder(JSMethods.MARSHALL_TO_JS);
@ -132,7 +136,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
createWrapperMethod.getModifiers().add(ElementModifier.NATIVE);
cls.addMethod(createWrapperMethod);
if (isJavaScriptImplementation(cls)) {
if (!isJavaScriptClass(cls) || isJavaScriptImplementation(cls)) {
cls.getInterfaces().add(JSMethods.JS_MARSHALLABLE);
}
}
@ -147,13 +151,17 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
MethodReference methodRef = new MethodReference(classHolder.getName(), method);
CallLocation callLocation = new CallLocation(methodRef);
var isConstructor = entry.getKey().getName().equals("<init>");
var paramCount = method.parameterCount();
if (export.vararg) {
--paramCount;
}
if (isConstructor) {
--paramCount;
}
var exportedMethodSignature = new ValueType[paramCount + 2];
Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT);
if (methodRef.getReturnType() == ValueType.VOID) {
if (methodRef.getReturnType() == ValueType.VOID && !isConstructor) {
exportedMethodSignature[exportedMethodSignature.length - 1] = ValueType.VOID;
}
MethodDescriptor exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++,
@ -164,7 +172,9 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
Program program = new Program();
exportedMethod.setProgram(program);
program.createVariable();
program.createVariable();
if (!isConstructor) {
program.createVariable();
}
BasicBlock basicBlock = program.createBasicBlock();
List<Instruction> marshallInstructions = new ArrayList<>();
@ -189,33 +199,52 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
basicBlock.addAll(marshallInstructions);
marshallInstructions.clear();
var unmarshalledInstance = new InvokeInstruction();
unmarshalledInstance.setType(InvocationType.SPECIAL);
unmarshalledInstance.setReceiver(program.createVariable());
unmarshalledInstance.setArguments(program.variableAt(1));
unmarshalledInstance.setMethod(new MethodReference(JSWrapper.class,
"unmarshallJavaFromJs", JSObject.class, Object.class));
basicBlock.add(unmarshalledInstance);
Variable receiverToPass;
if (isConstructor) {
var create = new ConstructInstruction();
create.setReceiver(program.createVariable());
create.setType(classHolder.getName());
basicBlock.add(create);
receiverToPass = create.getReceiver();
} else {
var unmarshalledInstance = new InvokeInstruction();
unmarshalledInstance.setType(InvocationType.SPECIAL);
unmarshalledInstance.setReceiver(program.createVariable());
unmarshalledInstance.setArguments(program.variableAt(1));
unmarshalledInstance.setMethod(new MethodReference(JSWrapper.class,
"unmarshallJavaFromJs", JSObject.class, Object.class));
basicBlock.add(unmarshalledInstance);
var castInstance = new CastInstruction();
castInstance.setValue(unmarshalledInstance.getReceiver());
castInstance.setReceiver(program.createVariable());
castInstance.setWeak(true);
castInstance.setTargetType(ValueType.object(classHolder.getName()));
basicBlock.add(castInstance);
var castInstance = new CastInstruction();
castInstance.setValue(unmarshalledInstance.getReceiver());
castInstance.setReceiver(program.createVariable());
castInstance.setWeak(true);
castInstance.setTargetType(ValueType.object(classHolder.getName()));
basicBlock.add(castInstance);
receiverToPass = castInstance.getReceiver();
}
InvokeInstruction invocation = new InvokeInstruction();
invocation.setType(method.getName().equals("<init>") ? InvocationType.SPECIAL : InvocationType.VIRTUAL);
invocation.setInstance(castInstance.getReceiver());
invocation.setType(isConstructor ? InvocationType.SPECIAL : InvocationType.VIRTUAL);
invocation.setInstance(receiverToPass);
invocation.setMethod(methodRef);
invocation.setArguments(variablesToPass);
basicBlock.add(invocation);
ExitInstruction exit = new ExitInstruction();
if (method.getResultType() != ValueType.VOID) {
invocation.setReceiver(program.createVariable());
exit.setValueToReturn(marshaller.wrapArgument(callLocation, invocation.getReceiver(),
method.getResultType(), JSType.MIXED, false));
if (method.getResultType() != ValueType.VOID || isConstructor) {
Variable result;
ValueType resultType;
if (isConstructor) {
result = receiverToPass;
resultType = ValueType.object(classHolder.getName());
} else {
result = program.createVariable();
invocation.setReceiver(result);
resultType = method.getResultType();
}
exit.setValueToReturn(marshaller.wrapArgument(callLocation, result, resultType, JSType.JAVA, false));
basicBlock.addAll(marshallInstructions);
marshallInstructions.clear();
}
@ -230,13 +259,15 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
}
}
private void exportStaticMethods(ClassHolder classHolder, Diagnostics diagnostics) {
private boolean exportStaticMethods(ClassHolder classHolder, Diagnostics diagnostics) {
int index = 0;
var hasMethods = false;
for (var method : classHolder.getMethods().toArray(new MethodHolder[0])) {
if (!method.hasModifier(ElementModifier.STATIC)
|| method.getAnnotations().get(JSExport.class.getName()) == null) {
continue;
}
hasMethods = true;
var paramCount = method.parameterCount();
var vararg = method.hasModifier(ElementModifier.VARARGS);
@ -246,6 +277,9 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
var callLocation = new CallLocation(method.getReference());
var exportedMethodSignature = new ValueType[paramCount + 1];
Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT);
if (method.getResultType() == ValueType.VOID) {
exportedMethodSignature[exportedMethodSignature.length - 1] = ValueType.VOID;
}
var exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++,
exportedMethodSignature);
var exportedMethod = new MethodHolder(exportedMethodDesc);
@ -287,7 +321,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
if (method.getResultType() != ValueType.VOID) {
invocation.setReceiver(program.createVariable());
exit.setValueToReturn(marshaller.wrapArgument(callLocation, invocation.getReceiver(),
method.getResultType(), JSType.MIXED, false));
method.getResultType(), JSType.JAVA, false));
basicBlock.addAll(marshallInstructions);
marshallInstructions.clear();
}
@ -298,6 +332,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
var export = createMethodExport(method);
exportedMethod.getAnnotations().add(createExportAnnotation(export));
}
return hasMethods;
}
private void transformVarargParam(Variable[] variablesToPass, Program program,

View File

@ -34,6 +34,19 @@ public class JSTypeHelper {
knownJavaScriptClasses.put(JSObject.class.getName(), true);
}
public JSType mapType(ValueType type) {
if (type instanceof ValueType.Object) {
var className = ((ValueType.Object) type).getClassName();
if (isJavaScriptClass(className)) {
return JSType.JS;
}
} else if (type instanceof ValueType.Array) {
var elementType = mapType(((ValueType.Array) type).getItemType());
return JSType.arrayOf(elementType);
}
return JSType.JAVA;
}
public boolean isJavaScriptClass(String className) {
Boolean isJsClass = knownJavaScriptClasses.get(className);
if (isJsClass == null) {

View File

@ -38,16 +38,7 @@ class JSTypeInference extends BaseTypeInference<JSType> {
@Override
protected JSType mapType(ValueType type) {
if (type instanceof ValueType.Object) {
var className = ((ValueType.Object) type).getClassName();
if (typeHelper.isJavaScriptClass(className)) {
return JSType.JS;
}
} else if (type instanceof ValueType.Array) {
var elementType = mapType(((ValueType.Array) type).getItemType());
return JSType.arrayOf(elementType);
}
return JSType.JAVA;
return typeHelper.mapType(type);
}
@Override

View File

@ -19,6 +19,7 @@ import org.teavm.backend.wasm.gc.TeaVMWasmGCHost;
import org.teavm.jso.JSObject;
import org.teavm.jso.impl.JS;
import org.teavm.jso.impl.JSBodyRepository;
import org.teavm.jso.impl.JSClassObjectToExpose;
import org.teavm.jso.impl.JSWrapper;
import org.teavm.model.MethodReference;
import org.teavm.vm.spi.TeaVMHost;
@ -35,6 +36,12 @@ public final class WasmGCJso {
wasmGCHost.addCustomTypeMapperFactory(new WasmGCJSTypeMapper());
wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository, jsFunctions, commonGen));
wasmGCHost.addGeneratorFactory(new WasmGCMarshallMethodGeneratorFactory(commonGen));
wasmGCHost.addClassConsumer((context, className) -> {
var cls = context.classes().get(className);
if (cls != null && cls.getAnnotations().get(JSClassObjectToExpose.class.getName()) != null) {
commonGen.getDefinedClass(WasmGCJsoContext.wrap(context), className);
}
});
var jsIntrinsic = new WasmGCJSIntrinsic(commonGen);
wasmGCHost.addIntrinsic(new MethodReference(JS.class, "wrap", String.class, JSObject.class), jsIntrinsic);

View File

@ -32,15 +32,20 @@ import org.teavm.backend.wasm.model.expression.WasmCast;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmExternConversion;
import org.teavm.backend.wasm.model.expression.WasmExternConversionType;
import org.teavm.backend.wasm.model.expression.WasmFunctionReference;
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.WasmThrow;
import org.teavm.jso.JSClass;
import org.teavm.jso.JSObject;
import org.teavm.jso.impl.AliasCollector;
import org.teavm.jso.impl.JSBodyAstEmitter;
import org.teavm.jso.impl.JSBodyBloatedEmitter;
import org.teavm.jso.impl.JSBodyEmitter;
import org.teavm.jso.impl.JSMarshallable;
import org.teavm.model.ClassReader;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
@ -51,6 +56,15 @@ class WasmGCJsoCommonGenerator {
private boolean rethrowExported;
private Map<String, WasmGlobal> stringsConstants = new HashMap<>();
private WasmFunction createClassFunction;
private WasmFunction defineFunctionFunction;
private WasmFunction defineMethodFunction;
private WasmFunction defineStaticMethodFunction;
private WasmFunction definePropertyFunction;
private WasmFunction defineStaticPropertyFunction;
private WasmFunction exportClassFunction;
private Map<String, WasmGlobal> definedClasses = new HashMap<>();
WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) {
this.jsFunctions = jsFunctions;
}
@ -70,7 +84,7 @@ class WasmGCJsoCommonGenerator {
}
}
void addInitializerPart(WasmGCJsoContext context, Consumer<WasmFunction> part) {
private void addInitializerPart(WasmGCJsoContext context, Consumer<WasmFunction> part) {
initialize(context);
initializerParts.add(part);
}
@ -183,4 +197,277 @@ class WasmGCJsoCommonGenerator {
});
return new WasmGetGlobal(global);
}
WasmGlobal getDefinedClass(WasmGCJsoContext context, String className) {
return definedClasses.computeIfAbsent(className, n -> defineClass(context, n));
}
private WasmGlobal defineClass(WasmGCJsoContext context, String className) {
var name = context.names().topLevel(context.names().suggestForClass(className + "@js"));
var global = new WasmGlobal(name, WasmType.Reference.EXTERN, new WasmNullConstant(WasmType.Reference.EXTERN));
context.module().globals.add(global);
var cls = context.classes().get(className);
var expressions = new ArrayList<WasmExpression>();
var isModule = context.entryPoint().equals(className);
var members = AliasCollector.collectMembers(cls, AliasCollector::isInstanceMember);
defineMethods(context, members, cls, global, expressions);
defineProperties(context, members, cls, global, expressions);
var staticMembers = AliasCollector.collectMembers(cls, AliasCollector::isStaticMember);
defineStaticMethods(context, staticMembers, cls, global, expressions, isModule);
defineStaticProperties(context, staticMembers, cls, global, expressions);
var simpleName = className.substring(className.lastIndexOf('.') + 1);
var javaClassName = context.strings().getStringConstant(simpleName);
var jsClassName = stringToJs(context, new WasmGetGlobal(javaClassName.global));
var exportedParent = parentExportedClass(context, cls.getParent());
var jsExportedParent = exportedParent != null
? new WasmGetGlobal(getDefinedClass(context, exportedParent))
: new WasmNullConstant(WasmType.Reference.EXTERN);
var needsExport = !className.equals(context.entryPoint())
&& (!staticMembers.methods.isEmpty() || !staticMembers.properties.isEmpty());
WasmExpression constructor;
if (members.constructor != null) {
var function = context.functions().forStaticMethod(members.constructor);
constructor = new WasmFunctionReference(function);
needsExport = true;
} else {
constructor = new WasmNullConstant(WasmType.Reference.FUNC);
}
var createClass = new WasmCall(createClassFunction(context), jsClassName, jsExportedParent, constructor);
expressions.add(0, new WasmSetGlobal(global, createClass));
if (needsExport) {
exportClass(context, cls, global, expressions);
}
context.addToInitializer(f -> f.getBody().addAll(expressions));
return global;
}
private void defineMethods(WasmGCJsoContext context, AliasCollector.Members members, ClassReader cls,
WasmGlobal global, List<WasmExpression> expressions) {
for (var aliasEntry : members.methods.entrySet()) {
if (!aliasEntry.getValue().getClassName().equals(cls.getName())) {
continue;
}
var fn = context.functions().forStaticMethod(aliasEntry.getValue());
fn.setReferenced(true);
var methodName = context.strings().getStringConstant(aliasEntry.getKey());
var jsMethodName = stringToJs(context, new WasmGetGlobal(methodName.global));
var defineMethod = new WasmCall(defineMethodFunction(context), new WasmGetGlobal(global),
jsMethodName, new WasmFunctionReference(fn));
expressions.add(defineMethod);
}
}
private void defineProperties(WasmGCJsoContext context, AliasCollector.Members members, ClassReader cls,
WasmGlobal global, List<WasmExpression> expressions) {
for (var aliasEntry : members.properties.entrySet()) {
var property = aliasEntry.getValue();
if (!property.getter.getClassName().equals(cls.getName())) {
continue;
}
var getter = context.functions().forStaticMethod(property.getter);
getter.setReferenced(true);
WasmFunction setter = null;
if (property.setter != null) {
setter = context.functions().forStaticMethod(property.setter);
setter.setReferenced(true);
}
var setterRef = setter != null
? new WasmFunctionReference(setter)
: new WasmNullConstant(WasmType.Reference.FUNC);
var methodName = context.strings().getStringConstant(aliasEntry.getKey());
var jsMethodName = stringToJs(context, new WasmGetGlobal(methodName.global));
var defineProperty = new WasmCall(definePropertyFunction(context), new WasmGetGlobal(global),
jsMethodName, new WasmFunctionReference(getter), setterRef);
expressions.add(defineProperty);
}
}
private void defineStaticMethods(WasmGCJsoContext context, AliasCollector.Members members, ClassReader cls,
WasmGlobal global, List<WasmExpression> expressions, boolean isModule) {
for (var aliasEntry : members.methods.entrySet()) {
if (!aliasEntry.getValue().getClassName().equals(cls.getName())) {
continue;
}
var fn = context.functions().forStaticMethod(aliasEntry.getValue());
fn.setReferenced(true);
if (isModule) {
var globalName = context.names().topLevel("teavm.js.export.function@" + aliasEntry.getKey());
var functionGlobal = new WasmGlobal(globalName, WasmType.Reference.EXTERN,
new WasmNullConstant(WasmType.Reference.EXTERN));
functionGlobal.setExportName(aliasEntry.getKey());
context.module().globals.add(functionGlobal);
fn.setReferenced(true);
var exportedFn = new WasmCall(defineFunctionFunction(context), new WasmFunctionReference(fn));
expressions.add(new WasmSetGlobal(functionGlobal, exportedFn));
}
var methodName = context.strings().getStringConstant(aliasEntry.getKey());
var jsMethodName = stringToJs(context, new WasmGetGlobal(methodName.global));
var defineMethod = new WasmCall(defineStaticMethodFunction(context), new WasmGetGlobal(global),
jsMethodName, new WasmFunctionReference(fn));
expressions.add(defineMethod);
}
}
private void defineStaticProperties(WasmGCJsoContext context, AliasCollector.Members members, ClassReader cls,
WasmGlobal global, List<WasmExpression> expressions) {
for (var aliasEntry : members.properties.entrySet()) {
var property = aliasEntry.getValue();
if (!property.getter.getClassName().equals(cls.getName())) {
continue;
}
var getter = context.functions().forStaticMethod(property.getter);
getter.setReferenced(true);
WasmFunction setter = null;
if (property.setter != null) {
setter = context.functions().forStaticMethod(property.setter);
setter.setReferenced(true);
}
var setterRef = setter != null
? new WasmFunctionReference(setter)
: new WasmNullConstant(WasmType.Reference.FUNC);
var methodName = context.strings().getStringConstant(aliasEntry.getKey());
var jsMethodName = stringToJs(context, new WasmGetGlobal(methodName.global));
var defineProperty = new WasmCall(defineStaticPropertyFunction(context), new WasmGetGlobal(global),
jsMethodName, new WasmFunctionReference(getter), setterRef);
expressions.add(defineProperty);
}
}
private void exportClass(WasmGCJsoContext context, ClassReader cls, WasmGlobal global,
List<WasmExpression> expressions) {
var exportName = getClassAliasName(cls);
var globalName = context.names().topLevel("teavm.js.export.class@" + exportName);
var exportGlobal = new WasmGlobal(globalName, WasmType.Reference.EXTERN,
new WasmNullConstant(WasmType.Reference.EXTERN));
exportGlobal.setExportName(exportName);
context.module().globals.add(exportGlobal);
var exported = new WasmCall(exportClassFunction(context), new WasmGetGlobal(global));
expressions.add(new WasmSetGlobal(exportGlobal, exported));
}
private String parentExportedClass(WasmGCJsoContext context, String className) {
while (className != null) {
var cls = context.classes().get(className);
if (cls == null) {
return null;
}
if (cls.getInterfaces().contains(JSMarshallable.class.getName())) {
return className;
}
className = cls.getParent();
}
return null;
}
private WasmFunction createClassFunction(WasmGCJsoContext context) {
if (createClassFunction == null) {
createClassFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN,
WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC));
createClassFunction.setName(context.names().suggestForClass("teavm.jso@createClass"));
createClassFunction.setImportName("createClass");
createClassFunction.setImportModule("teavmJso");
context.module().functions.add(createClassFunction);
}
return createClassFunction;
}
private WasmFunction defineFunctionFunction(WasmGCJsoContext context) {
if (defineFunctionFunction == null) {
defineFunctionFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN,
WasmType.Reference.FUNC));
defineFunctionFunction.setName(context.names().suggestForClass("teavm.jso@defineFunction"));
defineFunctionFunction.setImportName("defineFunction");
defineFunctionFunction.setImportModule("teavmJso");
context.module().functions.add(defineFunctionFunction);
}
return defineFunctionFunction;
}
private WasmFunction defineMethodFunction(WasmGCJsoContext context) {
if (defineMethodFunction == null) {
defineMethodFunction = new WasmFunction(context.functionTypes().of(null,
WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC));
defineMethodFunction.setName(context.names().suggestForClass("teavm.jso@defineMethod"));
defineMethodFunction.setImportName("defineMethod");
defineMethodFunction.setImportModule("teavmJso");
context.module().functions.add(defineMethodFunction);
}
return defineMethodFunction;
}
private WasmFunction defineStaticMethodFunction(WasmGCJsoContext context) {
if (defineStaticMethodFunction == null) {
defineStaticMethodFunction = new WasmFunction(context.functionTypes().of(null,
WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC));
defineStaticMethodFunction.setName(context.names().suggestForClass("teavm.jso@defineStaticMethod"));
defineStaticMethodFunction.setImportName("defineStaticMethod");
defineStaticMethodFunction.setImportModule("teavmJso");
context.module().functions.add(defineStaticMethodFunction);
}
return defineStaticMethodFunction;
}
private WasmFunction definePropertyFunction(WasmGCJsoContext context) {
if (definePropertyFunction == null) {
definePropertyFunction = new WasmFunction(context.functionTypes().of(null,
WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC,
WasmType.Reference.FUNC));
definePropertyFunction.setName(context.names().suggestForClass("teavm.jso@defineProperty"));
definePropertyFunction.setImportName("defineProperty");
definePropertyFunction.setImportModule("teavmJso");
context.module().functions.add(definePropertyFunction);
}
return definePropertyFunction;
}
private WasmFunction defineStaticPropertyFunction(WasmGCJsoContext context) {
if (defineStaticPropertyFunction == null) {
defineStaticPropertyFunction = new WasmFunction(context.functionTypes().of(null,
WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC,
WasmType.Reference.FUNC));
defineStaticPropertyFunction.setName(context.names().suggestForClass("teavm.jso@defineStaticProperty"));
defineStaticPropertyFunction.setImportName("defineStaticProperty");
defineStaticPropertyFunction.setImportModule("teavmJso");
context.module().functions.add(defineStaticPropertyFunction);
}
return defineStaticPropertyFunction;
}
private WasmFunction exportClassFunction(WasmGCJsoContext context) {
if (exportClassFunction == null) {
exportClassFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN,
WasmType.Reference.EXTERN));
exportClassFunction.setName(context.names().suggestForClass("teavm.jso@exportClass"));
exportClassFunction.setImportName("exportClass");
exportClassFunction.setImportModule("teavmJso");
context.module().functions.add(exportClassFunction);
}
return exportClassFunction;
}
private String getClassAliasName(ClassReader cls) {
var name = cls.getSimpleName();
if (name == null) {
name = cls.getName().substring(cls.getName().lastIndexOf('.') + 1);
}
var jsExport = cls.getAnnotations().get(JSClass.class.getName());
if (jsExport != null) {
var nameValue = jsExport.getValue("name");
if (nameValue != null) {
var nameValueString = nameValue.getString();
if (!nameValueString.isEmpty()) {
name = nameValueString;
}
}
}
return name;
}
}

View File

@ -18,6 +18,7 @@ package org.teavm.jso.impl.wasmgc;
import java.util.function.Consumer;
import org.teavm.backend.wasm.BaseWasmFunctionRepository;
import org.teavm.backend.wasm.WasmFunctionTypes;
import org.teavm.backend.wasm.gc.WasmGCClassConsumerContext;
import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider;
import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper;
import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider;
@ -26,8 +27,11 @@ import org.teavm.backend.wasm.intrinsics.gc.WasmGCIntrinsicContext;
import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.WasmTag;
import org.teavm.model.ClassReaderSource;
interface WasmGCJsoContext {
ClassReaderSource classes();
WasmModule module();
WasmFunctionTypes functionTypes();
@ -42,10 +46,17 @@ interface WasmGCJsoContext {
WasmTag exceptionTag();
String entryPoint();
void addToInitializer(Consumer<WasmFunction> initializerContributor);
static WasmGCJsoContext wrap(WasmGCIntrinsicContext context) {
return new WasmGCJsoContext() {
@Override
public ClassReaderSource classes() {
return context.hierarchy().getClassSource();
}
@Override
public WasmModule module() {
return context.module();
@ -81,6 +92,11 @@ interface WasmGCJsoContext {
return context.exceptionTag();
}
@Override
public String entryPoint() {
return context.entryPoint();
}
@Override
public void addToInitializer(Consumer<WasmFunction> initializerContributor) {
context.addToInitializer(initializerContributor);
@ -90,6 +106,11 @@ interface WasmGCJsoContext {
static WasmGCJsoContext wrap(WasmGCCustomGeneratorContext context) {
return new WasmGCJsoContext() {
@Override
public ClassReaderSource classes() {
return context.classes();
}
@Override
public WasmModule module() {
return context.module();
@ -125,6 +146,65 @@ interface WasmGCJsoContext {
return context.exceptionTag();
}
@Override
public String entryPoint() {
return context.entryPoint();
}
@Override
public void addToInitializer(Consumer<WasmFunction> initializerContributor) {
context.addToInitializer(initializerContributor);
}
};
}
static WasmGCJsoContext wrap(WasmGCClassConsumerContext context) {
return new WasmGCJsoContext() {
@Override
public ClassReaderSource classes() {
return context.classes();
}
@Override
public WasmModule module() {
return context.module();
}
@Override
public WasmFunctionTypes functionTypes() {
return context.functionTypes();
}
@Override
public BaseWasmFunctionRepository functions() {
return context.functions();
}
@Override
public WasmGCNameProvider names() {
return context.names();
}
@Override
public WasmGCStringProvider strings() {
return context.strings();
}
@Override
public WasmGCTypeMapper typeMapper() {
return context.typeMapper();
}
@Override
public WasmTag exceptionTag() {
return context.exceptionTag();
}
@Override
public String entryPoint() {
return context.entryPoint();
}
@Override
public void addToInitializer(Consumer<WasmFunction> initializerContributor) {
context.addToInitializer(initializerContributor);

View File

@ -15,31 +15,20 @@
*/
package org.teavm.jso.impl.wasmgc;
import java.util.ArrayList;
import org.teavm.backend.wasm.generators.gc.WasmGCCustomGenerator;
import org.teavm.backend.wasm.generators.gc.WasmGCCustomGeneratorContext;
import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmGlobal;
import org.teavm.backend.wasm.model.WasmLocal;
import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmFunctionReference;
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.jso.impl.AliasCollector;
import org.teavm.model.ClassReader;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
class WasmGCMarshallMethodGenerator implements WasmGCCustomGenerator {
private WasmGCJsoCommonGenerator commonGen;
private WasmFunction javaObjectToJSFunction;
private WasmFunction createClassFunction;
private WasmFunction defineMethodFunction;
private WasmFunction definePropertyFunction;
WasmGCMarshallMethodGenerator(WasmGCJsoCommonGenerator commonGen) {
this.commonGen = commonGen;
@ -52,8 +41,7 @@ class WasmGCMarshallMethodGenerator implements WasmGCCustomGenerator {
var thisLocal = new WasmLocal(context.typeMapper().mapType(ValueType.object(method.getClassName())), "this");
function.add(thisLocal);
var cls = context.classes().get(method.getClassName());
var jsClassGlobal = defineClass(jsoContext, cls);
var jsClassGlobal = commonGen.getDefinedClass(jsoContext, method.getClassName());
var wrapperFunction = javaObjectToJSFunction(context);
function.getBody().add(new WasmCall(wrapperFunction, new WasmGetLocal(thisLocal),
new WasmGetGlobal(jsClassGlobal)));
@ -71,91 +59,4 @@ class WasmGCMarshallMethodGenerator implements WasmGCCustomGenerator {
return javaObjectToJSFunction;
}
WasmGlobal defineClass(WasmGCJsoContext context, ClassReader cls) {
var name = context.names().topLevel(context.names().suggestForClass(cls.getName()));
var global = new WasmGlobal(name, WasmType.Reference.EXTERN, new WasmNullConstant(WasmType.Reference.EXTERN));
context.module().globals.add(global);
var expressions = new ArrayList<WasmExpression>();
var className = context.strings().getStringConstant(cls.getName());
var jsClassName = commonGen.stringToJs(context, new WasmGetGlobal(className.global));
var createClass = new WasmCall(createClassFunction(context), jsClassName);
expressions.add(new WasmSetGlobal(global, createClass));
var members = AliasCollector.collectMembers(cls, AliasCollector::isInstanceMember);
for (var aliasEntry : members.methods.entrySet()) {
if (!aliasEntry.getValue().getClassName().equals(cls.getName())) {
continue;
}
var fn = context.functions().forStaticMethod(aliasEntry.getValue());
fn.setReferenced(true);
var methodName = context.strings().getStringConstant(aliasEntry.getKey());
var jsMethodName = commonGen.stringToJs(context, new WasmGetGlobal(methodName.global));
var defineMethod = new WasmCall(defineMethodFunction(context), new WasmGetGlobal(global),
jsMethodName, new WasmFunctionReference(fn));
expressions.add(defineMethod);
}
for (var aliasEntry : members.properties.entrySet()) {
var property = aliasEntry.getValue();
if (!property.getter.getClassName().equals(cls.getName())) {
continue;
}
var getter = context.functions().forStaticMethod(property.getter);
getter.setReferenced(true);
WasmFunction setter = null;
if (property.setter != null) {
setter = context.functions().forStaticMethod(property.setter);
setter.setReferenced(true);
}
var setterRef = setter != null
? new WasmFunctionReference(setter)
: new WasmNullConstant(WasmType.Reference.FUNC);
var methodName = context.strings().getStringConstant(aliasEntry.getKey());
var jsMethodName = commonGen.stringToJs(context, new WasmGetGlobal(methodName.global));
var defineProperty = new WasmCall(definePropertyFunction(context), new WasmGetGlobal(global),
jsMethodName, new WasmFunctionReference(getter), setterRef);
expressions.add(defineProperty);
}
context.addToInitializer(f -> f.getBody().addAll(expressions));
return global;
}
private WasmFunction createClassFunction(WasmGCJsoContext context) {
if (createClassFunction == null) {
createClassFunction = new WasmFunction(context.functionTypes().of(WasmType.Reference.EXTERN,
WasmType.Reference.EXTERN));
createClassFunction.setName(context.names().suggestForClass("teavm.jso@createClass"));
createClassFunction.setImportName("createClass");
createClassFunction.setImportModule("teavmJso");
context.module().functions.add(createClassFunction);
}
return createClassFunction;
}
private WasmFunction defineMethodFunction(WasmGCJsoContext context) {
if (defineMethodFunction == null) {
defineMethodFunction = new WasmFunction(context.functionTypes().of(null,
WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC));
defineMethodFunction.setName(context.names().suggestForClass("teavm.jso@defineMethod"));
defineMethodFunction.setImportName("defineMethod");
defineMethodFunction.setImportModule("teavmJso");
context.module().functions.add(defineMethodFunction);
}
return defineMethodFunction;
}
private WasmFunction definePropertyFunction(WasmGCJsoContext context) {
if (definePropertyFunction == null) {
definePropertyFunction = new WasmFunction(context.functionTypes().of(null,
WasmType.Reference.EXTERN, WasmType.Reference.EXTERN, WasmType.Reference.FUNC,
WasmType.Reference.FUNC));
definePropertyFunction.setName(context.names().suggestForClass("teavm.jso@defineProperty"));
definePropertyFunction.setImportName("defineProperty");
definePropertyFunction.setImportModule("teavmJso");
context.module().functions.add(definePropertyFunction);
}
return definePropertyFunction;
}
}

View File

@ -22,7 +22,7 @@
</head>
<script type="text/javascript">
function launch() {
TeaVM.wasm.load("wasm-gc/benchmark.wasm").then(teavm => teavm.main());
TeaVM.wasmGC.load("wasm-gc/benchmark.wasm").then(teavm => teavm.exports.main([]));
}
</script>
<body onload="launch()">

View File

@ -30,7 +30,7 @@
<script type="application/javascript">
let runner = null;
function init() {
TeaVM.wasm.load("wasm-gc/pi.wasm", {
TeaVM.wasmGC.load("wasm-gc/pi.wasm", {
installImports(o) {
function putwchar(ch) {
$rt_putStdoutCustom(String.fromCharCode(ch));
@ -39,13 +39,12 @@
o.teavmConsole.putcharStdout = putwchar;
},
}).then(teavm => {
this.instance = teavm.instance;
runner = n => teavm.main([n.toString()]);
runner = n => teavm.exports.main([n.toString()]);
document.getElementById("run").disabled = false;
})
}
function calculate() {
var count = parseInt(document.getElementById("digit-count").value);
let count = parseInt(document.getElementById("digit-count").value);
runner(count);
}
init();

View File

@ -19,13 +19,20 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.teavm.backend.javascript.JSModuleType;
import org.teavm.backend.javascript.JavaScriptTarget;
import org.teavm.backend.wasm.WasmDebugInfoLevel;
import org.teavm.backend.wasm.WasmDebugInfoLocation;
import org.teavm.backend.wasm.WasmGCTarget;
import org.teavm.backend.wasm.disasm.Disassembler;
import org.teavm.backend.wasm.disasm.DisassemblyHTMLWriter;
import org.teavm.browserrunner.BrowserRunDescriptor;
import org.teavm.browserrunner.BrowserRunner;
import org.teavm.tooling.ConsoleTeaVMToolLog;
@ -35,8 +42,19 @@ import org.teavm.vm.TeaVMOptimizationLevel;
public class ExportTest {
private static File targetFile = new File(new File(System.getProperty("teavm.junit.target")), "jso-export");
private static BrowserRunner runner = new BrowserRunner(
targetFile,
private static File jsTargetFile = new File(targetFile, "js");
private static File wasmGCTargetFile = new File(targetFile, "wasm-gc");
private static boolean jsNeeded = Boolean.parseBoolean(System.getProperty("teavm.junit.js", "true"));
private static boolean wasmGCNeeded = Boolean.parseBoolean(System.getProperty("teavm.junit.wasm-gc", "true"));
private static BrowserRunner jsRunner = new BrowserRunner(
jsTargetFile,
"JAVASCRIPT",
BrowserRunner.pickBrowser(System.getProperty("teavm.junit.js.runner")),
false
);
private static BrowserRunner wasmGCRunner = new BrowserRunner(
wasmGCTargetFile,
"JAVASCRIPT",
BrowserRunner.pickBrowser(System.getProperty("teavm.junit.js.runner")),
false
@ -44,12 +62,22 @@ public class ExportTest {
@BeforeClass
public static void start() {
runner.start();
if (jsNeeded) {
jsRunner.start();
}
if (wasmGCNeeded) {
wasmGCRunner.start();
}
}
@AfterClass
public static void stop() {
runner.stop();
if (jsNeeded) {
jsRunner.stop();
}
if (wasmGCNeeded) {
wasmGCRunner.stop();
}
}
@Test
@ -69,7 +97,7 @@ public class ExportTest {
@Test
public void exportClassMembers() {
testExport("exportClassMembers", ModuleWithExportedClassMembers.class);
testExport("exportClassMembers", ModuleWithExportedClassMembers.class, true);
}
@Test
@ -79,24 +107,34 @@ public class ExportTest {
@Test
public void exportClasses() {
testExport("exportClasses", ModuleWithExportedClasses.class);
testExport("exportClasses", ModuleWithExportedClasses.class, true);
}
@Test
public void varargs() {
testExport("varargs", ModuleWithVararg.class);
testExport("varargs", ModuleWithVararg.class, true);
}
private void testExport(String name, Class<?> moduleClass) {
if (!Boolean.parseBoolean(System.getProperty("teavm.junit.js", "true"))) {
return;
testExport(name, moduleClass, false);
}
private void testExport(String name, Class<?> moduleClass, boolean skipWasm) {
if (jsNeeded) {
testExportJs(name, moduleClass);
}
if (wasmGCNeeded && !skipWasm) {
testExportWasmGC(name, moduleClass);
}
}
private void testExportJs(String name, Class<?> moduleClass) {
try {
var jsTarget = new JavaScriptTarget();
jsTarget.setModuleType(JSModuleType.ES2015);
jsTarget.setObfuscated(false);
var teavm = new TeaVMBuilder(jsTarget).build();
var outputDir = new File(targetFile, name);
var outputDir = new File(jsTargetFile, name);
teavm.installPlugins();
teavm.setEntryPoint(moduleClass.getName());
teavm.setOptimizationLevel(TeaVMOptimizationLevel.ADVANCED);
@ -115,10 +153,64 @@ public class ExportTest {
writer.write(" test().then(() => callback()).catch(e => callback(e));\n");
writer.write("}\n");
}
var testProviderFile = new File(outputDir, "provider.js");
try (var writer = new OutputStreamWriter(new FileOutputStream(testProviderFile), StandardCharsets.UTF_8)) {
writer.write("import * as obj from '/tests/" + name + "/test.js';\n");
writer.write("export default Promise.resolve(obj);");
}
var descriptor = new BrowserRunDescriptor(name, "tests/" + name + "/runner.js", true,
List.of("resources/org/teavm/jso/export/assert.js"), null);
runner.runTest(descriptor);
jsRunner.runTest(descriptor);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void testExportWasmGC(String name, Class<?> moduleClass) {
try {
var wasmGCTarget = new WasmGCTarget();
wasmGCTarget.setObfuscated(false);
wasmGCTarget.setDebugLocation(WasmDebugInfoLocation.EMBEDDED);
wasmGCTarget.setDebugLevel(WasmDebugInfoLevel.DEOBFUSCATION);
var teavm = new TeaVMBuilder(wasmGCTarget).build();
var outputDir = new File(wasmGCTargetFile, name);
teavm.installPlugins();
teavm.setEntryPoint(moduleClass.getName());
teavm.setOptimizationLevel(TeaVMOptimizationLevel.ADVANCED);
outputDir.mkdirs();
teavm.build(outputDir, "test.wasm");
if (!teavm.getProblemProvider().getSevereProblems().isEmpty()) {
var log = new ConsoleTeaVMToolLog(false);
TeaVMProblemRenderer.describeProblems(teavm, log);
throw new RuntimeException("TeaVM compilation error");
}
var disassemblyFile = new File(outputDir, "test.wast.html");
try (var output = new PrintWriter(new OutputStreamWriter(new FileOutputStream(disassemblyFile)))) {
var disassemblyWriter = new DisassemblyHTMLWriter(output);
disassemblyWriter.setWithAddress(true);
var disassembler = new Disassembler(disassemblyWriter);
disassembler.disassemble(Files.readAllBytes(new File(outputDir, "test.wasm").toPath()));
}
var testRunnerFile = new File(outputDir, "runner.js");
try (var writer = new OutputStreamWriter(new FileOutputStream(testRunnerFile), StandardCharsets.UTF_8)) {
writer.write("import { test } from '/resources/org/teavm/jso/export/" + name + ".js';\n");
writer.write("export function main(args, callback) {\n");
writer.write(" test().then(() => callback()).catch(e => callback(e));\n");
writer.write("}\n");
}
var testProviderFile = new File(outputDir, "provider.js");
try (var writer = new OutputStreamWriter(new FileOutputStream(testProviderFile), StandardCharsets.UTF_8)) {
writer.write("await import('/resources/org/teavm/backend/wasm/wasm-gc-runtime.js');\n");
writer.write("let teavm = await TeaVM.wasmGC.load('/tests/" + name + "/test.wasm');\n");
writer.write("export default teavm.exports;\n");
}
var descriptor = new BrowserRunDescriptor(name, "tests/" + name + "/runner.js", true,
List.of("resources/org/teavm/jso/export/assert.js"), null);
wasmGCRunner.runTest(descriptor);
} catch (IOException e) {
throw new RuntimeException(e);
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createObject, consumeObject, C } from '/tests/exportClassMembers/test.js';
const { createObject, consumeObject, C } = await (await import('/tests/exportClassMembers/provider.js')).default;
export async function test() {
let o = createObject("qwe");

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { A, BB } from '/tests/exportClasses/test.js';
const { A, BB } = await (await import("/tests/exportClasses/provider.js")).default;
export async function test() {
assertEquals(23, A.foo());

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { takeObject } from '/tests/importClassMembers/test.js';
const { takeObject } = await (await import('/tests/importClassMembers/provider.js')).default;
export async function test() {
assertEquals("object taken: foo = 23, bar = qw", takeObject({

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { foo, bar, getCount, getAnotherCount } from '/tests/initializer/test.js';
const { foo, bar, getCount, getAnotherCount } = await (await import('/tests/initializer/provider.js')).default;
export async function test() {
assertEquals("foo", foo());

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as java from '/tests/primitives/test.js';
const java = await (await import('/tests/primitives/provider.js')).default;
function testReturnPrimitives() {
assertEquals(true, java.boolResult());

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { foo } from '/tests/simple/test.js';
const { foo } = await (await import('/tests/simple/provider.js')).default;
export async function test() {
assertEquals(23, foo());

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import * as varargs from '/tests/varargs/test.js';
const varargs = await (await import('/tests/varargs/provider.js')).default;
export async function test() {
assertEquals("strings: a, b", varargs.strings("a", "b"));

View File

@ -210,9 +210,7 @@ function launchWasmGCTest(file, argument, callback) {
}
}
let instance = null;
TeaVM.wasm.load(file.path, {
TeaVM.wasmGC.load(file.path, {
installImports: function(o) {
o.teavmConsole.putcharStdout = putchar;
o.teavmConsole.putcharStderr = putcharStderr;
@ -220,22 +218,16 @@ function launchWasmGCTest(file, argument, callback) {
success() {
callback(wrapResponse({ status: "OK" }));
},
failure(javaString) {
let jsString = "";
let length = instance.exports.stringLength(javaString);
for (let i = 0; i < length; ++i) {
jsString += String.fromCharCode(instance.exports.charAt(javaString, i));
}
failure(message) {
callback(wrapResponse({
status: "failed",
errorMessage: jsString
errorMessage: message
}));
}
};
}
}).then(teavm => {
instance = teavm.instance;
return teavm.main(argument ? [argument] : []);
return teavm.exports.main(argument ? [argument] : []);
}).catch(err => {
callback(wrapResponse({
status: "failed",

View File

@ -17,6 +17,7 @@ package org.teavm.junit;
import org.teavm.classlib.impl.console.JSConsoleStringPrintStream;
import org.teavm.interop.Import;
import org.teavm.jso.core.JSString;
final class TestWasmGCEntryPoint {
private TestWasmGCEntryPoint() {
@ -29,7 +30,7 @@ final class TestWasmGCEntryPoint {
} catch (Throwable e) {
var out = new JSConsoleStringPrintStream();
e.printStackTrace(out);
reportFailure(out.toString());
reportFailure(JSString.valueOf(out.toString()));
}
TestEntryPoint.run(args.length > 0 ? args[0] : null);
}
@ -38,5 +39,5 @@ final class TestWasmGCEntryPoint {
private static native void reportSuccess();
@Import(module = "teavmTest", name = "failure")
private static native void reportFailure(String message);
private static native void reportFailure(JSString message);
}

View File

@ -24,7 +24,7 @@
<script type="text/javascript" src="${SCRIPT}-runtime.js"></script>
<script type="text/javascript">
let instance;
TeaVM.wasm.load("${SCRIPT}", {
TeaVM.wasmGC.load("${SCRIPT}", {
installImports(o) {
o.teavmTest = {
success() {
@ -32,22 +32,14 @@
document.body.appendChild(pre);
pre.appendChild(document.createTextNode("OK"));
},
failure(javaString) {
let jsString = "";
let length = instance.exports.stringLength(javaString);
for (let i = 0; i < length; ++i) {
jsString += String.fromCharCode(instance.exports.charAt(javaString, i));
}
var pre = document.createElement("pre");
failure(message) {
let pre = document.createElement("pre");
document.body.appendChild(pre);
pre.appendChild(document.createTextNode(jsString));
pre.appendChild(document.createTextNode(message));
}
};
}
}).then(teavm => {
instance = teavm.instance;
teavm.main(["${IDENTIFIER}"])
});
}).then(teavm => teavm.exports.main(["${IDENTIFIER}"]));
</script>
</body>
</html>