Example #1
0
        private void ImplementCreateChildren(CompilationContext ctx, TypeBuilder t, IBranch root)
        {
            var relevantCumulations = ctx.ScheduledCumulations.ContainsKey(root) ? 
                ctx.ScheduledCumulations[root].Where(co => !(co.Subject.IsFov() || co.Subject.Parent.IsFov())) : null;
            var special = ctx.CumulativeCompilation && relevantCumulations != null;
            if (special && relevantCumulations.IsEmpty()) return;

            var base_cc = t.BaseType.GetMethod("CreateChildren", BF.All);
            var cc = t.DefineOverride(base_cc);
            if (!base_cc.IsAbstract) cc.il().ldarg(0).call(base_cc);
            ctx.CCs[t] = cc;

            var svd = root.GetBranches().Where(b => b.Name == "_sourceValueDeclarations");
            var formulae = root.GetBranches().Where(b => b.Name == "_formulaDeclarations");
            var conditions = root.GetBranches().Where(b => b.Name == "_conditions");
            var nodes = root.GetBranches().Except(svd).Except(formulae).Except(conditions).ToArray();
            if (special) nodes = nodes.Where(b => relevantCumulations.Any(co => co.Subject == b)).ToArray();

            foreach (var b in nodes)
            {
                var b_type = CompileNode(ctx, b);

                var f_child = t.DefineField("_" + b.GetPropertyName().ToLower(), b_type, FA.Private);
                var p_child = t.DefineProperty(b.GetPropertyName(), PropA.None, b_type, new Type[0]);
                p_child.SetCustomAttribute(new CustomAttributeBuilder(
                    typeof(VPathAttribute).GetConstructor(typeof(String).MkArray()),
                    b.VPath.ToString().MkArray()));

                var get = t.DefineMethod("get_" + b.GetPropertyName(), MA.PublicProp, b_type, new Type[0]);
                p_child.SetGetMethod(get);

                Label nodeWasCreatedByTheFactory;
                get.il()
                   .ldarg(0)
                   .ldfld(f_child)

                   // here we check whether the factory returned a valid (non-null) node
                   // if the latter is false, then the node has been deleted during cumulative recompilation
                   // and we need to crash with the same message as CompiledNode.Child(vpath)
                   .def_label(out nodeWasCreatedByTheFactory)
                   .brtrue(nodeWasCreatedByTheFactory)
                   .ldstr(String.Format("There's no compiled node at VPath '{0}'.", b.VPath))
                   .@throw(typeof(NotImplementedException), typeof(String))

                   .label(nodeWasCreatedByTheFactory)
                   .ldarg(0)
                   .ldfld(f_child)
                   .ret();

                cc.il()
                  .ldarg(0)
                  .ldarg(0)
                  .callvirt(typeof(CompiledNode).GetProperty("Root", BF.All).GetGetMethod(true))
                  .ldstr(b.VPath.ToString())
                  .newobj(typeof(VPath), typeof(String))
                  .ldarg(0)
                  .callvirt(typeof(CompiledScenario).GetMethod("CreateNode", BF.All))
                  .stfld(f_child)
                  .ldarg(0)
                  .callvirt(typeof(CompiledNode).GetProperty("Children").GetGetMethod())
                  .ldarg(0)
                  .ldfld(f_child)
                  .callvirt(typeof(CompiledNodeCollection).GetMethod("Add"));
            }

            cc.il().ret();
        }
