public static Stmt ctor(Ctx ctx) { Expression<Func<int>> eHashCode = () => hashCode; var field = (FieldInfo)((MemberExpression)eHashCode.Body).Member; var f = ctx.Module.Import(field); var fExpr = new ExprFieldAccess(ctx, null, f).Named("hashCode"); var stmt = new StmtJsExplicit(ctx, "this.$=++hashCode;", ctx.ThisNamed, fExpr); return stmt; }
protected virtual ICode VisitJsExplicit(StmtJsExplicit s) { this.ThrowOnNoOverride(); var namedExprs = this.HandleList(s.NamedExprs, x => { if (x == null) { return null; } var expr = (Expr)this.Visit(x.Expr); return expr == x.Expr ? x : expr.Named(x.Name); }); if (namedExprs != null) { return new StmtJsExplicit(s.Ctx, s.JavaScript, namedExprs); } else { return s; } }
public Stmt GetImpl(Ctx ctx) { var a = ctx.MethodParameter(0).Named("a"); var b = ctx.MethodParameter(1).Named("b"); var hi = ctx.Local(ctx.UInt64, "hi"); var lo = ctx.Local(ctx.UInt64, "lo"); var limit = ctx.Literal(0x100000000UL, ctx._UInt64, "limit"); var js = @" hi = a[0] + b[0]; lo = a[1] + b[1]; if (lo >= limit) { lo -= limit; hi++; } if (hi >= limit) hi -= limit; return [hi, lo]; "; var stmt = new StmtJsExplicit(ctx, js, a, b, hi, lo, limit); return stmt; }
public Stmt GetImpl(Ctx ctx) { var a = ctx.MethodParameter(0, "a"); var b = ctx.MethodParameter(1, "b"); var hi = ctx.Local(ctx.UInt64, "hi"); var lo = ctx.Local(ctx.UInt64, "lo"); var limit = ctx.Literal(0x100000000UL, ctx._UInt64, "limit"); var js = @" hi = a[0] - b[0]; lo = a[1] - b[1]; if (lo < 0) { lo += limit; hi--; } if (hi < 0) hi += limit; return [hi, lo]; "; var stmt = new StmtJsExplicit(ctx, js, a, b, hi, lo, limit); return stmt; }
protected virtual ICode VisitJsExplicit(StmtJsExplicit s) { this.ThrowOnNoOverride(); var namedExprs = this.HandleList(s.NamedExprs, x => { if (x == null) { return(null); } var expr = (Expr)this.Visit(x.Expr); return(expr == x.Expr ? x : expr.Named(x.Name)); }); if (namedExprs != null) { return(new StmtJsExplicit(s.Ctx, s.JavaScript, namedExprs)); } else { return(s); } }
public static Stmt Encode(Ctx ctx) { var arg = ctx.MethodParameter(0, "arg"); var id = ctx.Local(ctx.Int32, "id"); var todo = ctx.Local(ctx.Object, "todo"); var todoOfs = ctx.Local(ctx.Int32, "todoOfs"); var enc = ctx.Local(ctx.Object, "enc"); var obj = ctx.Local(ctx.Object, "obj"); var json = ctx.Local(ctx.Object, "json"); var jsonPart = ctx.Local(ctx.Object, "jsonPart"); var o = ctx.Local(ctx.Object, "o"); var type = ctx.Local(ctx.Type, "type"); var oKey = ctx.Local(ctx.String, "oKey"); var isRoot = ctx.Local(ctx.Boolean, "isRoot"); var i = ctx.Local(ctx.Int32, "i"); var isArray = new ExprJsTypeData(ctx, TypeData.IsArray).Named("isArray"); var jsName = new ExprJsTypeData(ctx, TypeData.JsName).Named("jsName"); var jsIsDict = new ExprJsTypeData(ctx, TypeData.IsDictionary).Named("jsIsDict"); var slots = ctx.Local(ctx.Object, "slots"); var ret = ctx.Local(ctx.Object, "ret"); // Value-types will be boxed on entry var js = @" try { id = 0; if (arg && arg._) { arg.$$ = '0'; } todo = [arg]; todoOfs = 0; enc = function(o, isRoot) { if (o === null || o === undefined) { return null; } var type = o._; if (!type) { // Unboxed value-type/primitive if (typeof(o) !== 'object' || o instanceof Array) { // primitive number, boolean, string, 64-bit number (array) if (typeof(o) === 'number') { // Number if (isNaN(o)) { return [0]; } if (o === Number.NEGATIVE_INFINITY) { return [-1]; } if (o === Number.POSITIVE_INFINITY) { return [1]; } } return o; } else { // Non-primitive value-type var ret = {}; for (var oKey in o) { ret[oKey] = enc(o[oKey]); } return ret; } } if (isRoot) { // Direct object encoding required if (type.jsIsDict) { var ret = { '_': type.jsName, 'v': [] }; var slots = o[type.jsIsDict[0]]; for (var i=0; i<slots.length; i++) { if (slots[i] && slots[i][type.jsIsDict[1]] >= 0) { ret.v.push(enc(slots[i][type.jsIsDict[2]]), enc(slots[i][type.jsIsDict[3]])); } } return ret; } if (type && type.isArray) { var ret = { '_': type.jsName, 'v': [] }; for (var i=0; i<o.length; i++) { ret.v.push(enc(o[i])); } return ret; } else { var ret = { '_': type.jsName }; for (var oKey in o) { if (oKey.charAt(0) !== '_' && oKey.charAt(0) !== '$') { ret[oKey] = enc(o[oKey]); } } return ret; } } else { if (!o.$$) { o.$$ = (++id).toString(); todo.push(o); } return [o.$$]; } }; json = []; while (todo.length > todoOfs) { obj = todo[todoOfs++]; jsonPart = enc(obj, true); json.push([(obj && obj.$$) || '0', jsonPart]); } for (i = 0; i<todo.length; i++) { if (todo[i] && todo[i].$$) { delete todo[i].$$; } } return json; } catch (eeee) { console.log('Ex: ' + eeee); throw eeee; } "; var stmt = new StmtJsExplicit(ctx, js, arg, id, todo, todoOfs, enc, obj, json, jsonPart, o, type, oKey, isRoot, i, isArray, ret, jsName, jsIsDict, slots); return stmt; }
protected override ICode VisitJsExplicit(StmtJsExplicit s) { this.NewLine(); this.HandleExplicitJs(s.JavaScript, s.NamedExprs); return s; }
public Stmt GetImpl(Ctx ctx) { var a = ctx.MethodParameter(0, "a"); var b = ctx.MethodParameter(1, "b"); var aa = ctx.Local(ctx.Int32.MakeArray(), "aa"); var bb = ctx.Local(ctx.Int32.MakeArray(), "bb"); var ia = ctx.Local(ctx.Int32, "ia"); var ib = ctx.Local(ctx.Int32, "ib"); var mul = ctx.Local(ctx.Int32, "mul"); var add = ctx.Local(ctx.Int32, "add"); var mulCarry = ctx.Local(ctx.Int32, "mulCarry"); var addCarry = ctx.Local(ctx.Int32, "addCarry"); var rrOfs = ctx.Local(ctx.Int32, "rrOfs"); var rr = ctx.Local(ctx.Int32.MakeArray(), "rr"); var mask = ctx.Literal(0xffff, ctx.Int32, "mask"); var limit = ctx.Literal(0x10000, ctx.Int32, "limit"); var js = @" aa = [a[0] >>> 16, a[0] & mask, a[1] >>> 16, a[1] & mask]; bb = [b[0] >>> 16, b[0] & mask, b[1] >>> 16, b[1] & mask]; rr = [0, 0, 0, 0]; for (ib = 3; ib >= 0; ib--) { mulCarry = 0; addCarry = 0; for(ia = 3; ia >= 3 - ib; ia--) { rrOfs = ia + ib - 3; mul = aa[ia] * bb[ib] + mulCarry; mulCarry = mul >>> 16; add = rr[rrOfs] + (mul & mask) + addCarry; if (add >= limit){ rr[rrOfs] = add - limit; addCarry = 1; } else { rr[rrOfs] = add; addCarry = 0; } } } return [rr[1] + rr[0] * limit, rr[3] + rr[2] * limit]; "; var stmt = new StmtJsExplicit(ctx, js, a, b, aa, bb, rr, mask, limit, ia, ib, mul, add, mulCarry, addCarry, rrOfs); return stmt; }
public static Stmt Decode(Ctx ctx) { var arg = ctx.MethodParameter(0, "arg"); var objs = ctx.Local(ctx.Object, "objs"); var refs = ctx.Local(ctx.Object, "refs"); var needDefer = ctx.Local(ctx.Object, "needDefer"); var defer = ctx.Local(ctx.Object, "defer"); var dec = ctx.Local(ctx.Object, "dec"); var type = ctx.Local(ctx.Type, "type"); var ret = ctx.Local(ctx.Object, "ret"); var o = ctx.Local(ctx.Object, "o"); var oArray = ctx.Local(ctx.Object, "oArray"); var i = ctx.Local(ctx.Int32, "i"); var isObject = ctx.Local(ctx.Boolean, "isObject"); var kVal = ctx.Local(ctx.Object, "kVal"); var vVal = ctx.Local(ctx.Object, "vVal"); var kRef = ctx.Local(ctx.Boolean, "kRef"); var vRef = ctx.Local(ctx.Boolean, "vRef"); var js = @" objs = {}; refs = []; needDefer = function(o) { return !!(o && o instanceof Array && o.length === 1 && typeof(o[0]) === 'string'); }; defer = function(ret, i, o) { if (needDefer(o[i])) { refs.push(function() { ret[i] = objs[o[i][0]]; }); } else { ret[i] = dec(o[i]); } }; dec = function(o) { var ret, i; if (o == null) { return null; } var isObject = false; if (o._ !== undefined) { if (o.__ === 2) { // Dictionary ret = $d[o.d][0]({ _: $$[o._] }); for (var i = 0; i < o.a.length; i++) { var kVal = o.a[i]; var vVal = o.b[i]; var kRef = needDefer(kVal); var vRef = needDefer(vVal); (function(kRef, kVal, vRef, vVal, o, ret) { refs.push(function() { if (kRef) kVal = objs[kVal[0]]; if (vRef) vVal = objs[vVal[0]]; $d[o.d][1](ret, kVal, vVal); }); })(kRef, kVal, vRef, vVal, o, ret); } } else if (o['']) { // Array var oArray = o['']; var ret = new Array(oArray.length); ret._ = $$[o._]; // TODO: Set $ for (var i = 0; i < oArray.length; i++) { defer(ret, i, oArray) } } else { // Object or boxed struct isObject = true; } } else if (typeof(o) === 'object' && !(o instanceof Array)) { // unboxed value-type isObject = true; } else if (o instanceof Array && o.length === 1) { // Special Single/Double switch (o[0]) { case 0: ret = NaN; break; case 1: ret = Number.POSITIVE_INFINITY; break; case -1: ret = Number.NEGATIVE_INFINITY; break; default: throw 'Unrecognised special: ' + o[0]; } } else { // unboxed primitive or null ret = o; } if (isObject) { var ret = o._ ? { '_': $$[o._] } : { }; // TODO: Set $ = hash id for (var i in o) { if (i !== '_') { defer(ret, i, o); } } } return ret; }; for (i = 0; i < arg.length; i++) { objs[arg[i][0]] = dec(arg[i][1]); } for (i = 0; i < refs.length; i++) { refs[i](); } ret = objs['0']; return ret; "; var stmt = new StmtJsExplicit(ctx, js, arg, objs, refs, needDefer, defer, dec, type, ret, o, oArray, i, isObject, kVal, vVal, kRef, vRef); return stmt; }
public static Stmt Equals(Ctx ctx) { var other = ctx.MethodParameter(0).Named("other"); var stmt = new StmtJsExplicit(ctx, "return this===other;", ctx.ThisNamed, other); return stmt; }
public Stmt GetImpl(Ctx ctx) { var a = ctx.MethodParameter(0); var b = ctx.MethodParameter(1); var divMod = new ExprCall(ctx, (Func<UInt64, UInt64, object>)_Int64UInt64.UInt64DivRem, null, a, b).Named("divMod"); var js = "return divMod[1];"; var stmt = new StmtJsExplicit(ctx, js, divMod); return stmt; }
public static JsResult CreateFrom(IEnumerable <MethodReference> rootMethods, bool verbose = false, bool testing = false) { var todo = new Queue <MethodReference>(); foreach (var method in rootMethods) { todo.Enqueue(method); } Action <MethodReference> addTodo = m => { if (m.ContainsGenericParameters()) { throw new Exception("Cannot add todo method with generic parameters"); } todo.Enqueue(m); }; // Each method, with the count of how often it is referenced. var methodsSeen = new Dictionary <MethodReference, int>(rootMethods.ToDictionary(x => x, x => 1), TypeExtensions.MethodRefEqComparerInstance); // Each type, with the count of how often it is referenced directly (newobj only at the moment). var typesSeen = new Dictionary <TypeReference, int>(TypeExtensions.TypeRefEqComparerInstance); // ASTs of all methods var methodAsts = new Dictionary <MethodReference, ICode>(TypeExtensions.MethodRefEqComparerInstance); // Each field, with the count of how often it is referenced. var fieldAccesses = new Dictionary <FieldReference, int>(TypeExtensions.FieldReqEqComparerInstance); // Each type records which virtual methods have their NewSlot definitions var virtualCalls = new Dictionary <TypeReference, HashSet <MethodReference> >(TypeExtensions.TypeRefEqComparerInstance); // Each basemost virtual method records the least-derived type actually called // This allows only virtual methods in more-derived types to be transcoded var virtualCallExactMethods = new Dictionary <MethodReference, IEnumerable <TypeReference> >(TypeExtensions.MethodRefEqComparerInstance); // Each interface type records which interface methods are called var interfaceCalls = new Dictionary <TypeReference, HashSet <MethodReference> >(TypeExtensions.TypeRefEqComparerInstance); // All instance constructors must be updated after all methods have been processed, to initialise all referenced // instance fields in the type, and to sort out 'return' statements. // This has to be done later, so the list of referenced fields in complete var instanceConstructors = new List <Ctx>(); while (todo.Any()) { // TODO: parallelise var mRef = todo.Dequeue(); var mDef = mRef.Resolve(); var tRef = mRef.DeclaringType; var tDef = tRef.Resolve(); var ctx = new Ctx(tRef, mRef); var ast = (ICode)JsResolver.ResolveMethod(ctx); if (ast == null) { var transcodeCtx = JsResolver.TranslateCtx(ctx) ?? ctx; if (transcodeCtx.MRef.ContainsGenericParameters()) { throw new InvalidOperationException("Method/type must not have generic parameters"); } if (transcodeCtx.MDef.IsAbstract) { throw new InvalidOperationException("Cannot transcode an abstract method"); } if (transcodeCtx.MDef.IsInternalCall) { throw new InvalidOperationException("Cannot transcode an internal call"); } if (transcodeCtx.MDef.IsExternal()) { throw new InvalidOperationException("Cannot transcode an external method"); } if (!transcodeCtx.MDef.HasBody) { throw new InvalidOperationException("Cannot transcode method without body"); } if (!typesSeen.ContainsKey(tRef)) { typesSeen.Add(tRef, 0); } ast = Transcoder.ToAst(transcodeCtx, verbose); } for (int i = 0; ; i++) { var astOrg = ast; ast = Transcoder.DoStep(s => (Stmt)VisitorJsRewriteSealedVCalls.V(s), (Stmt)ast, "VisitorJsRewriteSealedVCalls", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveAll.V(s), (Stmt)ast, "VisitorJsResolveAll", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveConv.V(s), (Stmt)ast, "VisitorJsResolveConv", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveSpecialTypes.V(s), (Stmt)ast, "VisitorJsResolveSpecialTypes", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveDelegates.V(s), (Stmt)ast, "VisitorJsResolveDelegates", verbose); // 64bit must be after everything else ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolve64Bit.V(s), (Stmt)ast, "VisitorJsResolve64Bit", verbose); if (ast == astOrg) { break; } if (i > 10) { // After 10 iterations even the most complex method should be sorted out throw new InvalidOperationException("Error: Stuck in loop trying to resolve AST"); } } ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveValueTypes.V(s), (Stmt)ast, "VisitorJsResolveValueTypes", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorIfSimplification.V(s), (Stmt)ast, "VisitorIfSimplification", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveByRefParameters.V(s), (Stmt)ast, "VisitorJsResolveByRefParameters", verbose); if (mDef.IsVirtual && mRef.DeclaringType.IsValueType) { // 'this' may be boxed or unboxed. Must be unboxed if boxed // This is required because in real .NET the boxed and unboxed versions are both directly // available at the this reference; this is not the case in the JS emulation of boxing var unbox = new StmtJsExplicit(ctx, "if (this._) this = this.v;", ctx.ThisNamed); ast = new StmtBlock(ctx, unbox, (Stmt)ast); Transcoder.Print((Stmt)ast, "Unbox-this", verbose); } if (mDef.IsConstructor && !mDef.IsStatic) { // Instance constructor; add instance field initialisation and final return of 'this' later instanceConstructors.Add(ctx); } var cctors = VisitorFindStaticConstructors.V(ast) .Where(x => !TypeExtensions.MethodRefEqComparerInstance.Equals(x, mRef)) .Distinct(TypeExtensions.MethodRefEqComparerInstance) .ToArray(); if (cctors.Any()) { // All methods that access static fields or methods must call the static constructor at the very // start of the method. Except the static construtor itself, which must not recurse into itself. var cctorCalls = cctors .Select(x => new StmtWrapExpr(ctx, new ExprCall(ctx, x, null, Enumerable.Empty <Expr>(), false))).ToArray(); ast = new StmtBlock(ctx, cctorCalls.Concat((Stmt)ast)); Transcoder.Print((Stmt)ast, "Call-cctors", verbose); } if (mDef.IsConstructor && mDef.IsStatic) { // At the beginning of the static constructor, it rewrites itself as an empty function, so it is only called once. var rewrite = new StmtAssignment(ctx, new ExprJsVarMethodReference(ctx, mRef), new ExprJsEmptyFunction(ctx)); ast = new StmtBlock(ctx, rewrite, (Stmt)ast); Transcoder.Print((Stmt)ast, "cctor-once-only", verbose); } methodAsts.Add(mRef, ast); var fieldRefs = VisitorFindFieldAccesses.V(ast); foreach (var fieldRef in fieldRefs) { fieldAccesses[fieldRef] = fieldAccesses.ValueOrDefault(fieldRef) + 1; } var arrayRefs = VisitorFindNewArrays.V(ast); foreach (var arrayRef in arrayRefs) { typesSeen[arrayRef] = typesSeen.ValueOrDefault(arrayRef) + 1; } var types = VisitorFindRequiredTypes.V(ast); foreach (var type in types) { typesSeen[type] = typesSeen.ValueOrDefault(type) + 1; } if (mDef.GetCustomAttribute <JsReturnTypeDeepUseAttribute>(true) != null) { var retType = mDef.ReturnType.FullResolve(ctx); var retTypes = retType.EnumThisAllContainedTypes().ToArray(); foreach (var type in retTypes) { typesSeen[type] = typesSeen.ValueOrDefault(type) + 1; if (type.IsGenericInstance && type.Resolve().FullName == "System.Collections.Generic.Dictionary`2") { // HACK - ensure no-arg ctor is present. JSON needs it var ctor = type.EnumResolvedMethods().First(x => x.Name == ".ctor" && !x.HasParameters); if (!methodsSeen.ContainsKey(ctor)) { methodsSeen.Add(ctor, 1); addTodo(ctor); } // HACK - ensure Add(key, value) method present. JSON need sit var mAdd = type.EnumResolvedMethods().First(x => x.Name == "Add"); if (!methodsSeen.ContainsKey(mAdd)) { methodsSeen.Add(mAdd, 1); addTodo(mAdd); } } } } var calledMethods = new List <ICall>(); var calls = VisitorFindCalls.V(ast); foreach (var call in calls.Where(x => x.ExprType == Expr.NodeType.NewObj || x.IsVirtualCall)) { // Add reference to each type constructed (direct access to type variable) typesSeen[call.Type] = typesSeen.ValueOrDefault(call.Type) + 1; } foreach (var call in calls) { if (call.CallMethod.DeclaringType.Resolve().IsInterface) { var methodSet = interfaceCalls.ValueOrDefault(call.CallMethod.DeclaringType, () => new HashSet <MethodReference>(TypeExtensions.MethodRefEqComparerInstance), true); methodSet.Add(call.CallMethod); // Methods that require transcoding are added to 'todo' later continue; } if (call.IsVirtualCall) { var mBasemost = call.CallMethod.GetBasemostMethod(null); var methodSet = virtualCalls.ValueOrDefault(mBasemost.DeclaringType, () => new HashSet <MethodReference>(TypeExtensions.MethodRefEqComparerInstance), true); methodSet.Add(mBasemost); var objType = call.Obj.Type; var already = virtualCallExactMethods.ValueOrDefault(mBasemost).EmptyIfNull(); if (!already.Any(x => x.IsBaseOfOrEqual(objType))) { virtualCallExactMethods[mBasemost] = already.Concat(objType).ToArray(); } // Methods that require transcoding are added to 'todo' later continue; } calledMethods.Add(call); } foreach (var call in calledMethods) { if (methodsSeen.ContainsKey(call.CallMethod)) { methodsSeen[call.CallMethod]++; } else { methodsSeen.Add(call.CallMethod, 1); //todo.Enqueue(call.CallMethod); addTodo(call.CallMethod); } } if (!todo.Any()) { // Add System.RuntimeType if any types have been seen if (typesSeen.Any(x => x.Value > 0) && !typesSeen.Any(x => x.Key.FullName == "System.RuntimeType")) { var runtimeType = ctx.Module.Import(Type.GetType("System.RuntimeType")); typesSeen.Add(runtimeType, 1); } // Scan all virtual calls and add any required methods // Need care to handle virtual methods with generic arguments var virtualRoots = new HashSet <MethodReference>(virtualCalls.SelectMany(x => x.Value), TypeExtensions.MethodRefEqComparerInstance); var requireMethods = from type in typesSeen.Keys //let typeReverseMapped = JsResolver.ReverseTypeMap(type) let typeAndBases = type.EnumThisAllBaseTypes().ToArray() let mVRoots = typeAndBases.SelectMany(x => virtualCalls.ValueOrDefault(x).EmptyIfNull()).ToArray() let methods = type.EnumResolvedMethods(mVRoots).ToArray() from method in methods //.Select(x => JsResolver.ResolveMethod(x)) let methodDef = method.Resolve() where methodDef != null // HACK? where !methodDef.IsStatic && methodDef.IsVirtual && !methodDef.IsAbstract where !methodsSeen.ContainsKey(method) let mBasemost = method.GetBasemostMethod(method) where virtualCallExactMethods.ValueOrDefault(mBasemost).EmptyIfNull().Any(x => { //return x.IsBaseOfOrEqual(typeReverseMapped) || typeReverseMapped.IsBaseOfOrEqual(x); return(x.IsBaseOfOrEqual(type) || type.IsBaseOfOrEqual(x)); }) where virtualRoots.Contains(mBasemost) select method; var requireMethodsArray = requireMethods.Distinct(TypeExtensions.MethodRefEqComparerInstance).ToArray(); foreach (var method in requireMethodsArray) { methodsSeen.Add(method, 1); // TODO: How to properly handle count? //todo.Enqueue(method); addTodo(method); } // Scan all interface calls and add any required methods var iFaceMethods = from type in typesSeen.Keys from iFace in interfaceCalls let iFaceType = iFace.Key let typeAndBases = type.EnumThisAllBaseTypes().ToArray() where typeAndBases.Any(x => x.DoesImplement(iFaceType)) let methods = typeAndBases.SelectMany(x => x.EnumResolvedMethods(iFace.Value)).ToArray() from method in methods//.Select(x => JsResolver.ResolveMethod(x)) where !methodsSeen.ContainsKey(method) let methodDef = method.Resolve() where methodDef != null // HACK? where !methodDef.IsStatic && methodDef.IsVirtual && !methodDef.IsAbstract from iFaceMethod in iFace.Value where method.IsImplementationOf(iFaceMethod) select method; var iFaceMethodsArray = iFaceMethods.Distinct(TypeExtensions.MethodRefEqComparerInstance).ToArray(); foreach (var method in iFaceMethodsArray) { methodsSeen.Add(method, 1); addTodo(method); //todo.Enqueue(method); } } } // Add type count to System.RuntimeType var runtimeTypeInc = typesSeen.Keys.FirstOrDefault(x => x.FullName == "System.RuntimeType"); if (runtimeTypeInc != null) { typesSeen[runtimeTypeInc] += 2; } var instanceFieldsByType = fieldAccesses .Where(x => !x.Key.Resolve().IsStatic) .ToLookup(x => x.Key.DeclaringType.FullResolve(x.Key), TypeExtensions.TypeRefEqComparerInstance); // Update all instance constructors to initialise instance fields, add final 'return' statement, // and update any early returns to return 'this' foreach (var ctx in instanceConstructors) { var fields = instanceFieldsByType[ctx.TRef].Select(x => x.Key); var initStmts = fields.Select(x => { var f = x.FullResolve(ctx.TRef, ctx.MRef); var assign = new StmtAssignment(ctx, new ExprFieldAccess(ctx, ctx.This, f), new ExprDefaultValue(ctx, f.FieldType)); return(assign); }).ToArray(); var returnStmt = new StmtReturn(ctx, ctx.This); var ast = methodAsts[ctx.MRef]; ast = new StmtBlock(ctx, initStmts.Concat((Stmt)ast).Concat(returnStmt)); ast = VisitorJsReturnThis.V(ast, ctx.This); methodAsts[ctx.MRef] = ast; } // Locally name all instance fields; base type names must not be re-used in derived types var instanceFieldsIndexed = new Dictionary <int, Tuple <IEnumerable <FieldReference>, int> >(); // <index, Tuple<all fields, total use count>> instanceFieldsByType.TypeTreeTraverse(x => x.Key, (fields, idx) => { var ordered = fields.OrderByDescending(x => x.Value).ToArray(); // Order by usage count, highest first foreach (var field in ordered) { var idxInfo = instanceFieldsIndexed.ValueOrDefault(idx, () => Tuple.Create(Enumerable.Empty <FieldReference>(), 0)); var newIdxInfo = Tuple.Create((IEnumerable <FieldReference>)idxInfo.Item1.Concat(field.Key).ToArray(), idxInfo.Item2 + field.Value); instanceFieldsIndexed[idx] = newIdxInfo; idx++; } return(idx); }, 0); var orderedInstanceFields = instanceFieldsIndexed.OrderByDescending(x => x.Value.Item2).ToArray(); var instanceFieldNameGen = new NameGenerator(); var instanceFieldNames = orderedInstanceFields .Select(x => new { name = instanceFieldNameGen.GetNewName(), fields = x.Value.Item1 }) .SelectMany(x => x.fields.Select(y => new { f = y, name = x.name })) .ToArray(); // Prepare list of static fields for global naming var staticFields = fieldAccesses.Where(x => x.Key.Resolve().IsStatic).ToArray(); // Prepare local variables for global naming. // All locals in all methods are sorted by usage count, then all methods usage counts are combined var clusters = methodAsts.Values.SelectMany(x => VisitorJsPhiClusters.V(x).Select(y => new ExprVarCluster(y))).ToArray(); var varToCluster = clusters.SelectMany(x => x.Vars.Select(y => new { cluster = x, var = y })).ToDictionary(x => x.var, x => x.cluster); var varsWithCount = methodAsts.Values.Select(x => { var methodVars = VisitorFindVars.V(x); // Parameters need one extra count, as they appear in the method declaration methodVars = methodVars.Concat(methodVars.Where(y => y.ExprType == Expr.NodeType.VarParameter).Distinct()); var ret = methodVars.Select(y => varToCluster.ValueOrDefault(y) ?? y) .GroupBy(y => y) .Select(y => new { var = y.Key, count = y.Count() }) .OrderByDescending(y => y.count) .ToArray(); return(ret); }).ToArray(); var localVarCounts = new Dictionary <int, int>(); foreach (var x in varsWithCount) { for (int i = 0; i < x.Length; i++) { localVarCounts[i] = localVarCounts.ValueOrDefault(i) + x[i].count; } } // Globally name all items that require names var needNaming = localVarCounts.Select(x => new { item = (object)x.Key, count = x.Value }) .Concat(methodsSeen.Select(x => new { item = (object)x.Key, count = x.Value })) .Concat(staticFields.Select(x => new { item = (object)x.Key, count = x.Value })) .Concat(typesSeen.Select(x => new { item = (object)x.Key, count = x.Value })) .Where(x => x.count > 0) .OrderByDescending(x => x.count) .ToArray(); var nameGen = new NameGenerator(); var globalNames = needNaming.ToDictionary(x => x.item, x => nameGen.GetNewName()); // Create map of all local variables to their names var localVarNames = varsWithCount.Select(x => x.Select((y, i) => new { y.var, name = globalNames[i] })) .SelectMany(x => x) .SelectMany(x => { var varCluster = x.var as ExprVarCluster; if (varCluster != null) { return(varCluster.Vars.Select(y => new { var = y, name = x.name })); } else { return(new[] { x }); } }) .ToDictionary(x => x.var, x => x.name); // Create map of all method names var methodNames = methodsSeen.Keys.ToDictionary(x => x, x => globalNames[x], TypeExtensions.MethodRefEqComparerInstance); // Create list of all static field names var staticFieldNames = staticFields.Select(x => new { f = x.Key, name = globalNames[x.Key] }); // Create map of all fields if (testing) { instanceFieldNames = instanceFieldNames.Select(x => { switch (x.f.FullName) { case "System.String System.Exception::_message": return(new { x.f, name = "$$_message" }); } return(x); }).ToArray(); } var fieldNames = instanceFieldNames.Concat(staticFieldNames).ToDictionary(x => x.f, x => x.name, TypeExtensions.FieldReqEqComparerInstance); // Create map of type names var typeNames = typesSeen .Where(x => x.Value > 0) .ToDictionary(x => x.Key, x => globalNames[x.Key], TypeExtensions.TypeRefEqComparerInstance); // Create virtual call tables var virtualCallIndices = new Dictionary <MethodReference, int>(TypeExtensions.MethodRefEqComparerInstance); var allVirtualMethods = new Dictionary <TypeReference, HashSet <MethodReference> >(TypeExtensions.TypeRefEqComparerInstance); typesSeen.Select(x => x.Key).TypeTreeTraverse(x => x, (type, vCalls) => { var mNewSlots = virtualCalls.ValueOrDefault(type).EmptyIfNull().ToArray(); int idx = vCalls.Length; foreach (var mNewSlot in mNewSlots) { virtualCallIndices[mNewSlot] = idx++; } var vCallsWithThisType = vCalls.Concat(mNewSlots).ToArray(); if (vCallsWithThisType.Length > 0) { var typesAndBases = type.EnumThisAllBaseTypes().ToArray(); var mVRoots = typesAndBases.SelectMany(x => virtualCalls.ValueOrDefault(x).EmptyIfNull()).ToArray(); var ms = type.EnumResolvedMethods(mVRoots).ToArray(); for (int i = 0; i < vCalls.Length; i++) { var mVCall = vCallsWithThisType[i]; foreach (var m in ms) { if (m.MatchMethodOnly(mVCall)) { vCallsWithThisType[i] = m; } } } var typeVMethods = new HashSet <MethodReference>(vCallsWithThisType, TypeExtensions.MethodRefEqComparerInstance); allVirtualMethods.Add(type, typeVMethods); } return(vCallsWithThisType); }, new MethodReference[0]); var typeData = Enum.GetValues(typeof(TypeData)).Cast <TypeData>().ToArray(); // Name all items that are within the type information var needTypeInformationNaming = interfaceCalls.Select(x => new { item = (object)x.Key, count = 1 }) .Concat(typeData.Select(x => new { item = (object)x, count = 1 })) .OrderByDescending(x => x.count) .ToArray(); var typeInformationNameGen = new NameGenerator(); var typeInformationNames = needTypeInformationNaming.ToDictionary(x => x.item, x => typeInformationNameGen.GetNewName()); if (testing) { typeInformationNames[TypeData.Name] = "$$TypeName"; typeInformationNames[TypeData.Namespace] = "$$TypeNamespace"; } // Create map of interfaces to their names var interfaceNames = interfaceCalls.Keys.ToDictionary(x => x, x => typeInformationNames[x], TypeExtensions.TypeRefEqComparerInstance); var interfaceCallIndices = interfaceCalls.SelectMany(x => x.Value.Select((m, i) => new { m, i })).ToDictionary(x => x.m, x => x.i, TypeExtensions.MethodRefEqComparerInstance); // Create map of type data constants var typeDataNames = typeData.ToDictionary(x => x, x => typeInformationNames[x]); var resolver = new JsMethod.Resolver { LocalVarNames = localVarNames, MethodNames = methodNames, FieldNames = fieldNames, TypeNames = typeNames, VirtualCallIndices = virtualCallIndices, InterfaceCallIndices = interfaceCallIndices, InterfaceNames = interfaceNames, TypeDataNames = typeDataNames, }; var js = new StringBuilder(); js.Append("(function(){"); int jsIndent = 1; Action jsNewLine = () => { js.AppendLine(); js.Append(' ', jsIndent * JsMethod.tabSize); }; jsNewLine(); js.Append("\"use strict\";"); jsNewLine(); // Construct methods foreach (var methodInfo in methodAsts) { var mRef = methodInfo.Key; var ast = methodInfo.Value; var mJs = JsMethod.Create(mRef, resolver, ast); var mJsLines = mJs.Split(new[] { Environment.NewLine }, StringSplitOptions.None); foreach (var line in mJsLines) { jsNewLine(); js.Append(line); } } // Construct static fields foreach (var field in staticFields.Select(x => x.Key)) { jsNewLine(); js.AppendFormat("// {0}", field.FullName); jsNewLine(); if (field.Name == "Empty" && field.DeclaringType.FullName == "System.String") { // Special case, as string does not have a static constructor to set String.Empty js.AppendFormat("var {0} = \"\";", fieldNames[field]); } else { js.AppendFormat("var {0} = {1};", fieldNames[field], DefaultValuer.Get(field.FieldType, fieldNames)); } } // Construct type data var typesSeenOrdered = typesSeen .Where(x => x.Value > 0) .Select(x => x.Key) .OrderByReferencedFirst(x => x) .ToArray(); var domTypes = new Dictionary <string, TypeReference>(); foreach (var type in typesSeenOrdered) { var unmappedType = type; var tDef = unmappedType.Resolve(); // Check for DOM types var jsClassAttr = tDef.GetCustomAttribute <JsClassAttribute>(); if (jsClassAttr != null) { if (jsClassAttr.ConstructorArguments.Count == 1) { // Non-abstract types only var tagOrConstructorName = (string)jsClassAttr.ConstructorArguments[0].Value; domTypes.Add(tagOrConstructorName, unmappedType); } } // Type JS jsNewLine(); js.AppendFormat("// {0}", unmappedType.FullName); jsNewLine(); js.AppendFormat("var {0}={{", typeNames[type]); // Type information js.AppendFormat("{0}:\"{1}\"", typeDataNames[TypeData.JsName], typeNames[type]); js.AppendFormat(", {0}:\"{1}\"", typeDataNames[TypeData.Name], unmappedType.Name()); js.AppendFormat(", {0}:\"{1}\"", typeDataNames[TypeData.Namespace], unmappedType.Namespace); js.AppendFormat(", {0}:{1}", typeDataNames[TypeData.IsValueType], unmappedType.IsValueType ? "true" : "false"); js.AppendFormat(", {0}:{1}", typeDataNames[TypeData.IsPrimitive], unmappedType.IsPrimitive ? "true" : "false"); js.AppendFormat(", {0}:{1}", typeDataNames[TypeData.IsArray], unmappedType.IsArray ? "true" : "false"); js.AppendFormat(", {0}:{1}", typeDataNames[TypeData.ElementType], unmappedType.IsArray ? typeNames.ValueOrDefault(((ArrayType)unmappedType).ElementType, "null") : "null"); js.AppendFormat(", {0}:{1}", typeDataNames[TypeData.IsInterface], tDef.IsInterface ? "true" : "false"); var assignableTo = typesSeenOrdered.Where(x => unmappedType.IsAssignableTo(x)).Where(x => !x.IsSame(unmappedType)).ToArray(); js.AppendFormat(", {0}:[{1}]", typeDataNames[TypeData.AssignableTo], string.Join(", ", assignableTo.Select(x => typeNames[x]))); if (tDef.FullName == "System.Collections.Generic.Dictionary`2") { var typeGen = (GenericInstanceType)type; var dict = tDef.Module.Import(typeof(DotNetWebToolkit.Cil2Js.JsResolvers.Classes._Dictionary <,>)); var dictGen = dict.MakeGeneric(typeGen.GenericArguments[0], typeGen.GenericArguments[1]); var jsSlotsName = fieldNames[dictGen.EnumResolvedFields().First(x => x.Name == "slots")]; var slot = dictGen.Resolve().NestedTypes.First(x => x.Name == "Slot"); var slotGen = slot.MakeGeneric(typeGen.GenericArguments[0], typeGen.GenericArguments[1]); var jsHashCodeName = fieldNames[slotGen.EnumResolvedFields().First(x => x.Name == "hashCode")]; var jskeyName = fieldNames[slotGen.EnumResolvedFields().First(x => x.Name == "key")]; var jsValueName = fieldNames[slotGen.EnumResolvedFields().First(x => x.Name == "value")]; js.AppendFormat(", {0}:['{1}','{2}','{3}','{4}']", typeDataNames[TypeData.IsDictionary], jsSlotsName, jsHashCodeName, jskeyName, jsValueName); } if (!tDef.IsInterface) { if (!tDef.IsAbstract) { // Virtual method table, only needed on concrete types var typeAndBases = type.EnumThisAllBaseTypes().ToArray(); var methods = allVirtualMethods.ValueOrDefault(type); if (methods != null) { var idxs = methods .Select(x => { var xBasemost = x.GetBasemostMethod(x); return(new { m = x, idx = virtualCallIndices[xBasemost] }); }) .OrderBy(x => x.idx) .ToArray(); var s = string.Join(", ", idxs.Select(x => methodNames.ValueOrDefault(x.m, "null"))); js.AppendFormat(", {0}:[{1}]", typeDataNames[TypeData.VTable], s); } // Interface tables, only needed on concrete types var implementedIFaces = interfaceCalls.Where(x => typeAndBases.Any(y => y.DoesImplement(x.Key))).ToArray(); foreach (var iFace in implementedIFaces) { js.Append(", "); var iFaceName = interfaceNames[iFace.Key]; js.AppendFormat("{0}:[", iFaceName); var qInterfaceTableNames = from iMethod in iFace.Value let tMethod = typeAndBases.SelectMany(x => x.EnumResolvedMethods(iMethod)).First(x => x.IsImplementationOf(iMethod)) //let tM2 = JsResolver.ResolveMethod(tMethod) let idx = interfaceCallIndices[iMethod] orderby idx let methodName = methodNames[tMethod] select methodName; var interfaceTableNames = qInterfaceTableNames.ToArray(); js.Append(string.Join(", ", interfaceTableNames)); js.Append("]"); } } } if (tDef.IsEnum && tDef.GetCustomAttribute <JsStringEnumAttribute>() != null) { // JS string/enum map var values = tDef.Fields.Where(x => x.IsLiteral).Select(x => { return(string.Format("{0}:\"{1}\",\"{1}\":{0}", x.Constant, JsResolver.JsName(x))); }).ToArray(); js.AppendFormat(", {0}:{{{1}}}", typeDataNames[TypeData.EnumStringMap], string.Join(", ", values)); } // end js.Append("};"); } // Add type of each type, if System.RuntimeType has been seen var typeRuntimeType = typesSeen.Keys.FirstOrDefault(x => x.FullName == "System.RuntimeType"); if (typeRuntimeType != null) { jsNewLine(); foreach (var type in typesSeenOrdered) { js.Append(typeNames[type]); js.Append("._ = "); } js.Append(typeNames[typeRuntimeType]); js.Append(";"); } // Add comments descibing each interface jsNewLine(); js.Append("// Interface name map"); jsNewLine(); js.AppendFormat("// {0} = VTable", typeDataNames[TypeData.VTable]); foreach (var iFace in interfaceNames) { jsNewLine(); js.AppendFormat("// {0} = {1}", iFace.Value, iFace.Key.FullName); } // Add map of DOM types if (domTypes.Any()) { jsNewLine(); js.Append("// DOM type mapping"); jsNewLine(); // TODO: Auto-name this js.Append("var __ = {"); jsIndent++; foreach (var domType in domTypes) { jsNewLine(); js.AppendFormat("'{0}': {1},", domType.Key, typeNames[domType.Value]); } js.Length--; jsIndent--; jsNewLine(); js.Append("};"); } if (typesSeenOrdered.Any()) { jsNewLine(); js.Append("// json type mapping"); jsNewLine(); // TODO: Auto-name this js.Append("var $$ = {"); foreach (var type in typesSeenOrdered) { js.AppendFormat("'{0}':{0},", typeNames[type]); } js.Length--; js.Append("};"); var typesDicts = typesSeenOrdered .Where(x => x.IsGenericInstance && x.Resolve().FullName == "System.Collections.Generic.Dictionary`2") .ToArray(); if (typesDicts.Any()) { jsNewLine(); js.Append("// json dictionary info"); jsNewLine(); // TODO: Auto-name or get rid of this js.Append("var $d = {"); var any = false; foreach (var type in typesDicts) { var typeName = typeNames[type]; var ctor = type.EnumResolvedMethods().First(x => x.Name == ".ctor" && !x.HasParameters); var mAdd = type.EnumResolvedMethods().First(x => x.Name == "Add"); // If dict not involved in JSON, these methods may not be present if (methodNames.ContainsKey(ctor) && methodNames.ContainsKey(mAdd)) { var ctorName = methodNames[ctor]; var mAddName = methodNames[mAdd]; js.AppendFormat("'{0}':[{1},{2}],", typeName, ctorName, mAddName); any = true; } } if (any) { js.Length--; } js.Append("};"); } } jsNewLine(); jsNewLine(); js.Append("// Exports"); if (!testing) { var rootMethodsByType = rootMethods.ToLookup(x => x.DeclaringType, TypeExtensions.TypeRefEqComparerInstance); Action <NamespaceTree> treeToJs = null; treeToJs = tree => { js.Append("{"); jsIndent++; foreach (var subNs in tree.Namespaces) { jsNewLine(); js.AppendFormat("'{0}': ", subNs.NamespacePart); treeToJs(subNs); } if (tree.Types.Any()) { foreach (var type in tree.Types) { jsNewLine(); js.AppendFormat("'{0}': {{", type.Name); jsIndent++; foreach (var method in rootMethodsByType[type]) { jsNewLine(); js.AppendFormat("'{0}': {1},", method.Name, methodNames[method]); } js.Length--; jsIndent--; jsNewLine(); js.Append("},"); } js.Length--; } jsIndent--; jsNewLine(); js.Append("}"); }; var trees = NamespaceTree.Make(rootMethodsByType.Select(x => x.Key)); foreach (var tree in trees) { jsNewLine(); js.AppendFormat("window['{0}'] = ", tree.NamespacePart); treeToJs(tree); js.Append(";"); } } else { jsNewLine(); js.AppendFormat("window['main'] = {0};", methodNames[rootMethods.First()]); } jsIndent--; jsNewLine(); jsNewLine(); js.Append("})();"); var jsStr = js.ToString(); //Console.WriteLine(jsStr); var qFieldMap = from fieldName in fieldNames let declType = fieldName.Key.DeclaringType let declTypeMapped = JsResolver.TypeMapReverse(declType) ?? declType let declTypeName = declTypeMapped.AssemblyQualifiedName() where declType != null group fieldName by declTypeName; var fieldMap = qFieldMap.ToDictionary(x => x.Key, x => x.ToDictionary(y => y.Key.Name, y => y.Value)); var qTypeMap = from typeName in typeNames let type = typeName.Key.AssemblyQualifiedName() where type != null select new { type, typeName.Value }; var ttt = qTypeMap.ToArray(); var typeMap = ttt.ToDictionary(x => x.type, x => x.Value); var jsTypeMap = new JsonTypeMap(typeMap, fieldMap); return(new JsResult(jsStr, jsTypeMap)); }
protected override ICode VisitJsExplicit(StmtJsExplicit s) { this.NewLine(); this.HandleExplicitJs(s.JavaScript, s.NamedExprs); return(s); }
public Stmt GetImpl(Ctx ctx) { var a = ctx.MethodParameter(0, "a"); var stmt = new StmtJsExplicit(ctx, "return [(~a[0]) >>> 0, (~a[1]) >>> 0];", a); return stmt; }
public Stmt GetImpl(Ctx ctx) { var a = ctx.MethodParameter(0, "a"); var b = ctx.MethodParameter(1, "b"); var stmt = new StmtJsExplicit(ctx, "return [(a[0] ^ b[0]) >>> 0, (a[1] ^ b[1]) >>> 0];", a, b); return stmt; }
protected override ICode VisitJsExplicit(StmtJsExplicit s) { this.NewLine(); this.code.Append(s.JavaScript); return s; }
public Stmt GetImpl(Ctx ctx) { var a = ctx.MethodParameter(0, "a"); var b = ctx.MethodParameter(1, "b"); var u = ctx.Local(ctx.Int32.MakeArray(), "u"); var v = ctx.Local(ctx.Int32.MakeArray(), "v"); var m = ctx.Local(ctx.Int32, "m"); var n = ctx.Local(ctx.Int32, "n"); var r = ctx.Local(ctx.Int32.MakeArray(), "r"); var q = ctx.Local(ctx.Int32.MakeArray(), "q"); var i = ctx.Local(ctx.Int32, "i"); var j = ctx.Local(ctx.Int32, "j"); var k = ctx.Local(ctx.Int32, "k"); var l = ctx.Local(ctx.Int32, "l"); var p = ctx.Local(ctx.Int32, "p"); var t = ctx.Local(ctx.Int32, "t"); var s = ctx.Local(ctx.Int32, "s"); var x = ctx.Local(ctx.Int32, "x"); var qhat = ctx.Local(ctx.Int32, "qhat"); var rhat = ctx.Local(ctx.Int32, "rhat"); var mask = ctx.Literal(0xffff, ctx.Int32, "mask"); var limit = ctx.Literal(0x10000, ctx.Int32, "limit"); var divByZeroEx = new ExprNewObj(ctx, ctx.Module.Import(typeof(DivideByZeroException).GetConstructor(Type.EmptyTypes))).Named("divByZeroEx"); var js = @" v = [b[1] & mask, b[1] >>> 16, b[0] & mask, b[0] >>> 16]; while (v[v.length - 1] === 0) { v = v.slice(0, -1); if (v.length === 0) throw divByZeroEx; } n = v.length; u = [a[1] & mask, a[1] >>> 16, a[0] & mask, a[0] >>> 16]; while (u[u.length - 1] === 0 && u.length > n) u = u.slice(0, -1); m = u.length; r = [0, 0, 0, 0]; q = [0, 0, 0, 0]; if (n === 1) { k = 0; for (j = m - 1; j >= 0; j--) { l = k * limit + u[j] q[j] = (l / v[0]) >>> 0; k = l - q[j] * v[0]; } r[0] = k; } else { x = v[n - 1]; s = 16; while (x > 0) { x >>>= 1; s--; } l = 0; for (i = n - 1; i > 0; i--) v[i] = ((v[i] << s) | (v[i - 1] >> (16 - s))) & mask; v[0] = (v[0] << s) & mask; u.push(0); for (i = m; i> 0; i--) u[i] = ((u[i] << s) | (u[i - 1] >> (16 - s))) & mask; u[0] = (u[0] << s) & mask; for (j = m - n; j >= 0; j--) { l = u[j + n] * limit + u[j + n - 1]; qhat = (l / v[n - 1]) >>> 0; rhat = l - qhat * v[n - 1]; for (;;) { if (qhat >= limit || qhat * v[n - 2] > limit * rhat + u[j + n - 2]) { qhat--; rhat += v[n - 1]; if (rhat < limit) continue; } break; } k = 0; for (i = 0; i < n; i++) { p = qhat * v[i]; t = u[i + j] - k - (p & mask); u[i + j] = t & mask; k = (p >> 16) - (t >> 16); } t = u[j + n] - k; u[j + n] = t; q[j] = qhat; if (t < 0) { q[j]--; k = 0; for (i = 0; i < n; i++) { t = u[i + j] + n[i] + k; u[i + j] = t & mask; k = t >> 16; } u[j + n] += k; } } for (i = 0; i< n; i++) { r[i] = ((u[i] >> s) | (u[i + 1] << (16 - s))) & mask; } } return [[q[2] + q[3] * limit, q[0] + q[1] * limit], [r[2] + r[3] * limit, r[0] + r[1] * limit]]; "; var stmt = new StmtJsExplicit(ctx, js, a, b, u, v, m, n, r, q, i, j, k, l, p, s, t, qhat, rhat, mask, limit, divByZeroEx); return stmt; }
private static Stmt FindStmtReturn(Ctx ctx) { var mRef = ctx.MRef; var mRefRedirected = GetRedirect(mRef); if (mRefRedirected != null) { mRef = mRefRedirected; } var mDef = mRef.Resolve(); var jsAttr = mDef.GetCustomAttribute<JsAttribute>(); if (jsAttr != null) { var ctorArgs = jsAttr.ConstructorArguments; if (ctorArgs.Count == 2) { if (ctorArgs[0].Type.IsType() && ((Array)ctorArgs[1].Value).Length == 0) { // IJsImpl class provides the implementation var iJsImpltype = ((TypeReference)ctorArgs[0].Value).LoadType(); var iJsImpl = (IJsImpl)Activator.CreateInstance(iJsImpltype); var stmt = iJsImpl.GetImpl(ctx); return stmt; } } else if (ctorArgs.Count == 1) { if (ctorArgs[0].Type.IsString()) { // Explicit JS string provides implementation var js = (string)ctorArgs[0].Value; var parameters = mRef.Parameters.Select((x, i) => ctx.MethodParameter(i, ((char)('a' + i)).ToString())).ToArray(); var stmt = new StmtJsExplicit(ctx, js, parameters.Concat(ctx.ThisNamed)); return stmt; } } } var mappedType = TypeMap(ctx.TRef); if (mappedType == null && mDef.GetCustomAttribute<JsRedirectAttribute>() != null) { mappedType = ctx.TRef; } if (mappedType != null) { var mappedMRef = FindJsMember(mRef, mappedType); if (mappedMRef != null) { if (mappedMRef.ReturnType.FullName == typeof(Stmt).FullName) { var m = mappedMRef.LoadMethod(); var stmt = (Stmt)m.Invoke(null, new object[] { ctx }); return stmt; } } } return null; }
public static Stmt ToString(Ctx ctx) { var js = "return String.fromCharCode(this);"; var stmt = new StmtJsExplicit(ctx, js, ctx.ThisNamed); return stmt; }
public Stmt GetImpl(Ctx ctx) { var a = ctx.MethodParameter(0, "a"); var b = ctx.MethodParameter(1, "b"); var neg = ctx.Local(ctx.Boolean, "neg"); var aNegate = new ExprUnary(ctx, UnaryOp.Negate, ctx.Int64, a.Expr).Named("aNegate"); var bNegate = new ExprUnary(ctx, UnaryOp.Negate, ctx.Int64, b.Expr).Named("bNegate"); var divMod = new ExprCall(ctx, (Func<UInt64, UInt64, object>)_Int64UInt64.UInt64DivRem, null, a.Expr, b.Expr).Named("divMod"); var r = ctx.Local(ctx.Int64, "r"); var rNegate = new ExprUnary(ctx, UnaryOp.Negate, ctx.Int64, r.Expr).Named("rNegate"); // TODO: Throw ArithmeticException if a == Int64.MinValue and b == -1 // TODO: Handle a or b being Int64.MinValue var js = @" neg = false; if (a[0] >>> 31) { a = aNegate; neg = true; } if (b[0] >>> 31) b = bNegate; r = divMod[1]; return neg ? rNegate : r; "; var stmt = new StmtJsExplicit(ctx, js, a, b, neg, r, aNegate, bNegate, divMod, rNegate); return stmt; }
public static Stmt get_FullName(Ctx ctx) { var eNamespace = new ExprJsTypeData(ctx, TypeData.Namespace).Named("namespace"); var eName = new ExprJsTypeData(ctx, TypeData.Name).Named("name"); var stmt = new StmtJsExplicit(ctx, "return this.namespace+\".\"+this.name;", ctx.ThisNamed, eNamespace, eName); return stmt; }
public static JsResult CreateFrom(IEnumerable<MethodReference> rootMethods, bool verbose = false, bool testing = false) { var todo = new Queue<MethodReference>(); foreach (var method in rootMethods) { todo.Enqueue(method); } Action<MethodReference> addTodo = m => { if (m.ContainsGenericParameters()) { throw new Exception("Cannot add todo method with generic parameters"); } todo.Enqueue(m); }; // Each method, with the count of how often it is referenced. var methodsSeen = new Dictionary<MethodReference, int>(rootMethods.ToDictionary(x => x, x => 1), TypeExtensions.MethodRefEqComparerInstance); // Each type, with the count of how often it is referenced directly (newobj only at the moment). var typesSeen = new Dictionary<TypeReference, int>(TypeExtensions.TypeRefEqComparerInstance); // ASTs of all methods var methodAsts = new Dictionary<MethodReference, ICode>(TypeExtensions.MethodRefEqComparerInstance); // Each field, with the count of how often it is referenced. var fieldAccesses = new Dictionary<FieldReference, int>(TypeExtensions.FieldReqEqComparerInstance); // Each type records which virtual methods have their NewSlot definitions var virtualCalls = new Dictionary<TypeReference, HashSet<MethodReference>>(TypeExtensions.TypeRefEqComparerInstance); // Each basemost virtual method records the least-derived type actually called // This allows only virtual methods in more-derived types to be transcoded var virtualCallExactMethods = new Dictionary<MethodReference, IEnumerable<TypeReference>>(TypeExtensions.MethodRefEqComparerInstance); // Each interface type records which interface methods are called var interfaceCalls = new Dictionary<TypeReference, HashSet<MethodReference>>(TypeExtensions.TypeRefEqComparerInstance); // All instance constructors must be updated after all methods have been processed, to initialise all referenced // instance fields in the type, and to sort out 'return' statements. // This has to be done later, so the list of referenced fields in complete var instanceConstructors = new List<Ctx>(); while (todo.Any()) { // TODO: parallelise var mRef = todo.Dequeue(); var mDef = mRef.Resolve(); var tRef = mRef.DeclaringType; var tDef = tRef.Resolve(); var ctx = new Ctx(tRef, mRef); var ast = (ICode)JsResolver.ResolveMethod(ctx); if (ast == null) { var transcodeCtx = JsResolver.TranslateCtx(ctx) ?? ctx; if (transcodeCtx.MRef.ContainsGenericParameters()) { throw new InvalidOperationException("Method/type must not have generic parameters"); } if (transcodeCtx.MDef.IsAbstract) { throw new InvalidOperationException("Cannot transcode an abstract method"); } if (transcodeCtx.MDef.IsInternalCall) { throw new InvalidOperationException("Cannot transcode an internal call"); } if (transcodeCtx.MDef.IsExternal()) { throw new InvalidOperationException("Cannot transcode an external method"); } if (!transcodeCtx.MDef.HasBody) { throw new InvalidOperationException("Cannot transcode method without body"); } if (!typesSeen.ContainsKey(tRef)) { typesSeen.Add(tRef, 0); } ast = Transcoder.ToAst(transcodeCtx, verbose); } for (int i = 0; ; i++) { var astOrg = ast; ast = Transcoder.DoStep(s => (Stmt)VisitorJsRewriteSealedVCalls.V(s), (Stmt)ast, "VisitorJsRewriteSealedVCalls", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveAll.V(s), (Stmt)ast, "VisitorJsResolveAll", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveConv.V(s), (Stmt)ast, "VisitorJsResolveConv", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveSpecialTypes.V(s), (Stmt)ast, "VisitorJsResolveSpecialTypes", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveDelegates.V(s), (Stmt)ast, "VisitorJsResolveDelegates", verbose); // 64bit must be after everything else ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolve64Bit.V(s), (Stmt)ast, "VisitorJsResolve64Bit", verbose); if (ast == astOrg) { break; } if (i > 10) { // After 10 iterations even the most complex method should be sorted out throw new InvalidOperationException("Error: Stuck in loop trying to resolve AST"); } } ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveValueTypes.V(s), (Stmt)ast, "VisitorJsResolveValueTypes", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorIfSimplification.V(s), (Stmt)ast, "VisitorIfSimplification", verbose); ast = Transcoder.DoStep(s => (Stmt)VisitorJsResolveByRefParameters.V(s), (Stmt)ast, "VisitorJsResolveByRefParameters", verbose); if (mDef.IsVirtual && mRef.DeclaringType.IsValueType) { // 'this' may be boxed or unboxed. Must be unboxed if boxed // This is required because in real .NET the boxed and unboxed versions are both directly // available at the this reference; this is not the case in the JS emulation of boxing var unbox = new StmtJsExplicit(ctx, "if (this._) this = this.v;", ctx.ThisNamed); ast = new StmtBlock(ctx, unbox, (Stmt)ast); Transcoder.Print((Stmt)ast, "Unbox-this", verbose); } if (mDef.IsConstructor && !mDef.IsStatic) { // Instance constructor; add instance field initialisation and final return of 'this' later instanceConstructors.Add(ctx); } var cctors = VisitorFindStaticConstructors.V(ast) .Where(x => !TypeExtensions.MethodRefEqComparerInstance.Equals(x, mRef)) .Distinct(TypeExtensions.MethodRefEqComparerInstance) .ToArray(); if (cctors.Any()) { // All methods that access static fields or methods must call the static constructor at the very // start of the method. Except the static construtor itself, which must not recurse into itself. var cctorCalls = cctors .Select(x => new StmtWrapExpr(ctx, new ExprCall(ctx, x, null, Enumerable.Empty<Expr>(), false))).ToArray(); ast = new StmtBlock(ctx, cctorCalls.Concat((Stmt)ast)); Transcoder.Print((Stmt)ast, "Call-cctors", verbose); } if (mDef.IsConstructor && mDef.IsStatic) { // At the beginning of the static constructor, it rewrites itself as an empty function, so it is only called once. var rewrite = new StmtAssignment(ctx, new ExprJsVarMethodReference(ctx, mRef), new ExprJsEmptyFunction(ctx)); ast = new StmtBlock(ctx, rewrite, (Stmt)ast); Transcoder.Print((Stmt)ast, "cctor-once-only", verbose); } methodAsts.Add(mRef, ast); var fieldRefs = VisitorFindFieldAccesses.V(ast); foreach (var fieldRef in fieldRefs) { fieldAccesses[fieldRef] = fieldAccesses.ValueOrDefault(fieldRef) + 1; } var arrayRefs = VisitorFindNewArrays.V(ast); foreach (var arrayRef in arrayRefs) { typesSeen[arrayRef] = typesSeen.ValueOrDefault(arrayRef) + 1; } var types = VisitorFindRequiredTypes.V(ast); foreach (var type in types) { typesSeen[type] = typesSeen.ValueOrDefault(type) + 1; } if (mDef.GetCustomAttribute<JsReturnTypeDeepUseAttribute>(true) != null) { var retType = mDef.ReturnType.FullResolve(ctx); var retTypes = retType.EnumThisAllContainedTypes().ToArray(); foreach (var type in retTypes) { typesSeen[type] = typesSeen.ValueOrDefault(type) + 1; if (type.IsGenericInstance && type.Resolve().FullName == "System.Collections.Generic.Dictionary`2") { // HACK - ensure no-arg ctor is present. JSON needs it var ctor = type.EnumResolvedMethods().First(x => x.Name == ".ctor" && !x.HasParameters); if (!methodsSeen.ContainsKey(ctor)) { methodsSeen.Add(ctor, 1); addTodo(ctor); } // HACK - ensure Add(key, value) method present. JSON need sit var mAdd = type.EnumResolvedMethods().First(x => x.Name == "Add"); if (!methodsSeen.ContainsKey(mAdd)) { methodsSeen.Add(mAdd, 1); addTodo(mAdd); } } } } var calledMethods = new List<ICall>(); var calls = VisitorFindCalls.V(ast); foreach (var call in calls.Where(x => x.ExprType == Expr.NodeType.NewObj || x.IsVirtualCall)) { // Add reference to each type constructed (direct access to type variable) typesSeen[call.Type] = typesSeen.ValueOrDefault(call.Type) + 1; } foreach (var call in calls) { if (call.CallMethod.DeclaringType.Resolve().IsInterface) { var methodSet = interfaceCalls.ValueOrDefault(call.CallMethod.DeclaringType, () => new HashSet<MethodReference>(TypeExtensions.MethodRefEqComparerInstance), true); methodSet.Add(call.CallMethod); // Methods that require transcoding are added to 'todo' later continue; } if (call.IsVirtualCall) { var mBasemost = call.CallMethod.GetBasemostMethod(null); var methodSet = virtualCalls.ValueOrDefault(mBasemost.DeclaringType, () => new HashSet<MethodReference>(TypeExtensions.MethodRefEqComparerInstance), true); methodSet.Add(mBasemost); var objType = call.Obj.Type; var already = virtualCallExactMethods.ValueOrDefault(mBasemost).EmptyIfNull(); if (!already.Any(x => x.IsBaseOfOrEqual(objType))) { virtualCallExactMethods[mBasemost] = already.Concat(objType).ToArray(); } // Methods that require transcoding are added to 'todo' later continue; } calledMethods.Add(call); } foreach (var call in calledMethods) { if (methodsSeen.ContainsKey(call.CallMethod)) { methodsSeen[call.CallMethod]++; } else { methodsSeen.Add(call.CallMethod, 1); //todo.Enqueue(call.CallMethod); addTodo(call.CallMethod); } } if (!todo.Any()) { // Add System.RuntimeType if any types have been seen if (typesSeen.Any(x => x.Value > 0) && !typesSeen.Any(x => x.Key.FullName == "System.RuntimeType")) { var runtimeType = ctx.Module.Import(Type.GetType("System.RuntimeType")); typesSeen.Add(runtimeType, 1); } // Scan all virtual calls and add any required methods // Need care to handle virtual methods with generic arguments var virtualRoots = new HashSet<MethodReference>(virtualCalls.SelectMany(x => x.Value), TypeExtensions.MethodRefEqComparerInstance); var requireMethods = from type in typesSeen.Keys //let typeReverseMapped = JsResolver.ReverseTypeMap(type) let typeAndBases = type.EnumThisAllBaseTypes().ToArray() let mVRoots = typeAndBases.SelectMany(x => virtualCalls.ValueOrDefault(x).EmptyIfNull()).ToArray() let methods = type.EnumResolvedMethods(mVRoots).ToArray() from method in methods//.Select(x => JsResolver.ResolveMethod(x)) let methodDef = method.Resolve() where methodDef != null // HACK? where !methodDef.IsStatic && methodDef.IsVirtual && !methodDef.IsAbstract where !methodsSeen.ContainsKey(method) let mBasemost = method.GetBasemostMethod(method) where virtualCallExactMethods.ValueOrDefault(mBasemost).EmptyIfNull().Any(x => { //return x.IsBaseOfOrEqual(typeReverseMapped) || typeReverseMapped.IsBaseOfOrEqual(x); return x.IsBaseOfOrEqual(type) || type.IsBaseOfOrEqual(x); }) where virtualRoots.Contains(mBasemost) select method; var requireMethodsArray = requireMethods.Distinct(TypeExtensions.MethodRefEqComparerInstance).ToArray(); foreach (var method in requireMethodsArray) { methodsSeen.Add(method, 1); // TODO: How to properly handle count? //todo.Enqueue(method); addTodo(method); } // Scan all interface calls and add any required methods var iFaceMethods = from type in typesSeen.Keys from iFace in interfaceCalls let iFaceType = iFace.Key let typeAndBases = type.EnumThisAllBaseTypes().ToArray() where typeAndBases.Any(x => x.DoesImplement(iFaceType)) let methods = typeAndBases.SelectMany(x => x.EnumResolvedMethods(iFace.Value)).ToArray() from method in methods//.Select(x => JsResolver.ResolveMethod(x)) where !methodsSeen.ContainsKey(method) let methodDef = method.Resolve() where methodDef != null // HACK? where !methodDef.IsStatic && methodDef.IsVirtual && !methodDef.IsAbstract from iFaceMethod in iFace.Value where method.IsImplementationOf(iFaceMethod) select method; var iFaceMethodsArray = iFaceMethods.Distinct(TypeExtensions.MethodRefEqComparerInstance).ToArray(); foreach (var method in iFaceMethodsArray) { methodsSeen.Add(method, 1); addTodo(method); //todo.Enqueue(method); } } } // Add type count to System.RuntimeType var runtimeTypeInc = typesSeen.Keys.FirstOrDefault(x => x.FullName == "System.RuntimeType"); if (runtimeTypeInc != null) { typesSeen[runtimeTypeInc] += 2; } var instanceFieldsByType = fieldAccesses .Where(x => !x.Key.Resolve().IsStatic) .ToLookup(x => x.Key.DeclaringType.FullResolve(x.Key), TypeExtensions.TypeRefEqComparerInstance); // Update all instance constructors to initialise instance fields, add final 'return' statement, // and update any early returns to return 'this' foreach (var ctx in instanceConstructors) { var fields = instanceFieldsByType[ctx.TRef].Select(x => x.Key); var initStmts = fields.Select(x => { var f = x.FullResolve(ctx.TRef, ctx.MRef); var assign = new StmtAssignment(ctx, new ExprFieldAccess(ctx, ctx.This, f), new ExprDefaultValue(ctx, f.FieldType)); return assign; }).ToArray(); var returnStmt = new StmtReturn(ctx, ctx.This); var ast = methodAsts[ctx.MRef]; ast = new StmtBlock(ctx, initStmts.Concat((Stmt)ast).Concat(returnStmt)); ast = VisitorJsReturnThis.V(ast, ctx.This); methodAsts[ctx.MRef] = ast; } // Locally name all instance fields; base type names must not be re-used in derived types var instanceFieldsIndexed = new Dictionary<int, Tuple<IEnumerable<FieldReference>, int>>(); // <index, Tuple<all fields, total use count>> instanceFieldsByType.TypeTreeTraverse(x => x.Key, (fields, idx) => { var ordered = fields.OrderByDescending(x => x.Value).ToArray(); // Order by usage count, highest first foreach (var field in ordered) { var idxInfo = instanceFieldsIndexed.ValueOrDefault(idx, () => Tuple.Create(Enumerable.Empty<FieldReference>(), 0)); var newIdxInfo = Tuple.Create((IEnumerable<FieldReference>)idxInfo.Item1.Concat(field.Key).ToArray(), idxInfo.Item2 + field.Value); instanceFieldsIndexed[idx] = newIdxInfo; idx++; } return idx; }, 0); var orderedInstanceFields = instanceFieldsIndexed.OrderByDescending(x => x.Value.Item2).ToArray(); var instanceFieldNameGen = new NameGenerator(); var instanceFieldNames = orderedInstanceFields .Select(x => new { name = instanceFieldNameGen.GetNewName(), fields = x.Value.Item1 }) .SelectMany(x => x.fields.Select(y => new { f = y, name = x.name })) .ToArray(); // Prepare list of static fields for global naming var staticFields = fieldAccesses.Where(x => x.Key.Resolve().IsStatic).ToArray(); // Prepare local variables for global naming. // All locals in all methods are sorted by usage count, then all methods usage counts are combined var clusters = methodAsts.Values.SelectMany(x => VisitorJsPhiClusters.V(x).Select(y => new ExprVarCluster(y))).ToArray(); var varToCluster = clusters.SelectMany(x => x.Vars.Select(y => new { cluster = x, var = y })).ToDictionary(x => x.var, x => x.cluster); var varsWithCount = methodAsts.Values.Select(x => { var methodVars = VisitorFindVars.V(x); // Parameters need one extra count, as they appear in the method declaration methodVars = methodVars.Concat(methodVars.Where(y => y.ExprType == Expr.NodeType.VarParameter).Distinct()); var ret = methodVars.Select(y => varToCluster.ValueOrDefault(y) ?? y) .GroupBy(y => y) .Select(y => new { var = y.Key, count = y.Count() }) .OrderByDescending(y => y.count) .ToArray(); return ret; }).ToArray(); var localVarCounts = new Dictionary<int, int>(); foreach (var x in varsWithCount) { for (int i = 0; i < x.Length; i++) { localVarCounts[i] = localVarCounts.ValueOrDefault(i) + x[i].count; } } // Globally name all items that require names var needNaming = localVarCounts.Select(x => new { item = (object)x.Key, count = x.Value }) .Concat(methodsSeen.Select(x => new { item = (object)x.Key, count = x.Value })) .Concat(staticFields.Select(x => new { item = (object)x.Key, count = x.Value })) .Concat(typesSeen.Select(x => new { item = (object)x.Key, count = x.Value })) .Where(x => x.count > 0) .OrderByDescending(x => x.count) .ToArray(); var nameGen = new NameGenerator(); var globalNames = needNaming.ToDictionary(x => x.item, x => nameGen.GetNewName()); // Create map of all local variables to their names var localVarNames = varsWithCount.Select(x => x.Select((y, i) => new { y.var, name = globalNames[i] })) .SelectMany(x => x) .SelectMany(x => { var varCluster = x.var as ExprVarCluster; if (varCluster != null) { return varCluster.Vars.Select(y => new { var = y, name = x.name }); } else { return new[] { x }; } }) .ToDictionary(x => x.var, x => x.name); // Create map of all method names var methodNames = methodsSeen.Keys.ToDictionary(x => x, x => globalNames[x], TypeExtensions.MethodRefEqComparerInstance); // Create list of all static field names var staticFieldNames = staticFields.Select(x => new { f = x.Key, name = globalNames[x.Key] }); // Create map of all fields if (testing) { instanceFieldNames = instanceFieldNames.Select(x => { switch (x.f.FullName) { case "System.String System.Exception::_message": return new { x.f, name = "$$_message" }; } return x; }).ToArray(); } var fieldNames = instanceFieldNames.Concat(staticFieldNames).ToDictionary(x => x.f, x => x.name, TypeExtensions.FieldReqEqComparerInstance); // Create map of type names var typeNames = typesSeen .Where(x => x.Value > 0) .ToDictionary(x => x.Key, x => globalNames[x.Key], TypeExtensions.TypeRefEqComparerInstance); // Create virtual call tables var virtualCallIndices = new Dictionary<MethodReference, int>(TypeExtensions.MethodRefEqComparerInstance); var allVirtualMethods = new Dictionary<TypeReference, HashSet<MethodReference>>(TypeExtensions.TypeRefEqComparerInstance); typesSeen.Select(x => x.Key).TypeTreeTraverse(x => x, (type, vCalls) => { var mNewSlots = virtualCalls.ValueOrDefault(type).EmptyIfNull().ToArray(); int idx = vCalls.Length; foreach (var mNewSlot in mNewSlots) { virtualCallIndices[mNewSlot] = idx++; } var vCallsWithThisType = vCalls.Concat(mNewSlots).ToArray(); if (vCallsWithThisType.Length > 0) { var typesAndBases = type.EnumThisAllBaseTypes().ToArray(); var mVRoots = typesAndBases.SelectMany(x => virtualCalls.ValueOrDefault(x).EmptyIfNull()).ToArray(); var ms = type.EnumResolvedMethods(mVRoots).ToArray(); for (int i = 0; i < vCalls.Length; i++) { var mVCall = vCallsWithThisType[i]; foreach (var m in ms) { if (m.MatchMethodOnly(mVCall)) { vCallsWithThisType[i] = m; } } } var typeVMethods = new HashSet<MethodReference>(vCallsWithThisType, TypeExtensions.MethodRefEqComparerInstance); allVirtualMethods.Add(type, typeVMethods); } return vCallsWithThisType; }, new MethodReference[0]); var typeData = Enum.GetValues(typeof(TypeData)).Cast<TypeData>().ToArray(); // Name all items that are within the type information var needTypeInformationNaming = interfaceCalls.Select(x => new { item = (object)x.Key, count = 1 }) .Concat(typeData.Select(x => new { item = (object)x, count = 1 })) .OrderByDescending(x => x.count) .ToArray(); var typeInformationNameGen = new NameGenerator(); var typeInformationNames = needTypeInformationNaming.ToDictionary(x => x.item, x => typeInformationNameGen.GetNewName()); if (testing) { typeInformationNames[TypeData.Name] = "$$TypeName"; typeInformationNames[TypeData.Namespace] = "$$TypeNamespace"; } // Create map of interfaces to their names var interfaceNames = interfaceCalls.Keys.ToDictionary(x => x, x => typeInformationNames[x], TypeExtensions.TypeRefEqComparerInstance); var interfaceCallIndices = interfaceCalls.SelectMany(x => x.Value.Select((m, i) => new { m, i })).ToDictionary(x => x.m, x => x.i, TypeExtensions.MethodRefEqComparerInstance); // Create map of type data constants var typeDataNames = typeData.ToDictionary(x => x, x => typeInformationNames[x]); var resolver = new JsMethod.Resolver { LocalVarNames = localVarNames, MethodNames = methodNames, FieldNames = fieldNames, TypeNames = typeNames, VirtualCallIndices = virtualCallIndices, InterfaceCallIndices = interfaceCallIndices, InterfaceNames = interfaceNames, TypeDataNames = typeDataNames, }; var js = new StringBuilder(); js.Append("(function(){"); int jsIndent = 1; Action jsNewLine = () => { js.AppendLine(); js.Append(' ', jsIndent * JsMethod.tabSize); }; jsNewLine(); js.Append("\"use strict\";"); jsNewLine(); // Construct methods foreach (var methodInfo in methodAsts) { var mRef = methodInfo.Key; var ast = methodInfo.Value; var mJs = JsMethod.Create(mRef, resolver, ast); var mJsLines = mJs.Split(new[] { Environment.NewLine }, StringSplitOptions.None); foreach (var line in mJsLines) { jsNewLine(); js.Append(line); } } // Construct static fields foreach (var field in staticFields.Select(x => x.Key)) { jsNewLine(); js.AppendFormat("// {0}", field.FullName); jsNewLine(); if (field.Name == "Empty" && field.DeclaringType.FullName == "System.String") { // Special case, as string does not have a static constructor to set String.Empty js.AppendFormat("var {0} = \"\";", fieldNames[field]); } else { js.AppendFormat("var {0} = {1};", fieldNames[field], DefaultValuer.Get(field.FieldType, fieldNames)); } } // Construct type data var typesSeenOrdered = typesSeen .Where(x => x.Value > 0) .Select(x => x.Key) .OrderByReferencedFirst(x => x) .ToArray(); var domTypes = new Dictionary<string, TypeReference>(); foreach (var type in typesSeenOrdered) { var unmappedType = type; var tDef = unmappedType.Resolve(); // Check for DOM types var jsClassAttr = tDef.GetCustomAttribute<JsClassAttribute>(); if (jsClassAttr != null) { if (jsClassAttr.ConstructorArguments.Count == 1) { // Non-abstract types only var tagOrConstructorName = (string)jsClassAttr.ConstructorArguments[0].Value; domTypes.Add(tagOrConstructorName, unmappedType); } } // Type JS jsNewLine(); js.AppendFormat("// {0}", unmappedType.FullName); jsNewLine(); js.AppendFormat("var {0}={{", typeNames[type]); // Type information js.AppendFormat("{0}:\"{1}\"", typeDataNames[TypeData.JsName], typeNames[type]); js.AppendFormat(", {0}:\"{1}\"", typeDataNames[TypeData.Name], unmappedType.Name()); js.AppendFormat(", {0}:\"{1}\"", typeDataNames[TypeData.Namespace], unmappedType.Namespace); js.AppendFormat(", {0}:{1}", typeDataNames[TypeData.IsValueType], unmappedType.IsValueType ? "true" : "false"); js.AppendFormat(", {0}:{1}", typeDataNames[TypeData.IsPrimitive], unmappedType.IsPrimitive ? "true" : "false"); js.AppendFormat(", {0}:{1}", typeDataNames[TypeData.IsArray], unmappedType.IsArray ? "true" : "false"); js.AppendFormat(", {0}:{1}", typeDataNames[TypeData.ElementType], unmappedType.IsArray ? typeNames.ValueOrDefault(((ArrayType)unmappedType).ElementType, "null") : "null"); js.AppendFormat(", {0}:{1}", typeDataNames[TypeData.IsInterface], tDef.IsInterface ? "true" : "false"); var assignableTo = typesSeenOrdered.Where(x => unmappedType.IsAssignableTo(x)).Where(x => !x.IsSame(unmappedType)).ToArray(); js.AppendFormat(", {0}:[{1}]", typeDataNames[TypeData.AssignableTo], string.Join(", ", assignableTo.Select(x => typeNames[x]))); if (tDef.FullName == "System.Collections.Generic.Dictionary`2") { var typeGen = (GenericInstanceType)type; var dict = tDef.Module.Import(typeof(DotNetWebToolkit.Cil2Js.JsResolvers.Classes._Dictionary<,>)); var dictGen = dict.MakeGeneric(typeGen.GenericArguments[0], typeGen.GenericArguments[1]); var jsSlotsName = fieldNames[dictGen.EnumResolvedFields().First(x => x.Name == "slots")]; var slot = dictGen.Resolve().NestedTypes.First(x => x.Name == "Slot"); var slotGen = slot.MakeGeneric(typeGen.GenericArguments[0], typeGen.GenericArguments[1]); var jsHashCodeName = fieldNames[slotGen.EnumResolvedFields().First(x => x.Name == "hashCode")]; var jskeyName = fieldNames[slotGen.EnumResolvedFields().First(x => x.Name == "key")]; var jsValueName = fieldNames[slotGen.EnumResolvedFields().First(x => x.Name == "value")]; js.AppendFormat(", {0}:['{1}','{2}','{3}','{4}']", typeDataNames[TypeData.IsDictionary], jsSlotsName, jsHashCodeName, jskeyName, jsValueName); } if (!tDef.IsInterface) { if (!tDef.IsAbstract) { // Virtual method table, only needed on concrete types var typeAndBases = type.EnumThisAllBaseTypes().ToArray(); var methods = allVirtualMethods.ValueOrDefault(type); if (methods != null) { var idxs = methods .Select(x => { var xBasemost = x.GetBasemostMethod(x); return new { m = x, idx = virtualCallIndices[xBasemost] }; }) .OrderBy(x => x.idx) .ToArray(); var s = string.Join(", ", idxs.Select(x => methodNames.ValueOrDefault(x.m, "null"))); js.AppendFormat(", {0}:[{1}]", typeDataNames[TypeData.VTable], s); } // Interface tables, only needed on concrete types var implementedIFaces = interfaceCalls.Where(x => typeAndBases.Any(y => y.DoesImplement(x.Key))).ToArray(); foreach (var iFace in implementedIFaces) { js.Append(", "); var iFaceName = interfaceNames[iFace.Key]; js.AppendFormat("{0}:[", iFaceName); var qInterfaceTableNames = from iMethod in iFace.Value let tMethod = typeAndBases.SelectMany(x => x.EnumResolvedMethods(iMethod)).First(x => x.IsImplementationOf(iMethod)) //let tM2 = JsResolver.ResolveMethod(tMethod) let idx = interfaceCallIndices[iMethod] orderby idx let methodName = methodNames[tMethod] select methodName; var interfaceTableNames = qInterfaceTableNames.ToArray(); js.Append(string.Join(", ", interfaceTableNames)); js.Append("]"); } } } if (tDef.IsEnum && tDef.GetCustomAttribute<JsStringEnumAttribute>() != null) { // JS string/enum map var values = tDef.Fields.Where(x => x.IsLiteral).Select(x => { return string.Format("{0}:\"{1}\",\"{1}\":{0}", x.Constant, JsResolver.JsName(x)); }).ToArray(); js.AppendFormat(", {0}:{{{1}}}", typeDataNames[TypeData.EnumStringMap], string.Join(", ", values)); } // end js.Append("};"); } // Add type of each type, if System.RuntimeType has been seen var typeRuntimeType = typesSeen.Keys.FirstOrDefault(x => x.FullName == "System.RuntimeType"); if (typeRuntimeType != null) { jsNewLine(); foreach (var type in typesSeenOrdered) { js.Append(typeNames[type]); js.Append("._ = "); } js.Append(typeNames[typeRuntimeType]); js.Append(";"); } // Add comments descibing each interface jsNewLine(); js.Append("// Interface name map"); jsNewLine(); js.AppendFormat("// {0} = VTable", typeDataNames[TypeData.VTable]); foreach (var iFace in interfaceNames) { jsNewLine(); js.AppendFormat("// {0} = {1}", iFace.Value, iFace.Key.FullName); } // Add map of DOM types if (domTypes.Any()) { jsNewLine(); js.Append("// DOM type mapping"); jsNewLine(); // TODO: Auto-name this js.Append("var __ = {"); jsIndent++; foreach (var domType in domTypes) { jsNewLine(); js.AppendFormat("'{0}': {1},", domType.Key, typeNames[domType.Value]); } js.Length--; jsIndent--; jsNewLine(); js.Append("};"); } if (typesSeenOrdered.Any()) { jsNewLine(); js.Append("// json type mapping"); jsNewLine(); // TODO: Auto-name this js.Append("var $$ = {"); foreach (var type in typesSeenOrdered) { js.AppendFormat("'{0}':{0},", typeNames[type]); } js.Length--; js.Append("};"); var typesDicts = typesSeenOrdered .Where(x => x.IsGenericInstance && x.Resolve().FullName == "System.Collections.Generic.Dictionary`2") .ToArray(); if (typesDicts.Any()) { jsNewLine(); js.Append("// json dictionary info"); jsNewLine(); // TODO: Auto-name or get rid of this js.Append("var $d = {"); var any = false; foreach (var type in typesDicts) { var typeName = typeNames[type]; var ctor = type.EnumResolvedMethods().First(x => x.Name == ".ctor" && !x.HasParameters); var mAdd = type.EnumResolvedMethods().First(x => x.Name == "Add"); // If dict not involved in JSON, these methods may not be present if (methodNames.ContainsKey(ctor) && methodNames.ContainsKey(mAdd)) { var ctorName = methodNames[ctor]; var mAddName = methodNames[mAdd]; js.AppendFormat("'{0}':[{1},{2}],", typeName, ctorName, mAddName); any = true; } } if (any) { js.Length--; } js.Append("};"); } } jsNewLine(); jsNewLine(); js.Append("// Exports"); if (!testing) { var rootMethodsByType = rootMethods.ToLookup(x => x.DeclaringType, TypeExtensions.TypeRefEqComparerInstance); Action<NamespaceTree> treeToJs = null; treeToJs = tree => { js.Append("{"); jsIndent++; foreach (var subNs in tree.Namespaces) { jsNewLine(); js.AppendFormat("'{0}': ", subNs.NamespacePart); treeToJs(subNs); } if (tree.Types.Any()) { foreach (var type in tree.Types) { jsNewLine(); js.AppendFormat("'{0}': {{", type.Name); jsIndent++; foreach (var method in rootMethodsByType[type]) { jsNewLine(); js.AppendFormat("'{0}': {1},", method.Name, methodNames[method]); } js.Length--; jsIndent--; jsNewLine(); js.Append("},"); } js.Length--; } jsIndent--; jsNewLine(); js.Append("}"); }; var trees = NamespaceTree.Make(rootMethodsByType.Select(x => x.Key)); foreach (var tree in trees) { jsNewLine(); js.AppendFormat("window['{0}'] = ", tree.NamespacePart); treeToJs(tree); js.Append(";"); } } else { jsNewLine(); js.AppendFormat("window['main'] = {0};", methodNames[rootMethods.First()]); } jsIndent--; jsNewLine(); jsNewLine(); js.Append("})();"); var jsStr = js.ToString(); //Console.WriteLine(jsStr); var qFieldMap = from fieldName in fieldNames let declType = fieldName.Key.DeclaringType let declTypeMapped = JsResolver.TypeMapReverse(declType) ?? declType let declTypeName = declTypeMapped.AssemblyQualifiedName() where declType != null group fieldName by declTypeName; var fieldMap = qFieldMap.ToDictionary(x => x.Key, x => x.ToDictionary(y => y.Key.Name, y => y.Value)); var qTypeMap = from typeName in typeNames let type = typeName.Key.AssemblyQualifiedName() where type != null select new { type, typeName.Value }; var ttt = qTypeMap.ToArray(); var typeMap = ttt.ToDictionary(x => x.type, x => x.Value); var jsTypeMap = new JsonTypeMap(typeMap, fieldMap); return new JsResult(jsStr, jsTypeMap); }
public Stmt GetImpl(Ctx ctx) { var a = ctx.MethodParameter(0, "a"); var b = ctx.MethodParameter(1, "b"); var stmt = new StmtJsExplicit(ctx, "return a[0] !== b[0] || a[1] !== b[1];", a, b); return stmt; }
public static Stmt GetType(Ctx ctx) { var js = "return typeof(this) == \"string\" ? stringType : this._ || __[this.tagName] || __[this.constructor.name];"; var stringType = new ExprJsTypeVarName(ctx, ctx.String).Named("stringType"); var stmt = new StmtJsExplicit(ctx, js, ctx.ThisNamed, stringType); return stmt; }