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.Platforms;
import org.teavm.interop.Unmanaged; import org.teavm.interop.Unmanaged;
import org.teavm.interop.UnsupportedOn; import org.teavm.interop.UnsupportedOn;
import org.teavm.jso.JSObject;
import org.teavm.jso.core.JSDate; import org.teavm.jso.core.JSDate;
import org.teavm.jso.impl.JS;
public class TDate implements TComparable<TDate> { public class TDate implements TComparable<TDate> {
private long value; private long value;
@ -420,7 +422,7 @@ public class TDate implements TComparable<TDate> {
} else if (PlatformDetector.isWebAssembly()) { } else if (PlatformDetector.isWebAssembly()) {
return toStringWebAssembly(value); return toStringWebAssembly(value);
} else if (PlatformDetector.isWebAssemblyGC()) { } else if (PlatformDetector.isWebAssemblyGC()) {
return toStringWebAssemblyGC(value); return JS.unwrapString(toStringWebAssemblyGC(value));
} else { } else {
return JSDate.create(value).stringValue(); return JSDate.create(value).stringValue();
} }
@ -435,7 +437,7 @@ public class TDate implements TComparable<TDate> {
private static native String toStringWebAssembly(double date); private static native String toStringWebAssembly(double date);
@Import(module = "teavmDate", name = "dateToString") @Import(module = "teavmDate", name = "dateToString")
private static native String toStringWebAssemblyGC(double date); private static native JSObject toStringWebAssemblyGC(double date);
@Deprecated @Deprecated
public String toLocaleString() { 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.generate.gc.WasmGCDeclarationsGenerator;
import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.runtime.StringInternPool; import org.teavm.backend.wasm.runtime.StringInternPool;
import org.teavm.backend.wasm.runtime.gc.WasmGCSupport;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
@ -34,47 +33,6 @@ public class WasmGCModuleGenerator {
createInitializer(); 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() { public WasmFunction generateReportGarbageCollectedStringFunction() {
var entryType = ValueType.object(StringInternPool.class.getName() + "$Entry"); var entryType = ValueType.object(StringInternPool.class.getName() + "$Entry");
return declarationsGenerator.functions().forStaticMethod(new MethodReference( return declarationsGenerator.functions().forStaticMethod(new MethodReference(

View File

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

View File

@ -33,4 +33,6 @@ public interface TeaVMWasmGCHost extends TeaVMHostExtension {
void addGenerator(MethodReference method, WasmGCCustomGenerator generator); void addGenerator(MethodReference method, WasmGCCustomGenerator generator);
void addCustomTypeMapperFactory(WasmGCCustomTypeMapperFactory customTypeMapperFactory); 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.BaseWasmFunctionRepository;
import org.teavm.backend.wasm.WasmFunctionTypes; 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.WasmGCCustomGeneratorProvider;
import org.teavm.backend.wasm.generate.gc.methods.WasmGCIntrinsicProvider; 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.methods.WasmGCMethodGenerator;
import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider;
import org.teavm.backend.wasm.model.WasmFunction; import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.WasmTag;
import org.teavm.dependency.DependencyInfo; import org.teavm.dependency.DependencyInfo;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassHierarchy; import org.teavm.model.ClassHierarchy;
@ -62,7 +65,8 @@ public class WasmGCDeclarationsGenerator {
WasmGCIntrinsicProvider intrinsics, WasmGCIntrinsicProvider intrinsics,
List<WasmGCCustomTypeMapperFactory> customTypeMapperFactories, List<WasmGCCustomTypeMapperFactory> customTypeMapperFactories,
Predicate<MethodReference> isVirtual, Predicate<MethodReference> isVirtual,
boolean strict boolean strict,
String entryPoint
) { ) {
this.module = module; this.module = module;
hierarchy = new ClassHierarchy(classes); hierarchy = new ClassHierarchy(classes);
@ -82,6 +86,7 @@ public class WasmGCDeclarationsGenerator {
customGenerators, customGenerators,
intrinsics, intrinsics,
strict, strict,
entryPoint,
initializerContributors::add initializerContributors::add
); );
var tags = new TagRegistry(classes, hierarchy); var tags = new TagRegistry(classes, hierarchy);
@ -157,4 +162,20 @@ public class WasmGCDeclarationsGenerator {
public WasmFunction dummyInitializer() { public WasmFunction dummyInitializer() {
return methodGenerator.getDummyInitializer(); 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 Map<String, Set<String>> interfaceImplementors;
private WasmGCNameProvider names; private WasmGCNameProvider names;
private boolean strict; private boolean strict;
private String entryPoint;
private Consumer<WasmGCInitializerContributor> initializerContributors; private Consumer<WasmGCInitializerContributor> initializerContributors;
public WasmGCGenerationContext(WasmModule module, WasmGCVirtualTableProvider virtualTables, public WasmGCGenerationContext(WasmModule module, WasmGCVirtualTableProvider virtualTables,
@ -73,7 +74,8 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
WasmGCSupertypeFunctionProvider supertypeFunctions, WasmGCClassInfoProvider classInfoProvider, WasmGCSupertypeFunctionProvider supertypeFunctions, WasmGCClassInfoProvider classInfoProvider,
WasmGCStandardClasses standardClasses, WasmGCStringProvider strings, WasmGCStandardClasses standardClasses, WasmGCStringProvider strings,
WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics, WasmGCCustomGeneratorProvider customGenerators, WasmGCIntrinsicProvider intrinsics,
WasmGCNameProvider names, boolean strict, Consumer<WasmGCInitializerContributor> initializerContributors) { WasmGCNameProvider names, boolean strict, String entryPoint,
Consumer<WasmGCInitializerContributor> initializerContributors) {
this.module = module; this.module = module;
this.virtualTables = virtualTables; this.virtualTables = virtualTables;
this.typeMapper = typeMapper; this.typeMapper = typeMapper;
@ -90,6 +92,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
this.intrinsics = intrinsics; this.intrinsics = intrinsics;
this.names = names; this.names = names;
this.strict = strict; this.strict = strict;
this.entryPoint = entryPoint;
this.initializerContributors = initializerContributors; this.initializerContributors = initializerContributors;
} }
@ -109,6 +112,10 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
return strings; return strings;
} }
public String entryPoint() {
return entryPoint;
}
public WasmGCVirtualTableProvider virtualTables() { public WasmGCVirtualTableProvider virtualTables() {
return virtualTables; return virtualTables;
} }
@ -136,7 +143,7 @@ public class WasmGCGenerationContext implements BaseWasmGenerationContext {
if (exceptionTag == null) { if (exceptionTag == null) {
exceptionTag = new WasmTag(functionTypes.of(null, exceptionTag = new WasmTag(functionTypes.of(null,
classInfoProvider.getClassInfo("java.lang.Throwable").getStructure().getReference())); classInfoProvider.getClassInfo("java.lang.Throwable").getStructure().getReference()));
exceptionTag.setExportName("javaException"); exceptionTag.setExportName("teavm.javaException");
module.tags.add(exceptionTag); module.tags.add(exceptionTag);
} }
return exceptionTag; return exceptionTag;

View File

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

View File

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

View File

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

View File

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

View File

@ -23,6 +23,7 @@ public class WasmGlobal extends WasmEntity {
private WasmType type; private WasmType type;
private WasmExpression initialValue; private WasmExpression initialValue;
private boolean immutable; private boolean immutable;
private String exportName;
public WasmGlobal(String name, WasmType type, WasmExpression initialValue) { public WasmGlobal(String name, WasmType type, WasmExpression initialValue) {
this.name = name; this.name = name;
@ -57,4 +58,12 @@ public class WasmGlobal extends WasmEntity {
public void setImmutable(boolean immutable) { public void setImmutable(boolean immutable) {
this.immutable = 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<WasmGlobal> globals = new WasmCollection<>();
public final WasmCollection<WasmCompositeType> types = new WasmCollection<>(); public final WasmCollection<WasmCompositeType> types = new WasmCollection<>();
public final WasmCollection<WasmTag> tags = new WasmCollection<>(); public final WasmCollection<WasmTag> tags = new WasmCollection<>();
public String memoryExportName = "memory";
public void add(WasmCustomSection customSection) { public void add(WasmCustomSection customSection) {
if (customSections.containsKey(customSection.getName())) { 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_FUNCTION = 0;
private static final int EXTERNAL_KIND_MEMORY = 2; 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 static final int EXTERNAL_KIND_TAG = 4;
private WasmBinaryWriter output; private WasmBinaryWriter output;
@ -241,7 +242,11 @@ public class WasmBinaryRenderer {
.filter(tag -> tag.getExportName() != null) .filter(tag -> tag.getExportName() != null)
.collect(Collectors.toList()); .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) { for (var function : functions) {
int functionIndex = module.functions.indexOf(function); int functionIndex = module.functions.indexOf(function);
@ -257,9 +262,16 @@ public class WasmBinaryRenderer {
section.writeByte(EXTERNAL_KIND_TAG); section.writeByte(EXTERNAL_KIND_TAG);
section.writeLEB(tagIndex); 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 // We also need to export the memory to make it accessible
section.writeAsciiString("memory"); section.writeAsciiString(module.memoryExportName);
section.writeByte(EXTERNAL_KIND_MEMORY); section.writeByte(EXTERNAL_KIND_MEMORY);
section.writeLEB(0); 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; return;
} }
processEntryPoint(); target.setEntryPoint(entryPoint, entryPointName);
dependencyAnalyzer.setAsyncSupported(target.isAsyncSupported()); dependencyAnalyzer.setAsyncSupported(target.isAsyncSupported());
dependencyAnalyzer.setInterruptor(() -> { dependencyAnalyzer.setInterruptor(() -> {
int progress = dependencyAnalyzer.getReachableClasses().size(); int progress = dependencyAnalyzer.getReachableClasses().size();
@ -390,6 +390,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
}); });
target.contributeDependencies(dependencyAnalyzer); target.contributeDependencies(dependencyAnalyzer);
dependencyAnalyzer.initDependencies(); dependencyAnalyzer.initDependencies();
processEntryPoint();
if (target.needsSystemArrayCopyOptimization()) { if (target.needsSystemArrayCopyOptimization()) {
dependencyAnalyzer.addDependencyListener(new StdlibDependencyListener()); dependencyAnalyzer.addDependencyListener(new StdlibDependencyListener());
} }

View File

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

View File

@ -15,9 +15,14 @@
*/ */
var TeaVM = TeaVM || {}; var TeaVM = TeaVM || {};
TeaVM.wasm = function() { if (window && window.TeaVM === undefined) {
window.TeaVM = TeaVM;
}
TeaVM.wasmGC = TeaVM.wasmGC || function() {
let exports; let exports;
let globalsCache = new Map(); let globalsCache = new Map();
let stackDeobfuscator = null;
let chromeExceptionRegex = / *at .+\.wasm:wasm-function\[[0-9]+]:0x([0-9a-f]+).*/;
let getGlobalName = function(name) { let getGlobalName = function(name) {
let result = globalsCache.get(name); let result = globalsCache.get(name);
if (typeof result === "undefined") { if (typeof result === "undefined") {
@ -45,11 +50,11 @@ TeaVM.wasm = function() {
this[javaExceptionSymbol] = javaException; this[javaExceptionSymbol] = javaException;
} }
get message() { get message() {
let exceptionMessage = exports.exceptionMessage; let exceptionMessage = exports["teavm.exceptionMessage"];
if (typeof exceptionMessage === "function") { if (typeof exceptionMessage === "function") {
let message = exceptionMessage(this[javaExceptionSymbol]); let message = exceptionMessage(this[javaExceptionSymbol]);
if (message != null) { if (message != null) {
return stringToJava(message); return message;
} }
} }
return "(could not fetch message)"; return "(could not fetch message)";
@ -59,7 +64,7 @@ TeaVM.wasm = function() {
function dateImports(imports) { function dateImports(imports) {
imports.teavmDate = { imports.teavmDate = {
currentTimeMillis: () => new Date().getTime(), currentTimeMillis: () => new Date().getTime(),
dateToString: timestamp => stringToJava(new Date(timestamp).toString()), dateToString: timestamp => new Date(timestamp).toString(),
getYear: timestamp => new Date(timestamp).getFullYear(), getYear: timestamp => new Date(timestamp).getFullYear(),
setYear(timestamp, year) { setYear(timestamp, year) {
let date = new Date(timestamp); let date = new Date(timestamp);
@ -108,12 +113,16 @@ TeaVM.wasm = function() {
function coreImports(imports) { function coreImports(imports) {
let finalizationRegistry = new FinalizationRegistry(heldValue => { let finalizationRegistry = new FinalizationRegistry(heldValue => {
if (typeof exports.reportGarbageCollectedValue === "function") { let report = exports["teavm.reportGarbageCollectedValue"];
exports.reportGarbageCollectedValue(heldValue) if (typeof report === "function") {
report(heldValue)
} }
}); });
let stringFinalizationRegistry = new FinalizationRegistry(heldValue => { let stringFinalizationRegistry = new FinalizationRegistry(heldValue => {
exports.reportGarbageCollectedString(heldValue); let report = exports["teavm.reportGarbageCollectedString"];
if (typeof report === "function") {
report(heldValue);
}
}); });
imports.teavm = { imports.teavm = {
createWeakRef(value, heldValue) { createWeakRef(value, heldValue) {
@ -129,7 +138,28 @@ TeaVM.wasm = function() {
stringFinalizationRegistry.register(value, heldValue) stringFinalizationRegistry.register(value, heldValue)
return weakRef; 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 javaObjectSymbol = Symbol("javaObject");
let functionsSymbol = Symbol("functions"); let functionsSymbol = Symbol("functions");
let functionOriginSymbol = Symbol("functionOrigin"); let functionOriginSymbol = Symbol("functionOrigin");
let wrapperCallMarkerSymbol = Symbol("wrapperCallMarker");
let jsWrappers = new WeakMap(); let jsWrappers = new WeakMap();
let javaWrappers = new WeakMap(); let javaWrappers = new WeakMap();
@ -182,7 +213,7 @@ TeaVM.wasm = function() {
} }
function javaExceptionToJs(e) { function javaExceptionToJs(e) {
if (e instanceof WebAssembly.Exception) { if (e instanceof WebAssembly.Exception) {
let tag = exports["javaException"]; let tag = exports["teavm.javaException"];
if (e.is(tag)) { if (e.is(tag)) {
let javaException = e.getArg(tag, 0); let javaException = e.getArg(tag, 0);
let extracted = extractException(javaException); let extracted = extractException(javaException);
@ -226,6 +257,22 @@ TeaVM.wasm = function() {
rethrowJsAsJava(e); 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 = { imports.teavmJso = {
emptyString: () => "", emptyString: () => "",
stringFromCharCode: code => String.fromCharCode(code), stringFromCharCode: code => String.fromCharCode(code),
@ -247,16 +294,68 @@ TeaVM.wasm = function() {
rethrowJsAsJava(e); rethrowJsAsJava(e);
} }
}, },
createClass(name) { createClass(name, parent, constructor) {
name = sanitizeName(name);
if (parent === null) {
let fn = new Function( let fn = new Function(
"javaObjectSymbol", "javaObjectSymbol",
"functionsSymbol", "functionsSymbol",
`return function JavaClass_${sanitizeName(name)}(javaObject) { "wrapperCallMarker",
"constructor",
"rethrowJavaAsJs",
`let fn;
fn = function ${name}(marker, javaObject) {
if (marker === wrapperCallMarker) {
this[javaObjectSymbol] = javaObject; this[javaObjectSymbol] = javaObject;
this[functionsSymbol] = null; 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, functionOriginSymbol); 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) { defineMethod(cls, name, fn) {
let params = []; let params = [];
@ -274,6 +373,10 @@ TeaVM.wasm = function() {
}; };
`)(rethrowJavaAsJs, fn); `)(rethrowJavaAsJs, fn);
}, },
defineStaticMethod(cls, name, fn) {
cls[name] = defineFunction(fn);
},
defineFunction: defineFunction,
defineProperty(cls, name, getFn, setFn) { defineProperty(cls, name, getFn, setFn) {
let descriptor = { let descriptor = {
get() { get() {
@ -295,6 +398,27 @@ TeaVM.wasm = function() {
} }
Object.defineProperty(cls.prototype, name, descriptor); 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) { javaObjectToJS(instance, cls) {
let existing = jsWrappers.get(instance); let existing = jsWrappers.get(instance);
if (typeof existing != "undefined") { if (typeof existing != "undefined") {
@ -474,41 +598,26 @@ TeaVM.wasm = function() {
options.installImports(importObj); options.installImports(importObj);
} }
return WebAssembly.instantiateStreaming(fetch(path), importObj).then((obj => { return WebAssembly.instantiateStreaming(fetch(path), importObj)
let teavm = {}; .then(r => {
teavm.main = createMain(obj.instance); exports = r.instance.exports;
teavm.instance = obj.instance; let userExports = {};
return teavm; let teavm = {
})); exports: userExports,
} instance: r.instance,
module: r.module
function stringToJava(str) { };
let sb = exports.createStringBuilder(); for (let key in r.instance.exports) {
for (let i = 0; i < str.length; ++i) { let exportObj = r.instance.exports[key];
exports.appendChar(sb, str.charCodeAt(i)); if (exportObj instanceof WebAssembly.Global) {
} Object.defineProperty(userExports, key, {
return exports.buildString(sb); get: () => exportObj.value
}
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 teavm;
});
}
return { load }; return { load, defaults };
}(); }();

View File

@ -213,10 +213,12 @@ class JSAliasRenderer implements RendererListener, MethodContributor {
} }
writer.append(")").ws().appendBlockStart(); writer.append(")").ws().appendBlockStart();
if (method != null) { if (method != null) {
writer.appendClass(cls.getName()).append(".call(this);").softNewLine(); writer.append("return ").appendMethod(method).append("(");
writer.appendMethod(method).append("(this");
for (var i = 0; i < method.parameterCount(); ++i) { 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(); writer.append(");").softNewLine();
} else { } 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 String JS_MARSHALLABLE = JSMarshallable.class.getName();
public static final MethodDescriptor MARSHALL_TO_JS = new MethodDescriptor("marshallToJs", JS_OBJECT); 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 { static {
for (int i = 0; i < INVOKE_METHODS.length; ++i) { 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.ValueType;
import org.teavm.model.Variable; import org.teavm.model.Variable;
import org.teavm.model.instructions.CastInstruction; import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ConstructInstruction;
import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.IntegerConstantInstruction; import org.teavm.model.instructions.IntegerConstantInstruction;
import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvocationType;
@ -120,11 +121,14 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
} }
exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod); exposeMethods(cls, exposedClass, context.getDiagnostics(), functorMethod);
exportStaticMethods(cls, context.getDiagnostics()); var hasStaticMethods = exportStaticMethods(cls, context.getDiagnostics());
if (isJavaScriptImplementation(cls) || !exposedClass.methods.isEmpty()) { if (isJavaScriptImplementation(cls) || !exposedClass.methods.isEmpty()) {
cls.getAnnotations().add(new AnnotationHolder(JSClassToExpose.class.getName())); 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))) { if (wasmGC && (!exposedClass.methods.isEmpty() || isJavaScriptClass(cls))) {
var createWrapperMethod = new MethodHolder(JSMethods.MARSHALL_TO_JS); var createWrapperMethod = new MethodHolder(JSMethods.MARSHALL_TO_JS);
@ -132,7 +136,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
createWrapperMethod.getModifiers().add(ElementModifier.NATIVE); createWrapperMethod.getModifiers().add(ElementModifier.NATIVE);
cls.addMethod(createWrapperMethod); cls.addMethod(createWrapperMethod);
if (isJavaScriptImplementation(cls)) { if (!isJavaScriptClass(cls) || isJavaScriptImplementation(cls)) {
cls.getInterfaces().add(JSMethods.JS_MARSHALLABLE); cls.getInterfaces().add(JSMethods.JS_MARSHALLABLE);
} }
} }
@ -147,13 +151,17 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
MethodReference methodRef = new MethodReference(classHolder.getName(), method); MethodReference methodRef = new MethodReference(classHolder.getName(), method);
CallLocation callLocation = new CallLocation(methodRef); CallLocation callLocation = new CallLocation(methodRef);
var isConstructor = entry.getKey().getName().equals("<init>");
var paramCount = method.parameterCount(); var paramCount = method.parameterCount();
if (export.vararg) { if (export.vararg) {
--paramCount; --paramCount;
} }
if (isConstructor) {
--paramCount;
}
var exportedMethodSignature = new ValueType[paramCount + 2]; var exportedMethodSignature = new ValueType[paramCount + 2];
Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT); Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT);
if (methodRef.getReturnType() == ValueType.VOID) { if (methodRef.getReturnType() == ValueType.VOID && !isConstructor) {
exportedMethodSignature[exportedMethodSignature.length - 1] = ValueType.VOID; exportedMethodSignature[exportedMethodSignature.length - 1] = ValueType.VOID;
} }
MethodDescriptor exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++, MethodDescriptor exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++,
@ -164,7 +172,9 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
Program program = new Program(); Program program = new Program();
exportedMethod.setProgram(program); exportedMethod.setProgram(program);
program.createVariable(); program.createVariable();
if (!isConstructor) {
program.createVariable(); program.createVariable();
}
BasicBlock basicBlock = program.createBasicBlock(); BasicBlock basicBlock = program.createBasicBlock();
List<Instruction> marshallInstructions = new ArrayList<>(); List<Instruction> marshallInstructions = new ArrayList<>();
@ -189,6 +199,14 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
basicBlock.addAll(marshallInstructions); basicBlock.addAll(marshallInstructions);
marshallInstructions.clear(); marshallInstructions.clear();
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(); var unmarshalledInstance = new InvokeInstruction();
unmarshalledInstance.setType(InvocationType.SPECIAL); unmarshalledInstance.setType(InvocationType.SPECIAL);
unmarshalledInstance.setReceiver(program.createVariable()); unmarshalledInstance.setReceiver(program.createVariable());
@ -204,18 +222,29 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
castInstance.setTargetType(ValueType.object(classHolder.getName())); castInstance.setTargetType(ValueType.object(classHolder.getName()));
basicBlock.add(castInstance); basicBlock.add(castInstance);
receiverToPass = castInstance.getReceiver();
}
InvokeInstruction invocation = new InvokeInstruction(); InvokeInstruction invocation = new InvokeInstruction();
invocation.setType(method.getName().equals("<init>") ? InvocationType.SPECIAL : InvocationType.VIRTUAL); invocation.setType(isConstructor ? InvocationType.SPECIAL : InvocationType.VIRTUAL);
invocation.setInstance(castInstance.getReceiver()); invocation.setInstance(receiverToPass);
invocation.setMethod(methodRef); invocation.setMethod(methodRef);
invocation.setArguments(variablesToPass); invocation.setArguments(variablesToPass);
basicBlock.add(invocation); basicBlock.add(invocation);
ExitInstruction exit = new ExitInstruction(); ExitInstruction exit = new ExitInstruction();
if (method.getResultType() != ValueType.VOID) { if (method.getResultType() != ValueType.VOID || isConstructor) {
invocation.setReceiver(program.createVariable()); Variable result;
exit.setValueToReturn(marshaller.wrapArgument(callLocation, invocation.getReceiver(), ValueType resultType;
method.getResultType(), JSType.MIXED, false)); 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); basicBlock.addAll(marshallInstructions);
marshallInstructions.clear(); 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; int index = 0;
var hasMethods = false;
for (var method : classHolder.getMethods().toArray(new MethodHolder[0])) { for (var method : classHolder.getMethods().toArray(new MethodHolder[0])) {
if (!method.hasModifier(ElementModifier.STATIC) if (!method.hasModifier(ElementModifier.STATIC)
|| method.getAnnotations().get(JSExport.class.getName()) == null) { || method.getAnnotations().get(JSExport.class.getName()) == null) {
continue; continue;
} }
hasMethods = true;
var paramCount = method.parameterCount(); var paramCount = method.parameterCount();
var vararg = method.hasModifier(ElementModifier.VARARGS); var vararg = method.hasModifier(ElementModifier.VARARGS);
@ -246,6 +277,9 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
var callLocation = new CallLocation(method.getReference()); var callLocation = new CallLocation(method.getReference());
var exportedMethodSignature = new ValueType[paramCount + 1]; var exportedMethodSignature = new ValueType[paramCount + 1];
Arrays.fill(exportedMethodSignature, JSMethods.JS_OBJECT); 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++, var exportedMethodDesc = new MethodDescriptor(method.getName() + "$exported$" + index++,
exportedMethodSignature); exportedMethodSignature);
var exportedMethod = new MethodHolder(exportedMethodDesc); var exportedMethod = new MethodHolder(exportedMethodDesc);
@ -287,7 +321,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
if (method.getResultType() != ValueType.VOID) { if (method.getResultType() != ValueType.VOID) {
invocation.setReceiver(program.createVariable()); invocation.setReceiver(program.createVariable());
exit.setValueToReturn(marshaller.wrapArgument(callLocation, invocation.getReceiver(), exit.setValueToReturn(marshaller.wrapArgument(callLocation, invocation.getReceiver(),
method.getResultType(), JSType.MIXED, false)); method.getResultType(), JSType.JAVA, false));
basicBlock.addAll(marshallInstructions); basicBlock.addAll(marshallInstructions);
marshallInstructions.clear(); marshallInstructions.clear();
} }
@ -298,6 +332,7 @@ class JSObjectClassTransformer implements ClassHolderTransformer {
var export = createMethodExport(method); var export = createMethodExport(method);
exportedMethod.getAnnotations().add(createExportAnnotation(export)); exportedMethod.getAnnotations().add(createExportAnnotation(export));
} }
return hasMethods;
} }
private void transformVarargParam(Variable[] variablesToPass, Program program, private void transformVarargParam(Variable[] variablesToPass, Program program,

View File

@ -34,6 +34,19 @@ public class JSTypeHelper {
knownJavaScriptClasses.put(JSObject.class.getName(), true); 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) { public boolean isJavaScriptClass(String className) {
Boolean isJsClass = knownJavaScriptClasses.get(className); Boolean isJsClass = knownJavaScriptClasses.get(className);
if (isJsClass == null) { if (isJsClass == null) {

View File

@ -38,16 +38,7 @@ class JSTypeInference extends BaseTypeInference<JSType> {
@Override @Override
protected JSType mapType(ValueType type) { protected JSType mapType(ValueType type) {
if (type instanceof ValueType.Object) { return typeHelper.mapType(type);
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;
} }
@Override @Override

View File

@ -19,6 +19,7 @@ import org.teavm.backend.wasm.gc.TeaVMWasmGCHost;
import org.teavm.jso.JSObject; import org.teavm.jso.JSObject;
import org.teavm.jso.impl.JS; import org.teavm.jso.impl.JS;
import org.teavm.jso.impl.JSBodyRepository; import org.teavm.jso.impl.JSBodyRepository;
import org.teavm.jso.impl.JSClassObjectToExpose;
import org.teavm.jso.impl.JSWrapper; import org.teavm.jso.impl.JSWrapper;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.vm.spi.TeaVMHost; import org.teavm.vm.spi.TeaVMHost;
@ -35,6 +36,12 @@ public final class WasmGCJso {
wasmGCHost.addCustomTypeMapperFactory(new WasmGCJSTypeMapper()); wasmGCHost.addCustomTypeMapperFactory(new WasmGCJSTypeMapper());
wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository, jsFunctions, commonGen)); wasmGCHost.addIntrinsicFactory(new WasmGCJSBodyRenderer(jsBodyRepository, jsFunctions, commonGen));
wasmGCHost.addGeneratorFactory(new WasmGCMarshallMethodGeneratorFactory(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); var jsIntrinsic = new WasmGCJSIntrinsic(commonGen);
wasmGCHost.addIntrinsic(new MethodReference(JS.class, "wrap", String.class, JSObject.class), jsIntrinsic); 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.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmExternConversion; import org.teavm.backend.wasm.model.expression.WasmExternConversion;
import org.teavm.backend.wasm.model.expression.WasmExternConversionType; 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.WasmGetGlobal;
import org.teavm.backend.wasm.model.expression.WasmGetLocal; import org.teavm.backend.wasm.model.expression.WasmGetLocal;
import org.teavm.backend.wasm.model.expression.WasmNullConstant; import org.teavm.backend.wasm.model.expression.WasmNullConstant;
import org.teavm.backend.wasm.model.expression.WasmSetGlobal; import org.teavm.backend.wasm.model.expression.WasmSetGlobal;
import org.teavm.backend.wasm.model.expression.WasmThrow; import org.teavm.backend.wasm.model.expression.WasmThrow;
import org.teavm.jso.JSClass;
import org.teavm.jso.JSObject; import org.teavm.jso.JSObject;
import org.teavm.jso.impl.AliasCollector;
import org.teavm.jso.impl.JSBodyAstEmitter; import org.teavm.jso.impl.JSBodyAstEmitter;
import org.teavm.jso.impl.JSBodyBloatedEmitter; import org.teavm.jso.impl.JSBodyBloatedEmitter;
import org.teavm.jso.impl.JSBodyEmitter; 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.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
@ -51,6 +56,15 @@ class WasmGCJsoCommonGenerator {
private boolean rethrowExported; private boolean rethrowExported;
private Map<String, WasmGlobal> stringsConstants = new HashMap<>(); 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) { WasmGCJsoCommonGenerator(WasmGCJSFunctions jsFunctions) {
this.jsFunctions = 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); initialize(context);
initializerParts.add(part); initializerParts.add(part);
} }
@ -183,4 +197,277 @@ class WasmGCJsoCommonGenerator {
}); });
return new WasmGetGlobal(global); 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 java.util.function.Consumer;
import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.BaseWasmFunctionRepository;
import org.teavm.backend.wasm.WasmFunctionTypes; 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.WasmGCNameProvider;
import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper; import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper;
import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringProvider; 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.WasmFunction;
import org.teavm.backend.wasm.model.WasmModule; import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.WasmTag; import org.teavm.backend.wasm.model.WasmTag;
import org.teavm.model.ClassReaderSource;
interface WasmGCJsoContext { interface WasmGCJsoContext {
ClassReaderSource classes();
WasmModule module(); WasmModule module();
WasmFunctionTypes functionTypes(); WasmFunctionTypes functionTypes();
@ -42,10 +46,17 @@ interface WasmGCJsoContext {
WasmTag exceptionTag(); WasmTag exceptionTag();
String entryPoint();
void addToInitializer(Consumer<WasmFunction> initializerContributor); void addToInitializer(Consumer<WasmFunction> initializerContributor);
static WasmGCJsoContext wrap(WasmGCIntrinsicContext context) { static WasmGCJsoContext wrap(WasmGCIntrinsicContext context) {
return new WasmGCJsoContext() { return new WasmGCJsoContext() {
@Override
public ClassReaderSource classes() {
return context.hierarchy().getClassSource();
}
@Override @Override
public WasmModule module() { public WasmModule module() {
return context.module(); return context.module();
@ -81,6 +92,11 @@ interface WasmGCJsoContext {
return context.exceptionTag(); return context.exceptionTag();
} }
@Override
public String entryPoint() {
return context.entryPoint();
}
@Override @Override
public void addToInitializer(Consumer<WasmFunction> initializerContributor) { public void addToInitializer(Consumer<WasmFunction> initializerContributor) {
context.addToInitializer(initializerContributor); context.addToInitializer(initializerContributor);
@ -90,6 +106,11 @@ interface WasmGCJsoContext {
static WasmGCJsoContext wrap(WasmGCCustomGeneratorContext context) { static WasmGCJsoContext wrap(WasmGCCustomGeneratorContext context) {
return new WasmGCJsoContext() { return new WasmGCJsoContext() {
@Override
public ClassReaderSource classes() {
return context.classes();
}
@Override @Override
public WasmModule module() { public WasmModule module() {
return context.module(); return context.module();
@ -125,6 +146,65 @@ interface WasmGCJsoContext {
return context.exceptionTag(); 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 @Override
public void addToInitializer(Consumer<WasmFunction> initializerContributor) { public void addToInitializer(Consumer<WasmFunction> initializerContributor) {
context.addToInitializer(initializerContributor); context.addToInitializer(initializerContributor);

View File

@ -15,31 +15,20 @@
*/ */
package org.teavm.jso.impl.wasmgc; 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.WasmGCCustomGenerator;
import org.teavm.backend.wasm.generators.gc.WasmGCCustomGeneratorContext; import org.teavm.backend.wasm.generators.gc.WasmGCCustomGeneratorContext;
import org.teavm.backend.wasm.model.WasmFunction; 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.WasmLocal;
import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmCall; 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.WasmGetGlobal;
import org.teavm.backend.wasm.model.expression.WasmGetLocal; 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.MethodReference;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
class WasmGCMarshallMethodGenerator implements WasmGCCustomGenerator { class WasmGCMarshallMethodGenerator implements WasmGCCustomGenerator {
private WasmGCJsoCommonGenerator commonGen; private WasmGCJsoCommonGenerator commonGen;
private WasmFunction javaObjectToJSFunction; private WasmFunction javaObjectToJSFunction;
private WasmFunction createClassFunction;
private WasmFunction defineMethodFunction;
private WasmFunction definePropertyFunction;
WasmGCMarshallMethodGenerator(WasmGCJsoCommonGenerator commonGen) { WasmGCMarshallMethodGenerator(WasmGCJsoCommonGenerator commonGen) {
this.commonGen = commonGen; this.commonGen = commonGen;
@ -52,8 +41,7 @@ class WasmGCMarshallMethodGenerator implements WasmGCCustomGenerator {
var thisLocal = new WasmLocal(context.typeMapper().mapType(ValueType.object(method.getClassName())), "this"); var thisLocal = new WasmLocal(context.typeMapper().mapType(ValueType.object(method.getClassName())), "this");
function.add(thisLocal); function.add(thisLocal);
var cls = context.classes().get(method.getClassName()); var jsClassGlobal = commonGen.getDefinedClass(jsoContext, method.getClassName());
var jsClassGlobal = defineClass(jsoContext, cls);
var wrapperFunction = javaObjectToJSFunction(context); var wrapperFunction = javaObjectToJSFunction(context);
function.getBody().add(new WasmCall(wrapperFunction, new WasmGetLocal(thisLocal), function.getBody().add(new WasmCall(wrapperFunction, new WasmGetLocal(thisLocal),
new WasmGetGlobal(jsClassGlobal))); new WasmGetGlobal(jsClassGlobal)));
@ -71,91 +59,4 @@ class WasmGCMarshallMethodGenerator implements WasmGCCustomGenerator {
return javaObjectToJSFunction; 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> </head>
<script type="text/javascript"> <script type="text/javascript">
function launch() { 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> </script>
<body onload="launch()"> <body onload="launch()">

View File

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

View File

@ -19,13 +19,20 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List; import java.util.List;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.teavm.backend.javascript.JSModuleType; import org.teavm.backend.javascript.JSModuleType;
import org.teavm.backend.javascript.JavaScriptTarget; 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.BrowserRunDescriptor;
import org.teavm.browserrunner.BrowserRunner; import org.teavm.browserrunner.BrowserRunner;
import org.teavm.tooling.ConsoleTeaVMToolLog; import org.teavm.tooling.ConsoleTeaVMToolLog;
@ -35,8 +42,19 @@ import org.teavm.vm.TeaVMOptimizationLevel;
public class ExportTest { public class ExportTest {
private static File targetFile = new File(new File(System.getProperty("teavm.junit.target")), "jso-export"); private static File targetFile = new File(new File(System.getProperty("teavm.junit.target")), "jso-export");
private static BrowserRunner runner = new BrowserRunner( private static File jsTargetFile = new File(targetFile, "js");
targetFile, 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", "JAVASCRIPT",
BrowserRunner.pickBrowser(System.getProperty("teavm.junit.js.runner")), BrowserRunner.pickBrowser(System.getProperty("teavm.junit.js.runner")),
false false
@ -44,12 +62,22 @@ public class ExportTest {
@BeforeClass @BeforeClass
public static void start() { public static void start() {
runner.start(); if (jsNeeded) {
jsRunner.start();
}
if (wasmGCNeeded) {
wasmGCRunner.start();
}
} }
@AfterClass @AfterClass
public static void stop() { public static void stop() {
runner.stop(); if (jsNeeded) {
jsRunner.stop();
}
if (wasmGCNeeded) {
wasmGCRunner.stop();
}
} }
@Test @Test
@ -69,7 +97,7 @@ public class ExportTest {
@Test @Test
public void exportClassMembers() { public void exportClassMembers() {
testExport("exportClassMembers", ModuleWithExportedClassMembers.class); testExport("exportClassMembers", ModuleWithExportedClassMembers.class, true);
} }
@Test @Test
@ -79,24 +107,34 @@ public class ExportTest {
@Test @Test
public void exportClasses() { public void exportClasses() {
testExport("exportClasses", ModuleWithExportedClasses.class); testExport("exportClasses", ModuleWithExportedClasses.class, true);
} }
@Test @Test
public void varargs() { public void varargs() {
testExport("varargs", ModuleWithVararg.class); testExport("varargs", ModuleWithVararg.class, true);
} }
private void testExport(String name, Class<?> moduleClass) { private void testExport(String name, Class<?> moduleClass) {
if (!Boolean.parseBoolean(System.getProperty("teavm.junit.js", "true"))) { testExport(name, moduleClass, false);
return;
} }
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 { try {
var jsTarget = new JavaScriptTarget(); var jsTarget = new JavaScriptTarget();
jsTarget.setModuleType(JSModuleType.ES2015); jsTarget.setModuleType(JSModuleType.ES2015);
jsTarget.setObfuscated(false); jsTarget.setObfuscated(false);
var teavm = new TeaVMBuilder(jsTarget).build(); var teavm = new TeaVMBuilder(jsTarget).build();
var outputDir = new File(targetFile, name); var outputDir = new File(jsTargetFile, name);
teavm.installPlugins(); teavm.installPlugins();
teavm.setEntryPoint(moduleClass.getName()); teavm.setEntryPoint(moduleClass.getName());
teavm.setOptimizationLevel(TeaVMOptimizationLevel.ADVANCED); teavm.setOptimizationLevel(TeaVMOptimizationLevel.ADVANCED);
@ -115,10 +153,64 @@ public class ExportTest {
writer.write(" test().then(() => callback()).catch(e => callback(e));\n"); writer.write(" test().then(() => callback()).catch(e => callback(e));\n");
writer.write("}\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, var descriptor = new BrowserRunDescriptor(name, "tests/" + name + "/runner.js", true,
List.of("resources/org/teavm/jso/export/assert.js"), null); 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) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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() { export async function test() {
let o = createObject("qwe"); let o = createObject("qwe");

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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() { export async function test() {
assertEquals(23, A.foo()); assertEquals(23, A.foo());

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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() { export async function test() {
assertEquals("object taken: foo = 23, bar = qw", takeObject({ assertEquals("object taken: foo = 23, bar = qw", takeObject({

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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() { export async function test() {
assertEquals("foo", foo()); assertEquals("foo", foo());

View File

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

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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() { export async function test() {
assertEquals(23, foo()); assertEquals(23, foo());

View File

@ -14,7 +14,7 @@
* limitations under the License. * 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() { export async function test() {
assertEquals("strings: a, b", varargs.strings("a", "b")); assertEquals("strings: a, b", varargs.strings("a", "b"));

View File

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

View File

@ -17,6 +17,7 @@ package org.teavm.junit;
import org.teavm.classlib.impl.console.JSConsoleStringPrintStream; import org.teavm.classlib.impl.console.JSConsoleStringPrintStream;
import org.teavm.interop.Import; import org.teavm.interop.Import;
import org.teavm.jso.core.JSString;
final class TestWasmGCEntryPoint { final class TestWasmGCEntryPoint {
private TestWasmGCEntryPoint() { private TestWasmGCEntryPoint() {
@ -29,7 +30,7 @@ final class TestWasmGCEntryPoint {
} catch (Throwable e) { } catch (Throwable e) {
var out = new JSConsoleStringPrintStream(); var out = new JSConsoleStringPrintStream();
e.printStackTrace(out); e.printStackTrace(out);
reportFailure(out.toString()); reportFailure(JSString.valueOf(out.toString()));
} }
TestEntryPoint.run(args.length > 0 ? args[0] : null); TestEntryPoint.run(args.length > 0 ? args[0] : null);
} }
@ -38,5 +39,5 @@ final class TestWasmGCEntryPoint {
private static native void reportSuccess(); private static native void reportSuccess();
@Import(module = "teavmTest", name = "failure") @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" src="${SCRIPT}-runtime.js"></script>
<script type="text/javascript"> <script type="text/javascript">
let instance; let instance;
TeaVM.wasm.load("${SCRIPT}", { TeaVM.wasmGC.load("${SCRIPT}", {
installImports(o) { installImports(o) {
o.teavmTest = { o.teavmTest = {
success() { success() {
@ -32,22 +32,14 @@
document.body.appendChild(pre); document.body.appendChild(pre);
pre.appendChild(document.createTextNode("OK")); pre.appendChild(document.createTextNode("OK"));
}, },
failure(javaString) { failure(message) {
let jsString = ""; let pre = document.createElement("pre");
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");
document.body.appendChild(pre); document.body.appendChild(pre);
pre.appendChild(document.createTextNode(jsString)); pre.appendChild(document.createTextNode(message));
} }
}; };
} }
}).then(teavm => { }).then(teavm => teavm.exports.main(["${IDENTIFIER}"]));
instance = teavm.instance;
teavm.main(["${IDENTIFIER}"])
});
</script> </script>
</body> </body>
</html> </html>