Java tutorial
/* * Copyright 2014-2016 Samsung Research America, Inc. * * Licensed 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. */ /** * Last-stage transformation of a transformed and lowered IR to * a representation of C code suitable for direct output. * * @author colin.gordon */ package com.samsung.sjs.backend; import com.samsung.sjs.backend.asts.ir.*; import com.samsung.sjs.backend.asts.c.*; import com.samsung.sjs.backend.asts.c.types.*; import com.samsung.sjs.types.*; import com.samsung.sjs.CompilerOptions; import com.samsung.sjs.FFILinkage; import com.samsung.sjs.JSEnvironment; import com.samsung.sjs.ModuleSystem; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; public final class IRCBackend extends CBackend { private static Logger logger = LoggerFactory.getLogger(IRCBackend.class); private Script program; private boolean debug; private Stack<Function> functions; private Stack<FunctionDeclaration> compiled_functions; private FFILinkage ffi; private JSEnvironment toplevel; // The set of live temporary variables that must be transferred in the event of a dirty flag // trip // TODO: Eventually generalize to Map<String,Type> to handle float shifting private Set<String> live_tmp_vars; private ModuleSystem modsys; private CompilerOptions options; private final SlowPathGenerator slowgen; public IRCBackend(Script r, CompilerOptions opts, IRFieldCollector.FieldMapping m, FFILinkage ffi, JSEnvironment env, ModuleSystem modsys) { super(new TypeTagSerializer(m)); program = r; this.debug = opts.debug(); this.options = opts; field_codes = m; assert (m != null); functions = new Stack<Function>(); compiled_functions = new Stack<FunctionDeclaration>(); this.ffi = ffi; this.toplevel = env; live_tmp_vars = new HashSet<>(); slowgen = new SlowPathGenerator(tts, m); this.modsys = modsys; } public com.samsung.sjs.backend.asts.c.Statement generateObjectMap(String name, List<String> properties) { if (debug) { System.err.println("Generating object map for " + name + ": " + properties); System.err.println("Using field coding..."); System.err.println(field_codes.toString()); } int[] vt = new int[field_codes.size()]; java.util.Arrays.fill(vt, -1); int physical_index = 0; for (String prop : properties) { if (debug) { System.err.println("Looking up index of " + prop); } vt[field_codes.indexOf(prop)] = physical_index++; } CArrayLiteral arr = new CArrayLiteral(); for (int x = 0; x < vt.length; x++) { arr.addElement(new com.samsung.sjs.backend.asts.c.IntLiteral(vt[x])); } // TODO: Refactor so we're not doing this hideous "int <name>[n] = " gen here com.samsung.sjs.backend.asts.c.VariableDeclaration vd = //new com.samsung.sjs.backend.asts.c.VariableDeclaration(false, new VTablePseudoType()); new com.samsung.sjs.backend.asts.c.VariableDeclaration(false, new CInteger()); //vd.addVariable(new Variable(name), arr); vd.addVariable(new Variable(name + "[]"), arr); return vd; } public static void exportPropertyOffsets(CompilationUnit ccode, IRFieldCollector.FieldMapping m) { String[] names_by_id = new String[m.size()]; for (Map.Entry<String, Integer> kv : m) { ccode.declarePropertyConstant(kv.getKey(), kv.getValue()); names_by_id[kv.getValue()] = kv.getKey(); } StringBuilder sb = new StringBuilder(); sb.append("wchar_t* ____propname_by_id[] = {"); for (int x = 0; x < names_by_id.length; x++) { sb.append("\n\tL\"" + names_by_id[x] + "\""); if (x != names_by_id.length - 1) { sb.append(","); } } sb.append("\n};"); ccode.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement(new InlineCCode(sb.toString()))); ccode.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( new InlineCCode("const int ___propname_count = " + names_by_id.length + ";"))); } public CompilationUnit compile() { CompilationUnit ccode = new CompilationUnit(); ccode.addStatement(new IncludeDirective("runtime.h")); ccode.addStatement(new IncludeDirective("ffi.h")); ccode.addStatement(new IncludeDirective("globals.h")); ccode.addStatement(new IncludeDirective("map.h")); if (options.eflEnabled()) { ccode.addStatement(new IncludeDirective("Elementary.h")); } ccode.exportString("#ifdef __cplusplus"); ccode.exportString("extern \"C\" {"); ccode.exportString("#endif // __cplusplus"); if (options.isGuestRuntime()) { ccode.exportString("extern int __sjs_main(int);"); } // Process vtables exported to other runtime modules (e.g., for console) for (Map.Entry<String, List<String>> table_req : ffi.getTablesToGenerate()) { ccode.addStatement(generateObjectMap(table_req.getKey(), table_req.getValue())); ccode.exportIndirectionMap(table_req.getKey()); } com.samsung.sjs.backend.asts.c.CompoundStatement vtables = new com.samsung.sjs.backend.asts.c.CompoundStatement(); ccode.addStatement(vtables); ccode.addStatement(new IncludeDirective("array.h")); BackPatchDeclarations bpd = new BackPatchDeclarations(); ccode.addStatement(bpd); // insert extern declarations for FFI entities for (Map.Entry<String, FFILinkage.LinkEntry> extern : ffi.entrySet()) { Type t = toplevel.get(extern.getKey()); if (t == null) { System.err.println("BAD: FFI linkage declaration for [" + extern.getKey() + "], but toplevel has no type for it"); } if (t.isIntersectionType()) { continue; // TODO: Implement runtime representation for intersection of multiple types } CType ct = getTypeConverter().convert(t); if (extern.getValue().boxed) { ccode.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( new InlineCCode("extern value_t* " + extern.getKey()))); } else { ccode.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( new InlineCCode("extern " + ct.toSource() + " " + extern.getKey()))); } } // extern decls for module load hooks for (String hook : modsys.getModuleLoadCalls()) { ccode.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( new InlineCCode("extern value_t " + hook + "()"))); } // This emits field #defines in the C code and header exportPropertyOffsets(ccode, field_codes); // Need to include interop *after* generating the property lookup table if (options.interopEnabled()) { ccode.addStatement(new IncludeDirective("interop.h")); } // Make space for string literal decls com.samsung.sjs.backend.asts.c.CompoundStatement strlits = new com.samsung.sjs.backend.asts.c.CompoundStatement(); ccode.addStatement(strlits); this.string_literal_decls = strlits; // Rhino seems to do a lot of casting from Node to AstNode //for (Node n : sourcetree) { for (IRNode n : program) { CNode result = n.accept(this); if (debug) { System.err.println("Converting [" + n.toSource(0) + "]"); System.err.println(">>> [" + result.toSource(0) + "]"); } if (result instanceof FunctionDeclaration) { bpd.preDeclare((FunctionDeclaration) result); } ccode.addStatement((com.samsung.sjs.backend.asts.c.Statement) result); } // Now that we've observed all anonymous C types, we can backtrack to generate some typedefs ccode.exportString("#ifdef __cplusplus"); ccode.exportString("} // extern C"); ccode.exportString("#endif // __cplusplus"); for (Map.Entry<Integer, Set<Pair<int[], Integer>>> entry : vtables_by_hash.entrySet()) { for (Pair<int[], Integer> vt_and_id : entry.getValue()) { int[] vt = vt_and_id.getKey(); int i = vt_and_id.getValue(); CArrayLiteral arr = new CArrayLiteral(); for (int x = 0; x < vt.length; x++) { arr.addElement(new com.samsung.sjs.backend.asts.c.IntLiteral(vt[x])); } // TODO: Refactor so we're not doing this hideous "int <name>[n] = " gen here com.samsung.sjs.backend.asts.c.VariableDeclaration vd = //new com.samsung.sjs.backend.asts.c.VariableDeclaration(false, new VTablePseudoType()); new com.samsung.sjs.backend.asts.c.VariableDeclaration(false, new CInteger()); vd.addVariable(new Variable("__vtable_id_" + i + "[]"), arr); vtables.addStatement(vd); } } for (String s : tts.getForwardDecls()) { strlits.addExpressionStatement(new InlineCCode(s)); } for (String s : tts.getPropArrayDecls()) { strlits.addExpressionStatement(new InlineCCode(s)); } for (String s : tts.getFieldArrayDecls()) { strlits.addExpressionStatement(new InlineCCode(s)); } for (String s : tts.getArgArrayDecls()) { strlits.addExpressionStatement(new InlineCCode(s)); } for (String s : tts.getCodeDecls()) { strlits.addExpressionStatement(new InlineCCode(s)); } for (String s : tts.getTagDecls()) { strlits.addExpressionStatement(new InlineCCode(s)); } return ccode; } @Override public com.samsung.sjs.backend.asts.c.Expression visitVar(Var node) { Variable v = new Variable(node.getIdentifier()); boolean should_unbox = false; if (lval_capture) { should_unbox = true; } if (shouldUnbox(node)) { if (debug) { System.err.println(">>>> Unboxing use of " + node.getIdentifier() + " in function " + functions.peek().getName()); } should_unbox = true; } if (should_unbox) { if (node.getIdentifier().startsWith("__tmp")) { // tmp var is no longer live live_tmp_vars.remove(node.getIdentifier()); } return v; } else { // TODO: Rename Unbox, since it's really looking inside a box, not unboxing return new Unbox(v); } } @Override public com.samsung.sjs.backend.asts.c.Expression visitArrayIndex(ArrayIndex node) { return visitArrayIndex(node, false); } public com.samsung.sjs.backend.asts.c.Expression visitArrayIndex(ArrayIndex node, boolean lval_position) { if (node.getArray().getType() instanceof EnvironmentType) { // Indexing into an environment, which is represented as a literal C array Var envname = (Var) node.getArray(); // TODO: Right now this only works with constant offsets, as for environment access if (!(envname.getType() instanceof EnvironmentType)) { System.err.println("Variable " + envname.toSource(0) + " is not an EnvironmentType"); } assert (envname.getType() instanceof EnvironmentType); com.samsung.sjs.backend.asts.ir.IntLiteral offset = (com.samsung.sjs.backend.asts.ir.IntLiteral) node .getOffset(); int ioffset = (int) offset.getValue(); ArrayIndexing ind = new ArrayIndexing(new Variable(envname.getIdentifier()), ioffset); if (lval_capture) { return ind; } else { return new Unbox(ind); // need to cast the box when it's a pointer //com.samsung.sjs.backend.asts.c.Expression access = null; //access = new ValueAs(new Unbox(ind), node.getType()); //if (lval_position) { // return access; //} else { // return new CastExpression(getTypeConverter().convert(node.getType()), access); //} } } else if (node.getArray().getType() instanceof StringType) { return new ValueCoercion(Types.mkString(), new com.samsung.sjs.backend.asts.c.FunctionCall(new Variable("__str__charAt"), node.getArray().accept(this).asExpression().inType(Types.mkString()), node.getOffset().accept(this).asExpression().inType(Types.mkInt())), false); } else { Type atype = node.getArray().getType(); if (!(atype.isArray() || atype.isMap())) { System.err.println("??? have an array that is not array or map, after handling env and string"); System.err.println(node.toSource(0) + " :: " + node.getType()); } assert (atype.isArray() || atype.isMap()); // Typing a JSArray access Type elemType = null; Type keyType = null; if (atype.isArray()) { ArrayType arrty = (ArrayType) node.getArray().getType(); elemType = arrty.elemType(); keyType = Types.mkInt(); } else { MapType mapty = (MapType) atype; elemType = mapty.elemType(); keyType = Types.mkString(); } if (node.getArray().isPure() && node.getOffset().isPure()) { com.samsung.sjs.backend.asts.c.Expression access = null; com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( atype.isArray() ? "array_get" : "__map_access"); f.addActualArgument(node.getArray().accept(this).asExpression().inType(node.getArray().getType())); f.addActualArgument( node.getOffset().accept(this).asExpression().inType(node.getOffset().getType())); //if (elemType instanceof PrimitiveType) { // access = new ValueAs(f, elemType); //} else { // access = new CastExpression(getTypeConverter().convert(elemType), new ValueAs(f, elemType)); //} //return access; return f; } else { // Note that array_get is a macro, and will duplicate both expressions many times Variable arr_tmp = newTempVariable(atype); Variable i_tmp = newTempVariable(keyType); Assignment asgn_arr = new Assignment(arr_tmp, "=", node.getArray().accept(this).asExpression().asValue(node.getArray().getType())); Assignment asgn_i = new Assignment(i_tmp, "=", node.getOffset().accept(this).asExpression().asValue(node.getOffset().getType())); com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( atype.isArray() ? "array_get" : "__map_access"); //f.addActualArgument(node.getArray().accept(this).asExpression()); //f.addActualArgument(node.getOffset().accept(this).asExpression()); f.addActualArgument(arr_tmp.inType(node.getArray().getType())); f.addActualArgument(i_tmp.inType(node.getOffset().getType())); com.samsung.sjs.backend.asts.c.Expression access = null; access = f; //return new CastExpression(getTypeConverter().convert(elemType), f); //if (elemType instanceof PrimitiveType) { // access = new ValueAs(f, elemType); //} else { // access = new CastExpression(getTypeConverter().convert(elemType), new ValueAs(f, elemType)); //} return new BinaryInfixExpression(asgn_arr, ",", new BinaryInfixExpression(asgn_i, ",", access)); } } } private boolean shouldUnbox(Var node) { //return (!functions.isEmpty() // Not in top-level // && !functions.peek().getCaptured().contains(node.getIdentifier()) // Not captured in a closure // && !program.getScope().isLocallyBound(node) // Don't unbox ffi objects // ); if (functions.isEmpty() /* top level ; TODO: Why? */ || functions.peek().getCaptured().contains(node.getIdentifier()) // captured in later closure ) { return false; } if (program.getScope().isLocallyBound(node)) { // bound in global context: check ffi linkage FFILinkage.LinkEntry l = ffi.get(node.getIdentifier()); if (l != null) { return !l.boxed; } else { // If we haven't found linkage information for a top-level variable, that's bad throw new IllegalArgumentException("Missing linkage information for global variable [" + node.getIdentifier() + "]; did we forget to pass a *.linkage.json file?"); } } // not top-level, not captured, and not FFI. unbox it. return true; } // TODO: Need to transfer a set of temp variables, not just the result of the failing call public void generateTypeViolationTransition(com.samsung.sjs.backend.asts.c.CompoundStatement cs, String jump_target, Var tmp) { BlockStatement bs = new BlockStatement(); if (tmp != null && tmp.getIdentifier().startsWith("__tmp")) { bs.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement(new Assignment( new Variable("__slow_" + tmp.getIdentifier()), "=", new Variable(tmp.getIdentifier())))); } for (String s : live_tmp_vars) { bs.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( new Assignment(new Variable("__slow_" + s), "=", new Variable(s)))); } bs.addStatement(new Goto(jump_target)); com.samsung.sjs.backend.asts.c.IfStatement ite = new IfStatement(new Variable("__dirty"), bs, null); cs.addStatement(ite); } @Override public com.samsung.sjs.backend.asts.c.Statement visitVarDecl(VarDecl node) { // We'll see at most one VarDecl for a given variable in some JS scope, so we should // always go ahead and prefix the hoisted declaration. CType decltype = getTypeConverter().convert(node.getType()); com.samsung.sjs.backend.asts.c.VariableDeclaration d = new com.samsung.sjs.backend.asts.c.VariableDeclaration( false, /*decltype*/new Value()); com.samsung.sjs.backend.asts.c.Expression var = node.getVar().accept(this).asExpression(); // var may be a box macro, or it may be a value-projection if (var instanceof ValueAs) { var = ((ValueAs) var).expr(); } // NOTE: It's tempting to check in here if something is a temporary variable introduced by // the ThreeAddress pass, and not hoist in that case. But the temp // variables must be function-scoped so they can be shared across the type-correct and // type-checking paths in interop mode. Solution: function scope for interop intermediates, // local scope here. The dirty flag check must then be generated in a context of which // locals are in scope, copy their values to the function-scoped checking-path // intermediates, and then transition. This means the cost of making intermediates // function-scoped causes C optimizer issues in the "slower" path, not the faster path. // TODO: replace with proper temp var flag if (node.getVar().asVar().getIdentifier().startsWith("__tmp")) { // This is a compiler-introduced intermediate (sort of SSA) variable, which has two // important properties we can't assume about most source-level variables: // 1. It is always written before being read // 2. Its initialization is well-defined; there's no cyclic definition mess to // untangle. // As a result, we do *not* need to hoist this to the top of the C function, and we // don't need to fabricate an undefined initialization! To boot, these are never // captured in closures because they name intermediate expressions. Let's emit C-block-scope declarations so the C compiler can do a better job reasoning. // // Note, however, that for interop mode, when generating the second body, we need to // duplicate (under a different but related name) this temp var, and hoist *that* // declaration, so the clean-to-dirty transition can copy temporaries appropriately. d.addVariable(var, node.getInitialValue().accept(this).asExpression().asValue(node.getInitialValue().getType())); if (options.interopEnabled() && node.getInitialValue().isCall()) { com.samsung.sjs.backend.asts.c.CompoundStatement cs = new com.samsung.sjs.backend.asts.c.CompoundStatement(); cs.addStatement(d); com.samsung.sjs.backend.asts.ir.Call fcall = node.getInitialValue().asCall(); generateTypeViolationTransition(cs, "__dirty_call_transition" + fcall.callsiteNumber(), node.getVar().asVar()); return cs; } // Since we just initialized this temp variable, it is now also live for dirty spills live_tmp_vars.add(node.getVar().asVar().getIdentifier()); return d; } if (node.getType() instanceof FloatType) { d.addVariable(var, shouldUnbox(node.getVar()) ? new com.samsung.sjs.backend.asts.c.DoubleLiteral(Double.NaN).asValue(Types.mkFloat()) : new AllocBox( new com.samsung.sjs.backend.asts.c.DoubleLiteral(Double.NaN).asValue(Types.mkFloat()), decltype)); } else { d.addVariable(var, shouldUnbox(node.getVar()) ? new com.samsung.sjs.backend.asts.c.IntLiteral(0).asValue(Types.mkInt()) : new AllocBox(new com.samsung.sjs.backend.asts.c.IntLiteral(0).asValue(Types.mkInt()), decltype)); } compiled_functions.peek().getBody().prefixStatement(d); // TODO: stop generating extra writes if there's an initial value... com.samsung.sjs.backend.asts.c.Expression initval = node.getInitialValue().accept(this).asExpression() .asValue(node.getInitialValue().getType()); if (node.getInitialValue().getType() instanceof IntegerType && node.getVar().getType() instanceof FloatType) { // coercion initval = coerceIntToFloat(initval); } Assignment wr = new Assignment(var, "=", initval); //new AllocBox( // node.getType() instanceof PrimitiveType ? // node.getInitialValue().accept(this).asExpression() : // new CastExpression(new CVoid(1), node.getInitialValue().accept(this).asExpression()) // , // decltype)); //if (options.interopEnabled() && node.getInitialValue() instanceof com.samsung.sjs.backend.asts.ir.FunctionCall) { // com.samsung.sjs.backend.asts.c.CompoundStatement cs = new com.samsung.sjs.backend.asts.c.CompoundStatement(); // cs.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement(wr)); // generateTypeViolationTransition(cs, "test_label", node.getVar().asVar()); // return cs; //} return new com.samsung.sjs.backend.asts.c.ExpressionStatement(wr); } @Override public com.samsung.sjs.backend.asts.c.Statement visitExpressionStatement( com.samsung.sjs.backend.asts.ir.ExpressionStatement node) { if (node.getExpression() == null) { return new com.samsung.sjs.backend.asts.c.ExpressionStatement(null); } CNode result = node.getExpression().accept(this); // Unfortunately, C and JS disagree on what is an expression if (result instanceof com.samsung.sjs.backend.asts.c.Expression) { if (result.asExpression() instanceof ValueCoercion) { // Peel off gratuitous value coercions result = ((ValueCoercion) result.asExpression()).getSubject(); } com.samsung.sjs.backend.asts.c.ExpressionStatement ret = new com.samsung.sjs.backend.asts.c.ExpressionStatement( result.asExpression()); if (options.interopEnabled()) { com.samsung.sjs.backend.asts.c.CompoundStatement cs = new com.samsung.sjs.backend.asts.c.CompoundStatement(); cs.addStatement(ret); if (node.getExpression().isCall()) { com.samsung.sjs.backend.asts.ir.Call fcall = node.getExpression().asCall(); generateTypeViolationTransition(cs, "__dirty_call_transition" + fcall.callsiteNumber(), null); } return cs; } return ret; } else { return (com.samsung.sjs.backend.asts.c.Statement) result; } } public com.samsung.sjs.backend.asts.c.Expression visitNonBooleanOr(BinaryOp node) { // we convert the left side as a value, to preserve possible undefinedness // TODO: the 3-addr conversion needs to decompose this when there are effects... com.samsung.sjs.backend.asts.c.Expression left = node.getLeft().accept(this).asExpression() .asValue(node.getLeft().getType()); com.samsung.sjs.backend.asts.c.Expression right = node.getRight().accept(this).asExpression() .asValue(node.getRight().getType()); ConditionalExpression condexpr = new ConditionalExpression(); com.samsung.sjs.backend.asts.c.FunctionCall test = new com.samsung.sjs.backend.asts.c.FunctionCall( "!val_is_falsy"); test.addActualArgument(left); condexpr.setTest(test); condexpr.setTrueBranch(left); condexpr.setFalseBranch(right); return condexpr; } @Override public com.samsung.sjs.backend.asts.c.Expression visitBinaryOp(BinaryOp node) { if (node.getType() == null) { System.err.println("BAD: binop without overall result type: " + node.toSource(0)); } assert (node.getType() != null); if (node.getLeft().getType() == null) { System.err.println("BAD: binop lhs w/o type: " + node.getLeft().toSource(0)); } if (node.getOp().equals("||") && !(node.getLeft().getType() instanceof BooleanType)) { return visitNonBooleanOr(node); } com.samsung.sjs.backend.asts.c.Expression left = node.getLeft().accept(this).asExpression() .inType(node.getLeft().getType()); com.samsung.sjs.backend.asts.c.Expression right = node.getRight().accept(this).asExpression() .inType(node.getRight().getType()); switch (node.getOp()) { case "|": case "&": case "^": case ">>": case ">>>": case "<<": // For bitwise operators, insert float -> int32 coercion for float operands if (node.getLeft().getType() instanceof FloatType) { left = new com.samsung.sjs.backend.asts.c.FunctionCall("___int_of_float", left); } if (node.getRight().getType() instanceof FloatType) { right = new com.samsung.sjs.backend.asts.c.FunctionCall("___int_of_float", right); } break; case "==": if (node.getLeft().getType() instanceof ObjectType || node.getRight().getType() instanceof ObjectType) { assert (node.getLeft().getType() instanceof ObjectType && node.getRight().getType() instanceof ObjectType); // 'null' gets coerced to the top object type if it doesn't meet with anything // else during inference. But if it's being compared with a string or another // tagged pointer type, we need to strip those bits left = new ValueAs(left.asValue(node.getLeft().getType()), TopReferenceType.make()); right = new ValueAs(right.asValue(node.getRight().getType()), TopReferenceType.make()); } default: break; } if (node.getOp().equals(">>>")) { // zero-fill right shift, as opposed to signed shift. return new InlineCCode( "int_as_val((int32_t)(((unsigned int)" + left.toSource(0) + ") >> " + right.toSource(0) + "))"); } if (node.getOp().equals("/") && node.getLeft().getType() instanceof IntegerType && node.getRight().getType() instanceof IntegerType) { // To avoid silently introducing integer division imprecision in a language that doesn't // have it, we must cast one of the operands to double right = new CastExpression(new CDouble(), right); } // TODO: Between here and above, we're clearly double-casting, but even with this lower one // removed, we're double-casting elsewhere... return new BinaryInfixExpression(left, lower_op(node.getOp()), right).asValue(node.getType()); } protected com.samsung.sjs.backend.asts.c.Expression coerceIntToFloat( com.samsung.sjs.backend.asts.c.Expression e) { CastExpression ce = new CastExpression(new CDouble(), e.inType(Types.mkInt())); return ce.asValue(Types.mkFloat()); } public com.samsung.sjs.backend.asts.c.Expression visitAssignment( com.samsung.sjs.backend.asts.ir.Expression left, String op, com.samsung.sjs.backend.asts.ir.Expression right) { boolean intfloat_coercion = false; if (left.getType() instanceof FloatType && right.getType() instanceof IntegerType) { intfloat_coercion = true; } // Remember that (modulo a future unboxing pass), // object fields are boxed (for inheritance), as well as variables (for capture) // By default, the target, if translated naively, is translated as an access to // the box contents, so we shouldn't need to treat anything specially here // until we do an unboxing optimization. if (left instanceof ArrayIndex) { // TODO: Ignore other calls to merge Var, Field, and Array assignments. Keep them separate, // which will keep the code paths clean. Finish splitting them. ArrayIndex node = (ArrayIndex) left; if (node.getArray().getType().isArray() || node.getArray().getType().isMap()) { Type elemType = null; boolean ismap = false; if (node.getArray().getType().isArray()) { ArrayType arrty = (ArrayType) node.getArray().getType(); elemType = arrty.elemType(); } else { ismap = true; MapType mapty = (MapType) node.getArray().getType(); elemType = mapty.elemType(); } com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( ismap ? "__map_store" : "array_put"); f.addActualArgument(node.getArray().accept(this).asExpression().inType(node.getArray().getType())); f.addActualArgument( node.getOffset().accept(this).asExpression().inType(node.getOffset().getType())); com.samsung.sjs.backend.asts.c.Expression rhs = right.accept(this).asExpression(); if (intfloat_coercion) { rhs = coerceIntToFloat(rhs); } if (op.length() == 2) { assert (node.getArray().isPure() && node.getOffset().isPure()); // TODO: memoize as elsewhere! f.addActualArgument( new BinaryInfixExpression(left.accept(this).asExpression().inType(node.getType()), String.valueOf(op.charAt(0)), rhs.inType(left.getType())) .asValue(node.getType())); } else { f.addActualArgument(rhs.asValue(right.getType())); //f.addActualArgument(new CastExpression(new Value(), right.accept(this).asExpression())); } //return new CastExpression(getTypeConverter().convert(elemType), f); //return new ValueAs(f, elemType); return f; } } com.samsung.sjs.backend.asts.c.Expression val = right.accept(this).asExpression(); if (intfloat_coercion) { val = coerceIntToFloat(val); } // TODO: return value shifting for floats in interop if (left instanceof FieldRead) { FieldRead fr = (FieldRead) left; com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "FIELD_READ_WRITABLE"); com.samsung.sjs.backend.asts.c.Expression obj = fr.getObject().accept(this).asExpression() .inType(fr.getObject().getType()); f.addActualArgument(new CastExpression(new ObjectPseudoType(), obj)); f.addActualArgument(new com.samsung.sjs.backend.asts.c.IntLiteral(field_codes.indexOf(fr.getField()))); Type rhs_type = null; if (left.getType() instanceof FloatType && right.getType() instanceof IntegerType) { rhs_type = Types.mkFloat(); } else { rhs_type = right.getType(); } switch (op) { case "=": return new BinaryInfixExpression(f, op, val.asValue(right.getType())).asValue(left.getType()); case "+=": return new BinaryInfixExpression(f, "=", new BinaryInfixExpression(f.inType(fr.getType()), "+", val.inType(rhs_type)) .asValue(left.getType())).asValue(left.getType()); case "-=": return new BinaryInfixExpression(f, "=", new BinaryInfixExpression(f.inType(fr.getType()), "-", val.inType(rhs_type)) .asValue(left.getType())).asValue(left.getType()); case "*=": return new BinaryInfixExpression(f, "=", new BinaryInfixExpression(f.inType(fr.getType()), "*", val.inType(rhs_type)) .asValue(left.getType())).asValue(left.getType()); case "/=": return new BinaryInfixExpression(f, "=", new BinaryInfixExpression(f.inType(fr.getType()), "/", val.inType(rhs_type)) .asValue(left.getType())).asValue(left.getType()); default: throw new IllegalArgumentException("Unsupported binary operator for fields: " + op); } } else if (left.isVar() && left.getType() instanceof FloatType) { if (op.equals("+=") || op.equals("-=") || op.equals("/=") || op.equals("*=")) { val = new BinaryInfixExpression(left.accept(this).asExpression().inType(left.getType()), String.valueOf(op.charAt(0)), val.inType(intfloat_coercion ? left.getType() : right.getType())).asValue(left.getType()); // TODO: Float shifting when in interop mode op = "="; } } com.samsung.sjs.backend.asts.c.Expression target = left.accept(this).asExpression(); if (target instanceof CastExpression) { target = ((CastExpression) target).getBaseExpression(); } // Prior to a type violation, the tag for a variable should be correct from initialization // onwards, so for non-pointers (i.e., no low tags) we can optimize local variable accesses to only touch the appropriate // projection if (left.isVar() && (left.getType() instanceof IntegerType || left.getType() instanceof ObjectType)) { return new ValueCoercion(left.getType(), new com.samsung.sjs.backend.asts.c.Assignment( new InlineCCode(target.toSource(0) + "." + ValueAs.value_field_of(left.getType())), op, val.inType(right.getType())), false); } // fix for type-based modifying operations String newop = null; switch (op) { case "&=": newop = "&"; break; case "|=": newop = "|"; break; case "^=": newop = "^"; break; case "+=": newop = "+"; break; case "-=": newop = "-"; break; case "*=": newop = "*"; break; case "/=": newop = "/"; break; case "%=": newop = "%"; break; case "<<=": newop = "<<"; break; case ">>=": newop = ">>"; break; default: } if (newop != null) { val = new BinaryInfixExpression(target.inType(left.getType()), newop, val.inType(right.getType())); op = "="; } return new com.samsung.sjs.backend.asts.c.Assignment(target, op, val.asValue(right.getType())); } @Override public com.samsung.sjs.backend.asts.c.Expression visitVarAssignment(VarAssignment node) { return visitAssignment(node.getAssignedVar(), node.getOperator(), node.getAssignedValue()); } @Override public com.samsung.sjs.backend.asts.c.Expression visitFieldAssignment(FieldAssignment node) { // TODO Ctor.prototype.m = ... is permitted, where Ctor is a function... Need to generate // partner objects for any function used in such a manner // But that would prohibit higher-order use of constructors (func(C, x) { return new C(x); }) if (!(node.getObject().getType() instanceof ObjectType)) { assert (node.getObject().getType() != null); assert (node.getObject().getType().isConstructor()); assert (node.getField().equals("prototype")); assert (node.getValue().getType().isObject()); com.samsung.sjs.backend.asts.c.Expression ctor = node.getObject().accept(this).asExpression() .inType(node.getObject().getType()); ctor = new CastExpression(getTypeConverter().convert(node.getObject().getType()), ctor); com.samsung.sjs.backend.asts.c.Expression proto = node.getValue().accept(this).asExpression() .inType(node.getValue().getType()); return new InlineCCode(ctor.toSource(0) + "->prototype.obj = " + proto.toSource(0)); } FieldRead fr = new FieldRead(node.getObject(), node.getField()); fr.setType(((ObjectType) node.getObject().getType()).findMemberType(node.getField())); return visitAssignment(fr, node.getOperator(), node.getValue()).asValue(fr.getType()); } @Override public com.samsung.sjs.backend.asts.c.Expression visitPredictedFieldAssignment(PredictedFieldAssignment node) { // TODO Ctor.prototype.m = ... is permitted, where Ctor is a function... Need to generate // partner objects for any function used in such a manner // But that would prohibit higher-order use of constructors (func(C, x) { return new C(x); }) assert (node.getObject().getType() instanceof ObjectType); PredictedFieldRead fr = mkPredictedFieldRead(node.getObject(), node.getField(), node.getBoxPointerOffset()); fr.setType(((ObjectType) node.getObject().getType()).findMemberType(node.getField())); String op = node.getOperator(); com.samsung.sjs.backend.asts.c.Expression val = node.getValue().accept(this).asExpression(); // TODO: return value shifting if we store a double.... // In this case we know the field is local since we're writing to it, which means it's // not boxed, and since we've predicted the box offset we can emit an lval com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "INLINE_BOX_ACCESS"); f.addActualArgument(fr.getObject().accept(this).asExpression().inType(fr.getObject().getType())); f.addActualArgument(new com.samsung.sjs.backend.asts.c.IntLiteral(fr.getOffset())); com.samsung.sjs.backend.asts.c.Expression lhs = new ValueAs(f, fr.getType()); // TODO: float shifting in interop //if (fr.getType() instanceof FloatType) { // // TODO: The call f constructed below should be reusable in place of fr.accept... except // // the current impl also handle casts and shifts. Should replace the call with a // // (direct) predicted field read // lhs = new InlineCCode(f.toSource(0)+".box"); // com.samsung.sjs.backend.asts.c.FunctionCall shift = // new com.samsung.sjs.backend.asts.c.FunctionCall("shift_double"); // switch (op) { // case "=": // shift.addActualArgument(val.inType(node.getValue().getType())); // break; // case "+=": // shift.addActualArgument(new BinaryInfixExpression( // fr.accept(this).asExpression().inType(fr.getType()), // This unshifts during the read; TODO: optimize since it's writable // "+", // val.inType(node.getValue().getType()))); // break; // case "-=": // shift.addActualArgument(new BinaryInfixExpression( // fr.accept(this).asExpression().inType(fr.getType()), // This unshifts during the read; TODO: optimize since it's writable // "-", // val.inType(node.getValue().getType()))); // break; // default: // throw new IllegalArgumentException("Unsupported read-write operator for doubles: "+op); // } // val = shift; // op = "="; // return new com.samsung.sjs.backend.asts.c.Assignment(lhs, op, val); //} // TODO: Inline this behavior as well? Or a general field-write specific variant return visitAssignment(fr, node.getOperator(), node.getValue()); } @Override public FunctionDeclaration visitFunction(Function node) { functions.push(node); CodeType ftype = (CodeType) node.getType(); Type retty = ftype.returnType(); FunctionDeclaration fn = null; // Must initialize GC before any other actions if ((node.getName().equals("main") || node.getName().equals("__sjs_main")) && options.getMMScheme() == CompilerOptions.MMScheme.GC) { fn = new FunctionDeclaration(node.getName(), getTypeConverter().convert(retty)); // set up efl environment if (options.eflEnabled()) { com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "elm_init"); assert (node.nargs() > 0); f.addActualArgument(new Variable(node.argName(0).getIdentifier())); f.addActualArgument(new com.samsung.sjs.backend.asts.c.NullLiteral()); fn.addBodyStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement(f)); f = new com.samsung.sjs.backend.asts.c.FunctionCall("elm_policy_set"); f.addActualArgument(new Variable("ELM_POLICY_QUIT")); f.addActualArgument(new Variable("ELM_POLICY_QUIT_LAST_WINDOW_CLOSED")); fn.addBodyStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement(f)); } fn.addBodyStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( new com.samsung.sjs.backend.asts.c.FunctionCall("GC_INIT"))); if (options.shouldStartInInterop()) { fn.addBodyStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( new com.samsung.sjs.backend.asts.c.FunctionCall("_____type_violation"))); } } else { fn = new FunctionDeclaration(node.getName(), retty instanceof VoidType ? new CVoid() : new Value() /*getTypeConverter().convert(retty)*/); } //fn.addArgument(node.getEnvironmentName(), new EnvironmentPseudoType(node.getEnvLayout())); for (int i = 0; i < node.nargs(); i++) { Var name = node.argName(i); Type ty = node.argType(i); if (ty == null) { System.err.println("ERROR: visiting function [" + node.getName() + "], no type on argument " + i); System.err.println("Full function:\n" + node.toSource(0)); } assert (ty != null); if (ty instanceof EnvironmentType) { assert (i == 0); fn.addArgument(node.getEnvironmentName(), new EnvironmentPseudoType(node.getEnvLayout())); } else if (ty instanceof CRuntimeArray) { // We need a box, but for a regular JS array, and we need to populate it with // a conversion function. CRuntimeArray cra = (CRuntimeArray) ty; CType argt = getTypeConverter().convert(ty); fn.addArgument("__" + name.getIdentifier(), argt); CType cellt = getTypeConverter().convert(cra.elemType()); com.samsung.sjs.backend.asts.c.VariableDeclaration vd = new com.samsung.sjs.backend.asts.c.VariableDeclaration( false, new JSArray()); com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "c_to_js_array"); f.addActualArgument(new Variable("__" + name.getIdentifier())); vd.addVariable(new Unbox(new Variable(name.getIdentifier())), new AllocBox(new CastExpression(new CVoid(1), f), new JSArray())); fn.addBodyStatement(vd); } else { CType t = new Value(); // was, when type-specializing: getTypeConverter().convert(ty); if ((node.getName().equals("main") || node.getName().equals("__sjs_main")) && options.getMMScheme() == CompilerOptions.MMScheme.GC) { // main() needs the right signature t = getTypeConverter().convert(ty); } if (shouldUnbox(name)) { // IMPORTANT: We've pushed the function we're generating onto the functions stack already fn.addArgument(name.getIdentifier(), t); } else { fn.addArgument("__" + name.getIdentifier(), t); // Now deal with creating a fresh box for a passed-by-value argument com.samsung.sjs.backend.asts.c.VariableDeclaration vd = new com.samsung.sjs.backend.asts.c.VariableDeclaration( false, t); vd.addVariable(new Unbox(new Variable(name.getIdentifier())), new AllocBox(new Variable("__" + name.getIdentifier()), t)); fn.addBodyStatement(vd); } } } // For constructors, copy down inherited field pointers. If the prototype is a native // wrapper, then also instantiate an SJS subclass if (node.isConstructor()) { logger.debug("Compiling constructor {} with result type: {}", node.getName(), retty); logger.debug(((ObjectType) retty).inheritedProperties().size() + " inherited properties"); for (Property p : ((ObjectType) retty).inheritedProperties()) { int vt_off = field_codes.indexOf(p.getName()); com.samsung.sjs.backend.asts.c.FunctionCall exp = new com.samsung.sjs.backend.asts.c.FunctionCall( "INHERIT_FIELD_COMPRESSED"); exp.addActualArgument(new InlineCCode("__this.obj")); exp.addActualArgument(new InlineCCode("__this.obj->__proto__")); exp.addActualArgument(new InlineCCode(String.valueOf(vt_off))); fn.addBodyStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement(exp)); } // Generate CPP proxy if needed; notice this (intentionally) may clobber a naive field // inheritance of _____cpp_receiver and _____gen_cpp_proxy! fn.addBodyStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( new InlineCCode("MAYBE_GEN_CPP_PROXY(__this.obj)"))); ObjectType oretty = (ObjectType) retty; // TODO: native prototype support //for (Property p : oretty.properties()) { // // TODO: We don't necessarily know, currently, if the prefix of this object will // // match the layout of the parent, statically. In fact, it often won't. But we // // should figure out how to make that the case. // // TODO: Inside a constructor, we should know exactly the vtable, and should be able // // to generate better code // if (p.getPropertySetId() == PropertySetId.RO) { // // inhertied property // int vt_off = field_codes.indexOf(p.getName()); // InlineCCode exp = new InlineCCode( // "__this->fields[__this->vtbl["+vt_off+ // "]].ptr = ((uint64_t)&__this->__proto__->fields[__this->__proto__->vtbl["+vt_off+"]]) | 0x1;"); // fn.addBodyStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement(exp)); // } //} } Block body = (Block) node.getBody(); compiled_functions.push(fn); BlockStatement typed = new BlockStatement(); for (com.samsung.sjs.backend.asts.ir.Statement n : body) { CNode s = n.accept(this); // TODO: UNDO THIS TERRIBLE HACK if (s instanceof com.samsung.sjs.backend.asts.c.Expression) { typed.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( (com.samsung.sjs.backend.asts.c.Expression) s)); } else { // the efl function calls should be run before the return statement if (s instanceof com.samsung.sjs.backend.asts.c.ReturnStatement) { if (options.eflEnabled() && node.getName().equals("main")) { typed.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( new com.samsung.sjs.backend.asts.c.FunctionCall("elm_run"))); typed.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( new com.samsung.sjs.backend.asts.c.FunctionCall("elm_shutdown"))); } } typed.addStatement((com.samsung.sjs.backend.asts.c.Statement) s); } } if (node.isConstructor()) { typed.addStatement(new ReturnStatement(new ThisPseudoLiteral())); } else if (retty instanceof VoidType) { typed.addStatement(new ReturnStatement()); } if (options.interopEnabled()) { // generate ill-typed body here BlockStatement untyped = new BlockStatement(); IfStatement fbody = new IfStatement(new UnaryExpression(new Variable("__dirty"), "!", false), typed, untyped); for (com.samsung.sjs.backend.asts.ir.Statement n : body) { // Remember to use the other backend for these statements CNode s = n.accept(this.slowgen); // TODO: UNDO THIS TERRIBLE HACK if (s instanceof com.samsung.sjs.backend.asts.c.Expression) { untyped.addStatement(new com.samsung.sjs.backend.asts.c.ExpressionStatement( (com.samsung.sjs.backend.asts.c.Expression) s)); } else { untyped.addStatement((com.samsung.sjs.backend.asts.c.Statement) s); } } if (node.isConstructor()) { untyped.addStatement(new ReturnStatement(new ThisPseudoLiteral())); } fn.addBodyStatement(fbody); } else { fn.addBodyStatement(typed); } functions.pop(); compiled_functions.pop(); return fn; } @Override public CNode visitAllocNewObject(AllocNewObject node) { // TODO: This doesn't support .prototype access yet, either for reads or setting // inherited methods. For now, we won't even support inheriting from Object. // Getting this to work before there are proper constructor types and AST nodes is too // brittle; it requires spreading around quite a few special cases, or multiple // re-traversals of the AST to re-type function references as constructor references. // For now, we'll keep the code, but won't use it. com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "construct_object"); assert (node.getVTable() != null); CType c_ctor_type = getTypeConverter().convert(node.getConstructor().getType()); // TODO: The construction and use of this vtable are more subtle than for object literals, // because we have to deal with ordering wrt inherited fields as well. // TODO: Constructor code gen can always statically resolve field offsets of the receiver int indir_id = memo_vtable(node.getVTable()); // TODO: This breakdown is wrong. The constructor should have control of the vtable, not // the allocation site f.addActualArgument(new Variable("__vtable_id_" + indir_id)); String tag = tts != null ? node.getType().generateTag(tts) : "NULL"; f.addActualArgument(new InlineCCode(tag)); Variable vtmp = newTempVariable(node.getConstructor().getType()); Assignment asgn = new Assignment(vtmp, "=", node.getConstructor().accept(this).asExpression()); //f.addActualArgument(new com.samsung.sjs.backend.asts.c.NullLiteral()); // TODO: get proto from the closure f.addActualArgument(new InlineCCode( new CastExpression(c_ctor_type, vtmp.inType(node.getConstructor().getType())).toSource(0) + "->prototype.obj")); // get proto from the closure ObjectType ty = (ObjectType) node.getType(); assert (ty != null); f.addActualArgument(new com.samsung.sjs.backend.asts.c.IntLiteral(ty.properties().size())); // f allocates the empty object, with no initialized fields --- not even boxes // TODO: Inheritance! Actually, that should be done in codegen for the constructor, which // should assume no boxes exist. com.samsung.sjs.backend.asts.c.FunctionCall ctor_call = new ClosureCall( vtmp.inType(node.getConstructor().getType()), c_ctor_type); // construct_object returns object_t* ctor_call.addActualArgument(new ValueCoercion(ty, f, options.encodeVals())); int argnum = 0; for (com.samsung.sjs.backend.asts.ir.Expression e : node.getArguments()) { Type formal_param = ((CodeType) node.getConstructor().getType()).paramTypes().get(argnum); boolean intfloat_coercion = false; if (e.getType() instanceof IntegerType && formal_param instanceof FloatType) { intfloat_coercion = true; } com.samsung.sjs.backend.asts.c.Expression carg = e.accept(this).asExpression(); ctor_call.addActualArgument(intfloat_coercion ? coerceIntToFloat(carg) : carg); argnum++; } return new com.samsung.sjs.backend.asts.c.BinaryInfixExpression(asgn, ",", ctor_call); } @Override public CNode visitFunctionCall(com.samsung.sjs.backend.asts.ir.FunctionCall node) { /* XXX: HACK! * We set a flag when something is calling a primitive, as those are dispatched directly, * and require variation from the value_t calling convention to pass C's typechecker. * On 64-bit, this should work okay since passing something smaller still consumes 64 bits, * but it will break in weird and terrible ways on 32-bit. The correct solution is to * change the (whole) runtime implementation (and IDL toolchain) for the new calling * convention. But this is sufficient to shake out codegen bugs. */ boolean hack = false; com.samsung.sjs.backend.asts.c.Expression ret = null; com.samsung.sjs.backend.asts.c.FunctionCall f; if (node.getTarget().toSource(0).equals("assert")) { hack = true; f = new com.samsung.sjs.backend.asts.c.FunctionCall("assert"); } else if (node.getTarget().toSource(0).equals("_____type_violation")) { hack = true; f = new com.samsung.sjs.backend.asts.c.FunctionCall("_____type_violation"); } else if (node.getTarget().toSource(0).equals("print") || node.getTarget().toSource(0).equals("printString")) { // Hack in an assert for testing purposes hack = true; f = new com.samsung.sjs.backend.asts.c.FunctionCall("print"); } else if (node.getTarget().toSource(0).equals("parseFloat")) { hack = true; f = new com.samsung.sjs.backend.asts.c.FunctionCall("parseFloat"); } else if (node.getTarget().toSource(0).equals("parseInt")) { hack = true; // TODO: Notice that this arity hack is even worse than most here f = new com.samsung.sjs.backend.asts.c.FunctionCall( node.getArguments().size() == 1 ? "parseInt_noradix" : "parseInt"); } else if (node.getTarget().toSource(0).equals("readline")) { hack = true; // Hack in an assert for testing purposes f = new com.samsung.sjs.backend.asts.c.FunctionCall("__readline"); } else if (node.getTarget().toSource(0).equals("printInt")) { hack = true; // Hack in an assert for testing purposes f = new com.samsung.sjs.backend.asts.c.FunctionCall("printInt"); } else if (node.getTarget().toSource(0).equals("printFloat")) { hack = true; // Hack in an assert for testing purposes f = new com.samsung.sjs.backend.asts.c.FunctionCall("printFloat"); } else if (node.getTarget().toSource(0).equals("printFloat10")) { hack = true; // Hack in an assert for testing purposes f = new com.samsung.sjs.backend.asts.c.FunctionCall("printFloat10"); } else if (node.getTarget().toSource(0).equals("string_of_int")) { hack = true; f = new com.samsung.sjs.backend.asts.c.FunctionCall("string_of_int"); } else if (node.isDirectCall()) { // Direct recursive call, possibly throuh a number of stack frames // Note that the direct call target is the PRE-closure-conversion target, // and closure conversion already renamed the target f = new com.samsung.sjs.backend.asts.c.FunctionCall(((Var) node.getTarget()).getIdentifier()); assert (!functions.isEmpty()); com.samsung.sjs.backend.asts.ir.Expression env = null; if (node.getDirectCallTarget().getEnvironmentName().equals(functions.peek().getEnvironmentName())) { env = new Var((node.getDirectCallTarget().getEnvironmentName())); } else { Var envname = mkVar(functions.peek().getEnvironmentName()); envname.setType(new EnvironmentType()); env = new ArrayIndex(envname, new com.samsung.sjs.backend.asts.ir.IntLiteral(functions.peek() .getEnvLayout().getOffset(node.getDirectCallTarget().getEnvironmentName()))); } lval_capture = true; f.addActualArgument(env.accept(this).asExpression()); lval_capture = false; f.addActualArgument( new com.samsung.sjs.backend.asts.c.NullLiteral().asValue(Types.mkObject(new LinkedList<>()))); } else if (node.getTarget() instanceof IntrinsicName) { hack = true; f = new com.samsung.sjs.backend.asts.c.FunctionCall(((IntrinsicName) node.getTarget()).getIdentifier()); } else { if (node.getTarget().getType() == null) { System.err.println("Function call target missing type: " + node.toSource(0)); } assert (node.getTarget().getType() != null); Type clos_type = node.getTarget().getType(); if (clos_type.isIntersectionType()) { clos_type = ((IntersectionType) clos_type).findFunctionType(node.getArguments().size()); } if (node.getTarget().isPure()) { // var evaluation is pure; duplicating the dereference is harmless (more work for C // optimizer) f = new ClosureCall(node.getTarget().accept(this).asExpression().inType(node.getTarget().getType()), getTypeConverter().convert(clos_type)); ret = null; } else { // fn expression may have side effects, need to memoize to avoid duplicating effects Variable vtmp = newTempVariable(clos_type); Assignment asgn = new Assignment(vtmp, "=", node.getTarget().accept(this).asExpression().asValue(clos_type)); ClosureCall ccall = new ClosureCall(vtmp.inType(node.getTarget().getType()), getTypeConverter().convert(clos_type)); f = ccall; ret = new BinaryInfixExpression(asgn, ",", ccall); } f.addActualArgument( new com.samsung.sjs.backend.asts.c.NullLiteral().asValue(Types.mkObject(new LinkedList<>()))); } Type clos_type = node.getTarget().getType(); if (clos_type != null && clos_type.isIntersectionType()) { clos_type = ((IntersectionType) clos_type).findFunctionType(node.getArguments().size()); } int argnum = 0; for (com.samsung.sjs.backend.asts.ir.Expression arg : node.getArguments()) { //f.addActualArgument(hack ? new ValueAs(arg.accept(this).asExpression(), arg.getType()) : arg.accept(this).asExpression()); // TODO: Currently, many intrinsics introduced in earlier phases aren't decorated with // types... Type formal_param = clos_type == null ? null : ((CodeType) clos_type).paramTypes().get(argnum); boolean intfloat_coercion = false; if (arg.getType() instanceof IntegerType && formal_param != null && formal_param instanceof FloatType) { intfloat_coercion = true; } com.samsung.sjs.backend.asts.c.Expression prearg = arg.accept(this).asExpression(); if (intfloat_coercion) { prearg = coerceIntToFloat(prearg); } Type castType = formal_param != null ? formal_param : arg.getType(); com.samsung.sjs.backend.asts.c.Expression carg = hack ? prearg.inType(castType) : prearg.asValue(castType); f.addActualArgument(carg); argnum++; } if (ret == null) { ret = f; } if (hack) { // If the type is unknown (eg. not populated in // IntrinsicsInliningPass) or the return type is not void, then we // perform this extra cast b/c of primitives like e.g. wcslen, // which returns a size_t (unsigned) that cannot be cast to value_t. // TODO: This is awful; IntrinsicNames don't get types set in IntrinsicsInliningPass... // mkIntrinsicName should take 2 args: expr and type if (node.getTarget().getType() == null) { ret = new CastExpression(getTypeConverter().convert(node.getType()), ret).asValue(node.getType()); } else { boolean returnIsDefinitelyVoid = false; if (node.getTarget().getType() instanceof IntersectionType) { boolean hasNonVoid = false; for (Type t : ((IntersectionType) node.getTarget().getType()).getTypes()) { if (t instanceof CodeType && !(((CodeType) t).returnType() instanceof VoidType)) hasNonVoid = true; } returnIsDefinitelyVoid = !hasNonVoid; } else if ((((CodeType) node.getTarget().getType()).returnType() instanceof VoidType)) { returnIsDefinitelyVoid = true; } if (!returnIsDefinitelyVoid) { ret = new CastExpression(getTypeConverter().convert(node.getType()), ret) .asValue(node.getType()); } } } return ret; } // temp variables can always be unboxed private Variable newTempVariable(Type t) { assert (!functions.isEmpty()); Scope s = functions.peek().getScope(); Var v = freshVar(s, t); s.declareVariable(v, t); CType cty = getTypeConverter().convert(t); com.samsung.sjs.backend.asts.c.VariableDeclaration decl = new VariableDeclaration(false, new Value()/*cty*/); Variable cvar = new Variable(v.getIdentifier()); decl.addVariable(cvar, new com.samsung.sjs.backend.asts.c.IntLiteral(0).asValue(Types.mkInt())); compiled_functions.peek().getBody().prefixStatement(decl); return cvar; } @Override public CNode visitMethodCall(MethodCall node) { com.samsung.sjs.backend.asts.c.Expression obj = node.getTarget().accept(this).asExpression() .inType(node.getTarget().getType()); String slot = node.getField(); if (!(node.getTarget().getType() instanceof PropertyContainer)) { System.err.println( "PANIC: Tried to cast non-PropertyContainer type as method dispatch target in compiling: " + node.toSource(0)); } PropertyContainer receiver_type = (PropertyContainer) node.getTarget().getType(); CodeType slot_type = (CodeType) receiver_type.getTypeForProperty(slot); CType c_slot_type = getTypeConverter().convert(slot_type, !options.useConstraints()); // mangle to closure obj = new CastExpression(new ObjectPseudoType(), obj); com.samsung.sjs.backend.asts.c.Expression o = null; Assignment asgn = null; if (node.getTarget().isPure()) { o = obj; } else { Variable vtmp = newTempVariable(receiver_type); System.err.println("Generating temp for method call receiver [" + obj.toSource(0) + "] of type " + receiver_type.toString()); // Prepare to generate some really ugly C code // First, store the object into the temp variable (it may be an expression w/ side effects) // TODO: Same transformation for field read/write // TODO: The address of the box storing an object field's value never changes, which opens // the door for an optimization that could be a big win. Similarly for the boxes for // globals. asgn = new Assignment(vtmp, "=", obj.asValue(node.getTarget().getType())); o = new CastExpression(new ObjectPseudoType(), vtmp.inType(receiver_type)); } // Then, generate a cast expression casting the field read to the correct type MemberRead mr = new MemberRead(o, c_slot_type, slot, field_codes.indexOf(slot)); mr.setDoNotCast(); com.samsung.sjs.backend.asts.c.Expression method_lookup = mr.inType(slot_type); // And call the method lookup com.samsung.sjs.backend.asts.c.FunctionCall f = new ClosureCall(method_lookup, c_slot_type); f.addActualArgument(o.asValue(node.getTarget().getType())); int argnum = 0; for (com.samsung.sjs.backend.asts.ir.Expression arg : node.getArguments()) { boolean intfloat_coercion = false; if (arg.getType() instanceof IntegerType && slot_type.paramTypes().get(argnum) instanceof FloatType) { intfloat_coercion = true; } com.samsung.sjs.backend.asts.c.Expression carg = arg.accept(this).asExpression(); f.addActualArgument(intfloat_coercion ? coerceIntToFloat(carg) : carg); argnum++; } com.samsung.sjs.backend.asts.c.Expression ret = f; //new ValueAs(f, ((CodeType)slot_type).returnType()); if (o == obj) { // pure assert (asgn == null); return ret; //return node.getTarget().getType().isArray() && !(c_slot_type.getReturnType() instanceof CVoid) // ? new ValueAs(f, slot_type.returnType()) : f; } else { // But only after executing the store to the temp variable! return new BinaryInfixExpression(asgn, ",", ret); //node.getTarget().getType().isArray() && !(c_slot_type.getReturnType() instanceof CVoid) //? new ValueAs(f, slot_type.returnType()) : f); } } @Override public CNode visitReturn(Return node) { if (node.hasResult()) { if (functions.peek().getName().equals("main") || functions.peek().getName().equals("__sjs_main")) { return new ReturnStatement(node.getResult().accept(this).asExpression().inType(Types.mkInt())); } return new ReturnStatement( node.getResult().accept(this).asExpression().asValue(node.getResult().getType())); } else { return new ReturnStatement(); } } // This exists because we sometimes need the function stack information to fiddle return types // for main, and this information isn't plumbed to the slow path generator public CNode visitSlowReturn(Return node, com.samsung.sjs.backend.asts.c.Expression val) { if (val == null) { return new ReturnStatement(); } else { if (functions.peek().getName().equals("main") || functions.peek().getName().equals("__sjs_main")) { return new ReturnStatement(val.inType(Types.mkInt())); } return new ReturnStatement(val.asValue(node.getResult().getType())); } } @Override public CNode visitUnaryOp(UnaryOp node) { if (node.getOp().equals("void")) { return generateVoidOp(node.getExpression()); } com.samsung.sjs.backend.asts.c.Expression exp = null; boolean mut = node.getOp().equals("++") || node.getOp().equals("--"); // Field reads are usually no longer lvals, but if we're modifying we can emit a field // access that *is* an lval if (mut && node.getExpression() instanceof PredictedFieldRead) { PredictedFieldRead fr = (PredictedFieldRead) node.getExpression(); // We've predicted the slot where this lives, and it's mutable so we know it's local, // not boxed com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "INLINE_BOX_ACCESS"); com.samsung.sjs.backend.asts.c.Expression oexpr = fr.getObject().accept(this).asExpression() .inType(fr.getObject().getType()); f.addActualArgument(new CastExpression(new ObjectPseudoType(), oexpr)); f.addActualArgument(new InlineCCode( fr.getOffset() + " /* " + fr.getField() + "," + field_codes.indexOf(fr.getField()) + " */")); String op = node.getOp(); String intrinsic = "UNARY_" + (node.isPostfix() ? "POST" : "PRE") + (op.equals("++") ? "_INC" : "_DEC"); if (node.getExpression().getType() instanceof FloatType) { intrinsic = "FLOAT_" + intrinsic; } com.samsung.sjs.backend.asts.c.FunctionCall intr = new com.samsung.sjs.backend.asts.c.FunctionCall( intrinsic); intr.addActualArgument(f); // relies on FIELD_READ_WRITABLE being an lval // these macros don't return value_ts return new ValueCoercion(fr.getType(), intr, false); // TODO: float shifting in interop } else if (mut && node.getExpression() instanceof FieldRead) { FieldRead fr = (FieldRead) node.getExpression(); com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "FIELD_READ_WRITABLE"); com.samsung.sjs.backend.asts.c.Expression oexpr = fr.getObject().accept(this).asExpression() .inType(fr.getObject().getType()); f.addActualArgument(new CastExpression(new ObjectPseudoType(), oexpr)); f.addActualArgument( new InlineCCode(field_codes.indexOf(fr.getField()) + " /* " + fr.getField() + " */")); String op = node.getOp(); String intrinsic = "UNARY_" + (node.isPostfix() ? "POST" : "PRE") + (op.equals("++") ? "_INC" : "_DEC"); if (node.getExpression().getType() instanceof FloatType) { intrinsic = "FLOAT_" + intrinsic; } com.samsung.sjs.backend.asts.c.FunctionCall intr = new com.samsung.sjs.backend.asts.c.FunctionCall( intrinsic); intr.addActualArgument(f); // relies on FIELD_READ_WRITABLE being an lval // these macros don't return value_ts return new ValueCoercion(fr.getType(), intr, false); // TODO: float shifting in interop } else if (mut && (node.getExpression().isVar() || (node.getExpression() instanceof ArrayIndex && ((ArrayIndex) node.getExpression()).getArray().getType() instanceof EnvironmentType))) { String op = node.getOp(); String intrinsic = "UNARY_" + (node.isPostfix() ? "POST" : "PRE") + (op.equals("++") ? "_INC" : "_DEC"); if (node.getExpression().getType() instanceof FloatType) { intrinsic = "FLOAT_" + intrinsic; } com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( intrinsic); f.addActualArgument(node.getExpression().accept(this).asExpression()); // these macros don't return value_ts return new ValueCoercion(node.getExpression().getType(), f, false); // TODO: float shifting in interop } else if (mut && node.getExpression() instanceof ArrayIndex) { // desugar array inc/dec since we don't generate lvals anymore // TODO: specialize these operations, since the get and put duplicate some work ArrayIndex ai = (ArrayIndex) node.getExpression(); // We're still abusing VarAssignment for array writes in the IR com.samsung.sjs.backend.asts.ir.IntLiteral one = mkIntLiteral(1); one.setType(Types.mkInt()); BinaryOp b = mkBinaryOp(ai, String.valueOf(node.getOp().charAt(0)), one); b.setType(ai.getType()); VarAssignment va = mkVarAssignment(ai, "=", b); va.setType(ai.getType()); return va.accept(this).asExpression().asValue(ai.getType()); } else { exp = node.getExpression().accept(this).asExpression(); } if (node.getOp().equals("~") && node.getExpression().getType() instanceof FloatType) { // need to do the double->int conversion com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "___int_of_float"); f.addActualArgument(exp.inType(node.getExpression().getType())); // underlying primitive returns a real int return new UnaryExpression(new ValueCoercion(Types.mkInt(), f, false).inType(Types.mkInt()), node.getOp(), node.isPostfix()).asValue(node.getType()); } return new UnaryExpression(exp.inType(node.getExpression().getType()), node.getOp(), node.isPostfix()) .asValue(node.getType()); } @Override public CNode visitFieldRead(FieldRead node) { // TODO: make temporaries when the object expr may not be idempotent! // see array access and method invocation for reference String field = node.getField(); if (node.getObject().getType().isConstructor()) { com.samsung.sjs.backend.asts.c.Expression ctor = new CastExpression( getTypeConverter().convert(node.getObject().getType()), node.getObject().accept(this).asExpression().inType(node.getObject().getType())); return new ValueCoercion(node.getType(), new InlineCCode(ctor.toSource(0) + "->prototype.obj"), false); } if (node.getObject().getType().isArray() && field.equals("length")) { com.samsung.sjs.backend.asts.c.Expression exp = node.getObject().accept(this).asExpression(); return new ValueCoercion(node.getType(), new CastExpression(new CInteger(), new InlineCCode("__array_len(" + exp.toSource(0) + ")")), false); } // get object as void* com.samsung.sjs.backend.asts.c.Expression obj = node.getObject().accept(this).asExpression() .inType(node.getObject().getType()); MemberRead mr = new MemberRead(new CastExpression(new ObjectPseudoType(), obj), getTypeConverter().convert(node.getType()), field, field_codes.indexOf(field)); mr.setDoNotCast(); // If the field is writable, we can skip emitting anything considering the prototype, so the // C compiler can do a better job optimizing if (((ObjectType) node.getObject().getType()).getProperty(field).isRW()) { mr.setWritable(); } return mr; } @Override public CNode visitPredictedFieldRead(PredictedFieldRead node) { // TODO: make temporaries when the object expr may not be idempotent! // see array access and method invocation for reference String field = node.getField(); com.samsung.sjs.backend.asts.c.Expression obj = node.getObject().accept(this).asExpression() .inType(node.getObject().getType()); PredictedMemberRead mr = new PredictedMemberRead(new CastExpression(new ObjectPseudoType(), obj), getTypeConverter().convert(node.getType()), field, field_codes.indexOf(field), node.getOffset()); if (node.isDirect()) { mr.setDirect(); } mr.setDoNotCast(); return mr; } @Override public CNode visitForInLoop(ForInLoop node) { com.samsung.sjs.backend.asts.c.CompoundStatement result = new com.samsung.sjs.backend.asts.c.CompoundStatement(); com.samsung.sjs.backend.asts.c.Expression map = node.getIteratee().accept(this).asExpression(); CType celemtype = getTypeConverter().convert(node.getIteratedType()); Variable v = new Variable(node.getVariable().getIdentifier()); com.samsung.sjs.backend.asts.c.VariableDeclaration idecl = new com.samsung.sjs.backend.asts.c.VariableDeclaration( false, new CMapIterator(celemtype)); Scope s = functions.peek().getScope(); Var iter = freshVar(s, Types.mkMapIteratorType(node.getIteratedType())); Variable iterv = new Variable(iter.getIdentifier()); idecl.addVariable(iterv, new com.samsung.sjs.backend.asts.c.FunctionCall(new Variable("__get_map_iterator"), map.inType(node.getIteratee().getType()))); result.addStatement(idecl); com.samsung.sjs.backend.asts.c.WhileLoop loop = new com.samsung.sjs.backend.asts.c.WhileLoop( new com.samsung.sjs.backend.asts.c.FunctionCall(new Variable("__map_iterator_has_next"), iterv)); loop.addStatement( new com.samsung.sjs.backend.asts.c.ExpressionStatement(new Assignment(v, "=", new CastExpression(new CString(), new com.samsung.sjs.backend.asts.c.FunctionCall( new Variable("__map_iterator_get_next"), iterv)) .asValue(Types.mkString())))); loop.addStatement(node.getBody().accept(this).asStatement()); result.addStatement(loop); return result; } @Override public CNode visitUntyped(UntypedAccess node) { String accessor = null; String tag = null; if (node.untypedVariable().isVar()) { switch (node.getType().rep()) { case INT: accessor = "ACCESS_INT"; break; case BOOL: accessor = "ACCESS_BOOL"; break; case STRING: accessor = "ACCESS_STRING"; break; case FLOAT: accessor = "ACCESS_FLOAT"; break; case OBJECT: case CODE: accessor = "ACCESS_HEAP_UNTYPED"; tag = node.getType().generateTag(tts); break; default: throw new IllegalArgumentException("Trying to emit untyped access to [" + node.toSource(0) + "] of rep sort " + node.getType().rep()); } } else { assert (node.untypedVariable() instanceof Require); accessor = "__coerce"; tag = node.getType().generateTag(tts); } com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall(accessor); if (node.untypedVariable().isVar()) { f.addActualArgument( new InlineCCode("&" + node.untypedVariable().accept(this).asExpression().toSource(0))); } else { f.addActualArgument(node.untypedVariable().accept(this).asExpression()); } if (tag != null) { f.addActualArgument(new InlineCCode(tag)); } return f; } /** * A class for generating the slow path. This handles statements and expressions, but relies * heavily on the state of the enclosing IRCBackend instance to coordinate. * * SlowPathGenerator is subordinate to the CBackend. For each function body, the CBackend is * run *first*, generating the type-correct code with escape checks, and as a side effect * accumulating a bunch of meta-information that is consumed by the SlowPathGenerator. This * information includes: * - Label names for jumping from (after) a failing operation in the typed path to the * corresponding point on the slow path. It is up to the SlowPathGenerator to actually emit * the appropriate labels. * - ... * * The CBackend tracks which temp variables variables, are live at each possible failure * location, and is responsible itself for generating function-scoped temporaries to share with * the slow path, and for shunting state from the fast-path-local temporary variables to the * corresponding function-scoped temps. */ public class SlowPathGenerator extends CBackend { public SlowPathGenerator(TypeTagSerializer tts, IRFieldCollector.FieldMapping m) { super(tts); field_codes = m; } @Override public com.samsung.sjs.backend.asts.c.Expression visitVar(Var node) { // TODO: handle unboxing of globals... (shouldUnbox method in IRCBackaned) if (node.getIdentifier().startsWith("__tmp")) { // this needs to be translated return new Variable("__slow_" + node.getIdentifier()); } else if (lval_capture) { return new Variable(node.getIdentifier()); } else { return IRCBackend.this.visitVar(node); } } @Override public com.samsung.sjs.backend.asts.c.Statement visitExpressionStatement( com.samsung.sjs.backend.asts.ir.ExpressionStatement node) { if (node.getExpression() == null) { return new com.samsung.sjs.backend.asts.c.ExpressionStatement(null); } CNode result = node.getExpression().accept(this); // Unfortunately, C and JS disagree on what is an expression if (result instanceof com.samsung.sjs.backend.asts.c.Expression) { if (result.asExpression() instanceof ValueCoercion) { // Peel off gratuitous value coercions result = ((ValueCoercion) result.asExpression()).getSubject(); } com.samsung.sjs.backend.asts.c.ExpressionStatement ret = new com.samsung.sjs.backend.asts.c.ExpressionStatement( result.asExpression()); // Emit a label after the call as a jump target from the type-correct path // TODO: This is basically duplicated between here and visitVarDecl... if (node.getExpression().isCall()) { com.samsung.sjs.backend.asts.ir.Call fcall = node.getExpression().asCall(); ret.setPostLabel("__dirty_call_transition" + fcall.callsiteNumber()); } return ret; } else { return (com.samsung.sjs.backend.asts.c.Statement) result; } } @Override public com.samsung.sjs.backend.asts.c.Statement visitVarDecl(VarDecl node) { // function-scoped locals were already generated by the CBackend. We only need to add // new function-scoped temp variables for exchange with the fast-path // TODO: should only need a fixed # of these per function body, reused across multiple // transition edges com.samsung.sjs.backend.asts.c.Expression var = node.getVar().accept(this).asExpression(); if (node.getVar().getIdentifier().startsWith("__tmp")) { CType decltype = IRCBackend.this.getTypeConverter().convert(node.getType()); com.samsung.sjs.backend.asts.c.VariableDeclaration d = new com.samsung.sjs.backend.asts.c.VariableDeclaration( false, /*decltype*/new Value()); d.addVariable(var, getUndef()); IRCBackend.this.compiled_functions.peek().getBody().prefixStatement(d); } Assignment wr = new Assignment(var, "=", node.getInitialValue().accept(this).asExpression().asValue(node.getInitialValue().getType())); com.samsung.sjs.backend.asts.c.ExpressionStatement ret = new com.samsung.sjs.backend.asts.c.ExpressionStatement( wr); // Emit a label after the call as a jump target from the type-correct path // TODO: This is basically duplicated between here and visitExpressionStatement... if (node.getInitialValue().isCall()) { com.samsung.sjs.backend.asts.ir.Call fcall = node.getInitialValue().asCall(); ret.setPostLabel("__dirty_call_transition" + fcall.callsiteNumber()); } return ret; } @Override public FunctionDeclaration visitFunction(Function node) { throw new IllegalArgumentException( "SlowPathGenerator should never directly emit a function; this should be orchestrated by the general C backend."); } @Override public com.samsung.sjs.backend.asts.c.Expression visitArrayIndex(ArrayIndex node) { if (node.getArray().getType() instanceof EnvironmentType) { // TODO: when lval_capture is set, this issues a warning that we're passing a // value_t* where an env_t (value_t**) is expected. This is accurate, and we really // need a cast here. This is used in the case that we're capturing an outer // recursive procedure's closure in our closure. com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( lval_capture ? "OP_GETINDEXUNBOXED" : "OP_GETINDEXSTARUNBOXED"); Var envname = (Var) node.getArray(); com.samsung.sjs.backend.asts.ir.IntLiteral offset = (com.samsung.sjs.backend.asts.ir.IntLiteral) node .getOffset(); int ioffset = (int) offset.getValue(); f.addActualArgument(node.getArray().accept(this).asExpression()); f.addActualArgument(new com.samsung.sjs.backend.asts.c.IntLiteral(ioffset)); return f; } com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "OP_GETPROP"); f.addActualArgument(node.getArray().accept(this).asExpression()); f.addActualArgument(node.getOffset().accept(this).asExpression()); return f; } @Override public CNode visitPredictedFieldRead(PredictedFieldRead node) { // TODO: Can we do any real specialization here? return visitFieldRead(node); } @Override public CNode visitFieldRead(FieldRead node) { com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "OP_GETPROP"); f.addActualArgument(node.getObject().accept(this).asExpression().asValue(node.getObject().getType())); // align the string constant f.addActualArgument(new Str(node.getField()).accept(this).asExpression().asValue(Types.mkString())); return f; } @Override public com.samsung.sjs.backend.asts.c.Expression visitPredictedFieldAssignment( PredictedFieldAssignment node) { // TODO: Need specialized primitive to exploit physical offset if the indirection map is // valid return visitFieldAssignment(node); } @Override public com.samsung.sjs.backend.asts.c.Expression visitFieldAssignment(FieldAssignment node) { // TODO: this doesn't handle +=, -=, /=, %=... // TODO: need to specialize to exploit fixed-layout lvals if valid com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "OP_SETPROP"); f.addActualArgument(node.getObject().accept(this).asExpression().asValue(node.getObject().getType())); // align the string constant f.addActualArgument(new Str(node.getField()).accept(this).asExpression().asValue(Types.mkString())); if (node.getOperator().equals("=")) { f.addActualArgument(node.getValue().accept(this).asExpression().asValue(node.getValue().getType())); } else { String op = node.getOperator().substring(0, node.getOperator().length() - 1); FieldRead fr = mkFieldRead(node.getObject(), node.getField()); fr.setType(node.getType()); BinaryOp binop = mkBinaryOp(fr, op, node.getValue()); binop.setType(node.getType()); f.addActualArgument(binop.accept(this).asExpression().asValue(node.getType())); } return f; } @Override public CNode visitReturn(Return node) { // This is horrid... need a way to convert return types without looking at function // stack return IRCBackend.this.visitSlowReturn(node, node.hasResult() ? node.getResult().accept(this).asExpression() : null); } @Override public CNode visitForInLoop(ForInLoop node) { throw new UnsupportedOperationException(); } @Override public com.samsung.sjs.backend.asts.c.Expression visitVarAssignment(VarAssignment node) { if (node.getAssignedVar() instanceof ArrayIndex) { ArrayIndex ai = (ArrayIndex) node.getAssignedVar(); if (ai.getArray().getType() instanceof EnvironmentType) { com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "OP_SETINDEXSTARUNBOXED"); Var envname = (Var) ai.getArray(); com.samsung.sjs.backend.asts.ir.IntLiteral offset = (com.samsung.sjs.backend.asts.ir.IntLiteral) ai .getOffset(); int ioffset = (int) offset.getValue(); f.addActualArgument(ai.getArray().accept(this).asExpression()); f.addActualArgument(new com.samsung.sjs.backend.asts.c.IntLiteral(ioffset)); f.addActualArgument(node.getAssignedValue().accept(this).asExpression()); return f; } com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "OP_SETPROP"); f.addActualArgument(ai.getArray().accept(this).asExpression()); f.addActualArgument(ai.getOffset().accept(this).asExpression()); f.addActualArgument(node.getAssignedValue().accept(this).asExpression()); return f; } else if (node.getOperator().charAt(0) == '=') { Assignment asgn; asgn = new Assignment(node.getAssignedVar().accept(this).asExpression(), node.getOperator(), node .getAssignedValue().accept(this).asExpression().asValue(node.getAssignedValue().getType())); return asgn; } else { // This is +=, -=, ... String op = node.getOperator().substring(0, node.getOperator().length() - 1); BinaryOp binop = mkBinaryOp(node.getAssignedVar(), op, node.getAssignedValue()); binop.setType(node.getAssignedVar().getType()); return new Assignment(node.getAssignedVar().accept(this).asExpression(), "=", binop.accept(this).asExpression()); } } @Override public CNode visitUnaryOp(UnaryOp node) { com.samsung.sjs.backend.asts.c.FunctionCall f = null; boolean primret = false; switch (node.getOp()) { case "-": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_NEG"); break; case "+": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_POS"); break; case "~": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_BITNOT"); break; case "!": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_LOGNOT"); break; case "++": case "--": if (node.getExpression().isVar() || (node.getExpression() instanceof ArrayIndex && ((ArrayIndex) node.getExpression()).getArray().getType() instanceof EnvironmentType)) { f = new com.samsung.sjs.backend.asts.c.FunctionCall("UNARY_" + (node.isPostfix() ? "POST" : "PRE") + (node.getOp().equals("++") ? "_INC" : "_DEC")); primret = true; break; } else if (node.getExpression() instanceof FieldRead) { // TODO: predicted field reads f = new com.samsung.sjs.backend.asts.c.FunctionCall( "OP_field_" + (node.getOp().equals("++") ? "inc" : "dec")); FieldRead fr = (FieldRead) node.getExpression(); f.addActualArgument(fr.getObject().accept(this).asExpression()); f.addActualArgument( new com.samsung.sjs.backend.asts.c.IntLiteral(field_codes.indexOf(fr.getField()))); f.addActualArgument(new com.samsung.sjs.backend.asts.c.BoolLiteral(node.isPostfix())); return f; } // TODO: General arrays default: throw new UnsupportedOperationException(node.getOp()); } f.addActualArgument(node.getExpression().accept(this).asExpression()); if (primret) { // TODO: these ++ and -- impls are hacks... they should really return values return new CastExpression(new CInteger(), f); } return f; } @Override public com.samsung.sjs.backend.asts.c.Expression visitBinaryOp(BinaryOp node) { com.samsung.sjs.backend.asts.c.FunctionCall f = null; switch (node.getOp()) { case "|": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_BITOR"); break; case "^": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_BITXOR"); break; case "&": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_BITAND"); break; case "==": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_EQ"); break; case "!=": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_NE"); break; case "===": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_STRICTEQ"); break; case "!==": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_STRICTNE"); break; case "<": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_LT"); break; case ">": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_GT"); break; case "<=": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_LE"); break; case ">=": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_GE"); break; case "+": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_ADD"); break; case "-": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_SUB"); break; case "*": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_MUL"); break; case "/": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_DIV"); break; case "%": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_MOD"); break; case ">>": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_SHR"); break; case "<<": f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_SHL"); break; case "||": // Pseudo op f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_FALSYOR"); break; case "&&": // Pseudo op f = new com.samsung.sjs.backend.asts.c.FunctionCall("OP_FALSYAND"); break; default: // TODO: missing bit shifts throw new UnsupportedOperationException(node.getOp()); } f.addActualArgument(node.getLeft().accept(this).asExpression().asValue(node.getLeft().getType())); f.addActualArgument(node.getRight().accept(this).asExpression().asValue(node.getRight().getType())); return f; } @Override public CNode visitAllocNewObject(AllocNewObject node) { // TODO: reduce duplication between this and IRCBackend::visitAllocNewObject() // Getting this to work before there are proper constructor types and AST nodes is too // brittle; it requires spreading around quite a few special cases, or multiple // re-traversals of the AST to re-type function references as constructor references. // For now, we'll keep the code, but won't use it. com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "construct_object"); assert (node.getVTable() != null); CType c_ctor_type = getTypeConverter().convert(node.getConstructor().getType()); // TODO: The construction and use of this vtable are more subtle than for object literals, // because we have to deal with ordering wrt inherited fields as well. // TODO: Constructor code gen can always statically resolve field offsets of the receiver int indir_id = memo_vtable(node.getVTable()); // TODO: This breakdown is wrong. The constructor should have control of the vtable, not // the allocation site f.addActualArgument(new Variable("__vtable_id_" + indir_id)); // TODO: Need to check for type errors dynamically String tag = tts != null ? node.getType().generateTag(tts) : "NULL"; f.addActualArgument(new InlineCCode(tag)); assert (node.getConstructor().isPure()); // It SHOULD be the case that 3-addr conversion already memoized this since the closure invocation macro already duplicates... com.samsung.sjs.backend.asts.c.Expression ctor_c = node.getConstructor().accept(this).asExpression(); //f.addActualArgument(new com.samsung.sjs.backend.asts.c.NullLiteral()); // TODO: get proto from the closure com.samsung.sjs.backend.asts.c.FunctionCall proto_lookup = new com.samsung.sjs.backend.asts.c.FunctionCall( "OP_GETPROP"); proto_lookup.addActualArgument(ctor_c); proto_lookup.addActualArgument(new InlineCCode("string_as_val(L\"prototype\")")); f.addActualArgument(proto_lookup.inType(new ObjectType())); ObjectType ty = (ObjectType) node.getType(); assert (ty != null); f.addActualArgument(new com.samsung.sjs.backend.asts.c.IntLiteral(ty.properties().size())); // f allocates the empty object, with no initialized fields --- not even boxes // TODO: Inheritance! Actually, that should be done in codegen for the constructor, which // should assume no boxes exist. com.samsung.sjs.backend.asts.c.FunctionCall ctor_call = new com.samsung.sjs.backend.asts.c.FunctionCall( "TYPEDCALL"); // construct_object returns object_t* ctor_call.addActualArgument(new ValueCoercion(ty, f, options.encodeVals())); ctor_call.addActualArgument(ctor_c); ctor_call.addActualArgument(new com.samsung.sjs.backend.asts.c.IntLiteral(node.getArguments().size())); for (com.samsung.sjs.backend.asts.ir.Expression e : node.getArguments()) { ctor_call.addActualArgument(e.accept(this).asExpression()); } return ctor_call; } @Override public CNode visitMethodCall(MethodCall node) { // TODO: Optimize for predictable property offsets... FieldRead fr = mkFieldRead(node.getTarget(), node.getField()); PropertyContainer receiver_type = (PropertyContainer) node.getTarget().getType(); Type slot_type = receiver_type.getTypeForProperty(node.getField()); fr.setType(slot_type); com.samsung.sjs.backend.asts.c.FunctionCall f = new com.samsung.sjs.backend.asts.c.FunctionCall( "TYPEDCALL"); f.addActualArgument(node.getTarget().accept(this).asExpression()); f.addActualArgument(fr.accept(this).asExpression()); f.addActualArgument(new com.samsung.sjs.backend.asts.c.IntLiteral(node.getArguments().size())); for (com.samsung.sjs.backend.asts.ir.Expression arg : node.getArguments()) { com.samsung.sjs.backend.asts.c.Expression carg = arg.accept(this).asExpression(); f.addActualArgument(carg.asValue(arg.getType())); } return f; } @Override public CNode visitFunctionCall(com.samsung.sjs.backend.asts.ir.FunctionCall node) { com.samsung.sjs.backend.asts.ir.Expression e = node.getTarget(); com.samsung.sjs.backend.asts.ir.Expression target2 = node.getTarget(); com.samsung.sjs.backend.asts.ir.FunctionCall node2 = null; boolean primhack = false; com.samsung.sjs.backend.asts.c.FunctionCall f = null; if (e instanceof IntrinsicName || e.toSource(0).equals("assert")) { // IRCBackend::visitFunctionCall has a litany of special cases... f = new com.samsung.sjs.backend.asts.c.FunctionCall(e.toSource(0)); if (e.toSource(0).equals("printString")) { f = new com.samsung.sjs.backend.asts.c.FunctionCall("print"); // ... } primhack = true; } else if (node.isDirectCall()) { // Direct recursive call, possibly throuh a number of stack frames // Note that the direct call target is the PRE-closure-conversion target, // and closure conversion already renamed the target f = new com.samsung.sjs.backend.asts.c.FunctionCall(((Var) node.getTarget()).getIdentifier()); assert (!functions.isEmpty()); com.samsung.sjs.backend.asts.ir.Expression env = null; if (node.getDirectCallTarget().getEnvironmentName() .equals(IRCBackend.this.functions.peek().getEnvironmentName())) { env = new Var((node.getDirectCallTarget().getEnvironmentName())); } else { Var envname = mkVar(IRCBackend.this.functions.peek().getEnvironmentName()); envname.setType(new EnvironmentType()); env = new ArrayIndex(envname, new com.samsung.sjs.backend.asts.ir.IntLiteral(IRCBackend.this.functions.peek() .getEnvLayout().getOffset(node.getDirectCallTarget().getEnvironmentName()))); } lval_capture = true; f.addActualArgument(env.accept(this).asExpression()); lval_capture = false; f.addActualArgument(new com.samsung.sjs.backend.asts.c.NullLiteral() .asValue(Types.mkObject(new LinkedList<>()))); } else { f = new com.samsung.sjs.backend.asts.c.FunctionCall("TYPEDCALL"); f.addActualArgument(new com.samsung.sjs.backend.asts.c.NullLiteral() .asValue(Types.mkObject(new LinkedList<>()))); f.addActualArgument(target2.accept(this).asExpression().asValue(target2.getType())); f.addActualArgument(new com.samsung.sjs.backend.asts.c.IntLiteral(node.getArguments().size())); } for (com.samsung.sjs.backend.asts.ir.Expression arg : node.getArguments()) { // TODO: Right now we'll crash if we feed ill-typed values to most intrinsics... com.samsung.sjs.backend.asts.c.Expression carg = arg.accept(this).asExpression(); System.err.println(carg.getClass()); f.addActualArgument(primhack ? carg.inType(arg.getType()) : carg.asValue(arg.getType())); } if (e instanceof IntrinsicName) { return new ValueCoercion(node.getType(), f, false); // TODO: switch this once we consistently encode floats! } else { return f; } } @Override public CNode visitUntyped(UntypedAccess node) { return node.untypedVariable().accept(this); } } }