Skip to content

Commit c7e6642

Browse files
authored
Merge pull request #55 from iolevel/parameter-unpacking
Parameter unpacking
2 parents b10c00d + 0ebf95e commit c7e6642

13 files changed

+272
-55
lines changed

src/Peachpie.CodeAnalysis/CodeGen/CodeGenerator.Convert.cs

+24-4
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,13 @@ public void EmitConvert(TypeSymbol from, TypeRefMask fromHint, TypeSymbol to)
11811181
//
11821182
from = EmitSpecialize(from, fromHint);
11831183

1184+
// conversion is not needed:
1185+
if (from.SpecialType == to.SpecialType &&
1186+
(from == to || (to.SpecialType != SpecialType.System_Object && from.IsOfType(to))))
1187+
{
1188+
return;
1189+
}
1190+
11841191
// specialized conversions:
11851192
switch (to.SpecialType)
11861193
{
@@ -1216,8 +1223,21 @@ public void EmitConvert(TypeSymbol from, TypeRefMask fromHint, TypeSymbol to)
12161223
}
12171224
else if (to == CoreTypes.PhpAlias)
12181225
{
1219-
EmitConvertToPhpValue(from, fromHint);
1220-
Emit_PhpValue_MakeAlias();
1226+
if (from != CoreTypes.PhpValue)
1227+
{
1228+
if (from != CoreTypes.PhpAlias)
1229+
{
1230+
// Template: new PhpAlias((PhpValue))
1231+
EmitConvertToPhpValue(from, fromHint);
1232+
Emit_PhpValue_MakeAlias();
1233+
}
1234+
}
1235+
else
1236+
{
1237+
// Template: <STACK>.EnsureAlias() // keeps already aliased value
1238+
EmitPhpValueAddr();
1239+
EmitCall(ILOpCode.Call, CoreMethods.PhpValue.EnsureAlias);
1240+
}
12211241
}
12221242
else if (to == CoreTypes.PhpNumber)
12231243
{
@@ -1245,9 +1265,9 @@ public void EmitConvert(TypeSymbol from, TypeRefMask fromHint, TypeSymbol to)
12451265
}
12461266
else
12471267
{
1248-
break;
1268+
break; // NotImplementedException
12491269
}
1250-
return;
1270+
return; // Handled
12511271
}
12521272

12531273
//

src/Peachpie.CodeAnalysis/CodeGen/CodeGenerator.Emit.cs

