sunnycase 6 anni fa
parent
commit
161d05700e

+ 1 - 1
CMakeLists.txt

@@ -21,7 +21,7 @@ set(THIRD_PARTY ${CMAKE_CURRENT_LIST_DIR}/third_party)
 set(CMAKE_CXX_STANDARD 17)
 
 if (MSVC)
-    add_definitions(/D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS /DNOMINMAX)
+    add_definitions(/D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS /DNOMINMAX /DUNICODE)
 else()
     add_compile_options(-Wno-multichar)
 endif()

+ 3 - 3
src/Chino.Chip.K210/HAL/Serial/Uarths.cs

@@ -5,7 +5,7 @@ namespace Chino.Chip.K210.HAL.Serial
 {
     public class Uarths
     {
-        private static ref UarthsReg Reg => ref Volatile.As<UarthsReg>(RegisterMap.UARTHS_BASE_ADDR);
+        private static ref UarthsReg Reg => ref IOVolatile.As<UarthsReg>(RegisterMap.UARTHS_BASE_ADDR);
 
         public static void DebugWrite(string text)
         {
@@ -20,8 +20,8 @@ namespace Chino.Chip.K210.HAL.Serial
 
         private static void DebugWriteByte(byte value)
         {
-            while (Volatile.Read(ref Reg.txdata).full != 0) ;
-            Volatile.Write(ref Reg.txdata, new uarths_txdata { data = value });
+            while (IOVolatile.Read(ref Reg.txdata).full != 0) ;
+            IOVolatile.Write(ref Reg.txdata, new uarths_txdata { data = value });
         }
     }
 

+ 135 - 0
src/Chino.Core/Collections/ValueRingBuffer.cs

@@ -0,0 +1,135 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Threading;
+
+namespace Chino.Collections
+{
+    public struct ValueRingBuffer<T>
+    {
+        private readonly T[] _buffer;
+        private int _readPointer;
+        private int _writePointer;
+
+        public ValueRingBuffer(int capacity)
+        {
+            Debug.Assert(capacity > 0);
+            _buffer = new T[capacity];
+            _readPointer = 0;
+            _writePointer = 0;
+        }
+
+        public int TryRead(Span<T> buffer)
+        {
+            Debug.Assert(buffer.Length > 0);
+            int toRead = buffer.Length;
+            if (toRead > 0)
+            {
+                var pointers = GetPointers();
+                if (pointers.writePtr > pointers.readPtr)
+                {
+                    var avail = pointers.writePtr - pointers.readPtr;
+                    var canRead = Math.Min(toRead, avail);
+                    if (canRead > 0)
+                    {
+                        _buffer.AsSpan().Slice(pointers.readPtr, canRead).CopyTo(buffer);
+                        Volatile.Write(ref _readPointer, pointers.readPtr + canRead);
+                        return canRead;
+                    }
+                }
+                else if (pointers.writePtr < pointers.readPtr)
+                {
+                    // read tail
+                    var avail = _buffer.Length - pointers.readPtr;
+                    var canRead = Math.Min(toRead, avail);
+                    int read = 0;
+                    if (canRead > 0)
+                    {
+                        _buffer.AsSpan().Slice(pointers.readPtr, canRead).CopyTo(buffer);
+                        read += canRead;
+                        toRead -= canRead;
+                    }
+
+                    avail = pointers.writePtr;
+                    canRead = Math.Min(toRead, avail);
+                    if (canRead > 0)
+                    {
+                        _buffer.AsSpan().Slice(0, canRead).CopyTo(buffer.Slice(read));
+                        read += canRead;
+                        Volatile.Write(ref _readPointer, canRead);
+                    }
+                    else
+                    {
+                        var pos = pointers.readPtr + read;
+                        if (pos == _buffer.Length) pos = 0;
+                        Volatile.Write(ref _readPointer, pos);
+                    }
+
+                    return read;
+                }
+            }
+
+            return 0;
+        }
+
+        public int TryWrite(ReadOnlySpan<T> buffer)
+        {
+            throw new NotImplementedException();
+            //Debug.Assert(buffer.Length > 0);
+            //int toWrite = buffer.Length;
+            //if (toWrite > 0)
+            //{
+            //    var pointers = GetPointers();
+            //    if (pointers.writePtr < pointers.readPtr)
+            //    {
+            //        var avail = pointers.writePtr - pointers.readPtr;
+            //        var canRead = Math.Min(toWrite, avail);
+            //        if (canRead > 0)
+            //        {
+            //            _buffer.AsSpan().Slice(pointers.readPtr, canRead).CopyTo(buffer);
+            //            Volatile.Write(ref _readPointer, pointers.readPtr + canRead);
+            //            return canRead;
+            //        }
+            //    }
+            //    else if (pointers.writePtr < pointers.readPtr)
+            //    {
+            //        // read tail
+            //        var avail = _buffer.Length - pointers.readPtr;
+            //        var canRead = Math.Min(toWrite, avail);
+            //        int read = 0;
+            //        if (canRead > 0)
+            //        {
+            //            _buffer.AsSpan().Slice(pointers.readPtr, canRead).CopyTo(buffer);
+            //            read += canRead;
+            //            toWrite -= canRead;
+            //        }
+
+            //        avail = pointers.writePtr;
+            //        canRead = Math.Min(toWrite, avail);
+            //        if (canRead > 0)
+            //        {
+            //            _buffer.AsSpan().Slice(0, canRead).CopyTo(buffer.Slice(read));
+            //            read += canRead;
+            //            Volatile.Write(ref _readPointer, canRead);
+            //        }
+            //        else
+            //        {
+            //            var pos = pointers.readPtr + read;
+            //            if (pos == _buffer.Length) pos = 0;
+            //            Volatile.Write(ref _readPointer, pos);
+            //        }
+
+            //        return read;
+            //    }
+            //}
+
+            //return 0;
+        }
+
+        private (int readPtr, int writePtr) GetPointers()
+        {
+            return (Volatile.Read(ref _readPointer), Volatile.Read(ref _writePointer));
+        }
+    }
+}

+ 19 - 0
src/Chino.Core/Devices/IO/Console.cs

@@ -0,0 +1,19 @@
+using Chino.Collections;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Chino.Devices.IO
+{
+    public abstract class Console : IConsole
+    {
+        public event EventHandler DataAvailable;
+
+        private readonly ValueRingBuffer<ConsoleEvent> _eventsBuffer;
+
+        public Console()
+        {
+            _eventsBuffer = new ValueRingBuffer<ConsoleEvent>(16);
+        }
+    }
+}

+ 30 - 0
src/Chino.Core/Devices/IO/IConsole.cs

@@ -1,9 +1,39 @@
 using System;
 using System.Collections.Generic;
+using System.Runtime.InteropServices;
 using System.Text;
 
 namespace Chino.Devices.IO
 {
+    public enum ConsoleEventType
+    {
+        Invalid = 0,
+        KeyEvent = 1
+    }
+
+    [StructLayout(LayoutKind.Explicit)]
+    public struct ConsoleEvent
+    {
+        [field: FieldOffset(0)]
+        public ConsoleEventType Type { get; }
+
+        [field: FieldOffset(4)]
+        public KeyEvent Key { get; }
+
+        public struct KeyEvent
+        {
+            public bool KeyDown { get; }
+
+            public char Char { get; }
+
+            public KeyEvent(bool keyDown, char c)
+            {
+                KeyDown = keyDown;
+                Char = c;
+            }
+        }
+    }
+
     public interface IConsole
     {
         event EventHandler DataAvailable;

+ 1 - 1
src/Chino.Core/Volatile.cs → src/Chino.Core/IOVolatile.cs

@@ -3,7 +3,7 @@ using System.Runtime.CompilerServices;
 
 namespace Chino
 {
-    public static class Volatile
+    public static class IOVolatile
     {
         [MethodImpl(MethodImplOptions.InternalCall)]
         public static extern ref T As<T>(uint address) where T : unmanaged;

+ 28 - 0
src/Native/arch/emulator/hal/io/console.cpp

@@ -1,16 +1,44 @@
 #include "Chino.Chip.Emulator.h"
 #include <Windows.h>
 #include <process.h>
+#include <wrl.h>
+// clang-format off
+#include "../../win_utils.h"
+// clang-format on
 
 using namespace natsu;
 using namespace System_Runtime::System;
 using namespace Chino_Chip_Emulator::Chino;
 using namespace Chino_Chip_Emulator::Chino::Chip::Emulator::HAL::IO;
+namespace wrl = Microsoft::WRL;
 
 namespace
 {
 void ConsoleReadThread(void *arg)
 {
+    auto this_ = gc_obj_ref<Console>(reinterpret_cast<Console *>(arg));
+    wrl::Wrappers::FileHandle cin(GetStdHandle(STD_INPUT_HANDLE));
+    THROW_WIN32_IF_NOT(cin.IsValid());
+
+    INPUT_RECORD input_records[16];
+    while (true)
+    {
+        DWORD input_read = 0;
+        THROW_WIN32_IF_NOT(ReadConsoleInput(cin.Get(), input_records, std::size(input_records), &input_read));
+        for (DWORD i = 0; i < input_read; i++)
+        {
+            auto &record = input_records[i];
+            switch (record.EventType)
+            {
+            case KEY_EVENT:
+                if (record.Event.KeyEvent.bKeyDown)
+                    putchar(record.Event.KeyEvent.uChar.UnicodeChar);
+                break;
+            default:
+                break;
+            }
+        }
+    }
 }
 }
 

+ 9 - 0
src/Native/arch/emulator/win_utils.h

@@ -0,0 +1,9 @@
+#pragma once
+#include <Windows.h>
+#include <stdexcept>
+
+#define THROW_WIN32_IF_NOT(x)         \
+    if (!(x))                         \
+    {                                 \
+        throw std::runtime_error(#x); \
+    }

+ 10 - 0
src/Native/main.cpp

@@ -1,5 +1,8 @@
 #include "Generated/Chino.Kernel.h"
 #include <algorithm>
+#if WIN32
+#include <Windows.h>
+#endif
 
 using namespace Chino_Kernel::Chino::Kernel;
 
@@ -9,5 +12,12 @@ int main()
 {
     InitializeHeap();
     Program::_s_Main();
+#if WIN32
+    while (1)
+        Sleep(0);
+#else
+    while (1)
+        ;
+#endif
     return 0;
 }

+ 19 - 0
src/Native/natsu.fcall.cpp

@@ -230,3 +230,22 @@ Double Math::_s_ModF(Double x, gc_ptr<Double> y)
 {
     return modf(x.m_value, &y->m_value);
 }
+
+gc_obj_ref<MulticastDelegate> MulticastDelegate::_s_CreateDelegateLike(gc_obj_ref<MulticastDelegate> delegate, gc_obj_ref<SZArray_1<Delegate>> invocationList)
+{
+    auto d_len = invocationList->length();
+    if (d_len == 0)
+    {
+        return null;
+    }
+    else if (d_len == 1)
+    {
+        return invocationList->get(0).cast<MulticastDelegate>();
+    }
+    else
+    {
+        auto d = gc_alloc(*delegate.header().vtable_, sizeof(MulticastDelegate)).cast<MulticastDelegate>();
+        d->_invocationList = invocationList;
+        return d;
+    }
+}

+ 9 - 0
src/Native/natsu.typedef.h

@@ -1,5 +1,6 @@
 // natsu clr runtime
 #pragma once
+#include <array>
 #include <cassert>
 #include <cstdint>
 #include <cstring>
@@ -68,6 +69,14 @@ inline constexpr int64_t to_int64(uint64_t value) noexcept
     return static_cast<const int64_t &>(value);
 }
 
+template <class T, size_t N>
+constexpr T bit_init(std::array<uint8_t, N> value) noexcept
+{
+    T t;
+    std::memcpy(&t, value.data(), sizeof(t));
+    return t;
+}
+
 inline constexpr void nop() noexcept
 {
 }

+ 31 - 5
src/Natsu.Compiler/Program.cs

@@ -18,7 +18,7 @@ namespace Natsu.Compiler
             @"..\..\..\..\..\out\bin\netcoreapp3.0\Chino.Core.dll",
             //@"..\..\..\..\..\out\bin\netcoreapp3.0\Chino.Chip.K210.dll",
             @"..\..\..\..\..\out\bin\netcoreapp3.0\Chino.Chip.Emulator.dll",
-            //@"..\..\..\..\..\out\bin\netcoreapp3.0\System.Private.CorLib.dll",
+            @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Private.CorLib.dll",
             @"..\..\..\..\..\out\bin\netcoreapp3.0\System.Runtime.dll",
             //@"..\..\..\..\..\out\bin\netcoreapp3.0\System.Diagnostics.Debug.dll",
             //@"..\..\..\..\..\out\bin\netcoreapp3.0\System.Runtime.InteropServices.dll",
@@ -250,7 +250,6 @@ namespace Natsu.Compiler
                     case ElementType.ByRef:
                     case ElementType.Ptr:
                     case ElementType.CModReqd:
-                    case ElementType.GenericInst:
                     case ElementType.Object:
                     case ElementType.Class:
                         break;
@@ -280,6 +279,14 @@ namespace Natsu.Compiler
                     case ElementType.SZArray:
                         AddTypeRef(declareDesc, _szArrayType.TypeDef, force);
                         break;
+                    case ElementType.GenericInst:
+                        {
+                            var sig = cntSig.ToGenericInstSig();
+                            AddTypeRef(declareDesc, sig.GenericType, force);
+                            foreach (var arg in sig.GenericArguments)
+                                AddTypeRef(declareDesc, arg, force);
+                            break;
+                        }
                     default:
                         throw new NotSupportedException();
                 }
@@ -373,7 +380,10 @@ namespace Natsu.Compiler
                 writer.Ident(ident).Write($"template <{string.Join(", ", typeNames)}> ");
             }
 
-            writer.Ident(ident).Write($"struct {type.Name};");
+            if (type.TypeDef.IsExplicitLayout)
+                writer.Ident(ident).Write($"union {type.Name};");
+            else
+                writer.Ident(ident).Write($"struct {type.Name};");
 
             foreach (var ns in nss)
                 writer.Write(" }");
@@ -411,7 +421,10 @@ namespace Natsu.Compiler
                 writer.Ident(ident).WriteLine($"template <{string.Join(", ", typeNames)}> ");
             }
 
-            writer.Ident(ident).Write($"struct {type.Name}");
+            if (type.TypeDef.IsExplicitLayout)
+                writer.Ident(ident).Write($"union {type.Name}");
+            else
+                writer.Ident(ident).Write($"struct {type.Name}");
             if (!type.TypeDef.IsValueType)
             {
                 var baseType = GetBaseType(type.TypeDef);
@@ -600,8 +613,21 @@ namespace Natsu.Compiler
             string prefix = string.Empty;
             if (value.IsStatic && !isStatic)
                 prefix = "static ";
+            bool isExplicit = value.DeclaringType.IsExplicitLayout;
 
-            writer.Ident(ident).WriteLine($"{prefix}{TypeUtils.EscapeVariableTypeName(value.FieldType, value.DeclaringType)} {TypeUtils.EscapeIdentifier(value.Name)};");
+            writer.Ident(ident);
+            if (isExplicit)
+                writer.Write("struct { ");
+            if (value.FieldOffset is uint offset && offset != 0)
+                writer.Write($"uint8_t pad_{value.Rid}_[{offset}]; ");
+
+            writer.Write($"{prefix}{TypeUtils.EscapeVariableTypeName(value.FieldType, value.DeclaringType)} {TypeUtils.EscapeIdentifier(value.Name)}");
+            if (value.InitialValue != null)
+                writer.Write($" = natsu::bit_init<{TypeUtils.EscapeVariableTypeName(value.FieldType, value.DeclaringType)}, {value.InitialValue.Length}>({{{string.Join(", ", value.InitialValue)}}})");
+            writer.Write(';');
+            if (isExplicit)
+                writer.Write(" };");
+            writer.WriteLine();
         }
 
         private void WriteConstantStringField(StreamWriter writer, int ident, FieldDef value)

+ 2 - 0
src/Natsu.Compiler/TypeUtils.cs

@@ -493,6 +493,8 @@ namespace Natsu.Compiler
                 throw new ArgumentException("Invalid identifier");
 
             var sb = new StringBuilder();
+            if (char.IsDigit(name[0]))
+                sb.Append('_');
             foreach (var c in name)
             {
                 if (char.IsLetterOrDigit(c) || c == '_')

+ 5 - 0
src/Natsu.sln

@@ -5,10 +5,15 @@ VisualStudioVersion = 16.0.29021.104
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Natsu.Compiler", "Natsu.Compiler\Natsu.Compiler.csproj", "{AC6248DB-5117-4C47-A3E7-D0B7E6258F6B}"
 	ProjectSection(ProjectDependencies) = postProject
+		{CE15C733-B010-41FD-B1B6-D0D4327F0653} = {CE15C733-B010-41FD-B1B6-D0D4327F0653}
 		{A4CE4A60-2E56-4712-901B-8E5FA1354FD6} = {A4CE4A60-2E56-4712-901B-8E5FA1354FD6}
+		{20176878-F31F-4D92-918C-9FD58CDD7480} = {20176878-F31F-4D92-918C-9FD58CDD7480}
 		{7374FA7F-5B76-40C8-8F8F-EB9CA53A8C2F} = {7374FA7F-5B76-40C8-8F8F-EB9CA53A8C2F}
+		{C4408589-968A-43B3-83A6-AF02D1BAA293} = {C4408589-968A-43B3-83A6-AF02D1BAA293}
 		{FE1EA2B2-2DF7-4570-9710-AF31D00DF2F6} = {FE1EA2B2-2DF7-4570-9710-AF31D00DF2F6}
 		{2C03C9E9-3EE6-4F74-8BF7-C9040E9E4A14} = {2C03C9E9-3EE6-4F74-8BF7-C9040E9E4A14}
+		{D261F7EB-5A98-4D47-B34B-D13B27A6988E} = {D261F7EB-5A98-4D47-B34B-D13B27A6988E}
+		{E7F325FF-7C85-4C64-8E96-E06AE3A5238D} = {E7F325FF-7C85-4C64-8E96-E06AE3A5238D}
 	EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Private.CorLib", "System.Private.CorLib\System.Private.CorLib.csproj", "{CE15C733-B010-41FD-B1B6-D0D4327F0653}"

+ 1 - 0
src/System.Private.CorLib/System.Private.CorLib.csproj

@@ -12,6 +12,7 @@
     <AssemblyOriginatorKeyFile>$(SolutionDir)../tools/Open.snk</AssemblyOriginatorKeyFile>
     <AssemblyVersion>4.2.1.0</AssemblyVersion>
     <EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
+    <Nullable>Enable</Nullable>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
     <NoWarn>1701;1702;0169</NoWarn>

+ 85 - 5
src/System.Private.CorLib/System/Delegate.cs

@@ -1,9 +1,11 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
 
 namespace System
 {
-    public abstract class Delegate
+    public abstract class Delegate : ICloneable
     {
         // _target is the object we will invoke on
         internal object _target;
@@ -17,13 +19,91 @@ namespace System
         {
         }
 
-        public static Delegate Combine(Delegate a, Delegate b)
+        public virtual object Clone() => MemberwiseClone();
+
+        [return: NotNullIfNotNull("a")]
+        [return: NotNullIfNotNull("b")]
+        public static Delegate? Combine(Delegate? a, Delegate? b)
         {
-            if (a == null) return b;
-            if (b == null) return a;
+            if (a is null)
+                return b;
+            if (b is null)
+                return a;
+
             return a.CombineImpl(b);
         }
 
-        protected abstract Delegate CombineImpl(Delegate newDelegate);
+        public static Delegate? Combine(params Delegate?[]? delegates)
+        {
+            if (delegates == null || delegates.Length == 0)
+                return null;
+
+            Delegate? d = delegates[0];
+            for (int i = 1; i < delegates.Length; i++)
+                d = Combine(d, delegates[i]);
+
+            return d;
+        }
+
+        protected virtual Delegate CombineImpl(Delegate d) => throw new MulticastNotSupportedException(SR.Multicast_Combine);
+
+        protected virtual Delegate? RemoveImpl(Delegate d) => d.Equals(this) ? null : this;
+
+        public virtual Delegate[] GetInvocationList() => new Delegate[] { this };
+
+        public static Delegate? Remove(Delegate? source, Delegate? value)
+        {
+            if (source == null)
+                return null;
+
+            if (value == null)
+                return source;
+
+            return source.RemoveImpl(value);
+        }
+
+        public static Delegate? RemoveAll(Delegate? source, Delegate? value)
+        {
+            Delegate? newDelegate;
+
+            do
+            {
+                newDelegate = source;
+                source = Remove(source, value);
+            }
+            while (newDelegate != source);
+
+            return newDelegate;
+        }
+
+        // Force inline as the true/false ternary takes it above ALWAYS_INLINE size even though the asm ends up smaller
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool operator ==(Delegate? d1, Delegate? d2)
+        {
+            // Test d2 first to allow branch elimination when inlined for null checks (== null)
+            // so it can become a simple test
+            if (d2 is null)
+            {
+                // return true/false not the test result https://github.com/dotnet/coreclr/issues/914
+                return (d1 is null) ? true : false;
+            }
+
+            return ReferenceEquals(d2, d1) ? true : d2.Equals((object?)d1);
+        }
+
+        // Force inline as the true/false ternary takes it above ALWAYS_INLINE size even though the asm ends up smaller
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool operator !=(Delegate? d1, Delegate? d2)
+        {
+            // Test d2 first to allow branch elimination when inlined for not null checks (!= null)
+            // so it can become a simple test
+            if (d2 is null)
+            {
+                // return true/false not the test result https://github.com/dotnet/coreclr/issues/914
+                return (d1 is null) ? false : true;
+            }
+
+            return ReferenceEquals(d2, d1) ? false : !d2.Equals(d1);
+        }
     }
 }

+ 64 - 1
src/System.Private.CorLib/System/MulticastDelegate.cs

@@ -17,7 +17,7 @@ namespace System
         // 1. Multicast delegate
         // 2. Secure/Wrapper delegate
         // 3. Inner delegate of secure delegate where the secure delegate security context is a collectible method
-        private Delegate[] _invocationList;
+        private Delegate[]? _invocationList;
 
         protected override Delegate CombineImpl(Delegate newDelegate)
         {
@@ -32,6 +32,69 @@ namespace System
             return CreateDelegateLike(this, newList);
         }
 
+        protected override Delegate? RemoveImpl(Delegate d)
+        {
+            var b = (MulticastDelegate)d;
+            var bList = b._invocationList;
+            var aList = _invocationList;
+            var aNum = aList == null ? 1 : aList.Length;
+            var bNum = bList == null ? 1 : bList.Length;
+
+            if (aNum == 1)
+            {
+                if (bNum == 1)
+                {
+                    if (ReferenceEquals(this, b))
+                        return null;
+                }
+
+                return this;
+            }
+            else
+            {
+                if (bNum == 1)
+                {
+                    for (int i = aNum - 1; i >= 0; i--)
+                    {
+                        if (aList[i] == d)
+                        {
+                            var newList = new Delegate[aNum - 1];
+                            Array.Copy(aList, 0, newList, 0, i);
+                            if (i != aNum - 1)
+                                Array.Copy(aList, i + 1, newList, i, aNum - i);
+                            return CreateDelegateLike(this, newList);
+                        }
+                    }
+                }
+                else
+                {
+                    for (int i = aNum - bNum; i >= 0; i--)
+                    {
+                        bool equals = true;
+                        for (int j = 0; j < bNum; j++)
+                        {
+                            if (aList[i + j] != bList[j])
+                            {
+                                equals = false;
+                                break;
+                            }
+                        }
+
+                        if (equals)
+                        {
+                            var newList = new Delegate[aNum - bNum];
+                            Array.Copy(aList, 0, newList, 0, i);
+                            if (i != aNum - bNum)
+                                Array.Copy(aList, i + bNum, newList, i, aNum - bNum);
+                            return CreateDelegateLike(this, newList);
+                        }
+                    }
+                }
+            }
+
+            return this;
+        }
+
         private void CopyDelegate(int num, Delegate src, Delegate[] srcList, Delegate[] destList, int offset)
         {
             if (num == 1)

+ 36 - 0
src/System.Private.CorLib/System/MulticastNotSupportedException.cs

@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+////////////////////////////////////////////////////////////////////////////////
+// MulticastNotSupportedException
+// This is thrown when you add multiple callbacks to a non-multicast delegate.
+////////////////////////////////////////////////////////////////////////////////
+
+using System.Runtime.Serialization;
+
+namespace System
+{
+    [Serializable]
+    [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
+    public sealed class MulticastNotSupportedException : SystemException
+    {
+        public MulticastNotSupportedException()
+            : base(SR.Arg_MulticastNotSupportedException)
+        {
+            HResult = HResults.COR_E_MULTICASTNOTSUPPORTED;
+        }
+
+        public MulticastNotSupportedException(string? message)
+            : base(message)
+        {
+            HResult = HResults.COR_E_MULTICASTNOTSUPPORTED;
+        }
+
+        public MulticastNotSupportedException(string? message, Exception? inner)
+            : base(message, inner)
+        {
+            HResult = HResults.COR_E_MULTICASTNOTSUPPORTED;
+        }
+    }
+}