[dart2wasm] Use WasmI32 (instead of double) as representation of integers across interop boundary

Currently code uses `double` to represent array indices, array lengths
etc. This seems wrong because `double`s can loose precision. This is
also a high overhead.

Understandably one could use `double` and only start to loose precision
with more than 32 bits, but realistically string lenghts, array lengths
cannot exceed 31-bits (i.e. 2 GBs) in practice.

=> Use Wasmi32 to represent array indices, array lengths, bytes loaded
from integer arrays, ...

Change-Id: Ife540622c85118d890a8e487677db56e2ce06f5a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/372424
Reviewed-by: Ömer Ağacan <omersa@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/sdk/lib/_internal/wasm/lib/js_helper.dart b/sdk/lib/_internal/wasm/lib/js_helper.dart
index d2c21b1..67b06a8 100644
--- a/sdk/lib/_internal/wasm/lib/js_helper.dart
+++ b/sdk/lib/_internal/wasm/lib/js_helper.dart
@@ -142,8 +142,6 @@
 }
 
 // Convert to double to avoid converting to [BigInt] in the case of int64.
-WasmExternRef intToJSNumber(int i) => toJSNumber(i.toDouble())!;
-
 WasmExternRef? getConstructorString(String constructor) =>
     getPropertyRaw(globalThisRaw(), constructor.toExternRef);
 
@@ -231,15 +229,17 @@
 
 WasmExternRef? toJSBoolean(bool b) => JS<WasmExternRef?>("b => !!b", b);
 
-double objectLength(WasmExternRef? o) => JS<double>("o => o.length", o);
+int objectLength(WasmExternRef? o) =>
+    JS<WasmI32>("o => o.length", o).toIntSigned();
 
-double byteLength(WasmExternRef? o) => JS<double>("o => o.byteLength", o);
+int byteLength(WasmExternRef? o) =>
+    JS<WasmI32>("o => o.byteLength", o).toIntSigned();
 
-double dataViewGetUint8(WasmExternRef? o, double i) =>
-    JS<double>("(o, i) => o.getUint8(i)", o, i);
+int dataViewGetUint8(WasmExternRef? o, int i) =>
+    JS<WasmI32>("(o, i) => o.getUint8(i)", o, i.toWasmI32()).toIntSigned();
 
-WasmExternRef? objectReadIndex(WasmExternRef? o, double index) =>
-    JS<WasmExternRef?>("(o, i) => o[i]", o, index);
+WasmExternRef? objectReadIndex(WasmExternRef? o, int index) =>
+    JS<WasmExternRef?>("(o, i) => o[i]", o, index.toWasmI32());
 
 Function unwrapJSWrappedDartFunction(WasmExternRef? f) =>
     JS<Function>("f => f.dartFunction", f);
@@ -272,14 +272,14 @@
 WasmExternRef? jsFloat64ArrayFromDartFloat64List(Float64List l) =>
     JS<WasmExternRef?>('l => arrayFromDartList(Float64Array, l)', l);
 
