private IStrongBox GetBox(ParameterExpression variable) { // Skip variables that are shadowed by a nested scope/lambda foreach (Set <ParameterExpression> hidden in _hiddenVars) { if (hidden.Contains(variable)) { return(null); } } HoistedLocals scope = _scope; object[] locals = _locals; while (true) { int hoistIndex; if (scope.Indexes.TryGetValue(variable, out hoistIndex)) { return((IStrongBox)locals[hoistIndex]); } scope = scope.Parent; if (scope == null) { break; } locals = HoistedLocals.GetParent(locals); } // TODO: this should be unreachable because it's an unbound // variable, so we should throw here. It's a breaking change, // however return(null); }
public void Quote_RuntimeVariables_Hoisted3() { var x = Expression.Parameter(typeof(int)); var y = Expression.Parameter(typeof(int)); var variables = new ReadOnlyCollection <ParameterExpression>(new[] { y }); var definitions = new Dictionary <ParameterExpression, StorageKind> { { y, StorageKind.Hoisted | StorageKind.Boxed } // NB: Using Boxed for LINQ ET compatibility. }; var h = new HoistedLocals(parent: null, variables, definitions); var e = Expression.Lambda <Func <int, IRuntimeVariables> >(Expression.RuntimeVariables(x, y), x); Assert.AreEqual(typeof(Closure <StrongBox <int> >), h.Closure.ClosureType); var c = new Closure <StrongBox <int> > { Item1 = new StrongBox <int>(42) }; var q = (Expression <Func <int, IRuntimeVariables> >)RuntimeOpsEx.Quote(e, h, c); var f = q.Compile(); Assert.AreEqual(41, f(41)[0]); Assert.AreEqual(42, f(41)[1]); Assert.AreEqual(2, f(41).Count); var r = f(43); r[0] = 44; r[1] = 45; Assert.AreEqual(44, r[0]); Assert.AreEqual(45, r[1]); Assert.AreEqual(45, c.Item1.Value); }
private IStrongBox GetBox(ParameterExpression variable) { int num; foreach (Set <ParameterExpression> set in this._shadowedVars) { if (set.Contains(variable)) { return(null); } } HoistedLocals parent = this._scope; object[] objArray = this._locals; while (!parent.Indexes.TryGetValue(variable, out num)) { parent = parent.Parent; if (parent == null) { throw ContractUtils.Unreachable; } objArray = HoistedLocals.GetParent(objArray); } return((IStrongBox)objArray[num]); }
private void EmitClosureToVariable(LambdaCompiler lc, HoistedLocals locals) { lc.EmitClosureArgument(); lc.IL.Emit(OpCodes.Ldfld, typeof(Closure).GetField("Locals")); AddLocal(lc, locals.SelfVariable); EmitSet(locals.SelfVariable); }
public void Quote_Hoisted_Parent() { var x = Expression.Parameter(typeof(int)); var variables = new ReadOnlyCollection <ParameterExpression>(new[] { x }); var definitions = new Dictionary <ParameterExpression, StorageKind> { { x, StorageKind.Hoisted | StorageKind.Boxed } // NB: Using Boxed for LINQ ET compatibility. }; var p = new HoistedLocals(parent: null, variables, definitions); var h = new HoistedLocals(p, new ReadOnlyCollection <ParameterExpression>(Array.Empty <ParameterExpression>()), new Dictionary <ParameterExpression, StorageKind>()); var e = Expression.Lambda <Func <int> >(x); Assert.AreEqual(typeof(Closure <Closure <StrongBox <int> > >), h.Closure.ClosureType); var c = new Closure <Closure <StrongBox <int> > > { Item1 = new Closure <StrongBox <int> > { Item1 = new StrongBox <int>(42) } }; var q = (Expression <Func <int> >)RuntimeOpsEx.Quote(e, h, c); var f = q.Compile(); Assert.AreEqual(42, f()); c.Item1.Item1.Value = 43; Assert.AreEqual(43, f()); }
private IStrongBox GetBox(ParameterExpression variable) { // Skip variables that are shadowed by a nested scope/lambda foreach (Set <ParameterExpression> hidden in _shadowedVars) { if (hidden.Contains(variable)) { return(null); } } HoistedLocals scope = _scope; object[] locals = _locals; while (true) { int hoistIndex; if (scope.Indexes.TryGetValue(variable, out hoistIndex)) { return((IStrongBox)locals[hoistIndex]); } scope = scope.Parent; if (scope == null) { break; } locals = HoistedLocals.GetParent(locals); } // Unbound variable: an error should've been thrown already // from VariableBinder throw ContractUtils.Unreachable; }
private IStrongBox GetStrongBox(int index) { long num = this._indexes[index]; object[] locals = this._data; for (int i = (int)(num >> 0x20); i > 0; i--) { locals = HoistedLocals.GetParent(locals); } return((IStrongBox)locals[(int)num]); }
// Creates IL locals for accessing closures private void EmitClosureAccess(LambdaCompiler lc, HoistedLocals locals) { if (locals == null) { return; } EmitClosureToVariable(lc, locals); while ((locals = locals.Parent) != null) { var v = locals.SelfVariable; var local = new LocalStorage(lc, v); local.EmitStore(ResolveVariable(v)); _locals.Add(v, local); } }
internal HoistedLocals(HoistedLocals parent, ReadOnlyCollection<ParameterExpression> vars) { if (parent != null) { // Add the parent locals array as the 0th element in the array vars = new TrueReadOnlyCollection<ParameterExpression>(vars.AddFirst(parent.SelfVariable)); } Dictionary<Expression, int> indexes = new Dictionary<Expression, int>(vars.Count); for (int i = 0; i < vars.Count; i++) { indexes.Add(vars[i], i); } SelfVariable = Expression.Variable(typeof(object[]), null); Parent = parent; Variables = vars; Indexes = new ReadOnlyDictionary<Expression, int>(indexes); }
private IStrongBox GetStrongBox(int index) { // We lookup the closure using two ints: // 1. The high dword is the number of parents to go up // 2. The low dword is the index into that array long closureKey = _indexes[index]; // walk up the parent chain to find the real environment object[] result = _data; for (int parents = (int)(closureKey >> 32); parents > 0; parents--) { result = HoistedLocals.GetParent(result); } // Return the variable storage return((IStrongBox)result[unchecked ((int)closureKey)]); }
private void SetParent(LambdaCompiler lc, CompilerScope parent) { Debug.Assert(_parent == null && parent != this); _parent = parent; if (NeedsClosure && _parent != null) { _closureHoistedLocals = _parent.NearestHoistedLocals; } var hoistedVars = GetVariables().Where(p => Definitions[p] == VariableStorageKind.Hoisted).ToReadOnly(); if (hoistedVars.Count > 0) { _hoistedLocals = new HoistedLocals(_closureHoistedLocals, hoistedVars); AddLocal(lc, _hoistedLocals.SelfVariable); } }
internal HoistedLocals(HoistedLocals parent, ReadOnlyCollection <ParameterExpression> vars) { if (parent != null) { // Add the parent locals array as the 0th element in the array vars = new TrueReadOnlyCollection <ParameterExpression>(vars.AddFirst(parent.SelfVariable)); } Dictionary <Expression, int> indexes = new Dictionary <Expression, int>(vars.Count); for (int i = 0; i < vars.Count; i++) { indexes.Add(vars[i], i); } SelfVariable = Expression.Variable(typeof(object[]), null); Parent = parent; Variables = vars; Indexes = new ReadOnlyDictionary <Expression, int>(indexes); }
public IStrongBox this[int index] { get { // We lookup the closure using two ints: // 1. The high dword is the number of parents to go up // 2. The low dword is the index into that array long closureKey = _indexes[index]; // walk up the parent chain to find the real environment object[] result = _data; for (int parents = (int)(closureKey >> 32); parents > 0; parents--) { result = HoistedLocals.GetParent(result); } // Return the variable storage return((IStrongBox)result[(int)closureKey]); } set { throw Error.CollectionReadOnly(); } }
/// <summary> /// Resolve a local variable in this scope or a closed over scope /// Throws if the variable is defined /// </summary> private Storage ResolveVariable(ParameterExpression variable, HoistedLocals hoistedLocals) { // Search IL locals and arguments, but only in this lambda for (CompilerScope s = this; s != null; s = s._parent) { Storage storage; if (s._locals.TryGetValue(variable, out storage)) { return(storage); } // if this is a lambda, we're done if (s.IsMethod) { break; } } // search hoisted locals for (HoistedLocals h = hoistedLocals; h != null; h = h.Parent) { int index; if (h.Indexes.TryGetValue(variable, out index)) { return(new ElementBoxStorage( ResolveVariable(h.SelfVariable, hoistedLocals), index, variable )); } } // // If this is an unbound variable in the lambda, the error will be // thrown from VariableBinder. So an error here is generally caused // by an internal error, e.g. a scope was created but it bypassed // VariableBinder. // throw Error.UndefinedVariable(variable.Name, variable.Type, CurrentLambdaName); }
/// <summary> /// Frees unnamed locals, clears state associated with this compiler /// </summary> internal CompilerScope Exit() { // free scope's variables if (!IsMethod) { foreach (Storage storage in _locals.Values) { storage.FreeLocal(); } } // Clear state that is associated with this parent // (because the scope can be reused in another context) CompilerScope parent = _parent; _parent = null; _hoistedLocals = null; _closureHoistedLocals = null; _locals.Clear(); return(parent); }
internal void EmitVariableAccess(LambdaCompiler lc, ReadOnlyCollection <ParameterExpression> vars) { if (NearestHoistedLocals != null) { // Find what array each variable is on & its index var indexes = new List <long>(vars.Count); foreach (var variable in vars) { // For each variable, find what array it's defined on ulong parents = 0; HoistedLocals locals = NearestHoistedLocals; while (!locals.Indexes.ContainsKey(variable)) { parents++; locals = locals.Parent; Debug.Assert(locals != null); } // combine the number of parents we walked, with the // real index of variable to get the index to emit. ulong index = (parents << 32) | (uint)locals.Indexes[variable]; indexes.Add((long)index); } if (indexes.Count > 0) { EmitGet(NearestHoistedLocals.SelfVariable); lc.EmitConstantArray(indexes.ToArray()); lc.IL.Emit(OpCodes.Call, typeof(RuntimeOps).GetMethod("CreateRuntimeVariables", new[] { typeof(object[]), typeof(long[]) })); return; } } // No visible variables lc.IL.Emit(OpCodes.Call, typeof(RuntimeOps).GetMethod("CreateRuntimeVariables", Type.EmptyTypes)); return; }
public void Quote_RuntimeVariables_Hoisted2() { var x = Expression.Parameter(typeof(int)); var variables = new ReadOnlyCollection <ParameterExpression>(Array.Empty <ParameterExpression>()); var definitions = new Dictionary <ParameterExpression, StorageKind>(); var h = new HoistedLocals(parent: null, variables, definitions); var e = Expression.Lambda <Func <int, IRuntimeVariables> >(Expression.RuntimeVariables(x), x); Assert.AreEqual(typeof(Empty), h.Closure.ClosureType); var c = new Empty(); var q = (Expression <Func <int, IRuntimeVariables> >)RuntimeOpsEx.Quote(e, h, c); var f = q.Compile(); Assert.AreEqual(42, f(42)[0]); Assert.AreEqual(1, f(41).Count); var r = f(43); r[0] = 44; Assert.AreEqual(44, r[0]); }
/// <summary> /// Resolve a local variable in this scope or a closed over scope /// Throws if the variable is defined /// </summary> private Storage ResolveVariable(ParameterExpression variable, HoistedLocals hoistedLocals) { // Search IL locals and arguments, but only in this lambda for (CompilerScope s = this; s != null; s = s._parent) { Storage storage; if (s._locals.TryGetValue(variable, out storage)) { return storage; } // if this is a lambda, we're done if (s.IsMethod) { break; } } // search hoisted locals for (HoistedLocals h = hoistedLocals; h != null; h = h.Parent) { int index; if (h.Indexes.TryGetValue(variable, out index)) { return new ElementBoxStorage( ResolveVariable(h.SelfVariable, hoistedLocals), index, variable ); } } // // If this is an unbound variable in the lambda, the error will be // thrown from VariableBinder. So an error here is generally caused // by an internal error, e.g. a scope was created but it bypassed // VariableBinder. // throw Error.UndefinedVariable(variable.Name, variable.Type, CurrentLambdaName); }
/// <summary> /// Frees unnamed locals, clears state associated with this compiler /// </summary> internal CompilerScope Exit() { // free scope's variables if (!IsMethod) { foreach (Storage storage in _locals.Values) { storage.FreeLocal(); } } // Clear state that is associated with this parent // (because the scope can be reused in another context) CompilerScope parent = _parent; _parent = null; _hoistedLocals = null; _closureHoistedLocals = null; _locals.Clear(); return parent; }
internal ExpressionQuoter(HoistedLocals scope, object[] locals) { _scope = scope; _locals = locals; }
/// <summary> /// Creates a new expression quoter. /// </summary> /// <param name="locals">The hoisted locals information gathered by the compiler at compile time.</param> /// <param name="closure">The closure to access for the binding of hoisted variables.</param> public ExpressionQuoter(HoistedLocals locals, IRuntimeVariables closure) { _locals = locals; _closure = closure; }