Example #2
0
        private void ImplementCreateProperties(CompilationContext ctx, TypeBuilder t, IBranch root)
        {
            var relevantCumulations = ctx.ScheduledCumulations.ContainsKey(root) ?
                ctx.ScheduledCumulations[root].Where(co => co.Subject.IsFov() || co.Subject.Parent.IsFov()) : null;
            var special = ctx.CumulativeCompilation && relevantCumulations != null;
            if (special && relevantCumulations.IsEmpty()) return;

            var base_cp = t.BaseType.GetMethod("CreateProperties", BF.All);
            var cp = t.DefineOverride(base_cp);
            if (!base_cp.IsAbstract) cp.il().ldarg(0).call(base_cp);
            ctx.CPs[t] = cp;

            var svd = root.GetBranches().Where(b => b.Name == "_sourceValueDeclarations");
            var formulae = root.GetBranches().Where(b => b.Name == "_formulaDeclarations");
            var conditions = root.GetBranches().Where(b => b.Name == "_conditions");
            var nodes = root.GetBranches().Except(svd).Except(formulae).Except(conditions);
            var svdAndFormulae = svd.Concat(formulae).SelectMany(b => b.GetBranches()).ToArray();
            if (special) svdAndFormulae = svdAndFormulae.Where(b => 
                relevantCumulations.Any(co => co.Subject == b || co.Subject.Parent == b)).ToArray();

            var propGetCache = new Dictionary<VPath, MethodInfo>();
            var fieldCache = new Dictionary<VPath, FieldBuilder>();
            var nameCache = new HashSet<String>();
            Action<IBranch, bool> ensureProperty = (b, external) =>
            {
                if (propGetCache.ContainsKey(b.VPath))
                {
                    return;
                }
                else
                {
                    var typeToken = b.GetValue("type").ContentString;
                    var propType = typeToken.GetTypeFromToken();

                    var baseProp = t.BaseType.GetProperties(BF.All).SingleOrDefault(
                        p => p.HasAttr<VPathAttribute>() && p.Attr<VPathAttribute>().VPath == b.VPath);
                    var basePropOk = baseProp != null && baseProp.PropertyType == propType;

                    String name;
                    if (basePropOk)
                    {
                        name = baseProp.Name;
                    }
                    else
                    {
                        var desiredName = b.GetPropertyName();
                        name = desiredName;

                        var i = 0;
                        while (nameCache.Contains(name))
                        {
                            name = desiredName + "~" + ++i;
                        }

                        nameCache.Add(b.GetPropertyName());
                    }

                    if (external)
                    {
                        if (basePropOk)
                        {
                            propGetCache.Add(b.VPath, baseProp.GetGetMethod(true));
                        }
                        else
                        {
                            var p_prop = t.DefineProperty(name, PropA.None, propType, new Type[0]);
                            p_prop.SetCustomAttribute(new CustomAttributeBuilder(
                                typeof(VPathAttribute).GetConstructor(typeof(String).MkArray()),
                                b.VPath.ToString().MkArray()));

                            var get = t.DefineMethod("get_" + name, MA.ProtectedProp, propType, new Type[0]);
                            get.il()
                               .ldarg(0)
                               .callvirt(typeof(CompiledNode).GetProperty("Root").GetGetMethod())
                               .ldstr(b.VPath.ToString())
                               .newobj(typeof(VPath), typeof(String))
                               .callvirt(typeof(ICompiledNode).GetMethod("Eval"))
                               .ret();

                            p_prop.SetGetMethod(get);
                            propGetCache.Add(b.VPath, get);
                        }
                    }
                    else
                    {
                        var p_prop = t.DefineProperty(name, PropA.None, propType, new Type[0]);
                        p_prop.SetCustomAttribute(new CustomAttributeBuilder(
                            typeof(VPathAttribute).GetConstructor(typeof(String).MkArray()),
                            b.VPath.ToString().MkArray()));

                        MethodBuilder get;
                        if (basePropOk)
                        {
                            var baseGet = baseProp.GetGetMethod(true);
                            get = t.DefineOverride(baseGet);
                            p_prop.SetGetMethod(get);
                        }
                        else
                        {
                            get = t.DefineMethod("get_" + name, MA.PublicProp, propType, new Type[0]);
                            p_prop.SetGetMethod(get);
                        }

                        propGetCache.Add(b.VPath, get);
                        fieldCache.Add(b.VPath, t.DefineField("_" + name.ToLower(), propType, FA.Private));
                    }
                }
            };

            Action<IBranch> updateCp = b =>
            {
                var get = propGetCache[b.VPath];
                cp.il()
                  .ldarg(0)
                  .callvirt(typeof(CompiledNode).GetProperty("Properties").GetGetMethod())
                  .ldarg(0)
                  .ldstr(b.GetPropertyName())
                  .ldstr(b.VPath)
                  .newobj(typeof(VPath), typeof(String))
                  .ldarg(0)
                  .ldftn(get)
                  .newobj(typeof(Func<IEsathObject>), typeof(Object), typeof(IntPtr))
                  .newobj(typeof(CompiledProperty), typeof(CompiledNode), typeof(String), typeof(VPath), typeof(Func<IEsathObject>))
                  .callvirt(typeof(CompiledPropertyCollection).GetMethod("Add"));
            };

            // handle deleted flae and svds i.e. create CompiledNode.Eval-like crash
            if (special)
            {
                foreach (IBranch deleted in relevantCumulations.Where(co => co.Reason == EventReason.Remove).Select(co => co.Subject))
                {
                    ensureProperty(deleted, false);
                    var get = (MethodBuilder)propGetCache[deleted.VPath];
                    get.il()
                       .ldstr(String.Format("There's no compiled property at VPath '{0}'.", deleted.VPath))
                       .@throw(typeof(NotImplementedException), typeof(String));
                    updateCp(deleted);
                }
            }

            // define all properties in advance so that we can reference them when needed
            svdAndFormulae.ForEach(b => ensureProperty(b, false));

            // implement defined properties
            foreach (var b in svdAndFormulae)
            {
                var typeToken = b.GetValue("type").ContentString;
                var propType = typeToken.GetTypeFromToken();

                var f_prop = fieldCache[b.VPath];
                var get = (MethodBuilder)propGetCache[b.VPath];

                // if a formula has just been created (i.e. has null elfCode), then we just generate notimplemented stuff
                // todo. this is a particular case of graceful dealing with invalid elf code
                // a solid approach would also handle such stuff as: non-existing references, resolving to invalid methods
                // and i think something else (needs thorough checking)
                // note. when implementing that stuff, be sure to burn the failboat elf code + the failure reason right into the assembly code
                // so that one can analyze the formula by him/herself and find the reason of the failure

                if (b.IsFormula())
                {
                    var host = b.GetValue("elfCode");
                    var code = host == null ? null : host.ContentString;
                    var elfCode = code == null ? null : code.ToCanonicalElf();

                    if (elfCode == null)
                    {
                        get.il().@throw(typeof(NotImplementedException));
                        updateCp(b);
                        continue;
                    }
                }

                Label cacheOk;
                LocalBuilder loc_cache, loc_vpath, loc_result;
                get.il()
                   .ldarg(0)
                   .callvirt(typeof(CompiledNode).GetProperty("Root").GetGetMethod())
                   .ldfld(typeof(CompiledScenario).GetField("CachedPropertiesRegistry", BF.All))
                   .def_local(typeof(HashSet<VPath>), out loc_cache)
                   .stloc(loc_cache)
                   .ldstr(b.VPath.ToString())
                   .newobj(typeof(VPath), typeof(String))
                   .def_local(typeof(VPath), out loc_vpath)
                   .stloc(loc_vpath)
                   .ldloc(loc_cache)
                   .ldloc(loc_vpath)
                   .callvirt(typeof(HashSet<VPath>).GetMethod("Contains"))
                   .def_label(out cacheOk)
                   .brtrue(cacheOk)

                   // here svd and flae codegen will store the evaluation result
                   .def_local(propType, out loc_result);

                if (b.IsSvd())
                {
                    Label isRuntime, stlocRuntime, isDesignTime, stlocDesignTime, coalesce;
                    LocalBuilder runtime, designTime;
                    get.il()

                        // runtime -> value is stored in the repository
                       .ldarg(0)
                       .callvirt(typeof(CompiledNode).GetProperty("Repository", BF.All).GetGetMethod(true))
                       .def_label(out stlocRuntime)
                       .def_label(out isRuntime)
                       .def_local(typeof(String), out runtime)
                       .brtrue_s(isRuntime)
                       .ldnull()
                       .br_s(stlocRuntime)
                       .label(isRuntime)
                       .ldarg(0)
                       .callvirt(typeof(CompiledNode).GetProperty("Repository", BF.All).GetGetMethod(true))
                       .ldarg(0)
                       .callvirt(typeof(CompiledNode).GetProperty("Scenario", BF.All).GetGetMethod(true))
                       .ldstr(b.VPath.ToString())
                       .newobj(typeof(VPath), typeof(String))
                       .callvirt(typeof(CachedVault).GetMethod("GetBranch"))
                       .ldstr("repositoryValue")
                       .newobj(typeof(VPath), typeof(String))
                       .callvirt(typeof(IBranch).GetMethod("GetValue"))
                       .callvirt(typeof(IValue).GetProperty("ContentString").GetGetMethod())
                       .newobj(typeof(VPath), typeof(String))
                       .callvirt(typeof(CachedVault).GetMethod("GetValue"))
                       .callvirt(typeof(IValue).GetProperty("ContentString").GetGetMethod())
                       .label(stlocRuntime)
                       .stloc(runtime)

                        // design time -> value is stored directly in the scenario next to the node
                       .ldarg(0)
                       .callvirt(typeof(CompiledNode).GetProperty("Repository", BF.All).GetGetMethod(true))
                       .def_label(out stlocDesignTime)
                       .def_label(out isDesignTime)
                       .def_local(typeof(String), out designTime)
                       .brfalse_s(isDesignTime)
                       .ldnull()
                       .br_s(stlocDesignTime)
                       .label(isDesignTime)
                       .ldarg(0)
                       .callvirt(typeof(CompiledNode).GetProperty("Scenario", BF.All).GetGetMethod(true))
                       .ldstr(b.VPath.ToString())
                       .newobj(typeof(VPath), typeof(String))
                       .callvirt(typeof(CachedVault).GetMethod("GetBranch"))
                       .ldstr("valueForTesting")
                       .newobj(typeof(VPath), typeof(String))
                       .callvirt(typeof(IBranch).GetMethod("GetValue"))
                       .callvirt(typeof(IValue).GetProperty("ContentString").GetGetMethod())
                       .label(stlocDesignTime)
                       .stloc(designTime)

                       // prepare for convert
                       .ldtoken(propType)
                       .callvirt(typeof(Type).GetMethod("GetTypeFromHandle"))
                       .callvirt(typeof(Elf.Helpers.ReflectionHelper).GetMethod("ElfDeserializer"))

                        // coalesce designtime and runtime values
                       .ldloc(designTime)
                       .dup()
                       .def_label(out coalesce)
                       .brtrue_s(coalesce)
                       .pop()
                       .ldloc(runtime)
                       .label(coalesce)

                       // convert the string to esath object
                       .callvirt(typeof(Func<String, IElfObject>).GetMethod("Invoke"))
                       .stloc(loc_result);
                }
                else if (b.IsFormula())
                {
                    var host = b.GetValue("elfCode");
                    var code = host == null ? null : host.ContentString;
                    var elfCode = code == null ? null : code.ToCanonicalElf();
                    elfCode.AssertNotNull(); // this is guaranteed by the check above (at the start of this method)

                    Label retTarget;
                    get.il().def_label(out retTarget);

                    var vm = new VirtualMachine();
                    vm.Load(elfCode);
                    var evis = ((NativeMethod)vm.Classes.Last().Methods.Single()).Body;

                    Func<String, VPath> idToVpath = elfid =>
                    { try { return elfid.FromElfIdentifier(); } catch { return null; } };

                    Func<String, IEsathObject> elfStrToEsath = elfStr =>
                    { try { return elfStr.FromStorageString(); } catch { return null; } };

                    var il = get.il();
                    var evalStack = new Stack<Type>();

                    Type auxResultType = null;
                    for (var j = 0; j < evis.Length; j++)
                    {
                        var evi = evis[j];
                        if (evi is Decl)
                        {
                            throw new NotSupportedException(
                                String.Format("The 'decl' instructions is not supported."));
                        }
                        else if (evi is Dup)
                        {
                            if (j < evis.Length - 1 && evis[j + 1] is PopRef)
                            {
                                // compile [dup, popref, pop] combo as simply [popref]
                                // since so far constructs like a = b = c are not used
                            }
                            else
                            {
                                var dupe = evalStack.Peek();
                                evalStack.Push(dupe);
                                il.dup();
                            }
                        }
                        else if (evi is Enter)
                        {
                            // do nothing
                        }
                        else if (evi is Invoke)
                        {
                            var invoke = (Invoke)evi;
                            var args = new Type[invoke.Argc];
                            for (var i = invoke.Argc - 1; i >= 0; --i)
                                args[i] = evalStack.Pop();

                            var resolved = DefaultInvocationResolver.Resolve(
                                vm,
                                invoke.Name,
                                vm.Classes.Last(),
                                args.Select(arg => vm.Classes.Single(c => c.Name == Elf.Helpers.ReflectionHelper.RtimplOf(arg))).ToArray());
                            if (resolved == null)
                            {
                                throw new NotSupportedException(String.Format(
                                    "The '{0}' instruction is not supported. " +
                                    "Cannot resolve invocation '{1}({2}).",
                                    invoke, invoke.Name, args.Select(arg => Elf.Helpers.ReflectionHelper.RtimplOf(arg)).StringJoin()));
                            }
                            else
                            {
                                if (!(resolved is ClrMethod))
                                {
                                    throw new NotSupportedException(String.Format(
                                        "The '{0}' instruction is not supported. " +
                                        "Invocation '{1}({2}) has been resolved to a native method '{3}'.",
                                        invoke, invoke.Name, args.Select(arg => Elf.Helpers.ReflectionHelper.RtimplOf(arg)).StringJoin(), resolved));
                                }
                                else
                                {
                                    var clrMethod = (MethodInfo)((ClrMethod)resolved).Rtimpl;
                                    if (clrMethod.ReturnType == typeof(void))
                                    {
                                        throw new NotSupportedException(String.Format(
                                            "The '{0}' instruction is not supported. " +
                                            "Invocation '{1}({2}) has been resolved to a void-returning method '{3}'.",
                                            invoke, invoke.Name, args.Select(arg => Elf.Helpers.ReflectionHelper.RtimplOf(arg)).StringJoin(), clrMethod));
                                    }
                                    else
                                    {
                                        il.callvirt(clrMethod);
                                        evalStack.Push(clrMethod.ReturnType);
                                    }
                                }
                            }
                        }
                        else if (evi is Jf)
                        {
                            throw new NotSupportedException(
                                String.Format("The 'jf' instruction is not supported."));
                        }
                        else if (evi is Jt)
                        {
                            throw new NotSupportedException(
                                String.Format("The 'jt' instruction is not supported."));
                        }
                        else if (evi is Elf.Core.Assembler.Label)
                        {
                            throw new NotSupportedException(
                                String.Format("The 'label' instruction is not supported."));
                        }
                        else if (evi is Leave)
                        {
                            // do nothing
                        }
                        else if (evi is Pop)
                        {
                            if (j > 0 && evis[j - 1] is PopRef)
                            {
                                // compile [dup, popref, pop] combo as simply [popref]
                                // since so far constructs like a = b = c are not used
                            }
                            else
                            {
                                il.pop();
                                evalStack.Pop();
                            }
                        }
                        else if (evi is PopAll)
                        {
                            evalStack.ForEach(t1 => { evalStack.Pop(); il.pop(); });
                        }
                        else if (evi is PopRef)
                        {
                            var popref = (PopRef)evi;

                            var vpath = idToVpath(popref.Ref);
                            if (vpath == null)
                            {
                                throw new NotSupportedException(String.Format(
                                    "The '{0}' instruction is not supported.", popref));
                            }
                            else
                            {
                                if (vpath != b.VPath)
                                {
                                    throw new NotSupportedException(String.Format(
                                        "The 'popref <vpath>' instruction is supported only " +
                                        "when vpath represents the current node. Current instruction " +
                                        "'{0}' is not supported", popref));
                                }
                                else
                                {
                                    auxResultType = evalStack.Pop();
                                    // todo. ensure that nothing significant follows this instruction
                                }
                            }
                        }
                        else if (evi is PushRef)
                        {
                            var pushref = (PushRef)evi;

                            var vpath = idToVpath(pushref.Ref);
                            if (vpath == null)
                            {
                                throw new NotSupportedException(String.Format(
                                    "The '{0}' instruction is not supported.", pushref));
                            }
                            else
                            {
                                if (!propGetCache.ContainsKey(vpath))
                                {
                                    var branch = Vault.GetBranch(vpath);
                                    if (branch == null)
                                    {
                                        throw new NotSupportedException(String.Format(
                                            "The 'pushref <vpath>' instruction is supported only " +
                                            "when vpath represents the svd or flae node. Current vpath '{0}' " +
                                            "doesn't reference any node in the scenario being compiled.", vpath));
                                    }

                                    if (!branch.IsFov())
                                    {
                                       throw new NotSupportedException(String.Format(
                                            "The 'pushref <vpath>' instruction is supported only " +
                                            "when vpath represents an svd or a flae node. Current branch " +
                                            "'{0}' at vpath '{1}' is not supported.", branch.Name, branch.VPath));
                                    }

                                    ensureProperty(branch, true);
                                }

                                var prop = propGetCache[vpath];
                                il.ldarg(0).callvirt(prop);
                                evalStack.Push(prop.ReturnType);
                            }
                        }
                        else if (evi is PushVal)
                        {
                            var pushval = (PushVal)evi;

                            if (!(pushval.Val is ElfStringLiteral) ||
                                (elfStrToEsath(((ElfStringLiteral)pushval.Val).Val)) == null)
                            {
                                throw new NotSupportedException(String.Format(
                                    "The 'pushval <val>' instruction is supported only " +
                                    "when val represents correctly encoded esath object. Current instruction " +
                                    "'{0}' is not supported", pushval));
                            }
                            else
                            {
                                var storageString = ((ElfStringLiteral)pushval.Val).Val;
                                var match = Regex.Match(storageString, @"^\[\[(?<token>.*?)\]\](?<content>.*)$");
                                match.Success.AssertTrue();

                                if (match.Success)
                                {
                                    var token = match.Result("${token}");
                                    var content = match.Result("${content}");

                                    il.ldtoken(token.GetTypeFromToken())
                                      .callvirt(typeof(Type).GetMethod("GetTypeFromHandle"))
                                      .callvirt(typeof(Elf.Helpers.ReflectionHelper).GetMethod("ElfDeserializer"))
                                      .ldstr(content)
                                      .callvirt(typeof(Func<String, IElfObject>).GetMethod("Invoke"));

                                    evalStack.Push(token.GetTypeFromToken());
                                }
                            }
                        }
                        else if (evi is Ret)
                        {
                            evalStack.IsEmpty().AssertTrue();
                            il.br_s(retTarget);
                        }
                        else
                        {
                            throw new NotSupportedException(String.Format(
                                "The '{0}' instruction is not supported.", evi));
                        }
                    }

                    if (auxResultType == propType)
                    {
                        il.label(retTarget)
                          .stloc(loc_result);
                    }
                    else
                    {
                        LocalBuilder auxResult;
                        var valPropGet = auxResultType.GetProperty("Val").GetGetMethod();

                        il.label(retTarget)
                          .def_local(auxResultType, out auxResult)
                          .stloc(auxResult)
                          .ldloc(auxResult)
                          .callvirt(valPropGet);

                        if (typeof(ElfNumber).IsAssignableFrom(auxResultType) &&
                            propType == typeof(EsathPercent))
                        {
                            // ldc_r8 is a must, since ldc_i4 performs integer multiplication
                            // and fails stuff like 1.2 * 100 (the result will be 100)

                            il.ldc_r8(100)
                              .mul();
                        }

                        il.convert(valPropGet.ReturnType, propType)
                          .stloc(loc_result);
                    }
                }
                else
                {
                    throw new NotSupportedException(
                        String.Format("Properties of type '{0}' are not supported", b));
                }

                get.il()
                   // the value is stored in a local after svd or flae codegen
                   .ldarg(0)
                   .ldloc(loc_result)
                   .stfld(f_prop)
                   .ldloc(loc_cache)
                   .ldloc(loc_vpath)
                   .callvirt(typeof(HashSet<VPath>).GetMethod("Add"))
                   .pop()

                   // if the value is cached, just execute this
                   .label(cacheOk)
                   .ldarg(0)
                   .ldfld(f_prop)
                   .ret();

                // finally register the property just compiled in the CreateProperties
                updateCp(b);
            }

            cp.il().ret();
        }
