Skip to content

Commit

Permalink
allocationless Closure invoke
Browse files Browse the repository at this point in the history
- avoids allocation for 0 or 1 closure argument
- avoids allocation in Closure creation
  • Loading branch information
jakubmisek committed Feb 22, 2025
1 parent 59e6888 commit 6840062
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 27 deletions.
21 changes: 19 additions & 2 deletions src/Peachpie.CodeAnalysis/CodeGen/CodeGenerator.Emit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;

namespace Pchp.CodeAnalysis.CodeGen
{
Expand Down Expand Up @@ -1937,12 +1936,30 @@ internal TypeSymbol EmitCall(ILOpCode code, MethodSymbol method, BoundExpression
}
else
{
// TODO: 1 argument can be marshaled to ReadOnlySpan<T> without allocation

// easy case,
// wrap remaining arguments to array
var values = (arg_index < arguments.Length) ? arguments.Skip(arg_index).AsImmutable() : ImmutableArray<BoundArgument>.Empty;
arg_index += values.Length;

// special cases:

// []
if (values.IsDefaultOrEmpty && p.Type.IsReadOnlySpan(null))
{
// default(ReadOnlySpan<T>)
EmitLoadDefaultOfValueType(p.Type);
break; // done
}
else if (values.Length == 1 && p.Type.IsReadOnlySpan(CoreTypes.PhpValue))
{
// 1 argument can be marshaled to ReadOnlySpan<T> without allocation
EmitConvert(Emit(values[0].Value), 0, p_element);
EmitCall(ILOpCode.Call, CoreMethods.Helpers.CreateReadOnlySpan_T.Symbol.Construct(p_element));
break;
}

//
result_type = Emit_NewArray(p_element, values);
}

Expand Down
86 changes: 62 additions & 24 deletions src/Peachpie.CodeAnalysis/CodeGen/Graph/BoundExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3387,7 +3387,7 @@ internal override TypeSymbol Emit(CodeGenerator cg)
EmitThis(cg); // $this
cg.EmitCallerTypeHandle(); // scope
EmitStaticType(cg); // statictype : PhpTypeInfo
EmitParametersArray(cg); // "parameters"
EmitCachedParametersArray(cg, ((LambdaFunctionExpr)PhpSyntax).Signature.FormalParams); // "parameters"
EmitUseArray(cg); // "static"

return cg.EmitCall(ILOpCode.Call, cg.CoreMethods.Operators.BuildClosure_Context_IPhpCallable_Object_RuntimeTypeHandle_PhpTypeInfo_PhpArray_PhpArray);
Expand Down Expand Up @@ -3447,36 +3447,74 @@ void EmitUseArray(CodeGenerator cg)
}
}

