| /* |
| * |
| * 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.io.*; |
| |
| // Note: java.util imports must be explicitly called out because |
| // "import java.util.*" conflicts with adobe.abc.Algorithms.Deque |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.BitSet; |
| import java.util.Collection; |
| import java.util.Comparator; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.PriorityQueue; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| import java.util.Vector; |
| |
| |
| import macromedia.asc.embedding.ConfigVar; |
| import macromedia.asc.util.ObjectList; |
| |
| import static macromedia.asc.embedding.avmplus.ActionBlockConstants.*; |
| import static adobe.abc.Algorithms.*; |
| import static adobe.abc.OptimizerConstants.*; |
| import static adobe.abc.TypeAnalysis.*; |
| import static java.lang.Boolean.TRUE; |
| import static java.lang.Boolean.FALSE; |
| |
| /* |
| * TODO |
| * |
| * (done) merge phi nodes |
| * (done) pretty print dags |
| * (done) scope capture |
| * (done) type tracking |
| * (done) constness |
| * (done) null tracking |
| * (done) emitter |
| * (done) convert lvn into dominator based dvn |
| * (done) sort cpool entries by frequency of use |
| * (done) eliminate unused cpool entries |
| * (done) split dag nodes to tree nodes |
| * (done) recalc local_count |
| * (done) recalc max_stack |
| * (done) recalc scope_depth |
| * (done) allow nonempty stack between blocks - stk_sched2 |
| * (done) give every slot a default value even if not from abc (vm-defined defaults) |
| * (done) model exception edges, remove PURE |
| * (done) flesh out branch simplifications in sccp |
| * (done) respect try/catch regions |
| * (done) don't assign local to OP_arg in catch. use arg & xarg |
| * (done) if N handlers specify the same catch block, ensure phi's merge args correctly. |
| * (done) strip unnecessary slot default values on export |
| * (done) preserve metadata |
| * (done) expand getlex to expose redundant findproperty |
| * (done) add null-ness to sccp lattice, remove NOTNULL flag |
| * (done) handle lt/gt correctly for strings |
| * (done) hasnext2 codegen |
| * (done) combine findpropstrict+getproperty into getlex when find's only use is get in same block. |
| * (done) normalize inc/declocal/_i into inc/decrement/_i |
| * |
| * force stkout contents for xarg blocks rather than clearing stack at xarg definition. |
| * don't allow xarg block to be combined with other blocks. |
| * handle OP_equals for all const types |
| * istype optimizations |
| * model external data dependencies, remove EFFECT |
| * treat pushwith & pushscope appropriately in findprop |
| * expand implicit conversions to expose them to optimizations |
| * fold implicit conversions to reduce code size |
| * use locals for activation slots that dont escape (need array ssa?) |
| * algebraic identities |
| * combine sccp values & types into single lattice |
| * add final-ness to Typeref |
| * interprocedural analysis to determine effect of functions |
| * check (prove?) correctness of SKIPTEST |
| * insert kills & coerces to appease AVM verifier |
| * add a no-catch edge to an EXIT block for any block with a PX expr. |
| * if we have catch blocks, any PX should end its block. (FCFG notation is premature) |
| * preds should include all edges, not just normal edges |
| * script loading dependencies. remove EFFECT from findprop/finddef if script is already loaded. |
| * convert chain of ifstricne<int> into switch if ints are dense |
| * write a verifier (easier than testing with actual avm) |
| * merge scripts based on init dependencies. |
| * in scheduler, propagate live & stk across exception edges |
| * get smarter about removing phi(coerce) without losing verifiability |
| * scope stack scheduling |
| * turn add into increment/_i when possible |
| * turn increment/decrement/_i into inc/declocal/_i if in/out assigned to same local in sched. |
| * inline method if target is known and body is smaller than call (eg isNaN, trivial getters) |
| * |
| * TODO algorithms |
| * |
| * partial redundancy elimination |
| * alias analysis |
| * tail merging |
| * loop inversion |
| * remove redundant path expressions |
| * |
| * TODO file format changes |
| * |
| * remove/shorten version number |
| * short branch offsets |
| * optional init methods |
| * every reference is a relative offset (eliminate lookup tables) |
| * remove empty activation traits |
| * optional trait names |
| * OP_add_d |
| * OP_concat |
| */ |
| |
| |
| public class GlobalOptimizer |
| { |
| // default configuration flags |
| final boolean USE_CALLMETHOD = false; |
| final boolean SHOW_CODE = true; |
| final boolean SHOW_DOMINATORS=false; |
| |
| // Configuration flags settable by options |
| boolean OUTPUT_DOT = false; |
| boolean SHOW_DFG = false; |
| boolean STRIP_DEBUG_INFO = true; |
| boolean ALLOW_NATIVE_CTORS = false; |
| boolean PRESERVE_METHOD_NAMES = false; |
| |
| boolean verbose_mode=false; |
| boolean legacy_verifier=false; |
| |
| // TODO: Needs more work to get right -- |
| // reading everything in-line means we're |
| // never really sure if something's an |
| // import or not b/c $GLOBALABC is processed |
| // as a target in its own right during |
| // the tamarin build. |
| // Have to organize the files list ahead |
| // of the read phase, would also help |
| // integration with the ASC entry point. |
| boolean reading_imports = true; |
| |
| /** |
| * The number of input files being linked together. |
| * @warn used to work around some linking bugs. |
| */ |
| int num_linked_files = 0; |
| |
| static TraceManager tm = new TraceManager(); |
| |
| public static void main(String[] args) throws IOException |
| { |
| GlobalOptimizer go = new GlobalOptimizer(); |
| |
| List<InputAbc> a = new ArrayList<InputAbc>(); |
| List<Integer> lengths = new ArrayList<Integer>(); |
| String filename = null; |
| byte[] before = null; |
| // Index of first export in the file list. |
| int first_exported_file = 0; |
| boolean obscure_natives = false; |
| boolean no_c_gen = false; |
| boolean quiet_mode = false; |
| |
| for (int i = 0; i < args.length; ++i) |
| { |
| if(args[i].equals("-obscure_natives")) { |
| obscure_natives = true; |
| continue; |
| } |
| if(args[i].equals("-no_c_gen")) { |
| no_c_gen = true; |
| continue; |
| } |
| if(args[i].equals("-d")) { |
| go.STRIP_DEBUG_INFO=false; |
| continue; |
| } |
| if(args[i].equals("-verbose")) { |
| go.verbose_mode = true; |
| quiet_mode = false; |
| continue; |
| } |
| if ( args[i].equals("-quiet")) |
| { |
| quiet_mode = true; |
| go.verbose_mode = false; |
| continue; |
| } |
| if ( args[i].equals("-legacy_verifier")) |
| { |
| go.legacy_verifier = !go.legacy_verifier; |
| addTraceAttr("legacy_verifier", go.legacy_verifier); |
| go.verboseStatus("legacy_verifier set to " + go.legacy_verifier); |
| continue; |
| } |
| if ( args[i].equals("-allow_native_ctors")) |
| { |
| go.ALLOW_NATIVE_CTORS = true; |
| continue; |
| } |
| if ( args[i].equals("-trace") && i+1 < args.length ) |
| { |
| i++; |
| tm.enable(new PrintWriter(new FileWriter(args[i]))); |
| |
| addTraceAttr("timestamp", new Date()); |
| continue; |
| } |
| if ( args[i].equals("-dot") ) |
| { |
| go.OUTPUT_DOT = true; |
| continue; |
| } |
| if ( args[i].equals("-dfg")) |
| { |
| go.SHOW_DFG = go.OUTPUT_DOT = true; |
| continue; |
| } |
| if ( args[i].equals("-preserve_method_names")) |
| { |
| go.PRESERVE_METHOD_NAMES = true; |
| continue; |
| } |
| if ( args[i].equals("-plugin") && i+1 < args.length) |
| { |
| i++; |
| go.loadPlugin(args[i]); |
| continue; |
| } |
| if ( args[i].startsWith("-") && args[i].contains(":")) |
| { |
| int delim_pos = args[i].indexOf(':'); |
| String plugin_name = args[i].substring(1, delim_pos); |
| String plugin_option = args[i].substring(delim_pos+1); |
| |
| PluginData plugin_data = go.analysis_phase_plugins.get(plugin_name); |
| |
| if ( null == plugin_data ) |
| { |
| // Iterate over the plugins and find a short |
| // name that matches this monster. |
| |
| for ( PluginData search_plugin: go.analysis_phase_plugins.values() ) |
| { |
| if ( search_plugin.plugin.getClass().getSimpleName().equals(plugin_name)) |
| { |
| plugin_data = search_plugin; |
| break; |
| } |
| } |
| } |
| |
| if ( null != plugin_data ) |
| { |
| plugin_data.options.add(plugin_option); |
| } |
| else |
| { |
| throw new IllegalArgumentException("No plugin named " + plugin_name + " is loaded."); |
| } |
| |
| continue; |
| } |
| |
| if(args[i].equals("--")) |
| { |
| go.reading_imports = false; |
| first_exported_file = a.size(); |
| continue; |
| } |
| |
| filename = args[i]; |
| |
| addTraceAttr("filename", filename); |
| |
| InputAbc ia = go.new InputAbc(); |
| lengths.add(ia.readAbc(filename)); |
| a.add(ia); |
| if(obscure_natives) { |
| ia.obscure_natives(); |
| obscure_natives = false; |
| } |
| |
| |
| } |
| |
| if ( 0 == args.length || first_exported_file >= a.size() ) |
| { |
| System.err.println("usage: GlobalOptimizer [-obscure_natives] [-no_c_gen] [-verbose] [-quiet] [imports] -- [exports]"); |
| return; |
| } |
| |
| go.initializePlugins(); |
| |
| // merge exports together |
| // TODO this is not right, it only works for builtin ABCs and only under TT... |
| // we really need to construct a new script (that calls all the scripts in the initScripts list) |
| // and append it to the end as the new initscript. |
| List<Integer> initScripts = new ArrayList<Integer>(); |
| InputAbc first = a.get(first_exported_file); |
| int before_length = lengths.get(first_exported_file); |
| initScripts.add(first.scripts.length - 1); |
| |
| // Combine other exported files. |
| for(int i=first_exported_file+1; i < a.size(); i++) |
| { |
| first.combine(a.get(i)); |
| initScripts.add(first.scripts.length - 1); |
| before_length += lengths.get(i); |
| go.num_linked_files++; |
| } |
| |
| // Optimize the combined ABC file and emit. |
| byte[] after = null; |
| |
| go.optimize(first); |
| after = go.emit(first, filename, initScripts, no_c_gen); |
| |
| |
| |
| |
| // Print summary statistics. |
| if ( ! quiet_mode ) |
| { |
| System.out.println(); |
| System.out.println("Before optimization: "+before_length); |
| System.out.println("After optimization: "+after.length); |
| int delta = before_length - after.length; |
| long percent = Math.round(delta/(double)before_length*100); |
| System.out.println("Difference: "+delta+ " "+percent+"%"); |
| } |
| } |
| |
| static byte[] load(String filename) throws IOException |
| { |
| InputStream in = new FileInputStream(filename); |
| try |
| { |
| byte[] before = new byte[in.available()]; |
| in.read(before); |
| return before; |
| } |
| finally |
| { |
| in.close(); |
| } |
| } |
| |
| class PluginData |
| { |
| OptimizerPlugin plugin; |
| Vector<String> options; |
| |
| PluginData(OptimizerPlugin plugin) |
| { |
| this.plugin = plugin; |
| this.options = new Vector<String>(); |
| } |
| } |
| private Map<String, PluginData> analysis_phase_plugins = new HashMap<String, PluginData>(); |
| |
| private void loadPlugin(String plugin_class_fqn) |
| { |
| try |
| { |
| Class clazz = Class.forName(plugin_class_fqn); |
| |
| OptimizerPlugin plugin = (OptimizerPlugin)clazz.newInstance(); |
| analysis_phase_plugins.put(plugin_class_fqn, new PluginData(plugin)); |
| } |
| catch ( Throwable plugin_trouble) |
| { |
| System.err.println("Unable to initialize plugin " + plugin_class_fqn + " due to: " + plugin_trouble); |
| } |
| } |
| |
| private void initializePlugins() |
| { |
| for ( String plugin_name: analysis_phase_plugins.keySet()) |
| { |
| PluginData p = analysis_phase_plugins.get(plugin_name); |
| p.plugin.initializePlugin(this, p.options); |
| } |
| } |
| |
| private void runPlugins() |
| { |
| } |
| |
| public class InputAbc |
| { |
| String strings[]; |
| int ints[]; |
| long uints[]; |
| double doubles[]; |
| Namespace namespaces[]; |
| Nsset nssets[]; |
| Name names[]; |
| Method methods[]; |
| Metadata metadata[]; |
| Type classes[]; |
| Type scripts[]; |
| boolean containsObject = false; |
| |
| Set<Type> toResolve; |
| List<InputAbc> mergedAbcs = new ArrayList<InputAbc>(); |
| public String src_filename; |
| |
| InputAbc() |
| { |
| mergedAbcs.add(this); |
| } |
| |
| Type lookup(int id) |
| { |
| if (0 == id) |
| return ANY(); |
| else |
| return TypeCache.instance().lookup(names[id], OBJECT()); |
| } |
| |
| int readAbc(String src_file) throws IOException |
| { |
| byte[] file_buffer = load(src_file); |
| readAbc(file_buffer); |
| |
| return file_buffer.length; |
| } |
| |
| void readAbc(byte[] abc) throws IOException |
| { |
| Reader p = new Reader(0, abc); |
| if (p.readU16() != 16 || p.readU16() != 46) |
| throw new RuntimeException("not an abc file"); |
| |
| ints = new int[p.readU30() + 1]; |
| for (int i = 1, n = ints.length - 1; i < n; i++) |
| ints[i] = p.readU30(); |
| |
| uints = new long[p.readU30() + 1]; |
| for (int i = 1, n = uints.length - 1; i < n; i++) |
| uints[i] = 0xffffffffL & p.readU30(); |
| |
| doubles = new double[p.readU30() + 1]; |
| for (int i = 1, n = doubles.length - 1; i < n; i++) |
| doubles[i] = p.readDouble(); |
| |
| strings = new String[p.readU30() + 1]; |
| strings[0] = ""; |
| for (int i = 1, n = strings.length - 1; i < n; i++) |
| { |
| int len = p.readU30(); |
| strings[i] = new String(abc, p.pos, len, "UTF-8"); |
| p.pos += len; |
| } |
| |
| namespaces = new Namespace[p.readU30() + 1]; |
| // TODO this should be AnyNamespace, not PUBLIC |
| namespaces[0] = Name.PUBLIC; |
| for (int i = 1, n = namespaces.length - 1; i < n; i++) |
| namespaces[i] = new Namespace(p.readU8(), strings[p.readU30()]); |
| |
| nssets = new Nsset[p.readU30() + 1]; |
| for (int i = 1, n = nssets.length-1; i < n; i++) |
| { |
| nssets[i] = new Nsset(new Namespace[p.readU30()]); |
| for (int j = 0, m = nssets[i].length; j < m; j++) |
| nssets[i].nsset[j] = namespaces[p.readU30()]; // ns_index |
| } |
| |
| names = new Name[p.readU30() + 1]; |
| for (int i = 1, n = names.length-1; i < n; i++) |
| { |
| names[i] = readName(p); |
| } |
| |
| methods = new Method[p.readU30()]; |
| int[] methodpos = new int[methods.length]; |
| for (int i = 0, n = methods.length; i < n; i++) |
| methods[i] = readMethod(p, methodpos, i); |
| |
| metadata = new Metadata[p.readU30()]; |
| for (int i = 0, n = metadata.length; i < n; i++) |
| metadata[i] = readMetadata(p); |
| |
| toResolve = new HashSet<Type>(); |
| |
| Type[] instances = new Type[p.readU30()]; |
| for (int i = 0, n = instances.length; i < n; i++) |
| instances[i] = readInstance(p); |
| |
| if ( containsObject ) |
| TypeCache.instance().setupBuiltins(); |
| |
| // finish resolving methods using mp we saved earlier |
| for (int i=0, n = methods.length; i < n; i++) |
| resolveSignatureType(new Reader(methodpos[i], p.abc), i, methods[i]); |
| |
| classes = new Type[instances.length]; |
| for (int i = 0, n = classes.length; i < n; i++) |
| classes[i] = readClass(p, instances[i]); |
| |
| scripts = new Type[p.readU30()]; |
| for (int i = 0, n = scripts.length; i < n; i++) |
| scripts[i] = readScript(p, i); |
| |
| for (int i = 0, n = p.readU30(); i < n; i++) |
| readBody(p); |
| |
| // lazy resolve slot type names |
| for (Type t: toResolve) |
| resolveType(t); |
| |
| toResolve = null; |
| } |
| |
| void obscure_natives() |
| { |
| for(Type t: classes) { |
| t.obscure_natives = true; |
| if (t.itype != null) |
| t.itype.obscure_natives = true; |
| } |
| for(Type t: scripts) { |
| t.obscure_natives = true; |
| if (t.itype != null) |
| t.itype.obscure_natives = true; |
| } |
| } |
| |
| void combine(InputAbc abc) |
| { |
| int[] newInts = new int[ints.length + abc.ints.length]; |
| System.arraycopy(ints, 0, newInts, 0, ints.length); |
| System.arraycopy(abc.ints, 0, newInts, ints.length, abc.ints.length); |
| ints = newInts; |
| |
| long[] newUints = new long[uints.length + abc.uints.length]; |
| System.arraycopy(uints, 0, newUints, 0, uints.length); |
| System.arraycopy(abc.uints, 0, newUints, uints.length, abc.uints.length); |
| uints = newUints; |
| |
| double[] newDoubles = new double[doubles.length + abc.doubles.length]; |
| System.arraycopy(doubles, 0, newDoubles, 0, doubles.length); |
| System.arraycopy(abc.doubles, 0, newDoubles, doubles.length, abc.doubles.length); |
| doubles = newDoubles; |
| |
| String[] newStrings = new String[strings.length + abc.strings.length]; |
| System.arraycopy(strings, 0, newStrings, 0, strings.length); |
| System.arraycopy(abc.strings, 0, newStrings, strings.length, abc.strings.length); |
| strings = newStrings; |
| |
| Namespace[] newNamespaces = new Namespace[namespaces.length + abc.namespaces.length]; |
| System.arraycopy(namespaces, 0, newNamespaces, 0, namespaces.length); |
| System.arraycopy(abc.namespaces, 0, newNamespaces, namespaces.length, abc.namespaces.length); |
| namespaces = newNamespaces; |
| |
| Nsset[] newNssets = new Nsset[nssets.length + abc.nssets.length]; |
| System.arraycopy(nssets, 0, newNssets, 0, nssets.length); |
| System.arraycopy(abc.nssets, 0, newNssets, nssets.length, abc.nssets.length); |
| nssets = newNssets; |
| |
| Name[] newNames = new Name[names.length + abc.names.length]; |
| System.arraycopy(names, 0, newNames, 0, names.length); |
| System.arraycopy(abc.names, 0, newNames, names.length, abc.names.length); |
| names = newNames; |
| |
| Method[] newMethods = new Method[methods.length + abc.methods.length]; |
| System.arraycopy(methods, 0, newMethods, 0, methods.length); |
| System.arraycopy(abc.methods, 0, newMethods, methods.length, abc.methods.length); |
| methods = newMethods; |
| |
| // we change the .abc property for Methods but not Bindings, canEarlyBind* |
| // deals with this by checking that Binding.abc is in the Method.abc.mergedAbcs set |
| for(Method m: abc.methods) { |
| m.abc = this; |
| } |
| |
| Metadata[] newMetadata = new Metadata[metadata.length + abc.metadata.length]; |
| System.arraycopy(metadata, 0, newMetadata, 0, metadata.length); |
| System.arraycopy(abc.metadata, 0, newMetadata, metadata.length, abc.metadata.length); |
| metadata = newMetadata; |
| |
| Type[] newClasses = new Type[classes.length + abc.classes.length]; |
| System.arraycopy(classes, 0, newClasses, 0, classes.length); |
| System.arraycopy(abc.classes, 0, newClasses, classes.length, abc.classes.length); |
| classes = newClasses; |
| |
| Type[] newScripts = new Type[scripts.length + abc.scripts.length]; |
| System.arraycopy(scripts, 0, newScripts, 0, scripts.length); |
| System.arraycopy(abc.scripts, 0, newScripts, scripts.length, abc.scripts.length); |
| scripts = newScripts; |
| |
| mergedAbcs.add(abc); |
| } |
| |
| void resolveType(Type t) |
| { |
| if (t.size != 0) |
| return; |
| int size = 0; |
| if (t.base != null) |
| { |
| resolveType(t.base); |
| size = t.base.size; |
| } |
| int hole = -1; |
| for (Binding b: t.defs.values()) |
| { |
| if (isSlot(b)) |
| { |
| if (!isClass(b)) |
| resolveSlotType(b); |
| // NOTE NOTE NOTE NOTE NOTE |
| // NOTE NOTE NOTE NOTE NOTE |
| // NOTE NOTE NOTE NOTE NOTE |
| // NOTE NOTE NOTE NOTE NOTE |
| // |
| // the logic here is replicated in QVM and GlobalOptimizer.java and is assumed by the slot offsets |
| // that it generates for native classes. If you change this here you must also change it there. |
| // |
| // NOTE NOTE NOTE NOTE NOTE |
| // NOTE NOTE NOTE NOTE NOTE |
| // NOTE NOTE NOTE NOTE NOTE |
| // NOTE NOTE NOTE NOTE NOTE |
| // NOTE NOTE NOTE NOTE NOTE |
| // |
| // @todo, this generates slots that are correct for 32-bit but not 64 bit builds |
| // |
| if (b.type.t == NUMBER()) |
| { |
| if (size%8 != 0) |
| { |
| hole = size; |
| size += 4; |
| } |
| b.offset = size; |
| size += 8; |
| } |
| else |
| { |
| if (hole != -1) |
| { |
| b.offset = hole; |
| hole = -1; |
| } |
| else |
| { |
| b.offset = size; |
| size += 4; |
| } |
| } |
| } |
| } |
| if (size > 0) |
| verboseStatus("sizeof "+t+" "+size); |
| t.size = size; |
| } |
| |
| Metadata readMetadata(Reader p) |
| { |
| Metadata md = new Metadata(); |
| md.name = strings[p.readU30()]; |
| Attr[] attrs = md.attrs = new Attr[p.readU30()]; |
| for (int j = 0, n=attrs.length; j < n; j++) |
| attrs[j] = new Attr(strings[p.readU30()]); |
| for (int j = 0, n=attrs.length; j < n; j++) |
| attrs[j].value = strings[p.readU30()]; |
| return md; |
| } |
| |
| void resolveSignatureType(Reader p, int i, Method m) |
| { |
| m.returns = lookup(p.readU30()).ref; // return type |
| |
| // Parameter types must be nullable |
| // to satisfy the AVM verifier. |
| for (int j = 1; j < m.getParams().length; j++) |
| { |
| int idx = p.readU30(); |
| Type ptype = lookup(idx); |
| assert(ptype != null); |
| m.getParams()[j] = ptype.ref.nullable(); |
| } |
| |
| p.readU30(); // debug name |
| p.readU8(); // flags |
| |
| if (m.hasOptional()) |
| { |
| m.optional_count = p.readU30(); |
| |
| m.values = new Object[m.getParams().length]; |
| |
| int first_optional_param = m.getParams().length - m.optional_count; |
| |
| for (int j = first_optional_param; j < m.getParams().length; j++) |
| { |
| m.values[j] = readArgDefault(p); |
| assert(m.values[j] != null); |
| } |
| } |
| } |
| |
| void readBody(Reader p) |
| { |
| Method m = methods[p.readU30()]; |
| |
| addTraceAttr("method", m); |
| m.max_stack = p.readU30(); |
| m.local_count = p.readU30(); |
| m.max_scope = -(p.readU30()-p.readU30()); |
| m.code_len = p.readU30(); |
| Reader pcode = new Reader(p); |
| p.pos += m.code_len; |
| readCode(m, pcode, p); |
| Type act = new Type(); |
| m.activation = act.ref.nonnull(); |
| act.base = ANY(); |
| if (m.getName() != null) |
| act.name = (m.getName().append(" activation")); |
| readTraits(p, act); |
| |
| } |
| |
| |
| Method readMethod(Reader p, int[] methodpos, int i) |
| { |
| Method m = new Method(i, this); |
| int param_count = p.readU30(); |
| m.params = new Typeref[param_count+1]; |
| m.getParams()[0] = ANY().ref; |
| methodpos[i] = p.pos; |
| p.readU30(); // return type |
| for (int j = 1; j <= param_count; j++) |
| p.readU30(); // param type |
| |
| // debugName is specified as a bare index into the string pool. |
| m.debugName = strings[p.readU30()]; |
| m.name = new Name(m.debugName); |
| |
| m.flags = p.readU8(); |
| |
| if (m.hasOptional()) |
| { |
| // Skip past optional parameters; they're re-read |
| // and recorded in resolveSignatureType(). |
| int optional_count = p.readU30(); |
| assert(optional_count > 0); |
| |
| for (int j = 0; j < optional_count; j++) |
| { |
| readArgDefault(p); |
| } |
| } |
| if (m.hasParamNames()) |
| { |
| m.paramNames = new Name[param_count+1]; |
| for (int j = 1; j <= param_count; j++) |
| m.paramNames[j] = new Name(strings[p.readU30()]); |
| } |
| return m; |
| } |
| |
| Name readName(Reader p) |
| { |
| int kind = p.readU8(); |
| switch (kind) |
| { |
| default: |
| throw new RuntimeException("Unknown name kind: "+kind); |
| case CONSTANT_TypeName: |
| { |
| int index = p.readU30(); // Index to the Multiname type, i.e. Vector |
| int count = p.readU30(); // number of type parameter names |
| assert(count == 1); // all we support for now |
| int typeparm = p.readU30(); // Multinames for the type parameters, i.e. String for a Vector.<String> |
| Name mn = this.names[index]; |
| Name type_param = this.names[typeparm]; |
| return new Name(kind, mn.name, mn.nsset, type_param.name); |
| } |
| case CONSTANT_Qname: |
| case CONSTANT_QnameA: |
| return new Name(kind, namespaces[p.readU30()], strings[p.readU30()]); |
| case CONSTANT_Multiname: |
| case CONSTANT_MultinameA: |
| return new Name(kind, strings[p.readU30()], nssets[p.readU30()]); |
| case CONSTANT_RTQname: |
| case CONSTANT_RTQnameA: |
| return new Name(kind, uniqueNs(), strings[p.readU30()]); |
| case CONSTANT_MultinameL: |
| case CONSTANT_MultinameLA: |
| return new Name(kind, unique(), nssets[p.readU30()]); |
| case CONSTANT_RTQnameL: |
| case CONSTANT_RTQnameLA: |
| return new Name(kind); |
| } |
| } |
| |
| void readTraits(Reader p, Type t) |
| { |
| toResolve.add(t); |
| t.defs = new Symtab<Binding>(); |
| |
| // Start counting slots either from |
| // the base type's last-used slot |
| // or from zero if there's no base. |
| int slot_id = (t.base != null)? t.base.slotCount: 0; |
| |
| // Slots used by this type. |
| int count = p.readU30(); |
| |
| for (int i = 0; i < count; i++) |
| { |
| Name name = names[p.readU30()]; |
| Binding b = new Binding(p.readU8(), name, this); |
| Binding old = t.defs.get(name); |
| |
| while (old != null && old.peer != null) |
| old = old.peer; |
| if (old != null) |
| old.peer = b; |
| |
| t.defs.put(name, b); |
| int slot = p.readU30(); // slot_id | disp_id |
| b.id = p.readU30(); // typename | class_id | method_id |
| switch (b.kind()) |
| { |
| case TRAIT_Var: |
| case TRAIT_Const: |
| case TRAIT_Class: |
| if (isClass(b)) |
| { |
| // TODO this is a bit tenuous |
| b.type = classes[b.id].ref.nonnull(); |
| b.value = NULL(); |
| } |
| else |
| { |
| b.value = readSlotDefault(p); |
| b.type = ANY().ref; |
| if (b.value instanceof Namespace) |
| TypeCache.instance().namespaceNames.put((Namespace)b.value, name); |
| } |
| |
| // TODO add vm's logic to accept or ignore compiler-assigned slot_id |
| if (slot == 0) |
| slot = ++slot_id; |
| else if (slot > slot_id) |
| slot_id = slot; |
| b.slot = slot; |
| break; |
| case TRAIT_Method: |
| case TRAIT_Getter: |
| case TRAIT_Setter: |
| b.slot = 0; |
| Method m = b.method = methods[b.id]; |
| m.cx = t; |
| m.getParams()[0] = t.ref.nonnull(); |
| m.kind = isMethod(b) ? "function" : isGetter(b) ? "get" : "set"; |
| m.name = name; |
| // ignore compiler-assigned disp_id's |
| // if (!slot) slot = ++disp_id; |
| // else if (slot > disp_id) disp_id = slot; |
| break; |
| default: |
| System.err.println("illegal trait kind " + b.kind() + " at offset " + p.pos); |
| assert (false); |
| } |
| |
| if (b.hasMetadata()) |
| { |
| b.md = new Metadata[p.readU30()]; |
| for (int j=0, m=b.md.length; j < m; j++) |
| b.md[j] = metadata[p.readU30()]; |
| } |
| } |
| t.slotCount = slot_id; |
| } |
| |
| Object readSlotDefault(Reader p) |
| { |
| int i = p.readU30(); |
| if (i != 0) |
| { |
| int kind = p.readU8(); |
| return defaultValue(kind,i); |
| } |
| return null; |
| } |
| |
| Object readArgDefault(Reader p) |
| { |
| int i = p.readU30(); |
| int kind = p.readU8(); |
| Object v = defaultValue(kind, i); |
| return v; |
| } |
| |
| Object defaultValue(int kind, int i) |
| { |
| switch (kind) |
| { |
| case CONSTANT_False: |
| return FALSE; |
| case CONSTANT_True: |
| return TRUE; |
| case CONSTANT_Null: |
| return NULL(); |
| case CONSTANT_Void: |
| return UNDEFINED; |
| case CONSTANT_Utf8: |
| return strings[i]; |
| case CONSTANT_Integer: |
| return ints[i]; |
| case CONSTANT_UInteger: |
| return uints[i]; |
| case CONSTANT_Double: |
| return doubles[i]; |
| case CONSTANT_Namespace: |
| case CONSTANT_PackageNamespace: |
| case CONSTANT_ProtectedNamespace: |
| case CONSTANT_PackageInternalNs: |
| case CONSTANT_ExplicitNamespace: |
| case CONSTANT_StaticProtectedNs: |
| case CONSTANT_PrivateNamespace: |
| return namespaces[i]; |
| } |
| assert(false); |
| return null; |
| } |
| |
| void resolveSlotType(Binding b) |
| { |
| b.type = lookup(b.id).ref; |
| if (b.value == null) |
| b.value = b.type.t.defaultValue; |
| } |
| |
| Type readScript(Reader p, int i) |
| { |
| Type s = new Type(); |
| assert(OBJECT() != null); |
| s.base = OBJECT(); |
| s.name = new Name("global"+i); |
| Method init = s.init = methods[p.readU30()]; // init_id |
| init.cx = s; |
| Typeref sref = s.ref.nonnull(); |
| init.getParams()[0] = sref; |
| init.name = s.getName(); |
| init.kind = "init"; |
| readTraits(p, s); |
| for (Binding b : s.defs.values()) |
| TypeCache.instance().globals.put(b.getName(), sref); |
| s.setFinal(); |
| return s; |
| } |
| |
| Type readClass(Reader p, Type it) |
| { |
| Type c = new Type(); |
| assert(CLASS() != null); |
| c.base = CLASS(); |
| c.itype = it; |
| c.name = it.getName().append("$"); |
| Method init = c.init = methods[p.readU30()]; // init_id |
| init.cx = c; |
| init.getParams()[0] = c.ref.nonnull(); |
| init.name = c.getName(); |
| init.kind = "init"; |
| readTraits(p, c); |
| c.setFinal(); |
| return c; |
| } |
| |
| Type readInstance(Reader p) |
| { |
| Type t = new Type(); |
| |
| // Qualified name of the type. |
| t.name = names[p.readU30()]; |
| // Base name of the type is in the ABC; |
| // resolve it, and note that the base type |
| // has been used as a base, so it won't be |
| // emitted as *. |
| t.base = lookup(p.readU30()); |
| TypeCache.instance().baseTypes.add(t.base); |
| |
| t.flags = p.readU8(); |
| if (t.hasProtectedNs()) |
| t.protectedNs = namespaces[p.readU30()]; |
| t.interfaces = new Type[p.readU30()]; |
| for (int j = 0, n = t.interfaces.length; j < n; j++) |
| { |
| t.interfaces[j] = lookup(p.readU30()); |
| } |
| t.init = methods[p.readU30()]; // init_id |
| if(t.init.isNative() && !ALLOW_NATIVE_CTORS) |
| throw new RuntimeException("Constructors can't be native: "+t); |
| t.init.cx = t; |
| t.init.getParams()[0] = t.ref.nonnull(); |
| t.init.kind = "init"; |
| t.init.name = t.getName(); |
| readTraits(p, t); |
| TypeCache.instance().namedTypes.put(t.getName(), t); |
| |
| if (t.getName().equals(new Name(Name.PKG_PUBLIC,"Object"))) |
| { |
| containsObject = true; |
| } |
| return t; |
| } |
| |
| void readCode(Method m, Reader p, Reader ptry) |
| { |
| |
| |
| addTraceAttr("Method", m); |
| addTraceAttr("code_start", p.pos); |
| |
| int local_count = m.local_count; |
| int end_pos = p.pos + m.code_len; |
| |
| // initial state of frame |
| |
| Expr[] frame = new Expr[local_count + m.max_scope + m.max_stack]; |
| Map<Integer,Block> blocks = new TreeMap<Integer,Block>(); |
| Map<Block,FrameState> states = new TreeMap<Block,FrameState>(); |
| int scopep = local_count; |
| int sp = local_count+m.max_scope; |
| traceEntry("InitialFrame"); |
| addTraceAttr("length", frame.length); |
| addTraceAttr("sp", sp); |
| addTraceAttr("scopep", scopep); |
| |
| m.entry = new Edge(m,null,0); |
| Block b = createBlock(m, m.entry, states, frame, sp, scopep); |
| Expr e; |
| |
| { |
| int i; |
| |
| for (i=0; i < m.getParams().length; i++) |
| { |
| // argument i |
| b.add(e = frame[i] = new Expr(m, OP_arg, i)); |
| if (i==0) |
| e.ref = new Name("this"); |
| else if (m.paramNames != null) |
| e.ref = m.paramNames[i]; |
| else |
| e.ref = new Name("arg"+i); |
| } |
| |
| if (m.needsArguments() || m.needsRest()) |
| { |
| b.add(e = frame[i] = new Expr(m, OP_arg, i)); |
| e.ref = new Name(m.needsArguments() ? "arguments" : "rest"); |
| i++; |
| } |
| |
| for (; i < local_count; i++) |
| { |
| // remaining locals are undefined, but we use OP_arg |
| // to suppress any codegen. sccp_eval knows what to do. |
| b.add(e = frame[i] = new Expr(m, OP_arg, i)); |
| e.ref = new Name("local"+i); |
| } |
| } |
| |
| BitSet trylabels = new BitSet(); |
| BitSet catchlabels = new BitSet(); |
| |
| // Remember start position of the code reader to compute |
| // relative offsets vs. absolute positions. |
| int code_start = p.pos; |
| int try_start = ptry.pos; // FIXME: use m.handlers-based processing, remove this |
| |
| Handler[] handlers = m.handlers = new Handler[ptry.readU30()]; |
| |
| if ( handlers.length > 0 ) |
| { |
| // TODO arg 0 only needs to be fixed if |
| // this is an instance method. |
| m.fixedLocals.put(frame[0], -1); |
| for (int j = 0, n=handlers.length; j < n; j++) |
| { |
| Handler h = handlers[j] = new Handler(); |
| |
| int from = ptry.readU30(); |
| int to = ptry.readU30(); |
| int target = ptry.readU30(); |
| h.type = lookup(ptry.readU30()).ref.nonnull(); |
| int name_idx = ptry.readU30(); |
| Name name = h.name = names[name_idx]; |
| |
| // The name may be null if the catch variable |
| // did not have a name. |
| // NOTE: Also see the emitCode write logic. |
| if ( name != null ) |
| { |
| Type a = new Type(name,ANY()); |
| h.activation = a.ref.nonnull(); |
| Binding bind = new Binding(TRAIT_Var, name, this); |
| bind.type = h.type; |
| a.defs.put(name, bind); |
| } |
| else |
| { |
| h.activation = ANY().ref.nonnull(); |
| } |
| |
| trylabels.set(from); |
| trylabels.set(to); |
| catchlabels.set(target); |
| } |
| } |
| |
| boolean reachable = true; |
| boolean in_catch_block = false; |
| |
| while (p.pos < end_pos) |
| { |
| int pos = p.pos; |
| int op = p.readU8(); |
| |
| if ( |
| OP_label == op || |
| blocks.containsKey(pos) || |
| trylabels.get(pos-code_start) || |
| catchlabels.get(pos-code_start) |
| ) |
| { |
| if ( catchlabels.get(pos-code_start) ) |
| { |
| in_catch_block = true; |
| } |
| else |
| { |
| in_catch_block = false; |
| } |
| |
| Edge edge = null; |
| |
| if (reachable) |
| { |
| assert(!in_catch_block); |
| |
| Edge succ[] = b.succ(); |
| if (0 == succ.length) |
| { |
| b.add(e = new Expr(m,OP_jump)); |
| e.succ = new Edge[] { edge = new Edge(m,b,0,blocks.get(pos)) }; |
| } |
| else |
| { |
| edge = succ[0]; |
| } |
| traceEntry("Successor"); |
| addTraceAttr("Edge", edge); |
| } |
| |
| // For non-catch blocks, the block's saved stack frame |
| // needs to be updated with the current stack frame -- |
| // this may create that saved stack frame as a |
| // side effect. For a catch block, the stack frame |
| // set up by the catch handler is already present |
| // and is known to be the correct starting state |
| // of the block. |
| if (! in_catch_block) |
| { |
| merge(m,edge, blocks, states, pos, frame, sp, scopep); |
| } |
| |
| b = blocks.get(pos); |
| assert(b != null); |
| FrameState state = states.get(b); |
| assert(state != null); |
| System.arraycopy(state.frame, 0, frame, 0, frame.length); |
| sp = state.sp; |
| scopep = state.scopep; |
| |
| reachable = true; |
| } |
| // now we know what block we're in and pos is where it starts. |
| |
| // Do we need to set up exception handlers? |
| if (handlers.length > 0 && b.xsucc == noedges) |
| { |
| // start of new block, add handlers. |
| |
| |
| addTraceAttr("offset", pos - code_start); |
| |
| List<Edge> xsucc = new ArrayList<Edge>(); |
| ptry.pos = try_start; |
| int n=ptry.readU30(); |
| addTraceAttr("NumHandlers", n); |
| |
| for (int j=0; j < n; j++) |
| { |
| int from = code_start + ptry.readU30(); |
| int to = code_start + ptry.readU30(); |
| int target = code_start + ptry.readU30(); |
| |
| traceEntry("Handler"); |
| addTraceAttr("from", from - code_start); |
| addTraceAttr("to", to - code_start); |
| addTraceAttr("target", target - code_start); |
| |
| if (pos >= from && pos < to) |
| { |
| addTraceAttr("activeHandler"); |
| Edge edge = new Edge(m, b, j, handlers[j]); |
| xmerge(m, edge, blocks, states, target, frame, sp, scopep); |
| xsucc.add(edge); |
| } |
| ptry.readU30(); // type |
| ptry.readU30(); // name |
| } |
| |
| b.xsucc = xsucc.toArray(new Edge[xsucc.size()]); |
| |
| } |
| |
| |
| addTraceAttr("offset", pos-code_start); |
| addTraceAttr("op", op); |
| addTraceAttr("opName", opNames[op]); |
| |
| switch (op) |
| { |
| case OP_label: |
| // already handled above |
| break; |
| |
| case OP_throw: |
| case OP_returnvalue: |
| b.add(e = new Expr(m,op, frame, sp--, 1)); |
| e.succ = noedges; |
| reachable = false; |
| merge(m,null, blocks, states, p.pos, frame, sp, scopep); |
| break; |
| |
| case OP_dxnslate: |
| b.add(new Expr(m,op,frame, sp--, 1)); |
| break; |
| |
| case OP_pushwith: |
| case OP_pushscope: |
| { |
| // includes a null pointer check and moves value to scope stack, so this isn't just a copy |
| b.add(frame[scopep++] = e = new Expr(m, op, frame, sp--, 1)); |
| |
| // The activation record saved for catch blocks' use |
| // is live across regions of code that the optimizer |
| // and verifier don't agree on, so it gets fixed into |
| // a local and "conflicts" with everything. |
| if ( in_catch_block && OP_phi == e.args[0].op ) |
| { |
| Expr activ = e.args[0]; |
| while ( OP_phi == activ.op && activ.args != null && activ.args.length > 0) |
| { |
| activ = activ.args[0]; |
| } |
| |
| if ( OP_newactivation == activ.op) |
| { |
| m.fixedLocals.put(activ, -1); |
| } |
| |
| } |
| break; |
| } |
| |
| case OP_popscope: |
| b.add(e = new Expr(m, op)); |
| e.scopes = new Expr[] { frame[--scopep] }; |
| frame[scopep] = null; |
| break; |
| |
| case OP_nextname: |
| case OP_hasnext: |
| case OP_nextvalue: |
| b.add(e = new Expr(m,op, frame, sp, 2)); |
| sp -= 2; |
| frame[sp++] = e; |
| break; |
| |
| case OP_pushnull: |
| b.add(frame[sp++] = new Expr(m,op,NULL())); |
| break; |
| case OP_pushundefined: |
| b.add(frame[sp++] = new Expr(m,op,UNDEFINED)); |
| break; |
| case OP_pushtrue: |
| b.add(frame[sp++] = new Expr(m,op,TRUE)); |
| break; |
| case OP_pushfalse: |
| b.add(frame[sp++] = new Expr(m,op,FALSE)); |
| break; |
| case OP_pushnan: |
| b.add(frame[sp++] = new Expr(m,op,Double.NaN)); |
| break; |
| |
| case OP_newactivation: |
| b.add(frame[sp++] = new Expr(m,op)); |
| break; |
| |
| case OP_getglobalscope: |
| b.add(e = frame[sp++] = new Expr(m, op)); |
| e.scopes = capture(frame, scopep, scopep-local_count); |
| traceEntry("scopep", scopep); |
| break; |
| |
| case OP_getscopeobject: |
| b.add(e = frame[sp++] = new Expr(m, op)); |
| e.scopes = capture(frame, local_count+p.readU8()+1, 1); |
| break; |
| |
| case OP_pop: |
| sp--; |
| break; |
| |
| case OP_dup: |
| frame[sp] = frame[sp-1]; |
| sp++; |
| break; |
| |
| case OP_swap: |
| { |
| e = frame[sp-1]; |
| frame[sp-1] = frame[sp-2]; |
| frame[sp-2] = e; |
| break; |
| } |
| |
| case OP_returnvoid: |
| b.add(e = new Expr(m,op)); |
| e.succ = noedges; |
| reachable = false; |
| merge(m,null, blocks, states, p.pos, frame, sp, scopep); |
| break; |
| |
| case OP_convert_s: |
| case OP_esc_xelem: |
| case OP_esc_xattr: |
| case OP_convert_i: |
| case OP_convert_u: |
| case OP_convert_d: |
| case OP_coerce_s: |
| case OP_negate: |
| case OP_increment: |
| case OP_decrement: |
| case OP_not: |
| case OP_bitnot: |
| case OP_increment_i: |
| case OP_decrement_i: |
| case OP_negate_i: |
| case OP_coerce_o: |
| case OP_convert_o: |
| case OP_typeof: |
| case OP_convert_b: |
| case OP_coerce_a: |
| // unary w/ possible side effect |
| b.add(frame[sp-1] = new Expr(m,op, frame, sp, 1)); |
| break; |
| |
| case OP_checkfilter: |
| // possible exception |
| b.add(new Expr(m,op, frame, sp, 1)); |
| break; |
| |
| case OP_astypelate: |
| case OP_add: |
| case OP_subtract: |
| case OP_multiply: |
| case OP_divide: |
| case OP_modulo: |
| case OP_lshift: |
| case OP_rshift: |
| case OP_urshift: |
| case OP_bitand: |
| case OP_bitor: |
| case OP_bitxor: |
| case OP_equals: |
| case OP_lessthan: |
| case OP_lessequals: |
| case OP_greaterthan: |
| case OP_greaterequals: |
| case OP_instanceof: |
| case OP_in: |
| case OP_add_i: |
| case OP_subtract_i: |
| case OP_multiply_i: |
| case OP_istypelate: |
| case OP_strictequals: |
| b.add(frame[sp-2] = new Expr(m,op, frame, sp, 2)); |
| sp--; |
| break; |
| |
| case OP_getlocal0: |
| case OP_getlocal1: |
| case OP_getlocal2: |
| case OP_getlocal3: |
| frame[sp++] = frame[op-OP_getlocal0]; |
| break; |
| |
| case OP_setlocal0: |
| case OP_setlocal1: |
| case OP_setlocal2: |
| case OP_setlocal3: |
| frame[op-OP_setlocal0] = frame[--sp]; |
| break; |
| |
| case OP_kill: |
| b.add(frame[p.readU30()] = new Expr(m,OP_pushundefined,UNDEFINED)); |
| break; |
| |
| case OP_pushshort: |
| b.add(frame[sp++] = new Expr(m,op, new Integer((short)p.readU30()))); |
| break; |
| case OP_pushstring: |
| b.add(frame[sp++] = new Expr(m,op, strings[p.readU30()])); |
| break; |
| case OP_pushint: |
| b.add(frame[sp++] = new Expr(m,op, new Integer(ints[p.readU30()]))); |
| break; |
| case OP_pushuint: |
| b.add(frame[sp++] = new Expr(m,op, new Long(uints[p.readU30()]))); |
| break; |
| case OP_pushdouble: |
| b.add(frame[sp++] = new Expr(m,op, new Double(doubles[p.readU30()]))); |
| break; |
| case OP_pushnamespace: |
| b.add(frame[sp++] = new Expr(m,op, namespaces[p.readU30()])); |
| break; |
| |
| case OP_getlocal: |
| frame[sp++] = frame[p.readU30()]; |
| break; |
| |
| case OP_setlocal: |
| frame[p.readU30()] = frame[--sp]; |
| break; |
| |
| case OP_coerce: |
| case OP_astype: |
| case OP_istype: |
| { |
| b.add(e = frame[sp-1] = new Expr(m, op, names[p.readU30()], frame, sp, 1)); |
| break; |
| } |
| |
| case OP_dxns: |
| b.add(new Expr(m,op, strings[p.readU30()])); |
| break; |
| |
| case OP_inclocal_i: |
| case OP_declocal_i: |
| case OP_inclocal: |
| case OP_declocal: |
| { |
| // turn these into increment/decrement, forget which local was used. |
| int i = p.readU30(); |
| op = op == OP_inclocal_i ? OP_increment_i : |
| op == OP_inclocal ? OP_increment : |
| op == OP_declocal_i ? OP_decrement_i : |
| OP_decrement; |
| b.add(e = frame[i] = new Expr(m, op, i, frame, i+1, 1)); |
| break; |
| } |
| |
| case OP_newfunction: |
| { |
| e = new Expr(m,op); |
| e.m = methods[p.readU30()]; |
| e.scopes = capture(frame, scopep, scopep-local_count); |
| traceEntry("scopep", scopep); |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_newclass: |
| { |
| e = new Expr(m, op, frame, sp, 1); |
| e.scopes = capture(frame, scopep, scopep-local_count); |
| traceEntry("scopep", scopep); |
| e.c = classes[p.readU30()]; |
| b.add(frame[sp-1] = e); |
| break; |
| } |
| |
| case OP_newobject: |
| { |
| int argc = 2*p.readU30(); |
| e = new Expr(m,op, frame, sp, argc); |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_newarray: |
| { |
| int argc = p.readU30(); |
| e = new Expr(m,op, frame, sp, argc); |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_newcatch: |
| { |
| e = new Expr(m, op, p.readU30()); |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_getsuper: |
| { |
| Name ref = names[p.readU30()]; |
| int argc = 1 + refArgc[ref.kind]; |
| e = new Expr(m,op, ref, frame, sp, argc); |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_setsuper: |
| { |
| Name ref = names[p.readU30()]; |
| int argc = 2 + refArgc[ref.kind]; |
| b.add(e = new Expr(m,op, ref, frame, sp, argc)); |
| sp -= argc; |
| break; |
| } |
| |
| case OP_call: |
| { |
| int argc = 2+p.readU30(); |
| e = new Expr(m, op, frame, sp, argc); |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_construct: |
| { |
| int argc = 1+p.readU30(); |
| e = new Expr(m,op, frame, sp, argc); |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_getdescendants: |
| case OP_deldescendants: |
| case OP_deleteproperty: |
| case OP_getproperty: |
| { |
| Name ref = names[p.readU30()]; |
| int argc = 1 + refArgc[ref.kind]; |
| e = new Expr(m, op, ref, frame, sp, argc); |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_getlex: |
| { |
| Name ref = names[p.readU30()]; |
| assert(refArgc[ref.kind] == 0); |
| e = new Expr(m, OP_findpropstrict, ref, frame, sp, 0); |
| e.scopes = capture(frame, scopep, scopep-local_count); |
| b.add(e); |
| e = new Expr(m, OP_getproperty, ref, new Expr[] { e }, 1, 1); |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_findpropstrict: |
| case OP_findproperty: |
| { |
| Name ref = names[p.readU30()]; |
| int argc = refArgc[ref.kind]; |
| e = new Expr(m,op, ref, frame, sp, argc); |
| e.scopes = capture(frame, scopep, scopep-local_count); |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_finddef: |
| { |
| Name ref = names[p.readU30()]; |
| int argc = refArgc[ref.kind]; |
| e = new Expr(m,op, ref, frame, sp, argc); |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_setproperty: |
| case OP_initproperty: |
| { |
| Name ref = names[p.readU30()]; |
| int argc = 2 + refArgc[ref.kind]; |
| e = new Expr(m,op, ref, frame, sp, argc); |
| sp -= argc; |
| b.add(e); |
| break; |
| } |
| |
| case OP_getslot: |
| { |
| e = new Expr(m,op, p.readU30(), frame, sp, 1); |
| b.add(e); |
| frame[sp-1] = e; |
| break; |
| } |
| |
| case OP_setslot: |
| { |
| e = new Expr(m,op, p.readU30(), frame, sp, 2); |
| b.add(e); |
| sp -= 2; |
| break; |
| } |
| |
| case OP_jump: |
| { |
| int offset = p.readS24(); |
| if (reachable) |
| { |
| b.add(e = new Expr(m,op)); |
| e.succ = new Edge[] { new Edge(m,b, 0, blocks.get(p.pos+offset)) }; |
| merge(m, null, blocks, states, p.pos, frame, sp, scopep); |
| merge(m,e.succ[0], blocks, states, p.pos+offset, frame, sp, scopep); |
| reachable = false; |
| } |
| break; |
| } |
| |
| case OP_iftrue: |
| case OP_iffalse: |
| { |
| b.add(e = new Expr(m, op, frame, sp--, 1)); |
| int offset = p.readS24(); |
| e.succ = new Edge[] { |
| new Edge(m,b,0,blocks.get(p.pos)), |
| new Edge(m,b,1,blocks.get(p.pos+offset)) }; |
| merge(m,null, blocks, states, p.pos, frame, sp, scopep); |
| merge(m,e.succ[1], blocks, states, p.pos+offset, frame, sp, scopep); |
| break; |
| } |
| |
| case OP_ifnlt: |
| case OP_ifnle: |
| case OP_ifngt: |
| case OP_ifnge: |
| case OP_ifne: |
| case OP_ifstrictne: |
| { |
| b.add(e = new Expr(m, ifoper(op), frame, sp, 2)); |
| traceEntry("ConditionalBranch"); |
| addTraceAttr(e); |
| b.add(e = new Expr(m, OP_iffalse, new Expr[] { e }, 1, 1)); |
| int offset = p.readS24(); |
| sp-=2; |
| e.succ = new Edge[] { |
| new Edge(m,b,0,blocks.get(p.pos)), |
| new Edge(m,b,1,blocks.get(p.pos+offset)) }; |
| merge(m,null, blocks, states, p.pos, frame, sp, scopep); |
| merge(m,e.succ[1], blocks, states, p.pos+offset, frame, sp, scopep); |
| break; |
| } |
| |
| case OP_ifeq: |
| case OP_iflt: |
| case OP_ifle: |
| case OP_ifgt: |
| case OP_ifge: |
| case OP_ifstricteq: |
| { |
| b.add(e = new Expr(m, ifoper(op), frame, sp, 2)); |
| traceEntry("ConditionalBranch"); |
| addTraceAttr(e); |
| b.add(e = new Expr(m, OP_iftrue, new Expr[] { e }, 1, 1)); |
| int offset = p.readS24(); |
| sp-=2; |
| e.succ = new Edge[] { |
| new Edge(m,b,0,blocks.get(p.pos)), |
| new Edge(m,b,1,blocks.get(p.pos+offset)) }; |
| merge(m,null, blocks, states, p.pos, frame, sp, scopep); |
| merge(m,e.succ[1], blocks, states, p.pos+offset, frame, sp, scopep); |
| break; |
| } |
| |
| case OP_lookupswitch: |
| { |
| b.add(e = new Expr(m,op, frame,sp--,1)); |
| // Default target |
| int target = pos+p.readS24(); |
| // "There are case_count+1 case offsets." |
| int case_count = 1+p.readU30(); |
| e.succ = new Edge[case_count+1]; |
| // Default case goes last. |
| e.succ[case_count] = new Edge(m,b, case_count, blocks.get(target)); |
| merge(m,e.succ[case_count], blocks, states, target, frame, sp, scopep); |
| for (int i=0; i < case_count; i++) |
| { |
| target = pos+p.readS24(); |
| e.succ[i] = new Edge(m,b, i, blocks.get(target)); |
| merge(m,e.succ[i], blocks, states, target, frame, sp, scopep); |
| } |
| |
| // no fall-through for switch |
| reachable = false; |
| break; |
| } |
| |
| case OP_pushbyte: |
| { |
| e = frame[sp++] = new Expr(m,op, new Integer((byte)p.readU8())); |
| b.add(e); |
| break; |
| } |
| |
| case OP_hasnext2: |
| { |
| |
| int oloc = p.readU30(); |
| int iloc = p.readU30(); |
| Expr index = frame[iloc]; |
| Expr obj = frame[oloc]; |
| addTraceAttr("index", index); |
| addTraceAttr("obj", obj); |
| b.add(e = frame[sp] = new Expr(m,op)); |
| e.locals = new Expr[] { obj, index }; |
| b.add(e = frame[oloc] = new Expr(m, OP_hasnext2_o)); |
| e.locals = new Expr[] { obj }; |
| b.add(e = frame[iloc] = new Expr(m, OP_hasnext2_i)); |
| e.locals = new Expr[] { index }; |
| sp++; |
| b.must_isolate_block = true; |
| |
| break; |
| } |
| |
| case OP_callmethod: |
| { |
| int id = p.readU30(); |
| int argc = 1+p.readU30(); |
| e = new Expr(m,op, id, frame, sp, argc); |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_callstatic: |
| { |
| int id = p.readU30(); |
| int argc = 1+p.readU30(); |
| e = new Expr(m,op, frame, sp, argc); |
| e.m = methods[id]; |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_applytype: |
| { |
| int argc = 1+p.readU30(); |
| e = new Expr(m,op, frame, sp, argc); |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_constructsuper: |
| { |
| int argc = 1+p.readU30(); |
| e = new Expr(m,op, frame, sp, argc); |
| sp -= argc; |
| b.add(e); |
| break; |
| } |
| |
| case OP_callsuper: |
| case OP_callproperty: |
| case OP_constructprop: |
| case OP_callproplex: |
| { |
| Name ref = names[p.readU30()]; |
| int argc = 1 + p.readU30() + refArgc[ref.kind]; |
| e = new Expr(m,op, ref, frame, sp, argc); |
| sp -= argc; |
| b.add(frame[sp++] = e); |
| break; |
| } |
| |
| case OP_callsupervoid: |
| case OP_callpropvoid: |
| { |
| Name ref = names[p.readU30()]; |
| int argc = 1 + p.readU30() + refArgc[ref.kind]; |
| e = new Expr(m,op, ref, frame, sp, argc); |
| sp -= argc; |
| b.add(e); |
| break; |
| } |
| |
| case OP_debugfile: |
| { |
| int ii = p.readU30(); |
| if (!STRIP_DEBUG_INFO) |
| b.add(e = new Expr(m,op, strings[ii])); |
| addTraceAttr("file", strings[ii]); |
| break; |
| } |
| |
| case OP_debugline: |
| case OP_bkptline: |
| { |
| int ii = p.readU30(); |
| if (!STRIP_DEBUG_INFO) |
| b.add(e = new Expr(m,op, ii)); |
| addTraceAttr("line", ii); |
| break; |
| } |
| case OP_debug: |
| { |
| int i1 = p.readU8(); |
| int i2 = p.readU30(); |
| int i3 = p.readU8(); |
| int i4 = p.readU30(); |
| if (!STRIP_DEBUG_INFO) |
| { |
| e = new Expr(m,op); |
| e.imm = new int[] { i1, i2, i3, i4 }; |
| b.add(e); |
| } |
| break; |
| } |
| |
| case OP_bkpt: |
| if (!STRIP_DEBUG_INFO) |
| { |
| // no args but position matters |
| b.add(new Expr(m,op)); |
| } |
| break; |
| |
| case OP_timestamp: |
| // no args but position matters |
| b.add(new Expr(m,op)); |
| break; |
| |
| case OP_nop: |
| break; |
| |
| default: |
| System.err.println("Unknown ABC bytecode "+op); |
| assert (false); |
| } |
| |
| |
| } |
| |
| dce(m); |
| |
| |
| } |
| |
| public int nameIndex(Name n) |
| { |
| int idx = 0; |
| for (Name x: names) |
| { |
| if ( x != null && ( x == n || x.equals(n) ) ) |
| return idx; |
| idx++; |
| } |
| |
| return -1; |
| } |
| } |
| |
| |
| static int rtcounter; |
| static String unique() |
| { |
| return unique("[]"); |
| } |
| static String unique(String prefix) |
| { |
| return prefix+(rtcounter++); |
| } |
| static Namespace uniqueNs() |
| { |
| return new Namespace(unique("ns")); |
| } |
| |
| Type OBJECT() { return TypeCache.instance().OBJECT;} |
| Type FUNCTION() { return TypeCache.instance().FUNCTION;} |
| Type CLASS() { return TypeCache.instance().CLASS;} |
| Type ARRAY() { return TypeCache.instance().ARRAY;} |
| Type INT() { return TypeCache.instance().INT;} |
| Type UINT() { return TypeCache.instance().UINT;} |
| Type NUMBER() { return TypeCache.instance().NUMBER;} |
| Type BOOLEAN() { return TypeCache.instance().BOOLEAN;} |
| Type STRING() { return TypeCache.instance().STRING;} |
| Type NAMESPACE() { return TypeCache.instance().NAMESPACE;} |
| Type XML() { return TypeCache.instance().XML;} |
| Type XMLLIST() { return TypeCache.instance().XMLLIST;} |
| Type QNAME() { return TypeCache.instance().QNAME;} |
| Type NULL() { return TypeCache.instance().NULL;} |
| Type VOID() { return TypeCache.instance().VOID;} |
| Type ANY() { return TypeCache.instance().ANY();} |
| |
| |
| static class Metadata implements Comparable |
| { |
| String name; |
| Attr[] attrs; |
| |
| public int compareTo(Object o) |
| { |
| Metadata md = (Metadata)o; |
| if (this == md) return 0; |
| int d = md.name.compareTo(name); |
| if (d != 0) return d; |
| if ((d = attrs.length - md.attrs.length) != 0) return d; |
| for (int i=0, n=attrs.length; i < n; i++) |
| if ((d = attrs[i].compareTo(md.attrs[i])) != 0) |
| return d; |
| return 0; |
| } |
| } |
| |
| static class Attr implements Comparable |
| { |
| String name; |
| String value; |
| Attr(String name) |
| { |
| this.name = name; |
| } |
| |
| public int compareTo(Object o) |
| { |
| Attr a = (Attr)o; |
| if (this == a) return 0; |
| int d = name.compareTo(a.name); |
| if (d != 0) return d; |
| if ((d = value.compareTo(a.value)) != 0) return d; |
| return 0; |
| } |
| } |
| |
| /** |
| * Methods ready for optimization. |
| * Populated by readyMethod(), called from readyType() |
| * and by initial processing of the OP_newfunction Expr. |
| */ |
| List<Method> ready = new ArrayList<Method>(); |
| |
| /** |
| * Track already-processed methods so they never go on the ready list twice. |
| * @warn Methods are tracked by hashCode() b/c Method defines a compare function |
| * that can compare different Methods as equal (if they're in different types). |
| * If Method.hashCode() is modified, then Method will need a unique ID to |
| * make this set work. |
| */ |
| Set<Method> already_processed = new HashSet<Method>(); |
| |
| |
| void readyType(Type t) |
| { |
| readyMethod(t.init); |
| |
| for (Binding b1: t.defs.values()) |
| if (b1.method != null) |
| readyMethod(b1.method); |
| } |
| |
| void readyMethod(Method m) |
| { |
| traceEntry("readyMethod"); |
| addTraceAttr(m); |
| if (m.entry != null && ! already_processed.contains(m) ) |
| { |
| addTraceAttr("process", true); |
| ready.add(m); |
| already_processed.add(m); |
| } |
| } |
| |
| void optimize(InputAbc a) |
| { |
| |
| for ( PluginData plugin: this.analysis_phase_plugins.values()) |
| { |
| CallGraph cg = new CallGraph(a); |
| plugin.plugin.runPlugin(a.src_filename, cg); |
| } |
| |
| for (Type t: a.scripts) |
| readyType(t); |
| |
| while (!ready.isEmpty()) |
| optimize(getMethod(ready)); |
| } |
| |
| /** |
| * organize the pools so the most commonly referenced elements |
| * have the lowest indexes. |
| * |
| * TODO - remove dup namespaces from nssets & remove dup nssets |
| */ |
| class Abc |
| { |
| Pool<Integer> intPool = new Pool<Integer>(1); |
| Pool<Long> uintPool = new Pool<Long>(1); |
| Pool<Double> doublePool = new Pool<Double>(1); |
| Pool<String> stringPool = new Pool<String>(1); |
| Pool<Namespace> nsPool = new Pool<Namespace>(1); |
| Pool<Nsset> nssetPool = new Pool<Nsset>(1); |
| Pool<Name> namePool = new Pool<Name>(1); |
| Pool<Method> methodPool1 = new Pool<Method>(0); |
| Pool<Method> methodPool2 = new Pool<Method>(0); |
| Pool<Metadata> metaPool = new Pool<Metadata>(0); |
| int bodyCount; |
| boolean haveNatives; |
| |
| List<Type> scripts = new ArrayList<Type>(); |
| List<Type> classes = new ArrayList<Type>(); |
| |
| int typeRef(Typeref tref) |
| { |
| return typeRef(tref.t); |
| } |
| |
| int typeRef(Type t) |
| { |
| if (t == ANY()) |
| return 0; |
| else if(t.emitAsAny()) { |
| verboseStatus("Emitting: " + t + " as any"); |
| return 0; |
| } else |
| return namePool.id(t.getName()); |
| } |
| |
| Pool<Method> poolFor(Method m) |
| { |
| // put abc methods first, native methods second. |
| return m.isNative() ? methodPool2 : methodPool1; |
| } |
| |
| int methodId(Method m) |
| { |
| return poolFor(m).id(m); |
| } |
| |
| void addScript(Type s) |
| { |
| // global object type |
| addMethod(s.init); |
| addTraits(s.defs); |
| scripts.add(s); |
| } |
| |
| void addClass(Type c) |
| { |
| // instance type |
| Type t = c.itype; |
| addName(t.getName()); |
| if (t.base != NULL()) |
| addTypeRef(t.base); |
| if (t.hasProtectedNs()) |
| addNamespace(t.protectedNs); |
| for (Type i: t.interfaces) |
| addInterfaceRef(i); |
| addMethod(t.init); |
| addTraits(t.defs); |
| |
| // class type |
| addMethod(c.init); |
| addTraits(c.defs); |
| classes.add(c); |
| } |
| |
| int classId(Type c) |
| { |
| return classes.indexOf(c); |
| } |
| |
| int scriptId(Type c) |
| { |
| return scripts.indexOf(c); |
| } |
| |
| void addTraits(Symtab<Binding> defs) |
| { |
| for (Binding b: defs.values()) |
| { |
| addName(b.getName()); |
| switch (b.kind()) |
| { |
| case TRAIT_Class: |
| addClass(b.type.t); |
| break; |
| case TRAIT_Var: |
| case TRAIT_Const: |
| addTypeRef(b.type); |
| if (b.value != null) |
| { |
| addConst(b.value); |
| } |
| break; |
| case TRAIT_Method: |
| case TRAIT_Getter: |
| case TRAIT_Setter: |
| addMethod(b.method); |
| break; |
| } |
| |
| /*if (b.hasMetadata()) |
| for (Metadata md : b.md) |
| addMetadata(md);*/ |
| } |
| } |
| |
| void addMetadata(Metadata md) |
| { |
| if (metaPool.add(md) == 1) |
| { |
| stringPool.add(md.name); |
| for (Attr a: md.attrs) |
| { |
| stringPool.add(a.name); |
| stringPool.add(a.value); |
| } |
| } |
| } |
| |
| void addConst(Object value) |
| { |
| if (value instanceof Integer) |
| intPool.add(intValue(value)); |
| else if (value instanceof Long) |
| uintPool.add(uintValue(value)); |
| else if (value instanceof Double) |
| doublePool.add(doubleValue(value)); |
| else if (value instanceof String) |
| stringPool.add((String)value); |
| else if (value instanceof Namespace) |
| addNamespace((Namespace) value); |
| } |
| |
| int constId(int kind, Object value) |
| { |
| switch (kind) |
| { |
| case CONSTANT_Integer: |
| return intPool.id(intValue(value)); |
| case CONSTANT_UInteger: |
| return uintPool.id(uintValue(value)); |
| case CONSTANT_Utf8: |
| return stringPool.id((String)value); |
| case CONSTANT_Double: |
| return doublePool.id(doubleValue(value)); |
| case CONSTANT_Namespace: |
| return nsPool.id((Namespace)value); |
| case CONSTANT_True: |
| return CONSTANT_True; |
| case CONSTANT_False: |
| return CONSTANT_False; |
| case CONSTANT_Null: |
| return CONSTANT_Null; |
| } |
| return 0; |
| } |
| |
| int constKind(Object value) |
| { |
| if (value instanceof Integer) |
| return CONSTANT_Integer; |
| if (value instanceof Long) |
| return CONSTANT_UInteger; |
| if (value instanceof Double) |
| return CONSTANT_Double; |
| if (value instanceof String) |
| return CONSTANT_Utf8; |
| if (value instanceof Namespace) |
| return ((Namespace)value).kind; |
| if (value == TRUE) |
| return CONSTANT_True; |
| if (value == FALSE) |
| return CONSTANT_False; |
| if (value == UNDEFINED) |
| return CONSTANT_Void; |
| if (value == NULL()) |
| return CONSTANT_Null; |
| return 0; |
| } |
| |
| void addNamespace(Namespace ns) |
| { |
| if (nsPool.add(ns) == 1) |
| { |
| if (!ns.isPrivateOrInternal()) |
| stringPool.add(ns.uri); |
| } |
| } |
| |
| void addNsset(Nsset nsset) |
| { |
| if (nssetPool.add(nsset) == 1) |
| for (Namespace ns: nsset) |
| addNamespace(ns); |
| } |
| |
| void addName(Name n) |
| { |
| if (namePool.add(n) == 1) |
| switch (n.kind) |
| { |
| case CONSTANT_Multiname: |
| case CONSTANT_MultinameA: |
| addNsset(n.nsset); |
| stringPool.add(n.name); |
| break; |
| case CONSTANT_Qname: |
| case CONSTANT_QnameA: |
| addNamespace(n.nsset(0)); |
| stringPool.add(n.name); |
| break; |
| case CONSTANT_RTQname: |
| case CONSTANT_RTQnameA: |
| stringPool.add(n.name); |
| break; |
| case CONSTANT_MultinameL: |
| case CONSTANT_MultinameLA: |
| addNsset(n.nsset); |
| break; |
| } |
| } |
| |
| void addTypeRef(Typeref tref) |
| { |
| addTypeRef(tref.t); |
| } |
| |
| void addTypeRef(Type t) |
| { |
| if (t != ANY() && !t.emitAsAny()) |
| addName(t.getName()); |
| } |
| |
| void addInterfaceRef(Type t) |
| { |
| addName(t.getName()); |
| } |
| |
| void addMethod(Method m) |
| { |
| if (poolFor(m).add(m) > 1) |
| return; |
| |
| if (m.entry != null) |
| bodyCount++; |
| |
| addTypeRef(m.returns.t); |
| for (int i=1, n=m.getParams().length; i < n; i++) |
| addTypeRef(m.getParams()[i]); |
| |
| if (m.hasOptional()) |
| for (Object v: m.values) |
| if (v != null) |
| addConst(v); |
| |
| if (!STRIP_DEBUG_INFO) |
| { |
| if (m.hasParamNames()) |
| for (int i=1; i < m.paramNames.length; i++) |
| addName(m.paramNames[i]); |
| } |
| |
| if ( PRESERVE_METHOD_NAMES ) |
| { |
| addConst(m.debugName); |
| } |
| |
| for (Handler t: m.handlers) |
| { |
| if ( t.name != null ) |
| addName(t.name); |
| addTypeRef(t.type); |
| } |
| |
| // native and interface methods don't have a body |
| haveNatives |= m.isNative(); |
| if (m.entry == null) |
| return; |
| |
| for (Block b: dfs(m.entry.to)) |
| { |
| for (Expr e: b) |
| { |
| switch (e.op) |
| { |
| case OP_debugfile: |
| if (!STRIP_DEBUG_INFO) |
| addConst(e.value); |
| break; |
| case OP_pushint: |
| case OP_pushuint: |
| case OP_pushstring: |
| case OP_pushnamespace: |
| case OP_pushdouble: |
| case OP_dxns: |
| addConst(e.value); |
| break; |
| case OP_istype: |
| case OP_astype: |
| case OP_findproperty: |
| case OP_findpropstrict: |
| case OP_callproperty: |
| case OP_callproplex: |
| case OP_callsuper: |
| case OP_callsupervoid: |
| case OP_constructprop: |
| case OP_getproperty: |
| case OP_setproperty: |
| case OP_deleteproperty: |
| case OP_getdescendants: |
| case OP_coerce: |
| case OP_getlex: |
| case OP_finddef: |
| case OP_callpropvoid: |
| case OP_initproperty: |
| addName(e.ref); |
| break; |
| case OP_newfunction: |
| case OP_callstatic: |
| addMethod(e.m); |
| break; |
| } |
| } |
| } |
| addTraits(m.activation.t.defs); |
| } |
| |
| void sort() |
| { |
| verboseStatus("NAMES RANK " + namePool.refs); |
| intPool.sort(); |
| uintPool.sort(); |
| doublePool.sort(); |
| stringPool.sort(); |
| nsPool.sort(); |
| nssetPool.sort(); |
| namePool.sort(); |
| metaPool.sort(); |
| methodPool1.sort(); |
| methodPool2.countFrom = methodPool1.size(); |
| methodPool2.sort(); |
| verboseStatus("NAMES " + namePool.values); |
| |
| // topological sort of the classes, base classes come first |
| if ( legacy_verifier ) |
| { |
| if ( num_linked_files > 1 ) |
| { |
| classes = new Algorithms.TopologicalSort<Type>().toplogicalSort( |
| classes, |
| new Algorithms.TopologicalSort.DependencyChecker<Type>() |
| { |
| public boolean depends(Type dep, Type parent) |
| { |
| return (dep.itype.isDerivedFrom(parent.itype)); |
| } |
| } |
| ); |
| } |
| } |
| else |
| { |
| // HACK ALERT: This sort is incorrect, but the topological |
| // sort causes tamarin-tracing builds to fail in shell.abc. |
| TreeSet<Type> cs = new TreeSet<Type>(new Comparator<Type>() |
| { |
| public int compare(Type a, Type b) |
| { |
| if (a == b) return 0; |
| // no, this is subtly wrong: we might be comparing two classes that have no parent-child relationship |
| // (eg SyntaxError and EvalError). this logic will push UP subclasses and push DOWN no-rel-classes and superclasses. |
| // it's more reliable to push DOWN superclasses and UP subclasses and no-rel-classes. (we can't just return 0 |
| // for no-rel because TreeSet will assume they are identical and eliminate one...) |
| // else if (istype(a.itype, b.itype)) return 1; |
| // else return -1; |
| else if (b.itype.extendsOrIsBase(a.itype)) return -1; |
| else return 1; |
| } |
| }); |
| cs.addAll(classes); |
| classes.clear(); |
| classes.addAll(cs); |
| } |
| } |
| |
| public int interfaceRef(Type t) |
| { |
| return namePool.id(t.getName()); |
| } |
| } |
| |
| int argc(Expr e) |
| { |
| switch (e.op) |
| { |
| case OP_callproperty: |
| case OP_callproplex: |
| case OP_callpropvoid: |
| case OP_callsuper: |
| case OP_callsupervoid: |
| case OP_constructprop: |
| return e.args.length - refArgc[e.ref.kind] - 1; |
| case OP_applytype: |
| case OP_callstatic: |
| case OP_callmethod: |
| case OP_constructsuper: |
| case OP_construct: |
| return e.args.length-1; |
| case OP_call: |
| return e.args.length-2; |
| case OP_newarray: |
| return e.args.length; |
| case OP_newobject: |
| assert(e.args.length % 2 == 0); |
| return e.args.length/2; |
| default: |
| assert(false); |
| } |
| return 0; |
| } |
| |
| static class IndentingPrintWriter extends PrintWriter |
| { |
| int indent; |
| IndentingPrintWriter(Writer w) |
| { |
| super(w); |
| } |
| public void println() |
| { |
| super.println(); |
| for (int i=0; i < indent; i++) |
| print(" "); |
| } |
| } |
| |
| byte[] emit(InputAbc a, String filename, List<Integer> initScripts, boolean no_c_gen) throws IOException |
| { |
| // schedule everything that is reachable. this will assign new id's to |
| // stuff, then we can write it all out in the right order. |
| Abc abc = new Abc(); |
| for (Type s: a.scripts) |
| abc.addScript(s); |
| |
| abc.sort(); |
| |
| String scriptname = filename.substring(0,filename.lastIndexOf('.')); |
| byte[] data = emitAbc(abc); |
| OutputStream out = new FileOutputStream(scriptname+".abc2"); |
| try |
| { |
| out.write(data); |
| } |
| finally |
| { |
| out.close(); |
| } |
| |
| if (abc.haveNatives && !no_c_gen) |
| { |
| PrintWriter out_h = new PrintWriter(new FileWriter(scriptname+".h2")); |
| IndentingPrintWriter out_c = new IndentingPrintWriter(new FileWriter(scriptname+".cpp2")); |
| try |
| { |
| emitSource(abc, scriptname, data, initScripts, out_h, out_c); |
| } |
| finally |
| { |
| out_c.close(); |
| out_h.close(); |
| } |
| } |
| return data; |
| } |
| |
| void emitSource(Abc abc, String name, byte[] data, List<Integer> initScripts, |
| PrintWriter out_h, IndentingPrintWriter out_c) |
| { |
| // header file - definitions & native method decls |
| out_h.println("/* machine generated file -- do not edit */"); |
| out_h.println("namespace avmplus {"); |
| |
| out_h.println("AVMPLUS_NATIVEMAP_DECLARE("+name+", "+ abc.methodPool1.size()+")"); |
| |
| out_c.println("/* machine generated file -- do not edit */"); |
| out_c.println("namespace avmplus {"); |
| |
| // output vm creation init method |
| out_h.println("extern AvmInstance "+name+"_init(GC* gc, const AvmConfiguration& c, const uint8_t* abc_data=NULL, size_t length=0);"); |
| |
| // data buf decl only visible to init method |
| out_c.println("extern const uint8_t "+name+"_abc_data["+data.length+"];"); |
| out_c.println("AvmInstance "+name+"_init(GC* gc, const AvmConfiguration& c, const uint8_t* abc_data/*=NULL*/, size_t length/*=0*/)"); |
| out_c.printf("{\n\tif (abc_data==NULL) { abc_data = %s_abc_data; length = %s; }\n", name, data.length); |
| out_c.printf("\tAvmInstance _vm = AvmInit(gc, c, abc_data, length, %s_natives, %s_offset);\n", name, name); |
| |
| for(int i: initScripts) { |
| out_c.printf("\tAvmInitScript(_vm, %d);\n", i); |
| } |
| |
| out_c.printf("\treturn _vm;\n}\n"); |
| |
| StringWriter b = new StringWriter(); |
| PrintWriter out_t = new PrintWriter(b); |
| Map<Integer,String> impls = new TreeMap<Integer,String>(); |
| |
| for (Type s: abc.scripts) |
| emitSourceTraits("", abc, s, out_h, impls, out_t, out_c); |
| |
| out_c.print("AVMPLUS_NATIVEMAP_BEGIN("+name+")"); |
| out_c.indent++; |
| out_c.println(); |
| for (int id: impls.keySet()) |
| { |
| String s = impls.get(id); |
| if (s.charAt(0) == 'f') |
| out_c.println("AVMPLUS_NATIVEMAP_FORTHMETHOD("+s.substring(1)+")"); |
| else |
| out_c.println("AVMPLUS_NATIVEMAP_CMETHOD("+s.substring(1)+")"); |
| } |
| out_c.indent--; |
| out_c.println("AVMPLUS_NATIVEMAP_END()"); |
| out_c.println(); |
| |
| // put thunks in cpp file |
| out_t.flush(); |
| out_c.println(b); |
| out_c.println(); |
| |
| // cpp file - abc data, thunks |
| out_c.print("const uint8_t "+name+"_abc_data["+data.length+"] = {"); |
| out_c.indent++; |
| out_c.println(); |
| for (int i=0, n=data.length; i < n; i++) |
| { |
| int x = data[i] & 255; |
| if (x < 10) out_c.print(" "); |
| else if (x < 100) out_c.print(' '); |
| out_c.print(x); |
| if (i+1 < n) out_c.print(", "); |
| if (i%16 == 15) out_c.println(); |
| } |
| out_c.indent--; |
| out_c.println("};"); |
| out_c.println("} /* namespace avmplus */"); |
| |
| out_h.println("} /* namespace avmplus */"); |
| } |
| |
| void emitSourceTraits(String prefix, Abc abc, Type s, |
| PrintWriter out_h, Map<Integer,String> impls, PrintWriter out_t, PrintWriter out_c) |
| { |
| out_h.println(); |
| |
| assert(!s.init.isNative()); |
| for (Binding b: s.defs.values()) |
| { |
| Namespace ns = b.getName().nsset(0); |
| String id = prefix + propLabel(b, ns); |
| boolean isNative = false; |
| String ctype = null; |
| if (b.md.length > 0) |
| for (Metadata md : b.md) |
| if (md.name.equals("native")) |
| { |
| isNative = true; |
| for (Attr a: md.attrs) |
| if (a.name.equals("type")) |
| ctype = a.value; |
| } |
| |
| if (b.method != null) { |
| if(b.method.isNative()) |
| emitSourceMethod(prefix, abc, b, ns, out_h, impls, out_t, s.obscure_natives); |
| else if(isNative && !s.obscure_natives) { |
| int scriptId = abc.scriptId(s); |
| if(scriptId == -1) |
| throw new RuntimeException("Only scripts can have native callins"); |
| emitCallinMethod(b, ns, abc.scriptId(s), out_h, out_c); |
| } |
| } else if (isClass(b)) |
| emitSourceClass(abc, out_h, out_c, impls, out_t, b, ns, s.obscure_natives); |
| else if (isSlot(b) && isNative) |
| emitSourceSlot(prefix, abc, b, ns, id, ctype, out_h, out_t, s.obscure_natives); |
| } |
| } |
| |
| void emitSourceSlot(String prefix, Abc abc, Binding b, Namespace ns, String id, String ctype, PrintWriter out_h, PrintWriter out_t, boolean obscure_natives) |
| { |
| if (obscure_natives) |
| return; |
| |
| if (b.type.t.isAtom()) |
| { |
| if (ctype != null) |
| { |
| // native GCObject's must be stored as atoms, field type must be *. |
| if (b.type.t != ANY() || !ns.isPrivateOrInternal()) |
| throw new RuntimeException("native field "+id+" must be private or internal and type *"); |
| out_h.println("AVMPLUS_NATIVE_SLOT_DECL_GC("+ctype+","+b.offset+","+id+")"); |
| } |
| else |
| { |
| out_h.println("AVMPLUS_NATIVE_SLOT_DECL_ATOM("+b.offset+","+id+")"); |
| } |
| } |
| else if (b.type.t.emitAsAny()) |
| { |
| ctype = ctype(b.type); |
| out_h.println("AVMPLUS_NATIVE_SLOT_DECL_GC("+ctype+","+b.offset+","+id+")"); |
| } |
| else if (b.type.t.numeric) |
| { |
| ctype = ctype(b.type); |
| out_h.println("AVMPLUS_NATIVE_SLOT_DECL_PRIM("+ctype+","+b.offset+","+id+")"); |
| } |
| else |
| { |
| ctype = ctype(b.type); |
| out_h.println("AVMPLUS_NATIVE_SLOT_DECL_RC("+ctype+","+b.offset+","+id+")"); |
| } |
| } |
| |
| void emitSourceClass(Abc abc, PrintWriter out_h, PrintWriter out_c, Map<Integer,String>impls, PrintWriter out_t, |
| Binding b, Namespace ns, boolean obscure_natives) |
| { |
| String label = (ns.isPublic()||ns.isInternal()) ? b.getName().name : |
| ns.isProtected() ? "protected_"+b.getName().name : |
| TypeCache.instance().namespaceNames.containsKey(ns) ? TypeCache.instance().namespaceNames.get(ns)+"_"+b.getName().name : |
| (ns.uri.replace(' ', '_').replace('.','_').replace('$','_')+'_'+b.getName().name); |
| |
| Type c = b.type.t; |
| |
| if (!obscure_natives) |
| { |
| out_h.println("const int abcclass_"+ label + " = " + abc.classId(c) + ";"); |
| } |
| |
| emitSourceTraits(label+"_", abc, c, out_h, impls, out_t, out_c); |
| emitSourceTraits(label+"_", abc, c.itype, out_h, impls, out_t, out_c); |
| } |
| |
| String ctype(Typeref tref) |
| { |
| Type t = tref.t; |
| if (t == VOID()) return "void"; |
| if (t.isAtom()) return "AvmBox"; |
| if (t == INT()) return "int32_t"; |
| if (t == BOOLEAN()) return "bool"; // no, this would be bad. |
| if (t == UINT()) return "uint32_t"; |
| if (t == STRING()) return "AvmString"; |
| if (t == NAMESPACE()) return "AvmNamespace"; |
| if (t == NUMBER()) return "double"; |
| if (t.base == null) return tref.nonnull().toString() + "*"; |
| else return "AvmObject /*"+tref.toString()+"*/"; |
| } |
| |
| void emitSourceMethod(String prefix, Abc abc, |
| Binding b, Namespace ns, PrintWriter out_h, |
| Map<Integer,String> impls, PrintWriter out_t, boolean obscure_natives) |
| { |
| Method m = b.method; |
| |
| String impl = prefix + propLabel(b, ns); |
| if (isGetter(b)) |
| impl += "_get"; |
| else if (isSetter(b)) |
| impl += "_set"; |
| |
| String forthword = null; |
| if (b.md.length > 0) |
| { |
| for (Metadata md : b.md) |
| { |
| if (md.name.equals("forth")) |
| { |
| for (Attr a: md.attrs) |
| { |
| if (a.name.equals("word")) |
| { |
| forthword = a.value; |
| break; |
| } |
| } |
| if (forthword == null) |
| throw new RuntimeException("the forth metadata must specify the word attribute"); |
| break; |
| } |
| } |
| } |
| |
| if (forthword != null) |
| { |
| // note that forth methods are allowed to have optional parameters, but |
| // the default values will be ignored! it is up to the forth implementation |
| // to check and fill them in properly (that is, the optional args are allowed |
| // only to satisfy signature requirements). |
| |
| out_h.printf("AVMPLUS_FORTH_METHOD_DECL(%s, %s)\n", forthword, impl); |
| |
| impls.put(abc.methodId(m), "f" + forthword); |
| } |
| else |
| { |
| if (m.hasOptional()) |
| { |
| throw new RuntimeException("native methods may not have optional parameters: "+impl); |
| } |
| |
| if (m.needsRest()) |
| { |
| throw new RuntimeException("native methods may not have rest args: "+impl); |
| } |
| |
| // create a C++ declaration for the native thunk. |
| createThunkArgs(out_h, impl, m, obscure_natives); |
| if(m.returns.t != VOID() && m.returns.t.base == null && !m.returns.t.isAtom()) |
| out_h.printf("AVMPLUS_NATIVE_METHOD_DECL_GCOBJ(%s, %s)\n", ctype(m.returns), impl); |
| else |
| out_h.printf("AVMPLUS_NATIVE_METHOD_DECL(%s, %s)\n", ctype(m.returns), impl); |
| |
| impls.put(abc.methodId(m), "n" + impl); |
| } |
| |
| } |
| |
| String propLabel(Binding b, Namespace ns) |
| { |
| return (ns.isPublic()||ns.isInternal()) ? b.getName().name : |
| ns.isPrivate() ? "private_"+b.getName().name : |
| ns.isProtected() ? "protected_"+b.getName().name : |
| TypeCache.instance().namespaceNames.get(ns)+"_"+b.getName().name; |
| } |
| |
| /** |
| * create C++ code for a struct for the argtypes for this method |
| * |
| * @param out_t |
| * @param id |
| * @param m |
| */ |
| void createThunkArgs(PrintWriter out_h, String id, Method m, boolean obscure_natives) |
| { |
| |
| if (!m.hasParamNames() && m.getParams().length > 1) |
| { |
| throw new RuntimeException("native method " + id + " must be generated with debug info (have no fear, it will be stripped)"); |
| } |
| |
| out_h.println(); |
| if (obscure_natives) |
| { |
| out_h.println("struct " + id + "_args;"); |
| return; |
| } |
| |
| out_h.println("struct " + id + "_args"); |
| out_h.println("{"); |
| |
| // args |
| int i=0; |
| for (int n=m.getParams().length; i < n; i++) |
| { |
| String argname = (i == 0) ? |
| ((m.getParams()[i].toString().indexOf("$") >= 0) ? "classself" : "self") : |
| m.paramNames[i].name; |
| if (m.getParams()[i].t==NUMBER()) |
| { |
| out_h.printf(" public: double %s;\n", argname); |
| } |
| else if (m.getParams()[i].t==BOOLEAN()) |
| { |
| out_h.printf(" public: int32_t %s_b; private: int32_t %s_pad; public: inline bool %s() const { return %s_b != 0; }\n", argname, argname, argname, argname); |
| } |
| else if (m.getParams()[i].t==OBJECT() || m.getParams()[i].t==ANY()) |
| { |
| out_h.printf(" public: AvmBoxArg %s;\n", argname); |
| } |
| else |
| { |
| out_h.printf(" public: %s %s; private: int32_t %s_pad; \n", ctype(m.getParams()[i]), argname, argname); |
| } |
| } |
| |
| out_h.printf(" public: AvmStatusOut status_out;\n"); |
| |
| out_h.println("};"); |
| } |
| |
| void emitCallinMethod(Binding b, Namespace ns, int class_id, |
| PrintWriter out_h, PrintWriter out_c) |
| { |
| Method m = b.method; |
| String impl = ns.uri.replace('.', '_') + "_" + b.getName().name; |
| writeCallin(m, class_id, impl, out_h, out_c); |
| } |
| |
| String boxSetter(Type t) |
| { |
| if(t == INT()) |
| return "Int"; |
| else if(t == UINT()) |
| return "Uint"; |
| else if(t == BOOLEAN()) |
| return "Bool"; |
| else if(t == STRING()) |
| return "String"; |
| else if(t == NAMESPACE()) |
| return "Namespace"; |
| else if(t == NUMBER()) |
| return "Double"; |
| else if(t.base == null) // same test use to emit ANY for native slots, valid? |
| return "GCObject"; |
| return "Object"; |
| } |
| |
| void writeCallin(Method m, int script_id, String impl, PrintWriter out_h, PrintWriter out_c) |
| { |
| StringWriter bodySW = new StringWriter(); |
| StringWriter declSW = new StringWriter(); |
| PrintWriter decl = new PrintWriter(declSW); |
| PrintWriter body = new PrintWriter(bodySW); |
| |
| decl.printf("%s %s(AvmInstance vm", ctype(m.returns), impl); |
| body.print("\n{\n"); |
| if(m.getParams().length > 1) |
| body.printf("\tAvmBox _args[%d];\n", m.getParams().length - 1); |
| // args |
| int i=1; |
| for (int n=m.getParams().length; i < n; i++) |
| { |
| Typeref t = m.getParams()[i]; |
| decl.printf(", %s %s", ctype(t), m.paramNames[i]); |
| body.printf("\t_args[%d] = AvmBox%s(%s);\n", i-1, boxSetter(t.t), m.paramNames[i]); |
| } |
| decl.print(")"); |
| body.print("\t"); |
| if(m.returns.t != VOID()) |
| body.print("const AvmBox _returnBox = "); |
| |
| body.printf("\tAvmInvokeCallin(vm, %d, %d, %d, %s);\n", |
| script_id, m.emit_id, m.getParams().length - 1, m.getParams().length > 1 ? "(AvmBox*)&_args" : "NULL"); |
| |
| if(m.returns.t != VOID()) |
| body.printf("\treturn AvmUnBox%s(_returnBox);\n", boxSetter(m.returns.t)); |
| else |
| body.print(";\n"); |
| body.print("}\n"); |
| |
| out_h.printf("%s;\n", declSW.getBuffer()); |
| out_c.print(declSW.getBuffer()); |
| out_c.print(bodySW.getBuffer()); |
| } |
| |
| byte[] emitAbc(Abc abc) throws IOException |
| { |
| AbcWriter w = new AbcWriter(); |
| w.writeU16(16); |
| w.writeU16(46); |
| |
| int pos = w.size(); |
| w.writeU30(abc.intPool.size()); |
| for (int x: abc.intPool.values) |
| w.writeU30(x); |
| |
| verboseStatus("ints count "+abc.intPool.size()+ " size " + (w.size()-pos)); |
| pos = w.size(); |
| |
| w.writeU30(abc.uintPool.size()); |
| for (long x: abc.uintPool.values) |
| w.writeU30((int)x); |
| |
| verboseStatus("uints count "+abc.uintPool.size()+ " size " + (w.size()-pos)); |
| pos = w.size(); |
| |
| verboseStatus("doubles "+abc.doublePool.size()); |
| w.writeU30(abc.doublePool.size()); |
| for (double x: abc.doublePool.values) |
| w.write64(Double.doubleToLongBits(x)); |
| |
| verboseStatus("double count "+abc.doublePool.size()+ " size " + (w.size()-pos)); |
| pos = w.size(); |
| |
| w.writeU30(abc.stringPool.size()); |
| for (String s: abc.stringPool.values) |
| { |
| w.writeU30(s.length()); |
| w.write(s.getBytes("UTF-8")); |
| } |
| verboseStatus("strings count "+abc.stringPool.size()+ " size " + (w.size()-pos)); |
| pos = w.size(); |
| |
| w.writeU30(abc.nsPool.size()); |
| for (Namespace ns: abc.nsPool.values) |
| emitNamespace(abc, w, ns); |
| verboseStatus("ns count "+abc.nsPool.size()+ " size " + (w.size()-pos)); |
| pos = w.size(); |
| |
| w.writeU30(abc.nssetPool.size()); |
| for (Nsset nsset: abc.nssetPool.values) |
| { |
| w.writeU30(nsset.length); |
| for (Namespace ns: nsset) |
| w.writeU30(abc.nsPool.id(ns)); |
| } |
| verboseStatus("nsset count "+abc.nssetPool.size()+ " size " + (w.size()-pos)); |
| pos = w.size(); |
| |
| w.writeU30(abc.namePool.size()); |
| for (Name n: abc.namePool.values) |
| { |
| w.write(n.kind); |
| switch (n.kind) |
| { |
| case CONSTANT_TypeName: |
| /* |
| * TODO: this code is correct in isolation, |
| * but until the rest of the type plumbing |
| * supports parameterized types, the resulting |
| * bytecode fails. So fail fast instead. |
| w.writeU30(abc.stringPool.id(n.name)); |
| // TODO: replace with the parameter count |
| // when we begin supporting it. |
| w.writeU30(1); |
| w.writeU30(abc.stringPool.id(n.type_param)); |
| break; |
| */ |
| throw new IllegalArgumentException("CONSTANT_TypeName is only allowed in an import file."); |
| case CONSTANT_Qname: |
| case CONSTANT_QnameA: |
| w.writeU30(abc.nsPool.id(n.nsset(0))); |
| w.writeU30(abc.stringPool.id(n.name)); |
| break; |
| case CONSTANT_Multiname: |
| case CONSTANT_MultinameA: |
| w.writeU30(abc.stringPool.id(n.name)); |
| w.writeU30(abc.nssetPool.id(n.nsset)); |
| break; |
| case CONSTANT_RTQname: |
| case CONSTANT_RTQnameA: |
| w.writeU30(abc.stringPool.id(n.name)); |
| break; |
| case CONSTANT_MultinameL: |
| case CONSTANT_MultinameLA: |
| w.writeU30(abc.nssetPool.id(n.nsset)); |
| break; |
| case CONSTANT_RTQnameL: |
| case CONSTANT_RTQnameLA: |
| break; |
| default: |
| assert (false); |
| } |
| } |
| verboseStatus("name count "+abc.namePool.size()+ " size " + (w.size()-pos)); |
| pos = w.size(); |
| |
| w.writeU30(abc.methodPool2.size()); |
| int method_id=0; |
| for (Method m: abc.methodPool1.values) |
| emitMethod(abc, w, method_id++, m); |
| for (Method m: abc.methodPool2.values) |
| emitMethod(abc, w, method_id++, m); |
| |
| w.writeU30(abc.metaPool.size()); |
| for (Metadata md: abc.metaPool.values) |
| { |
| w.writeU30(abc.stringPool.id(md.name)); |
| w.writeU30(md.attrs.length); |
| for (Attr a: md.attrs) |
| w.writeU30(abc.stringPool.id(a.name)); |
| for (Attr a: md.attrs) |
| w.writeU30(abc.stringPool.id(a.value)); |
| } |
| |
| w.writeU30(abc.classes.size()); |
| for (Type c: abc.classes) |
| { |
| Type t = c.itype; |
| w.writeU30(abc.namePool.id(t.getName())); |
| w.writeU30(abc.typeRef(t.base)); |
| w.write(t.flags); |
| if (t.hasProtectedNs()) |
| w.writeU30(abc.nsPool.id(t.protectedNs)); |
| w.writeU30(t.interfaces.length); |
| for (Type i: t.interfaces) |
| w.writeU30(abc.interfaceRef(i)); |
| w.writeU30(abc.methodId(t.init)); |
| emitTraits(w, abc, t); |
| } |
| |
| for (Type c: abc.classes) |
| { |
| w.writeU30(abc.methodId(c.init)); |
| emitTraits(w, abc, c); |
| } |
| |
| w.writeU30(abc.scripts.size()); |
| for (Type s: abc.scripts) |
| { |
| w.writeU30(abc.methodId(s.init)); |
| emitTraits(w, abc, s); |
| } |
| |
| w.writeU30(abc.bodyCount); |
| emitBodies(abc, w, abc.methodPool1); |
| emitBodies(abc, w, abc.methodPool2); |
| |
| return w.toByteArray(); |
| } |
| |
| void emitNamespace(Abc emitNamespace, AbcWriter w, Namespace ns) |
| { |
| if (ns.isPrivateOrInternal()) |
| { |
| // internal -> private == sealed packages! |
| w.write(CONSTANT_PrivateNamespace); |
| w.writeU30(0); |
| } |
| else |
| { |
| w.write(ns.kind); |
| w.writeU30(emitNamespace.stringPool.id(ns.uri)); |
| } |
| } |
| |
| void emitBodies(Abc abc, AbcWriter w, Pool<Method> pool) throws IOException |
| { |
| for (Method m: pool.values) |
| { |
| if (m.entry == null) |
| continue; |
| w.writeU30(m.emit_id); |
| w.writeU30(m.max_stack); |
| w.writeU30(m.local_count); |
| if (m.cx != null && m.cx.scopes != null) |
| { |
| w.writeU30(m.cx.scopes.length); // init_scope_depth |
| w.writeU30(m.cx.scopes.length+m.max_scope); // max_scope_depth |
| } |
| else |
| { |
| w.writeU30(0); // init_scope_depth |
| w.writeU30(m.max_scope); // max_scope_depth |
| } |
| |
| emitCode(w, abc, m); |
| |
| emitTraits(w, abc, m.activation.t); |
| } |
| } |
| |
| void emitMethod(Abc abc, AbcWriter w, int method_id, Method m) |
| { |
| m.emit_id = method_id; |
| verboseStatus("METHOD " + method_id + " was " + m.id); |
| w.writeU30(m.getParams().length-1); |
| w.writeU30(abc.typeRef(m.returns)); |
| for (int i=1, n=m.getParams().length; i < n; i++) |
| w.writeU30(abc.typeRef(m.getParams()[i])); |
| |
| if (PRESERVE_METHOD_NAMES) |
| w.writeU30(abc.stringPool.id(m.debugName)); |
| else |
| w.writeU30(0); |
| |
| int flags = m.flags; |
| if (STRIP_DEBUG_INFO) |
| flags &= ~METHOD_HasParamNames; |
| w.write(flags); |
| if (m.hasOptional()) |
| { |
| w.writeU30(m.optional_count); |
| for (int j = m.getParams().length - m.optional_count; j < m.getParams().length; j++) |
| { |
| int kind = abc.constKind(m.values[j]); |
| w.writeU30(abc.constId(kind, m.values[j])); // index |
| w.write(kind); |
| } |
| } |
| if ((flags & METHOD_HasParamNames) != 0) |
| { |
| for (int i=1; i < m.paramNames.length; i++) |
| w.writeU30(abc.stringPool.id(m.paramNames[i].name)); |
| } |
| } |
| |
| void emitBlock(AbcWriter out, Block b, Abc abc) |
| { |
| |
| addTraceAttr("Block", b); |
| |
| // emit all but the last instruction of each block. |
| for (Expr e: b) |
| { |
| // Don't emit control-flow insns here; |
| // they're handled by intrablock |
| // processing. |
| if (e.succ != null) |
| break; |
| |
| traceEntry("Expr", "Expr", e); |
| addTraceAttr("op", opNames[e.op]); |
| addTraceAttr("pos", out.size()); |
| |
| out.write(e.op); |
| switch (e.op) |
| { |
| case OP_hasnext2: |
| out.writeU30(e.imm[0]); |
| out.writeU30(e.imm[1]); |
| break; |
| case OP_findproperty: |
| case OP_findpropstrict: |
| case OP_getlex: |
| case OP_getsuper: case OP_setsuper: |
| case OP_getproperty: case OP_setproperty: |
| case OP_deleteproperty: case OP_getdescendants: |
| case OP_initproperty: |
| case OP_istype: |
| case OP_coerce: |
| case OP_astype: |
| case OP_finddef: |
| out.writeU30(abc.namePool.id(e.ref)); |
| break; |
| case OP_callproperty: |
| case OP_callproplex: |
| case OP_callpropvoid: |
| case OP_callsuper: |
| case OP_callsupervoid: |
| case OP_constructprop: |
| out.writeU30(abc.namePool.id(e.ref)); |
| out.writeU30(argc(e)); |
| break; |
| case OP_constructsuper: |
| case OP_call: |
| case OP_construct: |
| case OP_newarray: |
| case OP_newobject: |
| out.writeU30(argc(e)); |
| break; |
| case OP_getlocal: |
| case OP_setlocal: |
| if (e.imm[0] < 4) |
| { |
| out.rewind(1); |
| out.write((e.op == OP_getlocal ? OP_getlocal0 : OP_setlocal0)+e.imm[0]); |
| break; |
| } |
| // else fall through |
| case OP_getslot: |
| case OP_setslot: |
| case OP_kill: |
| case OP_inclocal: |
| case OP_declocal: |
| case OP_inclocal_i: |
| case OP_declocal_i: |
| case OP_newcatch: |
| //case OP_getglobalslot: |
| //case OP_setglobalslot: |
| out.writeU30(e.imm[0]); |
| break; |
| case OP_newclass: |
| out.writeU30(abc.classId(e.c)); |
| break; |
| case OP_newfunction: |
| out.writeU30(abc.methodId(e.m)); |
| break; |
| case OP_applytype: |
| out.writeU30(argc(e)); |
| break; |
| case OP_callstatic: |
| //case OP_callmethod: |
| out.writeU30(abc.methodId(e.m)); |
| out.writeU30(argc(e)); |
| break; |
| case OP_pushshort: |
| out.writeU30(intValue(e.value)); |
| break; |
| case OP_pushbyte: |
| out.write(intValue(e.value)); |
| break; |
| case OP_getscopeobject: |
| out.write(e.imm[0]); |
| break; |
| case OP_pushstring: |
| case OP_dxns: |
| out.writeU30(abc.stringPool.id((String)e.value)); |
| break; |
| case OP_debugfile: |
| if (STRIP_DEBUG_INFO) |
| throw new RuntimeException("impossible"); |
| out.writeU30(abc.stringPool.id((String)e.value)); |
| break; |
| case OP_pushnamespace: |
| out.writeU30(abc.nsPool.id((Namespace)e.value)); |
| break; |
| case OP_pushint: |
| out.writeU30(abc.intPool.id(intValue(e.value))); |
| break; |
| case OP_pushuint: |
| out.writeU30(abc.uintPool.id(uintValue(e.value))); |
| break; |
| case OP_pushdouble: |
| out.writeU30(abc.doublePool.id(doubleValue(e.value))); |
| break; |
| case OP_debugline: |
| case OP_bkptline: |
| if (STRIP_DEBUG_INFO) |
| throw new RuntimeException("impossible"); |
| out.writeU30(e.imm[0]); |
| break; |
| case OP_debug: |
| if (STRIP_DEBUG_INFO) |
| throw new RuntimeException("impossible"); |
| out.write(e.imm[0]); |
| out.writeU30(e.imm[1]); |
| out.write(e.imm[2]); |
| out.writeU30(e.imm[3]); |
| break; |
| } |
| } |
| |
| |
| } |
| |
| void emitCode(AbcWriter out, Abc abc, Method m) throws IOException |
| { |
| |
| addTraceAttr("Method", m); |
| |
| Map<Block,Integer> padding = new HashMap<Block,Integer>(); |
| Deque<Block> code = schedule(m.entry.to); |
| Map<Block,AbcWriter> writers = new HashMap<Block,AbcWriter>(); |
| |
| // Find back edges to see where we need labels |
| // Note: can't use isBackedge() here b/c schedule |
| // may not lay out blocks in the same order. |
| BitSet labels = new BitSet(); |
| BitSet done = new BitSet(); |
| |
| for (Block b: code) |
| { |
| done.set(b.id); |
| for (Edge e: b.succ()) |
| if ( done.get(e.to.id) ) |
| { |
| labels.set(e.to.id); |
| } |
| } |
| |
| // emit the code and leave room for the branch offsets, |
| // and compute the final position of each block. |
| Map<Block,Integer> pos = new HashMap<Block,Integer>(); |
| Map<Block,Integer> blockends = new HashMap<Block,Integer>(); |
| int code_len = 0; |
| Deque<Block> work = new ArrayDeque<Block>(code); |
| while (!work.isEmpty()) |
| { |
| Block b = work.removeFirst(); |
| |
| addTraceAttr("Block", b); |
| addTraceAttr("pos", code_len); |
| pos.put(b,code_len); |
| AbcWriter w = new AbcWriter(); |
| writers.put(b, w); |
| if (labels.get(b.id)) |
| { |
| addTraceAttr("hasLabel"); |
| w.write(OP_label); |
| } |
| emitBlock(w, b, abc); |
| |
| code_len += w.size(); |
| Expr last = b.last(); |
| |
| if (last.succ.length == 0) |
| { |
| // returnvoid, returnvalue, or throw |
| w.write(last.op); |
| code_len++; |
| traceEntry("TransferOut", "op", opNames[last.op]); |
| } |
| else if (isJump(last)) |
| { |
| traceEntry("Jump", "target", last.succ[0].to); |
| if (work.isEmpty() || last.succ[0].to != work.peekFirst()) |
| { |
| // target is not fall through block so we'll emit a jump. |
| code_len += 4; |
| padding.put(b, 4); |
| addTraceAttr("fallThrough", false); |
| } |
| else |
| addTraceAttr("fallThrough", true); |
| } |
| else if (isBranch(last)) |
| { |
| if (work.isEmpty() || last.succ[0].to != work.peekFirst()) |
| { |
| code_len += 8; |
| padding.put(b, 8); |
| } |
| else |
| { |
| code_len += 4; |
| padding.put(b, 4); |
| } |
| } |
| else |
| { |
| assert(last.op == OP_lookupswitch); |
| // Switch table contains a U30 case count and S24 offsets. |
| int switch_size = 1 + out.sizeOfU30(last.succ.length) + 3*last.succ.length; |
| code_len += switch_size; |
| padding.put(b, switch_size); |
| } |
| blockends.put(b,code_len); |
| |
| } |
| |
| out.writeU30(code_len); |
| int code_start = out.size(); |
| traceEntry("WriteCode", "Start", code_start); |
| for (Block b: code) |
| { |
| |
| addTraceAttr("Block", b); |
| addTraceAttr("postorder", b.postorder); |
| addTraceAttr("startPos", out.size()); |
| addTraceAttr("offset", out.size() - code_start); |
| writers.get(b).writeTo(out); |
| if (padding.containsKey(b)) |
| { |
| Expr last = b.last(); |
| |
| |
| addTraceAttr(last); |
| addTraceAttr("op", opNames[last.op]); |
| addTraceAttr("padding", padding.get(b)); |
| |
| if (isBranch(last)) |
| { |
| addTraceAttr("isBranch"); |
| emitBranch(out, last.op, last.succ[1].to, code_start, pos); |
| padding.put(b, padding.get(b)-4); |
| } |
| if (padding.get(b) == 4) |
| { |
| traceEntry("ImpliedJump"); |
| addTraceAttr("EdgeId", last.succ[0].id); |
| emitBranch(out, OP_jump, last.succ[0].to, code_start, pos); |
| } |
| if ( last.op == OP_lookupswitch ) |
| { |
| emitLookupswitch(out, last, code_start, pos); |
| } |
| |
| } |
| |
| |
| } |
| |
| // Input code occasionally contains |
| // an empty (or non-effecting, and |
| // thus effectively empty) try block. |
| // Elide these. |
| |
| int valid_handlers_count = 0; |
| for (Handler h: m.handlers) |
| { |
| if ( h.entry != null ) |
| valid_handlers_count++; |
| } |
| |
| out.writeU30(valid_handlers_count); |
| for (Handler h: m.handlers) |
| { |
| if ( null == h.entry ) |
| continue; |
| |
| int from = code_len; |
| int to = 0; |
| for (Block b: code) |
| { |
| for (Edge x: b.xsucc) |
| { |
| if (x.to == h.entry) |
| { |
| if (pos.get(b) < from) |
| from = pos.get(b); |
| if (blockends.get(b) > to) |
| to = blockends.get(b); |
| } |
| } |
| } |
| out.writeU30(from); |
| out.writeU30(to); |
| |
| int off = pos.get(h.entry); |
| |
| verboseStatus("handler "+h.entry+ " ["+from+","+to+")->"+off); |
| out.writeU30(off); |
| out.writeU30(abc.typeRef(h.type)); |
| // See corresponding logic |
| // in readCode() where the |
| // handler's read in. |
| if ( h.name != null ) |
| out.writeU30(abc.namePool.id(h.name)); |
| else |
| out.writeU30(0); |
| } |
| |
| |
| } |
| |
| |
| void emitBranch(AbcWriter out, int op, Block target, int code_start, Map<Block,Integer>pos) |
| { |
| traceEntry("emitBranch"); |
| addTraceAttr("op", opNames[op]); |
| addTraceAttr("target", target); |
| out.write(op); |
| int to = code_start + pos.get(target); |
| int from = out.size()+3; |
| addTraceAttr("to", to); |
| addTraceAttr("from", from); |
| addTraceAttr("offset", to-from); |
| out.writeS24(to-from); |
| } |
| |
| void emitLookupswitch(AbcWriter out, Expr insn_switch, int code_start, Map<Block, Integer> pos) |
| { |
| |
| |
| // "The base location is the address of the lookupswitch instruction itself." - AVM2 |
| int base_loc = out.size(); |
| addTraceAttr("baseLoc", base_loc); |
| |
| out.write(OP_lookupswitch); |
| int case_size = insn_switch.succ.length - 2; |
| addTraceAttr("case_size", case_size); |
| |
| int default_target = code_start + pos.get(insn_switch.succ[case_size + 1].to) - base_loc; |
| traceEntry("default", "target", default_target); |
| addTraceAttr("Block", insn_switch.succ[case_size + 1].to); |
| out.writeS24(default_target); |
| out.writeU30(case_size); |
| for ( int i = 0; i <= case_size; i++ ) |
| { |
| int case_target = code_start + pos.get(insn_switch.succ[i].to) - base_loc; |
| traceEntry("case", "target", case_target); |
| addTraceAttr("Block", insn_switch.succ[i].to); |
| out.writeS24(case_target); |
| } |
| |
| } |
| |
| void emitTraits(AbcWriter out, Abc abc, Type t) |
| { |
| Symtab<Binding> defs = t.defs; |
| out.writeU30(defs.size()); |
| for (Binding b: defs.values()) |
| { |
| out.writeU30(abc.namePool.id(b.getName())); |
| out.write(b.flags_kind & ~(TRAIT_FLAG_metadata<<4)); |
| switch (b.kind()) |
| { |
| case TRAIT_Var: |
| case TRAIT_Const: |
| if ( t.base == null || 0 == t.base.slotCount ) |
| out.writeU30(b.slot); |
| else |
| out.write(0); |
| out.writeU30(abc.typeRef(b.type)); |
| if (!b.defaultValueChanged()) |
| { |
| // vm fills in default value based on type |
| out.writeU30(0); |
| } |
| else |
| { |
| // non-default default value |
| int kind = abc.constKind(b.value); |
| int id = abc.constId(kind, b.value); |
| out.writeU30(id); |
| if (id != 0) |
| out.write(kind); |
| } |
| break; |
| case TRAIT_Class: |
| out.writeU30(b.slot); |
| out.writeU30(abc.classId(b.type.t)); |
| break; |
| case TRAIT_Method: |
| case TRAIT_Getter: |
| case TRAIT_Setter: |
| out.writeU30(b.slot); |
| out.writeU30(abc.methodId(b.method)); |
| break; |
| default: |
| assert(false); |
| } |
| } |
| } |
| |
| void optimize(Method m) |
| { |
| verboseStatus("OPTIMIZE "+m.id + " "+ m.getName()); |
| |
| if(m.entry == null) |
| return; |
| |
| |
| addTraceAttr("Method", m); |
| printMethod(m, "BEFORE OPT"); |
| |
| if (OUTPUT_DOT) |
| dot("-before",m); |
| |
| sccp(m); |
| dvn(m); |
| |
| if (cfgopt(m)) |
| { |
| printMethod(m, "AFTER CFGOPT"); |
| |
| sccp(m); |
| dvn(m); |
| } |
| |
| // find operations to fold together. |
| fold(m); |
| |
| printMethod(m, "AFTER FOLD"); |
| |
| insert_casts(m); |
| remove_phi(m); |
| |
| if (OUTPUT_DOT) |
| dot("-after",m); |
| |
| if ( legacy_verifier ) |
| { |
| appeaseLegacyVerifier(m); |
| if (OUTPUT_DOT) |
| dot("-appeased",m); |
| } |
| |
| computeFrameCounts(m); |
| |
| if ( verbose_mode ) |
| { |
| printabc(schedule(m.entry.to)); |
| } |
| |
| |
| } |
| |
| public static byte[] optimize( byte[] raw_abc, String filename, ObjectList<ConfigVar> optimizer_configs, ObjectList<String> import_filespecs ) |
| throws java.io.IOException |
| { |
| GlobalOptimizer go = new GlobalOptimizer(); |
| go.legacy_verifier = true; |
| |
| boolean quiet_mode = false; |
| |
| for ( ConfigVar config_var: optimizer_configs ) |
| { |
| if (config_var.name.equalsIgnoreCase("-IMPORT")) |
| { |
| go.new InputAbc().readAbc(load(config_var.value)); |
| } |
| else if ( config_var.name.equalsIgnoreCase("-LEGACY_VERIFIER")) |
| { |
| go.legacy_verifier = !go.legacy_verifier; |
| } |
| else if ( config_var.name.equalsIgnoreCase("-PRESERVE_METHOD_NAMES")) |
| { |
| go.PRESERVE_METHOD_NAMES = true; |
| } |
| else if ( config_var.name.equalsIgnoreCase("-QUIET")) |
| { |
| quiet_mode = true; |
| } |
| else if ( config_var.name.equalsIgnoreCase("-ALLOW_NATIVE_CTORS")) |
| { |
| go.ALLOW_NATIVE_CTORS = true; |
| } |
| if ( config_var.name.equalsIgnoreCase("-plugin") ) |
| { |
| go.loadPlugin(config_var.value); |
| continue; |
| } |
| if ( config_var.name.startsWith("-") && config_var.name.contains(":")) |
| { |
| int delim_pos = config_var.name.indexOf(':'); |
| String plugin_name = config_var.name.substring(1, delim_pos); |
| String plugin_option = config_var.name.substring(delim_pos+1); |
| |
| PluginData plugin_data = go.analysis_phase_plugins.get(plugin_name); |
| |
| if ( null == plugin_data ) |
| { |
| // Iterate over the plugins and find a short |
| // name that matches this monster. |
| |
| for ( PluginData search_plugin: go.analysis_phase_plugins.values() ) |
| { |
| if ( search_plugin.plugin.getClass().getSimpleName().equals(plugin_name)) |
| { |
| plugin_data = search_plugin; |
| break; |
| } |
| } |
| } |
| |
| if ( null != plugin_data ) |
| { |
| plugin_data.options.add(plugin_option); |
| } |
| else |
| { |
| throw new IllegalArgumentException("No plugin named " + plugin_name + " is loaded."); |
| } |
| |
| continue; |
| } |
| else |
| { |
| throw new IllegalArgumentException("Unknown -o2:configuration_value " + config_var.name); |
| } |
| } |
| |
| // TODO: ASC and GO data structures need to interoperate. |
| // For now, just redo the imports. |
| for ( String import_filespec: import_filespecs) |
| { |
| go.new InputAbc().readAbc(import_filespec); |
| } |
| |
| InputAbc input_abc = go.new InputAbc(); |
| input_abc.src_filename = filename; |
| |
| input_abc.readAbc(raw_abc); |
| |
| go.initializePlugins(); |
| go.optimize(input_abc); |
| |
| Abc abc = go.new Abc(); |
| |
| for (Type s: input_abc.scripts) |
| { |
| abc.addScript(s); |
| } |
| |
| abc.sort(); |
| |
| byte[] optimized_abc = go.emitAbc(abc); |
| |
| if ( !quiet_mode ) |
| { |
| System.out.println("Original ABC size: " + raw_abc.length); |
| System.out.println("Optimized ABC size: " + optimized_abc.length); |
| } |
| |
| return optimized_abc; |
| |
| } |
| |
| void fold(Method m) |
| { |
| Deque<Block> code = dfs(m.entry.to); |
| EdgeMap<Expr> uses = findUses(code); |
| for (Block b: code) |
| { |
| for (Expr e: b) |
| { |
| switch (e.op) |
| { |
| case OP_iftrue: |
| case OP_iffalse: |
| { |
| Expr a0 = e.args[0]; |
| if (containsOnly(uses.get(a0),e)) |
| { |
| if (false) |
| ; |
| else if (a0.op == OP_not) |
| subsume_arg(e, (e.op == OP_iftrue ? OP_iffalse : OP_iftrue), uses); |
| |
| else if (a0.op == OP_convert_b) |
| subsume_arg(e, e.op, uses); |
| |
| else if (a0.op == OP_greaterequals) |
| subsume_arg(e, (e.op == OP_iftrue ? OP_ifge : OP_ifnge), uses); |
| |
| else if (a0.op == OP_greaterthan) |
| subsume_arg(e, (e.op == OP_iftrue ? OP_ifgt : OP_ifngt), uses); |
| |
| else if (a0.op == OP_lessthan) |
| subsume_arg(e, (e.op == OP_iftrue ? OP_iflt : OP_ifnlt), uses); |
| |
| else if (a0.op == OP_lessequals) |
| subsume_arg(e, (e.op == OP_iftrue ? OP_ifle : OP_ifnle), uses); |
| |
| // too aggressive, breaks (at least) zlib |
| //else if (a0.op == OP_equals) |
| // subsume_arg(e, (e.op == OP_iftrue ? OP_ifeq : OP_ifne), uses); |
| |
| else if (a0.op == OP_strictequals) |
| subsume_arg(e, (e.op == OP_iftrue ? OP_ifstricteq : OP_ifstrictne), uses); |
| } |
| break; |
| } |
| |
| case OP_getproperty: |
| { |
| Expr a0 = e.args[0]; |
| if (containsOnly(uses.get(a0),e) && |
| a0.op == OP_findpropstrict && |
| a0.args.length == 0 && |
| e.args.length == 1) |
| { |
| e.op = OP_getlex; |
| e.args = noexprs; |
| e.scopes = a0.scopes; |
| a0.setPure(); |
| e.flags |= a0.flags&PX | a0.flags&EFFECT; |
| } |
| } |
| break; |
| } |
| } |
| } |
| dce(m); |
| } |
| |
| |
| |
| int findPhiArg(Expr phi, Edge e) |
| { |
| for (int i=0, n=phi.pred.length; i < n; i++) |
| if (phi.pred[i].equals(e)) |
| return i; |
| assert(false); |
| return -1; |
| } |
| |
| /** |
| * determine if the two blocks have the same exception scope. |
| * @param b1 |
| * @param b2 |
| * @return |
| */ |
| boolean sameExScope(Block b1, Block b2) |
| { |
| if (b1 == b2) return true; |
| Edge[] xs1 = b1.xsucc; |
| Edge[] xs2 = b2.xsucc; |
| if (xs1.length != xs2.length) |
| return false; |
| |
| for (int i=0, n=xs1.length; i < n; i++) |
| { |
| Edge e1 = xs1[i]; |
| Edge e2 = xs2[i]; |
| if (e1.to != e2.to || e1.handler != e2.handler) |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * control flow optimizations. |
| * |
| * turn B1->B2 into B1B2 as long as B1's only successor is B2 and B2's only predecessor is B1. |
| * |
| * any branch to a jump can branch to the jump's target |
| * |
| * jump to a return can be replaced by the return |
| * |
| * ISSUE phi's represent copies on an edge. Need to validate that removing |
| * edges doesn't remove a required phi-copy. Any critical edges that have |
| * been split by previous operations may be removed by this pass. |
| * |
| * Should we only do this pass on non-ssa form? |
| */ |
| boolean cfgopt(Method m) |
| { |
| Deque<Block> code = dfs(m.entry.to); |
| SetMap<Block,Edge> pred = allpreds(code); |
| |
| boolean changedout=false; |
| boolean changed; |
| do |
| { |
| changed = false; |
| blocks: for (Block b: code) |
| { |
| Expr last = b.last(); |
| Block taken; |
| if (isJump(last) && sameExScope(b, taken=last.succ[0].to)) |
| { |
| Edge s = last.succ[0]; |
| if (containsOnly(pred.get(taken), s) && !taken.must_isolate_block) |
| { |
| // phi nodes can only occur in nodes with more than one predecessor |
| assert(taken.first().op != OP_phi); |
| verboseStatus("STRAIGHTEN "+s); |
| b.remove(last); |
| b.addAll(taken); |
| for (Edge edge: taken.succ()) |
| edge.from = b; |
| changed = true; |
| printabc(b, new PrintWriter(System.out)); |
| break blocks; |
| } |
| Expr first = taken.first(); |
| if (first.op == OP_returnvalue || first.op == OP_returnvoid) |
| { |
| verboseStatus("PRUNE "+b+"->"+s); |
| last.op = first.op; |
| last.args = first.op == OP_returnvoid ? noexprs : new Expr[] { first.args[0] }; |
| last.succ = noedges; |
| changed = true; |
| break blocks; |
| } |
| Expr r; |
| if (first.op == OP_phi && taken.size()==2 && (r=taken.last()).op == OP_returnvalue && |
| r.args[0] == first) |
| { |
| // successor block is returnvalue(phi) |
| verboseStatus("PRUNE "+b+"->"+s); |
| int i = findPhiArg(first, last.succ[0]); |
| last.op = r.op; |
| last.args = new Expr[] { first.args[i] }; |
| last.succ = noedges; |
| first.removePhiInput(i); |
| changed = true; |
| break blocks; |
| } |
| } |
| |
| if (isBranch(last)) |
| { |
| Edge out = last.succ[1]; |
| Expr cond = last.args[0]; |
| taken = out.to; |
| Expr phi, br; |
| if (last.args.length == 1 && taken.size()==2 && (phi=taken.first()).op==OP_phi && |
| ((br=taken.last()).op == OP_iftrue || br.op == OP_iffalse)) |
| { |
| int i = findPhiArg(phi,out); |
| if (phi.args[i]==cond) |
| { |
| // cond is in phi contributed by this edge. so we have a redundant test. |
| // we want to retarget the branch: and adjust any phi nodes that are affected. |
| Edge before = br.op == last.op ? br.succ[1] : br.succ[0]; |
| verboseStatus("SKIPTEST old "+out+" new "+before); |
| phi.removePhiInput(i); |
| copyTargetPhi(phi, cond, before, out); |
| changed = true; |
| break blocks; |
| } |
| } |
| |
| if (pred.get(last.succ[0].to).size() > 1 && pred.get(taken).size() == 1) |
| { |
| // fall through has multiple preds, taken has only one. reverse the sense |
| // of the test and reverse the edges. Note that this doesn't change the |
| // flow so we don't need to recompute preds, etc. |
| invert(last); |
| changed = true; |
| break blocks; |
| } |
| } |
| |
| for (Edge edge : last.succ) |
| { |
| if (skip(edge)) |
| { |
| changed = true; |
| break blocks; |
| } |
| } |
| } |
| |
| if (changed) |
| { |
| dce(m); |
| code = dfs(m.entry.to); |
| pred = allpreds(code); |
| changedout = true; |
| } |
| } |
| while (changed); |
| return changedout; |
| } |
| |
| boolean skip(Edge edge) |
| { |
| Block to = edge.to; |
| Expr j = to.last(); |
| if (to.size()==1 && isJump(j)) |
| { |
| // any edge targeting a jump can target the jump's target. |
| verboseStatus("SKIP " + j.succ[0]); |
| copyTarget(j.succ[0], edge); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * reverse the sense of a conditional test and swap the outgoing edges. |
| * @param br |
| */ |
| void invert(Expr br) |
| { |
| verboseStatus("INVERT "+br); |
| |
| switch (br.op) |
| { |
| case OP_iftrue: br.op = OP_iffalse; break; |
| case OP_iffalse: br.op = OP_iftrue; break; |
| case OP_ifnlt: br.op = OP_iflt; break; |
| case OP_ifnle: br.op = OP_ifle; break; |
| case OP_ifngt: br.op = OP_ifgt; break; |
| case OP_ifnge: br.op = OP_ifge; break; |
| case OP_ifeq: br.op = OP_ifne; break; |
| case OP_ifne: br.op = OP_ifeq; break; |
| case OP_iflt: br.op = OP_ifnlt; break; |
| case OP_ifle: br.op = OP_ifnle; break; |
| case OP_ifgt: br.op = OP_ifngt; break; |
| case OP_ifge: br.op = OP_ifnge; break; |
| case OP_ifstricteq: br.op = OP_ifstrictne; break; |
| case OP_ifstrictne:br.op = OP_ifstricteq; break; |
| } |
| |
| Edge e0 = br.succ[0]; |
| Edge e1 = br.succ[1]; |
| e0.label = 1; |
| e1.label = 0; |
| br.succ[0] = e1; |
| br.succ[1] = e0; |
| } |
| |
| int ifoper(int op) |
| { |
| switch (op) |
| { |
| default: assert(false); return 0; |
| case OP_iflt: |
| case OP_ifnlt: return OP_lessthan; |
| case OP_ifle: |
| case OP_ifnle: return OP_lessequals; |
| case OP_ifgt: |
| case OP_ifngt: return OP_greaterthan; |
| case OP_ifge: |
| case OP_ifnge: return OP_greaterequals; |
| case OP_ifne: |
| case OP_ifeq: return OP_equals; |
| case OP_ifstricteq: |
| case OP_ifstrictne: return OP_strictequals; |
| } |
| } |
| |
| /** |
| * for each phi in the target block, add a new phi arg for the new edge, |
| * contributing the same variable that the old edge contributed. |
| * @param succ |
| * @param oldpred |
| * @param newpred |
| */ |
| void copyTarget(Edge before, Edge after) |
| { |
| after.to = before.to; |
| for (Expr e: before.to) |
| if (e.op == OP_phi) |
| e.append(e.args[findPhiArg(e,before)], after); |
| } |
| |
| void copyTargetPhi(Expr phi, Expr a, Edge before, Edge after) |
| { |
| after.to = before.to; |
| for (Expr e: before.to) |
| { |
| if (e.op == OP_phi) |
| { |
| Expr phiArg = e.args[findPhiArg(e,before)]; |
| e.append(phiArg==phi ? a : phiArg, after); |
| } |
| else |
| break; |
| } |
| } |
| |
| boolean containsOnly(Collection c, Object elem) |
| { |
| return c.size() == 1 && c.contains(elem); |
| } |
| |
| boolean equiv(Name a, Name b) |
| { |
| return a.match(b) == 0; |
| } |
| |
| boolean equiv(Expr[] a, Expr[] b) |
| { |
| if (a == null || b == null) return false; |
| if (a.length != b.length) return false; |
| for (int i=0; i < a.length; i++) |
| if (!equiv(a[i],b[i])) |
| return false; |
| return true; |
| } |
| |
| boolean equiv(Expr a, Expr b) |
| { |
| if (a == b) |
| return true; |
| |
| if (a != null && b != null && a.op == b.op) |
| { |
| switch (a.op) |
| { |
| case OP_pushtrue: |
| case OP_pushfalse: |
| case OP_pushnan: |
| case OP_pushnull: |
| case OP_pushundefined: |
| case OP_getlocal0: |
| case OP_getlocal1: |
| case OP_getlocal2: |
| case OP_getlocal3: |
| return true; |
| case OP_pushbyte: |
| case OP_pushshort: |
| case OP_pushint: |
| case OP_pushuint: |
| case OP_pushdouble: |
| case OP_pushnamespace: |
| case OP_pushstring: |
| return a.value.equals(b.value); |
| case OP_getlocal: |
| case OP_arg: |
| return a.imm[0] == b.imm[0]; |
| case OP_getglobalscope: |
| return a.scopes.length == 0 && b.scopes.length == 0 || |
| a.scopes.length > 0 && b.scopes.length > 0 && equiv(a.scopes[0], b.scopes[0]); |
| /*case OP_getslot: |
| // TODO unsafe, don't know about intervening stores |
| return a.imm[0] == b.imm[0] && equiv(a.args[0],b.args[0]); |
| case OP_getproperty: |
| // TODO unsafe -- ignores intervening stores & side effects |
| return equiv(a.ref, b.ref) && equiv(a.args,b.args);*/ |
| case OP_finddef: |
| return 0 == a.ref.match(b.ref); |
| } |
| } |
| return false; |
| } |
| |
| void makeCopy(Expr e, Expr a) |
| { |
| // make e be a copy of a |
| assert(e != a); |
| e.op = OP_dup; |
| e.locals = new Expr[] { a }; |
| e.scopes = e.args = noexprs; |
| e.setPure(); |
| } |
| |
| void makeNop(Expr e) |
| { |
| e.op = OP_nop; |
| e.args = e.locals = e.scopes = noexprs; |
| e.setPure(); |
| } |
| |
| Expr dvn_find(Expr e, Block b, Map<Block,Block>idom) |
| { |
| do |
| { |
| for (Expr a: b) |
| { |
| if (a == e) break; |
| if (equiv(a,e)) |
| return a; |
| } |
| b = idom.get(b); |
| } |
| while (b != null); |
| return null; |
| } |
| |
| /** |
| * dominator based value numbering. This is just LVN plus a search up the |
| * dominator tree. When a matching expression is found turn the |
| * instruction into a copy of the found expression. |
| * @param code |
| */ |
| void dvn(Method m) |
| { |
| Deque<Block> code = dfs(m.entry.to); |
| Map<Block,Block> idom = idoms(code, preds(code)); |
| boolean changed; |
| do |
| { |
| changed = false; |
| for (Block b: code) |
| { |
| for (Expr e: b) |
| { |
| Expr a = dvn_find(e, b, idom); |
| if (a != null) |
| { |
| makeCopy(e,a); |
| changed = true; |
| } |
| } |
| } |
| if (changed) |
| dce(m); |
| } |
| while (changed); |
| } |
| |
| boolean constify(Expr e, Object v) |
| { |
| if (v != null && v != BOTTOM && e.value == null && !hasSideEffect(e)) |
| { |
| // expr is const |
| if (v instanceof Integer) |
| { |
| int i = intValue(v); |
| e.op = (i == (byte)i) ? OP_pushbyte : |
| (i == (short)i) ? OP_pushshort : |
| OP_pushint; |
| } |
| else if (v instanceof Long) |
| { |
| e.op = OP_pushuint; |
| } |
| else if (v instanceof Number) |
| { |
| double d = doubleValue(v); |
| e.op = Double.isNaN(d) ? OP_pushnan : OP_pushdouble; |
| } |
| else if (v instanceof Boolean) |
| { |
| e.op = v == TRUE ? OP_pushtrue : OP_pushfalse; |
| } |
| else if (v instanceof Namespace) |
| { |
| e.op = OP_pushnamespace; |
| } |
| else if (v == UNDEFINED) |
| { |
| e.op = OP_pushundefined; |
| } |
| else if (v == NULL()) |
| { |
| assert(!e.onScope()); |
| e.op = OP_pushnull; |
| } |
| else |
| { |
| assert(v instanceof String); |
| e.op = OP_pushstring; |
| } |
| e.pred = noedges; |
| e.args = e.scopes = e.locals = noexprs; |
| e.value = v; |
| return true; |
| } |
| return false; |
| } |
| |
| boolean jumpify(Expr e, Set<Edge> reached) |
| { |
| Edge taken = null; |
| for (Edge s: e.succ) |
| { |
| if (reached.contains(s)) |
| { |
| if (taken == null) |
| taken = s; |
| else if (s != taken) |
| { |
| taken = null; |
| break; |
| } |
| } |
| } |
| if (taken != null) |
| { |
| e.op = OP_jump; |
| e.args = noexprs; |
| e.succ = new Edge[] { taken }; |
| return true; |
| } |
| return false; |
| } |
| |
| boolean makeConvert(Expr e, Type t, int op, Map<Expr,Typeref> types) |
| { |
| e.op = op; |
| e.args = new Expr[] { e.args[1] }; |
| e.flags = flagTable[op]; |
| if (type(types,e.args[0]).isPrimitive()) |
| e.setPure(); |
| return true; |
| } |
| |
| boolean convertify(Expr e, Binding b0, Map<Expr,Typeref>types) |
| { |
| if (b0.type != null && e.args.length == 2) |
| { |
| if (b0.type.t.itype == NUMBER()) |
| return makeConvert(e,NUMBER(),OP_convert_d,types); |
| if (b0.type.t.itype == INT()) |
| return makeConvert(e,INT(),OP_convert_i,types); |
| if (b0.type.t.itype == UINT()) |
| return makeConvert(e,UINT(),OP_convert_u,types); |
| if (b0.type.t.itype == STRING()) |
| return makeConvert(e,STRING(),OP_convert_s,types); |
| if (b0.type.t.itype == BOOLEAN()) |
| return makeConvert(e,BOOLEAN(),OP_convert_b,types); |
| } |
| return false; |
| } |
| |
| Expr unwrapScope(Expr e, int i) |
| { |
| // Phi pseudo-Exprs may appear in the scope stack |
| // if the block immediately follows a try/catch, |
| // or if the block is the fall-through alternative |
| // of a conditional jump. In either of these cases, |
| // the compiler set up all phi inputs to refer back |
| // to the same underlying object, so just traverse |
| // back along the first dataflow edge. |
| while ( e.scopes[i].op == OP_phi ) |
| { |
| assert(e.scopes[i].args != null && e.scopes[i].args[0] != null); |
| e.scopes[i] = e.scopes[i].args[0]; |
| } |
| |
| assert(e.scopes[i].onScope()); |
| return e.scopes[i].args[0]; |
| } |
| |
| static Type[] copyOf(Type[] in, int newlen) |
| { |
| Type[] out = new Type[newlen]; |
| if (newlen > in.length) |
| newlen = in.length; |
| System.arraycopy(in, 0, out, 0, newlen); |
| return out; |
| } |
| |
| static Typeref[] copyOf(Typeref[] in, int newlen) |
| { |
| Typeref[] out = new Typeref[newlen]; |
| if (newlen > in.length) |
| newlen = in.length; |
| System.arraycopy(in, 0, out, 0, newlen); |
| return out; |
| } |
| |
| static Expr[] copyOf(Expr[] in, int newlen) |
| { |
| Expr[] out = new Expr[newlen]; |
| if (newlen > in.length) |
| newlen = in.length; |
| System.arraycopy(in, 0, out, 0, newlen); |
| return out; |
| } |
| |
| static Edge[] copyOf(Edge[] in, int newlen) |
| { |
| Edge[] out = new Edge[newlen]; |
| if (newlen > in.length) |
| newlen = in.length; |
| System.arraycopy(in, 0, out, 0, newlen); |
| return out; |
| } |
| |
| /** |
| * sparse conditional constant propagation |
| * @param code |
| */ |
| void sccp(Method m) |
| { |
| |
| addTraceAttr("Method", m); |
| |
| // first build the SSA Edges. |
| Deque<Block> code = dfs(m.entry.to); |
| EdgeMap<Expr> uses = findUses(code); |
| |
| Map<Expr,Object> values = new TreeMap<Expr,Object>(); |
| Map<Expr,Typeref> types = new TreeMap<Expr,Typeref>(); |
| Set<Edge> reached = new TreeSet<Edge>(); |
| |
| sccp_analyze(m, uses, values, types, reached); |
| |
| verboseStatus("REACHED " + reached); |
| verboseStatus("TYPES " + types); |
| |
| sccp_cfgopt(values, types, reached); |
| |
| // now the CFG is cleaned up |
| dce(m); |
| |
| // now do type/value based peephole work on each expr in DFS order |
| code = dfs(m.entry.to); |
| uses = findUses(code); |
| |
| // modify the code based on analysis: |
| // * capture scope types for OP_newclass & OP_newfunction |
| // * turn expressions into constants |
| |
| TreeSet<Expr> work = new TreeSet<Expr>(); |
| for (Block b: code) |
| for (Expr e: b) |
| work.add(e); |
| |
| while (!work.isEmpty()) |
| { |
| Expr e = getExpr(work); |
| sccp_modify(m, uses, values, types, e, work); |
| } |
| |
| dce(m); |
| //verboseStatus("after sccp"); |
| //print(dfs(m.entry.to)); |
| |
| } |
| |
| void sccp_cfgopt(Map<Expr, Object> values, Map<Expr,Typeref> types, Set<Edge> reached) |
| { |
| Set<Block> blocks = new TreeSet<Block>(); |
| for (Edge e: reached) |
| blocks.add(e.to); |
| |
| for (Block b: blocks) |
| { |
| // clean up phis |
| for (Expr e: b) |
| { |
| if (e.op == OP_phi) |
| { |
| for (int j=e.pred.length-1; j >= 0; j--) |
| { |
| Edge p = e.pred[j]; |
| if (!reached.contains(p)) |
| e.removePhiInput(j); |
| } |
| } |
| else if (e.succ != null) |
| { |
| if (e.succ.length > 1 && !jumpify(e, reached) && e.op == OP_lookupswitch) |
| { |
| Expr phi; |
| if (b.size()==2 && (phi=b.first()).op == OP_phi && e.args[0]==phi) |
| { |
| // block is switch(phi) |
| // TODO what about a switch with only some branches not taken? |
| for (int i=phi.args.length-1; i >= 0; i--) |
| { |
| Object v = values.get(phi.args[i]); |
| if (v instanceof Number) |
| { |
| int j = intValue(v); |
| Edge in = phi.pred[i]; |
| Edge out = e.succ[j]; |
| copyTarget(out, in); |
| phi.removePhiInput(i); |
| } |
| } |
| } |
| } |
| } |
| else if (e.isOper() && e.onStack()) |
| { |
| boolean pure = true; |
| for (Expr a: e.args) if (!type(types,a).isPrimitive()) pure = false; |
| for (Expr a: e.locals) if (!type(types,a).isPrimitive()) pure = false; |
| if (pure) |
| e.setPure(); |
| constify(e, values.get(e)); |
| } |
| } |
| } |
| } |
| |
| boolean subsume_arg(Expr e, int op, EdgeMap<Expr>uses) |
| { |
| e.op = op; |
| Expr a = e.args[0]; |
| e.flags |= a.flags & (PX|EFFECT); |
| a.setPure(); // so it becomes dead and is removed. |
| uses.get(a).remove(e); |
| e.args = copyOf(a.args, a.args.length); |
| for (Expr x: e.args) |
| uses.get(x).add(e); |
| return true; |
| } |
| |
| boolean canEarlyBindMethod(Method m, Binding b) |
| { |
| return m.abc.mergedAbcs.contains(b.abc); |
| } |
| |
| boolean canEarlyBindSlot(Method m, Binding b) |
| { |
| return b.slot != 0 && m.abc.mergedAbcs.contains(b.abc); |
| } |
| |
| static final Name AS3_TOSTRING = new Name(Name.AS3, "toString"); |
| |
| void sccp_modify(Method m, EdgeMap<Expr> uses, Map<Expr, Object> values, Map<Expr, Typeref> types, |
| Expr e, TreeSet<Expr> work) |
| { |
| sccp_rename(uses, e, e.args); |
| sccp_rename(uses, e, e.locals); |
| boolean changed; |
| do |
| { |
| changed = false; |
| switch (e.op) |
| { |
| case OP_newclass: |
| { |
| // capture scope chain |
| Type c = e.c; |
| c.scopes = copyOf(m.cx.scopes, m.cx.scopes.length+e.scopes.length); |
| int i = m.cx.scopes.length; |
| for (Expr s: e.scopes) |
| c.scopes[i++] = types.get(s); |
| readyType(c); |
| |
| Type t = c.itype; |
| t.scopes = copyOf(c.scopes, c.scopes.length+1); |
| t.scopes[c.scopes.length] = c.ref.nonnull(); |
| readyType(t); |
| break; |
| } |
| case OP_newfunction: |
| { |
| Method f = e.m; |
| // TODO makeIntoPrototypeFunction() |
| Type t = new Type(m.getName(), FUNCTION()); |
| t.scopes = copyOf(m.cx.scopes, m.cx.scopes.length+e.scopes.length); |
| int i = m.cx.scopes.length; |
| for (Expr s: e.scopes) |
| t.scopes[i++] = types.get(s); |
| f.cx = t; |
| readyMethod(f); |
| break; |
| } |
| case OP_returnvalue: |
| if (type(types,e.args[0]) == VOID()) |
| { |
| e.op = OP_returnvoid; |
| e.args = noexprs; |
| } |
| else |
| { |
| Type t0 = type(types,e.args[0]); |
| if (t0.extendsOrIsBase(m.returns.t)) |
| { |
| Expr a0 = e.args[0]; |
| if (m.returns.t == INT() && a0.op == OP_convert_i) |
| { |
| uses.get(a0).remove(e); |
| a0 = e.args[0] = a0.args[0]; |
| uses.get(a0).add(e); |
| } |
| } |
| } |
| break; |
| |
| case OP_getglobalscope: |
| { |
| if (m.cx.scopes.length == 0) |
| { |
| // global is inner scope 0, so copy that |
| makeCopy(e, unwrapScope(e, 0)); |
| changed = true; |
| } |
| break; |
| } |
| |
| case OP_getscopeobject: |
| { |
| makeCopy(e, unwrapScope(e, 0)); |
| changed = true; |
| break; |
| } |
| |
| case OP_istypelate: |
| { |
| Type t1 = type(types,e.args[1]); |
| if (t1.itype != null && TypeCache.instance().containsNamedType(t1.itype)) |
| { |
| e.op = OP_istype; |
| e.ref = t1.itype.getName(); |
| e.args = new Expr[] { e.args[0] }; |
| e.clearPx(); |
| changed = true; |
| } |
| break; |
| } |
| |
| case OP_istype: |
| { |
| if (TypeCache.instance().containsNamedType(e.ref)) |
| e.clearPx(); |
| break; |
| } |
| |
| case OP_pushscope: |
| case OP_pushwith: |
| { |
| if (!types.get(e.args[0]).nullable) |
| e.clearPx(); |
| break; |
| } |
| |
| case OP_coerce: |
| { |
| Expr a0 = e.args[0]; |
| Typeref t = types.get(e); |
| Typeref t0 = types.get(a0); |
| if (t == t0) |
| { |
| // upcast |
| makeCopy(e, a0); |
| changed = true; |
| } |
| else |
| { |
| Object v0 = values.get(a0); |
| if (v0 == NULL() && t0.nullable && t0.t != VOID()) |
| { |
| makeCopy(e, a0); |
| changed = true; |
| } |
| else |
| { |
| if (TypeCache.instance().namedTypes.get(e.ref) == OBJECT()) |
| { |
| e.op = OP_coerce_o; |
| e.clearEffect(); |
| e.ref = null; |
| e.imm = null; |
| changed = true; |
| } |
| } |
| } |
| break; |
| } |
| |
| // turn findprop into either getglobalscope, getscopeobject, or finddef to expose redundancies |
| case OP_findpropstrict: |
| case OP_findproperty: |
| { |
| int i = findInner(e.ref, e.scopes, types); |
| if (i >= 0) |
| { |
| makeCopy(e, unwrapScope(e,i)); |
| for (Expr s: e.scopes) |
| uses.get(s).remove(e); |
| changed = true; |
| } |
| else if ((i = findOuter(e.ref, m.cx.scopes)) >= 0) |
| { |
| if (i == 0) // getglobalscope |
| { |
| e.op = OP_getglobalscope; |
| e.scopes = noexprs; |
| e.setPure(); |
| for (Expr s: e.scopes) |
| uses.get(s).remove(e); |
| changed = true; |
| } |
| else |
| { |
| // can't change the opcode |
| e.setPure(); |
| // TODO - early bind e.ref |
| } |
| } |
| else if (TypeCache.instance().globals.contains(e.ref)) |
| { |
| e.op = OP_finddef; |
| e.flags = flagTable[e.op]; |
| e.scopes = noexprs; |
| e.ref = TypeCache.instance().globals.getName(e.ref); |
| for (Expr s: e.scopes) |
| uses.get(s).remove(e); |
| changed = true; |
| } |
| /* |
| * TODO: This transformation is too simplistic. |
| * |
| else if (e.op == OP_findproperty) |
| { |
| // will return global if nothing found. |
| if (m.cx.scopes.length == 0) |
| { |
| makeCopy(e, unwrapScope(e,0)); |
| for (Expr s: e.scopes) |
| uses.get(s).remove(e); |
| changed = true; |
| } |
| else |
| { |
| e.op = OP_getglobalscope; |
| for (Expr s: e.scopes) |
| uses.get(s).remove(e); |
| e.scopes = noexprs; |
| e.setPure(); |
| changed = true; |
| } |
| } |
| */ |
| break; |
| } |
| |
| // getsuper should look in base of enclosing type. |
| // if either bind to a final getter, use callstatic. |
| |
| case OP_getproperty: |
| case OP_getsuper: |
| { |
| Typeref t0 = types.get(e.args[0]); |
| Binding bind = t0.findGet(e.ref); |
| if (isSlot(bind)) |
| { |
| e.clearEffect(); |
| if (!t0.nullable) |
| e.clearPx(); |
| e.ref = bind.getName(); |
| if (canEarlyBindSlot(m, bind)) |
| { |
| if (!isConst(bind) || !constify(e, values.get(e))) |
| { |
| e.op = OP_getslot; |
| e.imm = new int[] { bind.slot }; |
| changed = true; |
| } |
| } |
| } |
| else if (bind != null) |
| { |
| // narrow the binding |
| e.ref = bind.getName(); |
| } |
| break; |
| } |
| |
| case OP_initproperty: |
| { |
| Type t0 = type(types,e.args[0]); |
| Object v1 = values.get(e.args[1]); |
| Binding bind = t0.find(e.ref); |
| if (bind != null) |
| { |
| // narrow the binding |
| e.ref = bind.getName(); |
| // isSlot() is to aggressive, breaks (at least) zlib |
| if (isConst(bind) && bind.value != null && bind.value.equals(v1)) |
| { |
| // initializing a slot to it's known default value? |
| // TODO ensure no intervening writes to non-const values |
| makeNop(e); |
| for (Expr a: e.args) |
| uses.get(a).remove(e); |
| } |
| else if (isSlot(bind) && canEarlyBindSlot(m, bind)) |
| { |
| e.op = OP_setslot; |
| e.imm = new int[] { bind.slot }; |
| changed = true; |
| } |
| } |
| break; |
| } |
| |
| case OP_setproperty: |
| case OP_setsuper: |
| { |
| Type t0 = type(types,e.args[0]); |
| Binding bind = t0.find(e.ref); |
| if (isSlot(bind)) |
| { |
| e.ref = bind.getName(); |
| if (canEarlyBindSlot(m, bind)) |
| { |
| e.op = OP_setslot; |
| e.imm = new int[] { bind.slot }; |
| changed = true; |
| } |
| } |
| else if (bind != null) |
| { |
| // narrow the binding |
| e.ref = bind.getName(); |
| } |
| break; |
| } |
| |
| case OP_callproperty: |
| case OP_callproplex: |
| case OP_callsuper: |
| case OP_constructprop: |
| case OP_callpropvoid: |
| { |
| // TODO turn callproplex into callproperty if binding is method |
| Type t0 = type(types,e.args[0]); |
| Binding b0 = t0.findGet(e.ref); |
| if (b0 != null) |
| { |
| // narrow the binding |
| e.ref = b0.getName(); |
| if (e.op == OP_callproperty && isMethod(b0)) |
| { |
| if (t0.isPrimitive() && e.args.length==1 && e.ref.equals(AS3_TOSTRING) && type(types,e) == STRING()) |
| { |
| // TODO this may open up further reductions. how to iterate? |
| // or, need to add similar logic in sccp_eval |
| e.op = OP_convert_s; |
| e.setPure(); |
| changed=true; |
| } |
| else if (canEarlyBindMethod(m,b0) && (t0.isFinal() || b0.isFinal())) |
| { |
| e.op = OP_callstatic; |
| e.m = b0.method; |
| changed=true; |
| } |
| else if (USE_CALLMETHOD && canEarlyBindMethod(m,b0)) |
| { |
| e.op = OP_callmethod; |
| e.imm = new int[] { b0.slot }; |
| changed = true; |
| } |
| } |
| else if (e.op == OP_callproperty && isClass(b0)) |
| { |
| changed |= convertify(e, b0, types); |
| } |
| } |
| if (uses.get(e).isEmpty()) |
| { |
| if (e.op == OP_callsuper) |
| e.op = OP_callsupervoid; |
| if (e.op == OP_callproperty) |
| e.op = OP_callpropvoid; |
| } |
| break; |
| } |
| |
| case OP_convert_u: |
| if (e.args[0].op == OP_convert_i) |
| e.args[0] = e.args[0].args[0]; |
| // fall through |
| case OP_coerce_s: |
| case OP_convert_s: |
| case OP_convert_d: |
| case OP_coerce_o: |
| case OP_convert_b: |
| case OP_convert_i: |
| case OP_coerce_a: |
| { |
| Expr a0 = e.args[0]; |
| Type t = type(types,e); |
| Type t0 = type(types,e.args[0]); |
| if (t == t0) |
| { |
| makeCopy(e,e.args[0]); |
| changed=true; |
| } |
| else if (e.op == OP_convert_i) |
| { |
| if (a0.op == OP_negate) |
| { |
| // convert_i(negate(x)) => negate_i(x) |
| e.op = OP_negate_i; |
| e.args = new Expr[] { a0.args[0] }; |
| changed=true; |
| } |
| else if (a0.op == OP_decrement) |
| { |
| e.op = OP_decrement_i; |
| e.args = new Expr[] { a0.args[0] }; |
| changed = true; |
| } |
| } |
| break; |
| } |
| |
| case OP_subtract: |
| { |
| Object v1 = values.get(e.args[1]); |
| if (doubleValue(v1) == 1) |
| { |
| e.args = new Expr[] { e.args[0] }; |
| e.op = OP_decrement; |
| changed = true; |
| } |
| else if (doubleValue(v1) == -1) |
| { |
| e.args = new Expr[] { e.args[0] }; |
| e.op = OP_increment; |
| changed = true; |
| } |
| break; |
| } |
| |
| case OP_add: |
| { |
| Type t = type(types,e); |
| Object v0 = values.get(e.args[0]); |
| Object v1 = values.get(e.args[1]); |
| if (t.numeric) |
| { |
| if (doubleValue(v0)==1) |
| { |
| e.args = new Expr[] { e.args[1] }; |
| e.op = OP_increment; |
| changed = true; |
| } |
| else if (doubleValue(v1)==1) |
| { |
| e.args = new Expr[] { e.args[0] }; |
| e.op = OP_increment; |
| changed = true; |
| } |
| else if (doubleValue(v0) == -1) |
| { |
| e.args = new Expr[] { e.args[1] }; |
| e.op = OP_decrement; |
| changed = true; |
| } |
| else if (doubleValue(v1) == -1) |
| { |
| e.args = new Expr[] { e.args[0] }; |
| e.op = OP_decrement; |
| changed = true; |
| } |
| } |
| else if (t == STRING()) |
| { |
| if ("".equals(v0)) |
| { |
| e.args = new Expr[] { e.args[1] }; |
| e.op = OP_convert_s; |
| changed = true; |
| } |
| else if ("".equals(v1)) |
| { |
| e.args = new Expr[] { e.args[0] }; |
| e.op = OP_convert_s; |
| changed = true; |
| } |
| } |
| } |
| } |
| |
| if (changed) |
| work.addAll(uses.get(e)); |
| } |
| while (changed); |
| } |
| |
| void sccp_rename(EdgeMap<Expr> uses, Expr e, Expr[]args) |
| { |
| |
| addTraceAttr(e); |
| |
| for (int i=args.length-1; i >= 0; i--) |
| { |
| Expr a = args[i]; |
| if (a.op == OP_dup) |
| { |
| uses.get(a).remove(e); |
| a = args[i] = a.locals[0]; |
| traceEntry("renamedLocal"); |
| addTraceAttr(a); |
| uses.get(a).add(e); |
| } |
| } |
| |
| |
| } |
| |
| Map<Expr,Typeref> verify_types(Method m, Deque<Block> code, Map<Block,Block> idom) |
| { |
| EdgeMap<Expr> uses = findUses(code); |
| Map<Expr,Typeref> types = new TreeMap<Expr,Typeref>(); |
| Set<Expr> work = new TreeSet<Expr>(); |
| for (Block b: code) |
| work.addAll(b.exprs); |
| do |
| { |
| Expr e = getExpr(work); |
| if (e.onStack() || e.inLocal() || e.onScope() || e.op==OP_phi) |
| { |
| Typeref tref = verify_eval(m, e, types, idom); |
| if (!tref.equals(types.get(e))) |
| { |
| types.put(e, tref); |
| work.addAll(uses.get(e)); |
| } |
| } |
| } |
| while (!work.isEmpty()); |
| return types; |
| } |
| |
| void sccp_analyze(Method m, EdgeMap<Expr> uses, Map<Expr, Object> values, Map<Expr, Typeref> types, Set<Edge> reached) |
| { |
| |
| addTraceAttr("Method", m); |
| |
| 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)) |
| { |
| |
| addTraceAttr("Expr", e); |
| sccp_eval(m, e, values, types, flowWork, ssaWork, uses); |
| |
| } |
| } |
| } |
| while (!flowWork.isEmpty()); |
| |
| } |
| |
| void insert_casts(Method m) |
| { |
| Deque<Block> code = dfs(m.entry.to); |
| SetMap<Block,Edge> pred = preds(code); |
| Map<Block,Block> idom = idoms(code, pred); |
| m.verifier_types = verify_types(m, code, idom); |
| |
| |
| |
| // TODO this fixes things by inserting upcasts only. We could use |
| // the types we know from sccp_analyze and insert casts that tell |
| // the verifier what we know that it can't figure out on its own. |
| // this can be done expression by expression rather than looking |
| // at phi nodes & splitting critical edges. |
| |
| // it would also be a good sanity check. If the verifier deduces a |
| // more specific type than we have, then we have a bug in either the |
| // verifier or the optimizer. |
| |
| for (Block b: code) |
| { |
| for (Expr e: b) |
| { |
| if (e.op == OP_phi) |
| { |
| Typeref etype = m.verifier_types.get(e); // merged type, possibly mdb of arg types |
| for (int i=e.args.length-1; i >= 0; i--) |
| { |
| Expr a = e.args[i]; |
| Edge p = e.pred[i]; |
| |
| if ( a.onScope() ) |
| continue; |
| |
| Typeref atype = m.verifier_types.get(a); |
| if (isLoop(p, idom) ? !etype.equals(atype) : etype.t.isAtom() != atype.t.isAtom()) |
| { |
| // skip String->String? |
| if((etype.t == atype.t) && etype.nullable) |
| continue; |
| |
| verboseStatus("MISSING CAST " + a + " " + atype+"->"+etype+" on " + p); |
| |
| if (isCritical(p,pred)) |
| { |
| split(p, m, pred); |
| p = e.pred[i]; |
| } |
| // TODO best to insert this right after a? |
| Expr upcast = upcast(a, m, etype.t); |
| append(p, upcast); |
| e.args[i] = upcast; |
| |
| } |
| } |
| } |
| } |
| } |
| |
| verboseStatus("VERIFY TYPES "+m.verifier_types); |
| |
| |
| } |
| |
| Expr upcast(Expr a, Method m, Type t) |
| { |
| if (t == ANY()) |
| { |
| return new Expr(m, OP_coerce_a, a); |
| } |
| else if (t == OBJECT()) |
| { |
| return new Expr(m, OP_coerce_o, a); |
| } |
| |
| return new Expr(m, OP_coerce, t.getName(), a); |
| } |
| |
| /** |
| * 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 |
| */ |
| void sccp_eval(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 = 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 = STRING().ref.nonnull(); |
| break; |
| |
| case OP_newcatch: |
| tref = m.handlers[e.imm[0]].activation; |
| break; |
| |
| case OP_newobject: |
| tref = OBJECT().ref.nonnull(); |
| break; |
| |
| case OP_newarray: |
| tref = 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 = ANY().ref; |
| } |
| break; |
| |
| case OP_newclass: |
| tref = e.c.ref.nonnull(); |
| break; |
| |
| case OP_newfunction: |
| tref = 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 (isSlot(b)) |
| { |
| // 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 (isConst(b) && b.defaultValueChanged()) |
| v = b.value; |
| } |
| else if (isMethod(b)) |
| { |
| // TODO if base type is or might be XML, return ANY |
| // TODO use MethodClosure, more specific than Function |
| tref = FUNCTION().ref.nonnull(); |
| } |
| else if (isGetter(b)) |
| { |
| tref = b.method.returns; |
| } |
| break; |
| } |
| |
| case OP_construct: |
| { |
| tref = 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 (isMethod(b)) |
| { |
| tref = b.method.returns; |
| } |
| else if (isSlot(b) && b.type != null) |
| { |
| // each of these has same logic as convert_i, convert_s, etc |
| if (b.type.t.itype == INT()) |
| { |
| tref = INT().ref; |
| if ( e.args.length > 1) |
| v = eval_convert_i(values.get(e.args[1])); |
| } |
| else if (b.type.t.itype == UINT()) |
| { |
| tref = UINT().ref; |
| if ( e.args.length > 1) |
| v = eval_convert_u(values.get(e.args[1])); |
| } |
| else if (b.type.t.itype == STRING()) |
| { |
| tref = STRING().ref.nonnull(); |
| if ( e.args.length > 1) |
| v = eval_convert_s(values.get(e.args[1])); |
| } |
| else if (b.type.t.itype == BOOLEAN()) |
| { |
| tref = BOOLEAN().ref; |
| if ( e.args.length > 1) |
| v = eval_convert_b(values.get(e.args[1])); |
| } |
| else if (b.type.t.itype == NUMBER()) |
| { |
| tref = 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 = ARRAY().ref.nonnull(); |
| else |
| tref = 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 (isSlot(b)) |
| { |
| // 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 (isConst(b) && b.defaultValueChanged()) |
| v = b.value; |
| } |
| else if (isMethod(b)) |
| { |
| // TODO if base type is or might be XML, return ANY |
| // TODO use MethodClosure, more specific than Function |
| tref = FUNCTION().ref.nonnull(); |
| } |
| else if (isGetter(b)) |
| { |
| tref = b.method.returns; |
| } |
| break; |
| } |
| |
| case OP_pushundefined: |
| v = e.value; |
| tref = VOID().ref; |
| break; |
| |
| case OP_pushnull: |
| v = e.value; |
| tref = NULL().ref; |
| break; |
| |
| case OP_pushtrue: |
| case OP_pushfalse: |
| v = e.value; |
| tref = BOOLEAN().ref; |
| break; |
| |
| case OP_pushbyte: |
| case OP_pushshort: |
| case OP_pushint: |
| v = e.value; |
| tref = INT().ref; |
| break; |
| |
| case OP_pushuint: |
| v = e.value; |
| tref = UINT().ref; |
| break; |
| |
| case OP_pushstring: |
| v = e.value; |
| tref = STRING().ref.nonnull(); |
| break; |
| |
| case OP_pushnan: |
| case OP_pushdouble: |
| v = e.value; |
| tref = NUMBER().ref; |
| break; |
| |
| case OP_pushnamespace: |
| v = e.value; |
| tref = 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 = BOOLEAN().ref; |
| v = eval_convert_b(values.get(e.args[0])); |
| break; |
| |
| case OP_not: |
| { |
| tref = 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 = BOOLEAN().ref; |
| break; |
| |
| case OP_lessthan: |
| case OP_lessequals: |
| case OP_greaterthan: |
| case OP_greaterequals: |
| { |
| tref = 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 = 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(VOID().ref) ) ) |
| { |
| v = values.get(e.args[0]); |
| tref = types.get(e.args[0]); |
| } |
| else |
| { |
| tref = 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 == STRING()) |
| { |
| tref = eval_coerce_s(t0); |
| v = eval_coerce_s(v0); |
| } |
| else if (t == OBJECT()) |
| { |
| tref = eval_coerce_o(t0); |
| v = eval_coerce_o(v0,t0.t); |
| } |
| else if (t == INT()) |
| { |
| tref = t.ref; |
| v = eval_convert_i(v0); |
| } |
| else if (t == UINT()) |
| { |
| tref = t.ref; |
| v = eval_convert_u(v0); |
| } |
| else if (t == NUMBER()) |
| { |
| tref = t.ref; |
| v = eval_convert_d(v0); |
| } |
| else if (t == 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 == NULL() || t0.t == VOID()) |
| { |
| tref = 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 = OBJECT().ref; |
| else |
| tref = t1.t.itype.ref; |
| } |
| else |
| { |
| tref = ANY().ref; |
| } |
| break; |
| } |
| |
| case OP_typeof: |
| { |
| Type t0 = type(types,e.args[0]); |
| if (t0 == INT() || t0 == UINT() || t0 == NUMBER()) |
| v = "number"; |
| else if (t0 == STRING()) |
| v = "string"; |
| else if (t0.extendsOrIsBase(XML()) || t0.extendsOrIsBase(XMLLIST())) |
| v = "xml"; |
| else if (t0 == VOID()) |
| v = "undefined"; |
| else if (t0 == BOOLEAN()) |
| v = "boolean"; |
| else if (t0.extendsOrIsBase(FUNCTION())) |
| v = "function"; |
| else if (t0 != OBJECT() && t0.extendsOrIsBase(OBJECT())) |
| v = "object"; |
| tref = 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 == STRING() && !t0.nullable || t1.t == STRING() && !t1.nullable) |
| { |
| tref = STRING().ref.nonnull(); |
| if (v0 != BOTTOM && v1 != BOTTOM) |
| v = stringValue(v0) + stringValue(v1); |
| } |
| else if (t0.t.numeric && t1.t.numeric) |
| { |
| tref = 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 = OBJECT().ref.nonnull(); // will be a String or a Number |
| } |
| break; |
| } |
| |
| case OP_divide: |
| { |
| tref = 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 = NUMBER().ref; |
| break; |
| |
| case OP_convert_d: |
| tref = NUMBER().ref; |
| v = eval_convert_d(values.get(e.args[0])); |
| break; |
| |
| case OP_convert_i: |
| tref = INT().ref; |
| v = eval_convert_i(values.get(e.args[0])); |
| break; |
| |
| case OP_convert_u: |
| tref = UINT().ref; |
| v = eval_convert_u(values.get(e.args[0])); |
| break; |
| |
| case OP_bitor: |
| { |
| tref = 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 = 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 = INT().ref; |
| break; |
| |
| case OP_urshift: |
| // TODO constant folding |
| tref = 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 == VOID()) |
| v = UNDEFINED; |
| else if (tref.t == NULL()) |
| v = 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)); |
| } |
| } |
| |
| Typeref verify_eval(Method m, Expr e, |
| Map<Expr,Typeref> types, Map<Block,Block> idom) |
| { |
| Typeref tref = null; |
| |
| if (e.op == OP_phi) |
| { |
| boolean loop = false; |
| for (int i=e.args.length-1; i >= 0; i--) |
| { |
| Expr a = e.args[i]; |
| loop |= isLoop(e.pred[i],idom); |
| // 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. |
| |
| // 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 (aref == null) |
| continue; // ignore unprocessed inputs |
| if (tref == null) |
| tref = aref; |
| else if (!tref.equals(aref)) |
| tref = mdb(tref,aref); |
| } |
| |
| if ( null == tref ) |
| { |
| tref = ANY().ref; |
| } |
| // make nullable types nullable at top of loop so verifier |
| // won't ever need to iterate. |
| if (loop) |
| tref = tref.t.ref; |
| } |
| else |
| { |
| tref = ANY().ref; |
| |
| // if any arg is TOP result is TOP (unchanged) |
| for (Expr a : e.args) if (!types.containsKey(a)) return tref; |
| for (Expr a : e.scopes) if (!types.containsKey(a)) return tref; |
| for (Expr a : e.locals) if (!types.containsKey(a)) return tref; |
| |
| switch (e.op) |
| { |
| default: |
| assert(false); |
| |
| case OP_hasnext2_o: |
| case OP_nextname: |
| case OP_nextvalue: |
| case OP_call: |
| case OP_getsuper: |
| case OP_getdescendants: |
| tref = ANY().ref; |
| break; |
| |
| case OP_convert_o: |
| // bug in verifier -- doesn't set state nonnull here. |
| tref = types.get(e.args[0]); |
| //tref = types.get(e.args[0]).nonnull(); |
| break; |
| |
| case OP_esc_xattr: |
| case OP_esc_xelem: |
| tref = STRING().ref.nonnull(); |
| break; |
| |
| case OP_newcatch: |
| tref = m.handlers[e.imm[0]].activation; |
| break; |
| |
| case OP_newobject: |
| tref = OBJECT().ref.nonnull(); |
| break; |
| |
| case OP_newarray: |
| tref = 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> |
| tref = types.get(e.scopes[0].args[0]); |
| } |
| break; |
| |
| case OP_getscopeobject: |
| tref = types.get(e.scopes[0].args[0]); |
| break; |
| |
| case OP_newclass: |
| tref = e.c.ref.nonnull(); |
| break; |
| |
| case OP_newfunction: |
| tref = 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) |
| { |
| 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 |
| { |
| 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); |
| tref = verify_eval_getproperty(tref, b); |
| break; |
| } |
| |
| case OP_construct: |
| { |
| Type base_type = type(types,e.args[0]); |
| |
| if ( base_type.itype != null ) |
| tref = base_type.itype.ref.nonnull(); |
| else |
| tref = base_type.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 (isMethod(b)) |
| { |
| tref = b.method.returns; |
| } |
| else if (isSlot(b) && b.type != null && b.type.t.itype != null) |
| { |
| // calling a class as a function, return type is cast to instance type |
| // issue what about special cases? RegExp? Date? |
| tref = b.type.t.itype.ref; |
| } |
| break; |
| } |
| |
| case OP_callsuper: |
| { |
| Type ot = m.cx.base; |
| Binding b = ot.findGet(e.ref); |
| if (isMethod(b)) |
| { |
| tref = b.method.returns; |
| } |
| 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 = ARRAY().ref.nonnull(); |
| else |
| tref = 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); |
| tref = verify_eval_getproperty(tref, b); |
| break; |
| } |
| |
| case OP_kill: |
| tref = ANY().ref; |
| break; |
| |
| case OP_pushundefined: |
| tref = VOID().ref; |
| break; |
| |
| case OP_pushnull: |
| tref = NULL().ref; |
| break; |
| |
| case OP_pushnamespace: |
| tref = NAMESPACE().ref.nonnull(); |
| break; |
| |
| case OP_pushscope: |
| case OP_pushwith: |
| // treat this as a copy. |
| tref = types.get(e.args[0]).nonnull(); |
| break; |
| |
| case OP_pushtrue: |
| case OP_pushfalse: |
| case OP_convert_b: |
| case OP_not: |
| case OP_deleteproperty: |
| 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: |
| case OP_lessthan: |
| case OP_lessequals: |
| case OP_greaterthan: |
| case OP_greaterequals: |
| tref = BOOLEAN().ref; |
| break; |
| |
| case OP_convert_s: |
| case OP_pushstring: |
| tref = STRING().ref.nonnull(); |
| break; |
| |
| case OP_coerce_s: |
| { |
| Typeref t0 = types.get(e.args[0]); |
| tref = new Typeref(STRING(), t0.nullable); |
| break; |
| } |
| |
| case OP_coerce_o: |
| { |
| Typeref t0 = types.get(e.args[0]); |
| tref = new Typeref(OBJECT(), t0.nullable); |
| break; |
| } |
| |
| case OP_coerce_a: |
| { |
| Typeref t0 = types.get(e.args[0]); |
| tref = new Typeref(ANY(), t0.nullable); |
| break; |
| } |
| |
| case OP_coerce: |
| tref = TypeCache.instance().namedTypes.get(e.ref).ref; |
| break; |
| |
| case OP_astype: |
| { |
| Typeref t0 = types.get(e.args[0]); |
| Type t = TypeCache.instance().namedTypes.get(e.ref); |
| if (!t0.t.extendsOrIsBase(t) || t0.t.isAtom() != t.isAtom()) |
| { |
| // TODO figure out what verifier is really doing here. |
| tref = t.ref; |
| } |
| else |
| { |
| tref = t0; |
| } |
| 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 = OBJECT().ref; |
| else |
| tref = t1.t.itype.ref; |
| } |
| else |
| { |
| tref = ANY().ref; |
| } |
| break; |
| } |
| |
| case OP_typeof: |
| tref = 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); |
| if (t0.t == STRING() && !t0.nullable || t1.t == STRING() && !t1.nullable) |
| { |
| tref = STRING().ref.nonnull(); |
| } |
| else if (t0.t.numeric && t1.t.numeric) |
| { |
| tref = NUMBER().ref; |
| } |
| else |
| { |
| // TODO make all primitives extend a type so we can use that type here. |
| tref = OBJECT().ref.nonnull(); // will be a String or a Number |
| } |
| break; |
| } |
| |
| case OP_divide: |
| case OP_subtract: |
| case OP_multiply: |
| case OP_modulo: |
| case OP_negate: |
| case OP_increment: |
| case OP_decrement: |
| case OP_convert_d: |
| case OP_pushnan: |
| case OP_pushdouble: |
| tref = NUMBER().ref; |
| break; |
| |
| case OP_convert_i: |
| case OP_bitor: |
| case OP_bitand: |
| 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: |
| case OP_pushbyte: |
| case OP_pushshort: |
| case OP_pushint: |
| case OP_bitnot: |
| tref = INT().ref; |
| break; |
| |
| case OP_pushuint: |
| case OP_convert_u: |
| case OP_urshift: |
| tref = UINT().ref; |
| break; |
| } |
| } |
| |
| assert(tref != null && tref.t != null); |
| return tref; |
| } |
| |
| private Typeref verify_eval_getproperty(Typeref tref, Binding b) |
| { |
| if (isSlot(b)) |
| { |
| tref = b.type; |
| } |
| else if (isMethod(b)) |
| { |
| //tref = FUNCTION().ref.nonnull(); // TODO use MethodClosure |
| // avmplus uses ANY here. see Verifier::readBinding(). |
| tref = ANY().ref; |
| } |
| else if (isGetter(b)) |
| { |
| tref = b.method.returns; |
| } |
| return tref; |
| } |
| |
| boolean isCritical(Edge e, SetMap<Block,Edge>pred) |
| { |
| return e.from.succ().length > 1 && pred.get(e.to).size() > 1; |
| } |
| |
| /** |
| * |
| * @param e - the edge to split. |
| * @post e points at the new block. |
| * @param m - the containing Method. |
| * @param pred - predecessors. Not entirely relevant... |
| * @return the Edge from the new block to e's original "to." |
| */ |
| Edge split(Edge e, Method m, SetMap<Block,Edge>pred) |
| { |
| assert(e.handler == null); // can't split exception edges |
| Expr j = new Expr(m, OP_jump); |
| Block d = new Block(m); |
| Block to = e.to; |
| Edge e2 = new Edge(m,d,0,to); |
| j.succ = new Edge[] { e2 }; |
| d.add(j); |
| verboseStatus("SPLIT "+e + " ... " + d + ", " + e2); |
| e.to = d; |
| pred.get(d).add(e); |
| pred.get(to).remove(e); |
| pred.get(to).add(e2); |
| replacePred(to,e,e2); |
| return e2; |
| } |
| |
| void replacePred(Block b, Edge before, Edge after) |
| { |
| for (Expr e: b) |
| { |
| if (e.op == OP_phi) |
| { |
| for (int i=0, n=e.pred.length; i < n; i++) |
| if (e.pred[i] == before) |
| e.pred[i] = after; |
| } |
| } |
| } |
| |
| Expr append(Edge edge, Expr e) |
| { |
| edge.from.appendExpr(e); |
| return e; |
| } |
| Expr prepend(Edge edge, Expr e) |
| { |
| Deque<Expr> exprs = edge.from.exprs; |
| exprs.addFirst(e); |
| return e; |
| } |
| |
| Expr setlocal(Method m, int i, Expr a) |
| { |
| return new Expr(m, OP_setlocal, i, new Expr[] { a }, 1, 1); |
| } |
| |
| Expr getlocal(Method m, int i) |
| { |
| return new Expr(m, OP_getlocal, i); |
| } |
| |
| Expr dup(Method m, Expr e) |
| { |
| Expr dup = new Expr(m, OP_dup); |
| dup.locals = new Expr[] { e }; |
| return dup; |
| } |
| |
| /** |
| * the last phase before emitting code. |
| * - stack scheduling |
| * - scope scheduling |
| * - assign local variables |
| * - replace phi's with copies if necessary |
| * - recompute frame size |
| * @param code |
| */ |
| void remove_phi(Method m) |
| { |
| Deque<Block> code = dfs(m.entry.to); |
| SetMap<Block,Edge> pred = preds(code); |
| |
| Map<Integer,Integer> locals = new TreeMap<Integer,Integer>(); |
| Map<Block,Deque<Expr>> exprs = new TreeMap<Block,Deque<Expr>>(); |
| ConflictGraph conflicts = new ConflictGraph(); |
| |
| printMethod(m, "BEFORE SCHED"); |
| |
| |
| addTraceAttr(m); |
| |
| restused: |
| if (m.needsArguments() || m.needsRest()) |
| { |
| // if we have a rest param or arguments and we don't use it, |
| // clear those flags and set the ignore-extra-args flag. |
| int rest = m.getParams().length; |
| for (Expr e: m.entry.to) |
| if (e.op == OP_arg && e.imm[0] == rest) |
| break restused; |
| m.flags &= ~(METHOD_Arguments|METHOD_Needrest); |
| m.flags |= METHOD_IgnoreRest; |
| verboseStatus("IGNORE_REST for "+m.getName()); |
| } |
| |
| int max_local = m.getParams().length-1; |
| |
| sched_greedy(m, code, locals, pred, exprs, conflicts); |
| |
| alloc_locals(code, locals, conflicts, m.fixedLocals); |
| |
| |
| // insert phi copies on edges where needed |
| Set<Edge> splits = new TreeSet<Edge>(); |
| for (Block b: code) |
| { |
| // identify the edges that need copies, split them, add copies. |
| // only split an edge once during this whole pass. |
| for (Expr e: b) |
| { |
| if (e.op != OP_phi) |
| break; |
| if (!locals.containsKey(e.id)) |
| continue; |
| |
| addTraceAttr(e); |
| int lhs = locals.get(e.id); |
| for (int i=e.args.length-1; i>=0; i--) |
| { |
| traceEntry("PhiInput"); |
| addTraceAttr("i", i); |
| addTraceAttr(e.args[i]); |
| int rhs = locals.get(e.args[i].id); |
| if (lhs != rhs) |
| { |
| Edge p = e.pred[i]; |
| if (!splits.contains(p)) |
| { |
| split(p,m,pred); |
| splits.add(p = e.pred[i]); |
| } |
| traceEntry("copyPhiInput"); |
| addTraceAttr("lhs", lhs); |
| addTraceAttr("rhs", rhs); |
| Expr get = getlocal(m, rhs); |
| prepend(p, get); |
| append(p, setlocal(m,lhs,get)); |
| } |
| } |
| |
| } |
| |
| // now swap in the scheduled code and renumber the get/setlocals we have in there. |
| b.exprs = exprs.get(b); |
| |
| for ( Expr e: b.exprs) |
| { |
| // assign locals & update local_count |
| int loc = max_local; |
| if (e.op == OP_getlocal || e.op == OP_setlocal) |
| { |
| loc = e.imm[0] = locals.get(e.imm[0]); |
| } |
| else if (e.op == OP_hasnext2) |
| { |
| int loc0 = locals.get(e.locals[0].id); |
| int loc1 = locals.get(e.locals[1].id); |
| e.imm = new int[] { loc0, loc1 }; |
| loc = loc0 > loc1 ? loc0 : loc1; |
| } |
| if (loc > max_local) |
| max_local = loc; |
| } |
| } |
| |
| m.local_count = max_local+1; |
| |
| // some of the edges we split didn't need to be. |
| cfgopt(m); |
| |
| printMethod(m, "AFTER SCHED"); |
| |
| |
| } |
| |
| /** |
| * Compute a Method's max_stack and max_scope settings. |
| * @param m - the Method of interest. |
| * @post m.max_stack and m_max_scope set. |
| * @note Factored out of remove_phi so that the verifier |
| * appeasment logic can safely issue getlocal insns. |
| */ |
| void computeFrameCounts(Method m) |
| { |
| Deque<Block> code = dfs(m.entry.to); |
| |
| |
| int max_stack = 0; |
| int max_scope = 0; |
| |
| Map<Block,Integer>stkin = new TreeMap<Block,Integer>(); |
| Map<Block,Integer>scpin = new TreeMap<Block,Integer>(); |
| stkin.put(m.entry.to, 0); |
| scpin.put(m.entry.to, 0); |
| |
| for ( Block b: code ) |
| { |
| int stkdepth = stkin.get(b); |
| int scpdepth = scpin.get(b); |
| |
| for (Expr e: b) |
| { |
| assert(!e.isSynthetic()); |
| |
| // compute max_stack |
| assert(stkdepth >= e.args.length); |
| stkdepth -= e.args.length; |
| if (e.onStack()) |
| { |
| stkdepth++; |
| } |
| if (stkdepth > max_stack) |
| max_stack = stkdepth; |
| |
| // compute max_scope |
| assert(scpdepth >= e.scopes.length); |
| if (e.op == OP_popscope) |
| scpdepth--; |
| else if (e.onScope()) |
| scpdepth++; |
| if (scpdepth > max_scope) |
| max_scope = scpdepth; |
| } |
| for (Edge s: b.succ()) |
| update_depth(s.to, stkdepth, stkin, scpdepth, scpin); |
| for (Edge s: b.xsucc) |
| update_depth(s.to, 1, stkin, 0, scpin); |
| } |
| |
| m.max_stack = max_stack; |
| m.max_scope = max_scope; |
| } |
| |
| /** |
| * Work around legacy verifier restrictions: |
| * Kill predecessor blocks' locals if they're live out |
| * on more than one path and not live in to a block. |
| * @param m - the Method under analysis. |
| */ |
| void appeaseLegacyVerifier(Method m) |
| { |
| |
| addTraceAttr(m); |
| |
| Deque<Block> code = schedule(m.entry.to); |
| SetMap<Block, Edge> pred = preds(code); |
| Set<Edge> single_path_to_exit = new HashSet<Edge>(); |
| |
| Map<Block,LocalVarState> reg_state_by_block = getLocalVarState(m); |
| |
| TypeConstraintMap constraints = new TypeConstraintMap(); |
| |
| // Create type constraints: |
| // Find edges where locals' types |
| // conflict with the expected type, which |
| // may be the consensus type for the local |
| // in the block or may be specific to the |
| // block. Apply coercions as necessary. |
| for ( Block b: code) |
| { |
| verboseStatus("Building constraints for " + b); |
| |
| LocalVarState block_state = reg_state_by_block.get(b); |
| assert (block_state != null); |
| |
| BitSet active = block_state.getActiveVariables(); |
| |
| // Kill active variables that aren't in a successor |
| // block's live-in set. |
| for ( Edge s:b.succ()) |
| { |
| if ( singlePathToExit(s, pred, single_path_to_exit)) |
| { |
| // No possibliity of conflict. |
| continue; |
| } |
| |
| Block successor = s.to; |
| |
| LocalVarState to_state = reg_state_by_block.get(successor); |
| assert ( to_state != null); |
| |
| TypeConstraints tc = constraints.getConstraints(s); |
| BitSet to_livein = to_state.getLivein(); |
| |
| for ( Integer r : foreach(active) ) |
| { |
| if ( !( to_livein.get(r) || m.fixedLocals.values().contains(r) ) ) |
| { |
| // If the target block redefines this variable, |
| // and all incoming types agree, then the kill |
| // is redundant. |
| if ( to_state.def.get(r) || to_state.killed_vars.get(r) ) |
| { |
| Typeref first_incoming_type = to_state.getInitialType(r); |
| |
| for ( Edge p: pred.get(successor) ) |
| { |
| boolean needs_kill; |
| |
| // TODO: It should be possible to extend this |
| // "zone of protection" to more than one |
| // predecessor block, but the verifier rejects it. |
| needs_kill = pred.get(successor).size() > 1; |
| |
| /* |
| Typeref pred_incoming_type = reg_state_by_block.get(p.from).getFinalType(r); |
| |
| if ( successor.is_backwards_branch_target ) |
| needs_kill = !first_incoming_type.equals(pred_incoming_type); |
| else |
| needs_kill = !first_incoming_type.t.isMachineCompatible(pred_incoming_type.t); |
| |
| // TODO: Need better analysis of when ANY means ANY and when it means "low." |
| needs_kill |= pred_incoming_type.t.equals(ANY); |
| */ |
| |
| if ( needs_kill ) |
| { |
| // TODO: These can be largely eliminated |
| // when the verifier gets smarter. |
| tc.addKill(r); |
| break; |
| } |
| |
| } |
| } |
| else |
| { |
| tc.addKill(r); |
| } |
| } |
| } |
| } |
| |
| // Analyze the types of live-in variables; form an |
| // objective input type, then coerce as necessary |
| // to get to that type. |
| BitSet livein = block_state.getLivein(); |
| |
| for ( Integer r : foreach(livein) ) |
| { |
| Typeref consensus_type = block_state.getInitialType(r); |
| assert(consensus_type != null); |
| |
| for ( Edge p: pred.get(b)) |
| { |
| LocalVarState from_state = reg_state_by_block.get(p.from); |
| assert( from_state != null); |
| |
| consensus_type = typeMeet(consensus_type, from_state.getFinalType(r)); |
| } |
| |
| for ( Edge p: pred.get(b)) |
| { |
| LocalVarState from_state = reg_state_by_block.get(p.from); |
| assert( from_state != null); |
| |
| TypeConstraints tc = constraints.getConstraints(p); |
| |
| if ( block_state.hard_coercions[r] != null ) |
| { |
| tc.addCoercion(r, block_state.hard_coercions[r]); |
| } |
| else if ( needsCoercion(m, consensus_type, from_state.getFinalType(r), b.is_backwards_branch_target )) |
| { |
| tc.addCoercion(r, consensus_type); |
| } |
| } |
| } |
| } |
| |
| // Find kills and coercions that can be done in the |
| // in the source block instead of the target blocks. |
| // TODO: Reach back through exblocks? |
| for ( Block b: code ) |
| { |
| LocalVarState block_state = reg_state_by_block.get(b); |
| assert (block_state != null); |
| |
| BitSet active = block_state.getActiveVariables(); |
| |
| TypeConstraints source_block_constraints = null; |
| |
| for ( Integer r: foreach(active)) |
| { |
| TypeConstraints first_constraint = null; |
| boolean all_constraints_agree = true; |
| |
| for ( Edge s:b.succ() ) |
| { |
| TypeConstraints tc = constraints.getConstraints(s); |
| if ( null == first_constraint ) |
| { |
| first_constraint = tc; |
| } |
| |
| // If the active register isn't in the constraint set, |
| // then this will be false, and thus it's necessary to |
| // ask the first constraint if it agrees with itself. |
| all_constraints_agree = first_constraint.agreesWith(tc, r); |
| if ( !all_constraints_agree ) |
| break; |
| } |
| |
| if ( all_constraints_agree ) |
| { |
| if ( null == source_block_constraints ) |
| source_block_constraints = new TypeConstraints(null); |
| |
| for ( Edge s: b.succ()) |
| { |
| source_block_constraints.takeConstraintFrom(constraints.get(s), r); |
| } |
| } |
| } |
| |
| if ( source_block_constraints != null ) |
| fixConstraints(m, b, source_block_constraints, block_state); |
| } |
| |
| // Fix any remaining constraints by |
| // splitting the affected edge and applying |
| // constraints to the new block. |
| // TODO: These blocks should be amenable to pooling. |
| for ( Block b: code ) |
| { |
| for ( Edge p:b.succ() ) |
| { |
| // Split the liveout block, and apply |
| // the constraints to the split block. |
| TypeConstraints tc = constraints.getConstraints(p); |
| |
| if ( tc != null && tc.killregs.size() > 0 || tc.coercions.size() > 0 ) |
| { |
| split(p, m, pred); |
| p.to.must_isolate_block = true; |
| fixConstraints(m, p.to, tc, null); |
| } |
| } |
| } |
| |
| |
| } |
| |
| boolean singlePathToExit(Edge s, SetMap<Block, Edge> pred, Set<Edge> known_single_paths) |
| { |
| Block b = s.to; |
| |
| if ( pred.get(b).size() > 1 ) |
| return false; |
| else if ( known_single_paths.contains(s) ) |
| return true; |
| |
| boolean single_path = false; // not a control value, compiler appeasement |
| |
| if ( b.succ() == noedges ) |
| single_path = true; |
| else |
| { |
| for ( Edge s_prime: b.succ() ) |
| { |
| single_path = singlePathToExit(s_prime, pred, known_single_paths); |
| if ( !single_path ) |
| break; |
| } |
| } |
| |
| if ( single_path ) |
| { |
| known_single_paths.add(s); |
| } |
| return single_path; |
| } |
| |
| boolean needsCoercion(Method m, Typeref to_ty, Typeref from_ty, boolean backwards_branch) |
| { |
| if ( to_ty.equals(from_ty) || ignoreTypeConflict(m, to_ty, from_ty) ) |
| return false; |
| |
| if ( isNumericType(from_ty) && isNumericType(to_ty)) |
| return false; |
| |
| boolean needs_coercion; |
| |
| Typeref merged_type = typeMerge(to_ty, from_ty); |
| |
| if ( null == merged_type ) |
| { |
| // No sensible meet of these types, |
| // coercion necessary. |
| needs_coercion = true; |
| } |
| else if ( backwards_branch ) |
| { |
| // FIXME: The type modeling in LocalVarState |
| // needs to account for cross-block nullable changes. |
| needs_coercion = true; |
| //needs_coercion = ! from_ty.equals(to_ty); |
| } |
| else |
| { |
| needs_coercion = !to_ty.t.isMachineCompatible(merged_type.t); |
| } |
| |
| return needs_coercion; |
| |
| } |
| |
| private boolean isNumericType(Typeref ty) |
| { |
| return ty.t.numeric; |
| } |
| |
| /** |
| * Ignore type conflicts that are probably due to other bugs. |
| * @param m |
| * @param liveinType - the suspect type. |
| * @param from_ty |
| * @return true if the type appears to be bogus. |
| * @warn this routine is bogus. |
| */ |
| boolean ignoreTypeConflict(Method m, Typeref to_ty, Typeref from_ty) |
| { |
| boolean result = to_ty.t.getName().name.startsWith("global") && from_ty.t.getName().name.startsWith("global"); |
| result |= from_ty.t.equals(m.activation.t) && to_ty.t.equals(m.activation.t); |
| return result; |
| } |
| |
| |
| |
| |
| /** |
| * Fix type constraints for a block. |
| * @param m - the Method that contains the Block. |
| * @param b - the Block to be fixed. |
| * @param bc - the Block's constraints. |
| * @param block_state |
| */ |
| void fixConstraints(Method m, Block b, TypeConstraints bc, LocalVarState block_state) |
| { |
| verboseStatus("fixConstraints " + b ); |
| |
| // A fair number of expressions can be coerced |
| // when they're defined. |
| Map<Expr,Expr> coerce_in_place = new HashMap<Expr,Expr>(); |
| |
| if ( block_state != null ) |
| { |
| Set<Integer> coerced_locals = new HashSet<Integer>(bc.coercions.keySet()); |
| |
| for ( Integer r: coerced_locals ) |
| { |
| if ( ! block_state.read_after_def.get(r)) |
| { |
| Expr generator = block_state.generating_exprs.get(r); |
| |
| if ( generator != null ) |
| { |
| coerce_in_place.put(generator, coerceExpr(m, bc.coercions.get(r).t, generator)); |
| bc.coercions.remove(r); |
| } |
| } |
| } |
| |
| if ( !coerce_in_place.isEmpty() ) |
| { |
| Deque<Expr> replaced_exprs = new ArrayDeque<Expr>(); |
| |
| while ( !b.exprs.isEmpty()) |
| { |
| Expr ex = b.exprs.removeFirst(); |
| |
| if ( coerce_in_place.containsKey(ex)) |
| { |
| replaced_exprs.add(coerce_in_place.get(ex)); |
| coerce_in_place.remove(ex); |
| } |
| |
| replaced_exprs.add(ex); |
| } |
| |
| assert(coerce_in_place.isEmpty()); |
| b.exprs = replaced_exprs; |
| } |
| } |
| |
| Expr last = b.succ().length > 0? b.exprs.removeLast(): null; |
| |
| for ( Integer regnum: bc.coercions.keySet() ) |
| { |
| Typeref ty = bc.coercions.get(regnum); |
| |
| if ( ty.equals(VOID().ref) ) |
| { |
| Expr void_expr = new Expr(m, OP_pushundefined); |
| b.exprs.add(void_expr); |
| b.exprs.add(setlocal(m, regnum, void_expr)); |
| } |
| if ( ty.equals(NULL().ref) ) |
| { |
| Expr void_expr = new Expr(m, OP_pushnull); |
| b.exprs.add(void_expr); |
| b.exprs.add(setlocal(m, regnum, void_expr)); |
| } |
| else |
| { |
| Expr getlocal = getlocal(m, regnum); |
| b.exprs.add(getlocal); |
| |
| Expr coerce_expr = coerceExpr(m, ty.t, getlocal); |
| b.exprs.add(coerce_expr); |
| b.exprs.add(setlocal(m, regnum, coerce_expr)); |
| } |
| verboseStatus("\tlocal " + regnum); |
| |
| } |
| |
| for ( Integer k: bc.killregs ) |
| { |
| b.exprs.add(new Expr(m, OP_kill, k.intValue())); |
| verboseStatus("kill " + k); |
| } |
| |
| if ( last != null ) |
| b.exprs.add(last); |
| } |
| |
| Expr coerceExpr(Method m, Type t, Expr a) |
| { |
| Expr result = null; |
| |
| assert(t != null); |
| |
| if ( ANY().equals(t)) |
| result = new Expr(m, OP_coerce_a, a); |
| else if (VOID().equals(t) ) |
| result = new Expr(m, OP_pushundefined); |
| else if ( NULL().equals(t) ) |
| result = new Expr(m, OP_pushnull); |
| else if ( INT().equals(t)) |
| result = new Expr(m, OP_convert_i, a); |
| else if ( OBJECT().equals(t)) |
| result = new Expr(m, OP_coerce_o, a); |
| else if ( STRING().equals(t)) |
| result = new Expr(m, OP_coerce_s, a); |
| else |
| { |
| result = new Expr(m, OP_coerce, t.getName(), a); |
| } |
| |
| verboseStatus("coerceExpr " + formatExpr(result)); |
| |
| return result; |
| } |
| |
| void alloc_locals(Deque<Block> code, Map<Integer, Integer> locals, ConflictGraph conflicts, Map<Expr, Integer> fixed_locals) |
| { |
| for (Block b: code) |
| { |
| for (Expr e: b) |
| { |
| if (locals.containsKey(e.id)) |
| { |
| alloc1(e,conflicts,locals); |
| |
| if ( fixed_locals.containsKey(e) && locals.get(e.id) != -1) |
| fixed_locals.put(e, locals.get(e.id)); |
| } |
| } |
| } |
| |
| for (Block b: code) |
| { |
| for (Expr e: b) |
| { |
| if (locals.containsKey(e.id)) |
| { |
| alloc2(e,conflicts,locals); |
| |
| if ( fixed_locals.containsKey(e) && locals.get(e.id) != -1) |
| fixed_locals.put(e, locals.get(e.id)); |
| } |
| } |
| } |
| |
| verboseStatus("CONFLICTS " + conflicts); |
| verboseStatus("LOCALS "+locals); |
| } |
| |
| void update_depth(Block b, int stkdepth, Map<Block,Integer> stkin, int scpdepth, Map<Block,Integer> scpin) |
| { |
| if (stkin.containsKey(b)) |
| assert(stkin.get(b) == stkdepth); |
| else |
| stkin.put(b,stkdepth); |
| |
| if (scpin.containsKey(b)) |
| {}// assert(scpin.get(b) == scpdepth); |
| else |
| scpin.put(b,scpdepth); |
| } |
| |
| void allocate(int id, int loc, Map<Integer,Integer>locals, ConflictGraph conflicts) |
| { |
| traceEntry("allocate"); |
| addTraceAttr("id", id); |
| addTraceAttr("loc", loc); |
| assert(locals.get(id) == -1 && loc != -1); |
| locals.put(id, loc); |
| |
| // sanity check |
| for (int j: conflicts.get(id)) |
| assert(locals.get(j) != loc); |
| } |
| |
| void alloc1(Expr e, ConflictGraph conflicts, Map<Integer,Integer> locals) |
| { |
| if (locals.get(e.id) != -1) |
| return; |
| |
| BitSet used = new BitSet(); |
| for (int i: conflicts.get(e.id)) |
| if (locals.get(i) != -1) |
| used.set(locals.get(i)); |
| |
| int loc = -1; |
| // If the expression's locked to a specific local (e.g., it's an arg), it must use that local. |
| if (e.locals.length == 1 && e.inLocal() && locals.containsKey(e.locals[0].id) && (loc=locals.get(e.locals[0].id)) != -1) |
| { |
| assert(!used.get(loc)); |
| allocate(e.id, loc, locals, conflicts); |
| } |
| |
| if (e.op != OP_phi) |
| return; |
| |
| // If any phi input is already allocated, and the local's available, use that slot. |
| for (Expr a: e.args) |
| { |
| if (locals.containsKey(a.id) && (loc=locals.get(a.id)) != -1 && !used.get(loc)) |
| { |
| allocate(e.id, loc, locals, conflicts); |
| break; |
| } |
| } |
| |
| if (loc == -1) |
| { |
| // No previous allocation found; allocate lowest-numbered free local. |
| loc = 0; |
| while (used.get(loc)) |
| loc++; |
| allocate(e.id, loc, locals, conflicts); |
| } |
| |
| // try to assign all inputs to the same local to avoid copies |
| nextarg: |
| for (Expr a: e.args) |
| { |
| if (locals.get(a.id) == -1) |
| { |
| for (int j: conflicts.get(a.id)) |
| if (locals.get(j) == loc) |
| continue nextarg; |
| allocate(a.id, loc, locals, conflicts); |
| } |
| } |
| } |
| |
| void alloc2(Expr e, ConflictGraph conflicts, Map<Integer,Integer> locals) |
| { |
| if (locals.get(e.id) != -1) |
| return; |
| |
| BitSet used = new BitSet(); |
| for (int i: conflicts.get(e.id)) |
| if (locals.get(i) != -1) |
| used.set(locals.get(i)); |
| |
| |
| // Ensure that nothing leaked out of alloc1. |
| assert ( ! (e.locals.length == 1 && e.inLocal() && locals.containsKey(e.locals[0].id) && (locals.get(e.locals[0].id)) != -1) ); |
| |
| int loc; |
| |
| // If an arg has a register allocated, try to re-use it. |
| if (e.args.length != 0 && locals.containsKey(e.args[0].id) && (loc=locals.get(e.args[0].id)) != -1 && |
| !used.get(loc)) |
| { |
| addTraceAttr("MayUseLoc", loc); |
| allocate(e.id, loc, locals, conflicts); |
| return; |
| } |
| |
| // otherwise assign the lowest numbered free local |
| loc = 0; |
| while (used.get(loc)) |
| loc++; |
| allocate(e.id, loc, locals, conflicts); |
| } |
| |
| Map<Block,LocalVarState> getLocalVarState(Method m) |
| { |
| Map<Block,LocalVarState> result = new TreeMap<Block,LocalVarState>(); |
| Map<Block, Typeref[]> frames_by_block = new TreeMap<Block, Typeref[]>(); |
| |
| Deque<Block> code = schedule(m.entry.to); |
| |
| // Verifier's initial frame state: |
| // Parameters have known types, |
| // locals are type ANY. |
| Typeref[] frame_state = new Typeref[m.local_count]; |
| |
| System.arraycopy(m.getParams(), 0, frame_state, 0, m.getParams().length); |
| |
| for (int i = m.getParams().length; i < frame_state.length; i++) |
| frame_state[i] = ANY().ref; |
| |
| verboseStatus("FRAME_STATE"); |
| |
| for ( Block b: code ) |
| { |
| if ( frames_by_block.containsKey(b)) |
| frame_state = frames_by_block.get(b); |
| |
| frame_state = buildLocalState(m, b, frame_state, result, frames_by_block); |
| } |
| |
| // Compute liveout sets. |
| computeLiveout(code, result); |
| |
| return result; |
| } |
| |
| Typeref[] buildLocalState(Method m, Block b, Typeref[] frame_state, Map<Block,LocalVarState> local_map, Map<Block, Typeref[]> states_by_block) |
| { |
| LocalVarState block_state = local_map.get(b); |
| |
| if ( null == block_state ) |
| { |
| block_state = new LocalVarState(m, b, frame_state); |
| local_map.put(b, block_state); |
| |
| switch (b.exprs.peekLast().op) |
| { |
| |
| default: |
| { |
| break; |
| } |
| |
| case OP_lookupswitch: |
| { |
| for ( Edge p: b.succ()) |
| checkTarget(b, p.to, block_state.fs_out, states_by_block); |
| break; |
| } |
| |
| case OP_jump: |
| { |
| checkTarget(b, b.succ()[0].to, block_state.fs_out, states_by_block); |
| break; |
| } |
| |
| case OP_ifnlt: |
| case OP_ifnle: |
| case OP_ifngt: |
| case OP_ifnge: |
| case OP_ifne: |
| case OP_ifstrictne: |
| case OP_iftrue: |
| case OP_iffalse: |
| case OP_ifeq: |
| case OP_iflt: |
| case OP_ifle: |
| case OP_ifgt: |
| case OP_ifge: |
| case OP_ifstricteq: |
| { |
| checkTarget(b, b.succ()[1].to, block_state.fs_out, states_by_block); |
| // Leading edge is always the fall-through. |
| break; |
| } |
| } |
| } |
| |
| return local_map.get(b).fs_out; |
| } |
| |
| void checkTarget(Block branching_block, Block target_block, Typeref[] current_frame_state, Map<Block, Typeref[]> frames_by_block) |
| { |
| if ( !frames_by_block.containsKey(target_block)) |
| { |
| verboseStatus (" .. checkTarget(" + branching_block + "->" + target_block + ") copying frame state"); |
| |
| Typeref[] target_frame = new Typeref[current_frame_state.length]; |
| System.arraycopy(current_frame_state, 0, target_frame, 0, current_frame_state.length); |
| frames_by_block.put(target_block, target_frame); |
| } |
| else |
| { |
| Typeref[] previous_state = frames_by_block.get(target_block); |
| |
| verboseStatus(" .. checkTarget(" + branching_block + "->" + target_block + ") merging frame state"); |
| |
| for ( int i = 0; i < previous_state.length; i++) |
| { |
| Typeref merged_type = typeMeet(previous_state[i], current_frame_state[i]); |
| |
| if ( target_block.is_backwards_branch_target) |
| previous_state[i] = merged_type.nullable(); |
| else |
| previous_state[i] = merged_type; |
| } |
| } |
| |
| dumpFrameState(current_frame_state); |
| } |
| |
| void dumpFrameState(Typeref[] fs_out) |
| { |
| if ( verbose_mode ) |
| { |
| StringBuffer frame_state_buffer = new StringBuffer(); |
| frame_state_buffer.append("\tLocals: "); |
| for ( int i = 0; i < fs_out.length; i++) |
| { |
| frame_state_buffer.append(fs_out[i].toString()); |
| frame_state_buffer.append(" "); |
| } |
| verboseStatus(frame_state_buffer); |
| } |
| } |
| |
| /** |
| * Compute variable live-out sets for a control-flow graph. |
| * Follows Cooper & Torczon's [section 9.2.2] Round-robin, iterative solver |
| * @param code - The CFG. Not required to be in any particular order. |
| * @param live_map - the result map of blocks to sets of live-out variables. |
| * @pre live_map must already have per-block data |
| * for upwardly exposed vars, defines, and kills. |
| */ |
| void computeLiveout(Deque<Block> code, Map<Block,LocalVarState> live_map) |
| { |
| boolean changed = true; |
| |
| while ( changed ) |
| { |
| changed = false; |
| |
| for ( Block b: code) |
| { |
| LocalVarState current_state = live_map.get(b); |
| |
| BitSet next_liveout = new BitSet(); |
| |
| for ( Edge p: b.succ() ) |
| { |
| next_liveout.or(live_map.get(p.to).getLivein()); |
| } |
| |
| for ( Edge x:b.xsucc) |
| { |
| next_liveout.or(live_map.get(x.to).getLivein()); |
| } |
| |
| changed |= current_state.mergeLiveout(next_liveout); |
| } |
| } |
| } |
| |
| static class ConflictGraph |
| { |
| Map<Integer,Set<Integer>> conflicts = new TreeMap<Integer,Set<Integer>>(); |
| |
| void add(Expr e1, Expr e2) |
| { |
| get(e1.id).add(e2.id); |
| get(e2.id).add(e1.id); |
| } |
| |
| boolean contains(Expr a, Expr b) |
| { |
| return conflicts.containsKey(a.id) && conflicts.get(a.id).contains(b.id); |
| } |
| |
| public String toString() |
| { |
| return String.valueOf(conflicts); |
| } |
| |
| Set<Integer> get(int i) |
| { |
| Set<Integer> s = conflicts.get(i); |
| if (s == null) |
| conflicts.put(i, s = new TreeSet<Integer>()); |
| return s; |
| } |
| } |
| |
| boolean hasStackEffect(Expr e) |
| { |
| if (e == null || e.op == OP_arg) |
| return true; |
| return e.onStack() || e.args.length > 0; |
| } |
| |
| Typeref typeMerge(Typeref t1, Typeref t2) |
| { |
| Typeref result = null; |
| |
| Type merged_type = typeMeet(t1.t, t2.t); |
| |
| if (!merged_type.equals(ANY()) || (t1.t.equals(ANY()) && t2.t.equals(ANY()))) |
| result = new Typeref ( merged_type, t1.nullable || t2.nullable); |
| |
| return result; |
| } |
| |
| |
| Typeref typeMeet(Typeref t1, Typeref t2) |
| { |
| Typeref result = null; |
| |
| Type merged_type = typeMeet(t1.t, t2.t); |
| |
| result = new Typeref ( merged_type, t1.nullable | t2.nullable); |
| |
| return result; |
| } |
| |
| /** |
| * Type-meet as per the AVM verifier. |
| * @param t1 a type. May be null. |
| * @param t2 a type. May be null. |
| * @return the type meet of these types. |
| * @return null if no common type exists. |
| */ |
| Type typeMeet(Type t1, Type t2) |
| { |
| if ( t1.equals(t2) ) |
| return t1; |
| else if ( isNumericType(t1.ref) && isNumericType(t2.ref)) |
| return NUMBER(); |
| else if ( VOID().equals(t1) || NULL().equals(t1)) |
| return t2; |
| else if ( VOID().equals(t2) || NULL().equals(t2) ) |
| return t1; |
| |
| // Return the common base type or ANY. |
| Type common_base = mdb(t1.ref, t2.ref).t; |
| |
| return common_base; |
| } |
| |
| |
| |
| /** |
| * Schedule the expressions in the method to maximize use of the operand |
| * stack, by processing the flow graph and expressions backwards. If we can't |
| * issue an instruction to fill a stack slot then we issue a getlocal instead, |
| * mark the expression live. |
| * |
| * When an expression is marked live, add a conflict edge with each other live |
| * expression at that point. [Chotin paper?] |
| * |
| * Phi expressions are handled specially - we make each argument live |
| * at the end of the corresponding predecessor block. Since the stack is empty |
| * at the end of each BB, all phi nodes are allocated to a variable. However, |
| * later, we attempt to allocate locals so phi nodes don't require copies. |
| * |
| * @param code |
| * @param locals |
| * @param pred |
| * @return |
| */ |
| |
| ConflictGraph sched_greedy(Method m, |
| Deque<Block> code, |
| Map<Integer,Integer> locals, |
| SetMap<Block,Edge>pred, |
| Map<Block,Deque<Expr>> exprs, |
| ConflictGraph cg) |
| { |
| |
| addTraceAttr(m); |
| |
| SetMap<Block,Expr> liveout = new SetMap<Block,Expr>(); |
| Map<Block,Deque<Expr>> stkout = new TreeMap<Block,Deque<Expr>>(); |
| Map<Block,Deque<Expr>> scpout = new TreeMap<Block,Deque<Expr>>(); |
| HashMap<Block,Deque<Object>> listings = new HashMap<Block,Deque<Object>>(); |
| |
| // low postorder numbers are first, |
| PriorityQueue<Block> work = new PriorityQueue<Block>(code.size(),new Comparator<Block>() { |
| public int compare(Block b1,Block b2) { |
| return b1.postorder-b2.postorder; |
| } |
| }); |
| |
| work.addAll(code); |
| |
| while (!work.isEmpty()) |
| { |
| Block b = work.remove(); |
| while (work.peek() == b) |
| work.remove(); // remove dups |
| |
| Set<Expr> live = new TreeSet<Expr>(); |
| Deque<Expr> in = new ArrayDeque<Expr>(b.exprs); |
| Deque<Expr> stk = new ArrayDeque<Expr>(); |
| Deque<Expr> scp = new ArrayDeque<Expr>(); |
| Deque<Object> verbose = new LinkedDeque<Object>(); |
| Deque<Expr> out = new LinkedDeque<Expr>(); |
| |
| Set<Expr> out_of_order = new TreeSet<Expr>(); |
| |
| |
| addTraceAttr(b); |
| |
| live.addAll(liveout.get(b)); |
| live.addAll(b.getLiveOut()); |
| live.addAll(m.fixedLocals.keySet()); |
| |
| for (Expr l: live) |
| locals.put(l.id, l.op == OP_arg ? l.imm[0] : -1); |
| |
| exprs.put(b,out); |
| listings.put(b,verbose); |
| |
| Set<Expr>phis = new TreeSet<Expr>(); |
| while (!in.isEmpty() || !stk.isEmpty()) |
| { |
| while (!stk.isEmpty() && hasStackEffect(in.peekLast())) |
| { |
| showstate(live, stk, scp, verbose); |
| |
| Expr e = remove_dup(stk, m, out, verbose); |
| |
| addTraceAttr(e); |
| addTraceAttr("formattedExpr", formatExpr(e)); |
| |
| if (e.inLocal() || e.op == OP_phi || |
| e != in.peekLast() || stk.contains(e)) |
| { |
| /* |
| if ( e != in.peekLast() && ! e.hasEffect() ) |
| { |
| verboseStatus(".. out-of-order, but issue " + e); |
| issue_expr(e, m, out, verbose, stk, scp, live, locals); |
| out_of_order.add(e); |
| } |
| else |
| */ |
| { |
| |
| addTraceAttr(e); |
| if ( e.inLocal() ) addTraceAttr("inLocal"); |
| else if ( e.op == OP_phi ) addTraceAttr("OP_phi"); |
| else if ( e != in.peekLast() ) addTraceAttr("stackMismatch", e.toString() + " != last Expr: " + formatExpr(in.peekLast()) ); |
| else if ( stk.contains(e) ) addTraceAttr("stackOverload", "stk.contains(" + e.toString() + ")"); |
| |
| issue_load(e, m, out, verbose, live, locals); |
| |
| } |
| } |
| else if (live.contains(e)) |
| { |
| // stack top ready to be issued but it's live. issue store. |
| // push e back twice: once for store, once to consume. then |
| // go around loop once more to handle the dup + issue. |
| traceEntry("DefineLiveExpr", e); |
| define(e,live,cg); |
| issue_store(e, m, out, verbose, stk); |
| traceEntry("PushBack", e); |
| stk.add(e); |
| } |
| else if (e.op == OP_xarg) |
| { |
| assert(stk.isEmpty()); |
| while (!stk.isEmpty()) |
| loadTOS(m, stk, scp, out, verbose, live, locals); |
| verbose.addFirst(in.removeLast()); |
| } |
| else |
| { |
| // e is ready to issue |
| traceEntry("IssueExpr", e); |
| issue_expr(in.removeLast(), m, out, verbose, stk, scp, live, locals); |
| } |
| |
| continue; |
| } |
| |
| if (!in.isEmpty()) |
| { |
| showstate(live, stk, scp, verbose); |
| Expr e = in.removeLast(); |
| |
| addTraceAttr(e); |
| addTraceAttr("formattedExpr", formatExpr(e)); |
| if ( out_of_order.contains(e) ) |
| { |
| traceEntry("IssuedOutOfOrder", e ); |
| continue; |
| } |
| else if (e.op == OP_phi) |
| { |
| issue_phi(e, verbose, phis, live, cg); |
| } |
| else if (e.op == OP_arg) |
| { |
| verbose.addFirst(e); |
| if (live.contains(e)) |
| define(e,live,cg); |
| } |
| else |
| { |
| if (live.contains(e)) |
| { |
| traceEntry("LiveExpr"); |
| addTraceAttr(e); |
| define(e,live,cg); |
| // ISSUE: Why not just issue a dup before the store? |
| if (e.onStack()) |
| { |
| traceEntry("StoreLiveExprFromStack ", e); |
| issue_store(e, m, out, verbose, stk); |
| traceEntry("PushBackOnStack", e); |
| in.add(e); |
| } |
| else if (e.inLocal()) |
| { |
| traceEntry("IssueExprFromLocal", e); |
| issue_expr(e, m, out, verbose, stk, scp, live, locals); |
| } |
| else if (e.onScope()) |
| { |
| traceEntry("IssueScopeExpr", e); |
| issue_expr(e, m, out, verbose, stk, scp, live, locals); |
| } |
| else |
| { |
| assert(false); |
| } |
| } |
| else if (e.op == OP_xarg) |
| { |
| issue_pop(m,out,verbose,e); |
| verbose.addFirst(e); |
| } |
| else |
| { |
| |
| addTraceAttr(e); |
| if (e.onStack()) |
| { |
| traceEntry("StackPop", e); |
| issue_pop(m,out,verbose,e); |
| } |
| issue_expr(e, m, out, verbose, stk, scp, live, locals); |
| |
| } |
| } |
| |
| } |
| } |
| |
| fwd_state(m, locals, pred, liveout, stkout, scpout, work, b, live, stk, scp, verbose, out, phis); |
| |
| } |
| verboseStatus("STK_LIVEOUT " + liveout); |
| verboseStatus("CONFLICTS " + cg); |
| |
| for (Block b: code) |
| { |
| verboseStatus(""); |
| verboseStatus(b); |
| for (Object o: listings.get(b)) |
| if (o instanceof Expr) |
| print((Expr)o); |
| else |
| verboseStatus(o); |
| } |
| |
| return cg; |
| } |
| |
| static void traceEntry(String traceTag) |
| { |
| tm.traceEntry(traceTag); |
| } |
| |
| static void traceEntry(String traceTag, String attrValue) |
| { |
| tm.traceEntry(traceTag, attrValue); |
| } |
| |
| static void traceEntry(String traceTag, String attrName, String attrValue) |
| { |
| tm.traceEntry(traceTag, attrName, attrValue); |
| } |
| |
| static void traceEntry(String traceTag, String attrName, Object attrValue) |
| { |
| tm.traceEntry(traceTag); |
| addTraceAttr(attrName, attrValue); |
| } |
| |
| static void traceEntry(String traceTag, int attrValue) |
| { |
| tm.traceEntry(traceTag, attrValue); |
| } |
| |
| |
| static void traceEntry(String traceTag, Expr expr) |
| { |
| traceEntry(traceTag); |
| addTraceAttr(expr); |
| } |
| |
| |
| static void addTraceAttr(String attrName) |
| { |
| tm.addAttr(attrName, "true"); |
| } |
| |
| static void addTraceAttr(Object o) |
| { |
| if ( null == o ) |
| return; |
| else if ( o instanceof Block ) |
| addTraceAttr("Block", o); |
| else if ( o instanceof Edge) |
| addTraceAttr("Edge", o); |
| else if ( o instanceof Expr) |
| addTraceAttr("Expr", o); |
| else if ( o instanceof Method ) |
| addTraceAttr("Method", o); |
| else |
| addTraceAttr(o.getClass().getSimpleName(), o); |
| } |
| |
| static void addTraceAttr(String attrName, Object attr) |
| { |
| tm.addAttr(attrName, attr); |
| |
| if ( attr != null && |
| ( attrName.equalsIgnoreCase("Expr") || |
| attrName.equalsIgnoreCase("Method") || |
| attrName.equalsIgnoreCase("Block"))) |
| { |
| tm.addAttr("HashCode", Integer.toHexString(attr.hashCode())); |
| } |
| } |
| |
| static void addTraceAttr(String attrName, int attr) |
| { |
| tm.addAttr(attrName, attr); |
| } |
| |
| static void addTraceAttr(String attrName, String attr) |
| { |
| tm.addAttr(attrName, attr); |
| } |
| |
| void showstate(Set<Expr> live, Deque<Expr> stk, Deque<Expr> scp, Deque<Object> verbose) |
| { |
| verbose.addFirst(" live "+live + " stk " + stk); |
| if (!scp.isEmpty()) |
| verbose.addFirst(" scp "+scp); |
| } |
| |
| Expr remove_dup(Deque<Expr>stk, Method m, Deque<Expr>out, Deque<Object>verbose) |
| { |
| Expr e = stk.removeLast(); |
| while (e == stk.peekLast()) |
| issue_dup(stk.removeLast(), m, out, verbose); |
| return e; |
| } |
| |
| void try_dup(Deque<Expr>stk, Method m, Deque<Expr>out, Deque<Object>verbose) |
| { |
| if (stk.size()>2) |
| { |
| Expr e = stk.removeLast(); |
| while (e == stk.peekLast()) |
| issue_dup(stk.removeLast(), m, out, verbose); |
| stk.add(e); |
| } |
| } |
| |
| void issue_phi(Expr e, Deque<Object>verbose, Set<Expr>phis, Set<Expr>live, ConflictGraph cg) |
| { |
| |
| addTraceAttr(e); |
| addTraceAttr("live", live.contains(e)); |
| |
| verbose.addFirst(e); |
| phis.add(e); |
| if (live.contains(e)) |
| for (Expr l: live) |
| if (l != e) |
| { |
| traceEntry("conflict"); |
| addTraceAttr(e); |
| addTraceAttr("conflictsWith", l); |
| cg.add(l,e); |
| } |
| |
| } |
| |
| ConflictGraph sched_lazy(Method m, |
| Deque<Block> code, |
| Map<Integer,Integer> locals, |
| SetMap<Block,Edge>pred, |
| Map<Block,Deque<Expr>> exprs, |
| ConflictGraph cg) |
| { |
| SetMap<Block,Expr> liveout = new SetMap<Block,Expr>(); |
| Map<Block,Deque<Expr>> stkout = new TreeMap<Block,Deque<Expr>>(); |
| Map<Block,Deque<Expr>> scpout = new TreeMap<Block,Deque<Expr>>(); |
| HashMap<Block,Deque<Object>> listings = new HashMap<Block,Deque<Object>>(); |
| |
| // low postorder numbers are first, |
| PriorityQueue<Block> work = new PriorityQueue<Block>(code.size(),new Comparator<Block>() { |
| public int compare(Block b1,Block b2) { |
| return b1.postorder-b2.postorder; |
| } |
| }); |
| |
| work.addAll(code); |
| while (!work.isEmpty()) |
| { |
| Block b = work.remove(); |
| while (work.peek() == b) |
| work.remove(); // remove dups |
| |
| Set<Expr> live = new TreeSet<Expr>(); |
| Deque<Expr> in = new ArrayDeque<Expr>(b.exprs); |
| Deque<Expr> stk = new ArrayDeque<Expr>(); |
| Deque<Expr> scp = new ArrayDeque<Expr>(); |
| Deque<Object> verbose = new LinkedDeque<Object>(); |
| Deque<Expr> out = new LinkedDeque<Expr>(); |
| |
| // if any succ has defined an expected stack, use it. |
| // if more than one have then they all have to match. |
| if (stkout.containsKey(b)) |
| stk.addAll(stkout.get(b)); |
| |
| // merge the live vars from each succ |
| live.addAll(liveout.get(b)); |
| |
| for (Expr l: live) |
| locals.put(l.id, l.op == OP_arg ? l.imm[0] : -1); |
| |
| exprs.put(b,out); |
| listings.put(b,verbose); |
| |
| Set<Expr>phis = new TreeSet<Expr>(); |
| |
| while (!in.isEmpty()) |
| { |
| showstate(live, stk, scp, verbose); |
| Expr e = in.removeLast(); |
| |
| if (e.op == OP_phi) |
| { |
| issue_phi(e, verbose, phis, live, cg); |
| continue; |
| } |
| assert(phis.isEmpty()); |
| |
| boolean onstack = false; |
| while (stk.contains(e)) |
| { |
| onstack = true; |
| Expr f = remove_dup(stk, m, out, verbose); |
| if (f != e || stk.contains(e)) |
| issue_load(f,m,out,verbose,live,locals); |
| } |
| |
| if (e.op == OP_arg) |
| { |
| if (onstack) |
| issue_load(e,m,out,verbose,live,locals); |
| verbose.addFirst(e); |
| if (live.contains(e)) |
| define(e,live,cg); |
| } |
| else |
| { |
| if (live.contains(e)) |
| { |
| if (e.op == OP_xarg) |
| while (!stk.isEmpty()) |
| loadTOS(m,stk,scp,out,verbose,live,locals); |
| define(e, live, cg); |
| if (onstack) |
| stk.add(e); |
| issue_store(e, m, out, verbose, stk); |
| in.add(e); |
| } |
| else if (e.op == OP_xarg) |
| { |
| if (!stk.isEmpty()) |
| { |
| while (!stk.isEmpty()) |
| loadTOS(m,stk,scp,out,verbose,live,locals); |
| in.add(e); // put xarg back |
| if (onstack) |
| stk.add(e); // make xarg the only thing on the stack. |
| } |
| else |
| { |
| if (!onstack) |
| issue_pop(m,out,verbose,e); |
| verbose.addFirst(e); //print but don't issue it. |
| } |
| } |
| else |
| { |
| if (!onstack && e.onStack()) |
| issue_pop(m,out,verbose,e); |
| issue_expr(e, m, out, verbose, stk, scp, live, locals); |
| } |
| } |
| } |
| |
| fwd_state(m, locals, pred, liveout, stkout, scpout, work, b, live, stk, scp, verbose, out, phis); |
| } |
| verboseStatus("SCHED LIVEOUT " + liveout); |
| verboseStatus("SCHED STKOUT " + stkout); |
| verboseStatus("SCHED CONFLICTS " + cg); |
| |
| for (Block b: code) |
| { |
| verboseStatus(""); |
| verboseStatus(b); |
| for (Object o: listings.get(b)) |
| if (o instanceof Expr) |
| print((Expr)o); |
| else |
| verboseStatus(o); |
| } |
| return cg; |
| } |
| |
| /** |
| * issue a load for whatever is on the stack top |
| */ |
| void loadTOS(Method m, Deque<Expr>stk, Deque<Expr>scp, Deque<Expr>out, Deque<Object>verbose, Set<Expr>live, Map<Integer,Integer>locals) |
| { |
| // stk doesn't match, make all successors expect nothing. |
| showstate(live, stk, scp, verbose); |
| Expr e = stk.removeLast(); |
| if (e == stk.peekLast()) |
| issue_dup(e, m, out, verbose); |
| else |
| issue_load(e, m, out, verbose, live, locals); |
| } |
| |
| /** |
| * forward the stack & live variable state from the top of the current |
| * block (b) to the end of each predecessor block, translating phi nodes |
| * at the same time. |
| * |
| * @param m |
| * @param locals |
| * @param pred |
| * @param liveout |
| * @param stkout |
| * @param work |
| * @param b |
| * @param live |
| * @param stk |
| * @param verbose |
| * @param out |
| * @param phis |
| */ |
| void fwd_state(Method m, Map<Integer, Integer> locals, |
| SetMap<Block, Edge> pred, SetMap<Block, Expr> liveout, |
| Map<Block, Deque<Expr>> stkout, Map<Block,Deque<Expr>> scpout, |
| PriorityQueue<Block> work, Block b, Set<Expr> live, |
| Deque<Expr> stk, Deque<Expr> scp, |
| Deque<Object> verbose, Deque<Expr> out, Set<Expr> phis) |
| { |
| // first prune stack to something safe to forward. |
| TreeMap<Block,Deque<Expr>>stkout2 = new TreeMap<Block,Deque<Expr>>(stkout); |
| |
| for (Edge p: pred.get(b)) |
| { |
| Block f = p.from; |
| Deque<Expr> stk2 = clone_stk(phis,stk,p); |
| if (!stkout2.containsKey(f)) |
| { |
| stkout2.put(f,stk2); |
| } |
| else |
| { |
| int prefix = stacks_equal(stk2,stkout2.get(f)); |
| assert(stk.size() >= prefix); |
| while (stk.size() > prefix) |
| loadTOS(m,stk,scp,out,verbose,live,locals); |
| } |
| } |
| showstate(live, stk, scp, verbose); |
| |
| for (Edge p: pred.get(b)) |
| { |
| Block f = p.from; |
| |
| // forward live exprs |
| Set<Expr> live2 = clone_live(phis,live,p); |
| if (liveout.get(f).addAll(live2)) |
| work.add(f); |
| |
| // forward stack exprs |
| Deque<Expr> stk2 = clone_stk(phis,stk,p); |
| if (!stkout.containsKey(f)) |
| { |
| stkout.put(f, stk2); |
| } |
| else |
| { |
| int prefix = stacks_equal(stk2,stkout.get(f)); |
| assert(stk2.size() == prefix && stkout.get(f).size() >= prefix); |
| if (stkout.get(f).size() > prefix) |
| { |
| stkout.put(f, stk2); |
| work.add(f); |
| for (Edge s: f.succ()) |
| if (s.to != b) |
| work.add(s.to); |
| } |
| } |
| } |
| } |
| |
| /** |
| * clone the live vars set for a particular predecessor edge, |
| * replacing phi's with the expr corresponding to that edge. |
| * |
| * @param phis |
| * @param live |
| * @param p |
| * @return |
| */ |
| Set<Expr> clone_live(Set<Expr>phis, Set<Expr>live, Edge p) |
| { |
| if (phis.isEmpty() || live.isEmpty()) |
| return live; |
| Set<Expr> copy = new TreeSet<Expr>(); |
| for (Expr e: live) |
| copy.add(phis.contains(e) ? e.args[findPhiArg(e, p)] : e); |
| return copy; |
| } |
| |
| /** |
| * clone the stack layout for a predecessor edge, replacing phi's |
| * with the argument coming from that edge. |
| * |
| * @param phis |
| * @param stk |
| * @param p |
| * @return |
| */ |
| Deque<Expr> clone_stk(Set<Expr>phis, Deque<Expr>stk, Edge p) |
| { |
| if (phis.isEmpty() || stk.isEmpty()) |
| return stk; |
| Deque<Expr> copy = new ArrayDeque<Expr>(); |
| for (Expr e: stk) |
| copy.add(phis.contains(e) ? e.args[findPhiArg(e, p)] : e); |
| return copy; |
| } |
| |
| int stacks_equal(Deque<Expr>stk1, Deque<Expr>stk2) |
| { |
| int i=0; |
| Iterator<Expr> i1 = stk1.iterator(); |
| Iterator<Expr> i2 = stk2.iterator(); |
| while (i1.hasNext() && i2.hasNext()) |
| { |
| if (i1.next() != i2.next()) |
| return i; |
| i++; |
| } |
| return i; |
| } |
| |
| void define(Expr e, Set<Expr> live, ConflictGraph cg) |
| { |
| |
| addTraceAttr(e); |
| live.remove(e); |
| for (Expr l: live) |
| { |
| cg.add(e,l); |
| traceEntry("conflict"); |
| addTraceAttr(e); |
| addTraceAttr("conflictsWith", l); |
| } |
| |
| } |
| |
| void issue_expr(Expr e, Method m, Deque<Expr>out, Deque<Object> verbose, Deque<Expr>stk, Deque<Expr>scp, Set<Expr>live, Map<Integer,Integer> locals) |
| { |
| |
| addTraceAttr(e); |
| if (!e.isSynthetic()) |
| out.addFirst(e); |
| verbose.addFirst(e); |
| for (Expr a: e.args) |
| { |
| traceEntry("PushArg", "Expr", a); |
| stk.add(a); |
| } |
| for (Expr a: e.locals) |
| { |
| traceEntry("UseLocal", "Expr", a); |
| use(a, live, locals); |
| } |
| try_dup(stk, m, out, verbose); |
| |
| } |
| |
| void issue_store(Expr e, Method m, Deque<Expr>out, Deque<Object> verbose, Deque<Expr>stk) |
| { |
| Expr set = setlocal(m, e.id, e); |
| |
| addTraceAttr("setlocalExpr", formatExpr(set)); |
| out.addFirst(set); |
| verbose.addFirst(set); |
| traceEntry("Push", formatExpr(e)); |
| stk.add(e); |
| |
| } |
| |
| void issue_dup(Expr e, Method m, Deque<Expr>out, Deque<Object> verbose) |
| { |
| Expr dup = dup(m,e); |
| out.addFirst(dup); |
| traceEntry("issue_dup", dup); |
| verbose.addFirst(dup); |
| } |
| |
| void issue_pop(Method m, Deque<Expr>out, Deque<Object> verbose, Expr a) |
| { |
| Expr pop = new Expr(m, OP_pop, a); |
| out.addFirst(pop); |
| verbose.addFirst(pop); |
| } |
| |
| void issue_load(Expr e, Method m, Deque<Expr>out, Deque<Object>verbose, Set<Expr>live, Map<Integer,Integer>locals) |
| { |
| use(e, live, locals); |
| Expr get = getlocal(m,e.id); |
| out.addFirst(get); |
| traceEntry("issue_load", get); |
| verbose.addFirst(get); |
| } |
| |
| void use(Expr e, Set<Expr> live, Map<Integer, Integer> locals) |
| { |
| traceEntry("use"); |
| addTraceAttr(e); |
| live.add(e); |
| locals.put(e.id, e.op == OP_arg ? e.imm[0] : -1); |
| addTraceAttr("locals_entry", locals.get(e.id)); |
| } |
| |
| void rename(Expr e, Expr[] args, Map<Expr,Expr> map, EdgeMap<Expr> ssaSucc) |
| { |
| |
| addTraceAttr(e); |
| |
| for (int i=0, n=args.length; i<n; i++) |
| { |
| Expr a = args[i]; |
| while (map.containsKey(a)) |
| { |
| ssaSucc.get(a).remove(e); |
| traceEntry("renamedArg"); |
| addTraceAttr("i", i); |
| addTraceAttr("orig", args[i]); |
| a = args[i] = map.get(a); |
| a.is_live_out = a.onStack() || a.inLocal(); |
| addTraceAttr("new", args[i]); |
| ssaSucc.get(a).add(e); |
| } |
| } |
| |
| |
| } |
| |
| /** |
| * Copy propagation. |
| * |
| * Phi nodes that only choose themselves or one other variable |
| * are replaced by references to that other variable: |
| * |
| * @i = phi(@i ... @j), op(@i) => = op(@j) |
| * |
| * additionally, references to a dup are replaced by references to the |
| * dup's argument. Dup is a copy instruction inserted by some previous |
| * optimization. |
| * |
| * @i = dup(@j), op(@i) => op(@j) |
| * |
| * We take advantage of SSA's sparseness by propagating changes along |
| * def->use edges and iterating until our worklist is empty. |
| * |
| * This pass also removes any phi arguments that are dead, i.e. if the |
| * edge contributing the argument is from an unreachable block. This |
| * may turn the phi into a copy which is subsequently removed. |
| */ |
| void cp(Deque<Block> code) |
| { |
| |
| EdgeMap<Expr> uses = findUses(code); |
| Map<Expr,Expr> map = new HashMap<Expr,Expr>(); |
| Set<Expr> work = new TreeSet<Expr>(); |
| |
| for (Block b: code) |
| { |
| if ( ! b.must_isolate_block) |
| { |
| for (Expr e: b) |
| if (e.op == OP_phi || e.op == OP_dup) |
| work.add(e); |
| } |
| } |
| |
| while (!work.isEmpty()) |
| { |
| Expr e = getExpr(work); |
| rename(e, e.args, map, uses); |
| rename(e, e.scopes, map, uses); |
| rename(e, e.locals, map, uses); |
| if (e.op == OP_dup) |
| { |
| map.put(e, e.locals[0]); |
| work.addAll(uses.get(e)); |
| } |
| else if (e.op == OP_phi) |
| { |
| assert(e.args.length == e.pred.length); |
| for (int j=e.pred.length-1; j >= 0; j--) |
| if (!code.contains(e.pred[j].from)) |
| e.removePhiInput(j); |
| Expr a = null; |
| for (int j=e.pred.length-1; j >= 0; j--) |
| { |
| if (e.args[j] != e && e.args[j] != a) |
| if (a == null) |
| a = e.args[j]; |
| else |
| { |
| a = null; |
| break; |
| } |
| } |
| if (a != null && map.get(e) != a) |
| { |
| map.put(e, a); |
| work.addAll(uses.get(e)); |
| e.clearEffect(); |
| } |
| } |
| } |
| |
| // Marshall live-out exprs by block. |
| for ( Block b: code) |
| { |
| for ( Expr e: b.exprs) |
| { |
| if ( e.is_live_out ) |
| { |
| b.addLiveOut(e); |
| } |
| } |
| |
| } |
| |
| |
| } |
| |
| boolean hasSideEffect(Expr e) |
| { |
| return e.isPx() || e.hasEffect(); |
| } |
| |
| void schedule_loop(Block b, EdgeMap<Block> loops, Deque<Block> scheduled) |
| { |
| Set<Block> loop = loops.get(b); |
| for (Block lb: dfs(b)) |
| { |
| if (!scheduled.contains(lb) && loop.contains(lb)) |
| { |
| scheduled.add(lb); |
| if (loops.containsKey(lb)) |
| schedule_loop(lb, loops, scheduled); |
| } |
| } |
| } |
| |
| /** |
| * layout blocks. Normal forward flow is laid out in reverse postorder. |
| * Loops are handled specially to keep blocks in the loop body close to |
| * each other. loop exit blocks are postponed until after the loop. |
| * @param entry |
| * @return |
| */ |
| Deque<Block> schedule(Block entry) |
| { |
| Deque<Block>code = dfs(entry); |
| Deque<Block>scheduled = new ArrayDeque<Block>(); |
| SetMap<Block,Edge> pred = preds(code); |
| Map<Block,Block> idom = idoms(code,pred); |
| EdgeMap<Block> loops = findLoops(code,idom,pred); |
| |
| if (!loops.isEmpty()) |
| verboseStatus("LOOPS "+loops); |
| |
| for (Block b: code) |
| { |
| // TODO: Post-test loops fail the AVM verifier |
| // under certain conditions (not well understood). |
| if (!scheduled.contains(b)) |
| scheduled.add(b); |
| if (loops.containsKey(b)) |
| schedule_loop(b, loops, scheduled); |
| } |
| |
| // Rearrange conditional branches so that fall-through edges |
| // occur in the optimizer's natural flow, and flag any |
| // backwards branch targets. |
| Deque<Block> branch_analysis = new ArrayDeque<Block>(); |
| branch_analysis.addAll(scheduled); |
| Set<Block> already_seen = new HashSet<Block>(); |
| |
| while (branch_analysis.size()>1) |
| { |
| Block b = branch_analysis.removeFirst(); |
| |
| Expr last = b.last(); |
| |
| Block next = branch_analysis.peekFirst(); |
| if (isBranch(last) && last.succ[0].to != next && |
| last.succ[1].to == next) |
| invert(last); |
| |
| // Check for backwards branches. |
| already_seen.add(b); |
| |
| b.is_backwards_branch_target = false; |
| |
| for ( Edge s: b.succ()) |
| { |
| s.to.is_backwards_branch_target |= s.is_backwards_branch = already_seen.contains(s.to); |
| } |
| } |
| |
| if ( verbose_mode ) |
| for (Block b: code) |
| if ( b.is_backwards_branch_target ) |
| verboseStatus(".. backwards branch target:" + b); |
| |
| return scheduled; |
| } |
| |
| boolean isJump(Expr e) |
| { |
| return e.op == OP_jump; |
| } |
| |
| boolean isBranch(Expr e) |
| { |
| return e.succ != null && e.succ.length == 2 && e.op != OP_lookupswitch; |
| } |
| |
| |
| /** |
| * returns true if P->S is a loop back edge. This is the |
| * case if S is visited first in reverse postorder and S |
| * dominates P. |
| * |
| * @param p |
| * @param s |
| * @param idom |
| * @return |
| */ |
| boolean isLoop(Edge e, Map<Block,Block>idom) |
| { |
| return e.isBackedge() && dominates(e.to, e.from, idom); |
| } |
| |
| EdgeMap<Block> findLoops(Deque<Block> code) |
| { |
| SetMap<Block,Edge> pred = preds(code); |
| return findLoops(code, idoms(code, pred), pred); |
| } |
| |
| /** |
| * loop code layout |
| * the loop is defined by the back edge from T->H. H (header) is the source |
| * of the back edge. We want H at the bottom of the loop and T at the top. |
| * Lay the blocks out in DFS order starting with T and skipping any blocks |
| * that aren't part of the loop. |
| * |
| * prefer to have fall-through blocks that are part of the loop. |
| */ |
| |
| EdgeMap<Block> findLoops(Deque<Block> code, Map<Block,Block>idom, SetMap<Block,Edge>pred) |
| { |
| EdgeMap<Block> loops = new EdgeMap<Block>(); |
| for (Block b: code) |
| for (Edge s: b.succ()) |
| if (isLoop(s, idom)) |
| { |
| verboseStatus("backedge "+s); |
| Block h = s.to; |
| // find the set of blocks that are in the loop body. |
| Set<Block> loop = loops.get(h); |
| Set<Block> work = new TreeSet<Block>(); |
| for (Edge p: pred.get(h)) |
| { |
| if (isLoop(p,idom) && !loop.contains(p.from) && p.from != h) |
| { |
| loop.add(p.from); |
| work.add(p.from); |
| } |
| } |
| while (!work.isEmpty()) |
| { |
| Block x = getBlock(work); |
| for (Edge p: pred.get(x)) |
| { |
| if (p.from != h && !loop.contains(p.from)) |
| { |
| loop.add(p.from); |
| work.add(p.from); |
| } |
| } |
| } |
| } |
| return loops; |
| } |
| |
| void dce_mark(BitSet used, Expr e) |
| { |
| if (used.get(e.id)) |
| return; |
| used.set(e.id); |
| for (Expr a: e.args) dce_mark(used, a); |
| for (Expr a: e.scopes) dce_mark(used, a); |
| for (Expr a: e.locals) dce_mark(used, a); |
| } |
| |
| /** |
| * Dead Code Elimination. First perform copy propagation and then |
| * remove any expressions not used by the essential expressions |
| * that have side effects. |
| * @param m |
| */ |
| void dce(Method m) |
| { |
| Deque<Block> code = dfs(m.entry.to); |
| |
| cp(code); |
| |
| BitSet marked = new BitSet(); |
| for (Block b: code) |
| for (Expr e: b) |
| if (hasSideEffect(e)) |
| dce_mark(marked,e); |
| |
| for (Block b: code) |
| for (Iterator<Expr> i = b.iterator(); i.hasNext();) |
| if (!marked.get(i.next().id)) |
| { |
| i.remove(); |
| } |
| } |
| |
| class TypeConstraintMap extends HashMap<Edge, TypeConstraints> |
| { |
| /** @see Serializable */ |
| private static final long serialVersionUID = 1903880092224622848L; |
| |
| TypeConstraintMap() |
| { |
| super(); |
| } |
| |
| /** |
| * Retrieve or create an Edge's constraints. |
| * @param key - the Edge of interest. |
| * @return the Edge's constraints. |
| */ |
| TypeConstraints getConstraints(Edge key) |
| { |
| TypeConstraints result = get(key); |
| |
| if ( null == result ) |
| { |
| result = new TypeConstraints(key); |
| put(key, result); |
| } |
| |
| return result; |
| } |
| |
| } |
| |
| String format(char op, Object[] a, char cp) |
| { |
| if (a == null) |
| return ""; |
| StringBuilder s = new StringBuilder(); |
| s.append(op); |
| for (Object o: a) |
| s.append(o).append(' '); |
| if (a.length > 0) |
| s.setCharAt(s.length()-1,cp); |
| else |
| s.append(cp); |
| return s.toString(); |
| } |
| |
| static boolean isSlot(Binding b) |
| { |
| return (b != null)? b.isSlot(): false; |
| } |
| static boolean isConst(Binding b) |
| { |
| return (b != null)? b.isConst(): false; |
| } |
| static boolean isClass(Binding b) |
| { |
| return (b != null)? b.isClass(): false; |
| } |
| static boolean isMethod(Binding b) |
| { |
| return (b != null)? b.isMethod() :false; |
| } |
| static boolean isGetter(Binding b) |
| { |
| return (b != null)? b.isGetter(): false; |
| } |
| static boolean isSetter(Binding b) |
| { |
| return (b != null)? b.isSetter(): false; |
| } |
| |
| /** |
| * how many stack elements need to be popped to complete |
| * the given multiname reference. |
| */ |
| static int[] refArgc = |
| { |
| 0,//byte CONSTANT_Void = 0x00; // not actually interned |
| 0,//byte CONSTANT_Utf8 = 0x01; |
| 0, //0x02 |
| 0,//byte CONSTANT_Integer = 0x03; |
| 0,//byte CONSTANT_UInteger = 0x04; |
| 0,//byte CONSTANT_PrivateNamespace = 0x05; |
| 0,//byte CONSTANT_Double = 0x06; |
| 0,//byte CONSTANT_Qname = 0x07; // ns::name, const ns, const name |
| 0,//byte CONSTANT_Namespace = 0x08; |
| 0,//byte CONSTANT_Multiname = 0x09; //[ns...]::name, const [ns...], const name |
| 0,//byte CONSTANT_False = 0x0A; |
| 0,//byte CONSTANT_True = 0x0B; |
| 0,//byte CONSTANT_Null = 0x0C; |
| 0,//byte CONSTANT_QnameA = 0x0D; // @ns::name, const ns, const name |
| 0,//byte CONSTANT_MultinameA = 0x0E;// @[ns...]::name, const [ns...], const name |
| 1,//byte CONSTANT_RTQname = 0x0F; // ns::name, var ns, const name |
| 1,//byte CONSTANT_RTQnameA = 0x10; // @ns::name, var ns, const name |
| 2,//byte CONSTANT_RTQnameL = 0x11; // ns::[name], var ns, var name |
| 2,//byte CONSTANT_RTQnameLA = 0x12; // @ns::[name], var ns, var name |
| 0,//0x13 |
| 0,//0x14 |
| 0,//byte CONSTANT_Namespace_Set = 0x15; // a set of namespaces - used by multiname |
| 0,//byte CONSTANT_PackageNamespace = 0x16; // a namespace that was derived from a package |
| 0,//byte CONSTANT_PackageInternalNs = 0x17; // a namespace that had no uri |
| 0,//byte CONSTANT_ProtectedNamespace = 0x18; |
| 0,//byte CONSTANT_ExplicitNamespace = 0x19; |
| 0,//byte CONSTANT_StaticProtectedNs = 0x1A; |
| 1,//byte CONSTANT_MultinameL = 0x1B; |
| 1,//byte CONSTANT_MultinameLA = 0x1C; |
| }; |
| |
| static class FrameState |
| { |
| Expr[] frame; |
| int sp; |
| int scopep; |
| |
| public FrameState(Expr[] frame, int sp, int scopep) |
| { |
| super(); |
| this.frame = new Expr[frame.length]; |
| this.sp = sp; |
| this.scopep = scopep; |
| } |
| } |
| |
| static boolean isLive(int i, Method m, int scopep) |
| { |
| return i < scopep || i >= m.local_count + m.max_scope; |
| } |
| |
| Block createBlock(Method m, Edge edge, Map<Block,FrameState>states, Expr[] frame, int sp, int scopep) |
| { |
| |
| Block b = new Block(m); |
| addTraceAttr("Block", b); |
| FrameState state = new FrameState(frame,sp,scopep); |
| |
| if (edge != null) |
| { |
| edge.to = b; |
| traceEntry("Edge"); |
| addTraceAttr("to", b); |
| } |
| for (int i=0; i < sp; i++) |
| { |
| if (isLive(i,m,scopep) && frame[i] != null) |
| { |
| Expr e = new Expr(m,OP_phi); |
| if (edge != null) |
| { |
| e.args = new Expr[] { frame[i] }; |
| e.pred = new Edge[] { edge }; |
| } |
| b.add(state.frame[i] = e); |
| } |
| } |
| traceFrame("NewBlockFrame", m, frame, scopep, sp); |
| states.put(b, state); |
| |
| return b; |
| } |
| |
| /** |
| * merge state with block at position pos. If the target block |
| * doesn't exist, create it first. |
| * |
| * @param m |
| * @param edge |
| * @param blocks |
| * @param states |
| * @param pos |
| * @param frame |
| * @param sp |
| * @param scopep |
| */ |
| void merge(Method m, Edge edge, Map<Integer,Block> blocks, Map<Block,FrameState> states, int pos, Expr[] frame, int sp, int scopep) |
| { |
| |
| addTraceAttr("Edge", edge); |
| |
| if (!blocks.containsKey(pos)) |
| { |
| Block b = createBlock(m,edge,states,frame,sp,scopep); |
| blocks.put(pos, b); |
| } |
| else if (edge != null) |
| { |
| edge.to = blocks.get(pos); |
| mergeFrameStates(m, edge, states, frame, sp, scopep); |
| } |
| |
| } |
| |
| /** |
| * merge current state with existing block (edge.to) |
| * @param m |
| * @param edge |
| * @param states |
| * @param frame |
| * @param sp |
| * @param scopep |
| */ |
| void mergeFrameStates(Method m, Edge edge, Map<Block,FrameState>states, Expr[] frame, int sp, int scopep) |
| { |
| |
| FrameState target = states.get(edge.to); |
| |
| /* |
| // FIXME: There are a restricted set of circumstances |
| // where it's permissible to have a stack mismatch -- |
| // asc doesn't completely restore scope stacks at a join |
| // from a catch block to a return, for example. |
| // Need a bottom value or sth similar to represent this. |
| |
| assert(target.sp == sp); |
| assert(target.scopep == scopep); |
| */ |
| for (int i=0; i < sp; i++) |
| { |
| if (isLive(i,m,scopep) && frame[i] != target.frame[i]) |
| { |
| if ( target.frame[i] == null ) |
| { |
| // FIXME: See above comment; |
| // only valid value here is |
| // an invalid value. |
| } |
| else |
| { |
| assert(frame[i] != null && target.frame[i].op == OP_phi); |
| target.frame[i].append(frame[i], edge); |
| } |
| } |
| } |
| |
| |
| } |
| |
| void xmerge(Method m, Edge edge, Map<Integer,Block> blocks, Map<Block,FrameState> states, int pos, Expr[] frame, int sp, int scopep) |
| { |
| |
| scopep = m.local_count; |
| sp = scopep + m.max_scope; |
| |
| Handler h = edge.handler; |
| |
| if (h.entry == null) |
| { |
| // first time, create handler block that jumps to catch block |
| addTraceAttr("firstTime"); |
| Block hb = h.entry = createBlock(m, edge, states, frame, sp, scopep); |
| Expr xarg, jump; |
| hb.add(xarg = new Expr(m,OP_xarg, edge.label)); |
| hb.add(jump = new Expr(m,OP_jump)); |
| jump.succ = new Edge[] { new Edge(m, hb, 0, blocks.get(pos)) }; |
| Expr save = frame[sp]; |
| frame[sp] = xarg; |
| |
| traceEntry("HandlerBlock"); |
| addTraceAttr("Block", hb); |
| addTraceAttr("Edge", edge); |
| traceFrame("Frame", m, frame, scopep, sp); |
| |
| // and merge the handler block with the real exception target. |
| merge(m, jump.succ[0], blocks, states, pos, frame, sp+1, scopep); |
| frame[sp] = save; |
| } |
| else |
| { |
| // each time, merge current state with handler block state. |
| addTraceAttr("notFirstTime"); |
| edge.to = h.entry; |
| mergeFrameStates(m, edge, states, frame, sp, scopep); |
| } |
| |
| } |
| |
| void traceFrame(String desc, Method m, Expr[] frame, int scopep, int sp) |
| { |
| |
| |
| |
| int i = 0; |
| for ( i = 0; i < m.local_count; i++ ) |
| { |
| traceEntry("Local"); |
| addTraceAttr("number", i); |
| addTraceAttr("value", frame[i]); |
| } |
| |
| |
| |
| addTraceAttr("scopep", scopep); |
| for (; i < scopep; i++ ) |
| { |
| traceEntry("Scope"); |
| addTraceAttr("index", i); |
| addTraceAttr("value", frame[i]); |
| } |
| |
| |
| |
| addTraceAttr("sp", sp); |
| for ( i = m.local_count + m.max_scope; i < sp; i++ ) |
| { |
| traceEntry("Operand"); |
| addTraceAttr("index", i); |
| addTraceAttr("value", frame[i]); |
| } |
| |
| |
| |
| } |
| |
| static Expr[] capture(Expr[] frame, int top, int len) |
| { |
| Expr[] args = new Expr[len]; |
| System.arraycopy(frame, top-len, args, 0, len); |
| |
| addTraceAttr("stackPtr", top); |
| addTraceAttr("len", len); |
| for ( int i = 0; i < len; i++) |
| { |
| traceEntry("PopStack"); |
| addTraceAttr(args[i]); |
| } |
| |
| return args; |
| } |
| |
| void print(Expr e) |
| { |
| if ( !verbose_mode ) |
| return; |
| PrintWriter pw = new PrintWriter(System.out); |
| printssa(e, pw); |
| pw.flush(); |
| } |
| |
| void printabc(Expr e, PrintWriter out) |
| { |
| if ( verbose_mode ) |
| out.println(formatExprAsAbc(e)); |
| } |
| |
| String formatExprAsAbc(Expr e) |
| { |
| StringBuilder s = new StringBuilder(); |
| s.append(" " + opNames[e.op]); |
| if (e.imm != null) |
| { |
| s.append('<'); |
| for (int i: e.imm) |
| s.append(i).append(','); |
| s.setCharAt(s.length()-1,'>'); |
| } |
| if (e.succ != null) |
| s.append(format('[',e.succ,']')); |
| if (e.value != null) |
| { |
| s.append(" "); |
| s.append(formatObject(e.value)); |
| } |
| if (e.ref != null) |
| { |
| s.append(" "); |
| s.append(e.ref); |
| } |
| |
| return s.toString(); |
| } |
| |
| void printssa(Expr e, PrintWriter out) |
| { |
| out.println(formatExpr(e)); |
| } |
| |
| String formatExpr(Expr e) |
| { |
| if ( null == e ) |
| { |
| return "null"; |
| } |
| |
| StringBuffer outBuffer = new StringBuffer(); |
| |
| outBuffer.append(e.toString()); |
| if (e.onStack() || e.inLocal() || e.onScope()) |
| outBuffer.append(" ="); |
| else |
| outBuffer.append(" "); |
| |
| if (e.value == null) |
| outBuffer.append(" "+opNames[e.op]); |
| if (e.imm != null) |
| { |
| outBuffer.append('<'); |
| for (int i: e.imm) |
| outBuffer.append(i).append(','); |
| outBuffer.setCharAt(outBuffer.length()-1,'>'); |
| } |
| if (e.args.length > 0) |
| outBuffer.append(format('(',e.args,')')); |
| if (e.locals.length > 0) |
| outBuffer.append(format('(',e.locals,')')); |
| if (e.scopes.length > 0) |
| outBuffer.append(format('{',e.scopes,'}')); |
| if (e.pred.length>0) |
| outBuffer.append(format('[',e.pred,']')); |
| if (e.succ != null) |
| outBuffer.append(format('[',e.succ,']')); |
| if (e.value != null) |
| outBuffer.append(formatObject(e.value)); |
| if (e.ref != null) |
| outBuffer.append(" "+e.ref);//.format()); // full name |
| |
| return outBuffer.toString(); |
| } |
| |
| void print(Object value, PrintWriter out) |
| { |
| out.print(formatObject(value)); |
| } |
| |
| String formatObject(Object value) |
| { |
| if (value instanceof String) |
| return(" \"" + ((String)value).replace("\n","\\n").replace("\r","\\r") + "\""); |
| else |
| return(" "+ value); |
| } |
| |
| void printMethod(Method m, String banner) |
| { |
| if ( ! verbose_mode ) |
| return; |
| |
| PrintWriter pw = new PrintWriter(System.out); |
| |
| pw.println(); |
| pw.println(); |
| |
| pw.println(banner); |
| pw.println("\t"+m.getName()+" local_count="+m.local_count+" max_stack="+m.max_stack+" max_scope="+m.max_scope); |
| |
| Deque<Block> blocks = dfs(m.entry.to); |
| |
| pw.println(blocks); |
| |
| for (Block b: blocks) |
| print(b,pw); |
| pw.println(); |
| pw.flush(); |
| } |
| |
| void printabc(Deque<Block> blocks) |
| { |
| if ( ! verbose_mode ) |
| return; |
| verboseStatus(blocks); |
| PrintWriter pw = new PrintWriter(System.out); |
| for (Block b: blocks) |
| printabc(b,pw); |
| pw.flush(); |
| } |
| |
| void print(Block b, PrintWriter pw) |
| { |
| pw.println(); |
| printssa(b, pw); |
| } |
| |
| void printabc(Block b, PrintWriter out) |
| { |
| out.println(); |
| out.println(b); |
| if (b.xsucc.length > 0) |
| out.println(Arrays.toString(b.xsucc)); |
| for (Expr s: b) |
| printabc(s,out); |
| } |
| |
| void printssa(Block b, PrintWriter out) |
| { |
| out.println(b); |
| if (b.xsucc.length > 0) |
| out.println(Arrays.toString(b.xsucc)); |
| for (Expr s: b) |
| printssa(s,out); |
| } |
| |
| void dot(String suffix, Method m) |
| { |
| // don't bother with single-block methods. |
| if (m.entry.to.succ().length == 0) |
| return; |
| |
| // save a dot file for the method |
| try |
| { |
| PrintWriter out = new PrintWriter(new FileWriter(m.getName()+suffix+".dot")); |
| try |
| { |
| Deque<Block> code = dfs(m.entry.to); |
| |
| out.println("digraph {"); |
| out.println("compound=true;"); |
| out.println("label=\""+m.getName()+suffix+"\";"); |
| out.println("labelloc=top;"); |
| out.println("fontsize=10;"); |
| |
| if (SHOW_DFG) |
| { |
| out.println("ranksep=.1; nodesep=.1;"); |
| out.println("node [shape=plaintext,width=.05,height=.05,fontsize=12];"); |
| } |
| else |
| { |
| out.println("ranksep=.25; nodesep=.25;"); |
| out.println("node [shape=box,width=.1,height=.1,fontsize=12];"); |
| } |
| |
| out.println("edge [arrowsize=.5,fontsize=8,labelfontsize=8];"); |
| |
| for (Block b: code) |
| if (SHOW_DFG) |
| dot_dfg(b,out); |
| else |
| dot(b,out); |
| |
| if ( SHOW_DOMINATORS ) |
| { |
| Map<Block,Block> doms = idoms(code,allpreds(code)); |
| out.println("node [shape=box];"); |
| out.println("subgraph cluster1 { label=\"Dominators\"; color=white; "); |
| for (Block b: dfs(m.entry.to)) |
| { |
| out.println("D"+b+" [label="+b+"];"); |
| if (doms.containsKey(b)) |
| out.println("D"+doms.get(b)+" -> D"+b); |
| } |
| out.println("}"); |
| } |
| |
| out.println("}"); |
| } |
| finally |
| { |
| out.close(); |
| } |
| } |
| catch (IOException e) |
| { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| static class LabelWriter extends PrintWriter |
| { |
| StringWriter w; |
| LabelWriter(StringWriter w) |
| { |
| super(w); |
| this.w = w; |
| } |
| public void println() { print("\\l"); flush(); } |
| public void print(String s) |
| { |
| super.print(s.replace("\"","''").replace("\u0278","φ")); |
| } |
| public String toString() |
| { |
| return w.toString(); |
| } |
| } |
| |
| void dot(Block b, PrintWriter out) |
| { |
| LabelWriter w = new LabelWriter(new StringWriter()); |
| |
| // create the label |
| if (SHOW_CODE) |
| printssa(b,w); |
| else |
| w.print(b); |
| String attr = "label=\""+w+"\""; |
| |
| out.println(b+" ["+attr+"];"); |
| |
| // CFG edges |
| for (Edge e: b.succ()) |
| dot(e, out); |
| for (Edge e: b.xsucc) |
| dot(e, out); |
| } |
| |
| void dot_dfg(Block b, PrintWriter out) |
| { |
| LabelWriter w = new LabelWriter(new StringWriter()); |
| |
| // create the label |
| w.print(b); |
| String attr = "label=\""+w+"\"; labeljust=l"; |
| |
| // dfg edges |
| out.println("subgraph cluster"+b+" { "+attr+";"); |
| Expr n = null; |
| Iterator<Expr> i = b.iterator(); |
| if (i.hasNext()) |
| n = i.next(); |
| while (n != null) |
| { |
| Expr e = n; |
| w = new LabelWriter(new StringWriter()); |
| printssa(e,w); |
| out.print("E"+e.id+" [label=\""+w+"\"];"); |
| if (i.hasNext()) |
| { |
| n = i.next(); |
| out.print("E"+e.id+" -> E"+n.id+" [style=invisible,arrowhead=none,weight=4];"); |
| } |
| else |
| { |
| n = null; |
| } |
| } |
| out.println("}"); |
| for (Expr e: b) |
| { |
| for (Expr a: e.args) |
| out.print("E"+a.id+" -> E"+e.id+" [color=green];"); |
| for (Expr a: e.locals) |
| out.print("E"+a.id+" -> E"+e.id+" [color=green];"); |
| for (Expr a: e.scopes) |
| out.print("E"+a.id+" -> E"+e.id+" [color=grey,style=dashed];"); |
| if (e.isPx()) |
| { |
| for (Edge x: b.xsucc) |
| { |
| //out.print("E"+e.id+" -> E"+x.to.first().id+ |
| // " [weight=2,style=dashed,ltail=cluster"+x.from+",lhead=cluster"+x.to+"];"); |
| out.print("E"+e.id+" -> E"+x.to.first().id+ |
| " [weight=2,style=dashed,color=red];"); |
| } |
| } |
| } |
| for (Edge s: b.succ()) |
| { |
| Expr e = b.last(); |
| int weight = s==b.last().succ[0] ? 4 : 2; |
| //out.print("E"+e.id+" -> E"+s.to.first().id+ |
| // " [weight="+weight+",ltail=cluster"+s.from+",lhead=cluster"+s.to+"];"); |
| out.print("E"+e.id+" -> E"+s.to.first().id+ |
| " [weight="+weight+"];"); |
| } |
| } |
| |
| void dot(Edge e, PrintWriter out) |
| { |
| ArrayList<String> attrs = new ArrayList<String>(); |
| if (e.isThrowEdge()) |
| { |
| attrs.add("style=dashed"); |
| } |
| else |
| { |
| // TODO smarter edge classification |
| if (e.isBackedge()) |
| attrs.add("tailport=w,headport=w"); |
| |
| if (e.label == 0) |
| attrs.add("weight=2"); |
| else |
| attrs.add("taillabel=\""+e.label+"\""); |
| } |
| out.println(e.from + " -> " + e.to + " "+attrs+";"); |
| } |
| |
| void verboseStatus(String msg) |
| { |
| if ( verbose_mode ) |
| System.out.println(msg); |
| } |
| |
| void verboseStatus(Object o) |
| { |
| if ( verbose_mode ) |
| System.out.println(o.toString()); |
| } |
| |
| class LocalVarState |
| { |
| /** |
| * Simulated verifier frame state; |
| * only locals are simulated. |
| */ |
| Typeref[] fs_out; |
| |
| /** |
| * Expected types of input registers. |
| */ |
| Typeref[] fs_in; |
| |
| Typeref[] hard_coercions; |
| |
| /** |
| * Live-Out variables are variables that are |
| * used (without being redefined) in a successor |
| * block. Note: the relationship is transitive. |
| */ |
| private BitSet liveout = new BitSet(); |
| |
| /** |
| * Variables killed in this block via explicit OP_kill insn. |
| */ |
| private BitSet killed_vars = new BitSet(); |
| |
| /** |
| * Variables defined in this block. |
| * @warn denormalized from defining_exprs |
| */ |
| private BitSet def = new BitSet(); |
| |
| /** |
| * Upwardly-exposed variables, read in this block |
| * before any definition. This means that their |
| * value comes from predecessor blocks. |
| */ |
| private BitSet ue_vars = new BitSet(); |
| |
| /** |
| * Variables read in block after their definition. This means |
| * the variable can't be cast at its definition site, although |
| * it could if the read's type agreed with the successors'. |
| */ |
| private BitSet read_after_def = new BitSet(); |
| |
| /** |
| * Generating expresssions for variable definitions; these are |
| * candidates for coercion before the variable's set, saving a |
| * considerable amount of hassle down the CFG. |
| */ |
| private Map<Integer, Expr> generating_exprs = new HashMap<Integer, Expr>(); |
| |
| /** |
| * Set on backwards branch targets, catch |
| * targets, etc -- the verifier will use |
| * more conservative type assumptions. |
| */ |
| private boolean conservative_verifier_rules; |
| |
| private Method m; |
| private Block b; |
| |
| /** |
| * Construct a LocalVariableState; compute its |
| * def, kill, and upwardly-exposed sets; and |
| * compute its frame state. |
| * @param m - the Method under analysis. |
| * @param b - the Block to which this analysis applies. |
| * @param initial_frame_state - the previously computed frame state. |
| * @param is_scheduled_backedge |
| */ |
| LocalVarState(Method m, Block b, Typeref[] initial_frame_state) |
| { |
| this.conservative_verifier_rules = b.is_backwards_branch_target; |
| |
| this.fs_in = new Typeref[initial_frame_state.length]; |
| |
| if ( ! this.conservative_verifier_rules ) |
| { |
| System.arraycopy(initial_frame_state, 0, this.fs_in, 0, initial_frame_state.length); |
| } |
| else |
| { |
| for ( int i = 0; i < m.local_count; i ++ ) |
| { |
| this.fs_in[i] = initial_frame_state[i].nullable(); |
| } |
| for ( int i = m.local_count + m.max_scope; i < this.fs_in.length; i++) |
| { |
| this.fs_in[i] = initial_frame_state[i].nullable(); |
| } |
| } |
| |
| this.fs_out = new Typeref[initial_frame_state.length]; |
| System.arraycopy(this.fs_in, 0, this.fs_out, 0, this.fs_in.length); |
| |
| |
| this.hard_coercions = new Typeref[initial_frame_state.length]; |
| |
| this.m = m; |
| this.b = b; |
| |
| // Keep a shadow of the frame state so that it's only dumped when it changes. |
| Typeref[] saved_fs = null; |
| |
| if ( verbose_mode ) |
| { |
| verboseStatus(b); |
| StringBuffer verbose_succ = new StringBuffer(); |
| verbose_succ.append("\tsucc: "); |
| for ( Edge p: b.succ()) |
| { |
| verbose_succ.append(p); |
| verbose_succ.append(" "); |
| } |
| verboseStatus(verbose_succ); |
| dumpFrameState(fs_out); |
| saved_fs = new Typeref[initial_frame_state.length]; |
| System.arraycopy(fs_out, 0, saved_fs, 0, fs_out.length); |
| } |
| |
| |
| for ( Expr e: b.exprs) |
| { |
| if ( verbose_mode ) |
| verboseStatus(formatExprAsAbc(e)); |
| |
| switch(e.op) |
| { |
| case OP_getlocal0: |
| case OP_getlocal1: |
| case OP_getlocal2: |
| case OP_getlocal3: |
| { |
| uses( e.op - OP_getlocal0); |
| break; |
| } |
| case OP_getlocal: |
| { |
| uses(e.imm[0]); |
| break; |
| } |
| |
| case OP_setlocal0: |
| case OP_setlocal1: |
| case OP_setlocal2: |
| case OP_setlocal3: |
| { |
| defines( e.op - OP_setlocal0, e ); |
| break; |
| } |
| |
| case OP_setlocal: |
| { |
| defines(e.imm[0], e); |
| break; |
| } |
| |
| case OP_hasnext2: |
| { |
| uses(e.imm[0]); |
| uses(e.imm[1]); |
| |
| expectsType(e.imm[0], ANY().ref); |
| expectsType(e.imm[1], INT().ref); |
| hard_coercions[e.imm[0]] = ANY().ref; |
| |
| defines(e.imm[0], e); |
| break; |
| } |
| |
| case OP_kill: |
| { |
| setKilled(e.imm[0]); |
| break; |
| } |
| |
| case OP_inclocal: |
| case OP_inclocal_i: |
| case OP_declocal: |
| case OP_declocal_i: |
| { |
| uses(e.imm[0]); |
| break; |
| } |
| |
| case OP_getslot: |
| case OP_setslot: |
| { |
| Expr stem = e.args[0]; |
| if ( stem.inLocal() ) |
| { |
| expectsType(stem.imm[0], m.verifier_types.get(stem)); |
| } |
| break; |
| } |
| |
| case OP_nextvalue: |
| case OP_nextname: |
| { |
| // TODO: Anything to do here? |
| break; |
| } |
| |
| default: |
| assert(!e.inLocal()); |
| } |
| |
| if ( verbose_mode ) |
| { |
| for ( int i = 0; i < fs_out.length; i++ ) |
| { |
| if ( saved_fs[i] != fs_out[i] ) |
| { |
| dumpFrameState(fs_out); |
| System.arraycopy(fs_out, 0, saved_fs, 0, fs_out.length); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| private void expectsType(int i, Typeref expected_type) |
| { |
| // If the variable's defined in this block, |
| // then it doesn't need coercion. |
| if ( !( def.get(i) || this.fs_in[i].t.equals(expected_type.t) ) ) |
| { |
| // TODO: track these coerced locals with a new bit set |
| // and cross check to ensure they're all livein. |
| verboseStatus("\texpectsType " + i + " " + expected_type); |
| this.fs_in[i] = this.fs_out[i] = expected_type; |
| } |
| } |
| |
| /** |
| * @param reg - the local of interest. |
| * @return the live-in type the verifier will expect |
| * to see on entry to the block. |
| */ |
| public Typeref getInitialType(int reg) |
| { |
| return fs_in[reg]; |
| } |
| |
| /** |
| * @param reg - the local of interest. |
| * @return the type the local has at the end of this block. |
| */ |
| public Typeref getFinalType(int reg) |
| { |
| return fs_out[reg]; |
| } |
| |
| public BitSet getKilled() |
| { |
| return (BitSet)this.killed_vars.clone(); |
| } |
| |
| private boolean mergeLiveout(BitSet next_liveout) |
| { |
| next_liveout.or(this.liveout); |
| boolean result = !this.liveout.equals(next_liveout); |
| |
| if ( result ) |
| { |
| this.liveout = next_liveout; |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Registers contributed by predecessor blocks. |
| * @return (liveout - def) + UE |
| */ |
| BitSet getLivein() |
| { |
| BitSet result = (BitSet)this.liveout.clone(); |
| result.andNot(this.def); |
| result.or(this.ue_vars); |
| return result; |
| } |
| |
| /** |
| * Locals still active at the end of a block. |
| * @note changes as variables are killed. |
| * @return (UE + def) - killed |
| */ |
| BitSet getActiveVariables() |
| { |
| BitSet result = getLivein(); |
| result.or(this.def); |
| |
| if ( b.equals(m.entry.to)) |
| { |
| // Note parameters as active. |
| for ( int i = 0; i < m.getParams().length; i++) |
| result.set(i); |
| } |
| result.andNot(this.killed_vars); |
| return result; |
| } |
| |
| BitSet getLiveout() |
| { |
| return (BitSet)liveout.clone(); |
| } |
| |
| BitSet getDefined() |
| { |
| return (BitSet)this.def.clone(); |
| } |
| |
| private void uses(int varnum) |
| { |
| if ( !def.get(varnum) ) |
| { |
| if ( !isAPriori(varnum) ) |
| ue_vars.set(varnum); |
| } |
| else |
| { |
| read_after_def.set(varnum); |
| } |
| } |
| |
| /** |
| * Is this local a routine parameter? |
| * If so, it is a priori present and |
| * shouldn't be considered upwardly exposed. |
| * @param varnum - the local of interest. |
| * @return true if the local's a parameter. |
| */ |
| private boolean isAPriori(int varnum) |
| { |
| return (this.b.equals(this.m.entry.to) && varnum < this.m.getParams().length); |
| } |
| |
| private void defines(int varnum, Expr generating_expr) |
| { |
| fs_out[varnum] = definingType(generating_expr); |
| def.set(varnum); |
| generating_exprs.put(varnum, generating_expr); |
| |
| killed_vars.clear(varnum); |
| read_after_def.clear(varnum); |
| } |
| |
| private Typeref definingType(Expr e) |
| { |
| Typeref result = null; |
| |
| if ( OP_hasnext2 == e.op ) |
| { |
| result = ANY().ref; |
| } |
| else if ( OP_setlocal == e.op ) |
| { |
| Expr value = e.args[0]; |
| |
| if ( OP_getlocal == value.op ) |
| { |
| result = fs_out[value.imm[0]]; |
| } |
| else |
| { |
| result = verify_eval(this.m, value, m.verifier_types, null); |
| } |
| } |
| else |
| { |
| assert(false); |
| } |
| |
| assert ( result != null ); |
| return result; |
| } |
| |
| public void setKilled(int varnum) |
| { |
| assert(!killed_vars.get(varnum)); |
| killed_vars.set(varnum); |
| generating_exprs.remove(varnum); |
| } |
| } |
| |
| |
| static class TypeConstraints implements Comparable |
| { |
| Set<Integer> killregs = new TreeSet<Integer>(); |
| Map<Integer, Typeref> coercions = new HashMap<Integer, Typeref>(); |
| |
| /** |
| * Path to the destination block. |
| */ |
| Edge path; |
| |
| /** |
| * The original destination block. |
| * May be different than path.to if |
| * the path is split during fixup. |
| * @see compareTo |
| */ |
| Block dest_block; |
| |
| TypeConstraints(Edge path) |
| { |
| this.path = path; |
| if ( path != null) |
| this.dest_block = path.to; |
| } |
| |
| void takeConstraintFrom(TypeConstraints tc, Integer r) |
| { |
| if ( killregs.contains(r) ) |
| { |
| assert(tc.killregs.contains(r)); |
| tc.killregs.remove(r); |
| } |
| else if ( coercions.containsKey(r) ) |
| { |
| assert(tc.coercions.containsKey(r) && tc.coercions.get(r).t.isMachineCompatible(this.coercions.get(r).t)); |
| tc.coercions.remove(r); |
| } |
| else if ( tc.killregs.contains(r) ) |
| { |
| this.killregs.add(r); |
| tc.killregs.remove(r); |
| } |
| else if ( tc.coercions.containsKey(r) ) |
| { |
| this.coercions.put(r, tc.coercions.get(r)); |
| tc.coercions.remove(r); |
| } |
| else |
| throw new IllegalStateException("neither constraint set contains local " + r); |
| } |
| |
| boolean agreesWith(TypeConstraints tc, Integer r) |
| { |
| if ( this.killregs.contains(r)) |
| return tc.killregs.contains(r); |
| else if ( this.coercions.containsKey(r)) |
| return tc.coercions.containsKey(r) && this.coercions.get(r).t.isMachineCompatible((tc.coercions.get(r).t)); |
| |
| // Local not found in this constraint set; |
| // by definition, the sets are in conflict |
| // with respect to this local. |
| return false; |
| } |
| |
| void addKill(int reg) |
| { |
| assert(!coercions.containsKey(reg)); |
| this.killregs.add(reg); |
| } |
| |
| void addCoercion(int reg, Typeref ty) |
| { |
| assert(!coercions.containsKey(reg) && !killregs.contains(reg)); |
| coercions.put(reg, ty); |
| } |
| |
| public boolean equals(Object o) |
| { |
| return 0 == compareTo(o); |
| } |
| |
| public int compareTo(Object arg0) |
| { |
| if ( ! ( arg0 instanceof TypeConstraints ) ) |
| return -1; |
| |
| TypeConstraints other = (TypeConstraints) arg0; |
| |
| if ( dest_block.id != other.dest_block.id ) |
| return (dest_block.id > other.dest_block.id)? 1:-1; |
| |
| if ( killregs.size() > other.killregs.size()) |
| return 1; |
| else if ( other.killregs.size() > this.killregs.size()) |
| return -1; |
| |
| if ( coercions.size() > other.coercions.size() ) |
| return 1; |
| else if ( other.coercions.size() > this.coercions.size()) |
| return -1; |
| |
| |
| for ( Integer x: this.killregs ) |
| if ( ! other.killregs.contains(x)) |
| return 1; |
| for ( Integer y: other.killregs ) |
| if ( ! this.killregs.contains(y)) |
| return -1; |
| |
| for ( Integer r: this.coercions.keySet() ) |
| { |
| Typeref this_ctype = coercions.get(r); |
| Typeref other_ctype = other.coercions.get(r); |
| |
| if ( null == other_ctype ) |
| return 1; |
| |
| if ( !this_ctype.equals(other_ctype) ) |
| return ( this_ctype.hashCode() > other_ctype.hashCode() )? 1: -1; |
| } |
| |
| for ( Integer o_r: other.coercions.keySet()) |
| if ( !this.coercions.containsKey(o_r)) |
| return -1; |
| |
| return 0; |
| } |
| } |
| |
| class Reader |
| { |
| int pos; |
| byte[] abc; |
| |
| Reader(int pos, byte[] abc) |
| { |
| this.pos = pos; |
| this.abc = abc; |
| } |
| |
| Reader(Reader r) |
| { |
| this(r.pos, r.abc); |
| } |
| |
| int readU8() |
| { |
| return 255 & abc[pos++]; |
| } |
| |
| int readU16() |
| { |
| return readU8() | readU8() << 8; |
| } |
| |
| int readS24() |
| { |
| return readU16() | ((byte) readU8()) << 16; |
| } |
| |
| int readU30() |
| { |
| int result = readU8(); |
| if (0==(result & 0x00000080)) |
| return result; |
| result = result & 0x0000007f | readU8()<<7; |
| if (0==(result & 0x00004000)) |
| return result; |
| result = result & 0x00003fff | readU8()<<14; |
| if (0==(result & 0x00200000)) |
| return result; |
| result = result & 0x001fffff | readU8()<<21; |
| if (0==(result & 0x10000000)) |
| return result; |
| return result & 0x0fffffff | readU8()<<28; |
| } |
| |
| double readDouble() |
| { |
| return Double.longBitsToDouble(readU16() | ((long)readU16())<<16 | |
| ((long)readU16())<<32 | ((long)readU16())<<48); |
| } |
| } |
| |
| class AbcWriter extends ByteArrayOutputStream |
| { |
| void rewind(int n) |
| { |
| super.count -= n; |
| } |
| void writeU16(int i) |
| { |
| write(i); |
| write(i>>8); |
| } |
| |
| void writeS24(int i) |
| { |
| writeU16(i); |
| write(i>>16); |
| } |
| |
| void write64(long i) |
| { |
| writeS24((int)i); |
| writeS24((int)(i>>24)); |
| writeU16((int)(i>>48)); |
| } |
| |
| void writeU30(int v) |
| { |
| if (v < 128 && v >= 0) |
| { |
| write(v); |
| } |
| else if (v < 16384 && v >= 0) |
| { |
| write(v & 0x7F | 0x80); |
| write(v >> 7); |
| } |
| else if (v < 2097152 && v >= 0) |
| { |
| write(v & 0x7F | 0x80); |
| write(v >> 7 | 0x80); |
| write(v >> 14); |
| } |
| else if (v < 268435456 && v >= 0) |
| { |
| write(v & 0x7F | 0x80); |
| write(v >> 7 | 0x80); |
| write(v >> 14 | 0x80); |
| write(v >> 21); |
| } |
| else |
| { |
| write(v & 0x7F | 0x80); |
| write(v >> 7 | 0x80); |
| write(v >> 14 | 0x80); |
| write(v >> 21 | 0x80); |
| write(v >> 28); |
| } |
| } |
| |
| int sizeOfU30(int v) |
| { |
| if (v < 128 && v >= 0) |
| { |
| return 1; |
| } |
| else if (v < 16384 && v >= 0) |
| { |
| return 2; |
| } |
| else if (v < 2097152 && v >= 0) |
| { |
| return 3; |
| } |
| else if (v < 268435456 && v >= 0) |
| { |
| return 4; |
| } |
| else |
| { |
| return 5; |
| } |
| } |
| } |
| } |