[dart2wasm] Use a Wasm array directly in chunked JSON parser state stack

Update `popWasmArray` desugaring to only clear the popped slot when the
element type is nullable.

Use `push/popWasmArray` in `_ChunkedJsonParserState.states` stack to
avoid indirection.

Change-Id: I641b4a78b85640b3676ef935bfd98b8cd5f7789d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/394483
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
diff --git a/pkg/dart2wasm/lib/transformers.dart b/pkg/dart2wasm/lib/transformers.dart
index 1cb91be..d5edd23 100644
--- a/pkg/dart2wasm/lib/transformers.dart
+++ b/pkg/dart2wasm/lib/transformers.dart
@@ -962,8 +962,8 @@
 
   Expression _transformPopWasmArray(StaticInvocation invocation) {
     final elementType = invocation.arguments.types[0] as InterfaceType;
-    final elementTypeNullable =
-        elementType.withDeclaredNullability(Nullability.nullable);
+    final elementIsNullable =
+        elementType.nullability != Nullability.nonNullable;
 
     final positionalArguments = invocation.arguments.positional;
     assert(positionalArguments.length == 2);
@@ -991,6 +991,8 @@
       return cloner.clone(node);
     }
 
+    final List<Statement> blockStatements = [];
+
     // length - 1
     final intSubtractType = _intSubtract.computeSignatureOrFunctionType();
     final lengthMinusOne = InstanceInvocation(InstanceAccessKind.Instance,
@@ -1008,24 +1010,28 @@
       arrayLengthUpdate = ExpressionStatement(
           VariableSet(lengthVariableGet.variable, lengthMinusOne));
     }
+    blockStatements.add(arrayLengthUpdate);
 
     // array[length]
     final arrayGet = StaticInvocation(_wasmArrayElementGet,
-        Arguments([clone(array), clone(length)], types: [elementTypeNullable]));
+        Arguments([clone(array), clone(length)], types: [elementType]));
 
     // final temp = array[length]
     final arrayGetVariable = VariableDeclaration.forValue(arrayGet,
-        isFinal: true, type: elementTypeNullable);
+        isFinal: true, type: elementType);
+    blockStatements.add(arrayGetVariable);
 
     // array[length] = null
-    final arrayClearElement = ExpressionStatement(StaticInvocation(
-        _wasmArrayElementSet,
-        Arguments([clone(array), clone(length), NullLiteral()],
-            types: [elementTypeNullable])));
+    if (elementIsNullable) {
+      final arrayClearElement = ExpressionStatement(StaticInvocation(
+          _wasmArrayElementSet,
+          Arguments([clone(array), clone(length), NullLiteral()],
+              types: [elementType])));
+      blockStatements.add(arrayClearElement);
+    }
 
     return BlockExpression(
-        Block([arrayLengthUpdate, arrayGetVariable, arrayClearElement]),
-        VariableGet(arrayGetVariable));
+        Block(blockStatements), VariableGet(arrayGetVariable));
   }
 }
 
diff --git a/sdk/lib/_internal/wasm/lib/convert_patch.dart b/sdk/lib/_internal/wasm/lib/convert_patch.dart
index 270bc6b..eff0229 100644
--- a/sdk/lib/_internal/wasm/lib/convert_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/convert_patch.dart
@@ -120,7 +120,7 @@
 
   GrowableList<dynamic>? stackPop() {
     assert(stackLength != 0);
-    return popWasmArray<GrowableList<dynamic>>(stack, stackLength);
+    return popWasmArray<GrowableList<dynamic>?>(stack, stackLength);
   }
 
   /** Contents of the current container being built, or null if not building a
@@ -328,7 +328,9 @@
 
   // The current parsing state.
   int state = _ChunkedJsonParser.STATE_INITIAL;
-  GrowableList<int> states = GrowableList<int>.empty();
+
+  WasmArray<WasmI64> states = WasmArray<WasmI64>(0);
+  int statesLength = 0;
 
   /**
    * Stores tokenizer state between chunks.
@@ -390,6 +392,7 @@
   ) {
     state = chunkedParserState.state;
     states = chunkedParserState.states;
+    statesLength = chunkedParserState.statesLength;
     partialState = chunkedParserState.partialState;
     _stringBuffer = chunkedParserState._stringBuffer;
     _numberBuffer = chunkedParserState._numberBuffer;
@@ -402,13 +405,21 @@
    * so the parser can go back to the correct value when the literal ends.
    */
   void saveState(int state) {
-    states.add(state);
+    pushWasmArray<WasmI64>(
+      states,
+      statesLength,
+      state.toWasmI64(),
+      (statesLength * 2) | 3,
+    );
   }
 
   /**
    * Restore a state pushed with [saveState].
    */
-  int restoreState() => states.removeLast(); // Throws if empty.
+  int restoreState() {
+    assert(statesLength > 0);
+    return popWasmArray<WasmI64>(states, statesLength).toInt();
+  }
 
   /**
    * Read out the result after successfully closing the parser.
diff --git a/sdk/lib/_internal/wasm/lib/internal_patch.dart b/sdk/lib/_internal/wasm/lib/internal_patch.dart
index 4186b79..859dc6b 100644
--- a/sdk/lib/_internal/wasm/lib/internal_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/internal_patch.dart
@@ -226,4 +226,8 @@
 );
 
 /// Similar to `pushWasmArray`, but for popping.
-external T? popWasmArray<T>(WasmArray<T?> array, int length);
+///
+/// Note that when [T] is not nullable, this does not clear the popped element
+/// slot in the array, which may cause memory leaks. Callers should manually
+/// clear non-nullable reference element slots in the array when popping.
+external T popWasmArray<T>(WasmArray<T> array, int length);