Javaのバイトコードで遊ぶ (四則演算)

Javaのバイトコードで遊ぶ (Hello World)

加算や減算などの基本的な数値演算をやってみます。

まずはintやfloatの計算。 定数値をスタックに積む命令にはバリエーションがあります。 値の大小に応じて、BIPUSHやSIPUSHを使う方法や、visitLdcInsnで任意の値を指定することができます。 visitLdcInsnは渡された値の型に応じたコードを生成してくれます。 また0や1に対しては、ICONST_0やICONST_1といった専用の命令も用意されています。

// intの演算
// 123 + 456
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
mv.visitIntInsn(BIPUSH, 123); // Byte.MIN_VALUE - Byte.MAX_VALUE の範囲の値
mv.visitIntInsn(SIPUSH, 456); // SHORT.MIN_VALUE - SHORT.MAX_VALUE の範囲の値
mv.visitInsn(IADD);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
        "(I)V", false);

// intの演算
// 123 - 456
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
mv.visitIntInsn(SIPUSH, 123);
mv.visitLdcInsn(456); // このような指定も可能
mv.visitInsn(ISUB);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
        "(I)V", false);

// floatの演算
// 123.0 / 456.0
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
mv.visitLdcInsn(123.0F);
mv.visitLdcInsn(456.0F);
mv.visitInsn(FDIV);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
        "(F)V", false);

次にlongやdoubleの計算。 intやfloatと異なり、1つの値に対してスタックやローカル変数領域において 2スロット消費しますので、visitMaxsの指定には注意が必要です。 またキャストも用いてみました。

// longの演算
// Integer.MAX_VALUE + 1
// 1つの数が2スロット消費するので注意
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
mv.visitLdcInsn(Integer.MAX_VALUE);
mv.visitInsn(I2L); // int -> long のキャスト
mv.visitInsn(LCONST_1);
mv.visitInsn(LADD);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
        "(J)V", false);

// doubleの演算
// 123.321 * 456.654
// 1つの数が2スロット消費するので注意
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
mv.visitLdcInsn(123.321);
mv.visitLdcInsn(456.654);
mv.visitInsn(DMUL);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
        "(D)V", false);

実行すると次の結果が表示されます。

579
-333
0.26973686
2147483648
56315.027934

最後にコードの全体を載せておきます。

Calculation

package example;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class Calculation implements Opcodes {
  private static byte[] generate(String className) {
    ClassWriter cw = new ClassWriter(0);
    MethodVisitor mv;

    cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, className, null,
        "java/lang/Object", null);

    // constructor
    mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
    mv.visitCode();
    mv.visitVarInsn(ALOAD, 0);
    mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V",
        false);
    mv.visitInsn(RETURN);
    mv.visitMaxs(1, 1);
    mv.visitEnd();

    // main
    mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main",
        "([Ljava/lang/String;)V", null, null);
    mv.visitCode();

    // intの演算
    // 123 + 456
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
    mv.visitIntInsn(BIPUSH, 123); // Byte.MIN_VALUE - Byte.MAX_VALUE の範囲の値
    mv.visitIntInsn(SIPUSH, 456); // SHORT.MIN_VALUE - SHORT.MAX_VALUE の範囲の値
    mv.visitInsn(IADD);
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V",
        false);

    // intの演算
    // 123 - 456
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
    mv.visitIntInsn(SIPUSH, 123);
    mv.visitLdcInsn(456); // このような指定も可能
    mv.visitInsn(ISUB);
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V",
        false);

    // floatの演算
    // 123.0 / 456.0
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
    mv.visitLdcInsn(123.0F);
    mv.visitLdcInsn(456.0F);
    mv.visitInsn(FDIV);
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(F)V",
        false);

    // longの演算
    // Integer.MAX_VALUE + 1
    // 1つの数が2スロット消費するので注意
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
    mv.visitLdcInsn(Integer.MAX_VALUE);
    mv.visitInsn(I2L); // int -> long のキャスト
    mv.visitInsn(LCONST_1);
    mv.visitInsn(LADD);
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V",
        false);

    // doubleの演算
    // 123.321 * 456.654
    // 1つの数が2スロット消費するので注意
    mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
        "Ljava/io/PrintStream;");
    mv.visitLdcInsn(123.321);
    mv.visitLdcInsn(456.654);
    mv.visitInsn(DMUL);
    mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(D)V",
        false);

    mv.visitInsn(RETURN);
    mv.visitMaxs(5, 1);
    mv.visitEnd();
    cw.visitEnd();

    return cw.toByteArray();
  }

  public static void main(String... args) throws Exception {
    String className = "Calculation";
    byte[] bytes = generate(className);
    ExampleUtil.execMain(className, bytes);
    ExampleUtil.write(className, bytes);
  }
}