Last active
May 19, 2025 13:45
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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}"); | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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