Page 5
Duplicating an existing operator. Step 2 - The Parser
Page 6
Index
See this article in english 
Page 7
Implementing a new operator by tree translation

Duplicating an existing operator. Step 3 - The Code Generation

Alexander Hristov

We finally arrive to probably the most interesting part - once the AST has been built, generating code from it. Actually, "this last" step is comprised of several steps :

This step includes the following nested steps:

All these classes do their work recursively, using the "visitor" pattern. If you don't know how this pattern works, I suggest that you read any of the following resources before continuing. All of them provide a good introduction to this pattern.

Visitors must either subclass JCTree.Visitor or implement the com.sun.source.tree.TreeVisitor interface. The former one seems most appropriate (and all the above steps use it) because it exposes the real data type of the nodes instead of merely the interface they implement.

Attribution and our operator

During the attribution stage, one of the actions that is performed is name resolution. This action affects everything, including operators. Java needs to know what types of operands an operator accept, and whether the action of the operator can be mapped directly to a JVM opcode or not. The com.sun.tools.javac.code.Symtab class holds all system names. A system name is a name that is buit-in in the Java language, that is known to the compiler, and that has some specific effect that cannot be achieved through Java-defined constructs. During compilation, a single instance of this class is used, and its constructor initializes the instance by adding all known system names. If we don't add our operator here, the attributor will think it refers to a name, and will issue the following error:

f:\javac\compiler\dist\bin\Test.java:3: cannot find symbol
symbol  : method #(int,int)
location: class Test
		System.out.println(1 # 2);
		                     ^
1 error

Adding an operator to the system symbol table is very easy : by calling the enterBinop() method (or enterUnop() if the operator is unary), which takes the following parameters: the name of the operator (a plain string), the type of the left operand, the type of the right operand, the result type and the opcode to use when translating this operator.

Types (primitive or otherwise) inside javac are represented as instances of the com.sun.tools.javac.code.Type class. Predefined types (int, short, Object, etc...) are declared as final instance variables of the SymTab class, so referring to them is as easy as specifying the appropriate variable name. For example, the Type instance representing the primitive int type is stored in the intType variable.

Each call to enterBinop() or enterUnop() allows to specify a single (left operand type,right operand type, result type) sequence, so if the operator allows operands of varying types (as is usually the case),you'll need as many calls as types you allow, excluding implicit widening conversions.

Finally, the VM opcode can be a real one (if there is a Java Virtual Machine instruction that directly performs the action of the operator) or a virtual one (if the action of the operator is more complex than a single VM instruction). In the second case, (a virtual opcode), the value you assign is up to you, as long as it is greater than 255 (the value of the biggest existing real VM opcode) and doesn't conflict with existing ones. Actually, if your operator is not going to make it to the code generation step (for example because it gets translated to something else during the desugaring phase), what you put here is largely irrelevant (except the special error constant).
All opcodes used by the compiler are defined as constants in the com.sun.tools.javac.jvm.ByteCodes class. If you need to add virtual opcodes, add them there as named constants.

Let's see a sample operator definition by means of enterBinop:

enterBinop("/", doubleType, doubleType, doubleType, ddiv);
enterBinop("/", floatType, floatType, floatType, fdiv);
enterBinop("/", longType, longType, longType, ldiv);
enterBinop("/", intType, intType, intType, idiv);

So in order to define our # operator, we must add the following lines :

Symtab.java
 
public class Symtab {
   ...
   /** Constructor; enters all predefined identifiers and operators
    *  into symbol table.
    */
   protected Symtab(Context context) throws CompletionFailure {
      context.put(symtabKey, this);

      names = Name.Table.instance(context);

      // Create the unknown type
      unknownType = new Type(TypeTags.UNKNOWN, null);
      ...
      enterBinop("!=", floatType, floatType, booleanType, fcmpl, ifne);
      enterBinop("!=", longType, longType, booleanType, lcmp, ifne);
      enterBinop("!=", intType, intType, booleanType, if_icmpne);

enterBinop("#", objectType, objectType, booleanType, if_acmpne); enterBinop("#", booleanType, booleanType, booleanType, if_icmpne); enterBinop("#", doubleType, doubleType, booleanType, dcmpl, ifne); enterBinop("#", floatType, floatType, booleanType, fcmpl, ifne); enterBinop("#", longType, longType, booleanType, lcmp, ifne); enterBinop("#", intType, intType, booleanType, if_icmpne);
enterBinop("&&", booleanType, booleanType, booleanType, bool_and); enterBinop("||", booleanType, booleanType, booleanType, bool_or); } }
 

In this case, we have simply adapted the calls that define the != operator, but using # as a symbol. If the operator is mapped directly to a real VM opcode, there's nothing more to do. Your version of the compiler is ready to compile source code that includes the newly defined # operator. Rebuild the compiler and try it with the following class:

Test.java
 
public class Test {
  public static void main(String[] args) {
    int x = 3;
    int y = 5;
    System.out.println((x#y)?"x is not equal to y":"x is equal to y");
  }
}  

 

Some easy changes

Now that we have a working version of our operator, we can perform some easy changes. For example, we could limit the # operator to be available only for comparison of primitives and not objects. A possible rationale for this would be to prevent mistakes of the type s1 != s2 where s1 and s2 are strings. This compiles under Java, and possibly returns true even if the strings contain the same value. The way to disallow a type combination in an operator is not by deleting our enterBinop line, but by specifying a special value called "error" as the VM opcode:

enterBinop("#", objectType, objectType, booleanType, error);

Build the compiler again and try to compile the following class:

Test.java
 
public class Test {
  public static void main(String[] args) {
    String s1 = new String("Hello");
    String s2 = new String("Hello");
    System.out.println(s1 # s2 );
  }
}

 

You will now get the following compile-time error:

f:\javac\compiler\dist\bin\Test.java:5: operator # cannot be applied to java.lang.String,java.lang.String
		System.out.println(s1 # s2 );
		                   ^
1 error

Now let's do another easy change. Remember the typical beginner's mistake in Java with the / operator? The division operator in java perfoms an integer division (without decimals) when both operands are integer?. So if you try to compile and run this class:

Test.java
 
public class Test {
   public static void main(String[] args) {
      System.out.println( 5 / 2 );
   }
}

 

you will get a "2" instead of a "2.5". Well, this can be changed. Look for the definition of the / operator. You'll see this:

Symtab.java
 
enterBinop("/", doubleType, doubleType, doubleType, ddiv);
enterBinop("/", floatType, floatType, floatType, fdiv);
enterBinop("/", longType, longType, longType, ldiv);
enterBinop("/", intType, intType, intType, idiv);

 

Whenever a / operator has two int or two long operands, the operation performed is an idiv (integer division) or an ldiv. Now if you comment out the last two lines, whenever a / operand is found with two int operands, since there is not a specific entry for this combination, the ints will be promoted to floats and the result will have decimals. Try it.

 

Comments

 

Add a Comment

Name (optional)
EMail (optional, will not be displayed)

Text