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(); }
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(); }
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 }
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 }
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(); }
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(); } } }
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; }