-WasmExternRef? jsDataViewFromDartByteData(ByteData data, double length) =>
+WasmExternRef? jsDataViewFromDartByteData(ByteData data, int length) =>
     JS<WasmExternRef?>("""(data, length) => {
           const view = new DataView(new ArrayBuffer(length));
           for (let i = 0; i < length; i++) {
               view.setUint8(i, dartInstance.exports.\$byteDataGetUint8(data, i));
           }
           return view;
-        }""", data, length);
+        }""", data, length.toWasmI32());
 
 WasmExternRef? jsArrayFromDartList(List<Object?> l) =>
     JS<WasmExternRef?>('l => arrayFromDartList(Array, l)', l);
@@ -295,7 +295,7 @@
 WasmExternRef? newArrayRaw() => JS<WasmExternRef?>('() => []');
 
 WasmExternRef? newArrayFromLengthRaw(int length) =>
-    JS<WasmExternRef?>('l => new Array(l)', length.toDouble());
+    JS<WasmExternRef?>('l => new Array(l)', length.toWasmI32());
 
 WasmExternRef? globalThisRaw() => JS<WasmExternRef?>('() => globalThis');
 
@@ -345,7 +345,7 @@
 WasmExternRef? jsArrayBufferFromDartByteBuffer(ByteBuffer buffer) {
   ByteData byteData = ByteData.view(buffer);
   WasmExternRef? dataView =
-      jsDataViewFromDartByteData(byteData, byteData.lengthInBytes.toDouble());
+      jsDataViewFromDartByteData(byteData, byteData.lengthInBytes);
   return getPropertyRaw(dataView, 'buffer'.toExternRef);
 }
 
@@ -407,7 +407,7 @@
   } else if (object is js_types.JSDataViewImpl) {
     return object.toExternRef;
   } else if (object is ByteData) {
-    return jsDataViewFromDartByteData(object, object.lengthInBytes.toDouble());
+    return jsDataViewFromDartByteData(object, object.lengthInBytes);
   } else if (object is List<Object?>) {
     return jsArrayFromDartList(object);
   } else if (object is num) {
@@ -506,27 +506,27 @@
   int length = byteLength(ref).toInt();
   ByteData data = ByteData(length);
   for (int i = 0; i < length; i++) {
-    data.setUint8(i, dataViewGetUint8(ref, i.toDouble()).toInt());
+    data.setUint8(i, dataViewGetUint8(ref, i));
   }
   return data;
 }
 
 List<double> jsFloatTypedArrayToDartFloatTypedData(
     WasmExternRef? ref, List<double> makeTypedData(int size)) {
-  int length = objectLength(ref).toInt();
+  int length = objectLength(ref);
   List<double> list = makeTypedData(length);
   for (int i = 0; i < length; i++) {
-    list[i] = toDartNumber(objectReadIndex(ref, i.toDouble()));
+    list[i] = toDartNumber(objectReadIndex(ref, i));
   }
   return list;
 }
 
 List<int> jsIntTypedArrayToDartIntTypedData(
     WasmExternRef? ref, List<int> makeTypedData(int size)) {
-  int length = objectLength(ref).toInt();
+  int length = objectLength(ref);
   List<int> list = makeTypedData(length);
   for (int i = 0; i < length; i++) {
-    list[i] = toDartNumber(objectReadIndex(ref, i.toDouble())).toInt();
+    list[i] = toDartNumber(objectReadIndex(ref, i)).toInt();
   }
   return list;
 }
@@ -541,12 +541,10 @@
 }
 
 List<JSAny?> toDartListJSAny(WasmExternRef? ref) => List<JSAny?>.generate(
-    objectLength(ref).round(),
-    (int n) => JSValue(objectReadIndex(ref, n.toDouble())) as JSAny?);
+    objectLength(ref), (int n) => JSValue(objectReadIndex(ref, n)) as JSAny?);
 
 List<Object?> toDartList(WasmExternRef? ref) => List<Object?>.generate(
-    objectLength(ref).round(),
-    (int n) => dartifyRaw(objectReadIndex(ref, n.toDouble())));
+    objectLength(ref), (int n) => dartifyRaw(objectReadIndex(ref, n)));
 
 // These two trivial helpers are needed to work around an issue with tearing off
 // functions that take / return [WasmExternRef].
@@ -596,12 +594,12 @@
 
 /// Methods used by the wasm runtime.
 @pragma("wasm:export", "\$listLength")
-double _listLength(List list) => list.length.toDouble();
+WasmI32 _listLength(List list) => list.length.toWasmI32();
 
 @pragma("wasm:export", "\$listRead")
-WasmExternRef? _listRead(List<Object?> list, double index) =>
-    jsifyRaw(list[index.toInt()]);
+WasmExternRef? _listRead(List<Object?> list, WasmI32 index) =>
+    jsifyRaw(list[index.toIntSigned()]);
 
 @pragma("wasm:export", "\$byteDataGetUint8")
-double _byteDataGetUint8(ByteData byteData, double index) =>
-    byteData.getUint8(index.toInt()).toDouble();
+WasmI32 _byteDataGetUint8(ByteData byteData, WasmI32 index) =>
+    byteData.getUint8(index.toIntSigned()).toWasmI32();
diff --git a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart
index 14db7dd..a8b32b3 100644
--- a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart
@@ -215,7 +215,7 @@
     final t = this;
     return JSDataView._(JSValue(t is js_types.JSDataViewImpl
         ? t.toExternRef
-        : jsDataViewFromDartByteData(t, lengthInBytes.toDouble())));
+        : jsDataViewFromDartByteData(t, lengthInBytes)));
   }
 }
 
diff --git a/sdk/lib/_internal/wasm/lib/js_util_patch.dart b/sdk/lib/_internal/wasm/lib/js_util_patch.dart
index 11ea89e..f8d54dc 100644
--- a/sdk/lib/_internal/wasm/lib/js_util_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/js_util_patch.dart
@@ -256,7 +256,7 @@
       convertedObjects[o] = dartList;
       final length = getProperty<double>(o, 'length').toInt();
       for (int i = 0; i < length; i++) {
-        dartList.add(convert(JSValue.box(objectReadIndex(ref, i.toDouble()))));
+        dartList.add(convert(JSValue.box(objectReadIndex(ref, i))));
       }
       return dartList;
     } else {
diff --git a/sdk/lib/_internal/wasm/lib/string.dart b/sdk/lib/_internal/wasm/lib/string.dart
index ec6b650..2618720 100644
--- a/sdk/lib/_internal/wasm/lib/string.dart
+++ b/sdk/lib/_internal/wasm/lib/string.dart
@@ -1630,31 +1630,31 @@
 // String accessors used to perform Dart<->JS string conversion
 
 @pragma("wasm:export", "\$stringLength")
-double _stringLength(String string) {
-  return string.length.toDouble();
+WasmI32 _stringLength(String string) {
+  return string.length.toWasmI32();
 }
 
 @pragma("wasm:export", "\$stringRead")
-double _stringRead(String string, double index) {
-  return string.codeUnitAt(index.toInt()).toDouble();
+WasmI32 _stringRead(String string, WasmI32 index) {
+  return string.codeUnitAt(index.toIntSigned()).toWasmI32();
 }
 
 @pragma("wasm:export", "\$stringAllocate1")
-OneByteString _stringAllocate1(double length) {
-  return OneByteString.withLength(length.toInt());
+OneByteString _stringAllocate1(WasmI32 length) {
+  return OneByteString.withLength(length.toIntSigned());
 }
 
 @pragma("wasm:export", "\$stringWrite1")
-void _stringWrite1(OneByteString string, double index, double codePoint) {
-  writeIntoOneByteString(string, index.toInt(), codePoint.toInt());
+void _stringWrite1(OneByteString string, WasmI32 index, WasmI32 codePoint) {
+  writeIntoOneByteString(string, index.toIntSigned(), codePoint.toIntSigned());
 }
 
 @pragma("wasm:export", "\$stringAllocate2")
-TwoByteString _stringAllocate2(double length) {
-  return TwoByteString.withLength(length.toInt());
+TwoByteString _stringAllocate2(WasmI32 length) {
+  return TwoByteString.withLength(length.toIntSigned());
 }
 
 @pragma("wasm:export", "\$stringWrite2")
-void _stringWrite2(TwoByteString string, double index, double codePoint) {
-  writeIntoTwoByteString(string, index.toInt(), codePoint.toInt());
+void _stringWrite2(TwoByteString string, WasmI32 index, WasmI32 codePoint) {
+  writeIntoTwoByteString(string, index.toIntSigned(), codePoint.toIntSigned());
 }