CompilerScope is the data structure which the Compiler keeps information related to compiling scopes. It stores the following information: 1. Parent relationship (for resolving variables) 2. Information about hoisted variables 3. Information for resolving closures Instances are produced by VariableBinder, which does a tree walk looking for scope nodes: LambdaExpression and BlockExpression.
        private void EnterScope(object node) {
            if (HasVariables(node) &&
                (_scope.MergedScopes == null || !_scope.MergedScopes.Contains(node))) {

                CompilerScope scope;
                if (!_tree.Scopes.TryGetValue(node, out scope)) {
                    //
                    // Very often, we want to compile nodes as reductions
                    // rather than as IL, but usually they need to allocate
                    // some IL locals. To support this, we allow emitting a
                    // BlockExpression that was not bound by VariableBinder.
                    // This works as long as the variables are only used
                    // locally -- i.e. not closed over.
                    //
                    // User-created blocks will never hit this case; only our
                    // internally reduced nodes will.
                    //
                    scope = new CompilerScope(node, false) { NeedsClosure = _scope.NeedsClosure };
                }

                _scope = scope.Enter(this, _scope);
                Debug.Assert(_scope.Node == node);
            }
        }
Example #2
0
        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);
            if (referenceScope.ReferenceCount == null)
            {
                referenceScope.ReferenceCount = new Dictionary <ParameterExpression, int>();
            }

            Helpers.IncrementCount(node, referenceScope.ReferenceCount);
            return(node);
        }
Example #3
0
        /// <summary>
        /// Creates a lambda compiler that will compile into the provided Methodbuilder
        /// </summary>
        private LambdaCompiler(AnalyzedTree tree, LambdaExpression lambda, MethodBuilder method)
        {
            var scope = tree.Scopes[lambda];
            var hasClosureArgument = scope.NeedsClosure;

            Type[] paramTypes = GetParameterTypes(lambda);
            if (hasClosureArgument)
            {
                paramTypes = paramTypes.AddFirst(typeof(Closure));
            }

            method.SetReturnType(lambda.ReturnType);
            method.SetParameters(paramTypes);
            var paramNames = lambda.Parameters.Map(p => p.Name);
            // parameters are index from 1, with closure argument we need to skip the first arg
            int startIndex = hasClosureArgument ? 2 : 1;

            for (int i = 0; i < paramNames.Length; i++)
            {
                method.DefineParameter(i + startIndex, ParameterAttributes.None, paramNames[i]);
            }

            _tree               = tree;
            _lambda             = lambda;
            _typeBuilder        = (TypeBuilder)method.DeclaringType.GetTypeInfo();
            _method             = method;
            _hasClosureArgument = hasClosureArgument;

            _ilg = method.GetILGenerator();

            // These are populated by AnalyzeTree/VariableBinder
            _scope          = scope;
            _boundConstants = tree.Constants[lambda];

            InitializeMethod();
        }
 private Storage ResolveVariable(ParameterExpression variable, HoistedLocals hoistedLocals)
 {
     for (CompilerScope scope = this; scope != null; scope = scope._parent)
     {
         Storage storage;
         if (scope._locals.TryGetValue(variable, out storage))
         {
             return(storage);
         }
         if (scope.IsMethod)
         {
             break;
         }
     }
     for (HoistedLocals locals = hoistedLocals; locals != null; locals = locals.Parent)
     {
         int num;
         if (locals.Indexes.TryGetValue(variable, out num))
         {
             return(new ElementBoxStorage(this.ResolveVariable(locals.SelfVariable, hoistedLocals), num, variable));
         }
     }
     throw Error.UndefinedVariable(variable.Name, variable.Type, this.CurrentLambdaName);
 }
Example #5
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.IsLambda)
                {
                    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 a genuine unbound variable, the error should be
            // thrown in VariableBinder.
            throw Error.UndefinedVariable(variable.Name, variable.Type, CurrentLambdaName);
        }
 internal virtual void EmitStore(CompilerScope.Storage value)
 {
     value.EmitLoad();
     this.EmitStore();
 }
 internal override void EmitStore(CompilerScope.Storage value)
 {
     base.Compiler.IL.Emit(OpCodes.Ldloc, this._boxLocal);
     value.EmitLoad();
     base.Compiler.IL.Emit(OpCodes.Stfld, this._boxValueField);
 }
 internal ElementBoxStorage(CompilerScope.Storage array, int index, ParameterExpression variable) : base(array.Compiler, variable)
 {
     this._array = array;
     this._index = index;
     this._boxType = typeof(StrongBox<>).MakeGenericType(new Type[] { variable.Type });
     this._boxValueField = this._boxType.GetField("Value");
 }
 private void SetParent(LambdaCompiler lc, CompilerScope parent)
 {
     this._parent = parent;
     if (this.NeedsClosure && (this._parent != null))
     {
         this._closureHoistedLocals = this._parent.NearestHoistedLocals;
     }
     ReadOnlyCollection<ParameterExpression> vars = (from p in this.GetVariables()
         where ((VariableStorageKind) this.Definitions[p]) == VariableStorageKind.Hoisted
         select p).ToReadOnly<ParameterExpression>();
     if (vars.Count > 0)
     {
         this._hoistedLocals = new HoistedLocals(this._closureHoistedLocals, vars);
         this.AddLocal(lc, this._hoistedLocals.SelfVariable);
     }
 }
 internal CompilerScope Exit()
 {
     if (!this.IsMethod)
     {
         foreach (Storage storage in this._locals.Values)
         {
             storage.FreeLocal();
         }
     }
     CompilerScope scope = this._parent;
     this._parent = null;
     this._hoistedLocals = null;
     this._closureHoistedLocals = null;
     this._locals.Clear();
     return scope;
 }
 internal CompilerScope Enter(LambdaCompiler lc, CompilerScope parent)
 {
     this.SetParent(lc, parent);
     this.AllocateLocals(lc);
     if (this.IsMethod && (this._closureHoistedLocals != null))
     {
         this.EmitClosureAccess(lc, this._closureHoistedLocals);
     }
     this.EmitNewHoistedLocals(lc);
     if (this.IsMethod)
     {
         this.EmitCachedVariables();
     }
     return this;
 }
Example #12
0
        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);
            }
        }
Example #13
0
        /// <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;
        }
Example #14
0
        /// <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;
        }
        private (CompilerScope parent, CompilerScope child)? GetInnerScope(object node, CompilerScope scope)
        {
            //
            // Very often, we want to compile nodes as reductions
            // rather than as IL, but usually they need to allocate
            // some IL locals. To support this, we allow emitting a
            // BlockExpression that was not bound by VariableBinder.
            // This works as long as the variables are only used
            // locally -- i.e. not closed over.
            //
            // User-created blocks will never hit this case; only our
            // internally reduced nodes will.
            //

            return(!HasVariables(node) || scope.MergedScopes?.Contains(node as BlockExpression) == true
                ? default
                :
                   (
                       scope,
                       _tree.Scopes.TryGetValue(node, out var innerScope)
                        ? innerScope
                        : new CompilerScope(node, false)
            {
                NeedsClosure = scope.NeedsClosure
            }
                   ));
        }