WASM Bindings optimizations and fixes (#41808)
authorKatelyn Gadd <kg@luminance.org>
Wed, 11 Nov 2020 04:46:06 +0000 (20:46 -0800)
committerGitHub <noreply@github.com>
Wed, 11 Nov 2020 04:46:06 +0000 (22:46 -0600)
* Fix MarshalTypedArrayByte and re-enable it. Re-enable TestFunctionApply

* Re-enable MarshalTypedArray

* Detect when the managed wrapper for a JS object has been collected and create a new one

* Remove debugging code

* Maintain a small pool of temporary root instances to minimize GC pressure. Improve benchmark

* Don't use release_roots in call_method due to varargs overhead

* Various call_method optimizations

* Checkpoint: Don't rely on finally block for teardown in call path, because it has a performance cost

* Checkpoint: Unboxing fast path for primitives

* Checkpoint: Fix unboxing fast path

* Update bindings to use bound static methods instead of call_method in various places

* Address PR feedback

* Revert sample and add separate proj for benchmark

* Fix benchmark

* Revert test change

* Fix passing mono object ptrs to bound functions
Fix passing strings across the boundary
Fix JS strings being truncated at the first null when passed to mono

* Implement unboxing for chars
Slightly optimize the unboxing slow path

* Don't allocate a root buffer for arguments if nothing needs to be rooted. Reuse scratch native buffer across calls unless re-entrant to avoid per-invoke malloc

* Fix whitespace damage from merge

* Tweaks to try and prevent boxing/gc

* Fix typo

* Add some tests

* Fix test failures

* Add more error handling and diagnostic messages
Fix a couple broken tests

* Repair merge damage

* Remove bindings benchmark

* Use TypedArray.fill 3-argument version to zero memory

* Checkpoint: Introduce format strings

* Fix interpolated strings

* Test refactoring

* Checkpoint: Add more test coverage for bindings and interop

* Checkpoint: Enum marshaling works

* Improve test coverage

* Checkpoint: Unify unboxing of primitive types

* Checkpoint: Unify unboxing of primitive types

* Checkpoint: Restore fn to satisfy runtime-test.js

* Checkpoint: Unify boxing for primitives

* Remove now-unused box methods

* Don't store names for null method IDs

* Fix indentation damage

* Add test

* Satisfy CI

* Accept weaker promises

Co-authored-by: Larry Ewing <lewing@microsoft.com>
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/TypedArrayTests.cs
src/mono/wasm/runtime-test.js
src/mono/wasm/runtime/binding_support.js
src/mono/wasm/runtime/driver.c
src/mono/wasm/runtime/library_mono.js

index 93d24cc..813f070 100644 (file)
@@ -71,7 +71,16 @@ namespace System.Runtime.InteropServices.JavaScript
             WeakReference? reference;
             lock (_boundObjects)
             {
-                if (!_boundObjects.TryGetValue(jsId, out reference))
+                if (_boundObjects.TryGetValue(jsId, out reference))
+                {
+                    if ((reference.Target == null) || ((reference.Target as JSObject)?.IsDisposed == true))
+                    {
+                        _boundObjects.Remove(jsId);
+                        reference = null;
+                    }
+                }
+
+                if (reference == null)
                 {
                     IntPtr jsIntPtr = (IntPtr)jsId;
                     reference = new WeakReference(mappedType > 0 ? BindJSType(jsIntPtr, ownsHandle, mappedType) : new JSObject(jsIntPtr, ownsHandle), true);
@@ -229,21 +238,6 @@ namespace System.Runtime.InteropServices.JavaScript
                 js.GetWrappedObject() ?? h.Target : h.Target;
         }
 
-        private static object BoxInt(int i)
-        {
-            return i;
-        }
-
-        private static object BoxDouble(double d)
-        {
-            return d;
-        }
-
-        private static object BoxBool(int b)
-        {
-            return b == 0 ? false : true;
-        }
-
         private static bool IsSimpleArray(object a)
         {
             return a is System.Array arr && arr.Rank == 1 && arr.GetLowerBound(0) == 0;
index 3c359f2..5957cb4 100644 (file)
@@ -88,6 +88,11 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
             return _marshalledObject;
         }
 
+        private static object InvokeReturnMarshalObj()
+        {
+            return _marshalledObject;
+        }
+
         internal static int _valOne, _valTwo;
         private static void ManipulateObject(JSObject obj)
         {
@@ -316,6 +321,8 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         internal static int _sumValue = 0;
         private static void CallFunctionSum()
         {
+            if (_sumFunction == null)
+                throw new Exception("_sumFunction is null");
             _sumValue = (int)_sumFunction.Call(null, 3, 5);
         }
 
@@ -323,6 +330,8 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         private static void CreateFunctionApply()
         {
             var math = (JSObject)Runtime.GetGlobalObject("Math");
+            if (math == null)
+                throw new Exception("Runtime.GetGlobalObject(Math) returned null");
             _mathMinFunction = (Function)math.GetObjectProperty("min");
 
         }
@@ -330,6 +339,8 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         internal static int _minValue = 0;
         private static void CallFunctionApply()
         {
+            if (_mathMinFunction == null)
+                throw new Exception("_mathMinFunction is null");
             _minValue = (int)_mathMinFunction.Apply(null, new object[] { 5, 6, 2, 3, 7 });
         }
 
@@ -345,5 +356,32 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
             _blobURI = blobUri;
         }
 
+        internal static uint _uintValue;
+        private static void InvokeUInt(uint value)
+        {
+            _uintValue = value;
+        }
+
+        internal static TestEnum _enumValue;
+        private static void SetEnumValue(TestEnum value)
+        {
+            _enumValue = value;
+        }
+        private static TestEnum GetEnumValue()
+        {
+            return _enumValue;
+        }
+
+        private static UInt64 GetUInt64()
+        {
+            return UInt64.MaxValue;
+        }
+    }
+
+    public enum TestEnum : uint {
+        FirstValue = 1,
+        Zero = 0,
+        Five = 5,
+        BigValue = 0xFFFFFFFEu
     }
 }
index 6fd1a21..23d57c2 100644 (file)
@@ -92,14 +92,13 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         }
 
         [Fact]
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/40112")]
         public static void FunctionMath()
         {
             JSObject math = (JSObject)Runtime.GetGlobalObject("Math");
-            Assert.NotNull(math);
+            Assert.True(math != null, "math != null");
 
             Function mathMax = (Function)math.GetObjectProperty("max");
-            Assert.NotNull(mathMax);
+            Assert.True(mathMax != null, "math.max != null");
 
             var maxValue = (int)mathMax.Apply(null, new object[] { 5, 6, 2, 3, 7 });
             Assert.Equal(7, maxValue);
@@ -108,7 +107,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
             Assert.Equal(7, maxValue);
 
             Function mathMin = (Function)((JSObject)Runtime.GetGlobalObject("Math")).GetObjectProperty("min");
-            Assert.NotNull(mathMin);
+            Assert.True(mathMin != null, "math.min != null");
 
             var minValue = (int)mathMin.Apply(null, new object[] { 5, 6, 2, 3, 7 });
             Assert.Equal(2, minValue);
index b07ecbe..1c14472 100644 (file)
@@ -126,6 +126,73 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
             Assert.Same(HelperMarshal._object1, HelperMarshal._object2);
         }
 
