Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Compilation/EmittedRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1586,6 +1586,10 @@ public class EmittedRuntime
public MethodBuilder TypedArrayGetBuffer { get; set; } = null!;
public MethodBuilder TypedArrayElementGet { get; set; } = null!;
public MethodBuilder TypedArrayElementSet { get; set; } = null!;
// Unboxed Float64Array element accessors (#878) — direct double get/set on the
// concrete $Float64Array, bypassing the boxed Get/Set + GetIndex dispatch.
public MethodBuilder Float64ArrayGetUnboxed { get; set; } = null!;
public MethodBuilder Float64ArraySetUnboxed { get; set; } = null!;

// Concrete TypedArray types (pure-IL for standalone DLLs)
public TypeBuilder Int8ArrayType { get; set; } = null!;
Expand Down
52 changes: 52 additions & 0 deletions Compilation/ILEmitter.Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,25 @@ protected override void EmitGetIndex(Expr.GetIndex gi)
return;
}

// Float64Array fast path (#878): when the receiver is a variable statically typed
// Float64Array, read the element UNBOXED via $Float64Array.GetUnboxed → native double
// on the stack. Eliminates the Runtime.GetIndex dispatch, the GetTypedArrayElement
// isinst/castclass, the virtual Get, and the per-element box. Out-of-range access
// faults via BitConverter exactly as the boxed path does today (OOB semantics
// unchanged). Receiver is a side-effect-free variable, so it is loaded once.
if (!gi.Optional && gi.Object is Expr.Variable
&& _ctx.TypeMap?.Get(gi.Object) is TypeInfo.TypedArray { ElementType: "Float64" })
{
EmitExpression(gi.Object);
EnsureBoxed();
IL.Emit(OpCodes.Castclass, _ctx.Runtime!.Float64ArrayType);
EmitExpressionAsDouble(gi.Index);
IL.Emit(OpCodes.Conv_I4);
IL.Emit(OpCodes.Callvirt, _ctx.Runtime!.Float64ArrayGetUnboxed);
SetStackType(StackType.Double);
return;
}

// Descriptor-driven fast path: when receiver is statically known to be an array,
// emit direct List<T> access — skips runtime type dispatch,
// index boxing, and Convert.ToInt32(object) overhead.
Expand Down Expand Up @@ -1041,6 +1060,39 @@ protected override void EmitSetIndex(Expr.SetIndex si)
return;
}

// Float64Array fast path (#878): variable statically typed Float64Array with a
// statically-numeric RHS — write the element UNBOXED via $Float64Array.SetUnboxed.
// Eliminates the Runtime.SetIndex dispatch, the isinst, the value box, and the
// Convert.ToDouble coercion. A non-numeric RHS falls through to the boxed path,
// which performs JS ToNumber coercion. OOB faults exactly as the boxed path does.
// Evaluate index then value (the receiver var is side-effect-free).
if (si.Object is Expr.Variable
&& _ctx.TypeMap?.Get(si.Object) is TypeInfo.TypedArray { ElementType: "Float64" }
&& _ctx.TypeMap?.Get(si.Value) is TypeInfo.Primitive { Type: TokenType.TYPE_NUMBER })
{
EmitExpressionAsDouble(si.Index);
IL.Emit(OpCodes.Conv_I4);
var idxLocal = IL.DeclareLocal(_ctx.Types.Int32);
IL.Emit(OpCodes.Stloc, idxLocal);

EmitExpression(si.Value);
EnsureDouble();
var valLocal = IL.DeclareLocal(_ctx.Types.Double);
IL.Emit(OpCodes.Stloc, valLocal);

EmitExpression(si.Object);
EnsureBoxed();
IL.Emit(OpCodes.Castclass, _ctx.Runtime!.Float64ArrayType);
IL.Emit(OpCodes.Ldloc, idxLocal);
IL.Emit(OpCodes.Ldloc, valLocal);
IL.Emit(OpCodes.Callvirt, _ctx.Runtime!.Float64ArraySetUnboxed);

// Assignment expression result: the (unboxed) assigned value.
IL.Emit(OpCodes.Ldloc, valLocal);
SetStackType(StackType.Double);
return;
}

