Skip to content

Instantly share code, notes, and snippets.

@m3m0r7
Last active May 19, 2025 13:45
Show Gist options
  • Save m3m0r7/6d4dce2af4ece6e41293afb4e2caa5e1 to your computer and use it in GitHub Desktop.
Save m3m0r7/6d4dce2af4ece6e41293afb4e2caa5e1 to your computer and use it in GitHub Desktop.
A sample code for PHPerKaigi 2019 which is an introduction to run JVM on PHP.
<?php
//
// [Test.java]
// class Test
// {
// public static void main(String[] args)
// {
// System.out.println("Hello World!");
// }
// }
//
// [Compile]
// javac -encoding UTF8 Test.java
//
$handle = fopen('Test.class', 'r');
$magic = current(unpack('N', fread($handle, 4)));
$minor_version = current(unpack('n', fread($handle, 2)));
$major_version = current(unpack('n', fread($handle, 2)));
$constant_pool_count = current(unpack('n', fread($handle, 2)));
//var_dump(
// dechex($magic),
// $minor_version,
// $major_version,
// $constant_pool_count
//);
function readUnsignedShort() {
global $handle;
return current(unpack('n', fread($handle, 2)));
}
$constant_pool_entries = [];
for ($i = 0; $i < $constant_pool_count - 1; $i++) {
$entry = [];
$tag = current(unpack('C', fread($handle, 1)));
switch ($tag) {
case 0x0A:
case 0x09:
$entry = [
'class_index' => readUnsignedShort(),
'name_and_type_index' => readUnsignedShort(),
];
break;
case 0x08:
$entry = [
'string_index' => readUnsignedShort(),
];
break;
case 0x07:
$entry = [
'class_index' => readUnsignedShort(),
];
break;
case 0x01:
$length = readUnsignedShort();
$entry = [
'string' => implode(
array_map(
'chr',
unpack("c{$length}", fread($handle, $length))
)
),
];
break;
case 0x0C:
$entry = [
'name_index' => readUnsignedShort(),
'descriptor_index' => readUnsignedShort(),
];
break;
}
$constant_pool_entries[] = $entry + ['tag' => $tag];
}
$access_flag = readUnsignedShort();
$this_class = readUnsignedShort();
$super_class = readUnsignedShort();
$interface_count = readUnsignedShort();
$fields_count = readUnsignedShort();
$methods_count = readUnsignedShort();
$cpe = $constant_pool_entries;
//var_dump(
// $access_flag,
// $cpe[$cpe[$this_class - 1]['class_index'] - 1],
// $cpe[$cpe[$super_class - 1]['class_index'] - 1],
// $interface_count,
// $fields_count,
// $methods_count
//);
function readUnsignedInt() {
global $handle;
return current(unpack('N', fread($handle, 4)));
}
function readAttribute() {
global $handle, $constant_pool_entries;
$cpe = $constant_pool_entries;
$attribute_name_index = readUnsignedShort();
$attributes_length = readUnsignedInt();
$name = $cpe[$attribute_name_index - 1]['string'];
$result = [
'name' => $name,
];
if ($name === 'Code') {
$result['max_stack'] = readUnsignedShort();
$result['max_locals'] = readUnsignedShort();
$result['code_length'] = readUnsignedInt();
$result['code'] = fread($handle, $result['code_length']);
$result['exception_table_length'] = readUnsignedShort();
$result['exception_tables'] = [];
for ($k = 0; $k < $result['exception_table_length']; $k++) {
$result['exception_tables'][] = [
'start_pc' => readUnsignedShort(),
'end_pc' => readUnsignedShort(),
'handler_pc' => readUnsignedShort(),
'catch_type' => readUnsignedShort(),
];
}
$result['attributes_count'] = readUnsignedShort();
$result['attribute_info'] = readAttribute();
} elseif ($name === 'LineNumberTable') {
$result['line_number_table_length'] = readUnsignedShort();
$result['line_number_tables'] = [];
for ($k = 0; $k < $result['line_number_table_length']; $k++) {
$result['line_number_tables'][] = [
'start_pc' => readUnsignedShort(),
'line_number' => readUnsignedShort(),
];
}
} elseif ($name === 'SourceFile') {
$result['sourcefile_index'] = readUnsignedShort();
}
return $result;
}
$methods = [];
for ($i = 0; $i < $methods_count; $i++) {
$method = [];
$method['access_flag'] = readUnsignedShort();
$method['name_index'] = $cpe[readUnsignedShort() - 1]['string'];
$method['descriptor_index'] = $cpe[readUnsignedShort() - 1]['string'];
$method['attributes_count'] = readUnsignedShort();
$method['attribute_info'] = [];
for ($j = 0; $j < $method['attributes_count']; $j++) {
$method['attribute_info'][] = readAttribute();
}
$methods[] = $method;
}
$attribute_count = readUnsignedShort();
$attribute_info = [];
for ($j = 0; $j < $attribute_count; $j++) {
$attribute_info[] = readAttribute();
}
// クラスの定義を読み込む
require __DIR__ . '/classes.php';
// main を探す
$main = array_values(array_filter($methods, fn ($method) => $method['name_index'] === 'main'))[0];
// main の code attribute を探す
$code_attribute = array_values(array_filter($main['attribute_info'], fn ($method) => $method['name'] === 'Code'))[0];
$code = $code_attribute['code'];
// オペコードを実行する
$stacks = [];
for ($i = 0; $i < strlen($code); $i++) {
$opcode = ord($code[$i]);
switch ($opcode) {
case 0xB2: // getstatic
// getstatic はオペランドを 2 つとる
$low = ord($code[++$i]);
$high = ord($code[++$i]);
$index = ($low | $high) - 1;
// java/lang/System
$callee_class = $cpe[$cpe[$cpe[$index]['class_index'] - 1]['class_index'] - 1]['string'];
// out
$field = $cpe[$cpe[$cpe[$index]['name_and_type_index'] - 1]['name_index'] - 1]['string'];
// フィールドの型情報
$return_type = $cpe[$cpe[$cpe[$index]['name_and_type_index'] - 1]['descriptor_index'] - 1]['string'];
// PHP で扱えるようにする
$callee_class = str_replace('/', '\\', $callee_class);
$stacks[] = [
$callee_class::$$field,
$return_type,
];
break;
case 0x12: // ldc
$index = ord($code[++$i]) - 1;
$stacks[] = $cpe[$cpe[$index]['string_index'] - 1]['string'];
break;
case 0xB6: // invokevirtual
$low = ord($code[++$i]);
$high = ord($code[++$i]);
$index = ($low | $high) - 1;
$callee_info = $cpe[$cpe[$index]['name_and_type_index'] - 1];
$method_name = $cpe[$callee_info['name_index'] - 1]['string']; // println
$arguments_string = $cpe[$callee_info['descriptor_index'] - 1]['string'];
$arguments_size = count(explode(';', $arguments_string)) - 1;
$arguments = [];
for ($j = 0; $j < $arguments_size; $j++) {
$arguments[] = array_pop($stacks);
}
[$class_instance] = array_pop($stacks);
$return = call_user_func([$class_instance, $method_name], ...$arguments);
break;
case 0xB1: // return
// return の場合は処理自体を終了させる
break 2;
default:
throw new Exception("未定義のオペコードです {$opcode}");
};
}
<?php
// 文字列出力用のクラスを定義する
namespace java\Lang {
class System
{
public static $out;
public static function __staticConstruct(): void
{
static::$out = new \java\Lang\Object_();
}
}
class Object_
{
public function println(string $string): void
{
echo "{$string}\n";
}
}
System::__staticConstruct();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment