| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package adobe.abc; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| |
| import adobe.abc.Algorithms.EdgeMap; |
| |
| import static adobe.abc.Algorithms.*; |
| import static adobe.abc.OptimizerConstants.BOTTOM; |
| import static adobe.abc.OptimizerConstants.NAN; |
| import static adobe.abc.OptimizerConstants.OP_arg; |
| import static adobe.abc.OptimizerConstants.OP_hasnext2_i; |
| import static adobe.abc.OptimizerConstants.OP_hasnext2_o; |
| import static adobe.abc.OptimizerConstants.OP_phi; |
| import static adobe.abc.OptimizerConstants.OP_xarg; |
| import static adobe.abc.OptimizerConstants.UNDEFINED; |
| import static adobe.abc.OptimizerConstants.opNames; |
| import static adobe.abc.TypeAnalysis.isPointer; |
| import static adobe.abc.TypeAnalysis.type; |
| import static java.lang.Boolean.FALSE; |
| import static java.lang.Boolean.TRUE; |
| import static macromedia.asc.embedding.avmplus.ActionBlockConstants.*; |
| |
| |
| public abstract class TypeAnalysis |
| { |
| |
| public static Map<Expr,Typeref> getExprTypes(Method m) |
| { |
| Map<Expr,Typeref> types = new HashMap<Expr,Typeref>(); |
| Map<Expr,Object> values = new HashMap<Expr,Object>(); |
| |
| analyzeTypes(m, types, values); |
| return types; |
| } |
| public static void analyzeTypes(Method m, Map<Expr,Typeref> types, Map<Expr,Object> values) |
| { |
| // first build the SSA Edges. |
| Deque<Block> code = dfs(m.entry.to); |
| EdgeMap<Expr> uses = findUses(code); |
| |
| Set<Edge> reached = new TreeSet<Edge>(); |
| |
| analyzeTypes(m, uses, values, types, reached); |
| } |
| |
| public static void analyzeTypes(Method m, EdgeMap<Expr> uses, Map<Expr, Object> values, Map<Expr, Typeref> types, Set<Edge> reached) |
| { |
| Set<Edge> flowWork = new TreeSet<Edge>(); |
| Set<Expr> ssaWork = new TreeSet<Expr>(); |
| Set<Expr> ready = new TreeSet<Expr>(); |
| |
| flowWork.add(m.entry); |
| do |
| { |
| while (!flowWork.isEmpty()) |
| { |
| Edge edge = getEdge(flowWork); |
| if (!reached.contains(edge)) |
| { |
| reached.add(edge); |
| Block b = edge.to; |
| ready.addAll(b.exprs); |
| ssaWork.addAll(b.exprs); |
| for (Edge x: b.xsucc) |
| flowWork.add(x); |
| } |
| } |
| while (!ssaWork.isEmpty()) |
| { |
| Expr e = getExpr(ssaWork); |
| if (ready.contains(e)) |
| { |
| evaluateExpr(m, e, values, types, flowWork, ssaWork, uses); |
| } |
| } |
| } |
| while (!flowWork.isEmpty()); |
| } |
| |
| /** |
| * visit a single expression. compute it's type and constant value. If |
| * either change, add any dependents to the appropriate work list. |
| * |
| * @param m |
| * @param e |
| * @param values |
| * @param types |
| * @param flowWork |
| * @param ssaWork |
| * @param uses |
| */ |
| private static void evaluateExpr(Method m, Expr e, |
| Map<Expr,Object> values, |
| Map<Expr,Typeref> types, |
| Set<Edge>flowWork, Set<Expr>ssaWork, |
| EdgeMap<Expr> uses) |
| { |
| Object v = null; |
| Typeref tref = null; |
| |
| if (e.op == OP_phi) |
| { |
| for (Expr a: e.args) |
| { |
| // compute the phi() meet operation. ignore any inputs we |
| // haven't evaluated yet. If all inputs have the same value, |
| // phi() has that value. Otherwise return BOTTOM. |
| Object av = values.get(a); |
| if (av == null) continue; // ignore TOP inputs |
| |
| if (v == null) |
| v = av; |
| else if (!av.equals(v)) |
| v = BOTTOM; |
| |
| // same idea for types, but if they aren't all the same |
| // then compute the most derived base class (mdb) of the types. |
| Typeref aref = types.get(a); |
| if (tref == null) |
| tref = aref; |
| else if (!tref.equals(aref)) |
| tref = mdb(tref,aref); |
| } |
| } |
| else |
| { |
| // if any arg is TOP result is TOP (unchanged) |
| for (Expr a : e.args) if (!values.containsKey(a)) return; |
| for (Expr a : e.scopes) if (!values.containsKey(a)) return; |
| for (Expr a : e.locals) if (!values.containsKey(a)) return; |
| |
| v = BOTTOM; |
| tref = TypeCache.instance().ANY.ref; |
| |
| switch (e.op) |
| { |
| default: |
| System.err.println("unhandled op:" + e.op + ":"+ opNames[e.op]); |
| assert(false); |
| |
| case OP_hasnext2_o: |
| case OP_nextname: |
| case OP_nextvalue: |
| case OP_call: |
| case OP_callsuper: |
| case OP_getsuper: |
| case OP_getdescendants: |
| break; |
| |
| case OP_convert_o: |
| { |
| tref = types.get(e.args[0]).nonnull(); |
| v = values.get(e.args[0]); |
| break; |
| } |
| case OP_esc_xattr: |
| case OP_esc_xelem: |
| tref = TypeCache.instance().STRING.ref.nonnull(); |
| break; |
| |
| case OP_newcatch: |
| tref = m.handlers[e.imm[0]].activation; |
| break; |
| |
| case OP_newobject: |
| tref = TypeCache.instance().OBJECT.ref.nonnull(); |
| break; |
| |
| case OP_newarray: |
| tref = TypeCache.instance().ARRAY.ref.nonnull(); |
| break; |
| |
| case OP_newactivation: |
| tref = m.activation; |
| break; |
| |
| case OP_getglobalscope: |
| if (m.cx.scopes.length > 0) |
| { |
| tref = m.cx.scopes[0]; |
| } |
| else |
| { |
| // same as getscopeobject<0> |
| v = values.get(e.scopes[0].args[0]); |
| tref = types.get(e.scopes[0].args[0]); |
| } |
| break; |
| |
| case OP_getscopeobject: |
| v = values.get(e.scopes[0].args[0]); |
| tref = types.get(e.scopes[0].args[0]); |
| if ( tref == null ) |
| { |
| // FIXME: Should be more thorough. |
| tref = TypeCache.instance().ANY.ref; |
| } |
| break; |
| |
| case OP_newclass: |
| tref = e.c.ref.nonnull(); |
| break; |
| |
| case OP_newfunction: |
| tref = TypeCache.instance().FUNCTION.ref.nonnull(); |
| break; |
| |
| case OP_finddef: |
| if (TypeCache.instance().globals.contains(e.ref)) |
| tref = TypeCache.instance().globals.get(e.ref); |
| break; |
| |
| case OP_findpropstrict: |
| case OP_findproperty: |
| { |
| int i = findInner(e.ref, e.scopes, types); |
| if (i >= 0) |
| { |
| v = values.get(e.scopes[i]); |
| tref = types.get(e.scopes[i]); |
| } |
| else if ((i = findOuter(e.ref, m.cx.scopes)) >= 0) |
| { |
| tref = m.cx.scopes[i]; |
| } |
| else if (TypeCache.instance().globals.contains(e.ref)) |
| { |
| tref = TypeCache.instance().globals.get(e.ref); |
| } |
| else |
| { |
| // not found. use global. |
| if (m.cx.scopes.length > 0) |
| { |
| tref = m.cx.scopes[0]; |
| } |
| else |
| { |
| v = values.get(e.scopes[0]); |
| tref = types.get(e.scopes[0]); |
| } |
| } |
| break; |
| } |
| |
| case OP_getlex: |
| { |
| // findproperty + getproperty |
| int i = findInner(e.ref, e.scopes, types); |
| Typeref stref = i >= 0 ? types.get(e.scopes[i]) : |
| (i=findOuter(e.ref, m.cx.scopes)) >= 0 ? m.cx.scopes[i] : |
| TypeCache.instance().globals.contains(e.ref) ? TypeCache.instance().globals.get(e.ref) : |
| m.cx.scopes.length > 0 ? m.cx.scopes[0] : |
| types.get(e.scopes[0]); |
| |
| Binding b = stref.t.findGet(e.ref); |
| // code below is a copy of OP_getproperty |
| if (b != null) |
| { |
| |
| if ( b.isSlot()) |
| { |
| // TODO we only compute const value here if primitive type. |
| // it would be more correct if we knew whether the initializer |
| // changed the const value. (consts can be computed in init). |
| tref = b.type; |
| if (b.isConst() && b.defaultValueChanged()) |
| v = b.value; |
| } |
| else if (b.isMethod()) |
| { |
| // TODO if base type is or might be XML, return ANY |
| // TODO use MethodClosure, more specific than Function |
| tref = TypeCache.instance().FUNCTION.ref.nonnull(); |
| } |
| else if (b.isGetter()) |
| { |
| tref = b.method.returns; |
| } |
| } |
| break; |
| } |
| |
| case OP_construct: |
| { |
| tref = TypeCache.instance().OBJECT.ref.nonnull(); |
| break; |
| } |
| |
| case OP_constructprop: |
| { |
| Type ot = type(types,e.args[0]); // type of base object |
| Binding b = ot.findGet(e.ref); |
| if (b != null && b.type != null && b.type.t.itype != null) |
| { |
| tref = b.type.t.itype.ref.nonnull(); |
| break; |
| } |
| break; |
| } |
| |
| case OP_callproperty: |
| case OP_callproplex: |
| { |
| Type ot = type(types, e.args[0]); |
| Binding b = ot.findGet(e.ref); |
| if ( b != null ) |
| { |
| if (b.isMethod()) |
| { |
| tref = b.method.returns; |
| } |
| else if (b.isSlot() && b.type != null) |
| { |
| // each of these has same logic as convert_i, convert_s, etc |
| if (b.type.t.itype == TypeCache.instance().INT) |
| { |
| tref = TypeCache.instance().INT.ref; |
| if ( e.args.length > 1) |
| v = eval_convert_i(values.get(e.args[1])); |
| } |
| else if (b.type.t.itype == TypeCache.instance().UINT) |
| { |
| tref = TypeCache.instance().UINT.ref; |
| if ( e.args.length > 1) |
| v = eval_convert_u(values.get(e.args[1])); |
| } |
| else if (b.type.t.itype == TypeCache.instance().STRING) |
| { |
| tref = TypeCache.instance().STRING.ref.nonnull(); |
| if ( e.args.length > 1) |
| v = eval_convert_s(values.get(e.args[1])); |
| } |
| else if (b.type.t.itype == TypeCache.instance().BOOLEAN) |
| { |
| tref = TypeCache.instance().BOOLEAN.ref; |
| if ( e.args.length > 1) |
| v = eval_convert_b(values.get(e.args[1])); |
| } |
| else if (b.type.t.itype == TypeCache.instance().NUMBER) |
| { |
| tref = TypeCache.instance().NUMBER.ref; |
| if ( e.args.length > 1) |
| v = eval_convert_d(values.get(e.args[1])); |
| } |
| } |
| } |
| break; |
| } |
| |
| case OP_applytype: |
| tref = types.get(e.args[0]).nonnull(); |
| break; |
| |
| case OP_callstatic: |
| tref = e.m.returns; |
| break; |
| |
| case OP_arg: |
| if (e.imm[0] < m.getParams().length) |
| tref = m.getParams()[e.imm[0]]; |
| else if (m.needsArguments()||m.needsRest() && e.imm[0] == m.getParams().length) |
| tref = TypeCache.instance().ARRAY.ref.nonnull(); |
| else |
| tref = TypeCache.instance().VOID.ref; |
| break; |
| |
| case OP_xarg: |
| tref = m.handlers[e.imm[0]].type; |
| break; |
| |
| case OP_getslot: |
| { |
| Type t0 = type(types, e.args[0]); |
| Binding b = t0.findSlot(e.imm[0]); |
| if (b != null) |
| tref = b.type; |
| break; |
| } |
| |
| case OP_getproperty: |
| { |
| Type t0 = type(types, e.args[0]); |
| Binding b = t0.findGet(e.ref); |
| if ( b != null ) |
| { |
| if (b.isSlot()) |
| { |
| // TODO we only compute const value here if primitive type. |
| // it would be more correct if we knew whether the initializer |
| // changed the const value. (consts can be computed in init). |
| tref = b.type; |
| if (b.isConst() && b.defaultValueChanged()) |
| v = b.value; |
| } |
| else if (b.isMethod()) |
| { |
| // TODO if base type is or might be XML, return ANY |
| // TODO use MethodClosure, more specific than Function |
| tref = TypeCache.instance().FUNCTION.ref.nonnull(); |
| } |
| else if (b.isGetter()) |
| { |
| tref = b.method.returns; |
| } |
| } |
| break; |
| } |
| |
| case OP_pushundefined: |
| v = e.value; |
| tref = TypeCache.instance().VOID.ref; |
| break; |
| |
| case OP_pushnull: |
| v = e.value; |
| tref = TypeCache.instance().NULL.ref; |
| break; |
| |
| case OP_pushtrue: |
| case OP_pushfalse: |
| v = e.value; |
| tref = TypeCache.instance().BOOLEAN.ref; |
| break; |
| |
| case OP_pushbyte: |
| case OP_pushshort: |
| case OP_pushint: |
| v = e.value; |
| tref = TypeCache.instance().INT.ref; |
| break; |
| |
| case OP_pushuint: |
| v = e.value; |
| tref = TypeCache.instance().UINT.ref; |
| break; |
| |
| case OP_pushstring: |
| v = e.value; |
| tref = TypeCache.instance().STRING.ref.nonnull(); |
| break; |
| |
| case OP_pushnan: |
| case OP_pushdouble: |
| v = e.value; |
| tref = TypeCache.instance().NUMBER.ref; |
| break; |
| |
| case OP_pushnamespace: |
| v = e.value; |
| tref = TypeCache.instance().NAMESPACE.ref.nonnull(); |
| break; |
| |
| case OP_jump: |
| flowWork.add(e.succ[0]); |
| return; |
| |
| case OP_lookupswitch: |
| { |
| Object v1 = values.get(e.args[0]); |
| if (v1 == BOTTOM) |
| for (Edge s: e.succ) |
| flowWork.add(s); |
| else |
| { |
| // input is const |
| int i = intValue(v1); |
| if (i < 0 || i >= e.succ.length-1) |
| i = e.succ.length-1; |
| flowWork.add(e.succ[i]); |
| } |
| return; |
| } |
| |
| case OP_iffalse: |
| case OP_iftrue: |
| { |
| Object v1 = values.get(e.args[0]); |
| if (v1 == BOTTOM) |
| { |
| flowWork.add(e.succ[0]); |
| flowWork.add(e.succ[1]); |
| } |
| else if (e.op == OP_iffalse) |
| flowWork.add(e.succ[booleanValue(v1) ? 0 : 1]); |
| else if (e.op == OP_iftrue) |
| flowWork.add(e.succ[booleanValue(v1) ? 1 : 0]); |
| return; |
| } |
| |
| case OP_pushscope: |
| case OP_pushwith: |
| // treat this as a copy. |
| v = values.get(e.args[0]); |
| tref = types.get(e.args[0]).nonnull(); |
| break; |
| |
| case OP_convert_b: |
| tref = TypeCache.instance().BOOLEAN.ref; |
| v = eval_convert_b(values.get(e.args[0])); |
| break; |
| |
| case OP_not: |
| { |
| tref = TypeCache.instance().BOOLEAN.ref; |
| Object v0 = values.get(e.args[0]); |
| if (v0 != BOTTOM) |
| v = booleanValue(v0) ? FALSE : TRUE; |
| break; |
| } |
| |
| case OP_deleteproperty: |
| // TODO result is const false for any declared property |
| case OP_deldescendants: |
| case OP_hasnext: |
| case OP_hasnext2: |
| case OP_equals: |
| case OP_strictequals: |
| case OP_in: |
| case OP_istype: |
| case OP_istypelate: |
| case OP_instanceof: |
| tref = TypeCache.instance().BOOLEAN.ref; |
| break; |
| |
| case OP_lessthan: |
| case OP_lessequals: |
| case OP_greaterthan: |
| case OP_greaterequals: |
| { |
| tref = TypeCache.instance().BOOLEAN.ref; |
| Object v0 = values.get(e.args[0]); |
| Object v1 = values.get(e.args[1]); |
| if (v0.equals(NAN) || v0 == UNDEFINED || v1.equals(NAN) || v1 == UNDEFINED) |
| v = FALSE; |
| else if (v0 != BOTTOM && v1 != BOTTOM) |
| v = e.op == OP_lessthan ? lessthan(v0,v1) : |
| e.op == OP_lessequals ? !lessthan(v1,v0) : |
| e.op == OP_greaterthan ? lessthan(v1,v0) : |
| !lessthan(v0,v1); |
| break; |
| } |
| |
| case OP_convert_s: |
| tref = TypeCache.instance().STRING.ref.nonnull(); |
| v = eval_convert_s(values.get(e.args[0])); |
| break; |
| |
| case OP_coerce_s: |
| { |
| tref = eval_coerce_s(types.get(e.args[0])); |
| v = eval_coerce_s(values.get(e.args[0])); |
| break; |
| } |
| |
| case OP_coerce_o: |
| { |
| Typeref t0 = types.get(e.args[0]); |
| tref = eval_coerce_o(t0); |
| v = eval_coerce_o(values.get(e.args[0]), t0.t); |
| break; |
| } |
| |
| case OP_coerce_a: |
| { |
| // This cast has meaning if it's casting from void. |
| // Otherwise, it's an upcast and can be removed; |
| // casts will be re-inserted as appropriate. |
| if ( ! (types.get(e.args[0]).equals(TypeCache.instance().VOID.ref) ) ) |
| { |
| v = values.get(e.args[0]); |
| tref = types.get(e.args[0]); |
| } |
| else |
| { |
| tref = TypeCache.instance().ANY.ref; |
| } |
| break; |
| } |
| |
| case OP_coerce: |
| { |
| Typeref t0 = types.get(e.args[0]); |
| Object v0 = values.get(e.args[0]); |
| Type t = TypeCache.instance().namedTypes.get(e.ref); |
| assert ( t != null ); |
| |
| if (t == TypeCache.instance().STRING) |
| { |
| tref = eval_coerce_s(t0); |
| v = eval_coerce_s(v0); |
| } |
| else if (t == TypeCache.instance().OBJECT) |
| { |
| tref = eval_coerce_o(t0); |
| v = eval_coerce_o(v0,t0.t); |
| } |
| else if (t == TypeCache.instance().INT) |
| { |
| tref = t.ref; |
| v = eval_convert_i(v0); |
| } |
| else if (t == TypeCache.instance().UINT) |
| { |
| tref = t.ref; |
| v = eval_convert_u(v0); |
| } |
| else if (t == TypeCache.instance().NUMBER) |
| { |
| tref = t.ref; |
| v = eval_convert_d(v0); |
| } |
| else if (t == TypeCache.instance().BOOLEAN) |
| { |
| tref = t.ref; |
| v = eval_convert_b(v0); |
| } |
| else |
| { |
| // pointer style cast |
| if (t0.t.extendsOrIsBase(t)) |
| { |
| // ignore upcasts |
| tref = t0; |
| v = v0; |
| } |
| else if (t0.t == TypeCache.instance().NULL || t0.t == TypeCache.instance().VOID) |
| { |
| tref = TypeCache.instance().NULL.ref; |
| } |
| else |
| { |
| tref = t.ref; |
| } |
| } |
| break; |
| } |
| |
| case OP_astype: |
| // TODO constant folding |
| tref = TypeCache.instance().namedTypes.get(e.ref).ref; |
| break; |
| |
| case OP_astypelate: |
| { |
| Typeref t1 = types.get(e.args[1]); |
| if (t1.t.itype != null) |
| { |
| if (t1.t.itype.atom || t1.t.itype.numeric) |
| tref = TypeCache.instance().OBJECT.ref; |
| else |
| tref = t1.t.itype.ref; |
| } |
| else |
| { |
| tref = TypeCache.instance().ANY.ref; |
| } |
| break; |
| } |
| |
| case OP_typeof: |
| { |
| Type t0 = type(types,e.args[0]); |
| if (t0 == TypeCache.instance().INT || t0 == TypeCache.instance().UINT || t0 == TypeCache.instance().NUMBER) |
| v = "number"; |
| else if (t0 == TypeCache.instance().STRING) |
| v = "string"; |
| else if (t0.extendsOrIsBase(TypeCache.instance().XML) || t0.extendsOrIsBase(TypeCache.instance().XMLLIST)) |
| v = "xml"; |
| else if (t0 == TypeCache.instance().VOID) |
| v = "undefined"; |
| else if (t0 == TypeCache.instance().BOOLEAN) |
| v = "boolean"; |
| else if (t0.extendsOrIsBase(TypeCache.instance().FUNCTION)) |
| v = "function"; |
| else if (t0 != TypeCache.instance().OBJECT && t0.extendsOrIsBase(TypeCache.instance().OBJECT)) |
| v = "object"; |
| tref = TypeCache.instance().STRING.ref.nonnull(); |
| break; |
| } |
| |
| case OP_add: |
| { |
| Expr a0 = e.args[0]; |
| Expr a1 = e.args[1]; |
| Typeref t0 = types.get(a0); |
| Typeref t1 = types.get(a1); |
| Object v0 = values.get(a0); |
| Object v1 = values.get(a1); |
| if (t0.t == TypeCache.instance().STRING && !t0.nullable || t1.t == TypeCache.instance().STRING && !t1.nullable) |
| { |
| tref = TypeCache.instance().STRING.ref.nonnull(); |
| if (v0 != BOTTOM && v1 != BOTTOM) |
| v = stringValue(v0) + stringValue(v1); |
| } |
| else if (t0.t.numeric && t1.t.numeric) |
| { |
| tref = TypeCache.instance().NUMBER.ref; |
| if (v0 instanceof Number && v1 instanceof Number) |
| v = doubleValue(v0) + doubleValue(v1); |
| } |
| else |
| { |
| // TODO make all primitives extend a type so we can use that type here. |
| tref = TypeCache.instance().OBJECT.ref.nonnull(); // will be a String or a Number |
| } |
| break; |
| } |
| |
| case OP_divide: |
| { |
| tref = TypeCache.instance().NUMBER.ref; |
| Object v0 = values.get(e.args[0]); |
| Object v1 = values.get(e.args[1]); |
| if (v0 instanceof Number && v1 instanceof Number) |
| v = doubleValue(v0) / doubleValue(v1); |
| break; |
| } |
| |
| case OP_subtract: |
| case OP_multiply: |
| case OP_modulo: |
| case OP_negate: |
| case OP_increment: |
| case OP_decrement: |
| tref = TypeCache.instance().NUMBER.ref; |
| break; |
| |
| case OP_convert_d: |
| tref = TypeCache.instance().NUMBER.ref; |
| v = eval_convert_d(values.get(e.args[0])); |
| break; |
| |
| case OP_convert_i: |
| tref = TypeCache.instance().INT.ref; |
| v = eval_convert_i(values.get(e.args[0])); |
| break; |
| |
| case OP_convert_u: |
| tref = TypeCache.instance().UINT.ref; |
| v = eval_convert_u(values.get(e.args[0])); |
| break; |
| |
| case OP_bitor: |
| { |
| tref = TypeCache.instance().INT.ref; |
| Object v0 = values.get(e.args[0]); |
| Object v1 = values.get(e.args[1]); |
| if (v0 instanceof Number && v1 instanceof Number) |
| v = intValue(v0) | intValue(v1); |
| break; |
| } |
| |
| case OP_bitand: |
| { |
| tref = TypeCache.instance().INT.ref; |
| Object v0 = values.get(e.args[0]); |
| Object v1 = values.get(e.args[1]); |
| if (v0 instanceof Number && v1 instanceof Number) |
| { |
| v = intValue(v0) & intValue(v1); |
| } |
| break; |
| } |
| |
| case OP_bitnot: |
| case OP_add_i: |
| case OP_subtract_i: |
| case OP_multiply_i: |
| case OP_negate_i: |
| case OP_bitxor: |
| case OP_lshift: |
| case OP_rshift: |
| case OP_hasnext2_i: |
| case OP_increment_i: |
| case OP_decrement_i: |
| // TODO constant folding |
| tref = TypeCache.instance().INT.ref; |
| break; |
| |
| case OP_urshift: |
| // TODO constant folding |
| tref = TypeCache.instance().UINT.ref; |
| break; |
| |
| // these ops do not produce any value |
| case OP_setslot: |
| case OP_setproperty: |
| case OP_setsuper: |
| //case OP_setglobalslot: // deprecated |
| case OP_initproperty: |
| case OP_callpropvoid: |
| case OP_constructsuper: |
| case OP_callsupervoid: |
| case OP_returnvoid: |
| case OP_returnvalue: |
| case OP_throw: |
| case OP_popscope: |
| case OP_debug: |
| case OP_debugline: |
| case OP_debugfile: |
| case OP_bkpt: |
| case OP_bkptline: |
| case OP_checkfilter: |
| return; |
| } |
| } |
| |
| assert(tref != null && tref.t != null); |
| |
| // singleton types have a specific value. |
| if (tref.t == TypeCache.instance().VOID) |
| v = UNDEFINED; |
| else if (tref.t == TypeCache.instance().NULL) |
| v = TypeCache.instance().NULL; |
| |
| if (v != null && !v.equals(values.get(e))) |
| { |
| values.put(e, v); |
| ssaWork.addAll(uses.get(e)); |
| } |
| |
| if (!tref.equals(types.get(e))) |
| { |
| types.put(e, tref); |
| ssaWork.addAll(uses.get(e)); |
| } |
| } |
| |
| public static boolean isPointer(Type t) |
| { |
| return !t.isAtom() && !t.numeric; |
| } |
| |
| public static Object eval_convert_i(Object v0) |
| { |
| return v0 instanceof Number ? intValue(v0) : |
| v0 == TRUE ? 1 : |
| v0 == FALSE ? 0 : |
| BOTTOM; |
| } |
| |
| public static Object eval_convert_u(Object v0) |
| { |
| return v0 instanceof Number ? uintValue(v0) : |
| v0 == TRUE ? 1 : |
| v0 == FALSE ? 0 : |
| BOTTOM; |
| } |
| |
| public static Object eval_convert_d(Object v0) |
| { |
| return v0 instanceof Number ? doubleValue(v0) : |
| v0 == TRUE ? 1 : |
| v0 == FALSE ? 0 : |
| BOTTOM; |
| } |
| |
| public static Object eval_convert_b(Object v0) |
| { |
| return v0 == BOTTOM ? BOTTOM : |
| booleanValue(v0) ? TRUE : FALSE; |
| } |
| |
| public static Object eval_convert_s(Object v0) |
| { |
| return v0 != BOTTOM ? stringValue(v0) : BOTTOM; |
| } |
| |
| public static Typeref eval_coerce_s(Typeref t0) |
| { |
| if (t0.nullable) |
| return t0.t == TypeCache.instance().VOID || t0.t == TypeCache.instance().NULL ? TypeCache.instance().NULL.ref : TypeCache.instance().STRING.ref; |
| else |
| return TypeCache.instance().STRING.ref.nonnull(); |
| } |
| |
| public static Object eval_coerce_s(Object v0) |
| { |
| return v0 == UNDEFINED || v0 == TypeCache.instance().NULL ? TypeCache.instance().NULL : |
| v0 != BOTTOM ? stringValue(v0) : |
| BOTTOM; |
| } |
| |
| public static Typeref eval_coerce_o(Typeref t0) |
| { |
| if (t0.nullable) |
| return t0.t.extendsOrIsBase(TypeCache.instance().OBJECT) ? t0 : |
| t0.t == TypeCache.instance().VOID || t0.t == TypeCache.instance().NULL ? TypeCache.instance().NULL.ref : |
| TypeCache.instance().OBJECT.ref; |
| else |
| return t0.t.extendsOrIsBase(TypeCache.instance().OBJECT) ? t0 : TypeCache.instance().OBJECT.ref.nonnull(); |
| } |
| |
| public static Object eval_coerce_o(Object v0, Type t0) |
| { |
| return t0.extendsOrIsBase(TypeCache.instance().OBJECT) ? v0 : |
| t0 == TypeCache.instance().VOID || t0 == TypeCache.instance().NULL ? TypeCache.instance().NULL : |
| BOTTOM; |
| } |
| |
| public static boolean lessthan(Object v0, Object v1) |
| { |
| if (v0 instanceof String && v1 instanceof String) |
| { |
| return ((String)v0).compareTo((String)v1) < 0; |
| } |
| else |
| { |
| return doubleValue(v0) < doubleValue(v1); |
| } |
| } |
| |
| public static Type type(Map<Expr,Typeref>types, Expr e) |
| { |
| assert(types.containsKey(e)); |
| return types.get(e).t; |
| } |
| |
| |
| public static int intValue(Object o) |
| { |
| return ((Number)o).intValue(); |
| } |
| |
| public static long uintValue(Object o) |
| { |
| return ((Number)o).longValue() & 0xFFFFFFFFL; |
| } |
| |
| public static double doubleValue(Object o) |
| { |
| return o instanceof Number ? ((Number)o).doubleValue() : Double.NaN; |
| } |
| |
| public static boolean booleanValue(Object o) |
| { |
| if (o instanceof Boolean) |
| return o == TRUE; |
| if (o instanceof String || o instanceof Namespace) |
| return true; |
| if (o == TypeCache.instance().NULL || o == UNDEFINED) |
| return false; |
| return doubleValue(o) != 0; |
| } |
| |
| public static String stringValue(Object v0) |
| { |
| // TODO ES3 compatible double format |
| return String.valueOf(v0); |
| } |
| |
| public static /** |
| * most derived base |
| * @param a - first type |
| * @param b - second type |
| * @return Typeref to the "nearest" common base type. |
| */ |
| Typeref mdb(Typeref a, Typeref b) |
| { |
| // TODO support interfaces |
| assert(a != b && a != null && b != null); |
| |
| // null is compatible with pointer types |
| if (a.t == TypeCache.instance().NULL && isPointer(b.t)) return b; |
| if (b.t == TypeCache.instance().NULL && isPointer(a.t)) return a; |
| |
| Set<Type> bases = new HashSet<Type>(); |
| for (Type t = a.t; t != null; t = t.base) |
| bases.add(t); |
| |
| for (Type t = b.t; t != null; t = t.base) |
| if (bases.contains(t)) |
| return new Typeref(t, a.nullable | b.nullable); |
| |
| return new Typeref(TypeCache.instance().ANY, a.nullable | b.nullable); |
| } |
| |
| /** |
| * find inner scope. returns index of object or -1 if not found. |
| * @param ref |
| * @param scopes |
| * @param types |
| * @return |
| */ |
| public static int findInner(Name ref, Expr[] scopes, Map<Expr,Typeref> types) |
| { |
| for (int i=scopes.length-1; i >= 0; i--) |
| { |
| Type st = type(types,scopes[i]); |
| Binding b = st.find(ref); |
| if (b != null) |
| return i; |
| } |
| return -1; |
| } |
| |
| /** |
| * find outer scope. returns index, or -1 if not found, in which |
| * case caller can look in globals. |
| * |
| * @param ref |
| * @param scopes |
| * @return |
| */ |
| public static int findOuter(Name ref, Typeref[] scopes) |
| { |
| for (int i=scopes.length-1; i >= 1; i--) |
| { |
| Type st = scopes[i].t; |
| Binding b = st.find(ref); |
| if (b != null) |
| return i; |
| } |
| Typeref st = TypeCache.instance().globals.get(ref); |
| if (st != null) |
| return -1; // how to identify which global? |
| if (scopes.length > 0 && scopes[0].t.find(ref) != null) |
| return 0; |
| return -1; // can't find it (caller can search globals, will return null). |
| } |
| |
| } |