// Descriptor-driven fast path: when receiver is statically known to be an array,
// emit direct List<T> access with auto-extension — skips runtime type dispatch,
// index boxing, and Convert.ToInt32(object) overhead.
Expand Down
64 changes: 64 additions & 0 deletions Compilation/RuntimeEmitter.TSTypedArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -737,5 +737,69 @@ private void EmitTypedArrayIndexer(

setIl.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(setter, runtime.TypedArrayElementSet);

// Unboxed double accessors for Float64Array (#878). These mirror the byte
// logic of the boxed Get/Set above but take/return a native `double` — no
// Box, no Convert.ToDouble coercion. The IL emitter binds them directly at
// statically-known Float64Array index sites, eliminating the GetIndex/SetIndex
// dispatch, the isinst ladder, and the per-element box. Like Get/Set, they do
// NOT bounds-check: out-of-range access faults via BitConverter/Array.Copy,
// exactly as the boxed path does today (OOB semantics unchanged).
if (bytesPerElement == 8 && isFloat)
{
EmitFloat64UnboxedAccessors(typeBuilder, runtime);
}
}

// double GetUnboxed(int index) / void SetUnboxed(int index, double value) on $Float64Array.
private void EmitFloat64UnboxedAccessors(TypeBuilder typeBuilder, EmittedRuntime runtime)
{
var getU = typeBuilder.DefineMethod(
"GetUnboxed",
MethodAttributes.Public | MethodAttributes.HideBySig,
_types.Double,
[_types.Int32]
);
var gil = getU.GetILGenerator();
// return BitConverter.ToDouble(_buffer, _byteOffset + index * 8);
gil.Emit(OpCodes.Ldarg_0);
gil.Emit(OpCodes.Ldfld, _typedArrayBufferField!);
gil.Emit(OpCodes.Ldarg_0);
gil.Emit(OpCodes.Ldfld, _typedArrayByteOffsetField!);
gil.Emit(OpCodes.Ldarg_1);
gil.Emit(OpCodes.Ldc_I4_8);
gil.Emit(OpCodes.Mul);
gil.Emit(OpCodes.Add);
gil.Emit(OpCodes.Call, typeof(BitConverter).GetMethod("ToDouble", [typeof(byte[]), typeof(int)])!);
gil.Emit(OpCodes.Ret);
runtime.Float64ArrayGetUnboxed = getU;

var setU = typeBuilder.DefineMethod(
"SetUnboxed",
MethodAttributes.Public | MethodAttributes.HideBySig,
_types.Void,
[_types.Int32, _types.Double]
);
var sil = setU.GetILGenerator();
var bytesLocal = sil.DeclareLocal(typeof(byte[]));
// var bytes = BitConverter.GetBytes(value);
sil.Emit(OpCodes.Ldarg_2);
sil.Emit(OpCodes.Call, typeof(BitConverter).GetMethod("GetBytes", [typeof(double)])!);
sil.Emit(OpCodes.Stloc, bytesLocal);
// Array.Copy(bytes, 0, _buffer, _byteOffset + index * 8, 8);
sil.Emit(OpCodes.Ldloc, bytesLocal);
sil.Emit(OpCodes.Ldc_I4_0);
sil.Emit(OpCodes.Ldarg_0);
sil.Emit(OpCodes.Ldfld, _typedArrayBufferField!);
sil.Emit(OpCodes.Ldarg_0);
sil.Emit(OpCodes.Ldfld, _typedArrayByteOffsetField!);
sil.Emit(OpCodes.Ldarg_1);
sil.Emit(OpCodes.Ldc_I4_8);
sil.Emit(OpCodes.Mul);
sil.Emit(OpCodes.Add);
sil.Emit(OpCodes.Ldc_I4_8);
sil.Emit(OpCodes.Call, typeof(Array).GetMethod("Copy", [typeof(Array), typeof(int), typeof(Array), typeof(int), typeof(int)])!);
sil.Emit(OpCodes.Ret);
runtime.Float64ArraySetUnboxed = setU;
}
}
Loading