blob: 3e5cecd8c7276fd5ec9e840b747dad6db6134cbb [file] [log] [blame]
/*
*
* 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.*;
import java.util.*;
import static macromedia.asc.embedding.avmplus.ActionBlockConstants.*;
import adobe.abc.GlobalOptimizer.*;
import static adobe.abc.OptimizerConstants.*;
import static java.lang.Boolean.TRUE;
import static java.lang.Boolean.FALSE;
public class AbcThunkGen
{
static class IndentingPrintWriter extends PrintWriter
{
int indent;
IndentingPrintWriter(Writer w)
{
super(w);
}
public void println()
{
for (int i=0; i < indent; i++)
print(" ");
super.println();
}
public void println(String s)
{
for (int i=0; i < indent; i++)
print(" ");
super.println(s);
}
}
public static void main(String[] args) throws IOException
{
if (args.length == 0)
{
System.out.println("usage: AbcThunkGen [-import foo.abc] bar.abc");
return;
}
byte[] abcdata = null;
InputAbc ia = null;
String filename = null;
GlobalOptimizer go = new GlobalOptimizer();
go.ALLOW_NATIVE_CTORS = true;
for (int i = 0; i < args.length; ++i)
{
if(args[i].equals("-import"))
{
i++;
InputAbc imported = go.new InputAbc();
imported.readAbc(load(args[i]));
continue;
}
if (ia != null)
{
throw new RuntimeException("only one abc file may be specified");
}
filename = args[i];
abcdata = load(filename);
ia = go.new InputAbc();
ia.readAbc(abcdata);
}
if (ia == null)
{
throw new RuntimeException("an abc file must be specified");
}
String scriptname = filename.substring(0,filename.lastIndexOf('.'));
emitNatives(ia, abcdata, scriptname, TypeCache.instance().namespaceNames);
}
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();
}
}
private InputAbc abc;
private byte[] abcdata;
private String name;
private Map<Namespace,Name> namespaceNames;
private PrintWriter out_h;
private IndentingPrintWriter out_c;
private Map<Integer,String> native_methods;
private HashMap<Type,Integer> class_id_map;
private HashMap<String, HashMap<String, Method>> unique_thunks;
AbcThunkGen(InputAbc abc, byte[] abcdata, String name, Map<Namespace,Name> namespaceNames,
PrintWriter out_h, IndentingPrintWriter out_c)
{
this.abc = abc;
this.abcdata = abcdata;
this.name = name;
this.namespaceNames = namespaceNames;
this.out_h = out_h;
this.out_c = out_c;
this.native_methods = new TreeMap<Integer,String>();
this.unique_thunks = new HashMap<String, HashMap<String, Method>>();
this.class_id_map = new HashMap<Type,Integer>();
for (int i = 0; i < abc.classes.length; ++i)
this.class_id_map.put(abc.classes[i], i);
}
static void emitNatives(InputAbc abc, byte[] abcdata, String name,
Map<Namespace,Name> namespaceNames) throws IOException
{
PrintWriter out_h = new PrintWriter(new FileWriter(name+".h2"));
IndentingPrintWriter out_c = new IndentingPrintWriter(new FileWriter(name+".cpp2"));
try
{
AbcThunkGen ngen = new AbcThunkGen(abc, abcdata, name, namespaceNames, out_h, out_c);
ngen.emit();
}
finally
{
out_c.close();
out_h.close();
}
}
private void emit()
{
// header file - definitions & native method decls
out_h.println("/* machine generated file -- do not edit */");
out_c.println("/* machine generated file -- do not edit */");
out_h.println("#define AVMTHUNK_VERSION 1");
out_h.printf("const uint32_t %s_abc_class_count = %d;\n",name,abc.classes.length);
out_h.printf("const uint32_t %s_abc_script_count = %d;\n",name,abc.scripts.length);
out_h.printf("const uint32_t %s_abc_method_count = %d;\n",name,abc.methods.length);
out_h.printf("const uint32_t %s_abc_length = %d;\n",name,abcdata.length);
out_h.printf("extern const uint8_t %s_abc_data[%d];\n",name,abcdata.length);
for (int i = 0; i < abc.scripts.length; ++i)
{
Type s = abc.scripts[i];
// not enough info in the ABC to recover the original name (eg abcpackage_Foo_as)
// so output identifiers based on the native script functions found
for (Binding bb : s.defs.values())
{
if (bb.method != null && bb.method.isNative())
out_h.println("const uint32_t abcscript_"+ bb.getName() + " = " + i + ";");
}
emitSourceTraits("", s);
}
out_c.println("// "+unique_thunks.size()+" unique thunks");
for (String sig: unique_thunks.keySet())
{
out_c.println();
HashMap<String, Method> users = unique_thunks.get(sig);
assert(users.size() > 0);
Method m = null;
for (String native_name: users.keySet())
{
out_c.println("// "+native_name);
m = users.get(native_name);
}
String thunkname = name+"_"+sig;
// emit both with and without-cookie versions, since we can't tell at this point which
// might be used for a particular method. rely on linker to strip the unused ones.
emitThunk(thunkname, m, false);
emitThunk(thunkname, m, true);
for (String native_name: users.keySet())
{
m = users.get(native_name);
out_h.printf(" const uint32_t %s = %d;\n", native_name, m.id);
// use #define here (rather than constants) to avoid the linker including them and thus preventing dead-stripping
// (sad but true, happens in some environments)
out_h.printf(" #define %s_thunk %s_thunk\n", native_name, thunkname);
out_h.printf(" #define %s_thunkc %s_thunkc\n", native_name, thunkname);
}
}
// cpp file - abc data, thunks
out_c.println("const uint8_t "+name+"_abc_data["+abcdata.length+"] = {");
for (int i=0, n=abcdata.length; i < n; i++)
{
int x = abcdata[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.println("};");
}
void emitSourceTraits(String prefix, Type s)
{
if (s.init != null && s.init.isNative())
{
String native_name = prefix + s.getName();
gatherThunk(native_name, s.init);
}
for (Binding b: s.defs.values())
{
Namespace ns = b.getName().nsset(0);
String id = prefix + propLabel(b, ns);
String ctype = null;
if (b.method != null)
{
if(b.method.isNative())
emitSourceMethod(prefix, b, ns);
}
else if (GlobalOptimizer.isClass(b))
{
emitSourceClass(b, ns);
}
}
}
static String to_cname(String nm)
{
// munge symbols that will make C unhappy
nm = nm.replace("+", "_");
nm = nm.replace("-", "_");
nm = nm.replace("?", "_");
nm = nm.replace("!", "_");
nm = nm.replace("<", "_");
nm = nm.replace(">", "_");
nm = nm.replace("=", "_");
nm = nm.replace("(", "_");
nm = nm.replace(")", "_");
nm = nm.replace("\"", "_");
nm = nm.replace("'", "_");
nm = nm.replace("*", "_");
nm = nm.replace(" ", "_");
nm = nm.replace(".", "_");
nm = nm.replace("$", "_");
nm = nm.replace(":", "_");
nm = nm.replace("/", "_");
return nm;
}
void emitSourceClass(Binding b, Namespace ns)
{
String label = ns_prefix(ns, true) + to_cname(b.getName().name);
Type c = b.type.t;
out_h.println();
out_h.println("const uint32_t abcclass_"+ label + " = " + class_id_map.get(c) + ";");
emitSourceTraits(label+"_", c);
emitSourceTraits(label+"_", c.itype);
}
String ctype_i(int ctype, boolean allowObject)
{
switch (ctype)
{
case CTYPE_OBJECT:
if (allowObject)
return "AvmObject";
// else fall thru
case CTYPE_ATOM:
return "AvmBox";
case CTYPE_VOID:
return "void";
case CTYPE_BOOLEAN:
return "AvmBoolArg";
case CTYPE_INT:
return "int32_t";
case CTYPE_UINT:
return "uint32_t";
case CTYPE_DOUBLE:
return "double";
case CTYPE_STRING:
return "AvmString";
case CTYPE_NAMESPACE:
return "AvmNamespace";
default:
assert(false);
return "";
}
}
void emitSourceMethod(String prefix, Binding b, Namespace ns)
{
Method m = b.method;
String native_name = prefix + propLabel(b, ns);
if (GlobalOptimizer.isGetter(b))
native_name += "_get";
else if (GlobalOptimizer.isSetter(b))
native_name += "_set";
gatherThunk(native_name, m);
}
String ns_prefix(Namespace ns, boolean iscls)
{
if (!ns.isPublic() && !ns.isInternal())
{
if (ns.isPrivate() && !iscls) return "private_";
if (ns.isProtected()) return "protected_";
if (namespaceNames.containsKey(ns)) return namespaceNames.get(ns) + "_";
}
String p = to_cname(ns.uri);
if (p.length() > 0) p += "_";
return p;
}
String propLabel(Binding b, Namespace ns)
{
return ns_prefix(ns, false) + b.getName().name;
}
int defValCType(Object value)
{
if (value instanceof Integer)
return CTYPE_INT;
if (value instanceof Long)
return CTYPE_UINT;
if (value instanceof Double)
{
double d = (Double)value;
if (d == (int)(d))
return CTYPE_INT;
if (d == (long)(d))
return CTYPE_UINT;
return CTYPE_DOUBLE;
}
if (value instanceof String)
return CTYPE_STRING;
// sorry, not supported in natives
// if (value instanceof Namespace)
// return CTYPE_NAMESPACE;
if (value == TRUE)
return CTYPE_BOOLEAN;
if (value == FALSE)
return CTYPE_BOOLEAN;
if (value.toString() == "undefined")
return CTYPE_ATOM;
if (value.toString() == "null")
return CTYPE_ATOM;
throw new RuntimeException("unsupported default-value type "+value.toString());
}
String defValStr(Object value)
{
// for numeric values, just emit inline rather than looking up in const pool
if (value instanceof Integer)
return value.toString();
if (value instanceof Long)
return value.toString();
if (value instanceof Double)
{
double d = (Double)value;
if (d == (int)(d))
return Integer.toString((int)d, 10);
if (d == (long)(d))
return Long.toString((long)d, 10) + "U";
if (Double.isInfinite(d))
return (d < 0.0) ? "kAvmThunkNegInfinity" : "kAvmThunkInfinity";
if (Double.isNaN(d))
return "kAvmThunkNaN";
return value.toString();
}
if (value instanceof String)
{
for (int i = 0; i < abc.strings.length; ++i)
if (abc.strings[i].equals(value))
return "AvmThunkConstant_AvmString("+Integer.toString(i, 10)+")/* \""+abc.strings[i]+"\" */";
}
// sorry, not supported in natives
// if (value instanceof Namespace)
// return value.toString();
if (value == TRUE)
return "true";
if (value == FALSE)
return "false";
if (value.toString() == "undefined")
return "kAvmThunkUndefined";
if (value.toString() == "null")
return "kAvmThunkNull";
throw new RuntimeException("unsupported default-value type "+value.toString());
}
int get_optional_count(Method m)
{
int optional_count = 0;
if (m.values != null)
for (Object v : m.values)
if (v != null)
optional_count++;
return optional_count;
}
String sigChar(int ctype, boolean allowObject)
{
switch (ctype)
{
case CTYPE_OBJECT:
if (allowObject)
return "o";
// else fall thru
case CTYPE_ATOM:
return "a";
case CTYPE_VOID:
return "v";
case CTYPE_BOOLEAN:
return "b";
case CTYPE_INT:
return "i";
case CTYPE_UINT:
return "u";
case CTYPE_DOUBLE:
return "d";
case CTYPE_STRING:
return "s";
case CTYPE_NAMESPACE:
return "n";
default:
assert(false);
return "";
}
}
String thunkSig(Method m)
{
String sig = sigChar(m.returns.t.ctype, false)+"2";
if (m.returns.t.ctype == CTYPE_DOUBLE)
sig += sigChar(CTYPE_DOUBLE, false);
else
sig += sigChar(CTYPE_ATOM, false);
sig += "_";
for (int i = 0; i < m.getParams().length; ++i)
{
sig += sigChar(m.getParams()[i].t.ctype, true);
}
if (m.hasOptional())
{
int param_count = m.getParams().length - 1;
int optional_count = get_optional_count(m);
for (int i = param_count - optional_count + 1; i <= param_count; i++)
{
String dts = sigChar(defValCType(m.values[i]), true);
String defval = to_cname(defValStr(m.values[i]));
sig += "_opt" + dts + defval;
}
}
else
{
assert(get_optional_count(m) == 0);
}
if (m.needsRest())
sig += "_rest";
return sig;
}
void gatherThunk(String native_name, Method m)
{
native_methods.put(m.id, native_name);
String sig = thunkSig(m);
if (!unique_thunks.containsKey(sig))
unique_thunks.put(sig, new HashMap<String, Method>());
unique_thunks.get(sig).put(native_name, m);
}
void emitThunk(String name, Method m, boolean cookie)
{
String ret = ctype_i(m.returns.t.ctype, false);
out_h.printf("extern AvmThunkRetType_%s AVMTHUNK_CALLTYPE %s_thunk%s(AvmMethodEnv env, uint32_t argc, const AvmBox* argv);\n", ret, name, cookie?"c":"");
out_c.printf("AvmThunkRetType_%s AVMTHUNK_CALLTYPE %s_thunk%s(AvmMethodEnv env, uint32_t argc, const AvmBox* argv)\n", ret, name, cookie?"c":"");
out_c.println("{");
out_c.indent++;
int param_count = m.getParams().length-1;
assert(param_count >= 0);
int optional_count = get_optional_count(m);
assert(optional_count <= param_count);
String argszprev = "0";
for (int i = 0; i < m.getParams().length; ++i)
{
String cts = ctype_i(m.getParams()[i].t.ctype, true);
if (i == 0)
out_c.println("const uint32_t argoff0 = 0;");
else
out_c.println("const uint32_t argoff"+i+" = argoff"+(i-1)+" + "+argszprev+";");
argszprev = "AvmThunkArgSize_"+cts;
}
if (m.needsRest())
{
out_c.println("const uint32_t argoffV = argoff"+(m.getParams().length-1)+" + "+argszprev+";");
}
int argct = m.getParams().length + (cookie?1:0) + (m.needsRest()?2:0);
String[] argvals = new String[argct];
String[] argtypes = new String[argct];
int argsidx = 0;
for (int i = 0; i < m.getParams().length; ++i)
{
String cts = ctype_i(m.getParams()[i].t.ctype, true);
String val = "AvmThunkUnbox_"+cts+"(argv[argoff" + i + "])";
if (i > param_count - optional_count)
{
String dts = ctype_i(defValCType(m.values[i]), true);
String defval = defValStr(m.values[i]);
if (!dts.equals(cts))
defval = "AvmThunkCoerce_"+dts+"_"+cts+"("+defval+")";
val = "(argc < "+i+" ? "+defval+" : "+val+")";
}
argvals[argsidx] = val;
argtypes[argsidx] = cts;
argsidx++;
if (i == 0 && cookie)
{
argvals[argsidx] = "AVMTHUNK_GET_COOKIE(env)";
argtypes[argsidx] = "int32_t";
argsidx++;
}
}
if (m.needsRest())
{
argvals[argsidx] = "(argc <= "+param_count+" ? NULL : argv + argoffV)";
argtypes[argsidx] = "const AvmBox*";
argsidx++;
argvals[argsidx] = "(argc <= "+param_count+" ? 0 : argc - "+param_count+")";
argtypes[argsidx] = "uint32_t";
argsidx++;
}
if (!m.hasOptional() && !m.needsRest())
out_c.println("(void)argc;");
out_c.println("AVMTHUNK_DEBUG_ENTER(env)");
String call = "";
if (m.returns.t.ctype != CTYPE_VOID)
call += "const "+ret+" ret = ";
call += "AVMTHUNK_CALL_FUNCTION_"+(argvals.length-1)+"(AVMTHUNK_GET_HANDLER(env), "+ret;
out_c.println(call);
out_c.indent++;
for (int i = 0; i < argvals.length; ++i)
{
out_c.println(", " + argtypes[i] + ", " + argvals[i]);
}
out_c.indent--;
out_c.println(");");
out_c.println("AVMTHUNK_DEBUG_EXIT(env)");
out_c.println("return AvmToRetType_"+ret+"(ret);");
out_c.indent--;
out_c.println("}");
}
}