void EmitParametersArray(CodeGenerator cg)
void EmitNewParametersArray(CodeGenerator cg, FormalParam[] ps)
{
var ps = ((LambdaFunctionExpr)PhpSyntax).Signature.FormalParams;
if (ps != null && ps.Length != 0)
{
// TODO: cache singleton

// new PhpArray(<count>){ ... }
cg.Builder.EmitIntConstant(ps.Length);
cg.EmitCall(ILOpCode.Newobj, cg.CoreMethods.Ctors.PhpArray_int);
// new PhpArray(<count>){ ... }
cg.Builder.EmitIntConstant(ps.Length);
cg.EmitCall(ILOpCode.Newobj, cg.CoreMethods.Ctors.PhpArray_int);

foreach (var p in ps)
{
var keyname = "$" + p.Name.Name.Value;
if (p.PassedByRef) keyname = "&" + keyname;
var value = (p.InitValue != null) ? "<optional>" : "<required>";
foreach (var p in ps)
{
var keyname = "$" + p.Name.Name.Value;
if (p.PassedByRef) keyname = "&" + keyname;
var value = (p.InitValue != null) ? "<optional>" : "<required>";

// <stack>.SetItemValue("&$name", "<optional>"|"<required>")
cg.Builder.EmitOpCode(ILOpCode.Dup);
cg.EmitIntStringKey(keyname);
cg.Builder.EmitStringConstant(value);
cg.EmitConvertToPhpValue(cg.CoreTypes.String, 0);
cg.EmitCall(ILOpCode.Call, cg.CoreMethods.PhpArray.Add_IntStringKey_PhpValue);
}
// <stack>.SetItemValue("&$name", "<optional>"|"<required>")
cg.Builder.EmitOpCode(ILOpCode.Dup);
cg.EmitIntStringKey(keyname);
cg.Builder.EmitStringConstant(value);
cg.EmitConvertToPhpValue(cg.CoreTypes.String, 0);
cg.EmitCall(ILOpCode.Call, cg.CoreMethods.PhpArray.Add_IntStringKey_PhpValue);
}
else
}

/// <summary>
/// Caches the array instance into an internal app-static field,
/// so repetitious creations only uses the existing instance.
/// </summary>
TypeSymbol EmitCachedParametersArray(CodeGenerator cg, FormalParam[] ps)
{
if (ps == null || ps.Length == 0)
{
// PhpArray.Empty
cg.Emit_PhpArray_Empty();
return cg.Emit_PhpArray_Empty();
}

// static PhpArray <arr>`;
var fld = cg.Factory.CreateSynthesizedField(cg.CoreTypes.PhpArray, "<params>", true);
var fldplace = new FieldPlace(null, fld, cg.Module);

// TODO: reuse existing cached PhpArray with the same content

// <fld> = new PhpArray(...)
var cctor = cg.Factory.CctorBuilder;

lock (cctor)
{
using (var cctor_cg = new CodeGenerator(cctor, cg.Module, cg.Diagnostics, cg.DeclaringCompilation.Options.OptimizationLevel, false, cg.Factory.Container, null, null, cg.Routine)
{
CallerType = cg.CallerType,
ContainingFile = cg.ContainingFile,
IsInCachedArrayExpression = true,
})
{
fldplace.EmitStorePrepare(cctor_cg.Builder);
EmitNewParametersArray(cctor_cg, ps);
fldplace.EmitStore(cctor_cg.Builder);
}
}

// LOAD <fld>
fld.EmitLoad(cg);

//// .DeepCopy()
//// if (this.Access.IsReadCopy) // unsafe ?
//{
// cg.EmitCall(ILOpCode.Callvirt, cg.CoreMethods.PhpArray.DeepCopy);
//}

//
return fld.Type; // ~ PhpArray
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/Peachpie.CodeAnalysis/Symbols/CoreMembers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,7 @@ public HelpersHolder(CoreTypes ct)
{
EmptyRuntimeTypeHandle = ct.Helpers.Field(nameof(EmptyRuntimeTypeHandle));
EmptyNullable_T = ct.Helpers.Method(nameof(EmptyNullable_T));
CreateReadOnlySpan_T = new CoreGenericMethod(ct.Helpers, "CreateReadOnlySpan", 1);
IsUserTypeDeclared_Context_PhpTypeInfo = ct.Helpers.Method("IsUserTypeDeclared", ct.Context, ct.PhpTypeInfo);
AsSpan_T = new CoreGenericMethod(ct.MemoryExtensions, "AsSpan", 1);

Expand All @@ -631,7 +632,7 @@ public HelpersHolder(CoreTypes ct)
public readonly CoreField EmptyRuntimeTypeHandle;

public readonly CoreMethod
EmptyNullable_T,
EmptyNullable_T, CreateReadOnlySpan_T,
IsUserTypeDeclared_Context_PhpTypeInfo,
AsSpan_T
;
Expand Down
6 changes: 6 additions & 0 deletions src/Peachpie.Runtime/Utilities/Helpers.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace Pchp.Core.Utilities
Expand All @@ -23,5 +24,10 @@ public static class Helpers
/// Gets value indicating the given user PHP type is declared in specified <see cref="Context"/>.
/// </summary>
public static bool IsUserTypeDeclared(Context ctx, Reflection.PhpTypeInfo tinfo) => ctx.IsUserTypeDeclared(tinfo);

/// <summary>
/// Create single-length readonlyspan from value on stack.
/// </summary>
public static ReadOnlySpan<T> CreateReadOnlySpan<T>(T value) => MemoryMarshal.CreateReadOnlySpan(ref value, 1);
}
}

0 comments on commit 6840062

Please sign in to comment.