+        [Theory]
+        [InlineData(byte.MinValue)]
+        [InlineData(byte.MaxValue)]
+        [InlineData(SByte.MinValue)]
+        [InlineData(SByte.MaxValue)]
+        [InlineData(uint.MaxValue)]
+        [InlineData(uint.MinValue)]
+        [InlineData(int.MaxValue)]
+        [InlineData(int.MinValue)]
+        [InlineData(double.MaxValue)]
+        [InlineData(double.MinValue)]
+        public static void InvokeUnboxNumberString(object o)
+        {
+            HelperMarshal._marshalledObject = o;
+            HelperMarshal._object1 = HelperMarshal._object2 = null;
+            var value = Runtime.InvokeJS(@"
+                var obj = App.call_test_method (""InvokeReturnMarshalObj"");
+                var res = App.call_test_method (""InvokeObj1"", [ obj.toString() ]);
+            ");
+
+            Assert.Equal(o.ToString().ToLower(), HelperMarshal._object1);
+        }
+
+        [Theory]
+        [InlineData(byte.MinValue, 0)]
+        [InlineData(byte.MaxValue, 255)]
+        [InlineData(SByte.MinValue, -128)]
+        [InlineData(SByte.MaxValue, 127)]
+        [InlineData(uint.MaxValue)]
+        [InlineData(uint.MinValue, 0)]
+        [InlineData(int.MaxValue)]
+        [InlineData(int.MinValue)]
+        [InlineData(double.MaxValue)]
+        [InlineData(double.MinValue)]
+        public static void InvokeUnboxNumber(object o, object expected = null)
+        {
+            HelperMarshal._marshalledObject = o;
+            HelperMarshal._object1 = HelperMarshal._object2 = null;
+            Runtime.InvokeJS(@"
+                var obj = App.call_test_method (""InvokeReturnMarshalObj"");
+                var res = App.call_test_method (""InvokeObj1"", [ obj ]);
+            ");
+
+            Assert.Equal(expected ?? o, HelperMarshal._object1);
+        }
+
+        [Theory]
+        [InlineData(byte.MinValue, 0)]
+        [InlineData(byte.MaxValue, 255)]
+        [InlineData(SByte.MinValue, -128)]
+        [InlineData(SByte.MaxValue, 127)]
+        [InlineData(uint.MaxValue)]
+        [InlineData(uint.MinValue, 0)]
+        [InlineData(int.MaxValue)]
+        [InlineData(int.MinValue)]
+        [InlineData(double.MaxValue)]
+        [InlineData(double.MinValue)]
+        public static void InvokeUnboxStringNumber(object o, object expected = null)
+        {
+            HelperMarshal._marshalledObject = HelperMarshal._object1 = HelperMarshal._object2 = null;
+            Runtime.InvokeJS(String.Format (@"
+                var res = App.call_test_method (""InvokeObj1"", [ {0} ]);
+            ", o));
+
+            Assert.Equal (expected ?? o, HelperMarshal._object1);
+        }
+
         [Fact]
         public static void JSInvokeInt()
         {
@@ -200,6 +267,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
             Assert.Equal(30, HelperMarshal._functionResultValue);
             Assert.Equal(60, HelperMarshal._i32Value);
         }
+
         [Fact]
         public static void BindStaticMethod()
         {
@@ -322,7 +390,6 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         }
 
         [Fact]
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/40112")]
         public static void MarshalTypedArray()
         {
             Runtime.InvokeJS(@"
@@ -440,29 +507,28 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
             Assert.Equal(7.5f, HelperMarshal._doubleBuffer[3]);
         }
 
-        [Fact]
-        public static void MarshalTypedArraySByte()
-        {
+        private static void RunMarshalTypedArrayJS(string type) {
             Runtime.InvokeJS(@"
                 var obj = { };
-                App.call_test_method (""SetTypedArraySByte"", [ obj ]);
-                App.call_test_method (""GetTypedArraySByte"", [ obj ]);
+                App.call_test_method (""SetTypedArray" + type + @""", [ obj ]);
+                App.call_test_method (""GetTypedArray" + type + @""", [ obj ]);
             ");
+        }
+
+        [Fact]
+        public static void MarshalTypedArraySByte()
+        {
+            RunMarshalTypedArrayJS("SByte");
             Assert.Equal(11, HelperMarshal._taSByte.Length);
             Assert.Equal(32, HelperMarshal._taSByte[0]);
             Assert.Equal(32, HelperMarshal._taSByte[HelperMarshal._taSByte.Length - 1]);
         }
 
         [Fact]
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/40112")]
         public static void MarshalTypedArrayByte()
         {
-            Runtime.InvokeJS(@"
-                var obj = { };
-                App.call_test_method (""SetTypedArrayByte"", [ obj ]);
-                App.call_test_method (""GetTypedArrayByte"", [ obj ]);
-            ");
-            Assert.Equal(11, HelperMarshal._taSByte.Length);
+            RunMarshalTypedArrayJS("Byte");
+            Assert.Equal(17, HelperMarshal._taByte.Length);
             Assert.Equal(104, HelperMarshal._taByte[0]);
             Assert.Equal(115, HelperMarshal._taByte[HelperMarshal._taByte.Length - 1]);
             Assert.Equal("hic sunt dracones", System.Text.Encoding.Default.GetString(HelperMarshal._taByte));
@@ -471,11 +537,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         [Fact]
         public static void MarshalTypedArrayShort()
         {
-            Runtime.InvokeJS(@"
-                var obj = { };
-                App.call_test_method (""SetTypedArrayShort"", [ obj ]);
-                App.call_test_method (""GetTypedArrayShort"", [ obj ]);
-            ");
+            RunMarshalTypedArrayJS("Short");
             Assert.Equal(13, HelperMarshal._taShort.Length);
             Assert.Equal(32, HelperMarshal._taShort[0]);
             Assert.Equal(32, HelperMarshal._taShort[HelperMarshal._taShort.Length - 1]);
@@ -484,11 +546,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         [Fact]
         public static void MarshalTypedArrayUShort()
         {
-            Runtime.InvokeJS(@"
-                var obj = { };
-                App.call_test_method (""SetTypedArrayUShort"", [ obj ]);
-                App.call_test_method (""GetTypedArrayUShort"", [ obj ]);
-            ");
+            RunMarshalTypedArrayJS("UShort");
             Assert.Equal(14, HelperMarshal._taUShort.Length);
             Assert.Equal(32, HelperMarshal._taUShort[0]);
             Assert.Equal(32, HelperMarshal._taUShort[HelperMarshal._taUShort.Length - 1]);
@@ -497,11 +555,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         [Fact]
         public static void MarshalTypedArrayInt()
         {
-            Runtime.InvokeJS(@"
-                var obj = { };
-                App.call_test_method (""SetTypedArrayInt"", ""o"", [ obj ]);
-                App.call_test_method (""GetTypedArrayInt"", ""o"", [ obj ]);
-            ");
+            RunMarshalTypedArrayJS("Int");
             Assert.Equal(15, HelperMarshal._taInt.Length);
             Assert.Equal(32, HelperMarshal._taInt[0]);
             Assert.Equal(32, HelperMarshal._taInt[HelperMarshal._taInt.Length - 1]);
@@ -510,11 +564,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         [Fact]
         public static void MarshalTypedArrayUInt()
         {
-            Runtime.InvokeJS(@"
-                var obj = { };
-                App.call_test_method (""SetTypedArrayUInt"", [ obj ]);
-                App.call_test_method (""GetTypedArrayUInt"", [ obj ]);
-            ");
+            RunMarshalTypedArrayJS("UInt");
             Assert.Equal(16, HelperMarshal._taUInt.Length);
             Assert.Equal(32, (int)HelperMarshal._taUInt[0]);
             Assert.Equal(32, (int)HelperMarshal._taUInt[HelperMarshal._taUInt.Length - 1]);
@@ -523,11 +573,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         [Fact]
         public static void MarshalTypedArrayFloat()
         {
-            Runtime.InvokeJS(@"
-                var obj = { };
-                App.call_test_method (""SetTypedArrayFloat"", [ obj ]);
-                App.call_test_method (""GetTypedArrayFloat"", [ obj ]);
-            ");
+            RunMarshalTypedArrayJS("Float");
             Assert.Equal(17, HelperMarshal._taFloat.Length);
             Assert.Equal(3.14f, HelperMarshal._taFloat[0]);
             Assert.Equal(3.14f, HelperMarshal._taFloat[HelperMarshal._taFloat.Length - 1]);
@@ -536,11 +582,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         [Fact]
         public static void MarshalTypedArrayDouble()
         {
-            Runtime.InvokeJS(@"
-                var obj = { };
-                App.call_test_method (""SetTypedArrayDouble"", ""o"", [ obj ]);
-                App.call_test_method (""GetTypedArrayDouble"", ""o"", [ obj ]);
-            ");
+            RunMarshalTypedArrayJS("Double");
             Assert.Equal(18, HelperMarshal._taDouble.Length);
             Assert.Equal(3.14d, HelperMarshal._taDouble[0]);
             Assert.Equal(3.14d, HelperMarshal._taDouble[HelperMarshal._taDouble.Length - 1]);
@@ -551,22 +593,160 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         {
             HelperMarshal._sumValue = 0;
             Runtime.InvokeJS(@"
-                App.call_test_method (""CreateFunctionSum"", null, [ ]);
-                App.call_test_method (""CallFunctionSum"", null, [  ]);
+                App.call_test_method (""CreateFunctionSum"", []);
+                App.call_test_method (""CallFunctionSum"", []);
             ");
             Assert.Equal(8, HelperMarshal._sumValue);
         }
 
         [Fact]
-        [ActiveIssue("https://github.com/dotnet/runtime/issues/40112")]
         public static void TestFunctionApply()
         {
             HelperMarshal._minValue = 0;
             Runtime.InvokeJS(@"
-                App.call_test_method (""CreateFunctionApply"", null, [ ]);
-                App.call_test_method (""CallFunctionApply"", null, [  ]);
+                App.call_test_method (""CreateFunctionApply"", []);
+                App.call_test_method (""CallFunctionApply"", []);
             ");
             Assert.Equal(2, HelperMarshal._minValue);
         }
+        
+        [Fact]
+        public static void BoundStaticMethodMissingArgs()
+        {
+            // TODO: We currently have code that relies on this behavior (missing args default to 0) but
+            //  it would be better if it threw an exception about the missing arguments. This test is here
+            //  to ensure we do not break things by accidentally changing this behavior -kg
+
+            HelperMarshal._intValue = 1;
+            Runtime.InvokeJS(@$"
+                var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+                invoke_int ();
+            ");
+            Assert.Equal(0, HelperMarshal._intValue);
+        }
+        
+        [Fact]
+        public static void BoundStaticMethodExtraArgs()
+        {
+            HelperMarshal._intValue = 0;
+            Runtime.InvokeJS(@$"
+                var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+                invoke_int (200, 400);
+            ");
+            Assert.Equal(200, HelperMarshal._intValue);
+        }
+        
+        [Fact]
+        public static void BoundStaticMethodArgumentTypeCoercion()
+        {
+            // TODO: As above, the type coercion behavior on display in this test is not ideal, but
+            //  changing it risks breakage in existing code so for now it is verified by a test -kg
+
+            HelperMarshal._intValue = 0;
+            Runtime.InvokeJS(@$"
+                var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+                invoke_int (""200"");
+            ");
+            Assert.Equal(200, HelperMarshal._intValue);
+
+            Runtime.InvokeJS(@$"
+                var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+                invoke_int (400.5);
+            ");
+            Assert.Equal(400, HelperMarshal._intValue);
+        }
+        
+        [Fact]
+        public static void BoundStaticMethodUnpleasantArgumentTypeCoercion()
+        {
+            HelperMarshal._intValue = 100;
+            Runtime.InvokeJS(@$"
+                var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+                invoke_int (""hello"");
+            ");
+            Assert.Equal(0, HelperMarshal._intValue);
+
+            // In this case at the very least, the leading "7" is not turned into the number 7
+            Runtime.InvokeJS(@$"
+                var invoke_int = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeInt"");
+                invoke_int (""7apples"");
+            ");
+            Assert.Equal(0, HelperMarshal._intValue);
+        }
+
+        [Fact]
+        public static void PassUintArgument()
+        {
+            HelperMarshal._uintValue = 0;
+            Runtime.InvokeJS(@$"
+                var invoke_uint = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeUInt"");
+                invoke_uint (0xFFFFFFFE);
+            ");
+
+            Assert.Equal(0xFFFFFFFEu, HelperMarshal._uintValue);
+        }
+        
+        [Fact]
+        public static void ReturnUintEnum ()
+        {
+            HelperMarshal._uintValue = 0;
+            HelperMarshal._enumValue = TestEnum.BigValue;
+            Runtime.InvokeJS(@$"
+                var get_value = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}GetEnumValue"");
+                var e = get_value ();
+                var invoke_uint = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}InvokeUInt"");
+                invoke_uint (e);
+            ");
+            Assert.Equal((uint)TestEnum.BigValue, HelperMarshal._uintValue);
+        }
+        
+        [Fact]
+        public static void PassUintEnumByValue ()
+        {
+            HelperMarshal._enumValue = TestEnum.Zero;
+            Runtime.InvokeJS(@$"
+                var set_enum = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}SetEnumValue"", ""j"");
+                set_enum (0xFFFFFFFE);
+            ");
+            Assert.Equal(TestEnum.BigValue, HelperMarshal._enumValue);
+        }
+        
+        [Fact]
+        public static void PassUintEnumByValueMasqueradingAsInt ()
+        {
+            HelperMarshal._enumValue = TestEnum.Zero;
+            // HACK: We're explicitly telling the bindings layer to pass an int here, not an enum
+            // Because we know the enum is : uint, this is compatible, so it works.
+            Runtime.InvokeJS(@$"
+                var set_enum = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}SetEnumValue"", ""i"");
+                set_enum (0xFFFFFFFE);
+            ");
+            Assert.Equal(TestEnum.BigValue, HelperMarshal._enumValue);
+        }
+        
+        [Fact]
+        public static void PassUintEnumByNameIsNotImplemented ()
+        {
+            HelperMarshal._enumValue = TestEnum.Zero;
+            var exc = Assert.Throws<JSException>( () => 
+                Runtime.InvokeJS(@$"
+                    var set_enum = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}SetEnumValue"", ""j"");
+                    set_enum (""BigValue"");
+                ")
+            );
+            Assert.StartsWith("Error: Expected numeric value for enum argument, got 'BigValue'", exc.Message);
+        }
+        
+        [Fact]
+        public static void CannotUnboxUint64 ()
+        {
+            var exc = Assert.Throws<JSException>( () => 
+                Runtime.InvokeJS(@$"
+                    var get_u64 = Module.mono_bind_static_method (""{HelperMarshal.INTEROP_CLASS}GetUInt64"", """");
+                    var u64 = get_u64();
+                ")
+            );
+            Assert.StartsWith("Error: int64 not available", exc.Message);
+        }
     }
 }
index 14b31fa..8968357 100644 (file)
@@ -23,7 +23,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         {
             var clamped = new byte[50];
             Uint8ClampedArray from = Uint8ClampedArray.From(clamped);
-            Assert.True(from.Length == 50);
+            Assert.Equal(50, from.Length);
             Assert.Equal("[object Uint8ClampedArray]", objectPrototype.Call(from));
         }
 
@@ -33,7 +33,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         {
             var array = new byte[50];
             Uint8Array from = Uint8Array.From(array);
-            Assert.True(from.Length == 50);
+            Assert.Equal(50, from.Length);
             Assert.Equal("[object Uint8Array]", objectPrototype.Call(from));
         }
 
@@ -43,7 +43,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         {
             var array = new ushort[50];
             Uint16Array from = Uint16Array.From(array);
-            Assert.True(from.Length == 50);
+            Assert.Equal(50, from.Length);
             Assert.Equal("[object Uint16Array]", objectPrototype.Call(from));
         }
 
@@ -53,7 +53,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         {
             var array = new uint[50];
             Uint32Array from = Uint32Array.From(array);
-            Assert.True(from.Length == 50);
+            Assert.Equal(50, from.Length);
             Assert.Equal("[object Uint32Array]", objectPrototype.Call(from));
         }
 
@@ -63,7 +63,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         {
             var array = new sbyte[50];
             Int8Array from = Int8Array.From(array);
-            Assert.True(from.Length == 50);
+            Assert.Equal(50, from.Length);
             Assert.Equal("[object Int8Array]", objectPrototype.Call(from));
         }
 
@@ -73,7 +73,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         {
             var array = new short[50];
             Int16Array from = Int16Array.From(array);
-            Assert.True(from.Length == 50);
+            Assert.Equal(50, from.Length);
             Assert.Equal("[object Int16Array]", objectPrototype.Call(from));
         }
 
@@ -83,7 +83,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         {
             var array = new int[50];
             Int32Array from = Int32Array.From(array);
-            Assert.True(from.Length == 50);
+            Assert.Equal(50, from.Length);
             Assert.Equal("[object Int32Array]", objectPrototype.Call(from));
         }
 
@@ -93,7 +93,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         {
             var array = new float[50];
             Float32Array from = Float32Array.From(array);
-            Assert.True(from.Length == 50);
+            Assert.Equal(50, from.Length);
             Assert.Equal("[object Float32Array]", objectPrototype.Call(from));
         }
 
@@ -103,7 +103,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         {
             var array = new double[50];
             Float64Array from = Float64Array.From(array);
-            Assert.True(from.Length == 50);
+            Assert.Equal(50, from.Length);
             Assert.Equal("[object Float64Array]", objectPrototype.Call(from));
         }
 
@@ -112,7 +112,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         public static void Uint8ClampedArrayFromSharedArrayBuffer(Function objectPrototype)
         {
             Uint8ClampedArray from = new Uint8ClampedArray(new SharedArrayBuffer(50));
-            Assert.True(from.Length == 50);
+            Assert.Equal(50, from.Length);
             Assert.Equal("[object Uint8ClampedArray]", objectPrototype.Call(from));
         }
 
@@ -121,7 +121,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         public static void Uint8ArrayFromSharedArrayBuffer(Function objectPrototype)
         {
             Uint8Array from = new Uint8Array(new SharedArrayBuffer(50));
-            Assert.True(from.Length == 50);
+            Assert.Equal(50, from.Length);
             Assert.Equal("[object Uint8Array]", objectPrototype.Call(from));
         }
 
@@ -130,7 +130,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         public static void Uint16ArrayFromSharedArrayBuffer(Function objectPrototype)
         {
             Uint16Array from = new Uint16Array(new SharedArrayBuffer(50));
-            Assert.True(from.Length == 25);
+            Assert.Equal(25, from.Length);
             Assert.Equal("[object Uint16Array]", objectPrototype.Call(from));
         }
 
@@ -139,7 +139,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         public static void Uint32ArrayFromSharedArrayBuffer(Function objectPrototype)
         {
             Uint32Array from = new Uint32Array(new SharedArrayBuffer(40));
-            Assert.True(from.Length == 10);
+            Assert.Equal(10, from.Length);
             Assert.Equal("[object Uint32Array]", objectPrototype.Call(from));
         }
 
index 51d494e..14a59c4 100644 (file)
@@ -392,6 +392,15 @@ var App = {
                }
        },
        call_test_method: function (method_name, args) {
-               return BINDING.call_static_method("[System.Private.Runtime.InteropServices.JavaScript.Tests]System.Runtime.InteropServices.JavaScript.Tests.HelperMarshal:" + method_name, args);
+               if (arguments.length > 2)
+                       throw new Error("Invalid number of arguments for call_test_method");
+
+               var fqn = "[System.Private.Runtime.InteropServices.JavaScript.Tests]System.Runtime.InteropServices.JavaScript.Tests.HelperMarshal:" + method_name;
+               try {
+                       return BINDING.call_static_method(fqn, args || []);
+               } catch (exc) {
+                       console.error("exception thrown in", fqn);
+                       throw exc;
+               }
        }
 };
index af6202f..ac215b1 100644 (file)
@@ -10,13 +10,14 @@ var BindingSupportLib = {
                mono_wasm_free_list: [],
                mono_wasm_owned_objects_frames: [],
                mono_wasm_owned_objects_LMF: [],
-               mono_wasm_marshal_enum_as_int: false,
+               mono_wasm_marshal_enum_as_int: true,
                mono_bindings_init: function (binding_asm) {
                        this.BINDING_ASM = binding_asm;
                },
 
                export_functions: function (module) {
                        module ["mono_bindings_init"] = BINDING.mono_bindings_init.bind(BINDING);
+                       module ["mono_bind_method"] = BINDING.bind_method.bind(BINDING);
                        module ["mono_method_invoke"] = BINDING.call_method.bind(BINDING);
                        module ["mono_method_get_call_signature"] = BINDING.mono_method_get_call_signature.bind(BINDING);
                        module ["mono_method_resolve"] = BINDING.resolve_method_fqn.bind(BINDING);
@@ -29,6 +30,9 @@ var BindingSupportLib = {
                bindings_lazy_init: function () {
                        if (this.init)
                                return;
+
+                       // avoid infinite recursion
+                       this.init = true;
                
                        Array.prototype[Symbol.for("wasm type")] = 1;
                        ArrayBuffer.prototype[Symbol.for("wasm type")] = 2;
@@ -48,21 +52,28 @@ var BindingSupportLib = {
                        Float64Array.prototype[Symbol.for("wasm type")] = 18;
 
                        this.assembly_load = Module.cwrap ('mono_wasm_assembly_load', 'number', ['string']);
+                       this.find_corlib_class = Module.cwrap ('mono_wasm_find_corlib_class', 'number', ['string', 'string']);
                        this.find_class = Module.cwrap ('mono_wasm_assembly_find_class', 'number', ['number', 'string', 'string']);
-                       this.find_method = Module.cwrap ('mono_wasm_assembly_find_method', 'number', ['number', 'string', 'number']);
+                       this._find_method = Module.cwrap ('mono_wasm_assembly_find_method', 'number', ['number', 'string', 'number']);
                        this.invoke_method = Module.cwrap ('mono_wasm_invoke_method', 'number', ['number', 'number', 'number', 'number']);
                        this.mono_string_get_utf8 = Module.cwrap ('mono_wasm_string_get_utf8', 'number', ['number']);
                        this.mono_wasm_string_from_utf16 = Module.cwrap ('mono_wasm_string_from_utf16', 'number', ['number', 'number']);
                        this.mono_get_obj_type = Module.cwrap ('mono_wasm_get_obj_type', 'number', ['number']);
-                       this.mono_unbox_int = Module.cwrap ('mono_unbox_int', 'number', ['number']);
-                       this.mono_unbox_float = Module.cwrap ('mono_wasm_unbox_float', 'number', ['number']);
                        this.mono_array_length = Module.cwrap ('mono_wasm_array_length', 'number', ['number']);
                        this.mono_array_get = Module.cwrap ('mono_wasm_array_get', 'number', ['number', 'number']);
                        this.mono_obj_array_new = Module.cwrap ('mono_wasm_obj_array_new', 'number', ['number']);
                        this.mono_obj_array_set = Module.cwrap ('mono_wasm_obj_array_set', 'void', ['number', 'number', 'number']);
                        this.mono_wasm_register_bundled_satellite_assemblies = Module.cwrap ('mono_wasm_register_bundled_satellite_assemblies', 'void', [ ]);
-                       this.mono_unbox_enum = Module.cwrap ('mono_wasm_unbox_enum', 'number', ['number']);
+                       this.mono_wasm_try_unbox_primitive_and_get_type = Module.cwrap ('mono_wasm_try_unbox_primitive_and_get_type', 'number', ['number', 'number']);
+                       this.mono_wasm_box_primitive = Module.cwrap ('mono_wasm_box_primitive', 'number', ['number', 'number', 'number']);
                        this.assembly_get_entry_point = Module.cwrap ('mono_wasm_assembly_get_entry_point', 'number', ['number']);
+                       
+                       this._box_buffer = Module._malloc(16);
+                       this._unbox_buffer = Module._malloc(16);
+                       this._class_int32 = this.find_corlib_class ("System", "Int32");
+                       this._class_uint32 = this.find_corlib_class ("System", "UInt32");
+                       this._class_double = this.find_corlib_class ("System", "Double");
+                       this._class_boolean = this.find_corlib_class ("System", "Boolean");
 
                        // receives a byteoffset into allocated Heap with a size.
                        this.mono_typed_array_new = Module.cwrap ('mono_wasm_typed_array_new', 'number', ['number','number','number','number']);
@@ -74,10 +85,11 @@ var BindingSupportLib = {
                        if (!this.binding_module)
                                throw "Can't find bindings module assembly: " + binding_fqn_asm;
 
+                       var namespace = null, classname = null;
                        if (binding_fqn_class !== null && typeof binding_fqn_class !== "undefined")
                        {
-                               var namespace = "System.Runtime.InteropServices.JavaScript";
-                               var classname = binding_fqn_class.length > 0 ? binding_fqn_class : "Runtime";
+                               namespace = "System.Runtime.InteropServices.JavaScript";
+                               classname = binding_fqn_class.length > 0 ? binding_fqn_class : "Runtime";
                                if (binding_fqn_class.indexOf(".") != -1) {
                                        var idx = binding_fqn_class.lastIndexOf(".");
                                        namespace = binding_fqn_class.substring (0, idx);
@@ -85,27 +97,34 @@ var BindingSupportLib = {
                                }
                        }
 
-                       var wasm_runtime_class = this.find_class (this.binding_module, namespace, classname)
+                       var wasm_runtime_class = this.find_class (this.binding_module, namespace, classname);
                        if (!wasm_runtime_class)
                                throw "Can't find " + binding_fqn_class + " class";
 
                        var get_method = function(method_name) {
-                               var res = BINDING.find_method (wasm_runtime_class, method_name, -1)
+                               var res = BINDING.find_method (wasm_runtime_class, method_name, -1);
                                if (!res)
                                        throw "Can't find method " + namespace + "." + classname + ":" + method_name;
                                return res;
-                       }
-                       this.bind_js_obj = get_method ("BindJSObject");
-                       this.bind_core_clr_obj = get_method ("BindCoreCLRObject");
-                       this.bind_existing_obj = get_method ("BindExistingObject");
-                       this.unbind_raw_obj_and_free = get_method ("UnBindRawJSObjectAndFree");                 
-                       this.get_js_id = get_method ("GetJSObjectId");
-                       this.get_raw_mono_obj = get_method ("GetDotNetObject");
-
-                       this.box_js_int = get_method ("BoxInt");
-                       this.box_js_double = get_method ("BoxDouble");
-                       this.box_js_bool = get_method ("BoxBool");
-                       this.is_simple_array = get_method ("IsSimpleArray");
+                       };
+
+                       var bind_runtime_method = function (method_name, signature) {
+                               var method = get_method (method_name);
+                               return BINDING.bind_method (method, 0, signature, "BINDINGS_" + method_name);
+                       };
+
+                       // NOTE: The bound methods have a _ prefix on their names to ensure
+                       //  that any code relying on the old get_method/call_method pattern will
+                       //  break in a more understandable way.
+
+                       this._bind_js_obj = bind_runtime_method ("BindJSObject", "iii");
+                       this._bind_core_clr_obj = bind_runtime_method ("BindCoreCLRObject", "ii");
+                       this._bind_existing_obj = bind_runtime_method ("BindExistingObject", "mi");
+                       this._unbind_raw_obj_and_free = bind_runtime_method ("UnBindRawJSObjectAndFree", "ii");
+                       this._get_js_id = bind_runtime_method ("GetJSObjectId", "m");
+                       this._get_raw_mono_obj = bind_runtime_method ("GetDotNetObject", "i!");
+
+                       this._is_simple_array = bind_runtime_method ("IsSimpleArray", "m");
                        this.setup_js_cont = get_method ("SetupJSContinuation");
 
                        this.create_tcs = get_method ("CreateTaskSource");
@@ -114,7 +133,7 @@ var BindingSupportLib = {
                        this.tcs_get_task_and_bind = get_method ("GetTaskAndBind");
                        this.get_call_sig = get_method ("GetCallSignature");
 
-                       this.object_to_string = get_method ("ObjectToString");
+                       this._object_to_string = bind_runtime_method ("ObjectToString", "m");
                        this.get_date_value = get_method ("GetDateValue");
                        this.create_date_time = get_method ("CreateDateTime");
                        this.create_uri = get_method ("CreateUri");
@@ -124,7 +143,28 @@ var BindingSupportLib = {
                        this.safehandle_get_handle = get_method ("SafeHandleGetHandle");
                        this.safehandle_release_by_handle = get_method ("SafeHandleReleaseByHandle");
 
-                       this.init = true;
+                       this._are_promises_supported = ((typeof Promise === "object") || (typeof Promise === "function")) && (typeof Promise.resolve === "function");
+               },
+
+               js_string_to_mono_string: function (string) {
+                       var buffer = Module._malloc ((string.length + 1) * 2);
+                       var buffer16 = (buffer / 2) | 0;
+                       for (var i = 0; i < string.length; i++)
+                               Module.HEAP16[buffer16 + i] = string.charCodeAt (i);
+                       Module.HEAP16[buffer16 + string.length] = 0;
+                       var result = this.mono_wasm_string_from_utf16 (buffer, string.length);
+                       Module._free (buffer);
+                       return result;
+               },
+
+               find_method: function (klass, name, n) {
+                       var result = this._find_method(klass, name, n);
+                       if (result) {
+                               if (!this._method_descriptions)
+                                       this._method_descriptions = new Map();
+                               this._method_descriptions.set(result, name);
+                       }
+                       return result;
                },
 
                get_js_obj: function (js_handle) {
@@ -138,7 +178,7 @@ var BindingSupportLib = {
                },
 
                is_nested_array: function (ele) {
-                       return this.call_method (this.is_simple_array, null, "mi", [ ele ]);
+                       return this._is_simple_array(ele);
                },
 
                js_string_to_mono_string: function (string) {
@@ -174,19 +214,19 @@ var BindingSupportLib = {
                        let elemRoot = MONO.mono_wasm_new_root (); 
 
                        try {
-                               var res = [];
                                var len = this.mono_array_length (arrayRoot.value);
+                               var res = new Array (len);
                                for (var i = 0; i < len; ++i)
                                {
                                        elemRoot.value = this.mono_array_get (arrayRoot.value, i);
                                        
                                        if (this.is_nested_array (elemRoot.value))
-                                               res.push (this._mono_array_to_js_array_rooted (elemRoot));
+                                               res[i] = this._mono_array_to_js_array_rooted (elemRoot);
                                        else
-                                               res.push (this._unbox_mono_obj_rooted (elemRoot));
+                                               res[i] = this._unbox_mono_obj_rooted (elemRoot);
                                }
                        } finally {
-                               elemRoot.release();
+                               elemRoot.release ();
                        }
 
                        return res;
@@ -220,105 +260,113 @@ var BindingSupportLib = {
                        }
                },
 
-               _unbox_mono_obj_rooted: function (root) {
-                       var mono_obj = root.value;
-                       if (mono_obj === 0)
-                               return undefined;
-                       
-                       var type = this.mono_get_obj_type (mono_obj);
-                       //See MARSHAL_TYPE_ defines in driver.c
-                       switch (type) {
-                       case 1: // int
-                               return this.mono_unbox_int (mono_obj);
-                       case 2: // float
-                               return this.mono_unbox_float (mono_obj);
-                       case 3: //string
-                               return this.conv_string (mono_obj);
-                       case 4: //vts
-                               throw new Error ("no idea on how to unbox value types");
-                       case 5: { // delegate
-                               var obj = this.extract_js_obj (mono_obj);
-                               obj.__mono_delegate_alive__ = true;
-                               // FIXME: Should we root the object as long as this function has not been GCd?
-                               return function () {
-                                       return BINDING.invoke_delegate (obj, arguments);
-                               };
-                       }
-                       case 6: {// Task
-
-                               if (typeof Promise === "undefined" || typeof Promise.resolve === "undefined")
-                                       throw new Error ("Promises are not supported thus C# Tasks can not work in this context.");
-
-                               var obj = this.extract_js_obj (mono_obj);
-                               var cont_obj = null;
-                               var promise = new Promise (function (resolve, reject) {
-                                       cont_obj = {
-                                               resolve: resolve,
-                                               reject: reject
-                                       };
-                               });
-
-                               this.call_method (this.setup_js_cont, null, "mo", [ mono_obj, cont_obj ]);
-                               obj.__mono_js_cont__ = cont_obj.__mono_gchandle__;
-                               cont_obj.__mono_js_task__ = obj.__mono_gchandle__;
-                               return promise;
-                       }
-
-                       case 7: // ref type
-                               return this.extract_js_obj (mono_obj);
+               _unbox_delegate_rooted: function (mono_obj) {
+                       var obj = this.extract_js_obj (mono_obj);
+                       obj.__mono_delegate_alive__ = true;
+                       // FIXME: Should we root the object as long as this function has not been GCd?
+                       return function () {
+                               // TODO: Just use Function.bind
+                               return BINDING.invoke_delegate (obj, arguments);
+                       };
+               },
 
-                       case 8: // bool
-                               return this.mono_unbox_int (mono_obj) != 0;
+               _unbox_task_rooted: function (mono_obj) {
+                       if (!this._are_promises_supported)
+                               throw new Error ("Promises are not supported thus 'System.Threading.Tasks.Task' can not work in this context.");
 
-                       case 9: // enum
+                       var obj = this.extract_js_obj (mono_obj);
+                       var cont_obj = null;
+                       var promise = new Promise (function (resolve, reject) {
+                               cont_obj = {
+                                       resolve: resolve,
+                                       reject: reject
+                               };
+                       });
 
-                               if(this.mono_wasm_marshal_enum_as_int)
-                               {
-                                       return this.mono_unbox_enum (mono_obj);
-                               }
-                               else
-                               {
-                                       enumValue = this.call_method(this.object_to_string, null, "m", [ mono_obj ]);
-                               }
+                       this.call_method (this.setup_js_cont, null, "mo", [ mono_obj, cont_obj ]);
+                       obj.__mono_js_cont__ = cont_obj.__mono_gchandle__;
+                       cont_obj.__mono_js_task__ = obj.__mono_gchandle__;
+                       return promise;
+               },
 
-                               return enumValue;
-
-                       case 10: // arrays
-                       case 11: 
-                       case 12: 
-                       case 13: 
-                       case 14: 
-                       case 15: 
-                       case 16: 
-                       case 17: 
-                       case 18:
+               _unbox_safehandle_rooted: function (mono_obj) {
+                       var addRef = true;
+                       var js_handle = this.call_method(this.safehandle_get_handle, null, "mi", [ mono_obj, addRef ]);
+                       var requiredObject = BINDING.mono_wasm_require_handle (js_handle);
+                       if (addRef)
                        {
-                               throw new Error ("Marshalling of primitive arrays are not supported.  Use the corresponding TypedArray instead.");
+                               if (typeof this.mono_wasm_owned_objects_LMF === "undefined")
+                                       this.mono_wasm_owned_objects_LMF = [];
+
+                               this.mono_wasm_owned_objects_LMF.push(js_handle);
                        }
-                       case 20: // clr .NET DateTime
-                               var dateValue = this.call_method(this.get_date_value, null, "md", [ mono_obj ]);
-                               return new Date(dateValue);
-                       case 21: // clr .NET DateTimeOffset
-                               var dateoffsetValue = this.call_method(this.object_to_string, null, "m", [ mono_obj ]);
-                               return dateoffsetValue;
-                       case 22: // clr .NET Uri
-                               var uriValue = this.call_method(this.object_to_string, null, "m", [ mono_obj ]);
-                               return uriValue;
-                       case 23: // clr .NET SafeHandle
-                               var addRef = true;
-                               var js_handle = this.call_method(this.safehandle_get_handle, null, "mii", [ mono_obj, addRef ]);
-                               // FIXME: Is this a GC object that needs to be rooted?
-                               var requiredObject = BINDING.mono_wasm_require_handle (js_handle);
-                               if (addRef)
-                               {
-                                       if (typeof this.mono_wasm_owned_objects_LMF === "undefined")
-                                               this.mono_wasm_owned_objects_LMF = [];
+                       return requiredObject;
+               },
 
-                                       this.mono_wasm_owned_objects_LMF.push(js_handle);
-                               }
-                               return requiredObject;
-                       default:
-                               throw new Error ("no idea on how to unbox object kind " + type + " at offset " + mono_obj);
+               _unbox_mono_obj_rooted_with_known_nonprimitive_type: function (mono_obj, type) {
+                       //See MARSHAL_TYPE_ defines in driver.c
+                       switch (type) {
+                               case 26: // int64
+                               case 27: // uint64
+                                       // TODO: Fix this once emscripten offers HEAPI64/HEAPU64 or can return them
+                                       throw new Error ("int64 not available");
+                               case 3: //string
+                                       return this.conv_string (mono_obj);
+                               case 4: //vts
+                                       throw new Error ("no idea on how to unbox value types");
+                               case 5: // delegate
+                                       return this._unbox_delegate_rooted (mono_obj);
+                               case 6: // Task
+                                       return this._unbox_task_rooted (mono_obj);
+                               case 7: // ref type
+                                       return this.extract_js_obj (mono_obj);
+                               case 10: // arrays
+                               case 11: 
+                               case 12: 
+                               case 13: 
+                               case 14: 
+                               case 15: 
+                               case 16: 
+                               case 17: 
+                               case 18:
+                                       throw new Error ("Marshalling of primitive arrays are not supported.  Use the corresponding TypedArray instead.");      
+                               case 20: // clr .NET DateTime
+                                       var dateValue = this.call_method(this.get_date_value, null, "md", [ mono_obj ]);
+                                       return new Date(dateValue);
+                               case 21: // clr .NET DateTimeOffset
+                                       var dateoffsetValue = this._object_to_string (mono_obj);
+                                       return dateoffsetValue;
+                               case 22: // clr .NET Uri
+                                       var uriValue = this._object_to_string (mono_obj);
+                                       return uriValue;
+                               case 23: // clr .NET SafeHandle
+                                       return this._unbox_safehandle_rooted (mono_obj);
+                               default:
+                                       throw new Error ("no idea on how to unbox object kind " + type + " at offset " + mono_obj);
+                       }
+               },
+
+               _unbox_mono_obj_rooted: function (root) {
+                       var mono_obj = root.value;
+                       if (mono_obj === 0)
+                               return undefined;
+                       
+                       var type = this.mono_wasm_try_unbox_primitive_and_get_type (mono_obj, this._unbox_buffer);
+                       switch (type) {
+                               case 1: // int
+                                       return Module.HEAP32[this._unbox_buffer / 4];
+                               case 25: // uint32
+                                       return Module.HEAPU32[this._unbox_buffer / 4];
+                               case 24: // float32
+                                       return Module.HEAPF32[this._unbox_buffer / 4];
+                               case 2: // float64
+                                       return Module.HEAPF64[this._unbox_buffer / 8];
+                               case 8: // boolean
+                                       return (Module.HEAP32[this._unbox_buffer / 4]) !== 0;
+                               case 28: // char
+                                       return String.fromCharCode(Module.HEAP32[this._unbox_buffer / 4]);
+                               default:
+                                       return this._unbox_mono_obj_rooted_with_known_nonprimitive_type (mono_obj, type);
                        }
                },
 
@@ -348,6 +396,27 @@ var BindingSupportLib = {
                        heapBytes.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, numBytes));
                        return heapBytes;
                },
+
+               _box_js_int: function (js_obj) {
+                       Module.HEAP32[this._box_buffer / 4] = js_obj;
+                       return this.mono_wasm_box_primitive (this._class_int32, this._box_buffer, 4);
+               },
+
+               _box_js_uint: function (js_obj) {
+                       Module.HEAPU32[this._box_buffer / 4] = js_obj;
+                       return this.mono_wasm_box_primitive (this._class_uint32, this._box_buffer, 4);
+               },
+
+               _box_js_double: function (js_obj) {
+                       Module.HEAPF64[this._box_buffer / 8] = js_obj;
+                       return this.mono_wasm_box_primitive (this._class_double, this._box_buffer, 8);
+               },
+
+               _box_js_bool: function (js_obj) {
+                       Module.HEAP32[this._box_buffer / 4] = js_obj ? 1 : 0;
+                       return this.mono_wasm_box_primitive (this._class_boolean, this._box_buffer, 4);
+               },
+
                js_to_mono_obj: function (js_obj) {
                        this.bindings_lazy_init ();
 
@@ -368,19 +437,28 @@ var BindingSupportLib = {
                                case js_obj === null:
                                case typeof js_obj === "undefined":
                                        return 0;
-                               case typeof js_obj === "number":
-                                       if (parseInt(js_obj) == js_obj)
-                                               return this.call_method (this.box_js_int, null, "im", [ js_obj ]);
-                                       return this.call_method (this.box_js_double, null, "dm", [ js_obj ]);
-                               case typeof js_obj === "string":
+                               case typeof js_obj === "number": {
+                                       if ((js_obj | 0) === js_obj)
+                                               result = this._box_js_int (js_obj);
+                                       else if ((js_obj >>> 0) === js_obj)
+                                               result = this._box_js_uint (js_obj);
+                                       else
+                                               result = this._box_js_double (js_obj);
+                                       
+                                       if (!result)
+                                               throw new Error (`Boxing failed for ${js_obj}`);
+
+                                       return result;
+                               } case typeof js_obj === "string":
                                        return this.js_string_to_mono_string (js_obj);
                                case typeof js_obj === "boolean":
-                                       return this.call_method (this.box_js_bool, null, "im", [ js_obj ]);
+                                       return this._box_js_bool (js_obj);
                                case isThenable() === true:
                                        var the_task = this.try_extract_mono_obj (js_obj);
                                        if (the_task)
                                                return the_task;
-                                       // FIXME: We need to root tcs for an appropriate timespan
+                                       // FIXME: We need to root tcs for an appropriate timespan, at least until the Task
+                                       //  is resolved
                                        var tcs = this.create_task_completion_source ();
                                        js_obj.then (function (result) {
                                                BINDING.set_task_result (tcs, result);
@@ -390,7 +468,7 @@ var BindingSupportLib = {
                                        return this.get_task_and_bind (tcs, js_obj);
                                case js_obj.constructor.name === "Date":
                                        // We may need to take into account the TimeZone Offset
-                                       return this.call_method(this.create_date_time, null, "dm", [ js_obj.getTime() ]);
+                                       return this.call_method(this.create_date_time, null, "d!", [ js_obj.getTime() ]);
                                default:
                                        return this.extract_mono_obj (js_obj);
                        }
@@ -403,7 +481,7 @@ var BindingSupportLib = {
                                case typeof js_obj === "undefined":
                                        return 0;
                                case typeof js_obj === "string":
-                                       return this.call_method(this.create_uri, null, "sm", [ js_obj ])
+                                       return this.call_method(this.create_uri, null, "s!", [ js_obj ])
                                default:
                                        return this.extract_mono_obj (js_obj);
                        }
@@ -558,45 +636,36 @@ var BindingSupportLib = {
                        this.typedarray_copy_from(newTypedArray, pinned_array, begin, end, bytes_per_element);
                        return newTypedArray;
                },
-               js_to_mono_enum: function (method, parmIdx, js_obj) {
+               js_to_mono_enum: function (js_obj, method, parmIdx) {
                        this.bindings_lazy_init ();
     
-                       if (js_obj === null || typeof js_obj === "undefined")
-                               return 0;
+                       if (typeof (js_obj) !== "number")
+                               throw new Error (`Expected numeric value for enum argument, got '${js_obj}'`);
                        
-                       var monoObj, monoEnum;
-                       try {
-                               monoObj = MONO.mono_wasm_new_root (this.js_to_mono_obj (js_obj));
-                               // Check enum contract
-                               monoEnum = MONO.mono_wasm_new_root (this.call_method (this.object_to_enum, null, "iimm", [ method, parmIdx, monoObj.value ]))
-                               // return the unboxed enum value.
-                               return this.mono_unbox_enum (monoEnum.value);
-                       } finally {
-                               MONO.mono_wasm_release_roots (monoObj, monoEnum);
-                       }
+                       return js_obj | 0;
                },
                wasm_binding_obj_new: function (js_obj_id, ownsHandle, type)
                {
-                       return this.call_method (this.bind_js_obj, null, "iii", [js_obj_id, ownsHandle, type]);
+                       return this._bind_js_obj (js_obj_id, ownsHandle, type);
                },
                wasm_bind_existing: function (mono_obj, js_id)
                {
-                       return this.call_method (this.bind_existing_obj, null, "mi", [mono_obj, js_id]);
+                       return this._bind_existing_obj (mono_obj, js_id);
                },
 
                wasm_bind_core_clr_obj: function (js_id, gc_handle)
                {
-                       return this.call_method (this.bind_core_clr_obj, null, "ii", [js_id, gc_handle]);
+                       return this._bind_core_clr_obj (js_id, gc_handle);
                },
 
                wasm_get_js_id: function (mono_obj)
                {
-                       return this.call_method (this.get_js_id, null, "m", [mono_obj]);
+                       return this._get_js_id (mono_obj);
                },
 
                wasm_get_raw_obj: function (gchandle)
                {
-                       return this.call_method (this.get_raw_mono_obj, null, "im", [gchandle]);
+                       return this._get_raw_mono_obj (gchandle);
                },
 
                try_extract_mono_obj:function (js_obj) {
@@ -626,26 +695,37 @@ var BindingSupportLib = {
                free_task_completion_source: function (tcs) {
                        if (tcs.is_mono_tcs_result_set)
                        {
-                               this.call_method (this.unbind_raw_obj_and_free, null, "ii", [ tcs.__mono_gchandle__ ]);
+                               this._unbind_raw_obj_and_free (tcs.__mono_gchandle__);
                        }
                        if (tcs.__mono_bound_task__)
                        {
-                               this.call_method (this.unbind_raw_obj_and_free, null, "ii", [ tcs.__mono_bound_task__ ]);
+                               this._unbind_raw_obj_and_free (tcs.__mono_bound_task__);
                        }
                },
 
                extract_mono_obj: function (js_obj) {
-
                        if (js_obj === null || typeof js_obj === "undefined")
                                return 0;
 
-                       if (!js_obj.is_mono_bridged_obj) {
-                               var gc_handle = this.mono_wasm_register_obj(js_obj);
-                               return this.wasm_get_raw_obj (gc_handle);
+                       var result = null;
+                       var gc_handle = js_obj.__mono_gchandle__;
+                       if (gc_handle) {
+                               result = this.wasm_get_raw_obj (gc_handle);
+
+                               // It's possible the managed object corresponding to this JS object was collected,
+                               //  in which case we need to make a new one.
+                               if (!result) {
+                                       delete js_obj.__mono_gchandle__;
+                                       delete js_obj.is_mono_bridged_obj;
+                               }
                        }
 
+                       if (!result) {
+                               gc_handle = this.mono_wasm_register_obj(js_obj);
+                               result = this.wasm_get_raw_obj (gc_handle);
+                       }
 
-                       return this.wasm_get_raw_obj (js_obj.__mono_gchandle__);
+                       return result;
                },
 
                extract_js_obj: function (mono_obj) {
@@ -666,6 +746,379 @@ var BindingSupportLib = {
                        return js_obj;
                },
 
+               _create_named_function: function (name, argumentNames, body, closure) {
+                       var result = null, keys = null, closureArgumentList = null, closureArgumentNames = null;
+
+                       if (closure) {
+                               closureArgumentNames = Object.keys (closure);
+                               closureArgumentList = new Array (closureArgumentNames.length);
+                               for (var i = 0, l = closureArgumentNames.length; i < l; i++)
+                                       closureArgumentList[i] = closure[closureArgumentNames[i]];
+                       }
+
+                       var constructor = this._create_rebindable_named_function (name, argumentNames, body, closureArgumentNames);
+                       result = constructor.apply (null, closureArgumentList);
+
+                       return result;
+               },
+
+               _create_rebindable_named_function: function (name, argumentNames, body, closureArgNames) {
+                       var strictPrefix = "\"use strict\";\r\n";
+                       var uriPrefix = "", escapedFunctionIdentifier = "";
+
+                       if (name) {
+                               uriPrefix = "//# sourceURL=https://mono-wasm.invalid/" + name + "\r\n";
+                               escapedFunctionIdentifier = name;
+                       } else {
+                               escapedFunctionIdentifier = "unnamed";
+                       }
+
+                       var rawFunctionText = "function " + escapedFunctionIdentifier + "(" +
+                               argumentNames.join(", ") +
+                               ") {\r\n" +
+                               body +
+                               "\r\n};\r\n";
+
+                       var lineBreakRE = /\r(\n?)/g;
+
+                       rawFunctionText = 
+                               uriPrefix + strictPrefix + 
+                               rawFunctionText.replace(lineBreakRE, "\r\n    ") + 
+                               `    return ${escapedFunctionIdentifier};\r\n`;
+
+                       var result = null, keys = null;
+
+                       if (closureArgNames) {
+                               keys = closureArgNames.concat ([rawFunctionText]);
+                       } else {
+                               keys = [rawFunctionText];
+                       }
+
+                       result = Function.apply (Function, keys);
+                       return result;
+               },
+
+               _create_primitive_converters: function () {
+                       var result = new Map ();
+                       result.set ('m', { steps: [{ }], size: 0});
+                       result.set ('s', { steps: [{ convert: this.js_string_to_mono_string.bind (this) }], size: 0, needs_root: true });
+                       result.set ('o', { steps: [{ convert: this.js_to_mono_obj.bind (this) }], size: 0, needs_root: true });
+                       result.set ('u', { steps: [{ convert: this.js_to_mono_uri.bind (this) }], size: 0, needs_root: true });
+
+                       // result.set ('k', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i64'}], size: 8});
+                       result.set ('j', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i32'}], size: 8});
+
+                       result.set ('i', { steps: [{ indirect: 'i32'}], size: 8});
+                       result.set ('l', { steps: [{ indirect: 'i64'}], size: 8});
+                       result.set ('f', { steps: [{ indirect: 'float'}], size: 8});
+                       result.set ('d', { steps: [{ indirect: 'double'}], size: 8});
+
+                       this._primitive_converters = result;
+                       return result;
+               },
+
+               _create_converter_for_marshal_string: function (args_marshal) {
+                       var primitiveConverters = this._primitive_converters;
+                       if (!primitiveConverters)
+                               primitiveConverters = this._create_primitive_converters ();
+
+                       var steps = [];
+                       var size = 0;
+                       var is_result_definitely_unmarshaled = false,
+                               is_result_possibly_unmarshaled = false,
+                               result_unmarshaled_if_argc = -1,
+                               needs_root_buffer = false;
+
+                       for (var i = 0; i < args_marshal.length; ++i) {
+                               var key = args_marshal[i];
+
+                               if (i === args_marshal.length - 1) {
+                                       if (key === "!") {
+                                               is_result_definitely_unmarshaled = true;
+                                               continue;
+                                       } else if (key === "m") {
+                                               is_result_possibly_unmarshaled = true;
+                                               result_unmarshaled_if_argc = args_marshal.length - 1;
+                                       }
+                               } else if (key === "!")
+                                       throw new Error ("! must be at the end of the signature");
+
+                               var conv = primitiveConverters.get (key);
+                               if (!conv)
+                                       throw new Error ("Unknown parameter type " + type);
+
+                               var localStep = Object.create (conv.steps[0]);
+                               localStep.size = conv.size;
+                               if (conv.needs_root)
+                                       needs_root_buffer = true;
+                               localStep.needs_root = conv.needs_root;
+                               localStep.key = args_marshal[i];
+                               steps.push (localStep);
+                               size += conv.size;
+                       }
+
+                       return { 
+                               steps: steps, size: size, args_marshal: args_marshal, 
+                               is_result_definitely_unmarshaled: is_result_definitely_unmarshaled,
+                               is_result_possibly_unmarshaled: is_result_possibly_unmarshaled,
+                               result_unmarshaled_if_argc: result_unmarshaled_if_argc,
+                               needs_root_buffer: needs_root_buffer
+                       };
+               },
+
+               _get_converter_for_marshal_string: function (args_marshal) {
+                       if (!this._signature_converters)
+                               this._signature_converters = new Map();
+
+                       var converter = this._signature_converters.get (args_marshal);
+                       if (!converter) {
+                               converter = this._create_converter_for_marshal_string (args_marshal);
+                               this._signature_converters.set (args_marshal, converter);
+                       }
+
+                       return converter;
+               },
+
+               _compile_converter_for_marshal_string: function (args_marshal) {
+                       var converter = this._get_converter_for_marshal_string (args_marshal);
+                       if (typeof (converter.args_marshal) !== "string")
+                               throw new Error ("Corrupt converter for '" + args_marshal + "'");
+
+                       if (converter.compiled_function && converter.compiled_variadic_function)
+                               return converter;
+
+                       var converterName = args_marshal.replace("!", "_result_unmarshaled");
+                       converter.name = converterName;
+                       
+                       var body = [];
+                       var argumentNames = ["buffer", "rootBuffer", "method"];
+
+                       // worst-case allocation size instead of allocating dynamically, plus padding
+                       var bufferSizeBytes = converter.size + (args_marshal.length * 4) + 16;
+                       var rootBufferSize = args_marshal.length;
+                       // ensure the indirect values are 8-byte aligned so that aligned loads and stores will work
+                       var indirectBaseOffset = ((((args_marshal.length * 4) + 7) / 8) | 0) * 8;
+
+                       var closure = {};
+                       var indirectLocalOffset = 0;
+
+                       body.push (
+                               `if (!buffer) buffer = Module._malloc (${bufferSizeBytes});`,
+                               `var indirectStart = buffer + ${indirectBaseOffset};`,
+                               "var indirect32 = (indirectStart / 4) | 0, indirect64 = (indirectStart / 8) | 0;",
+                               "var buffer32 = (buffer / 4) | 0;",
+                               ""
+                       );
+
+                       for (let i = 0; i < converter.steps.length; i++) {
+                               var step = converter.steps[i];
+                               var closureKey = "step" + i;
+                               var valueKey = "value" + i;
+
+                               var argKey = "arg" + i;
+                               argumentNames.push (argKey);
+
+                               if (step.convert) {
+                                       closure[closureKey] = step.convert;
+                                       body.push (`var ${valueKey} = ${closureKey}(${argKey}, method, ${i});`);
+                               } else {
+                                       body.push (`var ${valueKey} = ${argKey};`);
+                               }
+
+                               if (step.needs_root)
+                                       body.push (`rootBuffer.set (${i}, ${valueKey});`);
+
+                               if (step.indirect) {
+                                       var heapArrayName = null;
+
+                                       switch (step.indirect) {
+                                               case "u32":
+                                                       heapArrayName = "HEAPU32";
+                                                       break;
+                                               case "i32":
+                                                       heapArrayName = "HEAP32";
+                                                       break;
+                                               case "float":
+                                                       heapArrayName = "HEAPF32";
+                                                       break;
+                                               case "double":
+                                                       body.push (`Module.HEAPF64[indirect64 + ${(indirectLocalOffset / 8)}] = ${valueKey};`);
+                                                       break;
+                                               case "i64":
+                                                       body.push (`Module.setValue (indirectStart + ${indirectLocalOffset}, ${valueKey}, 'i64');`);
+                                                       break;
+                                               default:
+                                                       throw new Error ("Unimplemented indirect type: " + step.indirect);
+                                       }
+
+                                       if (heapArrayName)
+                                               body.push (`Module.${heapArrayName}[indirect32 + ${(indirectLocalOffset / 4)}] = ${valueKey};`);
+
+                                       body.push (`Module.HEAP32[buffer32 + ${i}] = indirectStart + ${indirectLocalOffset};`, "");
+                                       indirectLocalOffset += step.size;
+                               } else {
+                                       body.push (`Module.HEAP32[buffer32 + ${i}] = ${valueKey};`, "");
+                                       indirectLocalOffset += 4;
+                               }
+                       }
+
+                       body.push ("return buffer;");
+
+                       var bodyJs = body.join ("\r\n"), compiledFunction = null, compiledVariadicFunction = null;
+                       try {
+                               compiledFunction = this._create_named_function("converter_" + converterName, argumentNames, bodyJs, closure);
+                               converter.compiled_function = compiledFunction;
+                       } catch (exc) {
+                               converter.compiled_function = null;
+                               console.warn("compiling converter failed for", bodyJs, "with error", exc);
+                               throw exc;
+                       }
+
+                       argumentNames = ["existingBuffer", "rootBuffer", "method", "args"];
+                       closure = {
+                               converter: compiledFunction
+                       };
+                       body = [
+                               "return converter(",
+                               "  existingBuffer, rootBuffer, method,"
+                       ];
+
+                       for (let i = 0; i < converter.steps.length; i++) {
+                               body.push(
+                                       "  args[" + i + 
+                                       (
+                                               (i == converter.steps.length - 1) 
+                                                       ? "]" 
+                                                       : "], "
+                                       )
+                               );
+                       }
+
+                       body.push(");");
+
+                       bodyJs = body.join ("\r\n");
+                       try {
+                               compiledVariadicFunction = this._create_named_function("variadic_converter_" + converterName, argumentNames, bodyJs, closure);
+                               converter.compiled_variadic_function = compiledVariadicFunction;
+                       } catch (exc) {
+                               converter.compiled_variadic_function = null;
+                               console.warn("compiling converter failed for", bodyJs, "with error", exc);
+                               throw exc;
+                       }
+
+                       converter.scratchRootBuffer = null;
+                       converter.scratchBuffer = 0 | 0;
+
+                       return converter;
+               },
+
+               _verify_args_for_method_call: function (args_marshal, args) {
+                       var has_args = args && (typeof args === "object") && args.length > 0;
+                       var has_args_marshal = typeof args_marshal === "string";
+
+                       if (has_args) {
+                               if (!has_args_marshal)
+                                       throw new Error ("No signature provided for method call.");
+                               else if (args.length > args_marshal.length)
+                                       throw new Error ("Too many parameter values. Expected at most " + args_marshal.length + " value(s) for signature " + args_marshal);
+                       }
+
+                       return has_args_marshal && has_args;
+               },
+
+               _get_buffer_for_method_call: function (converter) {
+                       if (!converter)
+                               return 0;
+                       
+                       var result = converter.scratchBuffer;
+                       converter.scratchBuffer = 0;
+                       return result;
+               },
+
+               _get_args_root_buffer_for_method_call: function (converter) {
+                       if (!converter)
+                               return null;
+
+                       if (!converter.needs_root_buffer)
+                               return null;
+
+                       var result;
+                       if (converter.scratchRootBuffer) {
+                               result = converter.scratchRootBuffer;
+                               converter.scratchRootBuffer = null;
+                       } else {
+                               // TODO: Expand the converter's heap allocation and then use
+                               //  mono_wasm_new_root_buffer_from_pointer instead. Not that important
+                               //  at present because the scratch buffer will be reused unless we are
+                               //  recursing through a re-entrant call
+                               result = MONO.mono_wasm_new_root_buffer (converter.steps.length);
+                               result.converter = converter;
+                       }
+                       return result;
+               },
+
+               _release_args_root_buffer_from_method_call: function (converter, argsRootBuffer) {
+                       if (!argsRootBuffer || !converter)
+                               return;
+
+                       // Store the arguments root buffer for re-use in later calls
+                       if (!converter.scratchRootBuffer) {
+                               argsRootBuffer.clear ();
+                               converter.scratchRootBuffer = argsRootBuffer;
+                       } else {
+                               argsRootBuffer.release ();
+                       }
+               },
+
+               _release_buffer_from_method_call: function (converter, buffer) {
+                       if (!converter || !buffer)
+                               return;
+
+                       if (!converter.scratchBuffer)
+                               converter.scratchBuffer = buffer | 0;
+                       else
+                               Module._free (buffer | 0);
+               },
+
+               _convert_exception_for_method_call: function (result, exception) {
+                       if (exception === 0)
+                               return null;
+
+                       var msg = this.conv_string (result);
+                       var err = new Error (msg); //the convention is that invoke_method ToString () any outgoing exception
+                       // console.warn ("error", msg, "at location", err.stack);
+                       return err;
+               },
+
+               _maybe_produce_signature_warning: function (converter) {
+                       if (converter.has_warned_about_signature)
+                               return;
+
+                       console.warn ("MONO_WASM: Deprecated raw return value signature: '" + converter.args_marshal + "'. End the signature with '!' instead of 'm'.");
+                       converter.has_warned_about_signature = true;
+               },
+
+               _decide_if_result_is_marshaled: function (converter, argc) {
+                       if (!converter)
+                               return true;
+
+                       if (
+                               converter.is_result_possibly_unmarshaled && 
+                               (argc === converter.result_unmarshaled_if_argc)
+                       ) {
+                               if (argc < converter.result_unmarshaled_if_argc)
+                                       throw new Error(["Expected >= ", converter.result_unmarshaled_if_argc, "argument(s) but got", argc, "for signature " + converter.args_marshal].join(" "));
+
+                               this._maybe_produce_signature_warning (converter);
+                               return false;
+                       } else {
+                               if (argc < converter.steps.length)
+                                       throw new Error(["Expected", converter.steps.length, "argument(s) but got", argc, "for signature " + converter.args_marshal].join(" "));
+
+                               return !converter.is_result_definitely_unmarshaled;
+                       }
+               },
+
                /*
                args_marshal is a string with one character per parameter that tells how to marshal it, here are the valid values:
 
@@ -679,108 +1132,212 @@ var BindingSupportLib = {
                o: js object will be converted to a C# object (this will box numbers/bool/promises)
                m: raw mono object. Don't use it unless you know what you're doing
 
-               additionally you can append 'm' to args_marshal beyond `args.length` if you don't want the return value marshaled
+               to suppress marshaling of the return value, place '!' at the end of args_marshal, i.e. 'ii!' instead of 'ii'
                */
                call_method: function (method, this_arg, args_marshal, args) {
                        this.bindings_lazy_init ();
 
-                       // Allocate memory for error
-                       var has_args = args !== null && typeof args !== "undefined" && args.length > 0;
-                       var has_args_marshal = args_marshal !== null && typeof args_marshal !== "undefined" && args_marshal.length > 0;
+                       // HACK: Sometimes callers pass null or undefined, coerce it to 0 since that's what wasm expects
+                       this_arg = this_arg | 0;
 
-                       if (has_args_marshal && (!has_args || args.length > args_marshal.length))
-                               throw Error("Parameter count mismatch.");
+                       // Detect someone accidentally passing the wrong type of value to method
+                       if ((method | 0) !== method)
+                               throw new Error (`method must be an address in the native heap, but was '${method}'`);
+                       if (!method)
+                               throw new Error ("no method specified");
+
+                       var needs_converter = this._verify_args_for_method_call (args_marshal, args);
 
-                       var args_start = null;
-                       var buffer = null;
-                       var [resultRoot, exceptionRoot] = MONO.mono_wasm_new_roots (2);
-                       var argsRootBuffer = null;
+                       var buffer = 0, converter = null, argsRootBuffer = null;
+                       var is_result_marshaled = true;
 
                        // check if the method signature needs argument mashalling
-                       if (has_args_marshal && has_args) {
-                               var i;
-
-                               var converters = this.converters;
-                               if (!converters) {
-                                       converters = new Map ();
-                                       converters.set ('m', { steps: [{ }], size: 0});
-                                       converters.set ('s', { steps: [{ convert: this.js_string_to_mono_string.bind (this)}], size: 0});
-                                       converters.set ('o', { steps: [{ convert: this.js_to_mono_obj.bind (this)}], size: 0});
-                                       converters.set ('u', { steps: [{ convert: this.js_to_mono_uri.bind (this)}], size: 0});
-                                       converters.set ('k', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i64'}], size: 8});
-                                       converters.set ('j', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i32'}], size: 8});
-                                       converters.set ('i', { steps: [{ indirect: 'i32'}], size: 8});
-                                       converters.set ('l', { steps: [{ indirect: 'i64'}], size: 8});
-                                       converters.set ('f', { steps: [{ indirect: 'float'}], size: 8});
-                                       converters.set ('d', { steps: [{ indirect: 'double'}], size: 8});
-                                       this.converters = converters;
-                               }
+                       if (needs_converter) {
+                               converter = this._compile_converter_for_marshal_string (args_marshal);
 
-                               var converter = converters.get (args_marshal);
-                               if (!converter) {
-                                       var steps = [];
-                                       var size = 0;
+                               is_result_marshaled = this._decide_if_result_is_marshaled (converter, args.length);
+       
+                               argsRootBuffer = this._get_args_root_buffer_for_method_call (converter);
 
-                                       for (i = 0; i < args_marshal.length; ++i) {
-                                               var conv = this.converters.get (args_marshal[i]);
-                                               if (!conv)
-                                                       throw Error ("Unknown parameter type " + type);
+                               var scratchBuffer = this._get_buffer_for_method_call (converter);
 
-                                               steps.push (conv.steps[0]);
-                                               size += conv.size;
-                                       }
-                                       converter = { steps: steps, size: size };
-                                       converters.set (args_marshal, converter);
-                               }
+                               buffer = converter.compiled_variadic_function (scratchBuffer, argsRootBuffer, method, args);
+                       }
 
-                               // FIXME: Allocate a root buffer to contain all the managed objects like strings so that they aren't
-                               //  collected until the method call completes.
-
-                               // assume at least 8 byte alignment from malloc
-                               var bufferSizeBytes = converter.size + (args.length * 4);
-                               var bufferSizeElements = (bufferSizeBytes / 4) | 0;
-                               argsRootBuffer = MONO.mono_wasm_new_root_buffer (bufferSizeElements);
-                               buffer = Module._malloc (bufferSizeBytes);
-                               var indirect_start = buffer; // buffer + buffer % 8
-                               args_start = indirect_start + converter.size;
-
-                               var slot = args_start;
-                               var indirect_value = indirect_start;
-                               for (i = 0; i < args.length; ++i) {
-                                       var handler = converter.steps[i];
-                                       var obj = handler.convert ? handler.convert (args[i], method, i) : args[i];
-
-                                       if (handler.indirect) {
-                                               Module.setValue (indirect_value, obj, handler.indirect);
-                                               obj = indirect_value;
-                                               indirect_value += 8;
-                                       } else {
-                                               argsRootBuffer.set (i, obj);
-                                       }
+                       return this._call_method_with_converted_args (method, this_arg, converter, buffer, is_result_marshaled, argsRootBuffer);
+               },
 
-                                       Module.setValue (slot, obj, "*");
-                                       slot += 4;
-                               }
-                       }
+               _handle_exception_for_call: function (
+                       converter, buffer, resultRoot, exceptionRoot, argsRootBuffer
+               ) {
+                       var exc = this._convert_exception_for_method_call (resultRoot.value, exceptionRoot.value);
+                       if (!exc)
+                               return;
 
-                       try {
-                               resultRoot.value = this.invoke_method (method, this_arg, args_start, exceptionRoot.get_address ());
-                               Module._free (buffer);
+                       this._teardown_after_call (converter, buffer, resultRoot, exceptionRoot, argsRootBuffer);
+                       throw exc;
+               },
 
-                               if (exceptionRoot.value != 0) {
-                                       var msg = this.conv_string (resultRoot.value);
-                                       throw new Error (msg); //the convention is that invoke_method ToString () any outgoing exception
-                               }
+               _handle_exception_and_produce_result_for_call: function (
+                       converter, buffer, resultRoot, exceptionRoot, argsRootBuffer, is_result_marshaled
+               ) {
+                       this._handle_exception_for_call (converter, buffer, resultRoot, exceptionRoot, argsRootBuffer);
 
-                               if (has_args_marshal && has_args) {
-                                       if (args_marshal.length >= args.length && args_marshal [args.length] === "m")
-                                               return resultRoot.value;
+                       if (is_result_marshaled)
+                               result = this._unbox_mono_obj_rooted (resultRoot);
+                       else
+                               result = resultRoot.value;
+
+                       this._teardown_after_call (converter, buffer, resultRoot, exceptionRoot, argsRootBuffer);
+                       return result;
+               },
+
+               _teardown_after_call: function (converter, buffer, resultRoot, exceptionRoot, argsRootBuffer) {
+                       this._release_args_root_buffer_from_method_call (converter, argsRootBuffer);
+                       this._release_buffer_from_method_call (converter, buffer | 0);
+
+                       if (resultRoot)
+                               resultRoot.release ();
+                       if (exceptionRoot)
+                               exceptionRoot.release ();
+               },
+
+               _get_method_description: function (method) {
+                       if (!this._method_descriptions)
+                               this._method_descriptions = new Map();
+
+                       var result = this._method_descriptions.get (method);
+                       if (!result)
+                               result = "method#" + method;
+                       return result;
+               },
+
+               _call_method_with_converted_args: function (method, this_arg, converter, buffer, is_result_marshaled, argsRootBuffer) {
+                       var resultRoot = MONO.mono_wasm_new_root (), exceptionRoot = MONO.mono_wasm_new_root ();
+                       resultRoot.value = this.invoke_method (method, this_arg, buffer, exceptionRoot.get_address ());
+                       return this._handle_exception_and_produce_result_for_call (converter, buffer, resultRoot, exceptionRoot, argsRootBuffer, is_result_marshaled);
+               },
+
+               bind_method: function (method, this_arg, args_marshal, friendly_name) {
+                       this.bindings_lazy_init ();
+
+                       this_arg = this_arg | 0;
+
+                       var converter = null;
+                       if (typeof (args_marshal) === "string")
+                               converter = this._compile_converter_for_marshal_string (args_marshal);
+
+                       var closure = {
+                               library_mono: MONO,
+                               binding_support: this,
+                               method: method,
+                               this_arg: this_arg
+                       };
+
+                       var converterKey = "converter_" + converter.name;
+
+                       if (converter)
+                               closure[converterKey] = converter;
+
+                       var argumentNames = [];
+                       var body = [
+                               "var resultRoot = library_mono.mono_wasm_new_root (), exceptionRoot = library_mono.mono_wasm_new_root ();",
+                               ""
+                       ];
+
+                       if (converter) {
+                               body.push(
+                                       `var argsRootBuffer = binding_support._get_args_root_buffer_for_method_call (${converterKey});`,
+                                       `var scratchBuffer = binding_support._get_buffer_for_method_call (${converterKey});`,
+                                       `var buffer = ${converterKey}.compiled_function (`,
+                                       "    scratchBuffer, argsRootBuffer, method,"
+                               );
+
+                               for (var i = 0; i < converter.steps.length; i++) {
+                                       var argName = "arg" + i;
+                                       argumentNames.push(argName);
+                                       body.push(
+                                               "    " + argName +
+                                               (
+                                                       (i == converter.steps.length - 1) 
+                                                               ? "" 
+                                                               : ", "
+                                               )
+                                       );
                                }
 
-                               return this._unbox_mono_obj_rooted (resultRoot);
-                       } finally {
-                               MONO.mono_wasm_release_roots (resultRoot, exceptionRoot, argsRootBuffer);
+                               body.push(");");
+       
+                       } else {
+                               body.push("var argsRootBuffer = null, buffer = 0;");
+                       }
+
+                       if (converter.is_result_definitely_unmarshaled) {
+                               body.push ("var is_result_marshaled = false;");
+                       } else if (converter.is_result_possibly_unmarshaled) {
+                               body.push (`var is_result_marshaled = arguments.length !== ${converter.result_unmarshaled_if_argc};`);
+                       } else {
+                               body.push ("var is_result_marshaled = true;");
+                       }
+
+                       // We inline a bunch of the invoke and marshaling logic here in order to eliminate the GC pressure normally
+                       //  created by the unboxing part of the call process. Because unbox_mono_obj(_rooted) can return non-numeric
+                       //  types, v8 and spidermonkey allocate and store its result on the heap (in the nursery, to be fair).
+                       // For a bound method however, we know the result will always be the same type because C# methods have known
+                       //  return types. Inlining the invoke and marshaling logic means that even though the bound method has logic
+                       //  for handling various types, only one path through the method (for its appropriate return type) will ever
+                       //  be taken, and the JIT will see that the 'result' local and thus the return value of this function are
+                       //  always of the exact same type. All of the branches related to this end up being predicted and low-cost.
+                       // The end result is that bound method invocations don't always allocate, so no more nursery GCs. Yay! -kg
+                       body.push(
+                               "",
+                               "resultRoot.value = binding_support.invoke_method (method, this_arg, buffer, exceptionRoot.get_address ());",
+                               `binding_support._handle_exception_for_call (${converterKey}, buffer, resultRoot, exceptionRoot, argsRootBuffer);`,
+                               "",
+                               "var resultPtr = resultRoot.value, result = undefined;",
+                               "if (!is_result_marshaled) ",
+                               "    result = resultPtr;",
+                               "else if (resultPtr !== 0) {",
+                               // For the common scenario where the return type is a primitive, we want to try and unbox it directly
+                               //  into our existing heap allocation and then read it out of the heap. Doing this all in one operation
+                               //  means that we only need to enter a gc safe region twice (instead of 3+ times with the normal,
+                               //  slower check-type-and-then-unbox flow which has extra checks since unbox verifies the type).
+                               "    var resultType = binding_support.mono_wasm_try_unbox_primitive_and_get_type (resultPtr, buffer);",
+                               "    switch (resultType) {",
+                               "    case 1:", // int
+                               "        result = Module.HEAP32[buffer / 4]; break;",
+                               "    case 25:", // uint32
+                               "        result = Module.HEAPU32[buffer / 4]; break;",
+                               "    case 24:", // float32
+                               "        result = Module.HEAPF32[buffer / 4]; break;",
+                               "    case 2:", // float64
+                               "        result = Module.HEAPF64[buffer / 8]; break;",
+                               "    case 8:", // boolean
+                               "        result = (Module.HEAP32[buffer / 4]) !== 0; break;",
+                               "    case 28:", // char
+                               "        result = String.fromCharCode(Module.HEAP32[buffer / 4]); break;",
+                               "    default:",
+                               "        result = binding_support._unbox_mono_obj_rooted_with_known_nonprimitive_type (resultRoot, resultType); break;",
+                               "    }",
+                               "}",
+                               "",
+                               `binding_support._teardown_after_call (${converterKey}, buffer, resultRoot, exceptionRoot, argsRootBuffer);`,
+                               "return result;"
+                       );
+
+                       bodyJs = body.join ("\r\n");
+
+                       if (friendly_name) {
+                               var escapeRE = /[^A-Za-z0-9_]/g;
+                               friendly_name = friendly_name.replace(escapeRE, "_");
                        }
+
+                       var displayName = "managed_" + (friendly_name || method);
+                       
+                       if (this_arg)
+                               displayName += "_with_this_" + this_arg;
+
+                       return this._create_named_function(displayName, argumentNames, bodyJs, closure);
                },
 
                invoke_delegate: function (delegate_obj, js_args) {
@@ -808,9 +1365,7 @@ var BindingSupportLib = {
                                argsRoot.value = this.js_array_to_mono_array (js_args);
                                if (!this.delegate_dynamic_invoke)
                                        throw new Error("System.Delegate.DynamicInvoke method can not be resolved.");
-                               // Note: the single 'm' passed here is causing problems with AOT.  Changed to "mo" again.  
-                               // This may need more analysis if causes problems again.
-                               return this.call_method (this.delegate_dynamic_invoke, delegateRoot.value, "mo", [ argsRoot.value ]);
+                               return this.call_method (this.delegate_dynamic_invoke, delegateRoot.value, "m", [ argsRoot.value ]);
                        } finally {
                                MONO.mono_wasm_release_roots (delegateRoot, argsRoot);
                        }
@@ -839,7 +1394,7 @@ var BindingSupportLib = {
 
                        var klass = this.find_class(asm, namespace, classname);
                        if (!klass)
-                               throw new Error ("Could not find class: " + namespace + ":" +classname);
+                               throw new Error ("Could not find class: " + namespace + ":" + classname + " in assembly " + assembly);
 
                        var method = this.find_method (klass, methodname, -1);
                        if (!method)
@@ -866,10 +1421,9 @@ var BindingSupportLib = {
                        if (typeof signature === "undefined")
                                signature = Module.mono_method_get_call_signature (method);
 
-                       return function() {
-                               return BINDING.call_method (method, null, signature, arguments);
-                       };
+                       return BINDING.bind_method (method, null, signature, fqn);
                },
+               
                bind_assembly_entry_point: function (assembly) {
                        this.bindings_lazy_init ();
 
@@ -1122,7 +1676,7 @@ var BindingSupportLib = {
         
                }
                BINDING.mono_wasm_unwind_LMF();
-        return BINDING.call_method (BINDING.box_js_bool, null, "im", [ result ]);
+        return BINDING._box_js_bool (result);
        },
        mono_wasm_get_by_index: function(js_handle, property_index, is_exception) {
                BINDING.bindings_lazy_init ();
@@ -1173,7 +1727,7 @@ var BindingSupportLib = {
 
                var js_name = BINDING.conv_string (global_name);
 
-               var globalObj = undefined;
+               var globalObj;
 
                if (!js_name) {
                        globalObj = globalThis;
@@ -1253,8 +1807,9 @@ var BindingSupportLib = {
                                var argsList = new Array();
                                argsList[0] = constructor;
                                if (js_args)
-                                       argsList = argsList.concat(js_args);
-                               var obj = new (constructor.bind.apply(constructor, argsList ));
+                                       argsList = argsList.concat (js_args);
+                               var tempCtor = constructor.bind.apply (constructor, argsList);
+                               var obj = new tempCtor ();
                                return obj;
                        };
        
index 86606ee..50d0c78 100644 (file)
@@ -106,10 +106,20 @@ mono_wasm_invoke_js (MonoString *str, int *is_exception)
                        res = res.toString ();
                        setValue ($2, 0, "i32");
                } catch (e) {
-                       res = e.toString ();
+                       res = e.toString();
                        setValue ($2, 1, "i32");
                        if (res === null || res === undefined)
                                res = "unknown exception";
+
+                       var stack = e.stack;
+                       if (stack) {
+                               // Some JS runtimes insert the error message at the top of the stack, some don't,
+                               //  so normalize it by using the stack as the result if it already contains the error
+                               if (stack.startsWith(res))
+                                       res = stack;
+                               else
+                                       res += "\n" + stack;
+                       }
                }
                var buff = Module._malloc((res.length + 1) * 2);
                stringToUTF16 (res, buff, (res.length + 1) * 2);
@@ -572,6 +582,12 @@ mono_wasm_assembly_load (const char *name)
        return res;
 }
 
+EMSCRIPTEN_KEEPALIVE MonoClass* 
+mono_wasm_find_corlib_class (const char *namespace, const char *name)
+{
+       return mono_class_from_name (mono_get_corlib (), namespace, name);
+}
+
 EMSCRIPTEN_KEEPALIVE MonoClass*
 mono_wasm_assembly_find_class (MonoAssembly *assembly, const char *namespace, const char *name)
 {
@@ -585,6 +601,21 @@ mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int argument
 }
 
 EMSCRIPTEN_KEEPALIVE MonoObject*
+mono_wasm_box_primitive (MonoClass *klass, void *value, int value_size)
+{
+       if (!klass)
+               return NULL;
+
+       MonoType *type = mono_class_get_type (klass);
+       int alignment;
+       if (mono_type_size (type, &alignment) > value_size)
+               return NULL;
+
+       // TODO: use mono_value_box_checked and propagate error out
+       return mono_value_box (root_domain, klass, value);
+}
+
+EMSCRIPTEN_KEEPALIVE MonoObject*
 mono_wasm_invoke_method (MonoMethod *method, MonoObject *this_arg, void *params[], MonoObject **out_exc)
 {
        MonoObject *exc = NULL;
@@ -721,7 +752,7 @@ MonoClass* mono_get_uri_class(MonoException** exc)
 }
 
 #define MARSHAL_TYPE_INT 1
-#define MARSHAL_TYPE_FP 2
+#define MARSHAL_TYPE_FP64 2
 #define MARSHAL_TYPE_STRING 3
 #define MARSHAL_TYPE_VT 4
 #define MARSHAL_TYPE_DELEGATE 5
@@ -745,17 +776,14 @@ MonoClass* mono_get_uri_class(MonoException** exc)
 #define MARSHAL_ARRAY_FLOAT 17
 #define MARSHAL_ARRAY_DOUBLE 18
 
-EMSCRIPTEN_KEEPALIVE int
-mono_wasm_get_obj_type (MonoObject *obj)
-{
-       if (!obj)
-               return 0;
-
-       /* Process obj before calling into the runtime, class_from_name () can invoke managed code */
-       MonoClass *klass = mono_object_get_class (obj);
-       MonoType *type = mono_class_get_type (klass);
-       obj = NULL;
+#define MARSHAL_TYPE_FP32 24
+#define MARSHAL_TYPE_UINT32 25
+#define MARSHAL_TYPE_INT64 26
+#define MARSHAL_TYPE_UINT64 27
+#define MARSHAL_TYPE_CHAR 28
 
+void mono_wasm_ensure_classes_resolved ()
+{
        if (!datetime_class && !resolved_datetime_class) {
                datetime_class = mono_class_from_name (mono_get_corlib(), "System", "DateTime");
                resolved_datetime_class = 1;
@@ -773,8 +801,12 @@ mono_wasm_get_obj_type (MonoObject *obj)
                safehandle_class = mono_class_from_name (mono_get_corlib(), "System.Runtime.InteropServices", "SafeHandle");
                resolved_safehandle_class = 1;
        }
+}
 
-       switch (mono_type_get_type (type)) {
+int
+mono_wasm_marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType *type)
+{
+       switch (mono_type) {
        // case MONO_TYPE_CHAR: prob should be done not as a number?
        case MONO_TYPE_BOOLEAN:
                return MARSHAL_TYPE_BOOL;
@@ -783,18 +815,25 @@ mono_wasm_get_obj_type (MonoObject *obj)
        case MONO_TYPE_I2:
        case MONO_TYPE_U2:
        case MONO_TYPE_I4:
-       case MONO_TYPE_U4:
-       case MONO_TYPE_I8:
-       case MONO_TYPE_U8:
        case MONO_TYPE_I:       // IntPtr
                return MARSHAL_TYPE_INT;
+       case MONO_TYPE_CHAR:
+               return MARSHAL_TYPE_CHAR;
+       case MONO_TYPE_U4:  // The distinction between this and signed int is
+                                               // important due to how numbers work in JavaScript
+               return MARSHAL_TYPE_UINT32;
+       case MONO_TYPE_I8:
+               return MARSHAL_TYPE_INT64;
+       case MONO_TYPE_U8:
+               return MARSHAL_TYPE_UINT64;
        case MONO_TYPE_R4:
+               return MARSHAL_TYPE_FP32;
        case MONO_TYPE_R8:
-               return MARSHAL_TYPE_FP;
+               return MARSHAL_TYPE_FP64;
        case MONO_TYPE_STRING:
                return MARSHAL_TYPE_STRING;
        case MONO_TYPE_SZARRAY:  { // simple zero based one-dim-array
-               MonoClass *eklass = mono_class_get_element_class(klass);
+               MonoClass *eklass = mono_class_get_element_class (klass);
                MonoType *etype = mono_class_get_type (eklass);
 
                switch (mono_type_get_type (etype)) {
@@ -819,6 +858,8 @@ mono_wasm_get_obj_type (MonoObject *obj)
                }
        }
        default:
+               mono_wasm_ensure_classes_resolved ();
+
                if (klass == datetime_class)
                        return MARSHAL_TYPE_DATE;
                if (klass == datetimeoffset_class)
@@ -842,6 +883,96 @@ mono_wasm_get_obj_type (MonoObject *obj)
 }
 
 EMSCRIPTEN_KEEPALIVE int
+mono_wasm_get_obj_type (MonoObject *obj)
+{
+       if (!obj)
+               return 0;
+
+       /* Process obj before calling into the runtime, class_from_name () can invoke managed code */
+       MonoClass *klass = mono_object_get_class (obj);
+       MonoType *type = mono_class_get_type (klass);
+       obj = NULL;
+
+       int mono_type = mono_type_get_type (type);
+
+       return mono_wasm_marshal_type_from_mono_type (mono_type, klass, type);
+}
+
+EMSCRIPTEN_KEEPALIVE int
+mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result)
+{
+       int *resultI = result;
+       int64_t *resultL = result;
+       float *resultF = result;
+       double *resultD = result;
+
+       if (!obj) {
+               *resultL = 0;
+               return 0;
+       }
+
+       /* Process obj before calling into the runtime, class_from_name () can invoke managed code */
+       MonoClass *klass = mono_object_get_class (obj);
+       MonoType *type = mono_class_get_type (klass), *original_type = type;
+
+       if (mono_class_is_enum (klass))
+               type = mono_type_get_underlying_type (type);
+       
+       int mono_type = mono_type_get_type (type);
+       
+       // FIXME: We would prefer to unbox once here but it will fail if the value isn't unboxable
+
+       switch (mono_type) {
+               case MONO_TYPE_I1:
+               case MONO_TYPE_BOOLEAN:
+                       *resultI = *(signed char*)mono_object_unbox (obj);
+                       break;
+               case MONO_TYPE_U1:
+                       *resultI = *(unsigned char*)mono_object_unbox (obj);
+                       break;
+               case MONO_TYPE_I2:
+               case MONO_TYPE_CHAR:
+                       *resultI = *(short*)mono_object_unbox (obj);
+                       break;
+               case MONO_TYPE_U2:
+                       *resultI = *(unsigned short*)mono_object_unbox (obj);
+                       break;
+               case MONO_TYPE_I4:
+               case MONO_TYPE_I:
+                       *resultI = *(int*)mono_object_unbox (obj);
+                       break;
+               case MONO_TYPE_U4:
+                       // FIXME: Will this behave the way we want for large unsigned values?
+                       *resultI = *(int*)mono_object_unbox (obj);
+                       break;
+               case MONO_TYPE_R4:
+                       *resultF = *(float*)mono_object_unbox (obj);
+                       break;
+               case MONO_TYPE_R8:
+                       *resultD = *(double*)mono_object_unbox (obj);
+                       break;
+               case MONO_TYPE_I8:
+               case MONO_TYPE_U8:
+                       // FIXME: At present the javascript side of things can't handle this,
+                       //  but there's no reason not to future-proof this API
+                       *resultL = *(int64_t*)mono_object_unbox (obj);
+                       break;
+               default:
+                       // If we failed to do a fast unboxing, return the original type information so
+                       //  that the caller can do a proper, slow unboxing later
+                       *resultL = 0;
+                       obj = NULL;
+                       return mono_wasm_marshal_type_from_mono_type (mono_type, klass, original_type);
+       }
+
+       // We successfully performed a fast unboxing here so use the type information
+       //  matching what we unboxed (i.e. an enum's underlying type instead of its type)
+       obj = NULL;
+       return mono_wasm_marshal_type_from_mono_type (mono_type, klass, type);
+}
+
+// FIXME: This function is retained specifically because runtime-test.js uses it
+EMSCRIPTEN_KEEPALIVE int
 mono_unbox_int (MonoObject *obj)
 {
        if (!obj)
@@ -864,6 +995,8 @@ mono_unbox_int (MonoObject *obj)
                return *(int*)ptr;
        case MONO_TYPE_U4:
                return *(unsigned int*)ptr;
+       case MONO_TYPE_CHAR:
+               return *(short*)ptr;
        // WASM doesn't support returning longs to JS
        // case MONO_TYPE_I8:
        // case MONO_TYPE_U8:
@@ -873,25 +1006,6 @@ mono_unbox_int (MonoObject *obj)
        }
 }
 
-EMSCRIPTEN_KEEPALIVE double
-mono_wasm_unbox_float (MonoObject *obj)
-{
-       if (!obj)
-               return 0;
-       MonoType *type = mono_class_get_type (mono_object_get_class(obj));
-
-       void *ptr = mono_object_unbox (obj);
-       switch (mono_type_get_type (type)) {
-       case MONO_TYPE_R4:
-               return *(float*)ptr;
-       case MONO_TYPE_R8:
-               return *(double*)ptr;
-       default:
-               printf ("Invalid type %d to mono_wasm_unbox_float\n", mono_type_get_type (type));
-               return 0;
-       }
-}
-
 EMSCRIPTEN_KEEPALIVE int
 mono_wasm_array_length (MonoArray *array)
 {
index 0f3cb96..fc21272 100644 (file)
@@ -82,6 +82,7 @@ var MonoSupportLib = {
                        module ["mono_wasm_globalization_init"] = MONO.mono_wasm_globalization_init;
                        module ["mono_wasm_get_loaded_files"] = MONO.mono_wasm_get_loaded_files;
                        module ["mono_wasm_new_root_buffer"] = MONO.mono_wasm_new_root_buffer;
+                       module ["mono_wasm_new_root_buffer_from_pointer"] = MONO.mono_wasm_new_root_buffer_from_pointer;
                        module ["mono_wasm_new_root"] = MONO.mono_wasm_new_root;
                        module ["mono_wasm_new_roots"] = MONO.mono_wasm_new_roots;
                        module ["mono_wasm_release_roots"] = MONO.mono_wasm_release_roots;
@@ -195,9 +196,12 @@ var MonoSupportLib = {
                },
 
                _mono_wasm_root_buffer_prototype: {
+                       _throw_index_out_of_range: function () {
+                               throw new Error ("index out of range");
+                       },
                        _check_in_range: function (index) {
                                if ((index >= this.__count) || (index < 0))
-                                       throw new Error ("index out of range");
+                                       this._throw_index_out_of_range();
                        },
                        /** @returns {NativePointer} */
                        get_address: function (index) {
@@ -215,23 +219,37 @@ var MonoSupportLib = {
                                return Module.HEAP32[this.get_address_32 (index)];
                        },
                        set: function (index, value) {
-                               this._check_in_range (index);
                                Module.HEAP32[this.get_address_32 (index)] = value;
                                return value;
                        },
+                       _unsafe_get: function (index) {
+                               return Module.HEAP32[this.__offset32 + index];
+                       },
+                       _unsafe_set: function (index, value) {
+                               Module.HEAP32[this.__offset32 + index] = value;
+                       },
+                       clear: function () {
+                               if (this.__offset)
+                                       MONO._zero_region (this.__offset, this.__count * 4);
+                       },
                        release: function () {
-                               if (this.__offset) {
+                               if (this.__offset && this.__ownsAllocation) {
                                        MONO.mono_wasm_deregister_root (this.__offset);
                                        MONO._zero_region (this.__offset, this.__count * 4);
                                        Module._free (this.__offset);
                                }
 
-                               this.__handle = this.__offset = this.__count = this.__offset32 = undefined;
+                               this.__handle = this.__offset = this.__count = this.__offset32 = 0;
                        },
+                       toString: function () {
+                               return "[root buffer @" + this.get_address (0) + ", size " + this.__count + "]";
+                       }
                },
 
                _scratch_root_buffer: null,
                _scratch_root_free_indices: null,
+               _scratch_root_free_indices_count: 0,
+               _scratch_root_free_instances: [],
 
                _mono_wasm_root_prototype: {
                        /** @returns {NativePointer} */
@@ -244,21 +262,33 @@ var MonoSupportLib = {
                        },
                        /** @returns {ManagedPointer} */
                        get: function () {
-                               var result = this.__buffer.get (this.__index);
+                               var result = this.__buffer._unsafe_get (this.__index);
                                return result;
                        },
                        set: function (value) {
-                               this.__buffer.set (this.__index, value);
+                               this.__buffer._unsafe_set (this.__index, value);
                                return value;
                        },
                        /** @returns {ManagedPointer} */
                        valueOf: function () {
                                return this.get ();
                        },
+                       clear: function () {
+                               this.set (0);
+                       },
                        release: function () {
-                               MONO._mono_wasm_release_scratch_index (this.__index);
-                               this.__buffer = undefined;
-                               this.__index = undefined;
+                               const maxPooledInstances = 128;
+                               if (MONO._scratch_root_free_instances.length > maxPooledInstances) {
+                                       MONO._mono_wasm_release_scratch_index (this.__index);
+                                       this.__buffer = 0;
+                                       this.__index = 0;
+                               } else {
+                                       this.set (0);
+                                       MONO._scratch_root_free_instances.push (this);
+                               }
+                       },
+                       toString: function () {
+                               return "[root @" + this.get_address () + "]";
                        }
                },
 
@@ -267,7 +297,8 @@ var MonoSupportLib = {
                                return;
 
                        this._scratch_root_buffer.set (index, 0);
-                       this._scratch_root_free_indices.push (index);
+                       this._scratch_root_free_indices[this._scratch_root_free_indices_count] = index;
+                       this._scratch_root_free_indices_count++;
                },
 
                _mono_wasm_claim_scratch_index: function () {
@@ -275,10 +306,10 @@ var MonoSupportLib = {
                                const maxScratchRoots = 8192;
                                this._scratch_root_buffer = this.mono_wasm_new_root_buffer (maxScratchRoots, "js roots");
 
-                               this._scratch_root_free_indices = new Array (maxScratchRoots);
+                               this._scratch_root_free_indices = new Int32Array (maxScratchRoots);
+                               this._scratch_root_free_indices_count = maxScratchRoots;
                                for (var i = 0; i < maxScratchRoots; i++)
-                                       this._scratch_root_free_indices[i] = i;
-                               this._scratch_root_free_indices.reverse ();
+                                       this._scratch_root_free_indices[i] = maxScratchRoots - i - 1;
 
                                Object.defineProperty (this._mono_wasm_root_prototype, "value", {
                                        get: this._mono_wasm_root_prototype.get,
@@ -287,15 +318,19 @@ var MonoSupportLib = {
                                });
                        }
 
-                       if (this._scratch_root_free_indices.length < 1)
+                       if (this._scratch_root_free_indices_count < 1)
                                throw new Error ("Out of scratch root space");
 
-                       var result = this._scratch_root_free_indices.pop ();
+                       var result = this._scratch_root_free_indices[this._scratch_root_free_indices_count - 1];
+                       this._scratch_root_free_indices_count--;
                        return result;
                },
 
                _zero_region: function (byteOffset, sizeBytes) {
-                       (new Uint8Array (Module.HEAPU8.buffer, byteOffset, sizeBytes)).fill (0);
+                       if (((byteOffset % 4) === 0) && ((sizeBytes % 4) === 0))
+                               Module.HEAP32.fill(0, byteOffset / 4, sizeBytes / 4);
+                       else
+                               Module.HEAP8.fill(0, byteOffset, sizeBytes);
                },
 
                /**
@@ -331,6 +366,43 @@ var MonoSupportLib = {
                        result.__count = capacity;
                        result.length = capacity;
                        result.__handle = this.mono_wasm_register_root (offset, capacityBytes, msg || 0);
+                       result.__ownsAllocation = true;
+
+                       return result;
+               },
+
+               /**
+                * Creates a root buffer object representing an existing allocation in the native heap and registers
+                *  the allocation with the GC. The caller is responsible for managing the lifetime of the allocation.
+                * @param {NativePointer} offset - the offset of the root buffer in the native heap.
+                * @param {number} capacity - the maximum number of elements the buffer can hold.
+                * @param {string} [msg] - a description of the root buffer (for debugging)
+                * @returns {WasmRootBuffer}
+                */
+               mono_wasm_new_root_buffer_from_pointer: function (offset, capacity, msg) {
+                       if (!this.mono_wasm_register_root || !this.mono_wasm_deregister_root) {
+                               this.mono_wasm_register_root = Module.cwrap ("mono_wasm_register_root", "number", ["number", "number", "string"]);
+                               this.mono_wasm_deregister_root = Module.cwrap ("mono_wasm_deregister_root", null, ["number"]);
+                       }
+
+                       if (capacity <= 0)
+                               throw new Error ("capacity >= 1");
+
+                       capacity = capacity | 0;
+                               
+                       var capacityBytes = capacity * 4;
+                       if ((offset % 4) !== 0)
+                               throw new Error ("Unaligned offset");
+
+                       this._zero_region (offset, capacityBytes);
+
+                       var result = Object.create (this._mono_wasm_root_buffer_prototype);
+                       result.__offset = offset;
+                       result.__offset32 = (offset / 4) | 0;
+                       result.__count = capacity;      
+                       result.length = capacity;
+                       result.__handle = this.mono_wasm_register_root (offset, capacityBytes, msg || 0);
+                       result.__ownsAllocation = false;
 
                        return result;
                },
@@ -345,12 +417,18 @@ var MonoSupportLib = {
                 * @returns {WasmRoot}
                 */
                mono_wasm_new_root: function (value) {
-                       var index = this._mono_wasm_claim_scratch_index ();
-                       var buffer = this._scratch_root_buffer;
+                       var result;
 
-                       var result = Object.create (this._mono_wasm_root_prototype);
-                       result.__buffer = buffer;
-                       result.__index = index;
+                       if (this._scratch_root_free_instances.length > 0) {
+                               result = this._scratch_root_free_instances.pop ();
+                       } else {
+                               var index = this._mono_wasm_claim_scratch_index ();
+                               var buffer = this._scratch_root_buffer;
+                                       
+                               result = Object.create (this._mono_wasm_root_prototype);
+                               result.__buffer = buffer;
+                               result.__index = index;
+                       }
 
                        if (value !== undefined) {
                                if (typeof (value) !== "number")