Example #3
0
        private Type CompileNode(CompilationContext ctx, IBranch b)
        {
#if FORCE_SINGLE_THREADED
            Application.DoEvents();
#endif
            // vault modifications guard
            if (!ctx.StillInTouch()) throw new CompilerOutOfTouchException();

            // type
            var t_parent = ctx.PrevSuccessfulType(b.VPath) ?? typeof(CompiledNode);
            (t_parent == typeof(CompiledNode) ^ (ctx.CumulativeCompilation && ctx.ScheduledCumulations.ContainsKey(b))).AssertTrue();
            var t = _mod.DefineType(ctx.RequestName(b.GetClassName()), TA.Public, t_parent);

#if TRACE
            try
            {
#endif
                // constructor
                var ctor = t.DefineConstructor(MA.PublicCtor, CallingConventions.Standard, typeof(CompiledNode).MkArray());
                ctor.DefineParameter(1, ParmA.None, "parent");
                ctor.il().ld_args(2).basector(typeof(CompiledNode), typeof(CompiledNode)).ret();

                // the [VPath(...)] attribute for reflection-based analysis
                t.SetCustomAttribute(new CustomAttributeBuilder(
                    typeof(VPathAttribute).GetConstructor(typeof(String).MkArray()),
                    b.VPath.ToString().MkArray()));

                // the [Revision(...)] attribute for reflection-based analysis
                t.SetCustomAttribute(new CustomAttributeBuilder(
                    typeof(RevisionAttribute).GetConstructor(new[] { typeof(ulong) }),
                    new object[] { Vault.Revision }));

                // the [Seq(...)] attribute for synchronization and namespacing purposes
                t.SetCustomAttribute(new CustomAttributeBuilder(
                    typeof(SeqAttribute).GetConstructor(new[] { typeof(int) }),
                    new object[] { ctx.WorkerSeq }));

                // trivial overrides
                t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Name"))
                    .il()
                    .ldstr(b.GetClassName() + "_seq" + ctx.WorkerSeq)
                    .ret();

                t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("VPath"))
                    .il()
                    .ldstr(b.VPath)
                    .newobj(typeof(VPath), typeof(String))
                    .ret();

                t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Revision"))
                    .il()
                    .ldc_i8(Vault.Revision)
                    .ret();

                t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Seq"))
                    .il()
                    .ldc_i4(ctx.WorkerSeq)
                    .ret();

                ImplementCreateChildren(ctx, t, b);
                ImplementCreateProperties(ctx, t, b);

                // finalize the type
                var t_created = t.CreateType();
                _types.Add(t_created);

                // create a factory method for this class
                var factoryName = b.GetClassName().Substring(b.GetClassName().LastIndexOf(".") + 1);
                var factoryMethod = ctx.FactoryType.DefineMethod("Create_" + ctx.RequestName(factoryName), MA.PublicStatic, typeof(CompiledNode), typeof(CompiledNode).MkArray());
                factoryMethod.SetCustomAttribute(new CustomAttributeBuilder(
                    typeof(VPathAttribute).GetConstructor(typeof(String).MkArray()),
                    b.VPath.ToString().MkArray()));
                factoryMethod.il().ldarg(0).newobj(t_created, typeof(CompiledNode).MkArray()).ret();

                // register self within the factory
                ctx.Factory.il()
                    .ldarg(0)
                    .ldstr(b.VPath.ToString())
                    .newobj(typeof(VPath), typeof(String))
                    .ldnull()
                    .ldftn(factoryMethod)
                    .newobj(typeof(Func<CompiledNode, CompiledNode>), typeof(Object), typeof(IntPtr))
                    .callvirt(typeof(NodeFactory).GetMethod("Register", BF.All));

                return t_created;
#if TRACE
            }
            finally
            {
                // attempts to finalize the type so that we can dump an assembly to disk after certain exceptions
                if (!t.IsCreated())
                {
                    Action<Action> neverFail = code => {try{code();}catch{}};
                    neverFail(() => t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Name")).il().ldnull().ret());
                    neverFail(() => t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("VPath")).il().ldnull().ret());
                    neverFail(() => t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Revision")).il().ldc_i8(0).ret());
                    neverFail(() => t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Seq")).il().ldc_i4(0).ret());
                    neverFail(() => t.DefineOverride(typeof(CompiledNode).GetMethod("CreateChildren", BF.All)).il().ret());
                    neverFail(() => t.DefineOverride(typeof(CompiledNode).GetMethod("CreateProperties", BF.All)).il().ret());
                    neverFail(() => ctx.CCs[t].il().ret());
                    neverFail(() => ctx.CPs[t].il().ret());
                    neverFail(() => t.CreateType());
                }
            }
#endif
        }
Example #4
0
        private Type CompileScenarioRecursively(CompilationContext ctx)
        {
#if FORCE_SINGLE_THREADED
            Application.DoEvents();
#endif
            // vault modifications guard
            if (!ctx.StillInTouch()) throw new CompilerOutOfTouchException();

            // type
            var t_parent = ctx.PrevSuccessfulType("scenario") ?? typeof(CompiledScenario);
            (t_parent == typeof(CompiledScenario) ^ ctx.CumulativeCompilation).AssertTrue();
            var t = _mod.DefineType(ctx.RequestName(Vault.GetClassName()), TA.Public, t_parent);
#if TRACE
            try
            {
#endif
                // constructors
                var ctorDesignMode = t.DefineConstructor(MA.PublicCtor, CallingConventions.Standard,
                    new[] { typeof(IVault) });
                ctorDesignMode.DefineParameter(1, ParmA.None, "scenario");
                ctorDesignMode.il().ld_args(2).basector(typeof(CompiledScenario), typeof(IVault)).ret();
                var ctorRuntimeMode = t.DefineConstructor(MA.PublicCtor, CallingConventions.Standard,
                    new[] { typeof(IVault), typeof(IVault) });
                ctorRuntimeMode.DefineParameter(1, ParmA.None, "scenario");
                ctorRuntimeMode.DefineParameter(2, ParmA.None, "repository");
                ctorRuntimeMode.il().ld_args(3).basector(typeof(CompiledScenario), typeof(IVault), typeof(IVault)).ret();

                // the [Version(...)] attribute for reflection-based analysis
                t.SetCustomAttribute(new CustomAttributeBuilder(
                    typeof(VersionAttribute).GetConstructor(new[] { typeof(String), typeof(ulong) }),
                    new object[] { Vault.Id.ToString(), Vault.Revision }));

                // the [Seq(...)] attribute for synchronization and namespacing purposes
                t.SetCustomAttribute(new CustomAttributeBuilder(
                    typeof(SeqAttribute).GetConstructor(new[] { typeof(int) }),
                    new object[] { ctx.WorkerSeq }));

                // trivial overrides
                t.DefineOverrideReadonly(typeof(CompiledScenario).GetProperty("Version"))
                    .il()
                    .ldstr(Vault.Id.ToString())
                    .newobj(typeof(Guid), typeof(String))
                    .ldc_i8(Vault.Revision)
                    .newobj(typeof(Version), typeof(Guid), typeof(ulong))
                    .ret();

                t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Name"))
                    .il()
                    .ldstr(Vault.GetClassName() + "_seq" + ctx.WorkerSeq)
                    .ret();

                t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("VPath"))
                    .il()
                    .call(typeof(VPath).GetProperty("Empty").GetGetMethod())
                    .ret();

                t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Revision"))
                    .il()
                    .ldc_i8(Vault.Revision)
                    .ret();

                t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Seq"))
                    .il()
                    .ldc_i4(ctx.WorkerSeq)
                    .ret();

                // factory initialization
                var parentFactory = ctx.PrevSuccessfulType("factory") ?? typeof(NodeFactory);
                ctx.FactoryType = _mod.DefineType(ctx.RequestName(t.Name + "_factory"), TA.Public, parentFactory);
                ctx.FactoryType.SetCustomAttribute(new CustomAttributeBuilder(
                    typeof(SeqAttribute).GetConstructor(new[] { typeof(int) }),
                    new object[] { ctx.WorkerSeq }));

                // setting up the factory method
                ctx.Factory = ctx.FactoryType.DefineConstructor(MA.PublicCtor, CallingConventions.Standard, new Type[0]);
                ctx.Factory.il().ldarg(0).basector(parentFactory);

                // complex overrides (the main work is here, below the stack)
                if (ctx.CompilingBaseline)
                {
                    // baseline compilation involves creating classes and properties 
                    // for every single node and svd/fla in the scenario
                    ImplementCreateChildren(ctx, t, Vault);
                    ImplementCreateProperties(ctx, t, Vault);
                }
                else
                {
                    var cum = ctx.ScheduledCumulations;
                    MTLog.Say(String.Format("Scheduled cumulations: [{0}]{1}",
                        cum.Count,
                        cum.Count == 0 ? String.Empty : Environment.NewLine +
                            cum.Select(kvp =>
                                String.Format("[{0}] {1}{2}",
                                    kvp.Value.Length,
                                    kvp.Key,
                                    cum.Count == 0 ? String.Empty : Environment.NewLine +
                                        kvp.Value.Select(co =>
                                            String.Format(">>{0}: {1}", co.Reason, co.Subject)
                                        ).StringJoin(Environment.NewLine))
                            ).StringJoin(Environment.NewLine)));

                    // cumulative compilation only should regenerate pieces of code 
                    // that are affected by events mentioned in ctx.NormalizedChangeSet
                    cum.Keys.ForEach(host => CompileNode(ctx, host));
                }

                // factory finalization
                ctx.Factory.il().ret();
                var t_factory_created = ctx.FactoryType.CreateType();
                _types.Add(t_factory_created);
                var createNodeFactory = t.DefineOverride(typeof(CompiledScenario).GetMethod("CreateNodeFactory", BF.All));
                createNodeFactory.il().newobj(t_factory_created).ret();

                // finalize the type
                var t_created = t.CreateType();
                _types.Add(t_created);
                return t_created;
#if TRACE
            }
            finally
            {
                // attempts to finalize the type so that we can dump an assembly to disk even after exceptions
                if (!t.IsCreated())
                {
                    Action<Action> neverFail = code => {try{code();}catch{}};
                    neverFail(() => t.DefineOverrideReadonly(typeof(CompiledScenario).GetProperty("Version")).il().ldc_i4(0).ret());
                    neverFail(() => t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Name")).il().ldnull().ret());
                    neverFail(() => t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("VPath")).il().ldnull().ret());
                    neverFail(() => t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Revision")).il().ldc_i8(0).ret());
                    neverFail(() => t.DefineOverrideReadonly(typeof(CompiledNode).GetProperty("Seq")).il().ldc_i4(0).ret());
                    neverFail(() => t.DefineOverride(typeof(CompiledNode).GetMethod("CreateChildren", BF.All)).il().ret());
                    neverFail(() => t.DefineOverride(typeof(CompiledNode).GetMethod("CreateProperties", BF.All)).il().ret());
                    neverFail(() => ctx.CCs[t].il().ret());
                    neverFail(() => ctx.CPs[t].il().ret());
                    neverFail(() => t.DefineOverride(typeof(CompiledScenario).GetMethod("CreateNodeFactory", BF.All)).il().ldnull().ret());
                    neverFail(() => t.CreateType());
                }

                if (!ctx.FactoryType.IsCreated())
                {
                    Action<Action> neverFail = code => {try{code();}catch{}};
                    neverFail(() => ctx.Factory.il().ret());
                    neverFail(() => ctx.FactoryType.CreateType());
                }
            }
#endif
        }
Example #5
0
        public CompiledScenarioCache GetCompiledSync()
        {
            MTLog.Say("Waiting at the barrier");
            lock (_barrier)
            {
                MTLog.Say("Breached the barrier");
                MTLog.Say(String.Format("Entering compile sync: " +
                    "_worker={0}, _lastSeq={1}, _isBroken={2}, {3}, _compilingBaseline={4} ({5} failures), _changeset=[{6}]{7}",
                    _worker == null ? "null" : _worker.Name,
                    _lastSuccessfulWorkerSeq,
                    _isBroken,
                    _isPermanentlyBroken,
                    _compilingBaseline,
                    _baselineCompilationFailures,
                    _changeSet.Count,
                    _changeSet.Count == 0 ? String.Empty : Environment.NewLine +
                        _changeSet.Select(e => String.Format("{0}: {1}", e.Reason, e.Subject)).StringJoin(Environment.NewLine)
                    ));

                // if the compiler is in broken state, i.e. an unexpected exception has occurred somewhen
                // then we seize all activities and just fall back to the eval2 scheme
                // broken state is unfixable till the next application restart in current implementation
                if (_isPermanentlyBroken)
                {
                    MTLog.Say("Leaving compile sync (reason: permanently broken)");
                    return null;
                }
                else
                {
                    // if the compiler was broken by an excepted exception, then its relatively safe
                    // to attempt recompilation if anything changes later (e.g. user potentially corrects a mistake)
                    if (_isBroken && _brokenChangeSet.SequenceEqual(_changeSet))
                    {
                        MTLog.Say("Leaving compile sync (reason: broken and no pending changes)");
                        return null;
                    }
                    else
                    {
                        // if we've already got some result, and worker thread ain't spawned yet
                        // then we can conclude that previous result 100% corresponds to the current state of the vault
                        // or else the compiler would rerun itself as a response to any significant change
                        // n0te that neither of the vars checked below can be modified from outside since we own the barrier
                        if (!_compilingBaseline && _changeSet.IsEmpty() && _lastResult != null)
                        {
                            MTLog.Say("Leaving compile sync (reason: no need to calc, reusing last result)");
                            return _lastResult;
                        }
                        else
                        {
                            if (_worker == null)
                            {
                                _flag.Reset();

                                var ctx = new CompilationContext(this);
#if FORCE_SINGLE_THREADED
                                WorkerLogic(ctx);
#else
                                _worker = new Thread(() => WorkerLogic(ctx));
                                _worker.Name = "worker_" + (ctx.CompilingBaseline ? "base" : "cumu") +
                                    ctx.Revision.ToString("000") + "_seq" + ctx.WorkerSeq.ToString("000") + GetThreadStack();
                                MTLog.Say(String.Format("Spawning the worker thread: {0}", _worker.Name));
                                _worker.Start();
#endif

                                // here we don't return: scroll down to see the rest of the code
                            }
                        }
                        
                    }
                }
            }

            // wait until the worker thread is finished (btw the worker can be killed 
            // and restarted if any changes in the vault occur)
            // this means that theoretically a blocking call to GetCompiledSync 
            // might cause caller to freeze for indefinite amount of time

            // to prevent freezing caller should expose the Vault (effectively prohibiting writes) 
            // before a synchronous call to compile

            // important: the wait should be performed outside the lock (_barrier){}
            // or else the worker wouldn't be able to complete execution
            MTLog.Say("Waiting for the _flag event");
            _flag.WaitOne();
            MTLog.Say("_flag event in signal state");

            MTLog.Say("Reentering compile sync (reason: worker has just finished)");

            // here we shouldn't just return the lastResult, since by this moment it could already be changed
            // so just enter the recursion (sigh, i wish i could force compiler to emit the "tailcall" instruction)
            return GetCompiledSync();
        }
Example #6
0
        private void WorkerLogic(CompilationContext ctx)
        {
#if TRACE
            var s_workingSet = 0L;
            var s_typesInDynamicAssembly = -1;
            var s_time = TimeSpan.Zero;
#endif

            try
            {
#if TRACE
                GC.Collect();
                s_workingSet = Process.GetCurrentProcess().WorkingSet64;
                s_typesInDynamicAssembly = _types.Count();
                s_time = Process.GetCurrentProcess().TotalProcessorTime;

                var firstLine = String.Format(
                    "Entering the worker thread: rev={0} ({1} mode)", ctx.Revision,
                     ctx.CompilingBaseline ? "baseline" : "cumulative");
                var secondLine = String.Format(
                    "The context is: revision={0}, seq={1}, lastSeq={2}",
                    ctx.Revision, ctx.WorkerSeq, ctx.LastSuccessfulWorkerSeq);
                var thirdLine = "Compilation mode: " + (ctx.CompilingBaseline ? "baseline" : "cumulative");
                var fourthLine = String.Format(
                    "The changeset is: [{0}]{1}",
                    ctx.Changeset.Length,
                    ctx.Changeset.Length == 0 ? String.Empty : Environment.NewLine +
                        ctx.Changeset.Select(e => String.Format("  *{0}: {1}", e.Reason, e.Subject)).StringJoin(Environment.NewLine));
                var fifthLine = String.Format(
                    "The normalized changeset is: [{0}]{1}",
                    ctx.NormalizedChangeSet.Count,
                    ctx.NormalizedChangeSet.Count == 0 ? String.Empty : Environment.NewLine +
                        ctx.NormalizedChangeSet.Select(kvp => String.Format("  *{0}: {1}", kvp.Value, kvp.Key)).StringJoin(Environment.NewLine));
                var finalMessage = String.Format("{1}{0}{2}{0}{3}{0}{4}{0}{5}", 
                    Environment.NewLine, firstLine, secondLine, thirdLine, fourthLine, fifthLine);
                MTLog.Say(finalMessage);
#endif

                // was used to test empty thread footprint: ~64k
//                var t_scenario = _asm.GetTypes().FirstOrDefault(t => typeof(ICompiledScenario).IsAssignableFrom(t));
//                t_scenario = t_scenario ?? CompileScenarioRecursively(ctx);

                var t_scenario = CompileScenarioRecursively(ctx);
                var pool = new CompiledScenarioCache(Vault, t_scenario);

                MTLog.Say("Waiting at the barrier, pending successfully completed");
                lock (_barrier)
                {
                    // final modification guard (btw, the compiler logic also has such guards: for every node being compiled)
                    // if we fail here during baseline compilation - sigh... so much memory is gonna to be wasted
                    if (!ctx.StillInTouch()) throw new CompilerOutOfTouchException();

                    MTLog.Say("Breached the barrier");
                    _isBroken = false;
                    _brokenChangeSet = null;
                    _isPermanentlyBroken = false;
                    _compilingBaseline = false;
                    _changeSet.Clear();
                    _lastResult = pool;
                    _lastSuccessfulWorkerSeq = ctx.WorkerSeq;
                    MTLog.Say("Successfully completed");
                }
            }
            catch (Exception ex)
            {
                MTLog.Say("Waiting at the barrier, pending error: " + ex);
                lock (_barrier)
                {
                    MTLog.Say("Breached the barrier");
                    _isBroken = true;
                    _brokenChangeSet = ctx.Changeset;
                    if (ctx.CompilingBaseline) _baselineCompilationFailures++;
                    _isPermanentlyBroken = _baselineCompilationFailures >= MaxBaselineCompilationFailures;
                    // don't touch _compilingBaseline -> it should remain unchanged
                    // don't touch _changeSet -> it should remain unchanged
                    _lastResult = null;
                    MTLog.Say("Error: " + ex);
                }
            }
            finally
            {
                try
                {
                    GC.Collect();

#if TRACE
                    var f_WorkingSet = Process.GetCurrentProcess().WorkingSet64;
                    var f_typesInDynamicAssembly = _types.Count();
                    var f_time = Process.GetCurrentProcess().TotalProcessorTime;

                    var memDelta = (f_WorkingSet - s_workingSet) / 1024;
                    var typesDelta = f_typesInDynamicAssembly - s_typesInDynamicAssembly;
                    var timeDelta_sec = (f_time - s_time).Seconds;
                    var timeDelta_ms = (f_time - s_time).Milliseconds;

                    MTLog.Say(String.Format("Leaving the worker thread.{0}"+
                        "Time delta: {1}.{2} sec, memory delta: {3} Kb ({4} Kb total), types generated: {5}, avg memory per type: {6} Kb",
                        Environment.NewLine,
                        timeDelta_sec, 
                        timeDelta_ms,
                        s_workingSet == 0 ? "??" : memDelta.ToString(),
                        f_WorkingSet,
                        s_typesInDynamicAssembly == -1 ? "??" : typesDelta.ToString(),
                        (s_typesInDynamicAssembly == -1 || s_workingSet == 0 || typesDelta == 0) ? "??" : (memDelta / typesDelta).ToString()));
#else
                    MTLog.Say("Leaving the worker thread");
#endif
                }
                finally
                {
                    _worker = null;
                    _flag.Set();
                }
            }
        }
Example #7
0
        public CompiledScenarioCache GetCompiledAsync()
        {
#if FORCE_SINGLE_THREADED
            return GetCompiledSync();
#endif

            // this method is 99% the same as the above one, so reference the latter's comments for more details

            MTLog.Say("Waiting at the barrier");
            lock (_barrier)
            {
                MTLog.Say("Breached the barrier");
                MTLog.Say(String.Format("Entering compile sync: " +
                    "_worker={0}, _lastSeq={1}, _isBroken={2}, {3}, _compilingBaseline={4} ({5} failures), _changeset=[{6}]{7}",
                    _worker == null ? "null" : _worker.Name,
                    _lastSuccessfulWorkerSeq,
                    _isBroken,
                    _isPermanentlyBroken,
                    _compilingBaseline,
                    _baselineCompilationFailures,
                    _changeSet.Count,
                    _changeSet.Count == 0 ? String.Empty : Environment.NewLine +
                        _changeSet.Select(e => String.Format("{0}: {1}", e.Reason, e.Subject)).StringJoin(Environment.NewLine)
                    ));

                if (_isPermanentlyBroken)
                {
                    MTLog.Say("Leaving compile async (reason: broken permanently)");
                    return null;
                }
                else
                {
                    if (_isBroken && _brokenChangeSet.SequenceEqual(_changeSet))
                    {
                        MTLog.Say("Leaving compile async (reason: broken and no pending changes)");
                        return null;
                    }
                    else
                    {
                        if (!_compilingBaseline && _changeSet.IsEmpty() && _lastResult != null)
                        {
                            MTLog.Say("Leaving compile sync (reason: no need to calc, reusing last result)");
                            return _lastResult;
                        }
                        else
                        {
                            if (_worker != null)
                            {
                                // if we currently have a worker thread running, no need to spawn a waiter thread
                                // because that was already done by a thread that had spawned the worker
                                MTLog.Say("Leaving compile async (reason: worker is alive, nothing more to do here)");
                                return null;
                            }
                            else
                            {
                                _flag.Reset();

                                var ctx = new CompilationContext(this);
                                _worker = new Thread(() => WorkerLogic(ctx));

                                _worker.Name = "worker_" + (ctx.CompilingBaseline ? "base" : "cumu") +
                                    ctx.Revision.ToString("000") + "_seq" + ctx.WorkerSeq.ToString("000") + GetThreadStack();
                                MTLog.Say(String.Format("Spawning the worker thread: {0}", _worker.Name));
                                _worker.Start();

                                // here we don't return: scroll down to see the rest of the code
                            }
                        }
                    }
                }
            }

            // the construct below ensures that if worker gets interrupted due to vault modifications
            // then it gets restarted again and again until generation succeeds (potential memory sink!)
            // it also serves another goal: if the worker has just finished calculating and 
            // while it was busy, compiler updated _changeSet, this quantum thread will restart the worker
            var waiter = new Thread(() =>
            {
                MTLog.Say("Entering the waiter thread");

                MTLog.Say("Waiting for the _flag event");
                _flag.WaitOne();
                MTLog.Say("_flag event in signal state");

                GetCompiledAsync();
                MTLog.Say("Leaving the waiter thread");
            });

            waiter.Name = "waiter" + NextWaiterId.ToString("000") + GetThreadStack();
            MTLog.Say("Spawning the waiter thread: " + waiter.Name);
            waiter.Start();

            // unlike the previous method, an asynchronous call doesn't wait for completion of the worker
            // it just returns null (e.g. in that case EvalSession ver3 just falls back to slower, but instantly accessible ver2)
            MTLog.Say("Leaving compile async (reason: spawned worker and its waiter, now leaving)");
            return null;
        }