Skip to content

Commit

Permalink
[WASM] Create WasmTypeLayout in a way that is compatible with modular…
Browse files Browse the repository at this point in the history
… compilation.

PiperOrigin-RevId: 562760101
  • Loading branch information
rluble authored and copybara-github committed Sep 5, 2023
1 parent 54deeb5 commit 4442a4a
Show file tree
Hide file tree
Showing 30 changed files with 297 additions and 211 deletions.
10 changes: 9 additions & 1 deletion build_defs/internal_do_not_use/j2wasm_common.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ def _compile(
exported_plugins = exported_plugins,
backend = "WASM_MODULAR",
output_jar = output_jar,
javac_opts = javac_opts,
javac_opts = javac_opts + [
# Preserve the private fields in turbine compilation. In order to create wasm structs for
# Java classes, all the fields from the super classes need to be seen even if compiling
# in a different library.
"-XDturbine.emitPrivateFields",
# Disable analysis for thread safety since it does not expect to see
# private fields from dependencies.
"-Xep:ThreadSafe:OFF",
],
artifact_suffix = artifact_suffix,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,23 @@ public MethodDescriptor getCopyMethodDescriptor() {
.build();
}

/** Returns the FieldDescriptor corresponding to the enclosing class instance. */
public FieldDescriptor getFieldDescriptorForEnclosingInstance() {
return FieldDescriptor.newBuilder()
.setEnclosingTypeDescriptor(toUnparameterizedTypeDescriptor())
.setName("this")
.setTypeDescriptor(
getEnclosingTypeDescriptor()
// Consider the outer instance type to be nullable to be make the type consistent
// across all places where it is used (backing field and constructor parameters).
.toNullable())
.setSynthetic(true)
.setFinal(true)
.setSynthetic(true)
.setOrigin(FieldOrigin.SYNTHETIC_OUTER_FIELD)
.build();
}

@Memoized
@Override
public String getUniqueId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format;
import static java.util.Comparator.comparingInt;

Expand Down Expand Up @@ -364,21 +365,39 @@ public JsMethodImport getJsMethodImport(MethodDescriptor methodDescriptor) {
.sorted(comparingInt(t -> t.getDeclaration().getClassHierarchyDepth()))
.forEach(
t -> {
WasmTypeLayout superTypeLayout = null;
if (t.getSuperTypeDescriptor() != null) {
superTypeLayout =
wasmTypeLayoutByTypeDeclaration.get(
t.getSuperTypeDescriptor().getTypeDeclaration());
}
wasmTypeLayoutByTypeDeclaration.put(
t.getDeclaration(), WasmTypeLayout.create(t, superTypeLayout));
TypeDeclaration typeDeclaration = t.getDeclaration();
WasmTypeLayout superWasmLayout =
getWasmLayout(typeDeclaration.getSuperTypeDeclaration());
var previous =
wasmTypeLayoutByTypeDeclaration.put(
typeDeclaration, WasmTypeLayout.createFromType(t, superWasmLayout));
// Since the layout is for a type in the AST, it is expected that the
// layout was not already created from the descriptor.
checkState(previous == null);
});

assignInterfaceSlots(library);

this.jsImports = jsImports;
}

private WasmTypeLayout getWasmLayout(TypeDeclaration typeDeclaration) {
if (typeDeclaration == null) {
return null;
}
if (!wasmTypeLayoutByTypeDeclaration.containsKey(typeDeclaration)) {
WasmTypeLayout wasmTypeLayout =
WasmTypeLayout.createFromTypeDeclaration(
typeDeclaration, getWasmTypeLayout(typeDeclaration.getSuperTypeDeclaration()));
// If the supertype layout was not created by the type it is requested here,
// it means that the type is from a different library and is ok to
// create its layout from the type model.
wasmTypeLayoutByTypeDeclaration.put(typeDeclaration, wasmTypeLayout);
return wasmTypeLayout;
}
return wasmTypeLayoutByTypeDeclaration.get(typeDeclaration);
}

/**
* Assigns a slot number (i.e. an index in the itable array) for each interface in the itable.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,59 @@
import com.google.j2cl.transpiler.ast.FieldDescriptor;
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.TypeDescriptors;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import javax.annotation.Nullable;

/** Runtime representation of a Java class in Wasm. */
@AutoValue
abstract class WasmTypeLayout {
static WasmTypeLayout create(Type javaType, WasmTypeLayout wasmSupertypeLayout) {
return new AutoValue_WasmTypeLayout(javaType, wasmSupertypeLayout);
/** Create a layout for a type that is declared the library being compiled. */
static WasmTypeLayout createFromType(Type javaType, WasmTypeLayout wasmSupertypeLayout) {
return new AutoValue_WasmTypeLayout(
javaType, javaType.getTypeDescriptor(), wasmSupertypeLayout);
}

/** The Java class represented by this Wasm type. */
/** Create a layout for a type that is declared in a different library. */
static WasmTypeLayout createFromTypeDeclaration(
TypeDeclaration typeDeclaration, WasmTypeLayout wasmSupertypeLayout) {
return new AutoValue_WasmTypeLayout(
null, typeDeclaration.toUnparameterizedTypeDescriptor(), wasmSupertypeLayout);
}

/**
* The Java class represented by this Wasm type if the type is in the current library, {@code
* null} if the type is from a different library.
*/
@Nullable
abstract Type getJavaType();

abstract DeclaredTypeDescriptor getTypeDescriptor();

/** The wasm representation of the superclass for this Java class. */
@Nullable
abstract WasmTypeLayout getWasmSupertypeLayout();

/** Returns all the fields that will be in the layout for struct for the Java class. */
@Memoized
Collection<FieldDescriptor> getAllInstanceFields() {
List<FieldDescriptor> instanceFields = new ArrayList<>();

if (getWasmSupertypeLayout() != null) {
instanceFields.addAll(getWasmSupertypeLayout().getAllInstanceFields());
WasmTypeLayout wasmSupertypeLayout = getWasmSupertypeLayout();
List<FieldDescriptor> instanceFields = new ArrayList<>();
if (wasmSupertypeLayout != null) {
instanceFields.addAll(wasmSupertypeLayout.getAllInstanceFields());
}
ImmutableList<FieldDescriptor> declaredInstanceFields =
getJavaType().getInstanceFields().stream()
.map(Field::getDescriptor)
.collect(toImmutableList());
if (TypeDescriptors.isWasmArraySubtype(getJavaType().getTypeDescriptor())) {

ImmutableList<FieldDescriptor> declaredInstanceFields = getDeclaredInstanceFields();

if (TypeDescriptors.isWasmArraySubtype(getTypeDescriptor())) {
// TODO(b/296475021): Remove the hack to treat the field as overriden by subclass' field.
// Override the type of the elements field in Wasm arrays by replacing the WasmArray elements
// field with that of their subtype.
Expand All @@ -72,9 +90,48 @@ Collection<FieldDescriptor> getAllInstanceFields() {
}

instanceFields.addAll(declaredInstanceFields);

return instanceFields;
}

private ImmutableList<FieldDescriptor> getDeclaredInstanceFields() {
Type type = getJavaType();
Stream<FieldDescriptor> declaredFieldDescriptors;
if (type != null) {
// If the type is in the library, just look at the field instances in the AST of the Type.
// In this scenario we will see exactly the instance fields even the ones that are synthesized
// for captures.
declaredFieldDescriptors = type.getInstanceFields().stream().map(Field::getDescriptor);
} else {
// If the type is not in the library, look at the declared fields for the type in the type
// model; in this case we will only see the instance fields that were declared on the type,
// but not the synthetic ones.
declaredFieldDescriptors =
getTypeDescriptor().getDeclaredFieldDescriptors().stream()
.filter(FieldDescriptor::isInstanceMember);

// The only synthetic field we care for types that are not in the current library is the field
// to store the enclosing instance which we can add it explicitly here if needed. In Wasm
// declaring structures and initializing instances requires knowing the full structure of the
// type, which includes all the fields inherited from supertypes including private fields.
// Luckily in Java, types that have other captures can not be subclasses outside the library,
// and due to the fact that initialization is behind a factory method, the structures of these
// types are never leaked outside the compilation unit the type is defined in.
if (getTypeDescriptor().getTypeDeclaration().isCapturingEnclosingInstance()) {
declaredFieldDescriptors =
Stream.concat(
declaredFieldDescriptors,
Stream.of(getTypeDescriptor().getFieldDescriptorForEnclosingInstance()));
}
}

return declaredFieldDescriptors
// Declared fields are sorted by mangled name because they might not appear in the same
// order in the AST.
.sorted(Comparator.comparing(FieldDescriptor::getMangledName))
.collect(toImmutableList());
}

/** Returns all the methods that will be part of the vtable for the Java class. */
Collection<MethodDescriptor> getAllPolymorphicMethods() {
return getAllPolymorphicMethodsByMangledName().values();
Expand All @@ -98,13 +155,13 @@ Map<String, MethodDescriptor> getAllPolymorphicMethodsByMangledName() {
instanceMethodsByMangledName.putAll(
getWasmSupertypeLayout().getAllPolymorphicMethodsByMangledName());
}
DeclaredTypeDescriptor typeDescriptor = getJavaType().getTypeDescriptor();
DeclaredTypeDescriptor typeDescriptor = getTypeDescriptor();
for (MethodDescriptor methodDescriptor : typeDescriptor.getPolymorphicMethods()) {
instanceMethodsByMangledName.put(methodDescriptor.getMangledName(), methodDescriptor);
}
// Patch entry for $getClassImpl, since it is explicitly overridden in every class but does not
// appear as overridden at the right target when calling getPolymorphicMethods().
if (!getJavaType().isInterface()) {
if (!typeDescriptor.isInterface()) {
MethodDescriptor getClassMethodDescriptor = getGetClassMethodDescriptor(typeDescriptor);
instanceMethodsByMangledName.put(
getClassMethodDescriptor.getMangledName(), getClassMethodDescriptor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ public boolean enterType(Type type) {
if (type.getDeclaration().isCapturingEnclosingInstance()) {
type.addMember(
0,
Field.Builder.from(getFieldDescriptorForEnclosingInstance(type.getDeclaration()))
Field.Builder.from(
type.getTypeDescriptor().getFieldDescriptorForEnclosingInstance())
.setSourcePosition(type.getSourcePosition())
.build());
}
Expand Down Expand Up @@ -276,7 +277,9 @@ public Method rewriteMethod(Method method) {
? addParameterAndInitializeBackingField(
methodBuilder,
0,
getFieldDescriptorForEnclosingInstance(typeDeclaration),
getCurrentType()
.getTypeDescriptor()
.getFieldDescriptorForEnclosingInstance(),
isDelegatingConstructor,
getCurrentType().getSourcePosition())
: null;
Expand Down Expand Up @@ -397,9 +400,7 @@ public Expression rewriteThisReference(ThisReference thisReference) {
do {
outerFieldAccess =
FieldAccess.newBuilder()
.setTarget(
getFieldDescriptorForEnclosingInstance(
currentTypeDescriptor.getTypeDeclaration()))
.setTarget(currentTypeDescriptor.getFieldDescriptorForEnclosingInstance())
.setQualifier(outerFieldAccess)
.build();
currentTypeDescriptor = currentTypeDescriptor.getEnclosingTypeDescriptor();
Expand Down Expand Up @@ -429,26 +430,6 @@ private static FieldDescriptor getFieldDescriptorForCapture(
.build();
}

/** Returns the FieldDescriptor corresponding to the enclosing class instance. */
private FieldDescriptor getFieldDescriptorForEnclosingInstance(
TypeDeclaration innerTypeDescriptor) {
return FieldDescriptor.newBuilder()
.setEnclosingTypeDescriptor(innerTypeDescriptor.toUnparameterizedTypeDescriptor())
.setName("this")
.setTypeDescriptor(
innerTypeDescriptor
.getEnclosingTypeDeclaration()
.toUnparameterizedTypeDescriptor()
// Consider the outer instance type to be nullable to be make the type consistent
// across all places where it is used (backing field and constructor parameters).
.toNullable())
.setSynthetic(true)
.setFinal(true)
.setSynthetic(true)
.setOrigin(FieldOrigin.SYNTHETIC_OUTER_FIELD)
.build();
}

/** Creates a variable that matches a field definition. */
private static Variable createParameterMatchingField(FieldDescriptor fieldDescriptor) {
return Variable.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@
(field $vtable (ref $anonymousinnerclass.AnonymousInnerClass.1C.vtable))
(field $itable (ref $itable))
(field $f_$systemIdentityHashCode__java_lang_Object (mut i32))
(field $$captured_arg__anonymousinnerclass_AnonymousInnerClass_1C (mut i32))
(field $$outer_this__anonymousinnerclass_AnonymousInnerClass_1C (mut (ref null $anonymousinnerclass.AnonymousInnerClass)))
(field $f_fInC__anonymousinnerclass_AnonymousInnerClass_1C (mut i32))
(field $$captured_arg__anonymousinnerclass_AnonymousInnerClass_1C (mut i32))
))
)
(type $anonymousinnerclass.AnonymousInnerClass.1C.vtable (sub $java.lang.Object.vtable (struct
Expand Down Expand Up @@ -108,8 +108,8 @@
(field $itable (ref $itable))
(field $f_$systemIdentityHashCode__java_lang_Object (mut i32))
(field $$outer_this__anonymousinnerclass_A_B (mut (ref null $anonymousinnerclass.A)))
(field $$outer_this__anonymousinnerclass_AnonymousInnerClass_2 (mut (ref null $anonymousinnerclass.AnonymousInnerClass)))
(field $$captured_a__anonymousinnerclass_AnonymousInnerClass_2 (mut (ref null $anonymousinnerclass.A)))
(field $$outer_this__anonymousinnerclass_AnonymousInnerClass_2 (mut (ref null $anonymousinnerclass.AnonymousInnerClass)))
))
)
(type $anonymousinnerclass.AnonymousInnerClass.2.vtable (sub $anonymousinnerclass.A.B.vtable (struct
Expand All @@ -126,11 +126,11 @@
(field $vtable (ref $anonymousinnerclass.AnonymousInnerClass.3.vtable))
(field $itable (ref $itable))
(field $f_$systemIdentityHashCode__java_lang_Object (mut i32))
(field $$captured_arg__anonymousinnerclass_AnonymousInnerClass_1C (mut i32))
(field $$outer_this__anonymousinnerclass_AnonymousInnerClass_1C (mut (ref null $anonymousinnerclass.AnonymousInnerClass)))
(field $f_fInC__anonymousinnerclass_AnonymousInnerClass_1C (mut i32))
(field $$captured_arg__anonymousinnerclass_AnonymousInnerClass_1C (mut i32))
(field $$outer_this__anonymousinnerclass_AnonymousInnerClass_3 (mut (ref null $anonymousinnerclass.AnonymousInnerClass)))
(field $$captured_arg__anonymousinnerclass_AnonymousInnerClass_3 (mut i32))
(field $$outer_this__anonymousinnerclass_AnonymousInnerClass_3 (mut (ref null $anonymousinnerclass.AnonymousInnerClass)))
))
)
(type $anonymousinnerclass.AnonymousInnerClass.3.vtable (sub $anonymousinnerclass.AnonymousInnerClass.1C.vtable (struct
Expand Down Expand Up @@ -659,7 +659,7 @@
(local $$instance (ref null $anonymousinnerclass.AnonymousInnerClass.1C))
(block
;;@ anonymousinnerclass/AnonymousInnerClass.java:30:10
(local.set $$instance (struct.new $anonymousinnerclass.AnonymousInnerClass.1C (ref.as_non_null (global.get $anonymousinnerclass.AnonymousInnerClass.1C.vtable)) (ref.as_non_null (global.get $itable.empty)) (i32.const 0) (ref.null $anonymousinnerclass.AnonymousInnerClass) (i32.const 0) (i32.const 0)))
(local.set $$instance (struct.new $anonymousinnerclass.AnonymousInnerClass.1C (ref.as_non_null (global.get $anonymousinnerclass.AnonymousInnerClass.1C.vtable)) (ref.as_non_null (global.get $itable.empty)) (i32.const 0) (i32.const 0) (ref.null $anonymousinnerclass.AnonymousInnerClass) (i32.const 0)))
;;@ anonymousinnerclass/AnonymousInnerClass.java:30:10
(call $$ctor__anonymousinnerclass_AnonymousInnerClass__int__void_$p_anonymousinnerclass_AnonymousInnerClass_1C@anonymousinnerclass.AnonymousInnerClass.1C (ref.as_non_null (local.get $$instance))(local.get $$outer_this)(local.get $$captured_arg))
;;@ anonymousinnerclass/AnonymousInnerClass.java:30:10
Expand Down Expand Up @@ -855,7 +855,7 @@
(local $$instance (ref null $anonymousinnerclass.AnonymousInnerClass.2))
(block
;;@ anonymousinnerclass/AnonymousInnerClass.java:29:21
(local.set $$instance (struct.new $anonymousinnerclass.AnonymousInnerClass.2 (ref.as_non_null (global.get $anonymousinnerclass.AnonymousInnerClass.2.vtable)) (ref.as_non_null (global.get $itable.empty)) (i32.const 0) (ref.null $anonymousinnerclass.A) (ref.null $anonymousinnerclass.AnonymousInnerClass) (ref.null $anonymousinnerclass.A)))
(local.set $$instance (struct.new $anonymousinnerclass.AnonymousInnerClass.2 (ref.as_non_null (global.get $anonymousinnerclass.AnonymousInnerClass.2.vtable)) (ref.as_non_null (global.get $itable.empty)) (i32.const 0) (ref.null $anonymousinnerclass.A) (ref.null $anonymousinnerclass.A) (ref.null $anonymousinnerclass.AnonymousInnerClass)))
;;@ anonymousinnerclass/AnonymousInnerClass.java:29:21
(call $$ctor__anonymousinnerclass_AnonymousInnerClass__anonymousinnerclass_A__void_$p_anonymousinnerclass_AnonymousInnerClass_2@anonymousinnerclass.AnonymousInnerClass.2 (ref.as_non_null (local.get $$instance))(local.get $$outer_this)(local.get $$captured_a))
;;@ anonymousinnerclass/AnonymousInnerClass.java:29:21
Expand Down Expand Up @@ -949,7 +949,7 @@
(local $$instance (ref null $anonymousinnerclass.AnonymousInnerClass.3))
(block
;;@ anonymousinnerclass/AnonymousInnerClass.java:33:18
(local.set $$instance (struct.new $anonymousinnerclass.AnonymousInnerClass.3 (ref.as_non_null (global.get $anonymousinnerclass.AnonymousInnerClass.3.vtable)) (ref.as_non_null (global.get $itable.empty)) (i32.const 0) (ref.null $anonymousinnerclass.AnonymousInnerClass) (i32.const 0) (i32.const 0) (ref.null $anonymousinnerclass.AnonymousInnerClass) (i32.const 0)))
(local.set $$instance (struct.new $anonymousinnerclass.AnonymousInnerClass.3 (ref.as_non_null (global.get $anonymousinnerclass.AnonymousInnerClass.3.vtable)) (ref.as_non_null (global.get $itable.empty)) (i32.const 0) (i32.const 0) (ref.null $anonymousinnerclass.AnonymousInnerClass) (i32.const 0) (i32.const 0) (ref.null $anonymousinnerclass.AnonymousInnerClass)))
;;@ anonymousinnerclass/AnonymousInnerClass.java:33:18
(call $$ctor__anonymousinnerclass_AnonymousInnerClass__int__void_$p_anonymousinnerclass_AnonymousInnerClass_3@anonymousinnerclass.AnonymousInnerClass.3 (ref.as_non_null (local.get $$instance))(local.get $$outer_this)(local.get $$captured_arg))
;;@ anonymousinnerclass/AnonymousInnerClass.java:33:18
Expand Down
Loading

0 comments on commit 4442a4a

Please sign in to comment.