Version 2.19.0-11.0.dev

Merge commit 'd8d9e06804dbeca93a30cc5283d96baaed89d746' into 'dev'
diff --git a/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart b/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart
index 13583e9..b43fd05 100644
--- a/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart
+++ b/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart
@@ -19,7 +19,6 @@
 const snapshots = <String>[
   'analysis_server',
   'dart2js',
-  'dartanalyzer',
   'dartdev',
   'dartdevc',
   'dds',
diff --git a/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart b/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart
index ade286d..42bc777 100644
--- a/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart
+++ b/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart
@@ -21,7 +21,6 @@
 const snapshots = <String>[
   'analysis_server',
   'dart2js',
-  'dartanalyzer',
   'dartdev',
   'dartdevc',
   'dds',
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index 56dca65..0e0f2c1 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -1340,7 +1340,7 @@
         b.i32_const(offset);
         b.i32_add();
       }
-      b.call_indirect(selector.signature);
+      b.call_indirect(selector.signature, translator.dispatchTable.wasmTable);
 
       translator.functions.activateSelector(selector);
     }
@@ -1599,7 +1599,7 @@
       b.i32_const(offset);
       b.i32_add();
     }
-    b.call_indirect(selector.signature);
+    b.call_indirect(selector.signature, translator.dispatchTable.wasmTable);
 
     translator.functions.activateSelector(selector);
 
@@ -1765,28 +1765,13 @@
   @override
   w.ValueType visitFunctionInvocation(
       FunctionInvocation node, w.ValueType expectedType) {
-    Expression receiver = node.receiver;
-    if (receiver is InstanceGet &&
-        receiver.interfaceTarget == translator.wasmFunctionCall) {
-      // Receiver is a WasmFunction
-      assert(receiver.name.text == "call");
-      w.RefType receiverType =
-          translator.translateType(dartTypeOf(receiver.receiver)) as w.RefType;
-      w.Local temp = addLocal(receiverType);
-      wrap(receiver.receiver, receiverType);
-      b.local_set(temp);
-      w.FunctionType functionType = receiverType.heapType as w.FunctionType;
-      assert(node.arguments.positional.length == functionType.inputs.length);
-      for (int i = 0; i < node.arguments.positional.length; i++) {
-        wrap(node.arguments.positional[i], functionType.inputs[i]);
-      }
-      b.local_get(temp);
-      b.call_ref();
-      return translator.outputOrVoid(functionType.outputs);
-    }
+    w.ValueType? intrinsicResult =
+        intrinsifier.generateFunctionCallIntrinsic(node);
+    if (intrinsicResult != null) return intrinsicResult;
+
     int parameterCount = node.functionType?.requiredParameterCount ??
         node.arguments.positional.length;
-    return _functionCall(parameterCount, receiver, node.arguments);
+    return _functionCall(parameterCount, node.receiver, node.arguments);
   }
 
   w.ValueType _functionCall(
diff --git a/pkg/dart2wasm/lib/dispatch_table.dart b/pkg/dart2wasm/lib/dispatch_table.dart
index 9817d7d..a283258 100644
--- a/pkg/dart2wasm/lib/dispatch_table.dart
+++ b/pkg/dart2wasm/lib/dispatch_table.dart
@@ -149,6 +149,7 @@
   final Map<int, SelectorInfo> selectorInfo = {};
   final Map<String, int> dynamicGets = {};
   late final List<Reference?> table;
+  late final w.DefinedTable wasmTable;
 
   DispatchTable(this.translator)
       : selectorMetadata =
@@ -292,11 +293,11 @@
         firstAvailable++;
       }
     }
