private void Reference(ParameterExpression node, VariableStorageKind storage) { CompilerScope?definition = null; foreach (CompilerScope scope in _scopes) { if (scope.Definitions.ContainsKey(node)) { definition = scope; break; } scope.NeedsClosure = true; if (scope.IsMethod) { storage = VariableStorageKind.Hoisted; } } if (definition == null) { throw Error.UndefinedVariable(node.Name, node.Type, CurrentLambdaName); } if (storage == VariableStorageKind.Hoisted) { if (node.IsByRef) { throw Error.CannotCloseOverByRef(node.Name, CurrentLambdaName); } definition.Definitions[node] = VariableStorageKind.Hoisted; } }
protected internal override Expression VisitRuntimeVariables(RuntimeVariablesExpression node) { foreach (var v in node.Variables) { // Force hoisting of these variables CompilerScope?definition = null; foreach (var scope in _scopes) { if (scope.Definitions.ContainsKey(v)) { definition = scope; break; } scope.NeedsClosure = true; } if (definition == null) { throw new InvalidOperationException($"variable '{v.Name}' of type '{v.Type}' referenced from scope '{CurrentLambdaName}', but it is not defined"); } if (v.IsByRef) { throw new InvalidOperationException($"Cannot close over byref parameter '{v.Name}' referenced in lambda '{CurrentLambdaName}'"); } definition.Definitions[v] = VariableStorageKind.Hoisted; } return(node); }
protected internal override Expression VisitParameter(ParameterExpression node) { Reference(node, VariableStorageKind.Local); // // Track reference count so we can emit it in a more optimal way if // it is used a lot. // CompilerScope?referenceScope = null; foreach (CompilerScope scope in _scopes) { // // There are two times we care about references: // 1. When we enter a lambda, we want to cache frequently // used variables // 2. When we enter a scope with closed-over variables, we // want to cache it immediately when we allocate the // closure slot for it // if (scope.IsMethod || scope.Definitions.ContainsKey(node)) { referenceScope = scope; break; } } Debug.Assert(referenceScope != null); referenceScope.ReferenceCount ??= new Dictionary <ParameterExpression, int>(); Helpers.IncrementCount(node, referenceScope.ReferenceCount); return(node); }
protected internal override Expression VisitParameter(ParameterExpression node) { var storage = VariableStorageKind.Local; CompilerScope?definition = null; CompilerScope?referenceScope = null; foreach (var scope in _scopes) { if (scope.Definitions.ContainsKey(node)) { definition = scope; break; } scope.NeedsClosure = true; if (!scope.IsMethod) { continue; } storage = VariableStorageKind.Hoisted; referenceScope = scope; } if (definition == null) { throw new InvalidOperationException($"variable '{node.Name}' of type '{node.Type}' referenced from scope '{CurrentLambdaName}', but it is not defined"); } if (storage == VariableStorageKind.Hoisted) { if (node.IsByRef) { throw new InvalidOperationException($"Cannot close over byref parameter '{node.Name}' referenced in lambda '{CurrentLambdaName}'"); } definition.Definitions[node] = VariableStorageKind.Hoisted; } referenceScope ??= definition; (referenceScope.ReferenceCount ?? (referenceScope.ReferenceCount = new Dictionary <ParameterExpression, int>())).TryGetValue(node, out var count); referenceScope.ReferenceCount[node] = count + 1; return(node); }
private void EmitLambdaBody(CompilerScope?parent, bool inlined, CompilationFlags flags) { _scope.Enter(this, parent); if (inlined) { // The arguments were already pushed onto the IL stack. // Store them into locals, popping in reverse order. // // If any arguments were ByRef, the address is on the stack and // we'll be storing it into the variable, which has a ref type. for (var i = _lambda.ParameterCount - 1; i >= 0; i--) { _scope.EmitSet(_lambda.GetParameter(i)); } } // Need to emit the expression start for the lambda body flags = UpdateEmitExpressionStartFlag(flags, CompilationFlags.EmitExpressionStart); if (_lambda.ReturnType == typeof(void)) { EmitExpressionAsVoid(_lambda.Body, flags); } else { EmitExpression(_lambda.Body, flags); } // Return must be the last instruction in a CLI method. // But if we're inlining the lambda, we want to leave the return // value on the IL stack. if (!inlined) { IL.Emit(OpCodes.Ret); } _scope.Exit(); // Validate labels Debug.Assert(_labelBlock.Parent == null && _labelBlock.Kind == LabelScopeKind.Lambda); foreach (var label in _labelInfo.Values) { label.ValidateFinish(); } }
private void SetParent(LambdaCompiler lc, CompilerScope?parent) { Debug.Assert(_parent == null && parent != this); _parent = parent; if (NeedsClosure && _parent != null) { _closureHoistedLocals = _parent.NearestHoistedLocals; } ReadOnlyCollection <ParameterExpression> hoistedVars = GetVariables().Where(p => Definitions[p] == VariableStorageKind.Hoisted).ToReadOnly(); if (hoistedVars.Count > 0) { _hoistedLocals = new HoistedLocals(_closureHoistedLocals, hoistedVars); AddLocal(lc, _hoistedLocals.SelfVariable); } }
/// <summary> /// Frees unnamed locals, clears state associated with this compiler /// </summary> internal void Exit() { // free scope's variables if (!IsMethod) { foreach (var storage in _locals.Values) { storage.FreeLocal(); } } // Clear state that is associated with this parent // (because the scope can be reused in another context) _parent = null; _hoistedLocals = null; _closureHoistedLocals = null; _locals.Clear(); }
/// <summary> /// Called when entering a lambda/block. Performs all variable allocation /// needed, including creating hoisted locals and IL locals for accessing /// parent locals /// </summary> internal CompilerScope Enter(LambdaCompiler lc, CompilerScope?parent) { SetParent(lc, parent); AllocateLocals(lc); if (IsMethod && _closureHoistedLocals != null) { EmitClosureAccess(lc, _closureHoistedLocals); } EmitNewHoistedLocals(lc); if (IsMethod) { EmitCachedVariables(); } return(this); }
/// <summary> /// Resolve a local variable in this scope or a closed over scope /// Throws if the variable is not 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) { if (s._locals.TryGetValue(variable, out Storage? 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); }