From 2ccb84b8e2e6add850802d5adcda11100243486a Mon Sep 17 00:00:00 2001 From: Roberto Lublinerman Date: Fri, 17 Nov 2023 16:36:21 -0800 Subject: [PATCH] [WASM] Separates the responsibilities for producing outputs and synthesizing .wat code. Formerly `WasmOutputGenerator` would sinthezise the top level .wat code and produce all the files in a wasm transpilation. This cl renames `WasmOutputGenerator` into `WasmOutputGenerationStage` whose responsibility is only to produce the output files and control how the output is produced; and introduces `WasmConstructsGenerator` whose responsibility is to produce .wat constructs for AST and runtime needs. `WasmOutputGenerationStage` is also responsible for the use and lifetime of the SourceBuilder objects. PiperOrigin-RevId: 583516872 --- .../BazelJ2wasmExportsGenerator.java | 4 +- .../j2cl/transpiler/backend/Backend.java | 6 +- .../backend/wasm/ExpressionTranspiler.java | 4 +- ...ator.java => WasmConstructsGenerator.java} | 245 ++-------------- .../backend/wasm/WasmGeneratorStage.java | 263 ++++++++++++++++++ 5 files changed, 289 insertions(+), 233 deletions(-) rename transpiler/java/com/google/j2cl/transpiler/backend/wasm/{WasmOutputsGenerator.java => WasmConstructsGenerator.java} (77%) create mode 100644 transpiler/java/com/google/j2cl/transpiler/backend/wasm/WasmGeneratorStage.java diff --git a/transpiler/java/com/google/j2cl/transpiler/BazelJ2wasmExportsGenerator.java b/transpiler/java/com/google/j2cl/transpiler/BazelJ2wasmExportsGenerator.java index 2ab79a1174..126cb714d7 100644 --- a/transpiler/java/com/google/j2cl/transpiler/BazelJ2wasmExportsGenerator.java +++ b/transpiler/java/com/google/j2cl/transpiler/BazelJ2wasmExportsGenerator.java @@ -31,7 +31,7 @@ import com.google.j2cl.transpiler.ast.Method; import com.google.j2cl.transpiler.ast.TypeDescriptors; import com.google.j2cl.transpiler.ast.WasmEntryPointBridgesCreator; -import com.google.j2cl.transpiler.backend.wasm.WasmOutputsGenerator; +import com.google.j2cl.transpiler.backend.wasm.WasmGeneratorStage; import com.google.j2cl.transpiler.frontend.common.PackageInfoCache; import com.google.j2cl.transpiler.frontend.jdt.JdtEnvironment; import com.google.j2cl.transpiler.frontend.jdt.JdtParser; @@ -110,7 +110,7 @@ protected void run(Problems problems) { .flatMap(t -> t.getDeclaredMethodDescriptors().stream()) .collect(toImmutableList())); - var generator = new WasmOutputsGenerator(out, /* libraryInfoOutputPath= */ null, problems); + var generator = new WasmGeneratorStage(out, /* libraryInfoOutputPath= */ null, problems); generator.generateMethods(exportedMethods); problems.abortIfHasErrors(); diff --git a/transpiler/java/com/google/j2cl/transpiler/backend/Backend.java b/transpiler/java/com/google/j2cl/transpiler/backend/Backend.java index 8b17328f04..87f58fabf5 100644 --- a/transpiler/java/com/google/j2cl/transpiler/backend/Backend.java +++ b/transpiler/java/com/google/j2cl/transpiler/backend/Backend.java @@ -20,7 +20,7 @@ import com.google.j2cl.transpiler.ast.Library; import com.google.j2cl.transpiler.backend.closure.OutputGeneratorStage; import com.google.j2cl.transpiler.backend.kotlin.KotlinGeneratorStage; -import com.google.j2cl.transpiler.backend.wasm.WasmOutputsGenerator; +import com.google.j2cl.transpiler.backend.wasm.WasmGeneratorStage; import com.google.j2cl.transpiler.passes.AddAbstractMethodStubs; import com.google.j2cl.transpiler.passes.AddBridgeMethods; import com.google.j2cl.transpiler.passes.AddDisambiguatingSuperMethodForwardingStubs; @@ -344,7 +344,7 @@ public ImmutableList> getPassFactories(BackendOption WASM { @Override public void generateOutputs(BackendOptions options, Library library, Problems problems) { - new WasmOutputsGenerator(options.getOutput(), options.getLibraryInfoOutput(), problems) + new WasmGeneratorStage(options.getOutput(), options.getLibraryInfoOutput(), problems) .generateMonolithicOutput(library); } @@ -482,7 +482,7 @@ public boolean isWasm() { WASM_MODULAR { @Override public void generateOutputs(BackendOptions options, Library library, Problems problems) { - new WasmOutputsGenerator(options.getOutput(), options.getLibraryInfoOutput(), problems) + new WasmGeneratorStage(options.getOutput(), options.getLibraryInfoOutput(), problems) .generateModularOutput(library); } diff --git a/transpiler/java/com/google/j2cl/transpiler/backend/wasm/ExpressionTranspiler.java b/transpiler/java/com/google/j2cl/transpiler/backend/wasm/ExpressionTranspiler.java index 1eb879289c..3993fe9351 100644 --- a/transpiler/java/com/google/j2cl/transpiler/backend/wasm/ExpressionTranspiler.java +++ b/transpiler/java/com/google/j2cl/transpiler/backend/wasm/ExpressionTranspiler.java @@ -68,7 +68,9 @@ final class ExpressionTranspiler { public static void renderWithUnusedResult( - Expression expression, SourceBuilder sourceBuilder, WasmGenerationEnvironment environment) { + Expression expression, + final SourceBuilder sourceBuilder, + final WasmGenerationEnvironment environment) { if (returnsVoid(expression)) { render(expression, sourceBuilder, environment); } else { diff --git a/transpiler/java/com/google/j2cl/transpiler/backend/wasm/WasmOutputsGenerator.java b/transpiler/java/com/google/j2cl/transpiler/backend/wasm/WasmConstructsGenerator.java similarity index 77% rename from transpiler/java/com/google/j2cl/transpiler/backend/wasm/WasmOutputsGenerator.java rename to transpiler/java/com/google/j2cl/transpiler/backend/wasm/WasmConstructsGenerator.java index ea089e473f..68363d0a4f 100644 --- a/transpiler/java/com/google/j2cl/transpiler/backend/wasm/WasmOutputsGenerator.java +++ b/transpiler/java/com/google/j2cl/transpiler/backend/wasm/WasmConstructsGenerator.java @@ -15,22 +15,16 @@ */ package com.google.j2cl.transpiler.backend.wasm; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Predicates.not; import static com.google.common.collect.ImmutableList.toImmutableList; import static java.lang.String.format; import static java.util.Arrays.stream; import com.google.common.collect.ImmutableList; -import com.google.j2cl.common.OutputUtils; -import com.google.j2cl.common.OutputUtils.Output; -import com.google.j2cl.common.Problems; -import com.google.j2cl.common.SourcePosition; import com.google.j2cl.common.StringUtils; import com.google.j2cl.transpiler.ast.AbstractVisitor; import com.google.j2cl.transpiler.ast.ArrayLiteral; import com.google.j2cl.transpiler.ast.ArrayTypeDescriptor; -import com.google.j2cl.transpiler.ast.CompilationUnit; import com.google.j2cl.transpiler.ast.DeclaredTypeDescriptor; import com.google.j2cl.transpiler.ast.Expression; import com.google.j2cl.transpiler.ast.Field; @@ -46,146 +40,29 @@ import com.google.j2cl.transpiler.ast.TypeDescriptors; import com.google.j2cl.transpiler.ast.Variable; import com.google.j2cl.transpiler.backend.common.SourceBuilder; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; -/** Generates all the outputs for Wasm compilation. */ -public class WasmOutputsGenerator { +/** Generates all the syntactic .wat constructs for wasm. */ +public class WasmConstructsGenerator { - private final Problems problems; - private final Output output; - private SourceBuilder builder = new SourceBuilder(); - private final Path libraryInfoOutputPath; + private final SourceBuilder builder; private WasmGenerationEnvironment environment; - public WasmOutputsGenerator(Output output, Path libraryInfoOutputPath, Problems problems) { - this.output = output; - this.libraryInfoOutputPath = libraryInfoOutputPath; - this.problems = problems; + public WasmConstructsGenerator(WasmGenerationEnvironment environment, SourceBuilder builder) { + this.environment = environment; + this.builder = builder; } - public void generateModularOutput(Library library) { - if (libraryInfoOutputPath != null) { - OutputUtils.writeToFile(libraryInfoOutputPath, new byte[0], problems); - } - - environment = - new WasmGenerationEnvironment( - library, JsImportsGenerator.collectImports(library, problems), /* isModular= */ true); - SummaryBuilder summaryBuilder = new SummaryBuilder(library, environment, problems); - - // TODO(rluble): Introduce/use flags to emit the readable version of the summary. For now emit - // summaries in both binary and text form for now. - output.write("summary.txtpb", summaryBuilder.toJson(problems)); - output.write("summary.binpb", summaryBuilder.toByteArray()); - - List usedNativeArrayTypes = collectUsedNativeArrayTypes(library); - - copyJavaSources(library); - - builder = new SourceBuilder(); - emitDynamicDispatchMethodTypes(library); - emitNativeArrayTypes(usedNativeArrayTypes); - emitForEachType(library, this::renderModularTypeStructs, "type definition"); - outputIfNotEmpty("types.wat"); - - builder = new SourceBuilder(); - emitForEachType(library, this::renderTypeMethods, "methods"); - outputIfNotEmpty("functions.wat"); - - builder = new SourceBuilder(); - emitGlobals(library); - outputIfNotEmpty("globals.wat"); - - builder = new SourceBuilder(); - emitDataSegments(library); - outputIfNotEmpty("data.wat"); - - generateJsImportsFile(); - } - - private void outputIfNotEmpty(String path) { - - String content = builder.build(); - if (content.isEmpty()) { - return; - } - - output.write(path, content); - } - - public void generateMonolithicOutput(Library library) { - copyJavaSources(library); - generateWasmModule(library); - generateJsImportsFile(); - } - - private void copyJavaSources(Library library) { - library.getCompilationUnits().stream() - .filter(not(CompilationUnit::isSynthetic)) - .forEach( - compilationUnit -> - output.copyFile( - compilationUnit.getFilePath(), compilationUnit.getPackageRelativePath())); - } - - private void generateWasmModule(Library library) { - environment = - new WasmGenerationEnvironment( - library, JsImportsGenerator.collectImports(library, problems)); - List usedNativeArrayTypes = collectUsedNativeArrayTypes(library); - - builder.appendln(";;; Code generated by J2WASM"); - builder.append("(module"); - // Emit all types at the beginning of the module. - emitLibraryRecGroup(library, usedNativeArrayTypes); - - // Declare a tag that will be used for Java exceptions. The tag has a single parameter that is - // the Throwable object being thrown by the throw instruction. - // The throw instruction will refer to this tag and will expect a single element in the stack - // with the type $java.lang.Throwable. - // TODO(b/277970998): Decide how to handle this hard coded import w.r.t. import generation. - builder.newLine(); - builder.append( - "(import \"imports\" \"j2wasm.ExceptionUtils.tag\" (tag $exception.event (param" - + " externref)))"); - // Add an export that uses the tag to workarund binaryen assuming the tag is never instantiated. - builder.append( - "(func $keep_tag_alive_hack (export \"_tag_hack_\") (param $param externref) " - + "(throw $exception.event (local.get $param)))"); - - // Emit all the globals, e.g. vtable instances, etc. - emitDataSegments(library); - emitDispatchTablesInitialization(library); - emitEmptyArraySingletons(usedNativeArrayTypes); - emitGlobals(library); - - // Emit intrinsics imports - emitImportsForBinaryenIntrinsics(library); - - // Last, emit all methods at the very end so that the synthetic code generated above does - // not inherit an incorrect source position. - emitForEachType(library, this::renderTypeMethods, "methods"); - - builder.newLine(); - builder.append(")"); - output.write("module.wat", builder.buildToList()); - output.write("namemap", emitNameMapping(library)); - } - - private void emitDataSegments(Library library) { + void emitDataSegments(Library library) { library.accept( new AbstractVisitor() { @Override @@ -240,8 +117,7 @@ private String toDataString(ArrayLiteral arrayLiteral) { } /** Emits all wasm type definitions into a single rec group. */ - private void emitLibraryRecGroup( - Library library, List usedNativeArrayTypes) { + void emitLibraryRecGroup(Library library, List usedNativeArrayTypes) { builder.newLine(); builder.append("(rec"); builder.indent(); @@ -270,12 +146,12 @@ private void emitItableSupportTypes() { builder.append(")))"); } - private void emitGlobals(Library library) { + void emitGlobals(Library library) { emitStaticFieldGlobals(library); } /** Emit the type for all function signatures that will be needed to reference vtable methods. */ - private void emitDynamicDispatchMethodTypes(Library library) { + void emitDynamicDispatchMethodTypes(Library library) { Set emittedFunctionTypeNames = new HashSet<>(); library .streamTypes() @@ -303,7 +179,7 @@ private void emitFunctionType(Set emittedFunctionTypeNames, MethodDescri *

In order to communicate information to binaryen, binaryen provides intrinsic methods that * need to be imported. */ - private void emitImportsForBinaryenIntrinsics(Library library) { + void emitImportsForBinaryenIntrinsics(Library library) { // Emit the intrinsic for calls with no side effects. The intrinsic method exported name is // "call.without.effects" and can be used to convey to binaryen that a certain function call @@ -360,7 +236,7 @@ private void renderMonolithicTypeStructs(Type type) { renderTypeStructs(type, /* isModular= */ false); } - private void renderModularTypeStructs(Type type) { + void renderModularTypeStructs(Type type) { renderTypeStructs(type, /* isModular= */ true); } @@ -459,41 +335,14 @@ private void emitStaticFieldGlobals(Type type) { emitEndCodeComment(type, "static fields"); } - private void renderTypeMethods(Type type) { + void renderTypeMethods(Type type) { type.getMethods().stream() .filter(method -> !method.isAbstract() || method.isNative()) .filter(m -> m.getDescriptor().getWasmInfo() == null) .forEach(this::renderMethod); } - public void generateMethods(List methods) { - if (methods.isEmpty()) { - return; - } - // Create the type objects and add all the exported methods to the corresponding type to - // initialize the WasmGenerationEnvironment. - CompilationUnit cu = CompilationUnit.createSynthetic("wasm.exports"); - Map typesByDeclaration = new LinkedHashMap<>(); - methods.forEach( - m -> { - TypeDeclaration typeDeclaration = - m.getDescriptor().getEnclosingTypeDescriptor().getTypeDeclaration(); - Type type = - typesByDeclaration.computeIfAbsent( - typeDeclaration, t -> new Type(SourcePosition.NONE, t)); - type.addMember(m); - }); - typesByDeclaration.values().forEach(cu::addType); - Library library = Library.newBuilder().setCompilationUnits(ImmutableList.of(cu)).build(); - environment = - new WasmGenerationEnvironment( - library, JsImportsGenerator.collectImports(library, problems)); - - methods.stream().forEach(this::renderMethod); - output.write("functions.wat", builder.build()); - } - - private void renderMethod(Method method) { + void renderMethod(Method method) { MethodDescriptor methodDescriptor = method.getDescriptor(); // TODO(b/264676817): Consider refactoring to have MethodDescriptor.isNative return true for // native constructors, or exposing isNativeConstructor from MethodDescriptor. @@ -668,7 +517,7 @@ private void renderTypeFields(Type type) { * Emit a function that will be used to initialize the runtime at module instantiation time; * together with the required type definitions. */ - private void emitDispatchTablesInitialization(Library library) { + void emitDispatchTablesInitialization(Library library) { builder.newLine(); // TODO(b/183994530): Initialize dynamic dispatch tables lazily. builder.append(";;; Initialize dynamic dispatch tables."); @@ -880,40 +729,7 @@ private void emitWasmStruct( builder.append(")"); } - private List collectUsedNativeArrayTypes(Library library) { - Set usedArrayTypes = new LinkedHashSet<>(); - // Collect native arrays from fields and variables; this covers all scenarios. - library.accept( - // TODO(b/303659726): Generalize a type visitor that could be used here and other places - // like in ImportGatherer. Or consider emitting the one dimensional array type for all - // native types. - new AbstractVisitor() { - @Override - public void exitField(Field field) { - collectIfArrayType(field.getDescriptor().getTypeDescriptor()); - } - - @Override - public void exitVariable(Variable variable) { - collectIfArrayType(variable.getTypeDescriptor()); - } - - private void collectIfArrayType(TypeDescriptor typeDescriptor) { - if (!typeDescriptor.isArray()) { - return; - } - - ArrayTypeDescriptor arrayTypeDescriptor = (ArrayTypeDescriptor) typeDescriptor; - if (arrayTypeDescriptor.isNativeWasmArray()) { - usedArrayTypes.add(arrayTypeDescriptor); - } - } - }); - - return new ArrayList<>(usedArrayTypes); - } - - private void emitNativeArrayTypes(List arrayTypes) { + void emitNativeArrayTypes(List arrayTypes) { emitBeginCodeComment("Native Array types"); arrayTypes.forEach(this::emitNativeArrayType); emitEndCodeComment("Native Array types"); @@ -929,7 +745,7 @@ private void emitNativeArrayType(ArrayTypeDescriptor arrayTypeDescriptor) { environment.getWasmFieldType(arrayTypeDescriptor.getComponentTypeDescriptor()))); } - private void emitEmptyArraySingletons(List arrayTypes) { + void emitEmptyArraySingletons(List arrayTypes) { emitBeginCodeComment("Empty array singletons"); arrayTypes.forEach(this::emitEmptyArraySingleton); emitEndCodeComment("Empty array singletons"); @@ -956,7 +772,7 @@ private void emitEmptyArraySingleton(ArrayTypeDescriptor arrayTypeDescriptor) { * Iterate through all the types in the library, supertypes first, calling the emitter for each of * them. */ - private void emitForEachType(Library library, Consumer emitter, String comment) { + void emitForEachType(Library library, Consumer emitter, String comment) { library .streamTypes() // Emit the types supertypes first. @@ -994,29 +810,4 @@ private void emitEndCodeComment(String commentId) { builder.newLine(); builder.append(";;; End of code for " + commentId); } - - private void generateJsImportsFile() { - JsImportsGenerator.generateOutputs(output, environment.getJsImports()); - } - - private String emitNameMapping(Library library) { - SourceBuilder builder = new SourceBuilder(); - library.accept( - new AbstractVisitor() { - @Override - public void exitMethod(Method method) { - MethodDescriptor methodDescriptor = method.getDescriptor(); - String methodImplementationName = - environment.getMethodImplementationName(methodDescriptor); - checkState(methodImplementationName.startsWith("$")); - builder.append( - String.format( - "%s:%s", - methodImplementationName.substring(1), - methodDescriptor.getQualifiedBinaryName())); - builder.newLine(); - } - }); - return builder.build(); - } } diff --git a/transpiler/java/com/google/j2cl/transpiler/backend/wasm/WasmGeneratorStage.java b/transpiler/java/com/google/j2cl/transpiler/backend/wasm/WasmGeneratorStage.java new file mode 100644 index 0000000000..d108839766 --- /dev/null +++ b/transpiler/java/com/google/j2cl/transpiler/backend/wasm/WasmGeneratorStage.java @@ -0,0 +1,263 @@ +/* + * Copyright 2020 Google Inc. + * + * 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 com.google.j2cl.transpiler.backend.wasm; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Predicates.not; + +import com.google.common.collect.ImmutableList; +import com.google.j2cl.common.OutputUtils; +import com.google.j2cl.common.OutputUtils.Output; +import com.google.j2cl.common.Problems; +import com.google.j2cl.common.SourcePosition; +import com.google.j2cl.transpiler.ast.AbstractVisitor; +import com.google.j2cl.transpiler.ast.ArrayTypeDescriptor; +import com.google.j2cl.transpiler.ast.CompilationUnit; +import com.google.j2cl.transpiler.ast.Field; +import com.google.j2cl.transpiler.ast.Library; +import com.google.j2cl.transpiler.ast.Method; +import com.google.j2cl.transpiler.ast.MethodDescriptor; +import com.google.j2cl.transpiler.ast.Type; +import com.google.j2cl.transpiler.ast.TypeDeclaration; +import com.google.j2cl.transpiler.ast.TypeDescriptor; +import com.google.j2cl.transpiler.ast.Variable; +import com.google.j2cl.transpiler.backend.common.SourceBuilder; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +/** Generates all the outputs for Wasm compilation. */ +public class WasmGeneratorStage { + + private final Problems problems; + private final Output output; + private final Path libraryInfoOutputPath; + private WasmGenerationEnvironment environment; + + public WasmGeneratorStage(Output output, Path libraryInfoOutputPath, Problems problems) { + this.output = output; + this.libraryInfoOutputPath = libraryInfoOutputPath; + this.problems = problems; + } + + public void generateModularOutput(Library library) { + if (libraryInfoOutputPath != null) { + OutputUtils.writeToFile(libraryInfoOutputPath, new byte[0], problems); + } + + environment = + new WasmGenerationEnvironment( + library, JsImportsGenerator.collectImports(library, problems), /* isModular= */ true); + SummaryBuilder summaryBuilder = new SummaryBuilder(library, environment, problems); + + // TODO(rluble): Introduce/use flags to emit the readable version of the summary. For now emit + // summaries in both binary and text form for now. + output.write("summary.txtpb", summaryBuilder.toJson(problems)); + output.write("summary.binpb", summaryBuilder.toByteArray()); + + List usedNativeArrayTypes = collectUsedNativeArrayTypes(library); + + copyJavaSources(library); + + emitToFile( + "types.wat", + generator -> { + generator.emitDynamicDispatchMethodTypes(library); + generator.emitNativeArrayTypes(usedNativeArrayTypes); + generator.emitForEachType( + library, generator::renderModularTypeStructs, "type definition"); + }); + + emitToFile( + "functions.wat", + generator -> generator.emitForEachType(library, generator::renderTypeMethods, "methods")); + + emitToFile("globals.wat", generator -> generator.emitGlobals(library)); + + emitToFile("data.wat", generator -> generator.emitDataSegments(library)); + + generateJsImportsFile(); + } + + private void emitToFile(String filename, Consumer emitter) { + SourceBuilder builder = new SourceBuilder(); + WasmConstructsGenerator generator = new WasmConstructsGenerator(environment, builder); + + emitter.accept(generator); + + String content = builder.build(); + if (content.isEmpty()) { + return; + } + output.write(filename, content); + } + + public void generateMonolithicOutput(Library library) { + copyJavaSources(library); + generateWasmModule(library); + generateJsImportsFile(); + } + + private void copyJavaSources(Library library) { + library.getCompilationUnits().stream() + .filter(not(CompilationUnit::isSynthetic)) + .forEach( + compilationUnit -> + output.copyFile( + compilationUnit.getFilePath(), compilationUnit.getPackageRelativePath())); + } + + private void generateWasmModule(Library library) { + environment = + new WasmGenerationEnvironment( + library, JsImportsGenerator.collectImports(library, problems)); + SourceBuilder builder = new SourceBuilder(); + WasmConstructsGenerator generator = new WasmConstructsGenerator(environment, builder); + + List usedNativeArrayTypes = collectUsedNativeArrayTypes(library); + + builder.appendln(";;; Code generated by J2WASM"); + builder.append("(module"); + // Emit all types at the beginning of the module. + generator.emitLibraryRecGroup(library, usedNativeArrayTypes); + + // Declare a tag that will be used for Java exceptions. The tag has a single parameter that is + // the Throwable object being thrown by the throw instruction. + // The throw instruction will refer to this tag and will expect a single element in the stack + // with the type $java.lang.Throwable. + // TODO(b/277970998): Decide how to handle this hard coded import w.r.t. import generation. + builder.newLine(); + builder.append( + "(import \"imports\" \"j2wasm.ExceptionUtils.tag\" (tag $exception.event (param" + + " externref)))"); + // Add an export that uses the tag to workarund binaryen assuming the tag is never instantiated. + builder.append( + "(func $keep_tag_alive_hack (export \"_tag_hack_\") (param $param externref) " + + "(throw $exception.event (local.get $param)))"); + + // Emit all the globals, e.g. vtable instances, etc. + generator.emitDataSegments(library); + generator.emitDispatchTablesInitialization(library); + generator.emitEmptyArraySingletons(usedNativeArrayTypes); + generator.emitGlobals(library); + + // Emit intrinsics imports + generator.emitImportsForBinaryenIntrinsics(library); + + // Last, emit all methods at the very end so that the synthetic code generated above does + // not inherit an incorrect source position. + generator.emitForEachType(library, generator::renderTypeMethods, "methods"); + + builder.newLine(); + builder.append(")"); + output.write("module.wat", builder.buildToList()); + output.write("namemap", emitNameMapping(library)); + } + + public void generateMethods(List methods) { + if (methods.isEmpty()) { + return; + } + + // Create the type objects and add all the exported methods to the corresponding type to + // initialize the WasmGenerationEnvironment. + CompilationUnit cu = CompilationUnit.createSynthetic("wasm.exports"); + Map typesByDeclaration = new LinkedHashMap<>(); + methods.forEach( + m -> { + TypeDeclaration typeDeclaration = + m.getDescriptor().getEnclosingTypeDescriptor().getTypeDeclaration(); + Type type = + typesByDeclaration.computeIfAbsent( + typeDeclaration, t -> new Type(SourcePosition.NONE, t)); + type.addMember(m); + }); + typesByDeclaration.values().forEach(cu::addType); + Library library = Library.newBuilder().setCompilationUnits(ImmutableList.of(cu)).build(); + environment = + new WasmGenerationEnvironment( + library, JsImportsGenerator.collectImports(library, problems)); + + SourceBuilder builder = new SourceBuilder(); + WasmConstructsGenerator generator = new WasmConstructsGenerator(environment, builder); + + methods.forEach(generator::renderMethod); + output.write("functions.wat", builder.buildToList()); + } + + private List collectUsedNativeArrayTypes(Library library) { + Set usedArrayTypes = new LinkedHashSet<>(); + // Collect native arrays from fields and variables; this covers all scenarios. + library.accept( + // TODO(b/303659726): Generalize a type visitor that could be used here and other places + // like in ImportGatherer. Or consider emitting the one dimensional array type for all + // native types. + new AbstractVisitor() { + @Override + public void exitField(Field field) { + collectIfArrayType(field.getDescriptor().getTypeDescriptor()); + } + + @Override + public void exitVariable(Variable variable) { + collectIfArrayType(variable.getTypeDescriptor()); + } + + private void collectIfArrayType(TypeDescriptor typeDescriptor) { + if (!typeDescriptor.isArray()) { + return; + } + + ArrayTypeDescriptor arrayTypeDescriptor = (ArrayTypeDescriptor) typeDescriptor; + if (arrayTypeDescriptor.isNativeWasmArray()) { + usedArrayTypes.add(arrayTypeDescriptor); + } + } + }); + + return new ArrayList<>(usedArrayTypes); + } + + private void generateJsImportsFile() { + JsImportsGenerator.generateOutputs(output, environment.getJsImports()); + } + + private String emitNameMapping(Library library) { + SourceBuilder builder = new SourceBuilder(); + library.accept( + new AbstractVisitor() { + @Override + public void exitMethod(Method method) { + MethodDescriptor methodDescriptor = method.getDescriptor(); + String methodImplementationName = + environment.getMethodImplementationName(methodDescriptor); + checkState(methodImplementationName.startsWith("$")); + builder.append( + String.format( + "%s:%s", + methodImplementationName.substring(1), + methodDescriptor.getQualifiedBinaryName())); + builder.newLine(); + } + }); + return builder.build(); + } +}