+
+    wasmTable = translator.m.addTable(w.RefType.func(), table.length);
   }
 
   void output() {
-    w.Module m = translator.m;
-    w.DefinedTable wasmTable = m.addTable(w.RefType.func(), table.length);
     for (int i = 0; i < table.length; i++) {
       Reference? target = table[i];
       if (target != null) {
diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart
index eaee6d1..6c190c7 100644
--- a/pkg/dart2wasm/lib/functions.dart
+++ b/pkg/dart2wasm/lib/functions.dart
@@ -34,36 +34,35 @@
 
   void collectImportsAndExports() {
     for (Library library in translator.libraries) {
-      for (Procedure procedure in library.procedures) {
-        _importOrExport(procedure);
-      }
+      library.procedures.forEach(_importOrExport);
+      library.fields.forEach(_importOrExport);
       for (Class cls in library.classes) {
-        for (Procedure procedure in cls.procedures) {
-          _importOrExport(procedure);
-        }
+        cls.procedures.forEach(_importOrExport);
       }
     }
   }
 
-  void _importOrExport(Procedure procedure) {
-    String? importName = translator.getPragma(procedure, "wasm:import");
+  void _importOrExport(Member member) {
+    String? importName = translator.getPragma(member, "wasm:import");
     if (importName != null) {
       int dot = importName.indexOf('.');
       if (dot != -1) {
-        assert(!procedure.isInstanceMember);
+        assert(!member.isInstanceMember);
         String module = importName.substring(0, dot);
         String name = importName.substring(dot + 1);
-        w.FunctionType ftype = _makeFunctionType(
-            procedure.reference, procedure.function.returnType, null,
-            isImportOrExport: true);
-        _functions[procedure.reference] =
-            m.importFunction(module, name, ftype, "$importName (import)");
+        if (member is Procedure) {
+          w.FunctionType ftype = _makeFunctionType(
+              member.reference, member.function.returnType, null,
+              isImportOrExport: true);
+          _functions[member.reference] =
+              m.importFunction(module, name, ftype, "$importName (import)");
+        }
       }
     }
     String? exportName =
-        translator.getPragma(procedure, "wasm:export", procedure.name.text);
+        translator.getPragma(member, "wasm:export", member.name.text);
     if (exportName != null) {
-      addExport(procedure.reference, exportName);
+      addExport(member.reference, exportName);
     }
   }
 
@@ -72,16 +71,26 @@
   }
 
   void initialize() {
-    // Add all exports to the worklist
-    for (Reference target in exports.keys) {
-      worklist.add(target);
-      Procedure node = target.asProcedure;
-      assert(!node.isInstanceMember);
-      assert(!node.isGetter);
-      w.FunctionType ftype = _makeFunctionType(
-          target, node.function.returnType, null,
-          isImportOrExport: true);
-      _functions[target] = m.addFunction(ftype, "$node");
+    // Add exports to the module and add exported functions to the worklist
+    for (var export in exports.entries) {
+      Reference target = export.key;
+      Member node = target.asMember;
+      if (node is Procedure) {
+        worklist.add(target);
+        assert(!node.isInstanceMember);
+        assert(!node.isGetter);
+        w.FunctionType ftype = _makeFunctionType(
+            target, node.function.returnType, null,
+            isImportOrExport: true);
+        w.DefinedFunction function = m.addFunction(ftype, "$node");
+        _functions[target] = function;
+        m.exportFunction(export.value, function);
+      } else if (node is Field) {
+        w.Table? table = translator.getTable(node);
+        if (table != null) {
+          m.exportTable(export.value, table);
+        }
+      }
     }
 
     // Value classes are always implicitly allocated.
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
index f7b12e47..7971b28 100644
--- a/pkg/dart2wasm/lib/intrinsics.dart
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -152,6 +152,18 @@
       return w.NumType.i64;
     }
 
+    // WasmTable.size
+    if (cls == translator.wasmTableClass) {
+      if (receiver is! StaticGet || receiver.target is! Field) {
+        throw "Table size not directly on a static field"
+            " at ${node.location}";
+      }
+      w.Table table = translator.getTable(receiver.target as Field)!;
+      assert(name == "size");
+      b.table_size(table);
+      return w.NumType.i32;
+    }
+
     // int.bitlength
     if (cls == translator.coreTypes.intClass && name == 'bitLength') {
       w.Local temp = codeGen.function.addLocal(w.NumType.i64);
@@ -447,6 +459,25 @@
       }
     }
 
+    // WasmTable.[] and WasmTable.[]=
+    if (cls == translator.wasmTableClass) {
+      if (receiver is! StaticGet || receiver.target is! Field) {
+        throw "Table indexing not directly on a static field"
+            " at ${node.location}";
+      }
+      w.Table table = translator.getTable(receiver.target as Field)!;
+      codeGen.wrap(node.arguments.positional[0], w.NumType.i32);
+      if (name == '[]') {
+        b.table_get(table);
+        return table.type;
+      } else {
+        assert(name == '[]=');
+        codeGen.wrap(node.arguments.positional[1], table.type);
+        b.table_set(table);
+        return codeGen.voidMarker;
+      }
+    }
+
     // List.[] on list constants
     if (receiver is ConstantExpression &&
         receiver.constant is ListConstant &&
@@ -974,6 +1005,60 @@
     return null;
   }
 
+  w.ValueType? generateFunctionCallIntrinsic(FunctionInvocation node) {
+    Expression receiver = node.receiver;
+
+    if (receiver is InstanceGet &&
+        receiver.interfaceTarget == translator.wasmFunctionCall) {
+      // Receiver is a WasmFunction
+      assert(receiver.name.text == "call");
+      w.RefType receiverType =
+          translator.translateType(dartTypeOf(receiver.receiver)) as w.RefType;
+      w.Local temp = codeGen.addLocal(receiverType);
+      codeGen.wrap(receiver.receiver, receiverType);
+      b.local_set(temp);
+      w.FunctionType functionType = receiverType.heapType as w.FunctionType;
+      assert(node.arguments.positional.length == functionType.inputs.length);
+      for (int i = 0; i < node.arguments.positional.length; i++) {
+        codeGen.wrap(node.arguments.positional[i], functionType.inputs[i]);
+      }
+      b.local_get(temp);
+      b.call_ref();
+      return translator.outputOrVoid(functionType.outputs);
+    }
+
+    if (receiver is InstanceInvocation &&
+        receiver.interfaceTarget == translator.wasmTableCallIndirect) {
+      // Receiver is a WasmTable.callIndirect
+      assert(receiver.name.text == "callIndirect");
+      Expression tableExp = receiver.receiver;
+      if (tableExp is! StaticGet || tableExp.target is! Field) {
+        throw "Table callIndirect not directly on a static field"
+            " at ${node.location}";
+      }
+      w.Table table = translator.getTable(tableExp.target as Field)!;
+      InterfaceType wasmFunctionType = InterfaceType(
+          translator.wasmFunctionClass,
+          Nullability.nonNullable,
+          [receiver.arguments.types.single]);
+      w.RefType receiverType =
+          translator.translateType(wasmFunctionType) as w.RefType;
+      w.Local tableIndex = codeGen.addLocal(w.NumType.i32);
+      codeGen.wrap(receiver.arguments.positional.single, w.NumType.i32);
+      b.local_set(tableIndex);
+      w.FunctionType functionType = receiverType.heapType as w.FunctionType;
+      assert(node.arguments.positional.length == functionType.inputs.length);
+      for (int i = 0; i < node.arguments.positional.length; i++) {
+        codeGen.wrap(node.arguments.positional[i], functionType.inputs[i]);
+      }
+      b.local_get(tableIndex);
+      b.call_indirect(functionType, table);
+      return translator.outputOrVoid(functionType.outputs);
+    }
+
+    return null;
+  }
+
   bool generateMemberIntrinsic(Reference target, w.DefinedFunction function,
       List<w.Local> paramLocals, w.Label? returnLabel) {
     Member member = target.asMember;
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 4fe9d4c..c130f70 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -76,6 +76,7 @@
   late final Class wasmEqRefClass;
   late final Class wasmDataRefClass;
   late final Class wasmFunctionClass;
+  late final Class wasmTableClass;
   late final Class boxedBoolClass;
   late final Class boxedIntClass;
   late final Class boxedDoubleClass;
@@ -112,6 +113,7 @@
   late final Class typeErrorClass;
   late final Class typeUniverseClass;
   late final Procedure wasmFunctionCall;
+  late final Procedure wasmTableCallIndirect;
   late final Procedure stackTraceCurrent;
   late final Procedure stringEquals;
   late final Procedure stringInterpolate;
@@ -143,6 +145,7 @@
   final Map<Field, int> fieldIndex = {};
   final Map<TypeParameter, int> typeParameterIndex = {};
   final Map<Reference, ParameterInfo> staticParamInfo = {};
+  final Map<Field, w.DefinedTable> declaredTables = {};
   late Procedure mainFunction;
   late final w.Module m;
   late final w.DefinedFunction initFunction;
@@ -194,6 +197,7 @@
     wasmEqRefClass = lookupWasm("WasmEqRef");
     wasmDataRefClass = lookupWasm("WasmDataRef");
     wasmFunctionClass = lookupWasm("WasmFunction");
+    wasmTableClass = lookupWasm("WasmTable");
     boxedBoolClass = lookupCore("_BoxedBool");
     boxedIntClass = lookupCore("_BoxedInt");
     boxedDoubleClass = lookupCore("_BoxedDouble");
@@ -232,6 +236,8 @@
     byteDataViewClass = lookupTypedData("_ByteDataView");
     wasmFunctionCall =
         wasmFunctionClass.procedures.firstWhere((p) => p.name.text == "call");
+    wasmTableCallIndirect = wasmTableClass.procedures
+        .firstWhere((p) => p.name.text == "callIndirect");
     stackTraceCurrent =
         stackTraceClass.procedures.firstWhere((p) => p.name.text == "current");
     stringEquals =
@@ -362,9 +368,7 @@
         if (!options.printWasm) print("");
       }
 
-      if (exportName != null) {
-        m.exportFunction(exportName, function);
-      } else if (options.exportAll) {
+      if (options.exportAll && exportName == null) {
         m.exportFunction(canonicalName, function);
       }
       var codeGen = CodeGenerator(this, function, reference);
@@ -734,6 +738,34 @@
     }
   }
 
+  /// Get the Wasm table declared by [field], or `null` if [field] is not a
+  /// declaration of a Wasm table.
+  ///
+  /// This function participates in tree shaking in the sense that if it's
+  /// never called for a particular table declaration, that table is not added
+  /// to the output module.
+  w.DefinedTable? getTable(Field field) {
+    w.DefinedTable? table = declaredTables[field];
+    if (table != null) return table;
+    DartType fieldType = field.type;
+    if (fieldType is InterfaceType && fieldType.classNode == wasmTableClass) {
+      w.RefType elementType =
+          translateType(fieldType.typeArguments.single) as w.RefType;
+      Expression sizeExp = (field.initializer as ConstructorInvocation)
+          .arguments
+          .positional
+          .single;
+      if (sizeExp is StaticGet && sizeExp.target is Field) {
+        sizeExp = (sizeExp.target as Field).initializer!;
+      }
+      int size = sizeExp is ConstantExpression
+          ? (sizeExp.constant as IntConstant).value
+          : (sizeExp as IntLiteral).value;
+      return declaredTables[field] = m.addTable(elementType, size);
+    }
+    return null;
+  }
+
   Member? singleTarget(TreeNode node) {
     DirectCallMetadataRepository metadata =
         component.metadata[DirectCallMetadataRepository.repositoryTag]
diff --git a/pkg/vm/lib/transformations/pragma.dart b/pkg/vm/lib/transformations/pragma.dart
index fe33aed..cb8ffff 100644
--- a/pkg/vm/lib/transformations/pragma.dart
+++ b/pkg/vm/lib/transformations/pragma.dart
@@ -158,8 +158,8 @@
       case kWasmEntryPointPragmaName:
         return ParsedEntryPointPragma(PragmaEntryPointType.Default);
       case kWasmExportPragmaName:
-        // Exports are treated as called entry points.
-        return ParsedEntryPointPragma(PragmaEntryPointType.CallOnly);
+        // Exports are treated as entry points.
+        return ParsedEntryPointPragma(PragmaEntryPointType.Default);
       default:
         return null;
     }
diff --git a/pkg/wasm_builder/lib/src/instructions.dart b/pkg/wasm_builder/lib/src/instructions.dart
index beb2411..64fdd47 100644
--- a/pkg/wasm_builder/lib/src/instructions.dart
+++ b/pkg/wasm_builder/lib/src/instructions.dart
@@ -583,6 +583,32 @@
     writeUnsigned(global.index);
   }
 
+  // Table instructions
+
+  /// Emit a `table.get` instruction.
+  void table_get(Table table) {
+    assert(_verifyTypes(const [NumType.i32], [table.type],
+        trace: ['table.get', table.index]));
+    writeByte(0x25);
+    writeUnsigned(table.index);
+  }
+
+  /// Emit a `table.set` instruction.
+  void table_set(Table table) {
+    assert(_verifyTypes([NumType.i32, table.type], const [],
+        trace: ['table.set', table.index]));
+    writeByte(0x26);
+    writeUnsigned(table.index);
+  }
+
+  /// Emit a `table.size` instruction.
+  void table_size(Table table) {
+    assert(_verifyTypes(const [], const [NumType.i32],
+        trace: ['table.size', table.index]));
+    writeBytes([0xFC, 0x10]);
+    writeUnsigned(table.index);
+  }
+
   // Memory instructions
 
   void _writeMemArg(Memory memory, int offset, int align) {
diff --git a/sdk/lib/wasm/wasm_types.dart b/sdk/lib/wasm/wasm_types.dart
index ea09211..8139271 100644
--- a/sdk/lib/wasm/wasm_types.dart
+++ b/sdk/lib/wasm/wasm_types.dart
@@ -153,6 +153,32 @@
   external F get call;
 }
 
+/// A Wasm table.
+@pragma("wasm:entry-point")
+class WasmTable<T> {
+  /// Declare a table with the given size.
+  ///
+  /// Must be an initializer for a static field. The [size] argument must be
+  /// either a constant or a reference to a `static` `final` field with a
+  /// constant initializer.
+  external WasmTable(int size);
+
+  /// Read from an entry in the table.
+  external T operator [](WasmI32 index);
+
+  /// Write to an entry in the table.
+  external void operator []=(WasmI32 index, T value);
+
+  /// The size of the table.
+  external WasmI32 get size;
+
+  /// Call a function stored in the table using the `call_indirect` Wasm
+  /// instructionm. The function value returned from this method must be
+  /// called directly.
+  @pragma("wasm:entry-point")
+  external F callIndirect<F extends Function>(WasmI32 index);
+}
+
 extension IntToWasmInt on int {
   WasmI32 toWasmI32() => WasmI32.fromInt(this);
   WasmI64 toWasmI64() => WasmI64.fromInt(this);
diff --git a/tests/web/wasm/table_test.dart b/tests/web/wasm/table_test.dart
new file mode 100644
index 0000000..fb6c31c
--- /dev/null
+++ b/tests/web/wasm/table_test.dart
@@ -0,0 +1,52 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:wasm';
+
+import 'package:expect/expect.dart';
+
+WasmTable<WasmFuncRef?> funcrefTable = WasmTable(3);
+WasmTable<WasmFunction<int Function(int)>?> funcTable = WasmTable(1);
+
+void f1() {}
+
+void f2(int x) {
+  Expect.equals(4, x);
+}
+
+int f3(int x) => x + 1;
+
+main() {
+  // Initialize untyped function table
+  Expect.equals(3, funcrefTable.size.toIntUnsigned());
+  funcrefTable[0.toWasmI32()] = WasmFunction.fromFunction(f1);
+  funcrefTable[1.toWasmI32()] = WasmFunction.fromFunction(f2);
+  funcrefTable[2.toWasmI32()] = WasmFunction.fromFunction(f3);
+
+  // Reading and calling functions in untyped function table
+  WasmFunction<void Function()>.fromRef(funcrefTable[0.toWasmI32()]!).call();
+  WasmFunction<void Function(int)>.fromRef(funcrefTable[1.toWasmI32()]!)
+      .call(4);
+  Expect.equals(
+      6,
+      WasmFunction<int Function(int)>.fromRef(funcrefTable[2.toWasmI32()]!)
+          .call(5));
+
+  // Calling functions in untyped function table with callIndirect
+  funcrefTable.callIndirect<void Function()>(0.toWasmI32())();
+  funcrefTable.callIndirect<void Function(int)>(1.toWasmI32())(4);
+  Expect.equals(
+      16, funcrefTable.callIndirect<int Function(int)>(2.toWasmI32())(15));
+
+  // Initialize typed function table
+  Expect.equals(1, funcTable.size.toIntUnsigned());
+  funcTable[0.toWasmI32()] = WasmFunction.fromFunction(f3);
+
+  // Reading and calling function in typed function table
+  Expect.equals(8, funcTable[0.toWasmI32()]!.call(7));
+
+  // Calling function in typed function table with callIndirect
+  Expect.equals(
+      18, funcTable.callIndirect<int Function(int)>(0.toWasmI32())(17));
+}
diff --git a/tools/VERSION b/tools/VERSION
index 33ccdaf..9dd910b 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 19
 PATCH 0
-PRERELEASE 10
+PRERELEASE 11
 PRERELEASE_PATCH 0
\ No newline at end of file