+20-9
Original file line numberDiff line numberDiff line change
@@ -748,15 +748,15 @@ TypeSymbol Emit_ArgsArray(TypeSymbol elementType)
748748
/// Emits <c>PhpValue[]</c> containing given <paramref name="args"/>.
749749
/// Argument unpacking is taken into account and flatterned.
750750
/// </summary>
751-
internal void Emit_ArgumentsIntoArray(ImmutableArray<BoundArgument> args) // TODO: which parameters are by-reference
751+
internal void Emit_ArgumentsIntoArray(ImmutableArray<BoundArgument> args, PhpSignatureMask byrefargs)
752752
{
753753
if (args.Length == 0)
754754
{
755755
Emit_EmptyArray(CoreTypes.PhpValue);
756756
}
757757
else if (args.Last().IsUnpacking) // => handle unpacking // last argument must be unpacking otherwise unpacking is not allowed anywhere else
758758
{
759-
UnpackArgumentsIntoArray(args);
759+
UnpackArgumentsIntoArray(args, byrefargs);
760760
}
761761
else
762762
{
@@ -769,9 +769,10 @@ internal void Emit_ArgumentsIntoArray(ImmutableArray<BoundArgument> args) // TO
769769
/// Argument unpacking is taken into account and flatterned.
770770
/// </summary>
771771
/// <param name="args">Arguments to be flatterned into a single dimensional array.</param>
772+
/// <param name="byrefargs">Mask of arguments that must be passed by reference.</param>
772773
/// <remarks>The method assumes the arguments list contains a variable unpacking. Otherwise this method is not well performance optimized.</remarks>
773774
/// <returns>Type symbol corresponding to <c>PhpValue[]</c></returns>
774-
internal TypeSymbol UnpackArgumentsIntoArray(ImmutableArray<BoundArgument> args) // TODO: which parameters are by-reference
775+
internal TypeSymbol UnpackArgumentsIntoArray(ImmutableArray<BoundArgument> args, PhpSignatureMask byrefargs)
775776
{
776777
if (args.IsDefaultOrEmpty)
777778
{
@@ -794,7 +795,7 @@ internal TypeSymbol UnpackArgumentsIntoArray(ImmutableArray<BoundArgument> args)
794795
// Symbol: Operators.Unpack
795796
var unpack_methods = CoreTypes.Operators.Symbol.GetMembers("Unpack").OfType<MethodSymbol>();
796797
var unpack_list_value_ulong = unpack_methods.Single(m => m.Parameters[1].Type == CoreTypes.PhpValue);
797-
//var unpack_list_array_ulong = unpack_methods.Single(m => m.Parameters[1].Type == CoreTypes.PhpArray);
798+
var unpack_list_array_ulong = unpack_methods.Single(m => m.Parameters[1].Type == CoreTypes.PhpArray);
798799

799800
// 1. create temporary List<PhpValue>
800801

@@ -811,10 +812,20 @@ internal TypeSymbol UnpackArgumentsIntoArray(ImmutableArray<BoundArgument> args)
811812
if (args[i].IsUnpacking)
812813
{
813814
// Template: Unpack(<STACK>, args[i], byrefs)
814-
EmitConvert(args[i].Value, CoreTypes.PhpValue);
815-
_il.EmitLongConstant(0L); // TODO: byref args
816-
EmitCall(ILOpCode.Call, unpack_list_value_ulong)
817-
.Expect(SpecialType.System_Void);
815+
var t = Emit(args[i].Value);
816+
if (t == CoreTypes.PhpArray)
817+
{
818+
_il.EmitLongConstant((long)(ulong)byrefargs); // byref args
819+
EmitCall(ILOpCode.Call, unpack_list_array_ulong)
820+
.Expect(SpecialType.System_Void);
821+
}
822+
else
823+
{
824+
EmitConvert(t, args[i].Value.TypeRefMask, CoreTypes.PhpValue);
825+
_il.EmitLongConstant((long)(ulong)byrefargs); // byref args
826+
EmitCall(ILOpCode.Call, unpack_list_value_ulong)
827+
.Expect(SpecialType.System_Void);
828+
}
818829
}
819830
else
820831
{
@@ -1198,7 +1209,7 @@ internal TypeSymbol EmitCall_UnpackingArgs(ILOpCode code, MethodSymbol method, B
11981209

11991210
// unpack arguments (after $this was evaluated)
12001211
// Template: PhpValue[] <tmpargs> = UNPACK <arguments>
1201-
var tmpargs = GetTemporaryLocal(UnpackArgumentsIntoArray(packedarguments));
1212+
var tmpargs = GetTemporaryLocal(UnpackArgumentsIntoArray(packedarguments, method.GetByRefArguments()));
12021213
_il.EmitLocalStore(tmpargs);
12031214
var tmpargs_place = new LocalPlace(tmpargs);
12041215

src/Peachpie.CodeAnalysis/CodeGen/Graph/BoundExpression.cs

+17-27
Original file line numberDiff line numberDiff line change
@@ -2249,16 +2249,9 @@ internal override TypeSymbol Emit(CodeGenerator cg)
22492249
{
22502250
if (!TargetMethod.IsErrorMethod())
22512251
{
2252-
if (this.HasArgumentsUnpacking)
2253-
{
2254-
return EmitDirectCall_UnpackingArgs(cg, IsVirtualCall ? ILOpCode.Callvirt : ILOpCode.Call, TargetMethod, LateStaticTypeRef);
2255-
}
2256-
else
2257-
{
2258-
// the most preferred case when both method and arguments are known,
2259-
// the method can be called directly
2260-
return EmitDirectCall(cg, IsVirtualCall ? ILOpCode.Callvirt : ILOpCode.Call, TargetMethod, LateStaticTypeRef);
2261-
}
2252+
// the most preferred case when method is known,
2253+
// the method can be called directly
2254+
return EmitDirectCall(cg, IsVirtualCall ? ILOpCode.Callvirt : ILOpCode.Call, TargetMethod, LateStaticTypeRef);
22622255
}
22632256

22642257
//
@@ -2278,15 +2271,12 @@ internal virtual TypeSymbol EmitDirectCall(CodeGenerator cg, ILOpCode opcode, Me
22782271
// TODO: in case of a global user routine -> emit check the function is declared
22792272
// <ctx>.AssertFunctionDeclared
22802273

2281-
return (this.ResultType = cg.EmitMethodAccess(cg.EmitCall(opcode, method, this.Instance, _arguments, staticType), method, Access));
2282-
}
2283-
2284-
internal TypeSymbol EmitDirectCall_UnpackingArgs(CodeGenerator cg, ILOpCode opcode, MethodSymbol method, BoundTypeRef staticType = null)
2285-
{
2286-
// TODO: in case of a global user routine -> emit check the function is declared
2287-
// <ctx>.AssertFunctionDeclared
2274+
var stacktype = this.HasArgumentsUnpacking
2275+
? cg.EmitCall_UnpackingArgs(opcode, method, this.Instance, _arguments, staticType) // call method with respect to argument unpacking
2276+
: cg.EmitCall(opcode, method, this.Instance, _arguments, staticType); // call method and pass provided arguments as they are
22882277

2289-
return (this.ResultType = cg.EmitMethodAccess(cg.EmitCall_UnpackingArgs(opcode, method, this.Instance, _arguments, staticType), method, Access));
2278+
//
2279+
return (this.ResultType = cg.EmitMethodAccess(stacktype, method, Access));
22902280
}
22912281

22922282
protected virtual string CallsiteName => null;
@@ -2410,7 +2400,7 @@ internal override TypeSymbol EmitDynamicCall(CodeGenerator cg)
24102400

24112401
cg.EmitConvert(_name.NameExpression, cg.CoreTypes.IPhpCallable); // (IPhpCallable)Name
24122402
cg.EmitLoadContext(); // Context
2413-
cg.Emit_ArgumentsIntoArray(_arguments); // PhpValue[]
2403+
cg.Emit_ArgumentsIntoArray(_arguments, default(PhpSignatureMask)); // PhpValue[]
24142404

24152405
return cg.EmitCall(ILOpCode.Callvirt, cg.CoreTypes.IPhpCallable.Symbol.LookupMember<MethodSymbol>("Invoke"));
24162406
}
@@ -2498,7 +2488,7 @@ internal override TypeSymbol Emit(CodeGenerator cg)
24982488

24992489
private TypeSymbol EmitNewClass(CodeGenerator cg)
25002490
{
2501-
if (!TargetMethod.IsErrorMethod() && !this.HasArgumentsUnpacking)
2491+
if (!TargetMethod.IsErrorMethod())
25022492
{
25032493
return EmitDirectCall(cg, ILOpCode.Newobj, TargetMethod);
25042494
}
@@ -2515,9 +2505,9 @@ private TypeSymbol EmitNewClass(CodeGenerator cg)
25152505
.Single()
25162506
.Construct(_typeref.ResolvedType);
25172507

2518-
cg.EmitLoadContext(); // Context
2519-
cg.EmitCallerRuntimeTypeHandle(); // RuntimeTypeHandle
2520-
cg.Emit_ArgumentsIntoArray(_arguments); // PhpValue[]
2508+
cg.EmitLoadContext(); // Context
2509+
cg.EmitCallerRuntimeTypeHandle(); // RuntimeTypeHandle
2510+
cg.Emit_ArgumentsIntoArray(_arguments, default(PhpSignatureMask)); // PhpValue[]
25212511

25222512
return cg.EmitCall(ILOpCode.Call, create_t);
25232513
}
@@ -2532,10 +2522,10 @@ private TypeSymbol EmitNewClass(CodeGenerator cg)
25322522
SpecialParameterSymbol.IsCallerClassParameter(s.Parameters[0]))
25332523
.Single();
25342524

2535-
cg.EmitLoadContext(); // Context
2536-
cg.EmitCallerRuntimeTypeHandle(); // RuntimeTypeHandle
2537-
_typeref.EmitLoadTypeInfo(cg, true); // PhpTypeInfo
2538-
cg.Emit_ArgumentsIntoArray(_arguments); // PhpValue[]
2525+
cg.EmitLoadContext(); // Context
2526+
cg.EmitCallerRuntimeTypeHandle(); // RuntimeTypeHandle
2527+
_typeref.EmitLoadTypeInfo(cg, true); // PhpTypeInfo
2528+
cg.Emit_ArgumentsIntoArray(_arguments, default(PhpSignatureMask)); // PhpValue[]
25392529

25402530
return cg.EmitCall(ILOpCode.Call, create);
25412531
}

src/Peachpie.CodeAnalysis/FlowAnalysis/StateBinder.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public static FlowState CreateInitialState(SourceRoutineSymbol/*!*/routine)
3838
var local = state.GetLocalHandle(p.Name);
3939
state.SetLocalType(local, p.GetResultType(typeCtx));
4040

41-
if (p.Syntax.PassedByRef)
41+
if (p.Syntax.PassedByRef && !p.Syntax.IsVariadic)
4242
{
4343
state.MarkLocalByRef(local);
4444
}

src/Peachpie.CodeAnalysis/Symbols/Php/PhpRoutineSymbolExtensions.cs

+39-2
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,14 @@ public static PhpParam[] GetExpectedArguments(this IPhpRoutineSymbol routine, Ty
174174
Contract.ThrowIfNull(routine);
175175

176176
var ps = routine.Parameters;
177-
var table = (routine as SourceRoutineSymbol)?.LocalsTable;
177+
//var table = (routine as SourceRoutineSymbol)?.LocalsTable;
178178
var result = new List<PhpParam>(ps.Length);
179179

180180
int index = 0;
181181

182182
foreach (ParameterSymbol p in ps)
183183
{
184-
if (result.Count == 0 && p.IsImplicitlyDeclared)
184+
if (result.Count == 0 && p.IsImplicitlyDeclared && !p.IsParams)
185185
{
186186
continue;
187187
}
@@ -208,6 +208,43 @@ public static PhpParam[] GetExpectedArguments(this IPhpRoutineSymbol routine, Ty
208208
return result.ToArray();
209209
}
210210

211+
/// <summary>
212+
/// Gets mask with 1-bits corresponding to an argument that must be passed by reference.
213+
/// </summary>
214+
internal static PhpSignatureMask GetByRefArguments(this IPhpRoutineSymbol routine)
215+
{
216+
Contract.ThrowIfNull(routine);
217+
218+
var mask = new PhpSignatureMask();
219+
var ps = routine.Parameters;
220+
221+
int index = 0;
222+
223+
foreach (ParameterSymbol p in ps)
224+
{
225+
if (index == 0 && p.IsImplicitlyDeclared && !p.IsParams)
226+
{
227+
continue;
228+
}
229+
230+
if (p.IsParams)
231+
{
232+
if (((ArrayTypeSymbol)p.Type).ElementType.Is_PhpAlias()) // PHP: ... &$p
233+
{
234+
mask.SetFrom(index++);
235+
}
236+
}
237+
else
238+
{
239+
mask[index++] =
240+
p.RefKind != RefKind.None || // C#: ref|out p
241+
p.Type.Is_PhpAlias(); // PHP: &$p
242+
}
243+
}
244+
245+
return mask;
246+
}
247+
211248
/// <summary>
212249
/// Gets additional flags of the caller routine.
213250
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Pchp.CodeAnalysis.Symbols
8+
{
9+
/// <summary>
10+
/// Mask of single bits representing a true-false state.
11+
/// </summary>
12+
internal struct PhpSignatureMask
13+
{
14+
ulong _mask;
15+
16+
static ulong BitAt(int index) => (1ul << index);
17+
18+
/// <summary>
19+
/// Implicitly converts the mask to <see cref="ulong"/> mask.
20+
/// </summary>
21+
public static implicit operator ulong(PhpSignatureMask mask) => mask._mask;
22+
23+
/// <summary>
24+
/// Combines two masks.
25+
/// </summary>
26+
public static PhpSignatureMask operator |(PhpSignatureMask a, PhpSignatureMask b) => new PhpSignatureMask() { _mask = a._mask | b._mask };
27+
28+
public bool this[int index]
29+
{
30+
get
31+
{
32+
return (_mask & BitAt(index)) != 0;
33+
}
34+
set
35+
{
36+
if (value)
37+
{
38+
_mask |= BitAt(index);
39+
}
40+
else
41+
{
42+
_mask &= ~BitAt(index);
43+
}
44+
}
45+
}
46+
47+
/// <summary>
48+
/// Sets all bits from given position to <c>1</c>.
49+
/// </summary>
50+
public void SetFrom(int start)
51+
{
52+
_mask |= ~(BitAt(start) - 1);
53+
}
54+
}
55+
}

src/Peachpie.CodeAnalysis/Symbols/Source/SourceParameterSymbol.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,16 @@ TypeSymbol ResolveType()
102102
// aliased parameter:
103103
if (_syntax.IsOut || _syntax.PassedByRef)
104104
{
105-
return DeclaringCompilation.CoreTypes.PhpAlias;
105+
if (_syntax.IsVariadic)
106+
{
107+
// PhpAlias[]
108+
return ArrayTypeSymbol.CreateSZArray(this.ContainingAssembly, DeclaringCompilation.CoreTypes.PhpAlias);
109+
}
110+
else
111+
{
112+
// PhpAlias
113+
return DeclaringCompilation.CoreTypes.PhpAlias;
114+
}
106115
}
107116

108117
// 1. specified type hint

src/Peachpie.CodeAnalysis/Symbols/TypeSymbolExtensions.cs

+10-1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ public static bool CanBeConst(this TypeSymbol typeSymbol)
6262
return typeSymbol.IsReferenceType || typeSymbol.IsEnumType() || typeSymbol.SpecialType.CanBeConst();
6363
}
6464

65+
public static bool Is_PhpAlias(this TypeSymbol t)
66+
{
67+
return t.MetadataName == "PhpAlias" && t.ContainingAssembly?.IsPchpCorLibrary == true;
68+
}
69+
70+
public static bool Is_PhpValue(this TypeSymbol t)
71+
{
72+
return t.MetadataName == "PhpValue" && t.ContainingAssembly?.IsPchpCorLibrary == true;
73+
}
74+
6575
public static bool IsOfType(this TypeSymbol t, TypeSymbol oftype)
6676
{
6777
if (oftype != null)
@@ -349,7 +359,6 @@ public static bool IsExpressionTree(this TypeSymbol _type)
349359
CheckFullName(type.ContainingSymbol, s_expressionsNamespaceName);
350360
}
351361

352-
353362
/// <summary>
354363
/// return true if the type is constructed from a generic interface that
355364
/// might be implemented by an array.

0 commit comments

Comments
 (0)