diff --git a/Compilation/RuntimeEmitter.Arrays.Mutators.cs b/Compilation/RuntimeEmitter.Arrays.Mutators.cs index e15b88f7..0ebd33d2 100644 --- a/Compilation/RuntimeEmitter.Arrays.Mutators.cs +++ b/Compilation/RuntimeEmitter.Arrays.Mutators.cs @@ -905,8 +905,7 @@ private void EmitArraySort(TypeBuilder typeBuilder, EmittedRuntime runtime) // Check frozen ONLY (sealed/non-extensible allows reordering, no length change) EmitArrayFrozenSealedCheck(il, runtime, frozenLabel, checkSealed: false, checkExtensible: false); - // Use a simple in-place insertion sort for stability - // This is efficient enough for typical use cases and guarantees stability + // Stable bottom-up merge sort (Θ(n log n)) — see EmitSortBodyOnLocal (#877). EmitSortBody(il, runtime, mutateInPlace: true); // Frozen return path - return unchanged list @@ -943,7 +942,7 @@ private void EmitArrayToSorted(TypeBuilder typeBuilder, EmittedRuntime runtime) } /// - /// Emits the body of the sort algorithm (stable insertion sort). + /// Emits the body of the sort algorithm (stable bottom-up merge sort, Θ(n log n)). /// When mutateInPlace is true, sorts arg0 and returns arg0. /// private void EmitSortBody(ILGenerator il, EmittedRuntime runtime, bool mutateInPlace) @@ -970,7 +969,6 @@ private void EmitSortBodyOnLocal(ILGenerator il, EmittedRuntime runtime, LocalBu var undefinedCountLocal = il.DeclareLocal(_types.Int32); // Count of undefined elements var iLocal = il.DeclareLocal(_types.Int32); var jLocal = il.DeclareLocal(_types.Int32); - var tempLocal = il.DeclareLocal(_types.Object); var compareResultLocal = il.DeclareLocal(_types.Int32); var str1Local = il.DeclareLocal(_types.String); var str2Local = il.DeclareLocal(_types.String); @@ -1043,227 +1041,370 @@ private void EmitSortBodyOnLocal(ILGenerator il, EmittedRuntime runtime, LocalBu il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Count").GetGetMethod()!); il.Emit(OpCodes.Blt, partitionLoopStart); - // === Phase 2: Sort defined elements (stable insertion sort) === - // for (i = 1; i < defined.Count; i++) - // for (j = i; j > 0 && Compare(defined[j-1], defined[j]) > 0; j--) - // Swap(defined, j-1, j) + // === Phase 2: Sort defined elements — stable bottom-up merge sort (Θ(n log n), #877) === + // Replaces the prior in-place insertion sort (Θ(n²)), which made compiled + // sort slower than the interpreter on large inputs. Merge sort is stable: + // on a tie the LEFT run's element (smaller original index) is taken first. + // The per-pair comparison is identical to the old one — a custom compareFn + // (double → sign; NaN/non-number ⇒ 0/equal) or, when the comparator is + // absent/undefined, the ECMA-262 default ToJsString/CompareOrdinal. Pure IL, + // no SharpTS.dll dependency. Ping-pongs between two object[] buffers. + var nLocal = il.DeclareLocal(_types.Int32); // defined.Count + var srcLocal = il.DeclareLocal(_types.ObjectArray); // current source buffer + var dstLocal = il.DeclareLocal(_types.ObjectArray); // merge destination buffer + var swapArrLocal = il.DeclareLocal(_types.ObjectArray); + var widthLocal = il.DeclareLocal(_types.Int32); + var loLocal = il.DeclareLocal(_types.Int32); + var midLocal = il.DeclareLocal(_types.Int32); + var hiLocal = il.DeclareLocal(_types.Int32); + var kLocal = il.DeclareLocal(_types.Int32); + + // n = defined.Count + il.Emit(OpCodes.Ldloc, definedLocal); + il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Count").GetGetMethod()!); + il.Emit(OpCodes.Stloc, nLocal); + // src = new object[n]; defined.CopyTo(src); dst = new object[n] + il.Emit(OpCodes.Ldloc, nLocal); + il.Emit(OpCodes.Newarr, _types.Object); + il.Emit(OpCodes.Stloc, srcLocal); + il.Emit(OpCodes.Ldloc, definedLocal); + il.Emit(OpCodes.Ldloc, srcLocal); + il.Emit(OpCodes.Callvirt, _types.GetMethod(_types.ListOfObject, "CopyTo", _types.ObjectArray)); + il.Emit(OpCodes.Ldloc, nLocal); + il.Emit(OpCodes.Newarr, _types.Object); + il.Emit(OpCodes.Stloc, dstLocal); + + var widthCond = il.DefineLabel(); + var widthBody = il.DefineLabel(); + var loCond = il.DefineLabel(); + var loBody = il.DefineLabel(); + var loNext = il.DefineLabel(); + var midAdd = il.DefineLabel(); + var midDone = il.DefineLabel(); + var hiAdd = il.DefineLabel(); + var hiDone = il.DefineLabel(); + var widthDouble = il.DefineLabel(); + var mergeCond = il.DefineLabel(); + var mergeBody = il.DefineLabel(); + var takeRight = il.DefineLabel(); + var afterTake = il.DefineLabel(); + var drainLeftCond = il.DefineLabel(); + var drainLeftBody = il.DefineLabel(); + var drainRightCond = il.DefineLabel(); + var drainRightBody = il.DefineLabel(); + var wbCond = il.DefineLabel(); + var wbBody = il.DefineLabel(); + + // for (width = 1; width < n; width *= 2) il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Stloc, iLocal); - - var outerLoopStart = il.DefineLabel(); - var outerLoopCondition = il.DefineLabel(); - var innerLoopStart = il.DefineLabel(); - var innerLoopCondition = il.DefineLabel(); - var incrementI = il.DefineLabel(); + il.Emit(OpCodes.Stloc, widthLocal); + il.Emit(OpCodes.Br, widthCond); - il.Emit(OpCodes.Br, outerLoopCondition); + il.MarkLabel(widthBody); - // Outer loop body - il.MarkLabel(outerLoopStart); - - // j = i - il.Emit(OpCodes.Ldloc, iLocal); - il.Emit(OpCodes.Stloc, jLocal); - - il.Emit(OpCodes.Br, innerLoopCondition); - - // Inner loop body - swap if needed - il.MarkLabel(innerLoopStart); + // for (lo = 0; lo < n; lo += 2*width) + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Stloc, loLocal); + il.Emit(OpCodes.Br, loCond); - // Swap defined[j-1] and defined[j] - // temp = defined[j] - il.Emit(OpCodes.Ldloc, definedLocal); - il.Emit(OpCodes.Ldloc, jLocal); - il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Item").GetGetMethod()!); - il.Emit(OpCodes.Stloc, tempLocal); + il.MarkLabel(loBody); - // defined[j] = defined[j-1] - il.Emit(OpCodes.Ldloc, definedLocal); - il.Emit(OpCodes.Ldloc, jLocal); - il.Emit(OpCodes.Ldloc, definedLocal); - il.Emit(OpCodes.Ldloc, jLocal); - il.Emit(OpCodes.Ldc_I4_1); + // mid = min(lo + width, n) — overflow-safe: if width >= n - lo then n else lo + width. + // (n - lo >= 0 always; lo + width is only computed when it is < n, so it can't overflow.) + il.Emit(OpCodes.Ldloc, widthLocal); + il.Emit(OpCodes.Ldloc, nLocal); + il.Emit(OpCodes.Ldloc, loLocal); il.Emit(OpCodes.Sub); - il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Item").GetGetMethod()!); - il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Item").GetSetMethod()!); + il.Emit(OpCodes.Blt, midAdd); + il.Emit(OpCodes.Ldloc, nLocal); + il.Emit(OpCodes.Stloc, midLocal); + il.Emit(OpCodes.Br, midDone); + il.MarkLabel(midAdd); + il.Emit(OpCodes.Ldloc, loLocal); + il.Emit(OpCodes.Ldloc, widthLocal); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, midLocal); + il.MarkLabel(midDone); - // defined[j-1] = temp - il.Emit(OpCodes.Ldloc, definedLocal); - il.Emit(OpCodes.Ldloc, jLocal); - il.Emit(OpCodes.Ldc_I4_1); + // hi = min(mid + width, n) = min(lo + 2*width, n) — overflow-safe via n - mid. + il.Emit(OpCodes.Ldloc, widthLocal); + il.Emit(OpCodes.Ldloc, nLocal); + il.Emit(OpCodes.Ldloc, midLocal); il.Emit(OpCodes.Sub); - il.Emit(OpCodes.Ldloc, tempLocal); - il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Item").GetSetMethod()!); + il.Emit(OpCodes.Blt, hiAdd); + il.Emit(OpCodes.Ldloc, nLocal); + il.Emit(OpCodes.Stloc, hiLocal); + il.Emit(OpCodes.Br, hiDone); + il.MarkLabel(hiAdd); + il.Emit(OpCodes.Ldloc, midLocal); + il.Emit(OpCodes.Ldloc, widthLocal); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, hiLocal); + il.MarkLabel(hiDone); - // j-- - il.Emit(OpCodes.Ldloc, jLocal); - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Sub); + // i = lo; j = mid; k = lo + il.Emit(OpCodes.Ldloc, loLocal); + il.Emit(OpCodes.Stloc, iLocal); + il.Emit(OpCodes.Ldloc, midLocal); il.Emit(OpCodes.Stloc, jLocal); + il.Emit(OpCodes.Ldloc, loLocal); + il.Emit(OpCodes.Stloc, kLocal); - // Inner loop condition: j > 0 && Compare(defined[j-1], defined[j]) > 0 - il.MarkLabel(innerLoopCondition); + il.Emit(OpCodes.Br, mergeCond); - // Check j > 0 - il.Emit(OpCodes.Ldloc, jLocal); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ble, incrementI); + // --- merge body: compare src[i] vs src[j] -> compareResultLocal --- + il.MarkLabel(mergeBody); - // Check compareFn (arg1) — must accept null AND $Undefined.Instance as - // "no comparator" per ECMA-262. `arr.sort(undefined)` passed - // $Undefined.Instance through which is non-null → Brtrue used to - // route to InvokeValue(undefined) and silently returned garbage. var hasCompareFn = il.DefineLabel(); var noCompareFn = il.DefineLabel(); var checkCompareResult = il.DefineLabel(); + // compareFn is "absent" when null OR $Undefined.Instance (ECMA-262): + // `arr.sort(undefined)` must use the default comparator, not invoke undefined. il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Brfalse, noCompareFn); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Isinst, runtime.UndefinedType); il.Emit(OpCodes.Brtrue, noCompareFn); il.Emit(OpCodes.Br, hasCompareFn); - il.MarkLabel(noCompareFn); - // Default comparison: ToString(a).CompareTo(ToString(b)) per ECMA-262 - // 23.1.3.30: SortCompare uses ToString which invokes valueOf/toString. - // Stringify produces a literal representation for objects (used by - // template literals + Array.toString) — that diverges from spec for - // objects with custom toString. ToJsString invokes the ToPrimitive - // protocol so `{toString: () => "-2"}` sorts as "-2". - // str1 = ToJsString(defined[j-1]) - il.Emit(OpCodes.Ldloc, definedLocal); - il.Emit(OpCodes.Ldloc, jLocal); - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Sub); - il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Item").GetGetMethod()!); + il.MarkLabel(noCompareFn); + // Default: CompareOrdinal(ToJsString(src[i]), ToJsString(src[j])). ToJsString + // runs the ToPrimitive protocol so `{toString:()=>"-2"}` sorts as "-2". + il.Emit(OpCodes.Ldloc, srcLocal); + il.Emit(OpCodes.Ldloc, iLocal); + il.Emit(OpCodes.Ldelem_Ref); il.Emit(OpCodes.Call, runtime.ToJsString); il.Emit(OpCodes.Stloc, str1Local); - - // str2 = ToJsString(defined[j]) - il.Emit(OpCodes.Ldloc, definedLocal); + il.Emit(OpCodes.Ldloc, srcLocal); il.Emit(OpCodes.Ldloc, jLocal); - il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Item").GetGetMethod()!); + il.Emit(OpCodes.Ldelem_Ref); il.Emit(OpCodes.Call, runtime.ToJsString); il.Emit(OpCodes.Stloc, str2Local); - - // compareResult = String.CompareOrdinal(str1, str2) il.Emit(OpCodes.Ldloc, str1Local); il.Emit(OpCodes.Ldloc, str2Local); il.Emit(OpCodes.Call, typeof(string).GetMethod("CompareOrdinal", [typeof(string), typeof(string)])!); il.Emit(OpCodes.Stloc, compareResultLocal); il.Emit(OpCodes.Br, checkCompareResult); - // Custom compareFn: call InvokeValue(compareFn, [a, b]) il.MarkLabel(hasCompareFn); - - // Build args array [defined[j-1], defined[j]] + // result = InvokeValue(compareFn, new object[] { src[i], src[j] }) il.Emit(OpCodes.Ldc_I4_2); il.Emit(OpCodes.Newarr, _types.Object); - - // args[0] = defined[j-1] il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ldloc, definedLocal); - il.Emit(OpCodes.Ldloc, jLocal); - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Sub); - il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Item").GetGetMethod()!); + il.Emit(OpCodes.Ldloc, srcLocal); + il.Emit(OpCodes.Ldloc, iLocal); + il.Emit(OpCodes.Ldelem_Ref); il.Emit(OpCodes.Stelem_Ref); - - // args[1] = defined[j] il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Ldloc, definedLocal); + il.Emit(OpCodes.Ldloc, srcLocal); il.Emit(OpCodes.Ldloc, jLocal); - il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Item").GetGetMethod()!); + il.Emit(OpCodes.Ldelem_Ref); il.Emit(OpCodes.Stelem_Ref); - var argsLocal = il.DeclareLocal(_types.ObjectArray); il.Emit(OpCodes.Stloc, argsLocal); - - // result = InvokeValue(compareFn, args) il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldloc, argsLocal); il.Emit(OpCodes.Call, runtime.InvokeValue); - // Convert result to int comparison value - // If result is double, use sign; if 0 or NaN, don't swap (stability) + // Convert compare result to a sign in compareResultLocal: + // double -> sign (<0 / 0 / >0); NaN or non-double -> 0 (equal -> take left -> stable). var resultIsNotDouble = il.DefineLabel(); + var notNaN = il.DefineLabel(); + var isZero = il.DefineLabel(); + var isPositive = il.DefineLabel(); var resultLocal = il.DeclareLocal(_types.Object); + var doubleResultLocal = il.DeclareLocal(_types.Double); il.Emit(OpCodes.Stloc, resultLocal); - il.Emit(OpCodes.Ldloc, resultLocal); il.Emit(OpCodes.Isinst, _types.Double); il.Emit(OpCodes.Brfalse, resultIsNotDouble); - - // It's a double - check if > 0 il.Emit(OpCodes.Ldloc, resultLocal); il.Emit(OpCodes.Unbox_Any, _types.Double); - var doubleResultLocal = il.DeclareLocal(_types.Double); il.Emit(OpCodes.Stloc, doubleResultLocal); - - // Check for NaN (NaN means no swap for stability) il.Emit(OpCodes.Ldloc, doubleResultLocal); il.Emit(OpCodes.Call, _types.DoubleIsNaN); - var notNaN = il.DefineLabel(); il.Emit(OpCodes.Brfalse, notNaN); - il.Emit(OpCodes.Ldc_I4_0); // NaN -> 0 (no swap) + il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Stloc, compareResultLocal); il.Emit(OpCodes.Br, checkCompareResult); - il.MarkLabel(notNaN); - // Convert double to int sign il.Emit(OpCodes.Ldloc, doubleResultLocal); il.Emit(OpCodes.Ldc_R8, 0.0); - var isZero = il.DefineLabel(); - var isPositive = il.DefineLabel(); il.Emit(OpCodes.Beq, isZero); - il.Emit(OpCodes.Ldloc, doubleResultLocal); il.Emit(OpCodes.Ldc_R8, 0.0); il.Emit(OpCodes.Bgt, isPositive); - - // Negative il.Emit(OpCodes.Ldc_I4_M1); il.Emit(OpCodes.Stloc, compareResultLocal); il.Emit(OpCodes.Br, checkCompareResult); - il.MarkLabel(isPositive); il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Stloc, compareResultLocal); il.Emit(OpCodes.Br, checkCompareResult); - il.MarkLabel(isZero); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Stloc, compareResultLocal); il.Emit(OpCodes.Br, checkCompareResult); - il.MarkLabel(resultIsNotDouble); - // Not a double - treat as 0 (no swap) il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Stloc, compareResultLocal); il.MarkLabel(checkCompareResult); - // If compareResult > 0, swap (continue inner loop) + // compareResult > 0 => src[i] sorts AFTER src[j] => take right; else take left (stable). il.Emit(OpCodes.Ldloc, compareResultLocal); il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Bgt, innerLoopStart); + il.Emit(OpCodes.Bgt, takeRight); - il.MarkLabel(incrementI); - // i++ + // dst[k] = src[i]; i++ + il.Emit(OpCodes.Ldloc, dstLocal); + il.Emit(OpCodes.Ldloc, kLocal); + il.Emit(OpCodes.Ldloc, srcLocal); + il.Emit(OpCodes.Ldloc, iLocal); + il.Emit(OpCodes.Ldelem_Ref); + il.Emit(OpCodes.Stelem_Ref); il.Emit(OpCodes.Ldloc, iLocal); il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Add); il.Emit(OpCodes.Stloc, iLocal); + il.Emit(OpCodes.Br, afterTake); + + il.MarkLabel(takeRight); + // dst[k] = src[j]; j++ + il.Emit(OpCodes.Ldloc, dstLocal); + il.Emit(OpCodes.Ldloc, kLocal); + il.Emit(OpCodes.Ldloc, srcLocal); + il.Emit(OpCodes.Ldloc, jLocal); + il.Emit(OpCodes.Ldelem_Ref); + il.Emit(OpCodes.Stelem_Ref); + il.Emit(OpCodes.Ldloc, jLocal); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, jLocal); + + il.MarkLabel(afterTake); + // k++ + il.Emit(OpCodes.Ldloc, kLocal); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, kLocal); - // Outer loop condition: i < defined.Count - il.MarkLabel(outerLoopCondition); + il.MarkLabel(mergeCond); + // while (i < mid && j < hi) keep merging; otherwise drain. il.Emit(OpCodes.Ldloc, iLocal); + il.Emit(OpCodes.Ldloc, midLocal); + il.Emit(OpCodes.Bge, drainLeftCond); + il.Emit(OpCodes.Ldloc, jLocal); + il.Emit(OpCodes.Ldloc, hiLocal); + il.Emit(OpCodes.Blt, mergeBody); + // i=hi: fall through to drain the left run. + + // while (i < mid) dst[k++] = src[i++] + il.MarkLabel(drainLeftCond); + il.Emit(OpCodes.Ldloc, iLocal); + il.Emit(OpCodes.Ldloc, midLocal); + il.Emit(OpCodes.Bge, drainRightCond); + il.MarkLabel(drainLeftBody); + il.Emit(OpCodes.Ldloc, dstLocal); + il.Emit(OpCodes.Ldloc, kLocal); + il.Emit(OpCodes.Ldloc, srcLocal); + il.Emit(OpCodes.Ldloc, iLocal); + il.Emit(OpCodes.Ldelem_Ref); + il.Emit(OpCodes.Stelem_Ref); + il.Emit(OpCodes.Ldloc, iLocal); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, iLocal); + il.Emit(OpCodes.Ldloc, kLocal); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, kLocal); + il.Emit(OpCodes.Ldloc, iLocal); + il.Emit(OpCodes.Ldloc, midLocal); + il.Emit(OpCodes.Blt, drainLeftBody); + + // while (j < hi) dst[k++] = src[j++] + il.MarkLabel(drainRightCond); + il.Emit(OpCodes.Ldloc, jLocal); + il.Emit(OpCodes.Ldloc, hiLocal); + il.Emit(OpCodes.Bge, loNext); + il.MarkLabel(drainRightBody); + il.Emit(OpCodes.Ldloc, dstLocal); + il.Emit(OpCodes.Ldloc, kLocal); + il.Emit(OpCodes.Ldloc, srcLocal); + il.Emit(OpCodes.Ldloc, jLocal); + il.Emit(OpCodes.Ldelem_Ref); + il.Emit(OpCodes.Stelem_Ref); + il.Emit(OpCodes.Ldloc, jLocal); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, jLocal); + il.Emit(OpCodes.Ldloc, kLocal); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, kLocal); + il.Emit(OpCodes.Ldloc, jLocal); + il.Emit(OpCodes.Ldloc, hiLocal); + il.Emit(OpCodes.Blt, drainRightBody); + + il.MarkLabel(loNext); + // lo += 2*width. hi already equals min(lo + 2*width, n) computed overflow-safe, + // so advancing lo to hi is the same step and saturates at n (no overflow). + il.Emit(OpCodes.Ldloc, hiLocal); + il.Emit(OpCodes.Stloc, loLocal); + il.MarkLabel(loCond); + il.Emit(OpCodes.Ldloc, loLocal); + il.Emit(OpCodes.Ldloc, nLocal); + il.Emit(OpCodes.Blt, loBody); + + // swap src <-> dst (this pass's output becomes next pass's input) + il.Emit(OpCodes.Ldloc, srcLocal); + il.Emit(OpCodes.Stloc, swapArrLocal); + il.Emit(OpCodes.Ldloc, dstLocal); + il.Emit(OpCodes.Stloc, srcLocal); + il.Emit(OpCodes.Ldloc, swapArrLocal); + il.Emit(OpCodes.Stloc, dstLocal); + + // width *= 2, saturating: if doubling would overflow int, clamp to int.MaxValue + // (which is >= n, so the loop then exits) instead of wrapping to a negative width. + il.Emit(OpCodes.Ldloc, widthLocal); + il.Emit(OpCodes.Ldc_I4, int.MaxValue / 2); + il.Emit(OpCodes.Ble, widthDouble); + il.Emit(OpCodes.Ldc_I4, int.MaxValue); + il.Emit(OpCodes.Stloc, widthLocal); + il.Emit(OpCodes.Br, widthCond); + il.MarkLabel(widthDouble); + il.Emit(OpCodes.Ldloc, widthLocal); + il.Emit(OpCodes.Ldc_I4_2); + il.Emit(OpCodes.Mul); + il.Emit(OpCodes.Stloc, widthLocal); + il.MarkLabel(widthCond); + il.Emit(OpCodes.Ldloc, widthLocal); + il.Emit(OpCodes.Ldloc, nLocal); + il.Emit(OpCodes.Blt, widthBody); + + // Write the sorted result (now in src) back into defined: defined[k] = src[k] + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Stloc, kLocal); + il.Emit(OpCodes.Br, wbCond); + il.MarkLabel(wbBody); il.Emit(OpCodes.Ldloc, definedLocal); - il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Count").GetGetMethod()!); - il.Emit(OpCodes.Blt, outerLoopStart); + il.Emit(OpCodes.Ldloc, kLocal); + il.Emit(OpCodes.Ldloc, srcLocal); + il.Emit(OpCodes.Ldloc, kLocal); + il.Emit(OpCodes.Ldelem_Ref); + il.Emit(OpCodes.Callvirt, _types.GetProperty(_types.ListOfObject, "Item").GetSetMethod()!); + il.Emit(OpCodes.Ldloc, kLocal); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, kLocal); + il.MarkLabel(wbCond); + il.Emit(OpCodes.Ldloc, kLocal); + il.Emit(OpCodes.Ldloc, nLocal); + il.Emit(OpCodes.Blt, wbBody); // === Phase 3: Rebuild original list with sorted defined + undefined at end === // list.Clear()