├── .gitignore ├── LICENSE ├── README.md ├── bytecode ├── op.go ├── op_string.go └── width_table.go ├── cmd ├── go-jdk │ ├── javap_command.go │ ├── jdeps_command.go │ ├── main.go │ ├── run_command.go │ ├── subcommand.go │ └── version_command.go └── internal │ └── cmdutil │ └── cmdutil.go ├── docs ├── architecture.md ├── logo.png └── logo_small.png ├── goreflect └── goreflect.go ├── ir ├── arg.go ├── class.go ├── inst.go ├── inst_flags.go ├── instkind_string.go └── utils.go ├── irfmt └── print.go ├── irgen ├── generator.go ├── irgen.go ├── irgen_test.go ├── stack.go ├── testdata │ └── irgen │ │ └── C1.java └── utils.go ├── iropt └── iropt.go ├── javap └── print.go ├── javatest ├── golib.go ├── java_test.go └── testdata │ ├── _golib │ ├── benchutil │ │ └── B.java │ └── testutil │ │ └── T.java │ ├── _javalib │ └── testutil │ │ └── T.java │ ├── arith1 │ └── Test.java │ ├── arrayreverse │ └── Test.java │ ├── arrays1 │ └── Test.java │ ├── arrays2 │ └── Test.java │ ├── bench │ ├── Bench.java │ └── callgo │ │ └── Bench.java │ ├── bubblesort │ └── Test.java │ ├── eratosthenes │ └── Test.java │ ├── gocall1 │ └── Test.java │ ├── gocall2 │ └── Test.java │ ├── intvalues │ └── Test.java │ ├── longvalues │ └── Test.java │ ├── loops1 │ └── Test.java │ ├── scopes │ └── Test.java │ ├── staticcall1 │ └── Test.java │ ├── staticcall2 │ └── Test.java │ ├── staticcall3 │ └── Test.java │ └── staticfinal │ └── Test.java ├── jclass ├── access_flags.go ├── attributes.go ├── class.go ├── constants.go ├── decoder.go ├── descriptor.go ├── descriptor_test.go └── utf.go ├── jdeps └── jdeps.go ├── jit ├── compiler │ └── x64 │ │ ├── compiler.go │ │ └── utils.go ├── jit.go └── x64 │ ├── asm_test.go │ ├── assembler.go │ ├── constants.go │ ├── instruction.go │ ├── jmp_test.go │ ├── testdata │ ├── asmtest.s │ └── gen.go │ └── utils.go ├── jruntime ├── bindfunc.go ├── call.go ├── call_amd64.s ├── call_amd64_test.go ├── env.go ├── new_object.go ├── object.go └── vm.go ├── loader ├── loader.go └── util.go ├── mmap ├── manager.go ├── mmap.go └── mmap_linux.go ├── symbol ├── symbol.go └── symbol_test.go └── vmdat └── vmdat.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.class 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 quasilyte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-jdk 2 | 3 | ![Logo](docs/logo_small.png) 4 | 5 | [go-jdk](https://github.com/quasilyte/go-jdk) is [OpenJDK](https://ru.wikipedia.org/wiki/OpenJDK)-like implementation 6 | written in Go with a goal to deliver a great embeddable [JVM](https://en.wikipedia.org/wiki/Java_virtual_machine) for 7 | [Go](http://golang.org/) applications without [CGo](https://golang.org/cmd/cgo/). 8 | 9 | Key features: 10 | 11 | * JVM bytecode converted to [register-based form](https://www.usenix.org/legacy/events%2Fvee05%2Ffull_papers/p153-yunhe.pdf) 12 | * Loaded code is JIT-compiled right away, no run-time tracing involved 13 | * Efficient `Go->JVM` calls 14 | * Efficient `JVM->Go` calls 15 | * `native` Java methods can be written in Go 16 | 17 | > Note: this project is in its early state. 18 | 19 | ```bash 20 | # Run Java class method (main or any other static method): 21 | go-jdk run -class Foo.class -method helloWorld 22 | 23 | # Disassemble Java class file with go-jdk: 24 | go-jdk javap Foo.class 25 | 26 | # Print IR representation instead of JVM bytecode: 27 | go-jdk javap -format=ir Foo.class 28 | 29 | # Print Java class dependencies: 30 | go-jdk jdeps Foo.class 31 | ``` 32 | -------------------------------------------------------------------------------- /bytecode/op_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Op -linecomment=true"; DO NOT EDIT. 2 | 3 | package bytecode 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[Nop-0] 12 | _ = x[Aconstnull-1] 13 | _ = x[Iconstm1-2] 14 | _ = x[Iconst0-3] 15 | _ = x[Iconst1-4] 16 | _ = x[Iconst2-5] 17 | _ = x[Iconst3-6] 18 | _ = x[Iconst4-7] 19 | _ = x[Iconst5-8] 20 | _ = x[Lconst0-9] 21 | _ = x[Lconst1-10] 22 | _ = x[Fconst0-11] 23 | _ = x[Fconst1-12] 24 | _ = x[Fconst2-13] 25 | _ = x[Dconst0-14] 26 | _ = x[Dconst1-15] 27 | _ = x[Bipush-16] 28 | _ = x[Sipush-17] 29 | _ = x[Ldc-18] 30 | _ = x[Ldcw-19] 31 | _ = x[Ldc2w-20] 32 | _ = x[Iload-21] 33 | _ = x[Lload-22] 34 | _ = x[Fload-23] 35 | _ = x[Dload-24] 36 | _ = x[Aload-25] 37 | _ = x[Iload0-26] 38 | _ = x[Iload1-27] 39 | _ = x[Iload2-28] 40 | _ = x[Iload3-29] 41 | _ = x[Lload0-30] 42 | _ = x[Lload1-31] 43 | _ = x[Lload2-32] 44 | _ = x[Lload3-33] 45 | _ = x[Fload0-34] 46 | _ = x[Fload1-35] 47 | _ = x[Fload2-36] 48 | _ = x[Fload3-37] 49 | _ = x[Dload0-38] 50 | _ = x[Dload1-39] 51 | _ = x[Dload2-40] 52 | _ = x[Dload3-41] 53 | _ = x[Aload0-42] 54 | _ = x[Aload1-43] 55 | _ = x[Aload2-44] 56 | _ = x[Aload3-45] 57 | _ = x[Iaload-46] 58 | _ = x[Laload-47] 59 | _ = x[Faload-48] 60 | _ = x[Daload-49] 61 | _ = x[Aaload-50] 62 | _ = x[Baload-51] 63 | _ = x[Caload-52] 64 | _ = x[Saload-53] 65 | _ = x[Istore-54] 66 | _ = x[Lstore-55] 67 | _ = x[Fstore-56] 68 | _ = x[Dstore-57] 69 | _ = x[Astore-58] 70 | _ = x[Istore0-59] 71 | _ = x[Istore1-60] 72 | _ = x[Istore2-61] 73 | _ = x[Istore3-62] 74 | _ = x[Lstore0-63] 75 | _ = x[Lstore1-64] 76 | _ = x[Lstore2-65] 77 | _ = x[Lstore3-66] 78 | _ = x[Fstore0-67] 79 | _ = x[Fstore1-68] 80 | _ = x[Fstore2-69] 81 | _ = x[Fstore3-70] 82 | _ = x[Dstore0-71] 83 | _ = x[Dstore1-72] 84 | _ = x[Dstore2-73] 85 | _ = x[Dstore3-74] 86 | _ = x[Astore0-75] 87 | _ = x[Astore1-76] 88 | _ = x[Astore2-77] 89 | _ = x[Astore3-78] 90 | _ = x[Iastore-79] 91 | _ = x[Lastore-80] 92 | _ = x[Fastore-81] 93 | _ = x[Dastore-82] 94 | _ = x[Aastore-83] 95 | _ = x[Bastore-84] 96 | _ = x[Castore-85] 97 | _ = x[Sastore-86] 98 | _ = x[Pop-87] 99 | _ = x[Pop2-88] 100 | _ = x[Dup-89] 101 | _ = x[Dupx1-90] 102 | _ = x[Dupx2-91] 103 | _ = x[Dup2-92] 104 | _ = x[Dup2x1-93] 105 | _ = x[Dup2x2-94] 106 | _ = x[Swap-95] 107 | _ = x[Iadd-96] 108 | _ = x[Ladd-97] 109 | _ = x[Fadd-98] 110 | _ = x[Dadd-99] 111 | _ = x[Isub-100] 112 | _ = x[Lsub-101] 113 | _ = x[Fsub-102] 114 | _ = x[Dsub-103] 115 | _ = x[Imul-104] 116 | _ = x[Lmul-105] 117 | _ = x[Fmul-106] 118 | _ = x[Dmul-107] 119 | _ = x[Idiv-108] 120 | _ = x[Ldiv-109] 121 | _ = x[Fdiv-110] 122 | _ = x[Ddiv-111] 123 | _ = x[Irem-112] 124 | _ = x[Lrem-113] 125 | _ = x[Frem-114] 126 | _ = x[Drem-115] 127 | _ = x[Ineg-116] 128 | _ = x[Lneg-117] 129 | _ = x[Fneg-118] 130 | _ = x[Dneg-119] 131 | _ = x[Ishl-120] 132 | _ = x[Lshl-121] 133 | _ = x[Ishr-122] 134 | _ = x[Lshr-123] 135 | _ = x[Iushr-124] 136 | _ = x[Lushr-125] 137 | _ = x[Iand-126] 138 | _ = x[Land-127] 139 | _ = x[Ior-128] 140 | _ = x[Lor-129] 141 | _ = x[Ixor-130] 142 | _ = x[Lxor-131] 143 | _ = x[Iinc-132] 144 | _ = x[I2l-133] 145 | _ = x[I2f-134] 146 | _ = x[I2d-135] 147 | _ = x[L2i-136] 148 | _ = x[L2f-137] 149 | _ = x[L2d-138] 150 | _ = x[F2i-139] 151 | _ = x[F2l-140] 152 | _ = x[F2d-141] 153 | _ = x[D2i-142] 154 | _ = x[D2l-143] 155 | _ = x[D2f-144] 156 | _ = x[I2b-145] 157 | _ = x[I2c-146] 158 | _ = x[I2s-147] 159 | _ = x[Lcmp-148] 160 | _ = x[Fcmpl-149] 161 | _ = x[Fcmpg-150] 162 | _ = x[Dcmpl-151] 163 | _ = x[Dcmpg-152] 164 | _ = x[Ifeq-153] 165 | _ = x[Ifne-154] 166 | _ = x[Iflt-155] 167 | _ = x[Ifge-156] 168 | _ = x[Ifgt-157] 169 | _ = x[Ifle-158] 170 | _ = x[Ificmpeq-159] 171 | _ = x[Ificmpne-160] 172 | _ = x[Ificmplt-161] 173 | _ = x[Ificmpge-162] 174 | _ = x[Ificmpgt-163] 175 | _ = x[Ificmple-164] 176 | _ = x[Ifacmpeq-165] 177 | _ = x[Ifacmpne-166] 178 | _ = x[Goto-167] 179 | _ = x[Jsr-168] 180 | _ = x[Ret-169] 181 | _ = x[Ireturn-172] 182 | _ = x[Lreturn-173] 183 | _ = x[Freturn-174] 184 | _ = x[Dreturn-175] 185 | _ = x[Areturn-176] 186 | _ = x[Return-177] 187 | _ = x[Getstatic-178] 188 | _ = x[Putstatic-179] 189 | _ = x[Getfield-180] 190 | _ = x[Putfield-181] 191 | _ = x[Invokevirtual-182] 192 | _ = x[Invokespecial-183] 193 | _ = x[Invokestatic-184] 194 | _ = x[Invokeinterface-185] 195 | _ = x[Invokedynamic-186] 196 | _ = x[New-187] 197 | _ = x[Newarray-188] 198 | _ = x[Anewarray-189] 199 | _ = x[Arraylength-190] 200 | _ = x[Athrow-191] 201 | _ = x[Checkcast-192] 202 | _ = x[Instanceof-193] 203 | _ = x[Monitorenter-194] 204 | _ = x[Monitorexit-195] 205 | _ = x[Multianewarray-197] 206 | _ = x[Ifnull-198] 207 | _ = x[Ifnonnull-199] 208 | _ = x[Gotow-200] 209 | _ = x[Jsrw-201] 210 | _ = x[Breakpoint-202] 211 | _ = x[Impdep1-254] 212 | _ = x[Impdep2-255] 213 | } 214 | 215 | const ( 216 | _Op_name_0 = "nopaconst_nulliconst_m1iconst_0iconst_1iconst_2iconst_3iconst_4iconst_5lconst_0lconst_1fconst_0fconst_1fconst_2dconst_0dconst_1bipushsipushldcldc_wldc2_wiloadlloadfloaddloadaloadiload_0iload_1iload_2iload_3lload_0lload_1lload_2lload_3fload_0fload_1fload_2fload_3dload_0dload_1dload_2dload_3aload_0aload_1aload_2aload_3ialoadlaloadfaloaddaloadaaloadbaloadcaloadsaloadistorelstorefstoredstoreastoreistore_0istore_1istore_2istore_3lstore_0lstore_1lstore_2lstore_3fstore_0fstore_1fstore_2fstore_3dstore_0dstore_1dstore_2dstore_3astore_0astore_1astore_2astore_3iastorelastorefastoredastoreaastorebastorecastoresastorepoppop2dupdup_x1dup_x2dup2dup2_x1dup2_x2swapiaddladdfadddaddisublsubfsubdsubimullmulfmuldmulidivldivfdivddiviremlremfremdremineglnegfnegdnegishllshlishrlshriushrlushriandlandiorlorixorlxoriinci2li2fi2dl2il2fl2df2if2lf2dd2id2ld2fi2bi2ci2slcmpfcmplfcmpgdcmpldcmpgifeqifneifltifgeifgtifleif_icmpeqif_icmpneif_icmpltif_icmpgeif_icmpgtif_icmpleif_acmpeqif_acmpnegotojsrret" 217 | _Op_name_1 = "ireturnlreturnfreturndreturnareturnreturngetstaticputstaticgetfieldputfieldinvokevirtualinvokespecialinvokestaticinvokeinterfaceinvokedynamicnewnewarrayanewarrayarraylengthathrowcheckcastinstanceofmonitorentermonitorexit" 218 | _Op_name_2 = "multianewarrayifnullifnonnullgoto_wjsr_wbreakpoint" 219 | _Op_name_3 = "impdep1impdep2" 220 | ) 221 | 222 | var ( 223 | _Op_index_0 = [...]uint16{0, 3, 14, 23, 31, 39, 47, 55, 63, 71, 79, 87, 95, 103, 111, 119, 127, 133, 139, 142, 147, 153, 158, 163, 168, 173, 178, 185, 192, 199, 206, 213, 220, 227, 234, 241, 248, 255, 262, 269, 276, 283, 290, 297, 304, 311, 318, 324, 330, 336, 342, 348, 354, 360, 366, 372, 378, 384, 390, 396, 404, 412, 420, 428, 436, 444, 452, 460, 468, 476, 484, 492, 500, 508, 516, 524, 532, 540, 548, 556, 563, 570, 577, 584, 591, 598, 605, 612, 615, 619, 622, 628, 634, 638, 645, 652, 656, 660, 664, 668, 672, 676, 680, 684, 688, 692, 696, 700, 704, 708, 712, 716, 720, 724, 728, 732, 736, 740, 744, 748, 752, 756, 760, 764, 768, 773, 778, 782, 786, 789, 792, 796, 800, 804, 807, 810, 813, 816, 819, 822, 825, 828, 831, 834, 837, 840, 843, 846, 849, 853, 858, 863, 868, 873, 877, 881, 885, 889, 893, 897, 906, 915, 924, 933, 942, 951, 960, 969, 973, 976, 979} 224 | _Op_index_1 = [...]uint8{0, 7, 14, 21, 28, 35, 41, 50, 59, 67, 75, 88, 101, 113, 128, 141, 144, 152, 161, 172, 178, 187, 197, 209, 220} 225 | _Op_index_2 = [...]uint8{0, 14, 20, 29, 35, 40, 50} 226 | _Op_index_3 = [...]uint8{0, 7, 14} 227 | ) 228 | 229 | func (i Op) String() string { 230 | switch { 231 | case i <= 169: 232 | return _Op_name_0[_Op_index_0[i]:_Op_index_0[i+1]] 233 | case 172 <= i && i <= 195: 234 | i -= 172 235 | return _Op_name_1[_Op_index_1[i]:_Op_index_1[i+1]] 236 | case 197 <= i && i <= 202: 237 | i -= 197 238 | return _Op_name_2[_Op_index_2[i]:_Op_index_2[i+1]] 239 | case 254 <= i && i <= 255: 240 | i -= 254 241 | return _Op_name_3[_Op_index_3[i]:_Op_index_3[i+1]] 242 | default: 243 | return "Op(" + strconv.FormatInt(int64(i), 10) + ")" 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /bytecode/width_table.go: -------------------------------------------------------------------------------- 1 | package bytecode 2 | 3 | // OpWidth table maps Op to its encoding width. 4 | // 5 | // For example, OpWidth[Bupush] returns 2 as you need two bytes 6 | // to encode bipush instruction. 7 | // 8 | // For variadic-length instructions like tableswitch it returns 0. 9 | var OpWidth = [256]byte{ 10 | Nop: 1 + 0, 11 | Aconstnull: 1 + 0, 12 | Iconstm1: 1 + 0, 13 | Iconst0: 1 + 0, 14 | Iconst1: 1 + 0, 15 | Iconst2: 1 + 0, 16 | Iconst3: 1 + 0, 17 | Iconst4: 1 + 0, 18 | Iconst5: 1 + 0, 19 | Lconst0: 1 + 0, 20 | Lconst1: 1 + 0, 21 | Fconst0: 1 + 0, 22 | Fconst1: 1 + 0, 23 | Fconst2: 1 + 0, 24 | Dconst0: 1 + 0, 25 | Dconst1: 1 + 0, 26 | Bipush: 1 + 1, // 1: byte 27 | Sipush: 1 + 2, // 2: byte1, byte2 28 | Ldc: 1 + 1, // 1: index 29 | Ldcw: 1 + 2, // 2: indexbyte1, indexbyte2 30 | Ldc2w: 1 + 2, // 2: indexbyte1, indexbyte2 31 | Iload: 1 + 1, // 1: index 32 | Lload: 1 + 1, // 1: index 33 | Fload: 1 + 1, // 1: index 34 | Dload: 1 + 1, // 1: index 35 | Aload: 1 + 1, // 1: index 36 | Iload0: 1 + 0, 37 | Iload1: 1 + 0, 38 | Iload2: 1 + 0, 39 | Iload3: 1 + 0, 40 | Lload0: 1 + 0, 41 | Lload1: 1 + 0, 42 | Lload2: 1 + 0, 43 | Lload3: 1 + 0, 44 | Fload0: 1 + 0, 45 | Fload1: 1 + 0, 46 | Fload2: 1 + 0, 47 | Fload3: 1 + 0, 48 | Dload0: 1 + 0, 49 | Dload1: 1 + 0, 50 | Dload2: 1 + 0, 51 | Dload3: 1 + 0, 52 | Aload0: 1 + 0, 53 | Aload1: 1 + 0, 54 | Aload2: 1 + 0, 55 | Aload3: 1 + 0, 56 | Iaload: 1 + 0, 57 | Laload: 1 + 0, 58 | Faload: 1 + 0, 59 | Daload: 1 + 0, 60 | Aaload: 1 + 0, 61 | Baload: 1 + 0, 62 | Caload: 1 + 0, 63 | Saload: 1 + 0, 64 | Istore: 1 + 1, // 1: index 65 | Lstore: 1 + 1, // 1: index 66 | Fstore: 1 + 1, // 1: index 67 | Dstore: 1 + 1, // 1: index 68 | Astore: 1 + 1, // 1: index 69 | Istore0: 1 + 0, 70 | Istore1: 1 + 0, 71 | Istore2: 1 + 0, 72 | Istore3: 1 + 0, 73 | Lstore0: 1 + 0, 74 | Lstore1: 1 + 0, 75 | Lstore2: 1 + 0, 76 | Lstore3: 1 + 0, 77 | Fstore0: 1 + 0, 78 | Fstore1: 1 + 0, 79 | Fstore2: 1 + 0, 80 | Fstore3: 1 + 0, 81 | Dstore0: 1 + 0, 82 | Dstore1: 1 + 0, 83 | Dstore2: 1 + 0, 84 | Dstore3: 1 + 0, 85 | Astore0: 1 + 0, 86 | Astore1: 1 + 0, 87 | Astore2: 1 + 0, 88 | Astore3: 1 + 0, 89 | Iastore: 1 + 0, 90 | Lastore: 1 + 0, 91 | Fastore: 1 + 0, 92 | Dastore: 1 + 0, 93 | Aastore: 1 + 0, 94 | Bastore: 1 + 0, 95 | Castore: 1 + 0, 96 | Sastore: 1 + 0, 97 | Pop: 1 + 0, 98 | Pop2: 1 + 0, 99 | Dup: 1 + 0, 100 | Dupx1: 1 + 0, 101 | Dupx2: 1 + 0, 102 | Dup2: 1 + 0, 103 | Dup2x1: 1 + 0, 104 | Dup2x2: 1 + 0, 105 | Swap: 1 + 0, 106 | Iadd: 1 + 0, 107 | Ladd: 1 + 0, 108 | Fadd: 1 + 0, 109 | Dadd: 1 + 0, 110 | Isub: 1 + 0, 111 | Lsub: 1 + 0, 112 | Fsub: 1 + 0, 113 | Dsub: 1 + 0, 114 | Imul: 1 + 0, 115 | Lmul: 1 + 0, 116 | Fmul: 1 + 0, 117 | Dmul: 1 + 0, 118 | Idiv: 1 + 0, 119 | Ldiv: 1 + 0, 120 | Fdiv: 1 + 0, 121 | Ddiv: 1 + 0, 122 | Irem: 1 + 0, 123 | Lrem: 1 + 0, 124 | Frem: 1 + 0, 125 | Drem: 1 + 0, 126 | Ineg: 1 + 0, 127 | Lneg: 1 + 0, 128 | Fneg: 1 + 0, 129 | Dneg: 1 + 0, 130 | Ishl: 1 + 0, 131 | Lshl: 1 + 0, 132 | Ishr: 1 + 0, 133 | Lshr: 1 + 0, 134 | Iushr: 1 + 0, 135 | Lushr: 1 + 0, 136 | Iand: 1 + 0, 137 | Land: 1 + 0, 138 | Ior: 1 + 0, 139 | Lor: 1 + 0, 140 | Ixor: 1 + 0, 141 | Lxor: 1 + 0, 142 | Iinc: 1 + 2, // 2: index, const 143 | I2l: 1 + 0, 144 | I2f: 1 + 0, 145 | I2d: 1 + 0, 146 | L2i: 1 + 0, 147 | L2f: 1 + 0, 148 | L2d: 1 + 0, 149 | F2i: 1 + 0, 150 | F2l: 1 + 0, 151 | F2d: 1 + 0, 152 | D2i: 1 + 0, 153 | D2l: 1 + 0, 154 | D2f: 1 + 0, 155 | I2b: 1 + 0, 156 | I2c: 1 + 0, 157 | I2s: 1 + 0, 158 | Lcmp: 1 + 0, 159 | Fcmpl: 1 + 0, 160 | Fcmpg: 1 + 0, 161 | Dcmpl: 1 + 0, 162 | Dcmpg: 1 + 0, 163 | Ifeq: 1 + 2, // 2: branchbyte1, branchbyte2 164 | Ifne: 1 + 2, // 2: branchbyte1, branchbyte2 165 | Iflt: 1 + 2, // 2: branchbyte1, branchbyte2 166 | Ifge: 1 + 2, // 2: branchbyte1, branchbyte2 167 | Ifgt: 1 + 2, // 2: branchbyte1, branchbyte2 168 | Ifle: 1 + 2, // 2: branchbyte1, branchbyte2 169 | Ificmpeq: 1 + 2, // 2: branchbyte1, branchbyte2 170 | Ificmpne: 1 + 2, // 2: branchbyte1, branchbyte2 171 | Ificmplt: 1 + 2, // 2: branchbyte1, branchbyte2 172 | Ificmpge: 1 + 2, // 2: branchbyte1, branchbyte2 173 | Ificmpgt: 1 + 2, // 2: branchbyte1, branchbyte2 174 | Ificmple: 1 + 2, // 2: branchbyte1, branchbyte2 175 | Ifacmpeq: 1 + 2, // 2: branchbyte1, branchbyte2 176 | Ifacmpne: 1 + 2, // 2: branchbyte1, branchbyte2 177 | Goto: 1 + 2, // 2: branchbyte1, branchbyte2 178 | Jsr: 1 + 2, // 2: branchbyte1, branchbyte2 179 | Ret: 1 + 1, // 1: index 180 | Ireturn: 1 + 0, 181 | Lreturn: 1 + 0, 182 | Freturn: 1 + 0, 183 | Dreturn: 1 + 0, 184 | Areturn: 1 + 0, 185 | Return: 1 + 0, 186 | Getstatic: 1 + 2, // 2: indexbyte1, indexbyte2 187 | Putstatic: 1 + 2, // 2: indexbyte1, indexbyte2 188 | Getfield: 1 + 2, // 2: indexbyte1, indexbyte2 189 | Putfield: 1 + 2, // 2: indexbyte1, indexbyte2 190 | Invokevirtual: 1 + 2, // 2: indexbyte1, indexbyte2 191 | Invokespecial: 1 + 2, // 2: indexbyte1, indexbyte2 192 | Invokestatic: 1 + 2, // 2: indexbyte1, indexbyte2 193 | Invokeinterface: 1 + 4, // 4: indexbyte1, indexbyte2, count, 0 194 | Invokedynamic: 1 + 4, // 4: indexbyte1, indexbyte2, 0, 0 195 | New: 1 + 2, // 2: indexbyte1, indexbyte2 196 | Newarray: 1 + 1, // 1: atype 197 | Anewarray: 1 + 2, // 2: indexbyte1, indexbyte2 198 | Arraylength: 1 + 0, 199 | Athrow: 1 + 0, 200 | Checkcast: 1 + 2, // 2: indexbyte1, indexbyte2 201 | Instanceof: 1 + 2, // 2: indexbyte1, indexbyte2 202 | Monitorenter: 1 + 0, 203 | Monitorexit: 1 + 0, 204 | Multianewarray: 1 + 3, // 3: indexbyte1, indexbyte2, dimensions 205 | Ifnull: 1 + 2, // 2: branchbyte1, branchbyte2 206 | Ifnonnull: 1 + 2, // 2: branchbyte1, branchbyte2 207 | Gotow: 1 + 4, // 4: branchbyte1, branchbyte2, branchbyte3, branchbyte4 208 | Jsrw: 1 + 4, // 4: branchbyte1, branchbyte2, branchbyte3, branchbyte4 209 | Breakpoint: 1 + 0, 210 | Impdep1: 1 + 0, 211 | Impdep2: 1 + 0, 212 | } 213 | -------------------------------------------------------------------------------- /cmd/go-jdk/javap_command.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | "strings" 10 | 11 | "github.com/quasilyte/go-jdk/cmd/internal/cmdutil" 12 | "github.com/quasilyte/go-jdk/ir" 13 | "github.com/quasilyte/go-jdk/irfmt" 14 | "github.com/quasilyte/go-jdk/irgen" 15 | "github.com/quasilyte/go-jdk/javap" 16 | "github.com/quasilyte/go-jdk/jit" 17 | "github.com/quasilyte/go-jdk/jruntime" 18 | "github.com/quasilyte/go-jdk/loader" 19 | ) 20 | 21 | func javapMain() error { 22 | var cmd javapCommand 23 | flag.StringVar(&cmd.format, "format", "raw", 24 | `output format: raw or ir`) 25 | flag.StringVar(&cmd.classPath, "cp", "", 26 | `class path to use`) 27 | flag.Parse() 28 | 29 | filenames := flag.Args() 30 | for _, filename := range filenames { 31 | if err := cmd.printFile(filename); err != nil { 32 | return fmt.Errorf("%s: %v", filename, err) 33 | } 34 | } 35 | 36 | return nil 37 | } 38 | 39 | type javapCommand struct { 40 | format string 41 | classPath string 42 | } 43 | 44 | func (cmd *javapCommand) printFile(filename string) error { 45 | if cmd.format == "raw" { 46 | jf, err := cmdutil.DecodeClassFile(filename) 47 | if err != nil { 48 | return fmt.Errorf("decode error: %v", err) 49 | } 50 | javap.Fprint(os.Stdout, jf) 51 | return nil 52 | } 53 | 54 | if cmd.format != "ir" { 55 | return fmt.Errorf("unknown format: %s", cmd.format) 56 | } 57 | 58 | vm, err := jruntime.OpenVM(runtime.GOARCH) 59 | if err != nil { 60 | return fmt.Errorf("open VM: %v", err) 61 | } 62 | defer vm.Close() 63 | 64 | toCompile, err := loader.LoadClass(&vm.State, filename, &loader.Config{ 65 | ClassPath: []string{cmd.classPath}, 66 | }) 67 | if err != nil { 68 | return fmt.Errorf("load class: %v", err) 69 | } 70 | if err := irgen.Generate(&vm.State, toCompile); err != nil { 71 | return fmt.Errorf("irgen: %v", err) 72 | } 73 | jitCtx := jit.Context{ 74 | Mmap: &vm.Mmap, 75 | State: &vm.State, 76 | } 77 | jruntime.BindFuncs(&jitCtx) 78 | if err := vm.Compiler.Compile(jitCtx, toCompile); err != nil { 79 | return fmt.Errorf("compile: %v", err) 80 | } 81 | pkg := toCompile[0] 82 | className := strings.TrimSuffix(filepath.Base(filename), ".class") 83 | var class *ir.Class 84 | for i := range pkg.Classes { 85 | if pkg.Classes[i].Out.Name == className { 86 | class = &pkg.Classes[i] 87 | break 88 | } 89 | } 90 | 91 | fmt.Printf("class %q\n", class.Name) 92 | for i := range class.Methods { 93 | m := &class.Methods[i] 94 | fmt.Printf(" method %s (slots=%d):\n", m.Out.Name, m.Out.FrameSlots) 95 | blockIndex := -1 96 | for i, inst := range m.Code { 97 | if inst.Flags.IsBlockLead() { 98 | blockIndex++ 99 | } 100 | fmt.Printf(" b%d %3d: %s\n", blockIndex, i, irfmt.Sprint(&vm.State, inst)) 101 | } 102 | code := m.Out.Code 103 | var codeChunks []string 104 | for len(code) != 0 { 105 | length := 32 106 | if len(code) < length { 107 | length = len(code) 108 | } 109 | codeChunks = append(codeChunks, fmt.Sprintf(" %x", code[:length])) 110 | code = code[length:] 111 | } 112 | if len(codeChunks) != 0 { 113 | fmt.Printf("%s (%d bytes)\n", strings.Join(codeChunks, "\n"), len(m.Out.Code)) 114 | } 115 | } 116 | 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /cmd/go-jdk/jdeps_command.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/quasilyte/go-jdk/cmd/internal/cmdutil" 8 | "github.com/quasilyte/go-jdk/jdeps" 9 | ) 10 | 11 | func jdepsMain() error { 12 | flag.Parse() 13 | 14 | targets := flag.Args() 15 | 16 | for _, target := range targets { 17 | cf, err := cmdutil.DecodeClassFile(target) 18 | if err != nil { 19 | return fmt.Errorf("%s: %v", target, err) 20 | } 21 | deps := jdeps.ClassDependencies(cf) 22 | fmt.Printf("%s depends on:\n", target) 23 | for _, dep := range deps { 24 | fmt.Printf(" %s\n", dep) 25 | } 26 | } 27 | 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /cmd/go-jdk/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | func main() { 8 | log.SetFlags(0) 9 | dispatchCommand(subCommands) 10 | } 11 | 12 | var subCommands = []*subCommand{ 13 | { 14 | Main: jdepsMain, 15 | Name: "jdeps", 16 | Short: "print JVM class file dependencies", 17 | Examples: []string{ 18 | "go-jdk jdeps -help", 19 | "go-jdk jdeps /path/to/File.class", 20 | }, 21 | }, 22 | 23 | { 24 | Main: javapMain, 25 | Name: "javap", 26 | Short: "disassemble JVM class or print its IR representation", 27 | Examples: []string{ 28 | "go-jdk javap -help", 29 | "go-jdk javap /path/to/File.class", 30 | "go-jdk javap -format=ir File.class", 31 | }, 32 | }, 33 | 34 | { 35 | Main: runMain, 36 | Name: "run", 37 | Short: "load and run JVM class", 38 | Examples: []string{ 39 | "go-jdk run -help", 40 | "go-jdk run -class /path/to/File.class", 41 | "go-jdk run -method add -class Arith.class 1 2", 42 | }, 43 | }, 44 | 45 | { 46 | Main: versionMain, 47 | Name: "version", 48 | Short: "print go-jdk version", 49 | Examples: []string{"go-jdk version"}, 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /cmd/go-jdk/run_command.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "path/filepath" 9 | "runtime" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/quasilyte/go-jdk/irgen" 15 | "github.com/quasilyte/go-jdk/jit" 16 | "github.com/quasilyte/go-jdk/jruntime" 17 | "github.com/quasilyte/go-jdk/loader" 18 | "github.com/quasilyte/go-jdk/vmdat" 19 | ) 20 | 21 | func runMain() error { 22 | var cmd runCommand 23 | flag.StringVar(&cmd.classFile, "class", "", 24 | `path to a class file`) 25 | flag.StringVar(&cmd.methodName, "method", "main", 26 | `class static method name`) 27 | flag.StringVar(&cmd.methodSignature, "signature", "", 28 | `method signature which is required if method is overloaded`) 29 | flag.BoolVar(&cmd.verbose, "v", false, 30 | `verbose output mode`) 31 | flag.StringVar(&cmd.classPath, "cp", "", 32 | `class path to use`) 33 | flag.IntVar(&cmd.heapMem, "heapmem", 0, 34 | `allocation bytes limit`) 35 | flag.Parse() 36 | cmd.methodArgs = flag.Args() 37 | 38 | if cmd.classFile == "" { 39 | return errors.New("-class argument can't be empty") 40 | } 41 | if cmd.methodName == "" { 42 | return errors.New("-method argument can't be empty") 43 | } 44 | 45 | return cmd.run() 46 | } 47 | 48 | type runCommand struct { 49 | heapMem int 50 | classFile string 51 | methodName string 52 | methodSignature string 53 | methodArgs []string 54 | classPath string 55 | verbose bool 56 | } 57 | 58 | func (cmd *runCommand) run() error { 59 | vm, err := jruntime.OpenVM(runtime.GOARCH) 60 | if err != nil { 61 | return fmt.Errorf("open VM: %v", err) 62 | } 63 | defer vm.Close() 64 | 65 | compileStart := time.Now() 66 | class, err := cmd.loadAndCompileClass(vm, cmd.classFile) 67 | compileTime := time.Since(compileStart) 68 | if err != nil { 69 | return fmt.Errorf("load class: %v", err) 70 | } 71 | 72 | method := class.FindMethod(cmd.methodName, cmd.methodSignature) 73 | if method == nil { 74 | return fmt.Errorf("method %s.%s not found", class.Name, cmd.methodName) 75 | } 76 | 77 | env := jruntime.NewEnv(vm, &jruntime.EnvConfig{ 78 | AllocBytesLimit: int64(cmd.heapMem), 79 | }) 80 | for i, arg := range cmd.methodArgs { 81 | v, err := strconv.ParseInt(arg, 10, 64) 82 | if err != nil { 83 | return fmt.Errorf("method arg[%d]: only int args are supported, found %s", i, arg) 84 | } 85 | env.IntArg(i, v) 86 | } 87 | 88 | callStart := time.Now() 89 | result, err := env.IntCall(method) 90 | callTime := time.Since(callStart) 91 | if err != nil { 92 | return fmt.Errorf("call returned error: %v", err) 93 | } 94 | 95 | argsString := strings.Join(cmd.methodArgs, ", ") 96 | log.Printf("%s.%s(%s) => %d\n", class.Name, method.Name, argsString, result) 97 | if cmd.verbose { 98 | log.Println("-- verbose output --") 99 | log.Printf("method machine code: %x\n", method.Code) 100 | log.Printf("class load time: %.8fs (%d ns)\n", 101 | compileTime.Seconds(), compileTime.Nanoseconds()) 102 | log.Printf("method call time: %.8fs (%d ns)\n", 103 | callTime.Seconds(), callTime.Nanoseconds()) 104 | } 105 | 106 | return nil 107 | } 108 | 109 | func (cmd *runCommand) loadAndCompileClass(vm *jruntime.VM, filename string) (*vmdat.Class, error) { 110 | toCompile, err := loader.LoadClass(&vm.State, filename, &loader.Config{ 111 | ClassPath: []string{cmd.classPath}, 112 | }) 113 | if err != nil { 114 | return nil, fmt.Errorf("load class: %v", err) 115 | } 116 | if err := irgen.Generate(&vm.State, toCompile); err != nil { 117 | return nil, fmt.Errorf("irgen: %v", err) 118 | } 119 | ctx := jit.Context{ 120 | Mmap: &vm.Mmap, 121 | State: &vm.State, 122 | } 123 | jruntime.BindFuncs(&ctx) 124 | if err := vm.Compiler.Compile(ctx, toCompile); err != nil { 125 | return nil, fmt.Errorf("compile: %v", err) 126 | } 127 | className := strings.TrimSuffix(filepath.Base(filename), ".class") 128 | return toCompile[0].Out.FindClass(className), nil 129 | } 130 | -------------------------------------------------------------------------------- /cmd/go-jdk/subcommand.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | // subCommand is an implementation of a linter sub-command. 9 | type subCommand struct { 10 | // Main is command entry point. 11 | Main func() error 12 | 13 | // Name is sub-command name used to execute it. 14 | Name string 15 | 16 | // Short describes command in one line of text. 17 | Short string 18 | 19 | // Examples shows one or more sub-command execution examples. 20 | Examples []string 21 | } 22 | 23 | // findSubCommand looks up SubCommand by its name. 24 | // Returns nil if requested command not found. 25 | func findSubCommand(cmdList []*subCommand, name string) *subCommand { 26 | for _, cmd := range cmdList { 27 | if cmd.Name == name { 28 | return cmd 29 | } 30 | } 31 | return nil 32 | } 33 | 34 | // printSubCommands prints cmdList info to the logger (usually stderr). 35 | func printSubCommands(cmdList []*subCommand) { 36 | log.Println("supported sub-commands:") 37 | for _, cmd := range cmdList { 38 | log.Printf("\t%s - %s", cmd.Name, cmd.Short) 39 | for _, ex := range cmd.Examples { 40 | log.Printf("\t\t$ %s", ex) 41 | } 42 | } 43 | } 44 | 45 | // dispatchCommand runs sub command out of specified cmdList based on 46 | // the first command line argument. 47 | func dispatchCommand(cmdList []*subCommand) { 48 | argv := os.Args 49 | if len(argv) == 2 { 50 | if argv[1] == "help" || argv[1] == "-help" || argv[1] == "--help" { 51 | printSubCommands(cmdList) 52 | os.Exit(0) 53 | } 54 | } 55 | if len(argv) < 2 { 56 | log.Printf("not enough arguments, expected sub-command name\n\n") 57 | printSubCommands(cmdList) 58 | os.Exit(1) 59 | } 60 | 61 | subIdx := 1 // [0] is program name 62 | sub := os.Args[subIdx] 63 | // Erase sub-command argument (index=1) to make it invisible for 64 | // sub commands themselves. 65 | os.Args = append(os.Args[:subIdx], os.Args[subIdx+1:]...) 66 | 67 | // Choose and run sub-command main. 68 | cmd := findSubCommand(cmdList, sub) 69 | if cmd == nil { 70 | log.Printf("unknown sub-command: %s\n\n", sub) 71 | printSubCommands(cmdList) 72 | os.Exit(1) 73 | } 74 | 75 | if err := cmd.Main(); err != nil { 76 | log.Fatal(err) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /cmd/go-jdk/version_command.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func versionMain() error { 8 | fmt.Println("0.0.1") 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /cmd/internal/cmdutil/cmdutil.go: -------------------------------------------------------------------------------- 1 | package cmdutil 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/quasilyte/go-jdk/jclass" 7 | ) 8 | 9 | func DecodeClassFile(filename string) (*jclass.File, error) { 10 | f, err := os.Open(filename) 11 | if err != nil { 12 | return nil, err 13 | } 14 | defer f.Close() 15 | var dec jclass.Decoder 16 | return dec.Decode(f) 17 | } 18 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | ## Architecture overview 2 | 3 | Package load path: 4 | 5 | 1. Package and all its dependencies are loaded from [Java class files](https://en.wikipedia.org/wiki/Java_class_file) 6 | 2. [Java bytecode](https://en.wikipedia.org/wiki/Java_bytecode) is then converted to IR 7 | 3. Generated IR gets optimized 8 | 4. JIT-compiler generates machine code from that IR 9 | 5. Loaded classes and packages are stored inside VM handle 10 | 11 | ### Main packages 12 | 13 | * [`jclass`](/jclass) decodes Java class files 14 | * [`ir`](/ir) describes our intermediate representation (IR) 15 | * [`irgen`](/irgen) converts bytecode into our IR 16 | * [`iropt`](/iropt) runs optimizations over IR 17 | * [`jdeps`](/jdeps) finds all package dependencies 18 | * [`loader`](/loader) fetches class files with all their dependencies 19 | * [`jit/x64`](/jit/x64) generates x86-64 machine code 20 | * [`jit/compiler/x64`](/jit/compiler/x64) turns IR into machine code 21 | * [`vmdat`](/vmdat) VM data structures that represent virtual machine state 22 | * [`symbol`](/symbol) defines index-like objects for efficient symbol referencing 23 | * [`jruntime`](/jruntime) implements loaded code runtime 24 | * [`mmap`](/mmap) wraps platform-dependent memory mapping code 25 | * [`javatest`](/javatest) runs tests written in Java against host VM 26 | 27 | ### Utility packages 28 | 29 | * [`bytecode`](/bytecode) describes the Java bytecode opcodes 30 | * [`irfmt`](/irfmt) converts IR instructions to pretty strings 31 | * [`javap`](/javap) pretty-prints Java class files 32 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quasilyte/go-jdk/888098460476b024c78035b388817761a84c6693/docs/logo.png -------------------------------------------------------------------------------- /docs/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quasilyte/go-jdk/888098460476b024c78035b388817761a84c6693/docs/logo_small.png -------------------------------------------------------------------------------- /goreflect/goreflect.go: -------------------------------------------------------------------------------- 1 | package goreflect 2 | 3 | type SliceHeader struct { 4 | Data uintptr 5 | Len int 6 | Cap int 7 | } 8 | -------------------------------------------------------------------------------- /ir/arg.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | 7 | "github.com/quasilyte/go-jdk/symbol" 8 | ) 9 | 10 | // Arg is an instruction argument (sometimes called operand). 11 | // Value bits (int64) should be interpreted differently depending on the Kind. 12 | type Arg struct { 13 | Kind ArgKind 14 | Value int64 15 | } 16 | 17 | func (arg Arg) FloatValue() float32 { return math.Float32frombits(uint32(arg.Value)) } 18 | 19 | func (arg Arg) DoubleValue() float64 { return math.Float64frombits(uint64(arg.Value)) } 20 | 21 | func (arg Arg) SymbolID() symbol.ID { return symbol.ID(arg.Value) } 22 | 23 | func (arg Arg) String() string { 24 | switch arg.Kind { 25 | case ArgEnv: 26 | return "env" 27 | case ArgBranch: 28 | return fmt.Sprintf("@%d", arg.Value) 29 | case ArgFlags: 30 | return "flags" 31 | case ArgReg: 32 | return fmt.Sprintf("r%d", arg.Value) 33 | case ArgIntConst: 34 | return fmt.Sprintf("%d", arg.Value) 35 | case ArgFloatConst: 36 | return formatFloat64(float64(arg.FloatValue())) 37 | case ArgDoubleConst: 38 | return formatFloat64(arg.DoubleValue()) 39 | case ArgSymbolID: 40 | sym := arg.SymbolID() 41 | return fmt.Sprintf("sym{%d,%d,%d}", sym.PackageIndex(), sym.ClassIndex(), sym.MemberIndex()) 42 | default: 43 | return fmt.Sprintf("{%d,%d}", arg.Kind, arg.Value) 44 | } 45 | } 46 | 47 | // ArgKind describes an argument category. 48 | // Most kinds change how Arg value should be interpreted. 49 | type ArgKind int 50 | 51 | const ( 52 | ArgInvalid ArgKind = iota 53 | 54 | ArgEnv 55 | ArgBranch 56 | ArgFlags 57 | ArgReg 58 | ArgIntConst 59 | ArgFloatConst 60 | ArgDoubleConst 61 | ArgSymbolID 62 | ) 63 | -------------------------------------------------------------------------------- /ir/class.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import ( 4 | "github.com/quasilyte/go-jdk/jclass" 5 | "github.com/quasilyte/go-jdk/vmdat" 6 | ) 7 | 8 | type Package struct { 9 | Classes []Class 10 | 11 | Out *vmdat.Package 12 | } 13 | 14 | type Class struct { 15 | Name string 16 | 17 | Methods []Method 18 | 19 | File *jclass.File 20 | Out *vmdat.Class 21 | } 22 | 23 | type Method struct { 24 | Code []Inst 25 | AccessFlags jclass.MethodAccessFlags 26 | 27 | Out *vmdat.Method 28 | } 29 | -------------------------------------------------------------------------------- /ir/inst.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Inst is a single IR instruction. 8 | // 9 | // If instruction has explicit output, Dst represents 10 | // output assignment destination. 11 | type Inst struct { 12 | Kind InstKind 13 | Dst Arg 14 | Args []Arg 15 | Flags InstFlags 16 | } 17 | 18 | func (inst Inst) String() string { 19 | var buf strings.Builder 20 | if inst.Dst.Kind != 0 { 21 | buf.WriteString(inst.Dst.String()) 22 | buf.WriteString(" = ") 23 | } 24 | buf.WriteString(inst.Kind.String()) 25 | for _, arg := range inst.Args { 26 | buf.WriteByte(' ') 27 | buf.WriteString(arg.String()) 28 | } 29 | return buf.String() 30 | } 31 | 32 | // InstKind describes instruction operation. 33 | // It can be treated as an "opcode" of IR abstract machine. 34 | type InstKind int 35 | 36 | //go:generate stringer -type=InstKind -trimprefix=Inst 37 | const ( 38 | InstInvalid InstKind = iota 39 | 40 | InstIload 41 | InstLload 42 | InstAload 43 | InstRet 44 | InstIret 45 | InstLret 46 | InstAret 47 | InstCallStatic 48 | InstCallGo 49 | InstIcmp 50 | InstLcmp 51 | InstJump 52 | InstJumpEqual 53 | InstJumpNotEqual 54 | InstJumpGtEq 55 | InstJumpGt 56 | InstJumpLt 57 | InstJumpLtEq 58 | InstImul 59 | InstIdiv 60 | InstIadd 61 | InstLadd 62 | InstFadd 63 | InstIsub 64 | InstIneg 65 | InstLneg 66 | InstDadd 67 | InstConvL2I 68 | InstConvF2I 69 | InstConvD2I 70 | InstConvI2L 71 | InstConvI2B 72 | InstNewBoolArray 73 | InstNewCharArray 74 | InstNewFloatArray 75 | InstNewDoubleArray 76 | InstNewByteArray 77 | InstNewShortArray 78 | InstNewIntArray 79 | InstNewLongArray 80 | InstIntArraySet 81 | InstIntArrayGet 82 | InstArrayLen 83 | ) 84 | -------------------------------------------------------------------------------- /ir/inst_flags.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | // InstFlags is a set of instruction bit fields. 4 | type InstFlags uint64 5 | 6 | const ( 7 | // flagBlockLead bit is set when instructions is a block leader. 8 | // A block leader is a first instruction in a basic block. 9 | flagBlockLead uint64 = 1 << iota 10 | 11 | // flagJumpTarget bit is set when instruction is a jump 12 | // target of some other instruction. First instruction 13 | // in a function body is also marked as such. 14 | // Mostly needed for alignment: if instruction is a 15 | // jump target, it can be beneficial to align it. 16 | flagJumpTarget 17 | ) 18 | 19 | func (f InstFlags) IsBlockLead() bool { return f.getFlag(flagBlockLead) } 20 | func (f *InstFlags) SetBlockLead(v bool) { f.setFlag(v, flagBlockLead) } 21 | 22 | func (f InstFlags) IsJumpTarget() bool { return f.getFlag(flagJumpTarget) } 23 | func (f *InstFlags) SetJumpTarget(v bool) { f.setFlag(v, flagJumpTarget) } 24 | 25 | func (f *InstFlags) setFlag(v bool, mask uint64) { 26 | if v { 27 | *f |= InstFlags(mask) 28 | } else { 29 | *f &^= InstFlags(mask) 30 | } 31 | } 32 | 33 | func (f *InstFlags) getFlag(mask uint64) bool { 34 | return *f&InstFlags(mask) != 0 35 | } 36 | -------------------------------------------------------------------------------- /ir/instkind_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=InstKind -trimprefix=Inst"; DO NOT EDIT. 2 | 3 | package ir 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[InstInvalid-0] 12 | _ = x[InstIload-1] 13 | _ = x[InstLload-2] 14 | _ = x[InstAload-3] 15 | _ = x[InstRet-4] 16 | _ = x[InstIret-5] 17 | _ = x[InstLret-6] 18 | _ = x[InstAret-7] 19 | _ = x[InstCallStatic-8] 20 | _ = x[InstCallGo-9] 21 | _ = x[InstIcmp-10] 22 | _ = x[InstLcmp-11] 23 | _ = x[InstJump-12] 24 | _ = x[InstJumpEqual-13] 25 | _ = x[InstJumpNotEqual-14] 26 | _ = x[InstJumpGtEq-15] 27 | _ = x[InstJumpGt-16] 28 | _ = x[InstJumpLt-17] 29 | _ = x[InstJumpLtEq-18] 30 | _ = x[InstImul-19] 31 | _ = x[InstIdiv-20] 32 | _ = x[InstIadd-21] 33 | _ = x[InstLadd-22] 34 | _ = x[InstFadd-23] 35 | _ = x[InstIsub-24] 36 | _ = x[InstIneg-25] 37 | _ = x[InstLneg-26] 38 | _ = x[InstDadd-27] 39 | _ = x[InstConvL2I-28] 40 | _ = x[InstConvF2I-29] 41 | _ = x[InstConvD2I-30] 42 | _ = x[InstConvI2L-31] 43 | _ = x[InstConvI2B-32] 44 | _ = x[InstNewBoolArray-33] 45 | _ = x[InstNewCharArray-34] 46 | _ = x[InstNewFloatArray-35] 47 | _ = x[InstNewDoubleArray-36] 48 | _ = x[InstNewByteArray-37] 49 | _ = x[InstNewShortArray-38] 50 | _ = x[InstNewIntArray-39] 51 | _ = x[InstNewLongArray-40] 52 | _ = x[InstIntArraySet-41] 53 | _ = x[InstIntArrayGet-42] 54 | _ = x[InstArrayLen-43] 55 | } 56 | 57 | const _InstKind_name = "InvalidIloadLloadAloadRetIretLretAretCallStaticCallGoIcmpLcmpJumpJumpEqualJumpNotEqualJumpGtEqJumpGtJumpLtJumpLtEqImulIdivIaddLaddFaddIsubInegLnegDaddConvL2IConvF2IConvD2IConvI2LConvI2BNewBoolArrayNewCharArrayNewFloatArrayNewDoubleArrayNewByteArrayNewShortArrayNewIntArrayNewLongArrayIntArraySetIntArrayGetArrayLen" 58 | 59 | var _InstKind_index = [...]uint16{0, 7, 12, 17, 22, 25, 29, 33, 37, 47, 53, 57, 61, 65, 74, 86, 94, 100, 106, 114, 118, 122, 126, 130, 134, 138, 142, 146, 150, 157, 164, 171, 178, 185, 197, 209, 222, 236, 248, 261, 272, 284, 295, 306, 314} 60 | 61 | func (i InstKind) String() string { 62 | if i < 0 || i >= InstKind(len(_InstKind_index)-1) { 63 | return "InstKind(" + strconv.FormatInt(int64(i), 10) + ")" 64 | } 65 | return _InstKind_name[_InstKind_index[i]:_InstKind_index[i+1]] 66 | } 67 | -------------------------------------------------------------------------------- /ir/utils.go: -------------------------------------------------------------------------------- 1 | package ir 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // formatFloat64 returns string representation of f that can't be 8 | // mixed up with integer literal. 9 | func formatFloat64(f float64) string { 10 | switch f { 11 | case 0: 12 | return "0.0" 13 | case 1: 14 | return "1.0" 15 | default: 16 | return strconv.FormatFloat(f, 'f', -1, 64) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /irfmt/print.go: -------------------------------------------------------------------------------- 1 | package irfmt 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/quasilyte/go-jdk/ir" 7 | "github.com/quasilyte/go-jdk/vmdat" 8 | ) 9 | 10 | func Sprint(st *vmdat.State, inst ir.Inst) string { 11 | var buf strings.Builder 12 | if inst.Dst.Kind != 0 { 13 | buf.WriteString(inst.Dst.String()) 14 | buf.WriteString(" = ") 15 | } 16 | buf.WriteString(inst.Kind.String()) 17 | for _, arg := range inst.Args { 18 | buf.WriteByte(' ') 19 | switch arg.Kind { 20 | case ir.ArgSymbolID: 21 | id := arg.SymbolID() 22 | pkg := st.Packages[id.PackageIndex()] 23 | class := pkg.Classes[id.ClassIndex()] 24 | name := class.Methods[id.MemberIndex()].Name 25 | buf.WriteString(name) 26 | default: 27 | buf.WriteString(arg.String()) 28 | } 29 | } 30 | return buf.String() 31 | } 32 | -------------------------------------------------------------------------------- /irgen/generator.go: -------------------------------------------------------------------------------- 1 | package irgen 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strings" 7 | 8 | "github.com/quasilyte/go-jdk/bytecode" 9 | "github.com/quasilyte/go-jdk/ir" 10 | "github.com/quasilyte/go-jdk/jclass" 11 | "github.com/quasilyte/go-jdk/vmdat" 12 | ) 13 | 14 | type generator struct { 15 | state *vmdat.State 16 | f *jclass.File 17 | m *jclass.Method 18 | tmpOffset int64 19 | 20 | st operandStack 21 | out []ir.Inst 22 | toResolve []unresolvedBranch 23 | } 24 | 25 | type unresolvedBranch struct { 26 | pc int32 27 | branch *int64 28 | } 29 | 30 | func (g *generator) Generate(i int, m *ir.Method) error { 31 | g.reset(&g.f.Methods[i]) 32 | return g.generate(m) 33 | } 34 | 35 | func (g *generator) reset(m *jclass.Method) { 36 | g.m = m 37 | g.st.reset() 38 | g.out = g.out[:0] 39 | g.toResolve = g.toResolve[:0] 40 | } 41 | 42 | func (g *generator) irArg(n int) ir.Arg { 43 | v := g.st.get(n) 44 | switch v.kind { 45 | case valueIntLocal, valueLongLocal, valueFloatLocal, valueDoubleLocal: 46 | return ir.Arg{Kind: ir.ArgReg, Value: v.value} 47 | case valueTmp: 48 | return ir.Arg{Kind: ir.ArgReg, Value: v.value + g.tmpOffset} 49 | case valueIntConst, valueLongConst: 50 | return ir.Arg{Kind: ir.ArgIntConst, Value: v.value} 51 | case valueFloatConst: 52 | return ir.Arg{Kind: ir.ArgFloatConst, Value: v.value} 53 | case valueDoubleConst: 54 | return ir.Arg{Kind: ir.ArgDoubleConst, Value: v.value} 55 | default: 56 | panic(fmt.Sprintf("can't convert arg %#v", v)) 57 | } 58 | } 59 | 60 | func (g *generator) generate(dst *ir.Method) error { 61 | var code []byte 62 | var frames []jclass.StackMapFrame 63 | for _, attr := range g.m.Attrs { 64 | attr, ok := attr.(jclass.CodeAttribute) 65 | if !ok { 66 | continue 67 | } 68 | code = attr.Code 69 | g.tmpOffset = int64(attr.MaxLocals) 70 | for _, attr := range attr.Attrs { 71 | attr, ok := attr.(jclass.StackMapTableAttribute) 72 | if !ok { 73 | continue 74 | } 75 | frames = attr.Frames 76 | break 77 | } 78 | break 79 | } 80 | 81 | pc2index := make(map[int32]int32, len(code)/2) 82 | 83 | var prevOp bytecode.Op 84 | pc := 0 85 | for pc < len(code) { 86 | pc2index[int32(pc)] = int32(len(g.out)) 87 | op := bytecode.Op(code[pc]) 88 | 89 | switch op { 90 | case bytecode.Iconstm1: 91 | g.st.push(valueIntConst, -1) 92 | case bytecode.Iconst0, bytecode.Iconst1, bytecode.Iconst2, bytecode.Iconst3, bytecode.Iconst4, bytecode.Iconst5: 93 | g.st.push(valueIntConst, int64(op-bytecode.Iconst0)) 94 | case bytecode.Lconst0, bytecode.Lconst1: 95 | g.st.push(valueLongConst, int64(op-bytecode.Lconst0)) 96 | case bytecode.Fconst0: 97 | g.st.push(valueFloatConst, int64(math.Float32bits(0.0))) 98 | case bytecode.Fconst1: 99 | g.st.push(valueFloatConst, int64(math.Float32bits(1.0))) 100 | case bytecode.Fconst2: 101 | g.st.push(valueFloatConst, int64(math.Float32bits(2.0))) 102 | case bytecode.Dconst0: 103 | g.st.push(valueDoubleConst, int64(math.Float64bits(0.0))) 104 | case bytecode.Dconst1: 105 | g.st.push(valueDoubleConst, int64(math.Float64bits(1.0))) 106 | 107 | case bytecode.Bipush: 108 | ib := int8(code[pc+1]) 109 | g.st.push(valueIntConst, int64(ib)) 110 | case bytecode.Sipush: 111 | ib1 := int16(code[pc+1]) 112 | ib2 := int16(code[pc+2]) 113 | g.st.push(valueIntConst, int64(ib1<<8+ib2)) 114 | 115 | case bytecode.Ldc: 116 | i := uint(code[pc+1]) 117 | switch c := g.f.Consts[i]; c := c.(type) { 118 | case *jclass.IntConst: 119 | g.st.push(valueIntConst, int64(c.Value)) 120 | case *jclass.FloatConst: 121 | g.st.push(valueFloatConst, int64(math.Float32bits(c.Value))) 122 | default: 123 | panic(fmt.Sprintf("%T const ldc", c)) 124 | } 125 | case bytecode.Ldc2w: 126 | ib1 := uint(code[pc+1]) 127 | ib2 := uint(code[pc+2]) 128 | i := ib1<<8 + ib2 129 | switch c := g.f.Consts[i]; c := c.(type) { 130 | case *jclass.IntConst: 131 | g.st.push(valueIntConst, int64(c.Value)) 132 | case *jclass.LongConst: 133 | g.st.push(valueLongConst, c.Value) 134 | case *jclass.FloatConst: 135 | g.st.push(valueFloatConst, int64(math.Float32bits(c.Value))) 136 | case *jclass.DoubleConst: 137 | g.st.push(valueDoubleConst, int64(math.Float64bits(c.Value))) 138 | default: 139 | panic(fmt.Sprintf("%T const ldc", c)) 140 | } 141 | 142 | case bytecode.Iload: 143 | g.convertLoad(pc, frames, prevOp, int64(code[pc+1]), ir.InstIload) 144 | case bytecode.Lload: 145 | g.convertLoad(pc, frames, prevOp, int64(code[pc+1]), ir.InstLload) 146 | case bytecode.Iload0, bytecode.Iload1, bytecode.Iload2, bytecode.Iload3: 147 | g.convertLoad(pc, frames, prevOp, int64(op-bytecode.Iload0), ir.InstIload) 148 | case bytecode.Lload0, bytecode.Lload1, bytecode.Lload2, bytecode.Lload3: 149 | g.convertLoad(pc, frames, prevOp, int64(op-bytecode.Lload0), ir.InstLload) 150 | case bytecode.Aload0, bytecode.Aload1, bytecode.Aload2, bytecode.Aload3: 151 | g.convertLoad(pc, frames, prevOp, int64(op-bytecode.Aload0), ir.InstAload) 152 | 153 | case bytecode.Istore: 154 | g.convertStore(int64(code[pc+1]), ir.InstIload) 155 | case bytecode.Lstore: 156 | g.convertStore(int64(code[pc+1]), ir.InstLload) 157 | case bytecode.Istore0, bytecode.Istore1, bytecode.Istore2, bytecode.Istore3: 158 | g.convertStore(int64(op-bytecode.Istore0), ir.InstIload) 159 | case bytecode.Lstore0, bytecode.Lstore1, bytecode.Lstore2, bytecode.Lstore3: 160 | g.convertStore(int64(op-bytecode.Lstore0), ir.InstLload) 161 | case bytecode.Astore0, bytecode.Astore1, bytecode.Astore2, bytecode.Astore3: 162 | g.convertStore(int64(op-bytecode.Astore0), ir.InstAload) 163 | 164 | case bytecode.Iastore: 165 | g.out = append(g.out, ir.Inst{ 166 | Kind: ir.InstIntArraySet, 167 | Args: []ir.Arg{ 168 | g.irArg(2), // array ref 169 | g.irArg(1), // index 170 | g.irArg(0), // value 171 | }, 172 | }) 173 | g.st.drop(3) 174 | 175 | case bytecode.Iaload: 176 | tmp := g.st.nextTmp() 177 | dst := ir.Arg{Kind: ir.ArgReg, Value: tmp + g.tmpOffset} 178 | g.out = append(g.out, ir.Inst{ 179 | Dst: dst, 180 | Kind: ir.InstIntArrayGet, 181 | Args: []ir.Arg{ 182 | g.irArg(1), // array ref 183 | g.irArg(0), // index 184 | }, 185 | }) 186 | g.st.drop(2) 187 | g.st.push(valueTmp, tmp) 188 | 189 | case bytecode.Dup: 190 | v := g.st.top() 191 | g.st.push(v.kind, v.value) 192 | 193 | case bytecode.Arraylength: 194 | tmp := g.st.nextTmp() 195 | dst := ir.Arg{Kind: ir.ArgReg, Value: tmp + g.tmpOffset} 196 | g.out = append(g.out, ir.Inst{ 197 | Dst: dst, 198 | Kind: ir.InstArrayLen, 199 | Args: []ir.Arg{g.irArg(0)}, 200 | }) 201 | g.st.drop(1) 202 | g.st.push(valueTmp, tmp) 203 | 204 | case bytecode.Iadd: 205 | g.convertBinOp(ir.InstIadd) 206 | case bytecode.Ladd: 207 | g.convertBinOp(ir.InstLadd) 208 | case bytecode.Fadd: 209 | g.convertBinOp(ir.InstFadd) 210 | case bytecode.Dadd: 211 | g.convertBinOp(ir.InstDadd) 212 | case bytecode.Isub: 213 | g.convertBinOp(ir.InstIsub) 214 | case bytecode.Imul: 215 | g.convertBinOp(ir.InstImul) 216 | case bytecode.Idiv: 217 | g.convertBinOp(ir.InstIdiv) 218 | 219 | case bytecode.Ineg: 220 | g.convertUnaryOp(ir.InstIneg) 221 | case bytecode.Lneg: 222 | g.convertUnaryOp(ir.InstLneg) 223 | 224 | case bytecode.Iinc: 225 | index := code[pc+1] 226 | delta := int8(code[pc+2]) 227 | dst := ir.Arg{Kind: ir.ArgReg, Value: int64(index)} 228 | g.out = append(g.out, ir.Inst{ 229 | Dst: dst, 230 | Kind: ir.InstIadd, 231 | Args: []ir.Arg{ 232 | dst, 233 | ir.Arg{Kind: ir.ArgIntConst, Value: int64(delta)}, 234 | }, 235 | }) 236 | 237 | case bytecode.L2i: 238 | g.convertUnaryOp(ir.InstConvL2I) 239 | case bytecode.F2i: 240 | g.convertUnaryOp(ir.InstConvF2I) 241 | case bytecode.D2i: 242 | g.convertUnaryOp(ir.InstConvD2I) 243 | case bytecode.I2l: 244 | g.convertUnaryOp(ir.InstConvI2L) 245 | case bytecode.I2b: 246 | g.convertUnaryOp(ir.InstConvI2B) 247 | 248 | case bytecode.Lcmp: 249 | g.convertCmp(ir.InstLcmp) 250 | case bytecode.Ifle: 251 | if g.st.top().kind != valueFlags { 252 | g.convertCmpZero() 253 | } 254 | g.convertCondJump(code, pc, ir.InstJumpLtEq) 255 | case bytecode.Iflt: 256 | if g.st.top().kind != valueFlags { 257 | g.convertCmpZero() 258 | } 259 | g.convertCondJump(code, pc, ir.InstJumpLt) 260 | case bytecode.Ifge: 261 | if g.st.top().kind != valueFlags { 262 | g.convertCmpZero() 263 | } 264 | g.convertCondJump(code, pc, ir.InstJumpGtEq) 265 | case bytecode.Ifeq: 266 | if g.st.top().kind != valueFlags { 267 | g.convertCmpZero() 268 | } 269 | g.convertCondJump(code, pc, ir.InstJumpEqual) 270 | case bytecode.Ifne: 271 | if g.st.top().kind != valueFlags { 272 | g.convertCmpZero() 273 | } 274 | g.convertCondJump(code, pc, ir.InstJumpNotEqual) 275 | 276 | case bytecode.Ificmpne: 277 | g.convertCmp(ir.InstIcmp) 278 | g.convertCondJump(code, pc, ir.InstJumpNotEqual) 279 | case bytecode.Ificmpge: 280 | g.convertCmp(ir.InstIcmp) 281 | g.convertCondJump(code, pc, ir.InstJumpGtEq) 282 | case bytecode.Ificmpgt: 283 | g.convertCmp(ir.InstIcmp) 284 | g.convertCondJump(code, pc, ir.InstJumpGt) 285 | 286 | case bytecode.Goto: 287 | ib1 := int16(code[pc+1]) 288 | ib2 := int16(code[pc+2]) 289 | inst := ir.Inst{ 290 | Kind: ir.InstJump, 291 | Args: []ir.Arg{ 292 | ir.Arg{Kind: ir.ArgBranch, Value: int64(ib1<<8 + ib2)}, 293 | }, 294 | } 295 | g.out = append(g.out, inst) 296 | g.toResolve = append(g.toResolve, unresolvedBranch{ 297 | pc: int32(pc), 298 | branch: &inst.Args[0].Value, 299 | }) 300 | 301 | case bytecode.Invokestatic: 302 | ib1 := uint(code[pc+1]) 303 | ib2 := uint(code[pc+2]) 304 | i := ib1<<8 + ib2 305 | m := g.f.Consts[i].(*jclass.MethodrefConst) 306 | className, pkgName := splitName(m.ClassName) 307 | pkg := g.state.FindPackage(pkgName) 308 | class := pkg.FindClass(className) 309 | method := class.FindMethod(m.Name, m.Descriptor) 310 | argc := argsCount(m.Descriptor) 311 | args := make([]ir.Arg, argc+1) 312 | args[0] = ir.Arg{Kind: ir.ArgSymbolID, Value: int64(method.ID)} 313 | for i := range args[1:] { 314 | args[len(args)-i-1] = g.irArg(i) 315 | } 316 | var dst ir.Arg 317 | var tmp int64 318 | if !strings.HasSuffix(m.Descriptor, ")V") { 319 | tmp = g.st.nextTmp() 320 | dst = ir.Arg{Kind: ir.ArgReg, Value: tmp + g.tmpOffset} 321 | } 322 | op := ir.InstCallStatic 323 | if method.AccessFlags.IsNative() { 324 | op = ir.InstCallGo 325 | } 326 | g.out = append(g.out, ir.Inst{ 327 | Dst: dst, 328 | Kind: op, 329 | Args: args, 330 | }) 331 | g.st.drop(argc) 332 | if dst.Kind != 0 { 333 | g.st.push(valueTmp, tmp) 334 | } 335 | 336 | case bytecode.Ireturn: 337 | g.convertRet(ir.InstIret) 338 | case bytecode.Lreturn: 339 | g.convertRet(ir.InstLret) 340 | case bytecode.Areturn: 341 | g.convertRet(ir.InstAret) 342 | case bytecode.Return: 343 | g.out = append(g.out, ir.Inst{ 344 | Kind: ir.InstRet, 345 | }) 346 | 347 | case bytecode.Newarray: 348 | g.convertNewArray(code, pc) 349 | 350 | default: 351 | panic(fmt.Sprintf("unhandled op=%[1]d (0x%[1]x)", code[pc])) 352 | } 353 | 354 | pc += int(bytecode.OpWidth[op]) 355 | prevOp = op 356 | } 357 | 358 | for _, u := range g.toResolve { 359 | pc := u.pc 360 | branch := int32(*u.branch) 361 | index, ok := pc2index[pc+branch] 362 | if !ok { 363 | panic(fmt.Sprintf("can't resolve branch with pc=%d and branch=%d", pc, branch)) 364 | } 365 | *u.branch = int64(index) 366 | } 367 | 368 | g.out[0].Flags.SetJumpTarget(true) 369 | for _, inst := range g.out { 370 | if isJump(inst) { 371 | index := inst.Args[0].Value 372 | g.out[index].Flags.SetJumpTarget(true) 373 | } 374 | } 375 | 376 | prevIsBranch := false 377 | for i, inst := range g.out { 378 | isLeader := i == 0 || prevIsBranch || inst.Flags.IsJumpTarget() 379 | if isLeader { 380 | g.out[i].Flags.SetBlockLead(true) 381 | } 382 | prevIsBranch = isJump(inst) 383 | } 384 | 385 | out := make([]ir.Inst, len(g.out)) 386 | copy(out, g.out) 387 | dst.Code = out 388 | dst.Out.FrameSlots = int(g.tmpOffset + g.st.tmp) 389 | return nil 390 | } 391 | 392 | func (g *generator) convertCondJump(code []byte, pc int, kind ir.InstKind) { 393 | ib1 := int16(code[pc+1]) 394 | ib2 := int16(code[pc+2]) 395 | inst := ir.Inst{ 396 | Kind: kind, 397 | Args: []ir.Arg{ 398 | ir.Arg{Kind: ir.ArgBranch, Value: int64(ib1<<8 + ib2)}, 399 | ir.Arg{Kind: ir.ArgFlags}, 400 | }, 401 | } 402 | g.out = append(g.out, inst) 403 | g.toResolve = append(g.toResolve, unresolvedBranch{ 404 | pc: int32(pc), 405 | branch: &inst.Args[0].Value, 406 | }) 407 | 408 | if g.st.top().kind != valueFlags { 409 | panic(fmt.Sprintf("%s arg is not flags", kind)) 410 | } 411 | g.st.drop(1) 412 | } 413 | 414 | func (g *generator) convertLoad(pc int, frames []jclass.StackMapFrame, prevOp bytecode.Op, index int64, kind ir.InstKind) { 415 | if isUnconditionalBranch(prevOp) { 416 | frame := findFrame(pc, frames) 417 | if frame == nil { 418 | panic(fmt.Sprintf("no frame for pc=%d", pc)) 419 | } 420 | diff := len(g.st.values) - int(frame.StackDepth) 421 | g.st.drop(diff) 422 | tmp := g.st.nextTmp() 423 | dst := ir.Arg{Kind: ir.ArgReg, Value: tmp + g.tmpOffset} 424 | g.out = append(g.out, ir.Inst{ 425 | Dst: dst, 426 | Kind: kind, 427 | Args: []ir.Arg{{Kind: ir.ArgReg, Value: index}}, 428 | }) 429 | g.st.push(valueTmp, tmp) 430 | } else { 431 | g.st.push(valueIntLocal, index) 432 | } 433 | } 434 | 435 | func (g *generator) convertStore(index int64, kind ir.InstKind) { 436 | dst := ir.Arg{Kind: ir.ArgReg, Value: index} 437 | g.out = append(g.out, ir.Inst{ 438 | Dst: dst, 439 | Kind: kind, 440 | Args: []ir.Arg{g.irArg(0)}, 441 | }) 442 | g.st.drop(1) 443 | } 444 | 445 | func (g *generator) convertCmpZero() { 446 | var kind ir.InstKind 447 | switch g.st.top().kind { 448 | case valueIntLocal: 449 | kind = ir.InstIcmp 450 | default: 451 | panic("unexpected kind for cmp zero") // FIXME 452 | } 453 | 454 | g.out = append(g.out, ir.Inst{ 455 | Dst: ir.Arg{Kind: ir.ArgFlags}, 456 | Kind: kind, 457 | Args: []ir.Arg{ 458 | g.irArg(0), 459 | ir.Arg{Kind: ir.ArgIntConst, Value: 0}, 460 | }, 461 | }) 462 | g.st.drop(1) 463 | g.st.push(valueFlags, 0) 464 | } 465 | 466 | func (g *generator) convertCmp(kind ir.InstKind) { 467 | g.out = append(g.out, ir.Inst{ 468 | Dst: ir.Arg{Kind: ir.ArgFlags}, 469 | Kind: kind, 470 | Args: []ir.Arg{g.irArg(1), g.irArg(0)}, 471 | }) 472 | g.st.drop(2) 473 | g.st.push(valueFlags, 0) 474 | } 475 | 476 | func (g *generator) convertNewArray(code []byte, pc int) { 477 | ib := int16(code[pc+1]) 478 | var kind ir.InstKind 479 | switch ib { 480 | case 4: 481 | kind = ir.InstNewBoolArray 482 | case 5: 483 | kind = ir.InstNewCharArray 484 | case 6: 485 | kind = ir.InstNewFloatArray 486 | case 7: 487 | kind = ir.InstNewDoubleArray 488 | case 8: 489 | kind = ir.InstNewByteArray 490 | case 9: 491 | kind = ir.InstNewShortArray 492 | case 10: 493 | kind = ir.InstNewIntArray 494 | case 11: 495 | kind = ir.InstNewLongArray 496 | } 497 | 498 | tmp := g.st.nextTmp() 499 | dst := ir.Arg{Kind: ir.ArgReg, Value: tmp + g.tmpOffset} 500 | g.out = append(g.out, ir.Inst{ 501 | Dst: dst, 502 | Kind: kind, 503 | Args: []ir.Arg{ 504 | {Kind: ir.ArgEnv}, 505 | g.irArg(0), 506 | }, 507 | }) 508 | g.st.drop(1) 509 | g.st.push(valueTmp, tmp) 510 | } 511 | 512 | func (g *generator) convertUnaryOp(kind ir.InstKind) { 513 | tmp := g.st.nextTmp() 514 | dst := ir.Arg{Kind: ir.ArgReg, Value: tmp + g.tmpOffset} 515 | g.out = append(g.out, ir.Inst{ 516 | Dst: dst, 517 | Kind: kind, 518 | Args: []ir.Arg{g.irArg(0)}, 519 | }) 520 | g.st.drop(1) 521 | g.st.push(valueTmp, tmp) 522 | } 523 | 524 | func (g *generator) convertBinOp(kind ir.InstKind) { 525 | tmp := g.st.nextTmp() 526 | dst := ir.Arg{Kind: ir.ArgReg, Value: tmp + g.tmpOffset} 527 | g.out = append(g.out, ir.Inst{ 528 | Dst: dst, 529 | Kind: kind, 530 | Args: []ir.Arg{g.irArg(1), g.irArg(0)}, 531 | }) 532 | g.st.drop(2) 533 | g.st.push(valueTmp, tmp) 534 | } 535 | 536 | func (g *generator) convertRet(kind ir.InstKind) { 537 | g.out = append(g.out, ir.Inst{ 538 | Kind: kind, 539 | Args: []ir.Arg{g.irArg(0)}, 540 | }) 541 | g.st.drop(1) 542 | } 543 | -------------------------------------------------------------------------------- /irgen/irgen.go: -------------------------------------------------------------------------------- 1 | package irgen 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/quasilyte/go-jdk/ir" 7 | "github.com/quasilyte/go-jdk/vmdat" 8 | ) 9 | 10 | func Generate(st *vmdat.State, packages []*ir.Package) error { 11 | var g generator 12 | g.state = st 13 | for _, pkg := range packages { 14 | for i := range pkg.Classes { 15 | c := &pkg.Classes[i] 16 | g.f = c.File 17 | for j := range c.Methods { 18 | m := &c.Methods[j] 19 | if m.Out.Name == "" { 20 | continue 21 | } 22 | if m.Out.AccessFlags.IsNative() { 23 | continue 24 | } 25 | if err := g.Generate(j, m); err != nil { 26 | return fmt.Errorf("%s: %s.%s: %v", 27 | pkg.Out.Name, c.Name, m.Out.Name, err) 28 | } 29 | } 30 | } 31 | } 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /irgen/irgen_test.go: -------------------------------------------------------------------------------- 1 | package irgen 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os/exec" 7 | "path/filepath" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/quasilyte/go-jdk/ir" 14 | "github.com/quasilyte/go-jdk/irfmt" 15 | "github.com/quasilyte/go-jdk/loader" 16 | "github.com/quasilyte/go-jdk/vmdat" 17 | ) 18 | 19 | func TestArgCount(t *testing.T) { 20 | tests := []struct { 21 | d string 22 | want int 23 | }{ 24 | {"()V", 0}, 25 | {"()I", 0}, 26 | {"(I)I", 1}, 27 | {"(II)I", 2}, 28 | {"(LFoo;)I", 1}, 29 | {"([[I)I", 1}, 30 | {"([I[I)I", 2}, 31 | {"(LFoo;LBar;)I", 2}, 32 | {"(LObject;I)I", 2}, 33 | {"(Z[LObject;Z)V", 3}, 34 | } 35 | 36 | for _, test := range tests { 37 | have := argsCount(test.d) 38 | if have != test.want { 39 | t.Errorf("argsCount(%q):\nhave: %d\nwant: %d", test.d, have, test.want) 40 | } 41 | } 42 | } 43 | 44 | func TestIrgen(t *testing.T) { 45 | absTestdata, err := filepath.Abs("testdata") 46 | if err != nil { 47 | t.Fatalf("abs(testdata): %v", err) 48 | } 49 | 50 | // Compile Java file. 51 | { 52 | args := []string{ 53 | "-cp", "testdata", 54 | "testdata/irgen/C1.java", 55 | } 56 | out, err := exec.Command("javac", args...).CombinedOutput() 57 | if err != nil { 58 | t.Fatalf("javac: %v: %s", err, out) 59 | } 60 | } 61 | 62 | // Collect magic comments. 63 | type irTest struct { 64 | lineNum int 65 | irText string 66 | } 67 | tests := map[string]irTest{} 68 | { 69 | methodNameRE := regexp.MustCompile(`(\w+)\(`) 70 | data, err := ioutil.ReadFile(filepath.Join("testdata", "irgen", "C1.java")) 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | lines := strings.Split(string(data), "\n") 75 | i := 0 76 | for i < len(lines) { 77 | l := lines[i] 78 | if strings.Contains(l, "// slots=") { 79 | var irLines []string 80 | for { 81 | comment := strings.Index(lines[i], "// ") 82 | if comment == -1 { 83 | break 84 | } 85 | irline := lines[i][comment+len("// "):] 86 | irLines = append(irLines, irline) 87 | i++ 88 | } 89 | methodName := methodNameRE.FindStringSubmatch(lines[i])[1] 90 | tests[methodName] = irTest{ 91 | lineNum: i + 1, 92 | irText: strings.Join(irLines, "\n") + "\n", 93 | } 94 | } else { 95 | i++ 96 | } 97 | } 98 | } 99 | 100 | var st vmdat.State 101 | st.Init() 102 | packages, err := loader.LoadPackage(&st, "irgen", &loader.Config{ 103 | ClassPath: []string{absTestdata}, 104 | }) 105 | if err != nil { 106 | t.Fatalf("load package: %v", err) 107 | } 108 | if len(packages) != 1 { 109 | t.Fatalf("expected 1 package, got %d packages", len(packages)) 110 | } 111 | if err := Generate(&st, packages); err != nil { 112 | t.Fatalf("irgen: %v", err) 113 | } 114 | 115 | pkg := packages[0] 116 | for i := range pkg.Classes { 117 | c := &pkg.Classes[i] 118 | for j := range c.Methods { 119 | m := &c.Methods[j] 120 | test, ok := tests[m.Out.Name] 121 | if !ok { 122 | continue 123 | } 124 | have := sprintMethod(&st, m) 125 | want := test.irText 126 | if have != want { 127 | t.Errorf("C1.java:%d: method %s:\nhave:\n%s\nwant:\n%s", 128 | test.lineNum, m.Out.Name, have, want) 129 | } 130 | } 131 | } 132 | } 133 | 134 | var branchArgRE = regexp.MustCompile(`@(\d+)`) 135 | 136 | func sprintMethod(st *vmdat.State, m *ir.Method) string { 137 | // TODO: move to irfmt package and use in javap as well. 138 | 139 | var buf strings.Builder 140 | 141 | index2label := map[int]string{} 142 | for i, inst := range m.Code { 143 | if inst.Flags.IsJumpTarget() && i != 0 { 144 | index2label[i] = fmt.Sprintf("label%d", len(index2label)) 145 | } 146 | } 147 | 148 | fmt.Fprintf(&buf, "slots=%d\n", m.Out.FrameSlots) 149 | blockIndex := -1 150 | for i, inst := range m.Code { 151 | if inst.Flags.IsBlockLead() { 152 | blockIndex++ 153 | } 154 | if inst.Flags.IsJumpTarget() && i != 0 { 155 | fmt.Fprintf(&buf, "%s:\n", index2label[i]) 156 | fmt.Fprintf(&buf, " b%d %s\n", blockIndex, irfmt.Sprint(st, inst)) 157 | } else { 158 | line := fmt.Sprintf(" b%d %s\n", blockIndex, irfmt.Sprint(st, inst)) 159 | m := branchArgRE.FindStringSubmatch(line) 160 | if m != nil { 161 | index, err := strconv.Atoi(m[1]) 162 | if err != nil { 163 | panic(err) 164 | } 165 | line = strings.Replace(line, m[0], index2label[index], 1) 166 | } 167 | buf.WriteString(line) 168 | } 169 | } 170 | 171 | return buf.String() 172 | } 173 | -------------------------------------------------------------------------------- /irgen/stack.go: -------------------------------------------------------------------------------- 1 | package irgen 2 | 3 | // operandStack simulates JVM run-time operands stack. 4 | type operandStack struct { 5 | tmp int64 6 | values []stackValue 7 | freelist []int64 8 | } 9 | 10 | // reset clears a stack. 11 | // Memory is re-used. 12 | func (st *operandStack) reset() { 13 | st.tmp = 0 14 | st.values = st.values[:0] 15 | st.freelist = st.freelist[:0] 16 | } 17 | 18 | func (st *operandStack) nextTmp() int64 { 19 | if len(st.freelist) != 0 { 20 | i := st.freelist[len(st.freelist)-1] 21 | st.freelist = st.freelist[:len(st.freelist)-1] 22 | return i 23 | } 24 | v := st.tmp 25 | st.tmp++ 26 | return v 27 | } 28 | 29 | func (st *operandStack) push(kind valueKind, v int64) { 30 | st.values = append(st.values, stackValue{kind: kind, value: v}) 31 | } 32 | 33 | // top returns last pushed stack value. 34 | func (st *operandStack) top() stackValue { 35 | return st.values[len(st.values)-1] 36 | } 37 | 38 | // drop removes n top values from a stack. 39 | func (st *operandStack) drop(n int) { 40 | for i := 0; i < n; i++ { 41 | v := st.get(i) 42 | if v.kind == valueTmp { 43 | st.freelist = append(st.freelist, v.value) 44 | } 45 | } 46 | 47 | st.values = st.values[:len(st.values)-n] 48 | } 49 | 50 | // get returns n-th stack value. 51 | // Indexing starts from the top, get(0) is identical to top(). 52 | func (st *operandStack) get(n int) stackValue { 53 | return st.values[len(st.values)-n-1] 54 | } 55 | 56 | type stackValue struct { 57 | kind valueKind 58 | value int64 59 | } 60 | 61 | type valueKind int 62 | 63 | const ( 64 | valueInvalid valueKind = iota 65 | 66 | valueIntConst 67 | valueLongConst 68 | valueFloatConst 69 | valueDoubleConst 70 | 71 | valueIntLocal 72 | valueLongLocal 73 | valueFloatLocal 74 | valueDoubleLocal 75 | 76 | valueTmp 77 | valueFlags 78 | ) 79 | -------------------------------------------------------------------------------- /irgen/testdata/irgen/C1.java: -------------------------------------------------------------------------------- 1 | package irgen; 2 | 3 | class C1 { 4 | // slots=0 5 | // b0 Iret 10 6 | public static int m1() { 7 | return 10; 8 | } 9 | 10 | // slots=2 11 | // b0 flags = Icmp r0 0 12 | // b0 JumpGtEq label0 flags 13 | // b1 r1 = Ineg r0 14 | // b1 Iret r1 15 | // label0: 16 | // b2 Iret r0 17 | public static int abs(int x) { 18 | if (x < 0) { 19 | return -x; 20 | } 21 | return x; 22 | } 23 | 24 | // slots=3 25 | // b0 flags = Lcmp r0 0 26 | // b0 JumpGtEq label0 flags 27 | // b1 r2 = Lneg r0 28 | // b1 Jump label1 29 | // label0: 30 | // b2 r2 = Lload r0 31 | // label1: 32 | // b3 Lret r2 33 | public static long labs(long x) { 34 | return (x < 0) ? -x : x; 35 | } 36 | 37 | // slots=4 38 | // b0 flags = Icmp r0 1 39 | // b0 JumpGt label0 flags 40 | // b1 Iret r0 41 | // label0: 42 | // b2 r1 = Isub r0 1 43 | // b2 r2 = CallStatic fib r1 44 | // b2 r1 = Isub r0 2 45 | // b2 r3 = CallStatic fib r1 46 | // b2 r1 = Iadd r2 r3 47 | // b2 Iret r1 48 | public static int fib(int n) { 49 | if (n <= 1) { 50 | return n; 51 | } 52 | return fib(n-1) + fib(n-2); 53 | } 54 | 55 | // slots=3 56 | // b0 flags = Icmp r0 1 57 | // b0 JumpGtEq label0 flags 58 | // b1 Iret 1 59 | // label0: 60 | // b2 r1 = Isub r0 1 61 | // b2 r2 = CallStatic factorial r1 62 | // b2 r1 = Imul r0 r2 63 | // b2 Iret r1 64 | public static int factorial(int n) { 65 | if (n < 1) { 66 | return 1; 67 | } 68 | return n * factorial(n-1); 69 | } 70 | 71 | // slots=4 72 | // b0 r1 = Iload 0 73 | // label0: 74 | // b1 flags = Icmp r0 0 75 | // b1 JumpLt label1 flags 76 | // b2 r2 = Isub r0 r1 77 | // b2 r0 = Iload r2 78 | // b2 r1 = Iadd r1 1 79 | // b2 r2 = Isub r0 r1 80 | // b2 r0 = Iload r2 81 | // b2 Jump label0 82 | // label1: 83 | // b3 r2 = Iload r1 84 | // b3 r3 = Isub r2 1 85 | // b3 Iret r3 86 | public static int sqrt(int n) { 87 | int b = 0; 88 | while (n >= 0) { 89 | n = n - b; 90 | b++; 91 | n = n - b; 92 | } 93 | return b - 1; 94 | } 95 | 96 | // slots=1 97 | // b0 r0 = NewIntArray env 10 98 | // b0 Aret r0 99 | public static int[] newIarray() { 100 | return new int[10]; 101 | } 102 | 103 | // slots=3 104 | // b0 r0 = Iload 128 105 | // b0 r2 = NewDoubleArray env r0 106 | // b0 r1 = Aload r2 107 | // b0 r2 = ArrayLen r1 108 | // b0 Iret r2 109 | public static int newDarray() { 110 | int length = 128; 111 | double[] arr = new double[length]; 112 | return arr.length; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /irgen/utils.go: -------------------------------------------------------------------------------- 1 | package irgen 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/quasilyte/go-jdk/bytecode" 7 | "github.com/quasilyte/go-jdk/ir" 8 | "github.com/quasilyte/go-jdk/jclass" 9 | ) 10 | 11 | // isJump reports whether inst is a jump instruction. 12 | func isJump(inst ir.Inst) bool { 13 | switch inst.Kind { 14 | case ir.InstJump, ir.InstJumpEqual, ir.InstJumpNotEqual, ir.InstJumpGtEq, ir.InstJumpGt, ir.InstJumpLt, ir.InstJumpLtEq: 15 | return true 16 | default: 17 | return false 18 | } 19 | } 20 | 21 | func isUnconditionalBranch(op bytecode.Op) bool { 22 | return op == bytecode.Goto 23 | } 24 | 25 | func argsCount(d string) int { 26 | n := 0 27 | jclass.MethodDescriptor(d).WalkParams(func(jclass.DescriptorType) { 28 | n++ 29 | }) 30 | return n 31 | } 32 | 33 | func splitName(full string) (name, pkg string) { 34 | delim := strings.LastIndexByte(full, '/') 35 | if delim == -1 { 36 | return full, "" 37 | } 38 | return full[delim+1:], full[:delim] 39 | } 40 | 41 | func findFrame(offset int, frames []jclass.StackMapFrame) *jclass.StackMapFrame { 42 | for i, frame := range frames { 43 | if int(frame.Offset) == offset { 44 | return &frames[i] 45 | } 46 | } 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /iropt/iropt.go: -------------------------------------------------------------------------------- 1 | package iropt 2 | 3 | import ( 4 | "github.com/quasilyte/go-jdk/ir" 5 | "github.com/quasilyte/go-jdk/vmdat" 6 | ) 7 | 8 | func Optimize(st *vmdat.State, packages []*ir.Package) { 9 | // Does nothing right now. 10 | // Stub for the future. 11 | } 12 | -------------------------------------------------------------------------------- /javap/print.go: -------------------------------------------------------------------------------- 1 | // Package javap is a very simplistic Java class files dumping utility. 2 | // 3 | // Mostly intended for testing and debugging. 4 | package javap 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "strings" 10 | 11 | "github.com/quasilyte/go-jdk/bytecode" 12 | "github.com/quasilyte/go-jdk/jclass" 13 | ) 14 | 15 | // Fprint pretty-prints c to a given writer. 16 | func Fprint(w io.Writer, c *jclass.File) { 17 | p := printer{w: w, c: c} 18 | 19 | className := c.ThisClassName 20 | p.write("class file for %q\n", className) 21 | p.write("version: %d.%d\n", c.Ver.Major, c.Ver.Minor) 22 | 23 | p.write("constants:\n") 24 | for i, c := range c.Consts[1:] { 25 | s := strings.ReplaceAll(fmt.Sprintf("%#v", c), "&jclass.", "&") 26 | p.write(" %-3d %s\n", i+1, s) 27 | } 28 | 29 | p.write("methods:\n") 30 | for _, m := range c.Methods { 31 | p.printMethod(m) 32 | } 33 | } 34 | 35 | type printer struct { 36 | w io.Writer 37 | c *jclass.File 38 | } 39 | 40 | func (p *printer) write(format string, args ...interface{}) { 41 | fmt.Fprintf(p.w, format, args...) 42 | } 43 | 44 | func (p *printer) printMethod(m jclass.Method) { 45 | sig := jclass.MethodDescriptor(m.Descriptor).SignatureString(m.Name) 46 | if m.AccessFlags.IsNative() { 47 | p.write(" native method %s\n", sig) 48 | return 49 | } 50 | 51 | p.write(" method %s:\n", sig) 52 | codeAttr := findAttr(p.c, m.Attrs, "Code").(jclass.CodeAttribute) 53 | p.write(" max_locals=%d max_stack=%d\n", 54 | codeAttr.MaxLocals, codeAttr.MaxStack) 55 | p.write(" bytecode (size=%d):\n", len(codeAttr.Code)) 56 | 57 | pc := 0 58 | code := codeAttr.Code 59 | for pc < len(code) { 60 | op := bytecode.Op(code[pc]) 61 | width := int(bytecode.OpWidth[op]) 62 | opbytes := code[pc : pc+width] 63 | fmt.Printf(" %3d %-18s %x\n", pc, op.String(), opbytes) 64 | pc += width 65 | } 66 | frameTab, ok := findAttr(p.c, codeAttr.Attrs, "StackMapTable").(jclass.StackMapTableAttribute) 67 | if ok && len(frameTab.Frames) != 0 { 68 | fmt.Println(" ---- (stack map) ----") 69 | for _, frame := range frameTab.Frames { 70 | fmt.Printf(" %3d depth=%d\n", frame.Offset, frame.StackDepth) 71 | } 72 | } 73 | 74 | p.write("\n") 75 | } 76 | 77 | func findAttr(c *jclass.File, attrs []jclass.Attribute, name string) jclass.Attribute { 78 | for _, attr := range attrs { 79 | switch attr := attr.(type) { 80 | case jclass.StackMapTableAttribute: 81 | if name == "StackMapTable" { 82 | return attr 83 | } 84 | case jclass.CodeAttribute: 85 | if name == "Code" { 86 | return attr 87 | } 88 | case jclass.RawAttribute: 89 | attrName := c.Consts[attr.NameIndex].(*jclass.Utf8Const).Value 90 | if name == attrName { 91 | return attr 92 | } 93 | } 94 | } 95 | return nil 96 | } 97 | -------------------------------------------------------------------------------- /javatest/golib.go: -------------------------------------------------------------------------------- 1 | package javatest 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "runtime" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/quasilyte/go-jdk/jruntime" 11 | ) 12 | 13 | var golibOutput bytes.Buffer 14 | 15 | func golibNop() {} 16 | 17 | func golibPrintInt(x int32) { 18 | fmt.Fprintf(&golibOutput, "%d\n", x) 19 | } 20 | 21 | func golibPrintLong(x int64) { 22 | fmt.Fprintf(&golibOutput, "%d\n", x) 23 | } 24 | 25 | func golibPrintIntArray(xs *jruntime.IntArrayObject) { 26 | var parts []string 27 | for _, x := range xs.AsSlice() { 28 | p := strconv.Itoa(int(x)) 29 | parts = append(parts, p) 30 | } 31 | fmt.Fprintf(&golibOutput, "[%s]\n", strings.Join(parts, ", ")) 32 | } 33 | 34 | func golibIsub(x, y int32) int32 { 35 | return x - y 36 | } 37 | 38 | func golibIsub3(x, y, z int32) int32 { 39 | return x - y - z 40 | } 41 | 42 | func golibII_L(a1 int32, a2 int32) int64 { 43 | return int64(a1 - a2) 44 | } 45 | 46 | func golibLI_I(a1 int64, a2 int32) int32 { 47 | return int32(a1) - a2 48 | } 49 | 50 | func golibIL_I(a1 int32, a2 int64) int32 { 51 | return a1 - int32(a2) 52 | } 53 | 54 | func golibILIL_I(a1 int32, a2 int64, a3 int32, a4 int64) int32 { 55 | return a1 - int32(a2) - a3 - int32(a4) 56 | } 57 | 58 | func golibGC() { 59 | runtime.GC() 60 | } 61 | -------------------------------------------------------------------------------- /javatest/java_test.go: -------------------------------------------------------------------------------- 1 | package javatest 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "runtime" 11 | "testing" 12 | 13 | "github.com/quasilyte/go-jdk/irgen" 14 | "github.com/quasilyte/go-jdk/jit" 15 | "github.com/quasilyte/go-jdk/jruntime" 16 | "github.com/quasilyte/go-jdk/loader" 17 | "github.com/quasilyte/go-jdk/vmdat" 18 | ) 19 | 20 | var testsDebug = os.Getenv("DEBUG") == "true" 21 | 22 | var tests = []*testParams{ 23 | {Pkg: "intvalues", Input: 400}, 24 | {Pkg: "longvalues", Input: -400}, 25 | {Pkg: "scopes"}, 26 | {Pkg: "arith1", Input: 100}, 27 | {Pkg: "gocall1", Input: -100}, 28 | {Pkg: "gocall2"}, 29 | {Pkg: "staticcall1"}, 30 | {Pkg: "staticcall2"}, 31 | {Pkg: "staticcall3"}, 32 | {Pkg: "staticfinal"}, 33 | {Pkg: "loops1"}, 34 | {Pkg: "arrays1"}, 35 | {Pkg: "arrays2"}, 36 | {Pkg: "bubblesort"}, 37 | {Pkg: "arrayreverse"}, 38 | {Pkg: "eratosthenes", Input: 30}, 39 | } 40 | 41 | func TestMain(m *testing.M) { 42 | if !hasCommand("java") { 43 | log.Println("skip: missing java") 44 | return 45 | } 46 | if !hasCommand("javac") { 47 | log.Println("skip: missing javac") 48 | return 49 | } 50 | 51 | fillTestDefaults(tests) 52 | generateJavaMain(tests) 53 | compileJava() 54 | code := m.Run() 55 | os.Remove(filepath.Join("testdata", "Main.java")) 56 | os.Exit(code) 57 | } 58 | 59 | func TestJava(t *testing.T) { 60 | for _, test := range tests { 61 | t.Run(test.Pkg, func(t *testing.T) { 62 | runTest(t, test) 63 | }) 64 | } 65 | } 66 | 67 | func BenchmarkJava(b *testing.B) { 68 | benchmarks := []*benchParams{ 69 | { 70 | Method: "nop", 71 | }, 72 | 73 | { 74 | Method: "callGoNop", 75 | Ops: 5, 76 | }, 77 | } 78 | runBenchmarks(b, benchmarks) 79 | } 80 | 81 | type benchParams struct { 82 | Name string 83 | Method string 84 | Ops int 85 | } 86 | 87 | type testParams struct { 88 | Pkg string 89 | 90 | EntryClass string 91 | EntryMethod string 92 | 93 | Input int32 94 | } 95 | 96 | func fillTestDefaults(tests []*testParams) { 97 | for _, test := range tests { 98 | if test.EntryClass == "" { 99 | test.EntryClass = "Test" 100 | } 101 | if test.EntryMethod == "" { 102 | test.EntryMethod = "run" 103 | } 104 | } 105 | } 106 | 107 | func runBenchmarks(b *testing.B, benchmarks []*benchParams) { 108 | vm, err := jruntime.OpenVM(runtime.GOARCH) 109 | if err != nil { 110 | b.Fatalf("open VM: %v", err) 111 | } 112 | defer vm.Close() 113 | 114 | vm.State.BindGoFunc("benchutil/B.nop", golibNop) 115 | 116 | pkg, err := loadAndCompilePackage(vm, "bench") 117 | if err != nil { 118 | b.Fatal(err) 119 | } 120 | 121 | for _, params := range benchmarks { 122 | method, err := findMethod(pkg, "Bench", params.Method) 123 | if err != nil { 124 | b.Fatal(err) 125 | } 126 | b.Run(params.Method, func(b *testing.B) { 127 | n := b.N 128 | if params.Ops > 1 && b.N != 1 { 129 | n /= params.Ops 130 | } 131 | env := jruntime.NewEnv(vm, &jruntime.EnvConfig{}) 132 | for i := 0; i < n; i++ { 133 | env.IntCall(method) 134 | } 135 | }) 136 | } 137 | } 138 | 139 | func runTest(t *testing.T, params *testParams) { 140 | vm, err := jruntime.OpenVM(runtime.GOARCH) 141 | if err != nil { 142 | t.Fatalf("open VM: %v", err) 143 | } 144 | defer vm.Close() 145 | 146 | vm.State.BindGoFunc("testutil/T.printInt", golibPrintInt) 147 | vm.State.BindGoFunc("testutil/T.printLong", golibPrintLong) 148 | vm.State.BindGoFunc("testutil/T.printIntArray", golibPrintIntArray) 149 | vm.State.BindGoFunc("testutil/T.isub", golibIsub) 150 | vm.State.BindGoFunc("testutil/T.isub3", golibIsub3) 151 | vm.State.BindGoFunc("testutil/T.ii_l", golibII_L) 152 | vm.State.BindGoFunc("testutil/T.il_i", golibIL_I) 153 | vm.State.BindGoFunc("testutil/T.li_i", golibLI_I) 154 | vm.State.BindGoFunc("testutil/T.ilil_i", golibILIL_I) 155 | vm.State.BindGoFunc("testutil/T.GC", golibGC) 156 | 157 | pkg, err := loadAndCompilePackage(vm, params.Pkg) 158 | if err != nil { 159 | t.Fatal(err) 160 | } 161 | 162 | method, err := findMethod(pkg, params.EntryClass, params.EntryMethod) 163 | if err != nil { 164 | t.Fatal(err) 165 | } 166 | if method.Descriptor != "(I)V" { 167 | t.Fatalf("entry method signature should be: void %s()", params.EntryMethod) 168 | } 169 | 170 | golibOutput.Reset() 171 | env := jruntime.NewEnv(vm, &jruntime.EnvConfig{}) 172 | env.IntArg(0, int64(params.Input)) 173 | if _, err := env.IntCall(method); err != nil { 174 | t.Fatalf("call error: %v", err) 175 | } 176 | have := golibOutput.String() 177 | want := runJava(t, params) 178 | if testsDebug { 179 | t.Logf("Go output:\n%s", have) 180 | } 181 | if have != want { 182 | t.Errorf("output mismatch:\nhave:\n%s\nwant:\n%s", have, want) 183 | } 184 | } 185 | 186 | func runJava(t *testing.T, params *testParams) string { 187 | args := []string{ 188 | "-cp", "testdata:testdata/_javalib", 189 | "Main", 190 | params.Pkg, 191 | } 192 | out, err := exec.Command("java", args...).CombinedOutput() 193 | if err != nil { 194 | t.Fatalf("java: %v: %s", err, out) 195 | } 196 | return string(out) 197 | } 198 | 199 | func generateJavaMain(tests []*testParams) { 200 | tmpl := template.Must(template.New(`Main`).Parse(` 201 | // Generated automatically by java_test.go. 202 | // This entry point is used by a host Java implementation. 203 | class Main { 204 | public static void main(String args[]) { 205 | switch (args[0]) { 206 | {{- range .}} 207 | case "{{.Pkg}}": 208 | {{.Pkg}}.{{.EntryClass}}.{{.EntryMethod}}({{.Input}}); 209 | return; 210 | {{- end}} 211 | default: 212 | System.out.println("unknown package: " + args[0]); 213 | } 214 | } 215 | } 216 | `)) 217 | f, err := os.Create(filepath.Join("testdata", "Main.java")) 218 | if err != nil { 219 | log.Fatalf("create file: %v", err) 220 | } 221 | defer f.Close() 222 | if err := tmpl.Execute(f, tests); err != nil { 223 | log.Fatalf("execute template: %v", err) 224 | } 225 | } 226 | 227 | func compileJava() { 228 | // TODO: compile with 1 javac call instead of 2? 229 | 230 | // Compilation of Main.java will create class files for all tests. 231 | { 232 | args := []string{ 233 | "-cp", "testdata:testdata/_javalib", 234 | "testdata/Main.java", 235 | } 236 | out, err := exec.Command("javac", args...).CombinedOutput() 237 | if err != nil { 238 | log.Fatalf("javac: %v: %s", err, out) 239 | } 240 | } 241 | 242 | // Now we only need to compile _golib classes. 243 | { 244 | args := []string{ 245 | "-cp", "testdata/_golib", 246 | "testdata/_golib/testutil/T.java", 247 | "testdata/_golib/benchutil/B.java", 248 | "testdata/bench/Bench.java", 249 | } 250 | out, err := exec.Command("javac", args...).CombinedOutput() 251 | if err != nil { 252 | log.Fatalf("javac: %v: %s", err, out) 253 | } 254 | } 255 | } 256 | 257 | func findMethod(pkg *vmdat.Package, className, methodName string) (*vmdat.Method, error) { 258 | class := pkg.FindClass(className) 259 | if class == nil { 260 | return nil, fmt.Errorf("class %s not found in %s", className, pkg.Name) 261 | } 262 | method := class.FindMethod(methodName, "") 263 | if method == nil { 264 | return nil, fmt.Errorf("method %s not found in %s", methodName, class.Name) 265 | } 266 | return method, nil 267 | } 268 | 269 | func loadAndCompilePackage(vm *jruntime.VM, pkg string) (*vmdat.Package, error) { 270 | absTestdata, err := filepath.Abs("testdata") 271 | if err != nil { 272 | return nil, err 273 | } 274 | packages, err := loader.LoadPackage(&vm.State, pkg, &loader.Config{ 275 | ClassPath: []string{ 276 | absTestdata, 277 | filepath.Join(absTestdata, "_golib"), 278 | }, 279 | }) 280 | if err != nil { 281 | return nil, fmt.Errorf("load %s: %v", pkg, err) 282 | } 283 | 284 | if err := irgen.Generate(&vm.State, packages); err != nil { 285 | return nil, fmt.Errorf("irgen: %v", err) 286 | } 287 | 288 | ctx := jit.Context{ 289 | Mmap: &vm.Mmap, 290 | State: &vm.State, 291 | } 292 | jruntime.BindFuncs(&ctx) 293 | if err := vm.Compiler.Compile(ctx, packages); err != nil { 294 | return nil, fmt.Errorf("compile: %v", err) 295 | } 296 | 297 | return packages[0].Out, nil 298 | } 299 | 300 | func hasCommand(name string) bool { 301 | err := exec.Command("/bin/sh", "-c", "command -v "+name).Run() 302 | return err == nil 303 | } 304 | -------------------------------------------------------------------------------- /javatest/testdata/_golib/benchutil/B.java: -------------------------------------------------------------------------------- 1 | package benchutil; 2 | 3 | public class B { 4 | public static native void nop(); 5 | } 6 | -------------------------------------------------------------------------------- /javatest/testdata/_golib/testutil/T.java: -------------------------------------------------------------------------------- 1 | package testutil; 2 | 3 | public class T { 4 | public static native void printInt(int x); 5 | public static native void printLong(long x); 6 | public static native void printIntArray(int[] xs); 7 | 8 | public static native int isub(int x, int y); 9 | public static native int isub3(int x, int y, int z); 10 | 11 | public static native long ii_l(int a1, int a2); 12 | public static native int li_i(long a1, int a2); 13 | public static native int il_i(int a1, long a2); 14 | public static native int ilil_i(int a1, long a2, int a3, long a4); 15 | 16 | public static native void GC(); 17 | } 18 | -------------------------------------------------------------------------------- /javatest/testdata/_javalib/testutil/T.java: -------------------------------------------------------------------------------- 1 | package testutil; 2 | 3 | import java.util.Arrays; 4 | 5 | public class T { 6 | public static void printInt(int x) { 7 | System.out.println(x); 8 | } 9 | 10 | public static void printLong(long x) { 11 | System.out.println(x); 12 | } 13 | 14 | public static void printIntArray(int[] xs) { 15 | System.out.println(Arrays.toString(xs)); 16 | } 17 | 18 | public static int isub(int x, int y) { 19 | return x - y; 20 | } 21 | 22 | public static int isub3(int x, int y, int z) { 23 | return x - y - z; 24 | } 25 | 26 | public static long ii_l(int a1, int a2) { 27 | return a1 - a2; 28 | } 29 | 30 | public static int li_i(long a1, int a2) { 31 | return (int)a1 - a2; 32 | } 33 | 34 | public static int il_i(int a1, long a2) { 35 | return a1 - (int)a2; 36 | } 37 | 38 | public static int ilil_i(int a1, long a2, int a3, long a4) { 39 | return a1 - (int)a2 - a3 - (int)a4; 40 | } 41 | 42 | public static void GC() {} 43 | } 44 | -------------------------------------------------------------------------------- /javatest/testdata/arith1/Test.java: -------------------------------------------------------------------------------- 1 | package arith1; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | T.printInt(x+1); 8 | x++; 9 | x += 1; 10 | x = x + 1; 11 | T.printInt(x); 12 | 13 | T.printInt(x-1); 14 | x--; 15 | x -= 1; 16 | x = x - 1; 17 | T.printInt(x); 18 | 19 | int a = 10; 20 | int b = 3; 21 | T.printInt(a-b); 22 | T.printInt(b-a); 23 | T.printInt(a+b); 24 | T.printInt(b+a); 25 | 26 | int y = 1000; 27 | T.printInt(a/b); 28 | T.printInt(b/a); 29 | T.printInt(y/a/b); 30 | T.printInt(a/2); 31 | T.printInt(a/3); 32 | T.printInt(a/5); 33 | 34 | T.printInt(a*b); 35 | T.printInt(b*a*y); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /javatest/testdata/arrayreverse/Test.java: -------------------------------------------------------------------------------- 1 | package arrayreverse; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | test0(); 8 | test1(); 9 | test2(); 10 | test5(); 11 | test7(); 12 | } 13 | 14 | public static void test0() { 15 | T.printIntArray(reverse(new int[]{})); 16 | } 17 | 18 | public static void test1() { 19 | T.printIntArray(reverse(new int[]{1})); 20 | } 21 | 22 | public static void test2() { 23 | T.printIntArray(reverse(new int[]{1, 2})); 24 | T.printIntArray(reverse(new int[]{2, 1})); 25 | } 26 | 27 | public static void test5() { 28 | T.printIntArray(reverse(new int[]{1, 2, 3, 4, 5})); 29 | T.printIntArray(reverse(new int[]{4, 3, 2, 1, 1})); 30 | } 31 | 32 | public static void test7() { 33 | T.printIntArray(reverse(new int[]{1, 1, 1, 2, 1, 1, 1})); 34 | } 35 | 36 | public static int[] reverse(int[] array) { 37 | for (int i = 0; i < array.length/2; i++) { 38 | int temp = array[i]; 39 | array[i] = array[array.length-i-1]; 40 | array[array.length-i-1] = temp; 41 | } 42 | return array; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /javatest/testdata/arrays1/Test.java: -------------------------------------------------------------------------------- 1 | package arrays1; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | // We have a few forced GC runs to ensure that 8 | // our objects (arrays) are not collected. 9 | 10 | int[] xs = new int[4]; 11 | T.printInt(xs.length); 12 | 13 | T.GC(); 14 | 15 | T.printInt(xs[0]); 16 | xs[0] = 15; 17 | T.printInt(xs[0]); 18 | 19 | T.printInt(xs[1]); 20 | xs[1] = xs[0]; 21 | T.printInt(xs[1]); 22 | 23 | T.printInt(xs.length); 24 | 25 | T.GC(); 26 | 27 | int[] ys = new int[10]; 28 | ys[0] = 1; 29 | ys[1] = 2; 30 | ys[3] = 3; 31 | T.printInt(ys[0]); 32 | T.printInt(ys[1]); 33 | T.printInt(ys[2]); 34 | T.printInt(ys[9]); 35 | 36 | T.GC(); 37 | 38 | int[] zs = makeArray(); 39 | zs[0] = -1; 40 | zs[1] = -2; 41 | zs[3] = -3; 42 | T.printInt(zs[0]); 43 | T.printInt(zs[1]); 44 | T.printInt(zs[2]); 45 | T.printInt(zs[9]); 46 | 47 | // Run operations again after GC. 48 | T.GC(); 49 | 50 | T.printInt(xs.length); 51 | 52 | T.printInt(xs[0]); 53 | xs[0] = 15; 54 | T.printInt(xs[0]); 55 | 56 | T.printInt(xs[1]); 57 | xs[1] = xs[0]; 58 | T.printInt(xs[1]); 59 | 60 | T.printInt(xs.length); 61 | 62 | T.printInt(ys[0]); 63 | T.printInt(ys[1]); 64 | T.printInt(ys[2]); 65 | T.printInt(ys[9]); 66 | 67 | T.printInt(zs[0]); 68 | T.printInt(zs[1]); 69 | T.printInt(zs[2]); 70 | T.printInt(zs[9]); 71 | 72 | indexVarAndSize(17); 73 | } 74 | 75 | private static int[] makeArray() { 76 | return new int[10]; 77 | } 78 | 79 | private static void indexVarAndSize(int length) { 80 | int[] a = new int[length]; 81 | T.printInt(a.length); 82 | int i1 = 0; 83 | T.printInt(a[i1]); 84 | a[i1] = 10; 85 | T.printInt(a[i1]); 86 | 87 | T.GC(); 88 | 89 | int i2 = 5; 90 | T.printInt(a[i2]); 91 | a[i2] = 20; 92 | T.printInt(a[i2]); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /javatest/testdata/arrays2/Test.java: -------------------------------------------------------------------------------- 1 | package arrays2; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | int[] lit1 = new int[]{1, 2, 3}; 8 | T.printIntArray(lit1); 9 | 10 | int n = lit1.length; 11 | T.printInt(n); 12 | for (int i = 0; i < n; i++) { 13 | T.printInt(lit1[i]); 14 | } 15 | 16 | arrayLoop1(lit1); 17 | arrayLoop2(lit1); 18 | arrayLoop3(lit1); 19 | } 20 | 21 | public static void arrayLoop1(int[] xs) { 22 | int n = xs.length; 23 | T.printInt(xs.length); 24 | T.printInt(n); 25 | for (int i = 0; i < n; i++) { 26 | T.printInt(xs[i]); 27 | } 28 | } 29 | 30 | public static void arrayLoop2(int[] xs) { 31 | for (int i = 0; i < xs.length; i++) { 32 | T.printInt(xs[i]); 33 | } 34 | } 35 | 36 | public static void arrayLoop3(int[] xs) { 37 | for (int x : xs) { 38 | T.printInt(x); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /javatest/testdata/bench/Bench.java: -------------------------------------------------------------------------------- 1 | package bench; 2 | 3 | import benchutil.B; 4 | 5 | public class Bench { 6 | public static void nop() { 7 | } 8 | 9 | public static void callGoNop() { 10 | B.nop(); 11 | B.nop(); 12 | B.nop(); 13 | B.nop(); 14 | B.nop(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /javatest/testdata/bench/callgo/Bench.java: -------------------------------------------------------------------------------- 1 | package bench.callgo; 2 | 3 | import benchutil.B; 4 | 5 | public class Bench { 6 | public static void run() { 7 | B.nop(); 8 | B.nop(); 9 | B.nop(); 10 | B.nop(); 11 | B.nop(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /javatest/testdata/bubblesort/Test.java: -------------------------------------------------------------------------------- 1 | package bubblesort; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | test0(); 8 | test1(); 9 | test2(); 10 | test5(); 11 | } 12 | 13 | public static void test0() { 14 | int[] a0 = new int[]{}; 15 | sort(a0); 16 | T.printIntArray(a0); 17 | } 18 | 19 | public static void test1() { 20 | int[] a1 = new int[]{1}; 21 | sort(a1); 22 | T.printIntArray(a1); 23 | } 24 | 25 | public static void test2() { 26 | int[] a2sorted = new int[]{1, 2}; 27 | sort(a2sorted); 28 | T.printIntArray(a2sorted); 29 | 30 | int[] a2 = new int[]{2, 1}; 31 | sort(a2); 32 | T.printIntArray(a2); 33 | } 34 | 35 | public static void test5() { 36 | int[] a5sorted = new int[]{1, 2, 3, 4, 5}; 37 | sort(a5sorted); 38 | T.printIntArray(a5sorted); 39 | 40 | int[] a5 = new int[]{3, 2, 1, 5, 4}; 41 | T.printIntArray(a5); 42 | sort(a5); 43 | T.printIntArray(a5); 44 | } 45 | 46 | public static void sort(int[] arr) { 47 | int n = arr.length; 48 | for (int i = 0; i < n-1; i++) { 49 | for (int j = 0; j < n-i-1; j++) { 50 | if (arr[j+1] <= arr[j]) { 51 | int temp = arr[j]; 52 | arr[j] = arr[j+1]; 53 | arr[j+1] = temp; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /javatest/testdata/eratosthenes/Test.java: -------------------------------------------------------------------------------- 1 | package eratosthenes; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | private static final int TRUE = 1; 7 | private static final int FALSE = 0; 8 | 9 | public static void run(int n) { 10 | // TODO: change int array to booleans when its implemented. 11 | // TODO: use *2 instead of *two when immediate imul is ready. 12 | 13 | int prime[] = new int[n+1]; 14 | for (int i = 0; i < n; i++) { 15 | prime[i] = TRUE; 16 | } 17 | 18 | for (int p = 2; p * p <= n; p++) { 19 | if (prime[p] == TRUE) { 20 | int two = 2; 21 | for (int i = p * two; i <= n; i += p) { 22 | prime[i] = FALSE; 23 | } 24 | } 25 | } 26 | 27 | for (int i = 2; i <= n; i++) { 28 | if (prime[i] == TRUE) { 29 | T.printInt(i); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /javatest/testdata/gocall1/Test.java: -------------------------------------------------------------------------------- 1 | package gocall1; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | T.printInt(T.isub(0, 0)); 8 | T.printInt(T.isub(1, 0)); 9 | T.printInt(T.isub(0, 1)); 10 | 11 | T.printInt(T.isub3(0, 0, 0)); 12 | T.printInt(T.isub3(1, 0, 0)); 13 | T.printInt(T.isub3(0, 1, 0)); 14 | T.printInt(T.isub3(0, 0, 1)); 15 | T.printInt(T.isub3(1, 2, 3)); 16 | T.printInt(T.isub3(3, 2, 1)); 17 | T.printInt(T.isub3(3, 3, 3)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /javatest/testdata/gocall2/Test.java: -------------------------------------------------------------------------------- 1 | package gocall2; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | T.printLong(T.ii_l(1, -10)); 8 | T.printLong(T.ii_l(-10, 1)); 9 | T.printLong(T.ii_l(-2, -2)); 10 | 11 | T.printInt(T.li_i(1000, 2000)); 12 | T.printInt(T.li_i(2000, 1000)); 13 | 14 | T.printInt(T.il_i(1, -10)); 15 | T.printInt(T.il_i(-10, 1)); 16 | T.printInt(T.il_i(-2, -2)); 17 | 18 | T.printInt(T.ilil_i(10, 4, -24, -4)); 19 | T.printInt(T.ilil_i(2, 8, 1, 1)); 20 | T.printInt(T.ilil_i(1, 1, 8, 2)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /javatest/testdata/intvalues/Test.java: -------------------------------------------------------------------------------- 1 | package intvalues; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | T.printInt(x); 8 | 9 | T.printInt(0); 10 | T.printInt(100); 11 | T.printInt(-100); 12 | T.printInt(0xff); 13 | T.printInt(0xffffff); 14 | T.printInt(-0xffffff); 15 | 16 | int v1 = 120; 17 | int v2 = -120; 18 | int v3 = -139438; 19 | int v4 = 192394122; 20 | T.printInt(v1); 21 | T.printInt(v2); 22 | T.printInt(v3); 23 | T.printInt(v4); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /javatest/testdata/longvalues/Test.java: -------------------------------------------------------------------------------- 1 | package longvalues; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | T.printLong(x); 8 | 9 | T.printLong(0); 10 | T.printLong(100); 11 | T.printLong(-100); 12 | T.printLong(0xff); 13 | T.printLong(0xffffff); 14 | T.printLong(-0xffffff); 15 | 16 | long v1 = 120; 17 | long v2 = -120; 18 | long v3 = -139438; 19 | long v4 = 192394122; 20 | T.printLong(v1); 21 | T.printLong(v2); 22 | T.printLong(v3); 23 | T.printLong(v4); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /javatest/testdata/loops1/Test.java: -------------------------------------------------------------------------------- 1 | package loops1; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | int v = 0; 8 | for (int i = 0; i < 100; i++) { 9 | if (i < 50) { 10 | v++; 11 | } 12 | } 13 | T.printInt(v); 14 | 15 | v = 101; 16 | int i = 0; 17 | while (i < 10) { 18 | T.printInt(i); 19 | v -= i; 20 | i += 3; 21 | } 22 | T.printInt(v); 23 | 24 | i = 110; 25 | do { 26 | i--; 27 | } while (i >= 100); 28 | T.printInt(i); 29 | 30 | whileGreater(0); 31 | whileGreater(5); 32 | } 33 | 34 | public static void whileGreater(int i) { 35 | while (i > 0) { 36 | i--; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /javatest/testdata/scopes/Test.java: -------------------------------------------------------------------------------- 1 | package scopes; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | T.printInt(x); 8 | { 9 | int x2 = 10; 10 | T.printInt(x2); 11 | } 12 | T.printInt(x); 13 | x = 20; 14 | { 15 | int x2 = 30; 16 | int y = 20; 17 | T.printInt(y); 18 | } 19 | int y = 30; 20 | T.printInt(y); 21 | T.printInt(x); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /javatest/testdata/staticcall1/Test.java: -------------------------------------------------------------------------------- 1 | package staticcall1; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | T.printInt(add(x, 1)); 8 | T.printInt(Test.add(x, 1)); 9 | 10 | T.printInt(iabs(0)); 11 | T.printInt(iabs(1)); 12 | T.printInt(iabs(-1)); 13 | T.printInt(iabs(13924)); 14 | 15 | T.printInt(iabs2(0)); 16 | T.printInt(iabs2(1)); 17 | T.printInt(iabs2(-1)); 18 | 19 | T.printInt(iabs3(0)); 20 | T.printInt(iabs3(1)); 21 | T.printInt(iabs3(-1)); 22 | 23 | T.printInt(iabs4(0)); 24 | T.printInt(iabs4(1)); 25 | T.printInt(iabs4(-1)); 26 | 27 | T.printLong(labs(0)); 28 | T.printLong(labs(1)); 29 | T.printLong(labs(-1)); 30 | T.printLong(labs(-15)); 31 | T.printLong(labs(13)); 32 | T.printLong(lucky7()); 33 | 34 | T.printLong(labs2(0)); 35 | T.printLong(labs2(1)); 36 | T.printLong(labs2(-1)); 37 | } 38 | 39 | public static int add(int x, int y) { 40 | return x + y; 41 | } 42 | 43 | public static int iabs(int x) { 44 | if (x < 0) { 45 | return -x; 46 | } 47 | return x; 48 | } 49 | 50 | public static int iabs2(int x) { 51 | if (x < 0) { 52 | x = -x; 53 | } 54 | return x; 55 | } 56 | 57 | public static int iabs3(int x) { 58 | int result = 0; 59 | if (x < 0) { 60 | result = -x; 61 | } else { 62 | result = x; 63 | } 64 | return result; 65 | } 66 | 67 | public static int iabs4(int x) { 68 | return (x < 0) ? -x : x; 69 | } 70 | 71 | public static long labs(long x) { 72 | if (x < 0) { 73 | return -x; 74 | } 75 | return x; 76 | } 77 | 78 | public static long labs2(long x) { 79 | return (x < 0) ? -x : x; 80 | } 81 | 82 | public static long lucky7() { return 777; } 83 | } 84 | -------------------------------------------------------------------------------- /javatest/testdata/staticcall2/Test.java: -------------------------------------------------------------------------------- 1 | package staticcall2; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | T.printInt(fib(0)); 8 | T.printInt(fib(1)); 9 | T.printInt(fib(2)); 10 | T.printInt(fib(3)); 11 | T.printInt(fib(4)); 12 | 13 | T.printInt(factorial(0)); 14 | T.printInt(factorial(1)); 15 | T.printInt(factorial(2)); 16 | T.printInt(factorial(3)); 17 | T.printInt(factorial(4)); 18 | T.printInt(factorial(5)); 19 | T.printInt(factorial(6)); 20 | 21 | T.printInt(sqrt(0)); 22 | T.printInt(sqrt(3)); 23 | T.printInt(sqrt(4)); 24 | T.printInt(sqrt(9)); 25 | T.printInt(sqrt(36)); 26 | T.printInt(sqrt(96)); 27 | T.printInt(sqrt(100)); 28 | T.printInt(sqrt(256)); 29 | 30 | T.printInt(gcd(1, 1)); 31 | T.printInt(gcd(1, 6)); 32 | T.printInt(gcd(30, 50)); 33 | T.printInt(gcd(36, 60)); 34 | T.printInt(gcd(60, 36)); 35 | T.printInt(gcd(2740, 1760)); 36 | T.printInt(gcd(1760, 2740)); 37 | } 38 | 39 | public static int fib(int n) { 40 | if (n <= 1) { 41 | return n; 42 | } 43 | return fib(n-1) + fib(n-2); 44 | } 45 | 46 | public static int factorial(int n) { 47 | if (n < 1) { 48 | return 1; 49 | } 50 | return n * factorial(n-1); 51 | } 52 | 53 | public static int sqrt(int n) { 54 | int b = 0; 55 | while (n >= 0) { 56 | n = n - b; 57 | b++; 58 | n = n - b; 59 | } 60 | return b - 1; 61 | } 62 | 63 | public static int gcd(int a, int b) { 64 | int q, r; 65 | while (b > 0) { 66 | q = a / b; 67 | r = a - q * b; 68 | a = b; 69 | b = r; 70 | } 71 | return a; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /javatest/testdata/staticcall3/Test.java: -------------------------------------------------------------------------------- 1 | package staticcall3; 2 | 3 | import testutil.T; 4 | 5 | public class Test { 6 | public static void run(int x) { 7 | // TODO: uncomment a test when long slots are fixed. 8 | 9 | T.printInt(ii_i(1000, 2000)); 10 | //T.printInt(il_i(1000, 2000)); 11 | T.printInt(li_i(1000, 2000)); 12 | T.printLong(ii_l(1000, 2000)); 13 | } 14 | 15 | private static int ii_i(int a1, int a2) { 16 | T.printInt(a1); 17 | T.printInt(a2); 18 | return 23; 19 | } 20 | 21 | private static int il_i(long a1, int a2) { 22 | T.printLong(a1); 23 | T.printInt(a2); 24 | return 1; 25 | } 26 | 27 | private static int li_i(int a1, long a2) { 28 | T.printInt(a1); 29 | T.printLong(a2); 30 | return 2; 31 | } 32 | 33 | private static long ii_l(int a1, int a2) { 34 | T.printInt(a1); 35 | T.printInt(a2); 36 | return 3; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /javatest/testdata/staticfinal/Test.java: -------------------------------------------------------------------------------- 1 | package staticfinal; 2 | 3 | import testutil.T; 4 | 5 | // TODO: implement putstatic and getstatic. 6 | 7 | public class Test { 8 | private static final int SEVEN = 7; 9 | private static final int TEN = SEVEN + 3; 10 | 11 | // private static final int[] INTS = {1, 2, 3}; 12 | 13 | public static void run(int x) { 14 | T.printInt(SEVEN + TEN); 15 | // T.printInt(INTS[0]); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jclass/access_flags.go: -------------------------------------------------------------------------------- 1 | package jclass 2 | 3 | // AccessFlags is a mask of flags used to denote access permissions to and properties 4 | // of this class or interface. 5 | // 6 | // The interpretation of each flag, when set, is specified below: 7 | // ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package. 8 | // ACC_FINAL 0x0010 Declared final; no subclasses allowed. 9 | // ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the invokespecial instruction. 10 | // ACC_INTERFACE 0x0200 Is an interface, not a class. 11 | // ACC_ABSTRACT 0x0400 Declared abstract; must not be instantiated. 12 | // ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code. 13 | // ACC_ANNOTATION 0x2000 Declared as an annotation type. 14 | // ACC_ENUM 0x4000 Declared as an enum type. 15 | // ACC_MODULE 0x8000 Is a module, not a class or interface. 16 | type AccessFlags uint16 17 | 18 | func (af AccessFlags) IsPublic() bool { return af&0x0001 != 0 } 19 | func (af AccessFlags) IsFinal() bool { return af&0x0010 != 0 } 20 | func (af AccessFlags) IsSuper() bool { return af&0x0020 != 0 } 21 | func (af AccessFlags) IsInterface() bool { return af&0x0200 != 0 } 22 | func (af AccessFlags) IsAbstract() bool { return af&0x0400 != 0 } 23 | func (af AccessFlags) IsSynthetic() bool { return af&0x1000 != 0 } 24 | func (af AccessFlags) IsAnnotation() bool { return af&0x2000 != 0 } 25 | func (af AccessFlags) IsEnum() bool { return af&0x4000 != 0 } 26 | func (af AccessFlags) IsModule() bool { return af&0x8000 != 0 } 27 | 28 | // MethodAccessFlags is a mask of flags used to denote access permission to and properties 29 | // of this method. 30 | // 31 | // The interpretation of each flag, when set, is specified below: 32 | // ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package. 33 | // ACC_PRIVATE 0x0002 Declared private; accessible only within the defining class and other classes belonging to the same nest. 34 | // ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses. 35 | // ACC_STATIC 0x0008 Declared static. 36 | // ACC_FINAL 0x0010 Declared final; must not be overridden. 37 | // ACC_SYNCHRONIZED 0x0020 Declared synchronized; invocation is wrapped by a monitor use. 38 | // ACC_BRIDGE 0x0040 A bridge method, generated by the compiler. 39 | // ACC_VARARGS 0x0080 Declared with variable number of arguments. 40 | // ACC_NATIVE 0x0100 Declared native; implemented in a language other than the Java programming language. 41 | // ACC_ABSTRACT 0x0400 Declared abstract; no implementation is provided. 42 | // ACC_STRICT 0x0800 Declared strictfp; floating-point mode is FP-strict. 43 | // ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code. 44 | type MethodAccessFlags uint16 45 | 46 | func (af MethodAccessFlags) IsPublic() bool { return af&0x0001 != 0 } 47 | func (af MethodAccessFlags) IsPrivate() bool { return af&0x0002 != 0 } 48 | func (af MethodAccessFlags) IsProtected() bool { return af&0x0004 != 0 } 49 | func (af MethodAccessFlags) IsStatic() bool { return af&0x0008 != 0 } 50 | func (af MethodAccessFlags) IsFinal() bool { return af&0x0010 != 0 } 51 | func (af MethodAccessFlags) IsSynchronized() bool { return af&0x0020 != 0 } 52 | func (af MethodAccessFlags) IsBridge() bool { return af&0x0040 != 0 } 53 | func (af MethodAccessFlags) IsVarargs() bool { return af&0x0080 != 0 } 54 | func (af MethodAccessFlags) IsNative() bool { return af&0x0100 != 0 } 55 | func (af MethodAccessFlags) IsAbstract() bool { return af&0x0400 != 0 } 56 | func (af MethodAccessFlags) IsStrict() bool { return af&0x0800 != 0 } 57 | func (af MethodAccessFlags) IsSynthetic() bool { return af&0x1000 != 0 } 58 | 59 | type FieldAccessFlags uint16 60 | -------------------------------------------------------------------------------- /jclass/attributes.go: -------------------------------------------------------------------------------- 1 | package jclass 2 | 3 | type Attribute interface { 4 | attribute() 5 | } 6 | 7 | type ( 8 | RawAttribute struct { 9 | NameIndex uint16 10 | Data []byte 11 | } 12 | 13 | CodeAttribute struct { 14 | MaxStack uint16 15 | MaxLocals uint16 16 | Code []byte 17 | ExceptionTable []ExceptionHandler 18 | Attrs []Attribute 19 | } 20 | 21 | StackMapTableAttribute struct { 22 | Frames []StackMapFrame 23 | } 24 | ) 25 | 26 | func (RawAttribute) attribute() {} 27 | func (CodeAttribute) attribute() {} 28 | func (StackMapTableAttribute) attribute() {} 29 | -------------------------------------------------------------------------------- /jclass/class.go: -------------------------------------------------------------------------------- 1 | package jclass 2 | 3 | type Method struct { 4 | AccessFlags MethodAccessFlags 5 | Name string 6 | Descriptor string 7 | Attrs []Attribute 8 | } 9 | 10 | type Field struct { 11 | AccessFlags FieldAccessFlags 12 | Name string 13 | Descriptor string 14 | Attrs []Attribute 15 | } 16 | 17 | type File struct { 18 | Ver Version 19 | Consts []Const 20 | AccessFlags AccessFlags 21 | ThisClassName string 22 | SuperClass uint16 23 | Interfaces []uint16 24 | Fields []Field 25 | Methods []Method 26 | Attrs []Attribute 27 | } 28 | 29 | type Version struct { 30 | Minor uint16 31 | Major uint16 32 | } 33 | 34 | type ExceptionHandler struct { 35 | StartPC uint16 36 | EndPC uint16 37 | HandlerPC uint16 38 | CatchType uint16 39 | } 40 | 41 | type StackMapFrame struct { 42 | Offset uint32 43 | StackDepth uint16 44 | } 45 | -------------------------------------------------------------------------------- /jclass/constants.go: -------------------------------------------------------------------------------- 1 | package jclass 2 | 3 | type Const interface { 4 | constant() 5 | } 6 | 7 | type ( 8 | Utf8Const struct { 9 | Value string 10 | } 11 | 12 | ClassConst struct { 13 | Name string 14 | } 15 | 16 | IntConst struct { 17 | Value int32 18 | } 19 | 20 | LongConst struct { 21 | Value int64 22 | } 23 | 24 | FloatConst struct { 25 | Value float32 26 | } 27 | 28 | DoubleConst struct { 29 | Value float64 30 | } 31 | 32 | FieldrefConst struct { 33 | ClassName string 34 | Name string 35 | Descriptor string 36 | } 37 | 38 | MethodrefConst struct { 39 | ClassName string 40 | Name string 41 | Descriptor string 42 | } 43 | 44 | NameAndTypeConst struct { 45 | Name string 46 | Descriptor string 47 | } 48 | ) 49 | 50 | func (*Utf8Const) constant() {} 51 | func (*IntConst) constant() {} 52 | func (*LongConst) constant() {} 53 | func (*FloatConst) constant() {} 54 | func (*DoubleConst) constant() {} 55 | func (*ClassConst) constant() {} 56 | func (*FieldrefConst) constant() {} 57 | func (*MethodrefConst) constant() {} 58 | func (*NameAndTypeConst) constant() {} 59 | -------------------------------------------------------------------------------- /jclass/decoder.go: -------------------------------------------------------------------------------- 1 | package jclass 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | "math" 9 | ) 10 | 11 | type Decoder struct { 12 | r *bufio.Reader 13 | f *File 14 | 15 | deferred struct { 16 | names map[*string]uint16 17 | names2 map[*string]uint16 18 | descriptors map[*string]uint16 19 | descriptors2 map[*string]uint16 20 | classNames map[*string]uint16 21 | } 22 | } 23 | 24 | func (d *Decoder) Decode(r io.Reader) (*File, error) { 25 | d.r = bufio.NewReader(r) 26 | d.f = &File{} 27 | d.deferred.names = map[*string]uint16{} 28 | d.deferred.names2 = map[*string]uint16{} 29 | d.deferred.descriptors = map[*string]uint16{} 30 | d.deferred.descriptors2 = map[*string]uint16{} 31 | d.deferred.classNames = map[*string]uint16{} 32 | err := d.decode() 33 | return d.f, err 34 | } 35 | 36 | func (d *Decoder) resolveDeferred() { 37 | for s, index := range d.deferred.names { 38 | *s = d.f.Consts[index].(*Utf8Const).Value 39 | } 40 | for s, index := range d.deferred.names2 { 41 | *s = d.f.Consts[index].(*NameAndTypeConst).Name 42 | } 43 | for s, index := range d.deferred.descriptors { 44 | *s = d.f.Consts[index].(*Utf8Const).Value 45 | } 46 | for s, index := range d.deferred.descriptors2 { 47 | *s = d.f.Consts[index].(*NameAndTypeConst).Descriptor 48 | } 49 | for s, index := range d.deferred.classNames { 50 | *s = d.f.Consts[index].(*ClassConst).Name 51 | } 52 | } 53 | 54 | func (d *Decoder) deferNameResolving(index uint16, s *string) { 55 | d.deferred.names[s] = index 56 | } 57 | 58 | func (d *Decoder) deferClassNameResolving(index uint16, s *string) { 59 | d.deferred.classNames[s] = index 60 | } 61 | 62 | func (d *Decoder) deferDescriptorResolving(index uint16, desc *string) { 63 | d.deferred.descriptors[desc] = index 64 | } 65 | 66 | func (d *Decoder) deferNameAndTypeResolving(index uint16, name, desc *string) { 67 | d.deferred.names2[name] = index 68 | d.deferred.descriptors2[desc] = index 69 | } 70 | 71 | func (d *Decoder) decode() error { 72 | steps := []struct { 73 | name string 74 | fn func() error 75 | }{ 76 | {"magic", d.decodeMagic}, 77 | {"version", d.decodeVersion}, 78 | {"constant pool", d.decodeConstantPool}, 79 | {"access flags", d.decodeClassAccessFlags}, 80 | {"this class", d.decodeClassName}, 81 | {"super class", d.makeUint16Decode(&d.f.SuperClass)}, 82 | {"interfaces", d.decodeInterfaces}, 83 | {"fields", d.decodeFields}, 84 | {"methods", d.decodeMethods}, 85 | {"attributes", d.decodeAttributes}, 86 | } 87 | for _, step := range steps { 88 | if err := step.fn(); err != nil { 89 | return fmt.Errorf("decode %s: %w", step.name, err) 90 | } 91 | } 92 | return nil 93 | } 94 | 95 | func (d *Decoder) decodeClassAccessFlags() error { 96 | v, err := d.readUint16() 97 | if err != nil { 98 | return err 99 | } 100 | d.f.AccessFlags = AccessFlags(v) 101 | return nil 102 | } 103 | 104 | func (d *Decoder) decodeClassName() error { 105 | v, err := d.readUint16() 106 | if err != nil { 107 | return err 108 | } 109 | d.f.ThisClassName = d.f.Consts[v].(*ClassConst).Name 110 | return nil 111 | } 112 | 113 | func (d *Decoder) decodeMagic() error { 114 | magic, err := d.readUint32() 115 | if err != nil { 116 | return err 117 | } 118 | if magic != 0xCAFEBABE { 119 | return fmt.Errorf("invalid value (want 0xCAFEBABE, got 0x%X)", magic) 120 | } 121 | return nil 122 | } 123 | 124 | func (d *Decoder) decodeVersion() error { 125 | minor, err := d.readUint16() 126 | if err != nil { 127 | return fmt.Errorf("read minor: %w", err) 128 | } 129 | major, err := d.readUint16() 130 | if err != nil { 131 | return fmt.Errorf("read major: %w", err) 132 | } 133 | d.f.Ver.Minor = minor 134 | d.f.Ver.Major = major 135 | return nil 136 | } 137 | 138 | func (d *Decoder) decodeConstantPool() error { 139 | n, err := d.readUint16() 140 | if err != nil { 141 | return fmt.Errorf("read count: %w", err) 142 | } 143 | cp := make([]Const, n) 144 | i := 1 // Constant at 0 index is undefined 145 | for i < len(cp) { 146 | c, skip, err := d.readConst() 147 | if err != nil { 148 | return fmt.Errorf("const%d: %w", i, err) 149 | } 150 | cp[i] = c 151 | i += skip 152 | } 153 | d.f.Consts = cp 154 | d.resolveDeferred() 155 | return nil 156 | } 157 | 158 | func (d *Decoder) decodeInterfaces() error { 159 | n, err := d.readUint16() 160 | if err != nil { 161 | return fmt.Errorf("read count: %w", err) 162 | } 163 | ifaces := make([]uint16, n) 164 | for i := range ifaces { 165 | index, err := d.readUint16() 166 | if err != nil { 167 | return fmt.Errorf("interface%d: %w", i, err) 168 | } 169 | ifaces[i] = index 170 | } 171 | d.f.Interfaces = ifaces 172 | return nil 173 | } 174 | 175 | func (d *Decoder) decodeFields() error { 176 | n, err := d.readUint16() 177 | if err != nil { 178 | return fmt.Errorf("read count: %w", err) 179 | } 180 | fields := make([]Field, n) 181 | for i := range fields { 182 | field, err := d.readField() 183 | if err != nil { 184 | return fmt.Errorf("field%d: %w", i, err) 185 | } 186 | fields[i] = field 187 | } 188 | d.f.Fields = fields 189 | return nil 190 | } 191 | 192 | func (d *Decoder) decodeMethods() error { 193 | n, err := d.readUint16() 194 | if err != nil { 195 | return fmt.Errorf("read count: %w", err) 196 | } 197 | methods := make([]Method, n) 198 | for i := range methods { 199 | field, err := d.readField() 200 | if err != nil { 201 | return fmt.Errorf("method%d: %w", i, err) 202 | } 203 | methods[i] = Method{ 204 | AccessFlags: MethodAccessFlags(field.AccessFlags), 205 | Name: field.Name, 206 | Descriptor: field.Descriptor, 207 | Attrs: field.Attrs, 208 | } 209 | } 210 | d.f.Methods = methods 211 | return nil 212 | } 213 | 214 | func (d *Decoder) decodeAttributes() error { 215 | attrs, err := d.readAttributes() 216 | if err != nil { 217 | return err 218 | } 219 | d.f.Attrs = attrs 220 | return nil 221 | } 222 | 223 | func (d *Decoder) makeUint16Decode(dst *uint16) func() error { 224 | return func() error { 225 | v, err := d.readUint16() 226 | if err != nil { 227 | return err 228 | } 229 | *dst = v 230 | return nil 231 | } 232 | } 233 | 234 | func (d *Decoder) readExceptionHandler() (ExceptionHandler, error) { 235 | var h ExceptionHandler 236 | startPC, err := d.readUint16() 237 | if err != nil { 238 | return h, fmt.Errorf("read start_pc: %w", err) 239 | } 240 | endPC, err := d.readUint16() 241 | if err != nil { 242 | return h, fmt.Errorf("read end_pc: %w", err) 243 | } 244 | handlerPC, err := d.readUint16() 245 | if err != nil { 246 | return h, fmt.Errorf("read handler_pc: %w", err) 247 | } 248 | catchType, err := d.readUint16() 249 | if err != nil { 250 | return h, fmt.Errorf("read catch_type: %w", err) 251 | } 252 | h.StartPC = startPC 253 | h.EndPC = endPC 254 | h.HandlerPC = handlerPC 255 | h.CatchType = catchType 256 | return h, nil 257 | } 258 | 259 | func (d *Decoder) readAttributes() ([]Attribute, error) { 260 | n, err := d.readUint16() 261 | if err != nil { 262 | return nil, fmt.Errorf("read attrs count: %w", err) 263 | } 264 | attrs := make([]Attribute, n) 265 | for i := range attrs { 266 | attr, err := d.readAttr() 267 | if err != nil { 268 | return nil, fmt.Errorf("attr%d: %w", i, err) 269 | } 270 | attrs[i] = attr 271 | } 272 | return attrs, nil 273 | } 274 | 275 | func (d *Decoder) readAttr() (Attribute, error) { 276 | nameIndex, err := d.readUint16() 277 | if err != nil { 278 | return nil, fmt.Errorf("read name_index: %w", err) 279 | } 280 | 281 | length, err := d.readUint32() 282 | if err != nil { 283 | return nil, fmt.Errorf("read attribute_length: %w", err) 284 | } 285 | 286 | var attr Attribute 287 | switch d.f.Consts[nameIndex].(*Utf8Const).Value { 288 | case "Code": 289 | maxStack, err := d.readUint16() 290 | if err != nil { 291 | return nil, fmt.Errorf("read max_stack: %w", err) 292 | } 293 | maxLocals, err := d.readUint16() 294 | if err != nil { 295 | return nil, fmt.Errorf("read max_locals: %w", err) 296 | } 297 | code, err := d.readByteSlice32() 298 | if err != nil { 299 | return nil, fmt.Errorf("read code: %w", err) 300 | } 301 | handlersCount, err := d.readUint16() 302 | if err != nil { 303 | return nil, fmt.Errorf("read exception_table_length: %v", err) 304 | } 305 | handlers := make([]ExceptionHandler, handlersCount) 306 | for i := range handlers { 307 | h, err := d.readExceptionHandler() 308 | if err != nil { 309 | return nil, fmt.Errorf("handler%d: %w", i, err) 310 | } 311 | handlers[i] = h 312 | } 313 | attrs, err := d.readAttributes() 314 | if err != nil { 315 | return nil, fmt.Errorf("read attributes: %w", err) 316 | } 317 | attr = CodeAttribute{ 318 | MaxStack: maxStack, 319 | MaxLocals: maxLocals, 320 | Code: code, 321 | ExceptionTable: handlers, 322 | Attrs: attrs, 323 | } 324 | 325 | case "StackMapTable": 326 | frames, err := d.readStackMapFrames() 327 | if err != nil { 328 | return nil, fmt.Errorf("read stack map frames: %v", err) 329 | } 330 | attr = StackMapTableAttribute{Frames: frames} 331 | 332 | default: 333 | buf := make([]byte, length) 334 | _, err = io.ReadFull(d.r, buf) 335 | if err != nil { 336 | return nil, fmt.Errorf("read info: %w", err) 337 | } 338 | attr = RawAttribute{ 339 | NameIndex: nameIndex, 340 | Data: buf, 341 | } 342 | } 343 | 344 | return attr, nil 345 | } 346 | 347 | func (d *Decoder) skipVerificationTypes(n int) error { 348 | // We could use verification info at some point in future. 349 | // Right now we skip it completely. 350 | const ( 351 | itemTop = 0 352 | itemInteger = 1 353 | itemFloat = 2 354 | itemDouble = 3 355 | itemLong = 4 356 | itemNull = 5 357 | itemUninitializedThis = 6 358 | itemObject = 7 359 | itemUninitialized = 8 360 | ) 361 | 362 | for i := 0; i < n; i++ { 363 | tag, err := d.r.ReadByte() 364 | if err != nil { 365 | return fmt.Errorf("verification_type_info%d: read tag: %v", i, err) 366 | } 367 | switch tag { 368 | case itemUninitialized, itemObject: 369 | _, err = d.r.Discard(2) 370 | if err != nil { 371 | return fmt.Errorf("verification_type_info%d: tag=%d: %v", i, tag, err) 372 | } 373 | } 374 | } 375 | 376 | return nil 377 | } 378 | 379 | func (d *Decoder) readStackMapFrames() ([]StackMapFrame, error) { 380 | framesCount, err := d.readUint16() 381 | if err != nil { 382 | return nil, fmt.Errorf("read number_of_entries: %v", err) 383 | } 384 | frames := make([]StackMapFrame, framesCount) 385 | offset := uint32(0) 386 | for i := range frames { 387 | tag, err := d.r.ReadByte() 388 | if err != nil { 389 | return nil, fmt.Errorf("frame%d: read type: %v", i, err) 390 | } 391 | 392 | depth := uint16(0) 393 | switch { 394 | case tag <= 63: // 0-63 same_frame 395 | offset += uint32(tag) 396 | case tag <= 127: // 64-127 same_locals_1_stack_item_frame 397 | offset += uint32(tag - 64) 398 | if err := d.skipVerificationTypes(1); err != nil { 399 | return nil, fmt.Errorf("same_locals_1_stack_item_frame: %v", err) 400 | } 401 | depth = 1 402 | case tag == 247: // same_locals_1_stack_item_frame_extended 403 | delta, err := d.readUint16() 404 | if err != nil { 405 | return nil, fmt.Errorf("same_locals_1_stack_item_frame_extended: %v", err) 406 | } 407 | offset += uint32(delta) 408 | depth = 1 409 | case tag >= 248 && tag <= 250: // 248-250 chop_frame 410 | delta, err := d.readUint16() 411 | if err != nil { 412 | return nil, fmt.Errorf("chop_frame: %v", err) 413 | } 414 | offset += uint32(delta) 415 | case tag == 251: // same_frame_extended 416 | delta, err := d.readUint16() 417 | if err != nil { 418 | return nil, fmt.Errorf("same_frame_extended: %v", err) 419 | } 420 | offset += uint32(delta) 421 | case tag >= 252 && tag <= 254: // 252-254 append_frame 422 | delta, err := d.readUint16() 423 | if err != nil { 424 | return nil, fmt.Errorf("append_frame: %v", err) 425 | } 426 | offset += uint32(delta) 427 | localsNum := int(tag - 251) 428 | if err := d.skipVerificationTypes(localsNum); err != nil { 429 | return nil, fmt.Errorf("append_frame: %v", err) 430 | } 431 | case tag == 255: // full_frame 432 | delta, err := d.readUint16() 433 | if err != nil { 434 | return nil, fmt.Errorf("full_frame: %v", err) 435 | } 436 | offset += uint32(delta) 437 | localsNum, err := d.readUint16() 438 | if err != nil { 439 | return nil, fmt.Errorf("full_frame: %v", err) 440 | } 441 | if err := d.skipVerificationTypes(int(localsNum)); err != nil { 442 | return nil, fmt.Errorf("full_frame: %v", err) 443 | } 444 | stackDepth, err := d.readUint16() 445 | if err != nil { 446 | return nil, fmt.Errorf("full_frame: %v", err) 447 | } 448 | if err := d.skipVerificationTypes(int(stackDepth)); err != nil { 449 | return nil, fmt.Errorf("full_frame: %v", err) 450 | } 451 | depth = stackDepth 452 | default: 453 | return nil, fmt.Errorf("unexpected tag: %d", tag) 454 | } 455 | 456 | if i != 0 { 457 | offset++ 458 | } 459 | 460 | frame := &frames[i] 461 | frame.Offset = offset 462 | frame.StackDepth = depth 463 | } 464 | 465 | return frames, nil 466 | } 467 | 468 | func (d *Decoder) readField() (Field, error) { 469 | var f Field 470 | accessFlags, err := d.readUint16() 471 | if err != nil { 472 | return f, fmt.Errorf("read access_flags: %w", err) 473 | } 474 | nameIndex, err := d.readUint16() 475 | if err != nil { 476 | return f, fmt.Errorf("read name_index: %w", err) 477 | } 478 | descriptorIndex, err := d.readUint16() 479 | if err != nil { 480 | return f, fmt.Errorf("read descriptor_index: %w", err) 481 | } 482 | attrs, err := d.readAttributes() 483 | if err != nil { 484 | return f, err 485 | } 486 | f.AccessFlags = FieldAccessFlags(accessFlags) 487 | f.Name = d.f.Consts[nameIndex].(*Utf8Const).Value 488 | f.Descriptor = d.f.Consts[descriptorIndex].(*Utf8Const).Value 489 | f.Attrs = attrs 490 | return f, nil 491 | } 492 | 493 | func (d *Decoder) readConst() (Const, int, error) { 494 | tag, err := d.r.ReadByte() 495 | if err != nil { 496 | return nil, 0, fmt.Errorf("read tag: %w", err) 497 | } 498 | 499 | var c Const 500 | skip := 1 // Almost all consts occupy 1 index 501 | switch tag { 502 | case 1: 503 | buf, err := d.readByteSlice16() 504 | if err != nil { 505 | return nil, 0, fmt.Errorf("read bytes: %w", err) 506 | } 507 | c = &Utf8Const{Value: string(buf)} 508 | case 3: 509 | v, err := d.readUint32() 510 | if err != nil { 511 | return nil, 0, fmt.Errorf("read bytes: %w", err) 512 | } 513 | c = &IntConst{Value: int32(v)} 514 | case 5: 515 | v, err := d.readUint64() 516 | if err != nil { 517 | return nil, 0, fmt.Errorf("read bytes: %w", err) 518 | } 519 | c = &LongConst{Value: int64(v)} 520 | skip = 2 521 | case 6: 522 | v, err := d.readUint64() 523 | if err != nil { 524 | return nil, 0, fmt.Errorf("read bytes: %w", err) 525 | } 526 | c = &DoubleConst{Value: math.Float64frombits(v)} 527 | skip = 2 528 | case 7: 529 | nameIndex, err := d.readUint16() 530 | if err != nil { 531 | return nil, 0, fmt.Errorf("read name_index: %w", err) 532 | } 533 | cc := &ClassConst{} 534 | d.deferNameResolving(nameIndex, &cc.Name) 535 | c = cc 536 | case 9, 10: 537 | classIndex, err := d.readUint16() 538 | if err != nil { 539 | return nil, 0, fmt.Errorf("read class_index: %w", err) 540 | } 541 | nameAndTypeIndex, err := d.readUint16() 542 | if err != nil { 543 | return nil, 0, fmt.Errorf("read name_and_type_index: %w", err) 544 | } 545 | switch tag { 546 | case 9: 547 | fc := &FieldrefConst{} 548 | d.deferClassNameResolving(classIndex, &fc.ClassName) 549 | d.deferNameAndTypeResolving(nameAndTypeIndex, &fc.Name, &fc.Descriptor) 550 | c = fc 551 | case 10: 552 | mc := &MethodrefConst{} 553 | d.deferClassNameResolving(classIndex, &mc.ClassName) 554 | d.deferNameAndTypeResolving(nameAndTypeIndex, &mc.Name, &mc.Descriptor) 555 | c = mc 556 | } 557 | case 12: 558 | nameIndex, err := d.readUint16() 559 | if err != nil { 560 | return nil, 0, fmt.Errorf("read name_index: %w", err) 561 | } 562 | descriptorIndex, err := d.readUint16() 563 | if err != nil { 564 | return nil, 0, fmt.Errorf("read descriptor_index: %w", err) 565 | } 566 | ntc := &NameAndTypeConst{} 567 | d.deferNameResolving(nameIndex, &ntc.Name) 568 | d.deferDescriptorResolving(descriptorIndex, &ntc.Descriptor) 569 | c = ntc 570 | default: 571 | return nil, 0, fmt.Errorf("unexpected tag: %d", tag) 572 | } 573 | 574 | return c, skip, nil 575 | } 576 | 577 | func (d *Decoder) readUint64() (uint64, error) { 578 | var buf [8]byte 579 | n, err := d.r.Read(buf[:]) 580 | if err != nil { 581 | return 0, err 582 | } 583 | if n != 8 { 584 | return 0, fmt.Errorf("not enough input bytes (want 8, got %d)", n) 585 | } 586 | v := binary.BigEndian.Uint64(buf[:]) 587 | return v, nil 588 | } 589 | 590 | func (d *Decoder) readUint32() (uint32, error) { 591 | var buf [4]byte 592 | n, err := d.r.Read(buf[:]) 593 | if err != nil { 594 | return 0, err 595 | } 596 | if n != 4 { 597 | return 0, fmt.Errorf("not enough input bytes (want 4, got %d)", n) 598 | } 599 | v := binary.BigEndian.Uint32(buf[:]) 600 | return v, nil 601 | } 602 | 603 | func (d *Decoder) readUint16() (uint16, error) { 604 | var buf [2]byte 605 | n, err := d.r.Read(buf[:]) 606 | if err != nil { 607 | return 0, err 608 | } 609 | if n != 2 { 610 | return 0, fmt.Errorf("not enough input bytes (want 2, got %d)", n) 611 | } 612 | v := binary.BigEndian.Uint16(buf[:]) 613 | return v, nil 614 | } 615 | 616 | func (d *Decoder) readByteSlice32() ([]byte, error) { 617 | length, err := d.readUint32() 618 | if err != nil { 619 | return nil, fmt.Errorf("read length: %w", err) 620 | } 621 | buf := make([]byte, length) 622 | _, err = io.ReadFull(d.r, buf) 623 | return buf, err 624 | } 625 | 626 | func (d *Decoder) readByteSlice16() ([]byte, error) { 627 | length, err := d.readUint16() 628 | if err != nil { 629 | return nil, fmt.Errorf("read length: %w", err) 630 | } 631 | buf := make([]byte, length) 632 | _, err = io.ReadFull(d.r, buf) 633 | return buf, err 634 | } 635 | -------------------------------------------------------------------------------- /jclass/descriptor.go: -------------------------------------------------------------------------------- 1 | package jclass 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type MethodDescriptor string 9 | 10 | func (d MethodDescriptor) ReturnType() DescriptorType { 11 | s := string(d) 12 | start := strings.LastIndexByte(s, ')') + 1 13 | var result DescriptorType 14 | walkDescriptor(s[start:], func(typ DescriptorType) { 15 | result = typ 16 | }) 17 | return result 18 | } 19 | 20 | func (d MethodDescriptor) WalkParams(visit func(typ DescriptorType)) { 21 | s := string(d) 22 | walkDescriptor(s[len("("):strings.IndexByte(s, ')')], visit) 23 | } 24 | 25 | func (d MethodDescriptor) SignatureString(name string) string { 26 | var params []string 27 | d.WalkParams(func(typ DescriptorType) { 28 | params = append(params, typ.String()) 29 | }) 30 | return fmt.Sprintf("%s %s(%s)", 31 | d.ReturnType().String(), name, strings.Join(params, ", ")) 32 | } 33 | 34 | func walkDescriptor(s string, visit func(typ DescriptorType)) { 35 | var typ DescriptorType 36 | i := 0 37 | for i < len(s) { 38 | switch s[i] { 39 | case ')': 40 | return 41 | case 'L': 42 | end := strings.IndexByte(s[i:], ';') + i 43 | typ.Kind = 'L' 44 | typ.Name = s[i+1 : end] 45 | visit(typ) 46 | i = end + len(";") 47 | typ.Dims = 0 48 | case '[': 49 | typ.Dims++ 50 | i++ 51 | default: 52 | // Everything else is treated as a single-letter 53 | // type descriptor part. 54 | typ.Kind = s[i] 55 | visit(typ) 56 | i++ 57 | typ.Dims = 0 58 | } 59 | } 60 | } 61 | 62 | type FieldDescriptor string 63 | 64 | func (d FieldDescriptor) GetType() DescriptorType { 65 | var result DescriptorType 66 | walkDescriptor(string(d), func(typ DescriptorType) { 67 | result = typ 68 | }) 69 | return result 70 | } 71 | 72 | type DescriptorType struct { 73 | Name string 74 | Kind byte 75 | Dims int 76 | } 77 | 78 | func (typ DescriptorType) IsReference() bool { 79 | return typ.Kind == 'L' 80 | } 81 | 82 | func (typ DescriptorType) String() string { 83 | var p string 84 | switch typ.Kind { 85 | case 'B': 86 | p = "byte" 87 | case 'C': 88 | p = "char" 89 | case 'D': 90 | p = "double" 91 | case 'F': 92 | p = "float" 93 | case 'I': 94 | p = "int" 95 | case 'J': 96 | p = "long" 97 | case 'S': 98 | p = "short" 99 | case 'Z': 100 | p = "boolean" 101 | case 'V': 102 | p = "void" 103 | case 'L': 104 | p = typ.Name 105 | } 106 | if typ.Dims != 0 { 107 | p += strings.Repeat("[]", typ.Dims) 108 | } 109 | return p 110 | } 111 | -------------------------------------------------------------------------------- /jclass/descriptor_test.go: -------------------------------------------------------------------------------- 1 | package jclass 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMethodDescriptor(t *testing.T) { 8 | tests := []struct { 9 | sym string 10 | raw string 11 | pretty string 12 | }{ 13 | {"", "()V", "void ()"}, 14 | {"", "(C)C", "char (char)"}, 15 | {"", "(J)S", "short (long)"}, 16 | {"f", "()V", "void f()"}, 17 | {"f", "(I)I", "int f(int)"}, 18 | {"ff", "(B[[[B)F", "float ff(byte, byte[][][])"}, 19 | {"f", "(IZ)Z", "boolean f(int, boolean)"}, 20 | {"f", "(LFoo;)[[I", "int[][] f(Foo)"}, 21 | {"", "(LA;LB;)LC;", "C (A, B)"}, 22 | {"", "(LAa;LBb;ILCc;)LDd;", "Dd (Aa, Bb, int, Cc)"}, 23 | {"", "(DLFoo;I[LBar;)V", "void (double, Foo, int, Bar[])"}, 24 | } 25 | 26 | for _, test := range tests { 27 | d := MethodDescriptor(test.raw) 28 | have := d.SignatureString(test.sym) 29 | want := test.pretty 30 | if have != want { 31 | t.Errorf("%q result mismatch:\nhave: %s\nwant: %s", test.raw, have, want) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /jclass/utf.go: -------------------------------------------------------------------------------- 1 | package jclass 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "unicode/utf16" 7 | ) 8 | 9 | func decodeUtf16(s []byte) (string, error) { 10 | // Not very efficient, but correct. Can speed it up later. 11 | pairs := make([]uint16, len(s)/2) 12 | if err := binary.Read(bytes.NewReader(s), binary.BigEndian, &pairs); err != nil { 13 | return "", err 14 | } 15 | return string(utf16.Decode(pairs)), nil 16 | } 17 | -------------------------------------------------------------------------------- /jdeps/jdeps.go: -------------------------------------------------------------------------------- 1 | package jdeps 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/quasilyte/go-jdk/jclass" 7 | ) 8 | 9 | // ClassDependencies returns a list of package names that given class depends on. 10 | // The list is not sorted. 11 | func ClassDependencies(c *jclass.File) []string { 12 | visitor := depsFinder{map[string]struct{}{}, c} 13 | visitor.addClass(c.SuperClass) 14 | visitor.addClasses(c.Interfaces) 15 | visitor.walkConsts() 16 | visitor.walkFields() 17 | visitor.walkMethods() 18 | // TODO: deal with inner classes 19 | return visitor.keys() 20 | } 21 | 22 | type depsFinder struct { 23 | deps map[string]struct{} 24 | classFile *jclass.File 25 | } 26 | 27 | func (f *depsFinder) keys() []string { 28 | keys := make([]string, 0, len(f.deps)) 29 | for k := range f.deps { 30 | keys = append(keys, k) 31 | } 32 | return keys 33 | } 34 | 35 | func (f *depsFinder) walkConsts() { 36 | for _, c := range f.classFile.Consts { 37 | switch c := c.(type) { 38 | case *jclass.MethodrefConst: 39 | f.addDependency(c.ClassName) 40 | f.walkMethodDescriptor(c.Descriptor) 41 | } 42 | } 43 | } 44 | 45 | func (f *depsFinder) addDependency(d string) { 46 | i := strings.LastIndexByte(d, '/') 47 | if i != -1 { 48 | f.deps[d[:i]] = struct{}{} 49 | } 50 | } 51 | 52 | func (f *depsFinder) addClass(i uint16) { 53 | if i != 0 { 54 | if class, ok := f.classFile.Consts[i].(*jclass.ClassConst); ok { 55 | f.addDependency(class.Name) 56 | } 57 | } 58 | } 59 | 60 | func (f *depsFinder) addClasses(indices []uint16) { 61 | for _, i := range indices { 62 | f.addClass(i) 63 | } 64 | } 65 | 66 | func (f *depsFinder) addType(typ jclass.DescriptorType) { 67 | if typ.IsReference() { 68 | f.addDependency(typ.Name) 69 | } 70 | } 71 | 72 | func (f *depsFinder) walkFields() { 73 | for _, field := range f.classFile.Fields { 74 | // TODO: walk attributes 75 | desc := jclass.FieldDescriptor(field.Descriptor) 76 | typ := desc.GetType() 77 | f.addType(typ) 78 | } 79 | } 80 | 81 | func (f *depsFinder) walkMethods() { 82 | for _, m := range f.classFile.Methods { 83 | // TODO: walk attributes 84 | f.walkMethodDescriptor(m.Descriptor) 85 | } 86 | } 87 | 88 | func (f *depsFinder) walkMethodDescriptor(s string) { 89 | desc := jclass.MethodDescriptor(s) 90 | desc.WalkParams(func(typ jclass.DescriptorType) { 91 | f.addType(typ) 92 | }) 93 | f.addType(desc.ReturnType()) 94 | } 95 | -------------------------------------------------------------------------------- /jit/compiler/x64/compiler.go: -------------------------------------------------------------------------------- 1 | package x64 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "unsafe" 7 | 8 | "github.com/quasilyte/go-jdk/ir" 9 | "github.com/quasilyte/go-jdk/jclass" 10 | "github.com/quasilyte/go-jdk/jit" 11 | "github.com/quasilyte/go-jdk/jit/x64" 12 | "github.com/quasilyte/go-jdk/symbol" 13 | "github.com/quasilyte/go-jdk/vmdat" 14 | ) 15 | 16 | // Compiler implements a class compiler for x86-64 (amd64) architecture. 17 | type Compiler struct { 18 | ctx jit.Context 19 | 20 | packageID uint64 21 | classID uint64 22 | methodID uint64 23 | 24 | asm *x64.Assembler 25 | relocs []relocation 26 | methodRelocs int 27 | method *ir.Method 28 | } 29 | 30 | type relocation struct { 31 | sourceID symbol.ID 32 | targetID symbol.ID 33 | targetOffset int 34 | } 35 | 36 | // NewCompiler returns a compiler for amd64 platform. 37 | func NewCompiler() *Compiler { 38 | return &Compiler{ 39 | asm: x64.NewAssembler(), 40 | relocs: make([]relocation, 0, 64), 41 | } 42 | } 43 | 44 | func (cl *Compiler) Compile(ctx jit.Context, packages []*ir.Package) error { 45 | cl.ctx = ctx 46 | cl.relocs = cl.relocs[:0] 47 | for _, p := range packages { 48 | cl.packageID = uint64(p.Out.ID) 49 | if err := cl.compilePackage(p); err != nil { 50 | return fmt.Errorf("package %s: %v", p.Out.Name, err) 51 | } 52 | } 53 | cl.link() 54 | return nil 55 | } 56 | 57 | func (cl *Compiler) link() { 58 | for _, rel := range cl.relocs { 59 | dstMethod := cl.getMethodByID(rel.targetID) 60 | srcMethod := cl.getMethodByID(rel.sourceID) 61 | addr := uint64(uintptr(unsafe.Pointer(&srcMethod.Code[0]))) 62 | binary.LittleEndian.PutUint64(dstMethod.Code[rel.targetOffset:], addr) 63 | } 64 | } 65 | 66 | func (cl *Compiler) compilePackage(p *ir.Package) error { 67 | for i := range p.Classes { 68 | cl.classID = uint64(i) 69 | c := &p.Classes[i] 70 | for j := range c.Methods { 71 | cl.methodID = uint64(j) 72 | m := &c.Methods[j] 73 | if err := cl.compileMethod(m); err != nil { 74 | // TODO: print descriptor in a more pretty way. 75 | return fmt.Errorf("%s.%s%s: %v", 76 | c.Name, m.Out.Name, m.Out.Descriptor, err) 77 | } 78 | } 79 | } 80 | return nil 81 | } 82 | 83 | func (cl *Compiler) compileMethod(m *ir.Method) error { 84 | if len(m.Code) == 0 { 85 | return nil 86 | } 87 | 88 | cl.asm.Reset() 89 | cl.methodRelocs = 0 90 | cl.method = m 91 | 92 | for i, inst := range m.Code { 93 | if inst.Flags.IsJumpTarget() { 94 | cl.asm.Label(int64(i)) 95 | } 96 | if !cl.assembleInst(inst) { 97 | return fmt.Errorf("can't assemble: %s", inst) 98 | } 99 | } 100 | 101 | length := cl.asm.Link() 102 | if length == 0 { 103 | return fmt.Errorf("no machine code is generated") 104 | } 105 | code, err := cl.ctx.Mmap.AllocateExecutable(length) 106 | if err != nil { 107 | return fmt.Errorf("mmap(%d): %v", length, err) 108 | } 109 | cl.asm.Put(code) 110 | m.Out.Code = code 111 | 112 | relocs := cl.relocs[len(cl.relocs)-cl.methodRelocs:] 113 | for i := range relocs { 114 | rel := &relocs[i] 115 | const mov64width = 2 116 | rel.targetOffset = int(cl.asm.OffsetOf(rel.targetOffset) + mov64width) 117 | } 118 | 119 | return nil 120 | } 121 | 122 | func (cl *Compiler) assembleInst(inst ir.Inst) bool { 123 | asm := cl.asm 124 | 125 | var a1, a2 ir.Arg 126 | dst := inst.Dst 127 | if len(inst.Args) > 0 { 128 | a1 = inst.Args[0] 129 | } 130 | if len(inst.Args) > 1 { 131 | a2 = inst.Args[1] 132 | } 133 | 134 | switch inst.Kind { 135 | case ir.InstJumpGtEq: 136 | asm.Jge(a1.Value) 137 | case ir.InstJumpGt: 138 | asm.Jgt(a1.Value) 139 | case ir.InstJumpLtEq: 140 | asm.Jle(a1.Value) 141 | case ir.InstJumpLt: 142 | asm.Jlt(a1.Value) 143 | case ir.InstJumpNotEqual: 144 | asm.Jne(a1.Value) 145 | case ir.InstJump: 146 | asm.Jmp(a1.Value) 147 | 148 | case ir.InstArrayLen: 149 | asm.MovqMemReg(x64.RSI, x64.RAX, ptrDisp(a1)) 150 | asm.MovlMemReg(x64.RAX, x64.RAX, 16) 151 | asm.MovlRegMem(x64.RAX, x64.RSI, scalarDisp(dst)) 152 | case ir.InstIntArrayGet: 153 | aref := a1 154 | index := a2 155 | asm.MovqMemReg(x64.RSI, x64.RAX, ptrDisp(aref)) 156 | asm.MovqMemReg(x64.RAX, x64.RAX, 8) 157 | switch index.Kind { 158 | case ir.ArgIntConst: 159 | asm.MovlMemReg(x64.RAX, x64.RAX, int32(index.Value)*4) 160 | case ir.ArgReg: 161 | asm.MovlMemReg(x64.RSI, x64.RCX, scalarDisp(index)) 162 | asm.MovlMemindexReg(x64.RAX, x64.RAX, x64.RCX) 163 | } 164 | asm.MovlRegMem(x64.RAX, x64.RSI, scalarDisp(dst)) 165 | case ir.InstIntArraySet: 166 | aref := a1 167 | index := a2 168 | v := inst.Args[2] 169 | asm.MovqMemReg(x64.RSI, x64.RAX, ptrDisp(aref)) 170 | asm.MovqMemReg(x64.RAX, x64.RAX, 8) 171 | switch { 172 | case v.Kind == ir.ArgIntConst && index.Kind == ir.ArgIntConst: 173 | asm.MovlConstMem(v.Value, x64.RAX, int32(index.Value)*4) 174 | case v.Kind == ir.ArgReg && index.Kind == ir.ArgIntConst: 175 | asm.MovlMemReg(x64.RSI, x64.RCX, scalarDisp(v)) 176 | asm.MovlRegMem(x64.RCX, x64.RAX, int32(index.Value)*4) 177 | case v.Kind == ir.ArgIntConst && index.Kind == ir.ArgReg: 178 | asm.MovlMemReg(x64.RSI, x64.RCX, scalarDisp(index)) 179 | asm.MovlConstMemindex(v.Value, x64.RAX, x64.RCX) 180 | case v.Kind == ir.ArgReg && index.Kind == ir.ArgReg: 181 | asm.MovlMemReg(x64.RSI, x64.R8, scalarDisp(v)) 182 | asm.MovlMemReg(x64.RSI, x64.RCX, scalarDisp(index)) 183 | asm.MovlRegMemindex(x64.R8, x64.RAX, x64.RCX) 184 | default: 185 | return false 186 | } 187 | case ir.InstNewIntArray: 188 | fnAddr := cl.ctx.Funcs.NewIntArray 189 | ok := cl.assembleCallGo(uintptr(fnAddr), "($I)[I", inst.Dst, inst.Args) 190 | if !ok { 191 | return false 192 | } 193 | 194 | case ir.InstAload: 195 | switch a1.Kind { 196 | case ir.ArgReg: 197 | asm.MovqMemReg(x64.RSI, x64.RAX, ptrDisp(a1)) 198 | asm.MovqRegMem(x64.RAX, x64.RSI, ptrDisp(dst)) 199 | default: 200 | return false 201 | } 202 | case ir.InstLload: 203 | switch a1.Kind { 204 | case ir.ArgReg: 205 | asm.MovqMemReg(x64.RSI, x64.RAX, scalarDisp(a1)) 206 | asm.MovqRegMem(x64.RAX, x64.RSI, scalarDisp(dst)) 207 | case ir.ArgIntConst: 208 | if !fits32bit(a1.Value) { 209 | return false 210 | } 211 | asm.MovqConst32Mem(int32(a1.Value), x64.RSI, scalarDisp(dst)) 212 | default: 213 | return false 214 | } 215 | 216 | case ir.InstIload: 217 | switch a1.Kind { 218 | case ir.ArgReg: 219 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 220 | asm.MovlRegMem(x64.RAX, x64.RSI, regDisp(dst)) 221 | case ir.ArgIntConst: 222 | asm.MovlConstMem(a1.Value, x64.RSI, regDisp(dst)) 223 | default: 224 | return false 225 | } 226 | 227 | case ir.InstIneg: 228 | if a1 == dst { 229 | asm.NeglMem(x64.RSI, regDisp(a1)) 230 | } else { 231 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 232 | asm.NeglReg(x64.RAX) 233 | asm.MovlRegMem(x64.RAX, x64.RSI, regDisp(dst)) 234 | } 235 | case ir.InstLneg: 236 | if a1 == dst { 237 | asm.NegqMem(x64.RSI, regDisp(a1)) 238 | } else { 239 | asm.MovqMemReg(x64.RSI, x64.RAX, regDisp(a1)) 240 | asm.NegqReg(x64.RAX) 241 | asm.MovqRegMem(x64.RAX, x64.RSI, regDisp(dst)) 242 | } 243 | 244 | case ir.InstIcmp: 245 | switch { 246 | case a1.Kind == ir.ArgReg && a2.Kind == ir.ArgIntConst: 247 | asm.CmplConstMem(a2.Value, x64.RSI, regDisp(a1)) 248 | case a1.Kind == ir.ArgReg && a2.Kind == ir.ArgReg: 249 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 250 | asm.CmplRegMem(x64.RAX, x64.RSI, regDisp(a2)) 251 | default: 252 | return false 253 | } 254 | case ir.InstLcmp: 255 | switch { 256 | case a1.Kind == ir.ArgReg && a2.Kind == ir.ArgIntConst: 257 | asm.CmpqConstMem(a1.Value, x64.RSI, regDisp(a1)) 258 | default: 259 | return false 260 | } 261 | 262 | case ir.InstIret: 263 | switch a1.Kind { 264 | case ir.ArgReg: 265 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 266 | case ir.ArgIntConst: 267 | asm.MovlConstReg(a1.Value, x64.RAX) 268 | default: 269 | return false 270 | } 271 | asm.JmpMem(x64.RSI, -16) 272 | case ir.InstAret: 273 | if a1.Kind != ir.ArgReg { 274 | return false 275 | } 276 | asm.MovqMemReg(x64.RSI, x64.RAX, ptrDisp(a1)) 277 | asm.JmpMem(x64.RSI, -16) 278 | case ir.InstLret: 279 | switch a1.Kind { 280 | case ir.ArgReg: 281 | asm.MovqMemReg(x64.RSI, x64.RAX, scalarDisp(a1)) 282 | case ir.ArgIntConst: 283 | if !fits32bit(a1.Value) { 284 | return false 285 | } 286 | asm.MovqConstReg(a1.Value, x64.RAX) 287 | } 288 | asm.JmpMem(x64.RSI, -16) 289 | case ir.InstRet: 290 | asm.JmpMem(x64.RSI, -16) 291 | 292 | case ir.InstCallGo: 293 | sym := inst.Args[0].SymbolID() 294 | pkg := cl.ctx.State.Packages[sym.PackageIndex()] 295 | class := pkg.Classes[sym.ClassIndex()] 296 | method := class.Methods[sym.MemberIndex()] 297 | key := fmt.Sprintf("%s/%s.%s", pkg.Name, class.Name, method.Name) 298 | fnAddr := cl.ctx.State.GoFuncs[key] 299 | return cl.assembleCallGo(fnAddr, method.Descriptor, inst.Dst, inst.Args[1:]) 300 | 301 | case ir.InstCallStatic: 302 | return cl.assembleCallStatic(inst) 303 | 304 | case ir.InstIsub: 305 | // We use negated argument for AddlConstMem for sub with constants. 306 | if a1 == dst { 307 | if a2.Kind == ir.ArgIntConst { 308 | asm.AddlConstMem(-a2.Value, x64.RSI, regDisp(dst)) 309 | } else { 310 | return false 311 | } 312 | } else { 313 | switch { 314 | case a2.Kind == ir.ArgIntConst: 315 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 316 | asm.AddlConstReg(-a2.Value, x64.RAX) 317 | asm.MovlRegMem(x64.RAX, x64.RSI, regDisp(dst)) 318 | case a2.Kind == ir.ArgReg: 319 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 320 | asm.SublMemReg(x64.RSI, x64.RAX, regDisp(a2)) 321 | asm.MovlRegMem(x64.RAX, x64.RSI, regDisp(dst)) 322 | default: 323 | return false 324 | } 325 | } 326 | case ir.InstIadd: 327 | if a1 == dst { 328 | if a2.Kind == ir.ArgIntConst { 329 | asm.AddlConstMem(a2.Value, x64.RSI, regDisp(dst)) 330 | } else { 331 | return false 332 | } 333 | } else { 334 | switch { 335 | case a2.Kind == ir.ArgIntConst: 336 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 337 | asm.AddlConstReg(a2.Value, x64.RAX) 338 | asm.MovlRegMem(x64.RAX, x64.RSI, regDisp(dst)) 339 | case a2.Kind == ir.ArgReg: 340 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 341 | asm.AddlMemReg(x64.RSI, x64.RAX, regDisp(a2)) 342 | asm.MovlRegMem(x64.RAX, x64.RSI, regDisp(dst)) 343 | default: 344 | return false 345 | } 346 | } 347 | case ir.InstImul: 348 | switch { 349 | case a2.Kind == ir.ArgReg: 350 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 351 | asm.ImullMemReg(x64.RSI, x64.RAX, regDisp(a2)) 352 | asm.MovlRegMem(x64.RAX, x64.RSI, regDisp(dst)) 353 | default: 354 | return false 355 | } 356 | case ir.InstIdiv: 357 | switch { 358 | case a1.Kind == ir.ArgReg && a2.Kind == ir.ArgReg: 359 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 360 | asm.Cdq() 361 | asm.IdivlMem(x64.RSI, regDisp(a2)) 362 | asm.MovlRegMem(x64.RAX, x64.RSI, regDisp(dst)) 363 | case a1.Kind == ir.ArgReg && a2.Kind == ir.ArgIntConst: 364 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 365 | asm.Cdq() 366 | asm.MovlConstReg(a2.Value, x64.RCX) 367 | asm.IdivlReg(x64.RCX) 368 | asm.MovlRegMem(x64.RAX, x64.RSI, regDisp(dst)) 369 | default: 370 | return false 371 | } 372 | 373 | case ir.InstConvI2L: 374 | asm.MovlqsxMemReg(x64.RSI, x64.RAX, regDisp(a1)) 375 | asm.MovqRegMem(x64.RAX, x64.RSI, regDisp(dst)) 376 | case ir.InstConvI2B: 377 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(a1)) 378 | asm.MovbRegMem(x64.RAX, x64.RSI, regDisp(dst)) 379 | default: 380 | return false 381 | } 382 | 383 | return true 384 | } 385 | 386 | func (cl *Compiler) assembleCallStatic(inst ir.Inst) bool { 387 | asm := cl.asm 388 | 389 | // Frame size is 16 bytes per every stack slot plus 390 | // one extra slot to store the return address. 391 | frameSize := cl.method.Out.FrameSlots*16 + 16 392 | sym := inst.Args[0].SymbolID() 393 | pkg := cl.ctx.State.Packages[sym.PackageIndex()] 394 | class := pkg.Classes[sym.ClassIndex()] 395 | method := class.Methods[sym.MemberIndex()] 396 | i := 1 397 | failed := false 398 | signature := jclass.MethodDescriptor(method.Descriptor) 399 | signature.WalkParams(func(typ jclass.DescriptorType) { 400 | arg := inst.Args[i] 401 | disp := int32(frameSize + (i-1)*16) 402 | switch { 403 | case typ.Dims != 0: 404 | if arg.Kind != ir.ArgReg { 405 | failed = true 406 | return 407 | } 408 | asm.MovqMemReg(x64.RSI, x64.RAX, ptrDisp(arg)) 409 | asm.MovqRegMem(x64.RAX, x64.RSI, disp+8) 410 | case typ.Kind == 'I': 411 | switch arg.Kind { 412 | case ir.ArgIntConst: 413 | asm.MovlConstMem(arg.Value, x64.RSI, disp) 414 | case ir.ArgReg: 415 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(arg)) 416 | asm.MovlRegMem(x64.RAX, x64.RSI, disp) 417 | default: 418 | failed = true 419 | } 420 | case typ.Kind == 'J': 421 | switch arg.Kind { 422 | case ir.ArgIntConst: 423 | if !fits32bit(arg.Value) { 424 | failed = true 425 | } 426 | asm.MovqConst32Mem(int32(arg.Value), x64.RSI, disp) 427 | case ir.ArgReg: 428 | asm.MovqMemReg(x64.RSI, x64.RAX, regDisp(arg)) 429 | asm.MovqRegMem(x64.RAX, x64.RSI, disp) 430 | default: 431 | failed = true 432 | } 433 | default: 434 | failed = true 435 | } 436 | i++ 437 | }) 438 | if failed { 439 | return false 440 | } 441 | 442 | asm.AddqConstReg(int64(frameSize), x64.RSI) 443 | { 444 | // The magic disp=16 is a width of instructions that 445 | // follow lea inside this block. 446 | asm.Raw(0x48, 0x8d, 0x05, 0x10, 0, 0, 0) // lea rax, [rip+16] 447 | asm.MovqRegMem(x64.RAX, x64.RSI, -16) 448 | index := asm.MovqFixup64Reg(x64.RAX) 449 | asm.JmpReg(x64.RAX) 450 | cl.pushReloc(inst.Args[0].SymbolID(), index) 451 | } 452 | asm.AddqConstReg(int64(-frameSize), x64.RSI) 453 | if inst.Dst.Kind != 0 { 454 | typ := signature.ReturnType() 455 | switch { 456 | case typ.Dims != 0: 457 | asm.MovqRegMem(x64.RAX, x64.RSI, ptrDisp(inst.Dst)) 458 | case typ.Kind == 'I': 459 | asm.MovlRegMem(x64.RAX, x64.RSI, regDisp(inst.Dst)) 460 | case typ.Kind == 'J': 461 | asm.MovqRegMem(x64.RAX, x64.RSI, regDisp(inst.Dst)) 462 | default: 463 | return false 464 | } 465 | } 466 | 467 | return true 468 | } 469 | 470 | func (cl *Compiler) assembleCallGo(fnAddr uintptr, desc string, dst ir.Arg, args []ir.Arg) bool { 471 | // TODO: refactor and optimize. 472 | 473 | const ( 474 | arg0offset = -96 475 | gocallOffset = 73 476 | frameSize = 96 477 | tmp0offset = 24 478 | envOffset = 16 479 | ) 480 | 481 | asm := cl.asm // Just for convenience 482 | 483 | offset := 0 484 | i := 0 485 | failed := false 486 | signature := jclass.MethodDescriptor(desc) 487 | 488 | signature.WalkParams(func(typ jclass.DescriptorType) { 489 | arg := args[i] 490 | switch { 491 | case typ.Dims != 0: 492 | if rem := offset % 8; rem != 0 { 493 | offset += rem 494 | } 495 | if arg.Kind != ir.ArgReg { 496 | failed = true 497 | return 498 | } 499 | asm.MovqMemReg(x64.RSI, x64.RAX, ptrDisp(arg)) 500 | asm.MovqRegMem(x64.RAX, x64.RBP, int32(arg0offset+offset)) 501 | offset += 8 502 | case typ.Kind == '$': 503 | // Dollar ($) is our special marker for env argument. 504 | if rem := offset % 8; rem != 0 { 505 | offset += rem 506 | } 507 | asm.MovqMemReg(x64.RBP, x64.RAX, envOffset) 508 | asm.MovqRegMem(x64.RAX, x64.RBP, int32(arg0offset+offset)) 509 | offset += 8 510 | case typ.Kind == 'I': 511 | if rem := offset % 4; rem != 0 { 512 | offset += rem 513 | } 514 | switch arg.Kind { 515 | case ir.ArgIntConst: 516 | asm.MovlConstMem(arg.Value, x64.RBP, int32(arg0offset+offset)) 517 | case ir.ArgReg: 518 | asm.MovlMemReg(x64.RSI, x64.RAX, regDisp(arg)) 519 | asm.MovlRegMem(x64.RAX, x64.RBP, int32(arg0offset+offset)) 520 | default: 521 | failed = true 522 | } 523 | offset += 4 524 | case typ.Kind == 'J': 525 | if rem := offset % 8; rem != 0 { 526 | offset += rem 527 | } 528 | switch arg.Kind { 529 | case ir.ArgIntConst: 530 | if !fits32bit(arg.Value) { 531 | failed = true 532 | } 533 | asm.MovqConst32Mem(int32(arg.Value), x64.RBP, int32(arg0offset+offset)) 534 | case ir.ArgReg: 535 | asm.MovqMemReg(x64.RSI, x64.RAX, regDisp(arg)) 536 | asm.MovqRegMem(x64.RAX, x64.RBP, int32(arg0offset+offset)) 537 | default: 538 | failed = true 539 | } 540 | offset += 8 541 | default: 542 | failed = true // TODO: handle all other argument types 543 | } 544 | i++ 545 | }) 546 | if failed { 547 | return false 548 | } 549 | 550 | asm.MovqRegMem(x64.RSI, x64.RDI, tmp0offset) // Spill SI 551 | asm.MovlConstReg(int64(fnAddr), x64.RCX) 552 | asm.MovlConstReg(int64(cl.ctx.Funcs.JcallScalar+gocallOffset), x64.RDI) 553 | asm.Raw(0x48, 0x8d, 0x05, 4+2, 0, 0, 0) 554 | asm.MovqRegMem(x64.RAX, x64.RBP, -8) 555 | asm.JmpReg(x64.RDI) 556 | asm.MovqMemReg(x64.RBP, x64.RDI, envOffset) // Load DI 557 | asm.MovqMemReg(x64.RDI, x64.RSI, tmp0offset) // Load SI 558 | if dst.Kind != 0 { 559 | // Return values start from a location aligned to a pointer size. 560 | if rem := offset % 8; rem != 0 { 561 | offset += rem 562 | } 563 | typ := signature.ReturnType() 564 | switch { 565 | case typ.Dims != 0 || typ.Kind == 'L': 566 | asm.MovqMemReg(x64.RBP, x64.RAX, int32(arg0offset+offset)) 567 | asm.MovqRegMem(x64.RAX, x64.RSI, ptrDisp(dst)) 568 | case typ.Kind == 'I': 569 | asm.MovlMemReg(x64.RBP, x64.RAX, int32(arg0offset+offset)) 570 | asm.MovlRegMem(x64.RAX, x64.RSI, regDisp(dst)) 571 | case typ.Kind == 'J': 572 | asm.MovqMemReg(x64.RBP, x64.RAX, int32(arg0offset+offset)) 573 | asm.MovqRegMem(x64.RAX, x64.RSI, regDisp(dst)) 574 | default: 575 | return false 576 | } 577 | } 578 | 579 | return true 580 | } 581 | 582 | func (cl *Compiler) pushReloc(src symbol.ID, offset int) { 583 | cl.methodRelocs++ 584 | cl.relocs = append(cl.relocs, relocation{ 585 | sourceID: src, 586 | targetID: symbol.NewID(cl.packageID, cl.classID, cl.methodID), 587 | targetOffset: offset, 588 | }) 589 | } 590 | 591 | func (cl *Compiler) getMethodByID(id symbol.ID) *vmdat.Method { 592 | i1 := id.PackageIndex() 593 | i2 := id.ClassIndex() 594 | i3 := id.MemberIndex() 595 | return &cl.ctx.State.Packages[i1].Classes[i2].Methods[i3] 596 | } 597 | -------------------------------------------------------------------------------- /jit/compiler/x64/utils.go: -------------------------------------------------------------------------------- 1 | package x64 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/quasilyte/go-jdk/ir" 7 | ) 8 | 9 | func ptrDisp(arg ir.Arg) int32 { 10 | return int32(arg.Value*16) + 8 11 | } 12 | 13 | func scalarDisp(arg ir.Arg) int32 { 14 | return int32(arg.Value * 16) 15 | } 16 | 17 | func regDisp(arg ir.Arg) int32 { 18 | return int32(arg.Value * 16) 19 | } 20 | 21 | func fits32bit(x int64) bool { 22 | return x >= math.MinInt32 && x <= math.MaxInt32 23 | } 24 | -------------------------------------------------------------------------------- /jit/jit.go: -------------------------------------------------------------------------------- 1 | package jit 2 | 3 | import ( 4 | "github.com/quasilyte/go-jdk/ir" 5 | "github.com/quasilyte/go-jdk/mmap" 6 | "github.com/quasilyte/go-jdk/vmdat" 7 | ) 8 | 9 | type Context struct { 10 | State *vmdat.State 11 | Mmap *mmap.Manager 12 | 13 | Funcs struct { 14 | JcallScalar uint32 15 | NewIntArray uint32 16 | } 17 | } 18 | 19 | // Compiler is used by a VM to generate machine code for class methods. 20 | type Compiler interface { 21 | Compile(Context, []*ir.Package) error 22 | } 23 | -------------------------------------------------------------------------------- /jit/x64/assembler.go: -------------------------------------------------------------------------------- 1 | package x64 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | func NewAssembler() *Assembler { 8 | return &Assembler{ 9 | pending: make([]instruction, 0, 256), 10 | jumps: make([]int, 0, 32), 11 | labels: make([]int, 0, 32), 12 | } 13 | } 14 | 15 | type Assembler struct { 16 | length int 17 | pending []instruction 18 | jumps []int 19 | labels []int 20 | } 21 | 22 | func (a *Assembler) Reset() { 23 | a.length = 0 24 | a.pending = a.pending[:0] 25 | a.jumps = a.jumps[:0] 26 | a.labels = a.labels[:0] 27 | } 28 | 29 | func (a *Assembler) OffsetOf(index int) int32 { 30 | return a.pending[index].offset 31 | } 32 | 33 | func (a *Assembler) Link() int { 34 | a.link() 35 | return a.length 36 | } 37 | 38 | func (a *Assembler) Put(dst []byte) { 39 | offset := 0 40 | for _, inst := range a.pending { 41 | copy(dst[offset:], inst.Bytes()) 42 | offset += int(inst.size) 43 | } 44 | } 45 | 46 | func (a *Assembler) Label(id int64) { 47 | a.labels = append(a.labels, len(a.pending)) 48 | a.push(instruction{imm: id, flags: flagPseudo}) 49 | } 50 | 51 | func (a *Assembler) Jmp(labelID int64) { 52 | a.pushJmp(jmp8op, labelID) 53 | } 54 | 55 | func (a *Assembler) Jne(labelID int64) { 56 | a.pushJcc(jne8op, labelID) 57 | } 58 | 59 | func (a *Assembler) Jge(labelID int64) { 60 | a.pushJcc(jge8op, labelID) 61 | } 62 | 63 | func (a *Assembler) Jlt(labelID int64) { 64 | a.pushJcc(jlt8op, labelID) 65 | } 66 | 67 | func (a *Assembler) Jle(labelID int64) { 68 | a.pushJcc(jle8op, labelID) 69 | } 70 | 71 | func (a *Assembler) Jgt(labelID int64) { 72 | a.pushJcc(jgt8op, labelID) 73 | } 74 | 75 | func (a *Assembler) Nop(length int) { 76 | // TODO(quasilyte): use wide NOPs. 77 | for i := 0; i < length; i++ { 78 | a.push(instruction{opcode: 0x90}) 79 | } 80 | } 81 | 82 | func (a *Assembler) Cdq() { 83 | a.push(instruction{opcode: 0x99}) 84 | } 85 | 86 | func (a *Assembler) CallReg(reg uint8) { 87 | a.push(instruction{ 88 | opcode: 0xff, 89 | reg1: op2, 90 | reg2: reg, 91 | flags: flagModRM, 92 | }) 93 | } 94 | 95 | func (a *Assembler) JmpMem(reg uint8, disp int32) { 96 | a.push(instruction{ 97 | opcode: 0xFF, 98 | reg1: op4, 99 | reg2: reg, 100 | flags: flagModRM | flagMemory, 101 | disp: disp, 102 | }) 103 | } 104 | 105 | func (a *Assembler) JmpReg(reg uint8) { 106 | a.push(instruction{ 107 | opcode: 0xFF, 108 | reg1: op4, 109 | reg2: reg, 110 | flags: flagModRM, 111 | }) 112 | } 113 | 114 | func (a *Assembler) Raw(enc ...byte) { 115 | var inst instruction 116 | copy(inst.buf[:], enc) 117 | inst.size = uint8(len(enc)) 118 | inst.flags = flagPseudo 119 | a.push(inst) 120 | } 121 | 122 | func (a *Assembler) NegqReg(reg uint8) { 123 | a.push(instruction{ 124 | opcode: 0xF7, 125 | reg1: op3, 126 | reg2: reg, 127 | flags: flagModRM | flagRexW, 128 | }) 129 | } 130 | 131 | func (a *Assembler) NeglReg(reg uint8) { 132 | a.push(instruction{ 133 | opcode: 0xF7, 134 | reg1: op3, 135 | reg2: reg, 136 | flags: flagModRM, 137 | }) 138 | } 139 | 140 | func (a *Assembler) NeglMem(reg uint8, disp int32) { 141 | a.push(instruction{ 142 | opcode: 0xF7, 143 | reg1: op3, 144 | reg2: reg, 145 | flags: flagModRM | flagMemory, 146 | disp: disp, 147 | }) 148 | } 149 | 150 | func (a *Assembler) NegqMem(reg uint8, disp int32) { 151 | a.push(instruction{ 152 | opcode: 0xF7, 153 | reg1: op3, 154 | reg2: reg, 155 | flags: flagModRM | flagMemory | flagRexW, 156 | disp: disp, 157 | }) 158 | } 159 | 160 | func (a *Assembler) CmplConstMem(v int64, reg uint8, disp int32) { 161 | if v >= -128 && v >= 127 { 162 | a.CmplConst8Mem(int8(v), reg, disp) 163 | } else { 164 | a.CmplConst32Mem(int32(v), reg, disp) 165 | } 166 | } 167 | 168 | func (a *Assembler) CmplConst8Mem(v int8, reg uint8, disp int32) { 169 | a.push(instruction{ 170 | opcode: 0x83, 171 | reg1: op7, 172 | reg2: reg, 173 | flags: flagModRM | flagMemory | flagImm8, 174 | disp: disp, 175 | imm: int64(v), 176 | }) 177 | } 178 | 179 | func (a *Assembler) CmplConst32Mem(v int32, reg uint8, disp int32) { 180 | a.push(instruction{ 181 | opcode: 0x81, 182 | reg1: op7, 183 | reg2: reg, 184 | flags: flagModRM | flagMemory | flagImm32, 185 | disp: disp, 186 | imm: int64(v), 187 | }) 188 | } 189 | 190 | func (a *Assembler) CmpqConstMem(v int64, reg uint8, disp int32) { 191 | if v >= -128 && v >= 127 { 192 | a.CmpqConst8Mem(int8(v), reg, disp) 193 | } else { 194 | a.CmpqConst32Mem(int32(v), reg, disp) 195 | } 196 | } 197 | 198 | func (a *Assembler) CmpqConst8Mem(v int8, reg uint8, disp int32) { 199 | a.push(instruction{ 200 | opcode: 0x83, 201 | reg1: op7, 202 | reg2: reg, 203 | flags: flagModRM | flagMemory | flagImm8 | flagRexW, 204 | disp: disp, 205 | imm: int64(v), 206 | }) 207 | } 208 | 209 | func (a *Assembler) CmpqConst32Mem(v int32, reg uint8, disp int32) { 210 | a.push(instruction{ 211 | opcode: 0x81, 212 | reg1: op7, 213 | reg2: reg, 214 | flags: flagModRM | flagMemory | flagImm32 | flagRexW, 215 | disp: disp, 216 | imm: int64(v), 217 | }) 218 | } 219 | 220 | func (a *Assembler) CmplRegMem(xreg uint8, yreg uint8, disp int32) { 221 | a.push(instruction{ 222 | opcode: 0x3B, 223 | reg1: xreg, 224 | reg2: yreg, 225 | flags: flagModRM | flagMemory, 226 | disp: disp, 227 | }) 228 | } 229 | 230 | func (a *Assembler) MovbRegMem(srcreg, dstreg uint8, disp int32) { 231 | a.push(instruction{ 232 | opcode: 0x88, 233 | reg1: srcreg, 234 | reg2: dstreg, 235 | flags: flagModRM | flagMemory, 236 | disp: disp, 237 | }) 238 | } 239 | 240 | func (a *Assembler) MovlRegMem(srcreg, dstreg uint8, disp int32) { 241 | a.push(instruction{ 242 | opcode: 0x89, 243 | reg1: srcreg, 244 | reg2: dstreg, 245 | flags: flagModRM | flagMemory, 246 | disp: disp, 247 | }) 248 | } 249 | 250 | func (a *Assembler) MovlRegMemindex(srcreg, dstreg, index uint8) { 251 | a.push(instruction{ 252 | opcode: 0x89, 253 | reg1: srcreg, 254 | reg2: dstreg, 255 | flags: flagModSIB | flagMemory | flagScale4, 256 | index: index, 257 | }) 258 | } 259 | 260 | func (a *Assembler) MovlMemindexReg(srcreg, dstreg, index uint8) { 261 | a.push(instruction{ 262 | opcode: 0x8B, 263 | reg1: dstreg, 264 | reg2: srcreg, 265 | index: index, 266 | flags: flagModSIB | flagMemory | flagScale4, 267 | }) 268 | } 269 | 270 | func (a *Assembler) MovlMemReg(srcreg, dstreg uint8, disp int32) { 271 | a.push(instruction{ 272 | opcode: 0x8B, 273 | reg1: dstreg, 274 | reg2: srcreg, 275 | flags: flagModRM | flagMemory, 276 | disp: disp, 277 | }) 278 | } 279 | 280 | func (a *Assembler) MovlConstMem(v int64, reg uint8, disp int32) { 281 | a.MovlConst32Mem(int32(v), reg, disp) 282 | } 283 | 284 | func (a *Assembler) MovlConstMemindex(v int64, reg uint8, index uint8) { 285 | a.MovlConst32Memindex(int32(v), reg, index) 286 | } 287 | 288 | func (a *Assembler) MovlConst32Memindex(v int32, reg uint8, index uint8) { 289 | a.push(instruction{ 290 | opcode: 0xC7, 291 | reg1: op0, 292 | reg2: reg, 293 | index: index, 294 | flags: flagModSIB | flagMemory | flagImm32 | flagScale4, 295 | imm: int64(v), 296 | }) 297 | } 298 | 299 | func (a *Assembler) MovlConst32Mem(v int32, reg uint8, disp int32) { 300 | a.push(instruction{ 301 | opcode: 0xC7, 302 | reg1: op0, 303 | reg2: reg, 304 | flags: flagModRM | flagMemory | flagImm32, 305 | disp: disp, 306 | imm: int64(v), 307 | }) 308 | } 309 | 310 | func (a *Assembler) MovlConstReg(v int64, reg uint8) { 311 | a.MovlConst32Reg(int32(v), reg) 312 | } 313 | 314 | func (a *Assembler) MovlConst32Reg(v int32, reg uint8) { 315 | a.push(instruction{ 316 | opcode: 0xB8, 317 | reg2: reg, 318 | flags: flagImm32 | flagReg2Op, 319 | imm: int64(v), 320 | }) 321 | } 322 | 323 | func (a *Assembler) MovqFixup64Reg(reg uint8) int { 324 | i := len(a.pending) 325 | a.MovqConst64Reg(0, reg) 326 | return i 327 | } 328 | 329 | func (a *Assembler) MovqConstReg(v int64, reg uint8) { 330 | if v >= math.MinInt32 && v <= math.MaxInt32 { 331 | a.MovqConst32Reg(int32(v), reg) 332 | } else { 333 | a.MovqConst64Reg(v, reg) 334 | } 335 | } 336 | 337 | func (a *Assembler) MovqConst32Reg(v int32, reg uint8) { 338 | a.push(instruction{ 339 | opcode: 0xC7, 340 | reg1: op0, 341 | reg2: reg, 342 | flags: flagImm32 | flagModRM | flagRexW, 343 | imm: int64(v), 344 | }) 345 | } 346 | 347 | func (a *Assembler) MovqConst64Reg(v int64, reg uint8) { 348 | a.push(instruction{ 349 | opcode: 0xB8, 350 | reg2: reg, 351 | flags: flagImm64 | flagRexW | flagReg2Op, 352 | imm: v, 353 | }) 354 | } 355 | 356 | func (a *Assembler) MovqConst32Mem(v int32, reg uint8, disp int32) { 357 | a.push(instruction{ 358 | opcode: 0xc7, 359 | reg2: reg, 360 | flags: flagModRM | flagMemory | flagImm32 | flagRexW, 361 | disp: disp, 362 | imm: int64(v), 363 | }) 364 | } 365 | 366 | func (a *Assembler) MovqRegMem(srcreg, dstreg uint8, disp int32) { 367 | a.push(instruction{ 368 | opcode: 0x89, 369 | reg1: srcreg, 370 | reg2: dstreg, 371 | flags: flagModRM | flagMemory | flagRexW, 372 | disp: disp, 373 | }) 374 | } 375 | 376 | func (a *Assembler) MovqMemReg(srcreg, dstreg uint8, disp int32) { 377 | a.push(instruction{ 378 | opcode: 0x8B, 379 | reg1: dstreg, 380 | reg2: srcreg, 381 | flags: flagModRM | flagMemory | flagRexW, 382 | disp: disp, 383 | }) 384 | } 385 | 386 | func (a *Assembler) SublMemReg(srcreg, dstreg uint8, disp int32) { 387 | a.push(instruction{ 388 | opcode: 0x2b, 389 | reg1: dstreg, 390 | reg2: srcreg, 391 | flags: flagModRM | flagMemory, 392 | disp: disp, 393 | }) 394 | } 395 | 396 | func (a *Assembler) IdivlReg(reg uint8) { 397 | a.push(instruction{ 398 | opcode: 0xF7, 399 | reg1: op7, 400 | reg2: reg, 401 | flags: flagModRM, 402 | }) 403 | } 404 | 405 | func (a *Assembler) IdivlMem(reg uint8, disp int32) { 406 | a.push(instruction{ 407 | opcode: 0xF7, 408 | reg1: op7, 409 | reg2: reg, 410 | flags: flagModRM | flagMemory, 411 | disp: disp, 412 | }) 413 | } 414 | 415 | func (a *Assembler) ImullMemReg(srcreg, dstreg uint8, disp int32) { 416 | a.push(instruction{ 417 | opcode: 0xAF, 418 | reg1: dstreg, 419 | reg2: srcreg, 420 | flags: flag0F | flagModRM | flagMemory, 421 | disp: disp, 422 | }) 423 | } 424 | 425 | func (a *Assembler) AddlMemReg(srcreg, dstreg uint8, disp int32) { 426 | a.push(instruction{ 427 | opcode: 0x03, 428 | reg1: dstreg, 429 | reg2: srcreg, 430 | flags: flagModRM | flagMemory, 431 | disp: disp, 432 | }) 433 | } 434 | 435 | func (a *Assembler) AddlConstMem(v int64, reg uint8, disp int32) { 436 | if v >= -128 && v >= 127 { 437 | a.AddlConst8Mem(int8(v), reg, disp) 438 | } else { 439 | a.AddlConst32Mem(int32(v), reg, disp) 440 | } 441 | } 442 | 443 | func (a *Assembler) AddlConst8Mem(v int8, reg uint8, disp int32) { 444 | a.push(instruction{ 445 | opcode: 0x83, 446 | reg1: op0, 447 | reg2: reg, 448 | flags: flagModRM | flagMemory | flagImm8, 449 | disp: disp, 450 | imm: int64(v), 451 | }) 452 | } 453 | 454 | func (a *Assembler) AddlConst32Mem(v int32, reg uint8, disp int32) { 455 | a.push(instruction{ 456 | opcode: 0x81, 457 | reg1: op0, 458 | reg2: reg, 459 | flags: flagModRM | flagMemory | flagImm32, 460 | disp: disp, 461 | imm: int64(v), 462 | }) 463 | } 464 | 465 | func (a *Assembler) AddlConstReg(v int64, reg uint8) { 466 | if v >= -128 && v >= 127 { 467 | a.AddlConst8Reg(int8(v), reg) 468 | } else { 469 | a.AddlConst32Reg(int32(v), reg) 470 | } 471 | } 472 | 473 | func (a *Assembler) AddlConst8Reg(v int8, reg uint8) { 474 | a.push(instruction{ 475 | opcode: 0x83, 476 | reg1: op0, 477 | reg2: reg, 478 | flags: flagModRM | flagImm8, 479 | imm: int64(v), 480 | }) 481 | } 482 | 483 | func (a *Assembler) AddlConst32Reg(v int32, reg uint8) { 484 | a.push(instruction{ 485 | opcode: 0x81, 486 | reg1: op0, 487 | reg2: reg, 488 | flags: flagModRM | flagImm32, 489 | imm: int64(v), 490 | }) 491 | } 492 | 493 | func (a *Assembler) AddqConstReg(v int64, reg uint8) { 494 | if v >= -128 && v >= 127 { 495 | a.AddqConst8Reg(int8(v), reg) 496 | } else { 497 | a.AddqConst32Reg(int32(v), reg) 498 | } 499 | } 500 | 501 | func (a *Assembler) AddqConst8Reg(v int8, reg uint8) { 502 | a.push(instruction{ 503 | opcode: 0x83, 504 | reg1: op0, 505 | reg2: reg, 506 | flags: flagModRM | flagImm8 | flagRexW, 507 | imm: int64(v), 508 | }) 509 | } 510 | 511 | func (a *Assembler) AddqConst32Reg(v int32, reg uint8) { 512 | a.push(instruction{ 513 | opcode: 0x81, 514 | reg1: op0, 515 | reg2: reg, 516 | flags: flagModRM | flagImm32 | flagRexW, 517 | imm: int64(v), 518 | }) 519 | } 520 | 521 | func (a *Assembler) AddqConst8Mem(v int8, reg uint8, disp int32) { 522 | a.push(instruction{ 523 | opcode: 0x83, 524 | reg1: op0, 525 | reg2: reg, 526 | flags: flagModRM | flagMemory | flagImm8 | flagRexW, 527 | disp: disp, 528 | imm: int64(v), 529 | }) 530 | } 531 | 532 | func (a *Assembler) AddqConst32Mem(v int32, reg uint8, disp int32) { 533 | a.push(instruction{ 534 | opcode: 0x81, 535 | reg1: op0, 536 | reg2: reg, 537 | flags: flagModRM | flagMemory | flagImm32 | flagRexW, 538 | disp: disp, 539 | imm: int64(v), 540 | }) 541 | } 542 | 543 | func (a *Assembler) MovlqsxMemReg(srcreg, dstreg uint8, disp int32) { 544 | a.push(instruction{ 545 | opcode: 0x63, 546 | reg1: dstreg, 547 | reg2: srcreg, 548 | flags: flagModRM | flagMemory | flagRexW, 549 | disp: disp, 550 | }) 551 | } 552 | 553 | func (a *Assembler) link() { 554 | // TODO: don't use a map here. 555 | id2index := make(map[int]int, len(a.labels)) 556 | for _, labelIndex := range a.labels { 557 | id := a.pending[labelIndex].imm 558 | id2index[int(id)] = labelIndex 559 | } 560 | 561 | // Pass 1 (fast, only jumps): find short jumps, assign opcodes. 562 | pass2needed := false 563 | for _, jumpIndex := range a.jumps { 564 | jmp := &a.pending[jumpIndex] 565 | labelIndex := id2index[int(jmp.imm)] 566 | label := &a.pending[labelIndex] 567 | dist := label.offset - jmp.offset 568 | 569 | if dist < -120 || dist > 120 { 570 | pass2needed = true 571 | jmp.opcode = jumpRel8ToRel32[jmp.opcode] 572 | jmp.size = uint8(jmp.disp) 573 | } 574 | } 575 | 576 | // Pass 2: re-calculate offsets. 577 | // 578 | // For small functions where all jumps are 8-bit relative, 579 | // we don't need a second pass at all. 580 | if pass2needed { 581 | offset := int32(0) 582 | for i, inst := range a.pending { 583 | a.pending[i].offset = offset 584 | offset += int32(inst.size) 585 | } 586 | // Update length by using last instruction offset and size. 587 | last := a.pending[len(a.pending)-1] 588 | a.length = int(last.offset + int32(last.size)) 589 | } 590 | 591 | // Pass 3 (fast, only jumps): assemble jumps. 592 | for _, jumpIndex := range a.jumps { 593 | jmp := &a.pending[jumpIndex] 594 | labelIndex := id2index[int(jmp.imm)] 595 | label := &a.pending[labelIndex] 596 | target := (label.offset - jmp.offset) - int32(jmp.size) 597 | 598 | // We're using a fact that all jumps have very similar encoding. 599 | // It's either 2, 5 or 6 bytes. 600 | // 2 bytes are used for all rel8 forms. 601 | // 5 bytes are unconditional jump (JMP) rel32 form. 602 | // 6 bytes is a conditional jump (Jcc) rel32 that requires 0x0F prefix. 603 | buf := jmp.buf[:0] 604 | if jmp.size == 6 { 605 | buf = append(buf, 0x0F) 606 | } 607 | buf = append(buf, jmp.opcode) 608 | if jmp.size == 2 { 609 | buf = append(buf, byte(target)) 610 | } else { 611 | buf = appendInt32(buf, target) 612 | } 613 | if len(buf) != int(jmp.size) { 614 | panic("len(buf) != jmp.size") 615 | } 616 | } 617 | } 618 | 619 | func (a *Assembler) pushJcc(op uint8, labelID int64) { 620 | inst := instruction{ 621 | opcode: op, 622 | imm: labelID, 623 | size: 2, // rel8 form size 624 | disp: 6, // rel32 form size 625 | flags: flagPseudo, 626 | } 627 | a.jumps = append(a.jumps, len(a.pending)) 628 | a.push(inst) 629 | } 630 | 631 | func (a *Assembler) pushJmp(op uint8, labelID int64) { 632 | inst := instruction{ 633 | opcode: op, 634 | imm: labelID, 635 | size: 2, // rel8 form size 636 | disp: 5, // rel32 form size 637 | flags: flagPseudo, 638 | } 639 | a.jumps = append(a.jumps, len(a.pending)) 640 | a.push(inst) 641 | } 642 | 643 | func (a *Assembler) push(inst instruction) { 644 | if inst.flags&flagPseudo == 0 { 645 | a.encode(&inst) 646 | } 647 | inst.offset = int32(a.length) 648 | a.length += int(inst.size) 649 | a.pending = append(a.pending, inst) 650 | } 651 | 652 | func (a *Assembler) encode(inst *instruction) { 653 | buf := inst.buf[:0] 654 | 655 | // Encode prefix. 656 | var rexPrefix byte 657 | if inst.flags&flagRexW != 0 { 658 | rexPrefix |= rexW 659 | } 660 | if inst.reg1 >= R8 { 661 | rexPrefix |= rexR 662 | } 663 | if inst.reg2 >= R8 { 664 | rexPrefix |= rexB 665 | } 666 | if rexPrefix != 0 { 667 | buf = append(buf, rexPrefix) 668 | } 669 | 670 | reg1 := inst.reg1 & regBitMask 671 | reg2 := inst.reg2 & regBitMask 672 | 673 | // Encode opcode. 674 | if inst.flags&flag0F != 0 { 675 | buf = append(buf, 0x0F) 676 | } 677 | opcode := inst.opcode 678 | if inst.flags&flagReg2Op != 0 { 679 | opcode += reg2 680 | } 681 | buf = append(buf, opcode) 682 | 683 | // Encode ModRM. 684 | // If ModSIB is set, we encode ModRM along with SIB. 685 | if inst.flags&flagModRM != 0 { 686 | if inst.flags&flagMemory != 0 { 687 | var mod byte 688 | switch { 689 | case inst.disp == 0: 690 | mod = disp0 691 | case fitsInt8(int64(inst.disp)): 692 | mod = disp8 693 | default: 694 | mod = disp32 695 | } 696 | buf = append(buf, modrm(mod, reg1, reg2)) 697 | } else { 698 | buf = append(buf, modrm(regreg, reg1, reg2)) 699 | } 700 | } else if inst.flags&flagModSIB != 0 { 701 | buf = append(buf, modrm(0, reg1, 4)) 702 | switch { 703 | case inst.flags&flagScale4 != 0: 704 | buf = append(buf, sib(scale4, inst.index, reg2)) 705 | case inst.flags&flagScale8 != 0: 706 | buf = append(buf, sib(scale8, inst.index, reg2)) 707 | } 708 | } 709 | 710 | // Encode displacement. 711 | if inst.disp != 0 { 712 | if fitsInt8(int64(inst.disp)) { 713 | buf = append(buf, byte(inst.disp)) 714 | } else { 715 | buf = appendInt32(buf, int32(inst.disp)) 716 | } 717 | } 718 | 719 | // Encode immediate. 720 | switch { 721 | case inst.flags&flagImm8 != 0: 722 | buf = append(buf, byte(inst.imm)) 723 | case inst.flags&flagImm32 != 0: 724 | buf = appendInt32(buf, int32(inst.imm)) 725 | case inst.flags&flagImm64 != 0: 726 | buf = appendInt64(buf, inst.imm) 727 | } 728 | 729 | inst.size = uint8(len(buf)) 730 | } 731 | -------------------------------------------------------------------------------- /jit/x64/constants.go: -------------------------------------------------------------------------------- 1 | package x64 2 | 3 | // Various encoding-related constants taken from the x86-64 manual. 4 | 5 | const ( 6 | // Rex prefix delivers 4 main bit fields: REX.W, REX.R, REX.X and REX.B. 7 | // 8 | // 0100 WRXB 9 | rexW = 0b0100_1000 10 | rexR = 0b0100_0100 11 | rexX = 0b0100_0010 12 | rexB = 0b0100_0001 13 | 14 | // First three modes are memory addressing with 0, 8 or 32-bit displacement. 15 | // Last mode is for reg-reg instructions (with no explicit memory operand). 16 | disp0 = 0b00 17 | disp8 = 0b01 18 | disp32 = 0b10 19 | regreg = 0b11 20 | 21 | scale1 = 0b00 22 | scale2 = 0b01 23 | scale4 = 0b10 24 | scale8 = 0b11 25 | 26 | // Opbytes occupy ModRM/reg field and expressed via / notation 27 | // in Intel manual. For example, /2 maps to the op2 constant. 28 | op0 = 0b000 29 | op1 = 0b001 30 | op2 = 0b010 31 | op3 = 0b011 32 | op4 = 0b100 33 | op5 = 0b101 34 | op6 = 0b110 35 | op7 = 0b111 36 | 37 | // Registers occupy ModRM/reg and ModRM/rm fields. 38 | regBitMask = 0b111 39 | RAX = 0b000 40 | RCX = 0b001 41 | RDX = 0b010 42 | RBX = 0b011 43 | RSP = 0b100 44 | RBP = 0b101 45 | RSI = 0b110 46 | RDI = 0b111 47 | R8 = 0b1000 48 | R9 = 0b1001 49 | R10 = 0b1010 50 | R11 = 0b1011 51 | R12 = 0b1100 52 | R13 = 0b1101 53 | R14 = 0b1110 54 | R15 = 0b1111 55 | ) 56 | 57 | const ( 58 | jmp8op = 0xEB 59 | jmp32op = 0xE9 60 | jge8op = 0x7D 61 | jge32op = 0x8D 62 | jgt8op = 0x7F 63 | jgt32op = 0x8F 64 | jlt8op = 0x7C 65 | jlt32op = 0x8C 66 | jle8op = 0x7E 67 | jle32op = 0x8E 68 | jne8op = 0x75 69 | jne32op = 0x85 70 | ) 71 | 72 | var jumpRel8ToRel32 = [256]byte{ 73 | jmp8op: jmp32op, 74 | jge8op: jge32op, 75 | jgt8op: jgt32op, 76 | jlt8op: jlt32op, 77 | jle8op: jle32op, 78 | jne8op: jne32op, 79 | } 80 | -------------------------------------------------------------------------------- /jit/x64/instruction.go: -------------------------------------------------------------------------------- 1 | package x64 2 | 3 | // instruction is a ready to be encoded machine instruction. 4 | // 5 | // We need this intermediate representation mostly for jump linkage. 6 | type instruction struct { 7 | imm int64 // Immediate operand 8 | disp int32 // Memory operand displacement (offset) 9 | offset int32 10 | 11 | buf [16]byte 12 | 13 | opcode uint8 // 8-bit instruction opcode 14 | size uint8 // Calculated encoded instruction width 15 | reg1 uint8 // 3 bits for ModRM.reg 16 | reg2 uint8 // 3 bits for ModRM.rm 17 | index uint8 // 3 bits for SIB.index 18 | flags uint16 // Hints on how to encode this instruction 19 | } 20 | 21 | func (i instruction) Bytes() []byte { 22 | return i.buf[:i.size] 23 | } 24 | 25 | const ( 26 | flagMemory uint16 = 1 << iota 27 | flagReg2Op 28 | flagModRM 29 | flagModSIB 30 | flagImm8 31 | flagImm32 32 | flagImm64 33 | flagScale4 34 | flagScale8 35 | flagPseudo 36 | flagRexW 37 | flag0F 38 | ) 39 | -------------------------------------------------------------------------------- /jit/x64/jmp_test.go: -------------------------------------------------------------------------------- 1 | package x64 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestLongJumps(t *testing.T) { 9 | checkEncoding := func(t *testing.T, result []byte, want string) { 10 | have := fmt.Sprintf("%x", result)[:len(want)] 11 | if have != want { 12 | t.Errorf("encoding mismatch:\nhave: %s\nwant: %s", have, want) 13 | } 14 | } 15 | 16 | t.Run("jmp", func(t *testing.T) { 17 | asm := NewAssembler() 18 | asm.Jmp(0) 19 | asm.Nop(0x9988) 20 | asm.Label(0) 21 | checkEncoding(t, linkToBytes(asm), "e988990000") 22 | }) 23 | 24 | t.Run("jge", func(t *testing.T) { 25 | asm := NewAssembler() 26 | asm.Jge(0) 27 | asm.Nop(0x6677) 28 | asm.Label(0) 29 | checkEncoding(t, linkToBytes(asm), "0f8d77660000") 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /jit/x64/testdata/asmtest.s: -------------------------------------------------------------------------------- 1 | #define NOP1 BYTE $0x90 2 | #define NOSPLIT 4 3 | 4 | TEXT testJge1(SB), 0, $0-0 5 | JGE forward2 // asm.Jge(2) 6 | forward2: 7 | JGE forward1 // asm.Label(2); asm.Jge(1) 8 | NOP1 // asm.Nop(1) 9 | forward1: 10 | NOP1 // asm.Label(1); asm.Nop(1) 11 | RET 12 | 13 | TEXT testJge2(SB), 0, $0-0 14 | l2: 15 | NOP1 // asm.Label(2); asm.Nop(1) 16 | JGE l1 // asm.Jge(1) 17 | l3: 18 | NOP1 // asm.Label(3); asm.Nop(1) 19 | JGE l2 // asm.Jge(2) 20 | l1: 21 | NOP1 // asm.Label(1); asm.Nop(1) 22 | JGE l3 // asm.Jge(3) 23 | RET 24 | 25 | TEXT testJmp1(SB), 0, $0-0 26 | JMP forward // asm.Jmp(7); asm.Label(7) 27 | forward: 28 | RET 29 | 30 | TEXT testJmp2(SB), 0, $0-0 31 | looping: 32 | JMP looping // asm.Label(3); asm.Jmp(3) 33 | RET 34 | 35 | TEXT testJmp3(SB), 0, $0-0 36 | backward: 37 | NOP1 // asm.Label(0); asm.Nop(1) 38 | JMP backward // asm.Jmp(0) 39 | RET 40 | 41 | TEXT testJmp4(SB), 0, $0-0 42 | JMP sharedlabel // asm.Jmp(0) 43 | NOP1 // asm.Nop(1) 44 | JMP sharedlabel // asm.Jmp(0) 45 | NOP1 // asm.Nop(1) 46 | sharedlabel: 47 | NOP1 // asm.Label(0); asm.Nop(1) 48 | RET 49 | 50 | TEXT testJmp5(SB), 0, $0-0 51 | NOP1 // asm.Nop(1) 52 | JMP l1 // asm.Jmp(1) 53 | l3: 54 | NOP1 // asm.Label(3); asm.Nop(1) 55 | JMP l2 // asm.Jmp(2) 56 | l1: 57 | NOP1 // asm.Label(1); asm.Nop(1) 58 | JMP l3 // asm.Jmp(3) 59 | l2: 60 | NOP1 // asm.Label(2); asm.Nop(1) 61 | RET 62 | 63 | TEXT testJmp6(SB), 0, $0-0 64 | l2: 65 | NOP1 // asm.Label(2); asm.Nop(1) 66 | JMP l1 // asm.Jmp(1) 67 | l3: 68 | NOP1 // asm.Label(3); asm.Nop(1) 69 | JMP l2 // asm.Jmp(2) 70 | l1: 71 | NOP1 // asm.Label(1); asm.Nop(1) 72 | JMP l3 // asm.Jmp(3) 73 | RET 74 | 75 | TEXT testJmpReg(SB), 0, $0-0 76 | JMP AX // asm.JmpReg(RAX) 77 | JMP DX // asm.JmpReg(RDX) 78 | JMP CX // asm.JmpReg(RCX) 79 | JMP R10 // asm.JmpReg(R10) 80 | RET 81 | 82 | TEXT testJmpMem(SB), 0, $0-0 83 | JMP (AX) // asm.JmpMem(RAX, 0) 84 | JMP -8(DI) // asm.JmpMem(RDI, -8) 85 | JMP 13935(CX) // asm.JmpMem(RCX, 13935) 86 | RET 87 | 88 | TEXT testAdd(SB), 0, $0-0 89 | ADDL (AX), DX // asm.AddlMemReg(RAX, RDX, 0) 90 | ADDL 8(SI), AX // asm.AddlMemReg(RSI, RAX, 8) 91 | ADDL $7, (AX) // asm.AddlConst8Mem(7, RAX, 0) 92 | ADDL $-9, -8(DX) // asm.AddlConst8Mem(-9, RDX, -8) 93 | ADDL $9300, 16(SI) // asm.AddlConst32Mem(9300, RSI, 16) 94 | ADDL $127, CX // asm.AddlConst8Reg(127, RCX) 95 | ADDL $-128, BX // asm.AddlConst8Reg(-128, RBX) 96 | ADDL $200, BP // asm.AddlConst32Reg(200, RBP) 97 | ADDQ $0, 0*8(SI) // asm.AddqConst8Mem(0, RSI, 0*8) 98 | ADDQ $1, 0*8(SI) // asm.AddqConst8Mem(1, RSI, 0*8) 99 | ADDQ $1, 1*8(SI) // asm.AddqConst8Mem(1, RSI, 1*8) 100 | ADDQ $-1, 3*8(SI) // asm.AddqConst8Mem(-1, RSI, 3*8) 101 | ADDQ $14, 10*8(SI) // asm.AddqConst8Mem(14, RSI, 10*8) 102 | ADDQ $14, 100*8(SI) // asm.AddqConst8Mem(14, RSI, 100*8) 103 | ADDQ $0xff, 0*8(SI) // asm.AddqConst32Mem(0xff, RSI, 0*8) 104 | ADDQ $0xff, 1*8(SI) // asm.AddqConst32Mem(0xff, RSI, 1*8) 105 | ADDQ $-129, 100*8(SI) // asm.AddqConst32Mem(-129, RSI, 100*8) 106 | ADDQ $1, AX // asm.AddqConst8Reg(1, RAX) 107 | ADDQ $-1, DI // asm.AddqConst8Reg(-1, RDI) 108 | ADDQ $5000, SP // asm.AddqConst32Reg(5000, RSP) 109 | ADDQ $313, R8 // asm.AddqConst32Reg(313, R8) 110 | ADDQ $-190, R9 // asm.AddqConst32Reg(-190, R9) 111 | RET 112 | 113 | TEXT testMov(SB), 0, $0-0 114 | MOVB AX, (AX) // asm.MovbRegMem(RAX, RAX, 0) 115 | MOVB CX, 24(SI) // asm.MovbRegMem(RCX, RSI, 24) 116 | MOVB BX, -300(BP) // asm.MovbRegMem(RBX, RBP, -300) 117 | MOVL $0, 0*8(SI) // asm.MovlConst32Mem(0, RSI, 0*8) 118 | MOVL $1, 0*8(DI) // asm.MovlConst32Mem(1, RDI, 0*8) 119 | MOVL $1, 1*8(AX) // asm.MovlConst32Mem(1, RAX, 1*8) 120 | MOVL $-50000, 40*8(SI) // asm.MovlConst32Mem(-50000, RSI, 40*8) 121 | MOVL (AX), AX // asm.MovlMemReg(RAX, RAX, 0) 122 | MOVL -16(CX), DX // asm.MovlMemReg(RCX, RDX, -16) 123 | MOVL AX, (AX) // asm.MovlRegMem(RAX, RAX, 0) 124 | MOVL DX, -16(CX) // asm.MovlRegMem(RDX, RCX, -16) 125 | MOVL $1355, AX // asm.MovlConst32Reg(1355, RAX) 126 | MOVL $-6643, DX // asm.MovlConst32Reg(-6643, RDX) 127 | MOVL $500, R14 // asm.MovlConst32Reg(500, R14) 128 | MOVQ 0*8(AX), BX // asm.MovqMemReg(RAX, RBX, 0*8) 129 | MOVQ 16*8(BX), AX // asm.MovqMemReg(RBX, RAX, 16*8) 130 | MOVQ AX, 0*8(DI) // asm.MovqRegMem(RAX, RDI, 0*8) 131 | MOVQ DX, 3*8(DI) // asm.MovqRegMem(RDX, RDI, 3*8) 132 | MOVQ AX, 0*8(AX) // asm.MovqRegMem(RAX, RAX, 0*8) 133 | MOVQ R15, 16(R14) // asm.MovqRegMem(R15, R14, 16) 134 | MOVQ R14, 16(R15) // asm.MovqRegMem(R14, R15, 16) 135 | MOVQ CX, 16(R14) // asm.MovqRegMem(RCX, R14, 16) 136 | MOVQ R14, 16(CX) // asm.MovqRegMem(R14, RCX, 16) 137 | MOVQ $140038723203072, AX // asm.MovqConst64Reg(140038723203072, RAX) 138 | MOVQ $9223372036854775807, DX // asm.MovqConst64Reg(9223372036854775807, RDX) 139 | MOVQ $-9223372036854775800, SI // asm.MovqConst64Reg(-9223372036854775800, RSI) 140 | MOVQ $922337203685477000, R9 // asm.MovqConst64Reg(922337203685477000, R9) 141 | MOVQ $922337203685477000, R11 // asm.MovqConst64Reg(922337203685477000, R11) 142 | MOVQ $1423, AX // asm.MovqConst32Reg(1423, RAX) 143 | MOVQ $-23, CX // asm.MovqConst32Reg(-23, RCX) 144 | MOVQ $1, DX // asm.MovqConst32Reg(1, RDX) 145 | MOVQ 100(BP), DX // asm.MovqMemReg(RBP, RDX, 100) 146 | MOVQ $1, 1(AX) // asm.MovqConst32Mem(1, RAX, 1) 147 | MOVQ $-1, 2(AX) // asm.MovqConst32Mem(-1, RAX, 2) 148 | MOVQ $0, -96(BP) // asm.MovqConst32Mem(0, RBP, -96) 149 | MOVQ $100, -96(BP) // asm.MovqConst32Mem(100, RBP, -96) 150 | RET 151 | 152 | TEXT testCmp(SB), 0, $0-0 153 | CMPL AX, 0*8(DI) // asm.CmplRegMem(RAX, RDI, 0*8) 154 | CMPL BX, 1*8(AX) // asm.CmplRegMem(RBX, RAX, 1*8) 155 | CMPL 16(SI), $0 // asm.CmplConst8Mem(0, RSI, 16) 156 | CMPL (AX), $15 // asm.CmplConst8Mem(15, RAX, 0) 157 | CMPL (DI), $242 // asm.CmplConst32Mem(242, RDI, 0) 158 | CMPL -8(BX), $-5343 // asm.CmplConst32Mem(-5343, RBX, -8) 159 | CMPQ 6*8(SI), $0 // asm.CmpqConst8Mem(0, RSI, 6*8) 160 | CMPQ (SI), $999 // asm.CmpqConst32Mem(999, RSI, 0) 161 | CMPQ 8(DI), $-999 // asm.CmpqConst32Mem(-999, RDI, 8) 162 | RET 163 | 164 | TEXT testNeg(SB), 0, $0-0 165 | NEGQ 0*8(SI) // asm.NegqMem(RSI, 0*8) 166 | NEGQ 5*8(AX) // asm.NegqMem(RAX, 5*8) 167 | NEGL AX // asm.NeglReg(RAX) 168 | NEGL DX // asm.NeglReg(RDX) 169 | NEGL (AX) // asm.NeglMem(RAX, 0) 170 | NEGL 100(BX) // asm.NeglMem(RBX, 100) 171 | NEGQ CX // asm.NegqReg(RCX) 172 | NEGQ BX // asm.NegqReg(RBX) 173 | RET 174 | 175 | TEXT testRaw(SB), 0, $0-0 176 | MOVL -16(CX), DX // asm.Raw(0x8b, 0x51, 0xf0) 177 | JMP AX // asm.Raw(0xff, 0xe0) 178 | CMPQ 6*8(SI), $0 // asm.Raw(0x48, 0x83, 0x7e, 0x30, 0x00) 179 | RET 180 | 181 | TEXT testCall(SB), NOSPLIT, $0-0 182 | CALL AX // asm.CallReg(RAX) 183 | CALL BX // asm.CallReg(RBX) 184 | RET 185 | 186 | TEXT testSub(SB), 0, $0-0 187 | SUBL (AX), DI // asm.SublMemReg(RAX, RDI, 0) 188 | SUBL 16(SI), AX // asm.SublMemReg(RSI, RAX, 16) 189 | SUBL 640(BX), DX // asm.SublMemReg(RBX, RDX, 640) 190 | RET 191 | 192 | TEXT testJgt1(SB), 0, $0-0 193 | JGT forward2 // asm.Jgt(2) 194 | forward2: 195 | JGT forward1 // asm.Label(2); asm.Jgt(1) 196 | NOP1 // asm.Nop(1) 197 | forward1: 198 | NOP1 // asm.Label(1); asm.Nop(1) 199 | RET 200 | 201 | TEXT testJgt2(SB), 0, $0-0 202 | l2: 203 | NOP1 // asm.Label(2); asm.Nop(1) 204 | JGT l1 // asm.Jgt(1) 205 | l3: 206 | NOP1 // asm.Label(3); asm.Nop(1) 207 | JGT l2 // asm.Jgt(2) 208 | l1: 209 | NOP1 // asm.Label(1); asm.Nop(1) 210 | JGT l3 // asm.Jgt(3) 211 | RET 212 | 213 | TEXT testImul(SB), 0, $0-0 214 | IMULL (AX), CX // asm.ImullMemReg(RAX, RCX, 0) 215 | IMULL 4(SI), CX // asm.ImullMemReg(RSI, RCX, 4) 216 | IMULL -8(DX), AX // asm.ImullMemReg(RDX, RAX, -8) 217 | RET 218 | 219 | TEXT testMovlqsx(SB), 0, $0-0 220 | MOVLQSX 4(AX), BX // asm.MovlqsxMemReg(RAX, RBX, 4) 221 | MOVLQSX 8(AX), AX // asm.MovlqsxMemReg(RAX, RAX, 8) 222 | RET 223 | 224 | TEXT testJlt1(SB), 0, $0-0 225 | JLT forward2 // asm.Jlt(2) 226 | forward2: 227 | JLT forward1 // asm.Label(2); asm.Jlt(1) 228 | NOP1 // asm.Nop(1) 229 | forward1: 230 | NOP1 // asm.Label(1); asm.Nop(1) 231 | RET 232 | 233 | TEXT testJlt2(SB), 0, $0-0 234 | l2: 235 | NOP1 // asm.Label(2); asm.Nop(1) 236 | JLT l1 // asm.Jlt(1) 237 | l3: 238 | NOP1 // asm.Label(3); asm.Nop(1) 239 | JLT l2 // asm.Jlt(2) 240 | l1: 241 | NOP1 // asm.Label(1); asm.Nop(1) 242 | JLT l3 // asm.Jlt(3) 243 | RET 244 | 245 | TEXT testCdq(SB), 0, $0-0 246 | CDQ // asm.Cdq() 247 | RET 248 | 249 | TEXT testIdivl(SB), 0, $0-0 250 | IDIVL (AX) // asm.IdivlMem(RAX, 0) 251 | IDIVL 16(CX) // asm.IdivlMem(RCX, 16) 252 | IDIVL AX // asm.IdivlReg(RAX) 253 | IDIVL BX // asm.IdivlReg(RBX) 254 | RET 255 | 256 | TEXT testMovIndex(SB), 0, $0-0 257 | MOVL (AX)(CX*4), AX // asm.MovlMemindexReg(RAX, RAX, RCX) 258 | MOVL (CX)(CX*4), BX // asm.MovlMemindexReg(RCX, RBX, RCX) 259 | MOVL (SI)(AX*4), CX // asm.MovlMemindexReg(RSI, RCX, RAX) 260 | MOVL (AX)(SI*4), CX // asm.MovlMemindexReg(RAX, RCX, RSI) 261 | MOVL $0, (AX)(CX*4) // asm.MovlConst32Memindex(0, RAX, RCX) 262 | MOVL $14, (DX)(DI*4) // asm.MovlConst32Memindex(14, RDX, RDI) 263 | MOVL $-6, (SI)(BX*4) // asm.MovlConst32Memindex(-6, RSI, RBX) 264 | MOVL AX, (AX)(CX*4) // asm.MovlRegMemindex(RAX, RAX, RCX) 265 | MOVL DX, (DI)(AX*4) // asm.MovlRegMemindex(RDX, RDI, RAX) 266 | MOVL SI, (SI)(SI*4) // asm.MovlRegMemindex(RSI, RSI, RSI) 267 | MOVL CX, (CX)(CX*4) // asm.MovlRegMemindex(RCX, RCX, RCX) 268 | MOVL R8, (SI)(CX*4) // asm.MovlRegMemindex(R8, RSI, RCX) 269 | RET 270 | 271 | TEXT testJle1(SB), 0, $0-0 272 | JLE forward2 // asm.Jle(2) 273 | forward2: 274 | JLE forward1 // asm.Label(2); asm.Jle(1) 275 | NOP1 // asm.Nop(1) 276 | forward1: 277 | NOP1 // asm.Label(1); asm.Nop(1) 278 | RET 279 | 280 | TEXT testJle2(SB), 0, $0-0 281 | l2: 282 | NOP1 // asm.Label(2); asm.Nop(1) 283 | JLE l1 // asm.Jle(1) 284 | l3: 285 | NOP1 // asm.Label(3); asm.Nop(1) 286 | JLE l2 // asm.Jle(2) 287 | l1: 288 | NOP1 // asm.Label(1); asm.Nop(1) 289 | JLE l3 // asm.Jle(3) 290 | RET 291 | 292 | TEXT testJne1(SB), 0, $0-0 293 | JNE forward2 // asm.Jne(2) 294 | forward2: 295 | JNE forward1 // asm.Label(2); asm.Jne(1) 296 | NOP1 // asm.Nop(1) 297 | forward1: 298 | NOP1 // asm.Label(1); asm.Nop(1) 299 | RET 300 | 301 | TEXT testJne2(SB), 0, $0-0 302 | l2: 303 | NOP1 // asm.Label(2); asm.Nop(1) 304 | JNE l1 // asm.Jne(1) 305 | l3: 306 | NOP1 // asm.Label(3); asm.Nop(1) 307 | JNE l2 // asm.Jne(2) 308 | l1: 309 | NOP1 // asm.Label(1); asm.Nop(1) 310 | JNE l3 // asm.Jne(3) 311 | RET 312 | -------------------------------------------------------------------------------- /jit/x64/testdata/gen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | func main() { 14 | log.SetFlags(0) 15 | 16 | _, err := runCommand("go", "tool", "asm", "asmtest.s") 17 | if err != nil { 18 | log.Fatalf("asm: %v", err) 19 | } 20 | out, err := runCommand("go", "tool", "objdump", "asmtest.o") 21 | if err != nil { 22 | log.Fatalf("objdump: %v", err) 23 | } 24 | data, err := ioutil.ReadFile("asmtest.s") 25 | if err != nil { 26 | log.Fatalf("read file: %v", err) 27 | } 28 | sourceLines := strings.Split(string(data), "\n") 29 | 30 | type asmLine struct { 31 | LineNum int 32 | loc string 33 | Enc string 34 | Asm string 35 | Comment string 36 | } 37 | type asmFunc struct { 38 | Name string 39 | Lines []asmLine 40 | } 41 | 42 | var funcs []asmFunc 43 | for _, funcString := range strings.Split(out, "\n\n") { 44 | lines := strings.Split(funcString, "\n") 45 | var fn asmFunc 46 | fn.Name = strings.TrimSuffix(strings.Fields(lines[0])[1], "(SB)") 47 | for _, l := range lines[1:] { 48 | if l == "" { 49 | continue 50 | } 51 | fields := strings.Fields(l) 52 | loc := fields[0] 53 | enc := fields[2] 54 | var lineNum int 55 | fmt.Sscanf(loc, "asmtest.s:%d", &lineNum) 56 | src := sourceLines[lineNum-1] 57 | parts := strings.Split(src, "//") 58 | asm := parts[0] 59 | var comment string 60 | if len(parts) > 1 { 61 | comment = parts[1] 62 | } 63 | fn.Lines = append(fn.Lines, asmLine{ 64 | LineNum: lineNum, 65 | loc: loc, 66 | Enc: enc, 67 | Asm: strings.TrimSpace(asm), 68 | Comment: comment, 69 | }) 70 | } 71 | lastLine := fn.Lines[len(fn.Lines)-1] 72 | if lastLine.Asm != "RET" { 73 | log.Fatalf("%s last line is not RET", fn.Name) 74 | } 75 | // Drop the last RET from the lines list. 76 | fn.Lines = fn.Lines[:len(fn.Lines)-1] 77 | funcs = append(funcs, fn) 78 | } 79 | 80 | tmpl := template.Must(template.New(`testcase`).Parse(` 81 | { 82 | name: "{{$.Name}}", 83 | want: []expected{ 84 | {{- range $.Lines}} 85 | { {{.LineNum}}, "{{.Asm}}", "{{.Enc}}" }, 86 | {{- end}} 87 | }, 88 | run: func(asm *Assembler) { 89 | {{- range $.Lines}} {{.Comment}}; {{end}} 90 | }, 91 | }, 92 | `)) 93 | for _, fn := range funcs { 94 | err := tmpl.Execute(os.Stdout, fn) 95 | if err != nil { 96 | log.Fatalf("run %s template: %v", fn.Name, err) 97 | } 98 | } 99 | } 100 | 101 | func runCommand(name string, args ...string) (string, error) { 102 | out, err := exec.Command(name, args...).CombinedOutput() 103 | if err != nil { 104 | return "", fmt.Errorf("%v: %s", err, out) 105 | } 106 | return string(out), nil 107 | } 108 | -------------------------------------------------------------------------------- /jit/x64/utils.go: -------------------------------------------------------------------------------- 1 | package x64 2 | 3 | func fitsInt8(v int64) bool { 4 | return v >= -128 && v <= 127 5 | } 6 | 7 | func modrm(mod, reg, rm byte) byte { 8 | return (mod << 6) | (reg << 3) | (rm << 0) 9 | } 10 | 11 | func sib(scale, index, base byte) byte { 12 | return (scale << 6) | (index << 3) | (base << 0) 13 | } 14 | 15 | func appendInt32(buf []byte, v int32) []byte { 16 | return append(buf, 17 | byte(v>>0), 18 | byte(v>>8), 19 | byte(v>>16), 20 | byte(v>>24)) 21 | } 22 | 23 | func appendInt64(buf []byte, v int64) []byte { 24 | return append(buf, 25 | byte(v>>0), 26 | byte(v>>8), 27 | byte(v>>16), 28 | byte(v>>24), 29 | byte(v>>32), 30 | byte(v>>40), 31 | byte(v>>48), 32 | byte(v>>56)) 33 | } 34 | -------------------------------------------------------------------------------- /jruntime/bindfunc.go: -------------------------------------------------------------------------------- 1 | package jruntime 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "github.com/quasilyte/go-jdk/jit" 7 | ) 8 | 9 | func BindFuncs(ctx *jit.Context) { 10 | ctx.Funcs.JcallScalar = funcAddr(jcallScalar) 11 | ctx.Funcs.NewIntArray = funcAddr(NewIntArray) 12 | } 13 | 14 | // funcAddr returns function value fn executable code address. 15 | func funcAddr(fn interface{}) uint32 { 16 | // emptyInterface is the header for an interface{} value. 17 | type emptyInterface struct { 18 | typ uintptr 19 | value *uintptr 20 | } 21 | e := (*emptyInterface)(unsafe.Pointer(&fn)) 22 | addr := *e.value 23 | if addr > 0xffffffff { 24 | panic("func addr does not fit in 32-bit") 25 | } 26 | return uint32(addr) 27 | } 28 | -------------------------------------------------------------------------------- /jruntime/call.go: -------------------------------------------------------------------------------- 1 | package jruntime 2 | 3 | // jcallScalar runs method code inside env context. 4 | // 5 | // Code is a pointer to a beginning of a method machine code. 6 | func jcallScalar(e *Env, code *byte) 7 | -------------------------------------------------------------------------------- /jruntime/call_amd64.s: -------------------------------------------------------------------------------- 1 | #include "textflag.h" 2 | #include "funcdata.h" 3 | 4 | // Go asm has no real rip-based addressing, this is why we 5 | // encode `lea rax, [rip+N]` instruction manually, byte-by-byte. 6 | // N is the length of all instructions in this macro block. 7 | #define JCALL(fnreg) \ 8 | BYTE $0x48; BYTE $0x8d; BYTE $0x05; BYTE $9; BYTE $0; BYTE $0; BYTE $0; \ 9 | MOVQ AX, (SI) \ 10 | ADDQ $16, SI \ 11 | JMP fnreg 12 | 13 | // Register roles: 14 | // AX - tmp register; also used for return value 15 | // BX - (pin to "this" pointer?) 16 | // CX - 17 | // DX - (note: affected by IDIV instruction) 18 | // SI - stack pointer 19 | // DI - env pointer (no need to spill, always in an argument slot) 20 | // 21 | // $96 bytes for Go call arguments space. 22 | // $16 bytes for 2 pointer arguments. 23 | 24 | // func jcallScalar(e *Env, code *byte) 25 | TEXT ·jcallScalar(SB), 0, $96-16 26 | // We only use local stack frame to pass Go func arguments. 27 | // As long as pointers that are placed there are also 28 | // reachable from other parts of the program, we should be fine. 29 | // See #32. 30 | NO_LOCAL_POINTERS 31 | MOVQ e+0(FP), DI // env is always at DI 32 | MOVQ 1*8(DI), SI // stack is always at SI 33 | MOVQ code+8(FP), CX 34 | JCALL(CX) 35 | MOVQ AX, -16(SI) // Save called function result, if any 36 | RET 37 | gocall: 38 | CALL CX 39 | JMP -8(BP) 40 | -------------------------------------------------------------------------------- /jruntime/call_amd64_test.go: -------------------------------------------------------------------------------- 1 | package jruntime 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | 7 | "github.com/quasilyte/go-jdk/mmap" 8 | ) 9 | 10 | // TODO(quasilyte): can we make this test less arch-specific? 11 | 12 | func TestJcall(t *testing.T) { 13 | var env Env 14 | env.slots = make([]stackSlot, 16) 15 | env.stack = &env.slots[0] 16 | 17 | funcCode := []byte{ 18 | // MOVQ $777, (SI) 19 | 0x48, 0xc7, 0x06, 0x09, 0x03, 0x00, 0x00, 20 | // MOVL $1, AX 21 | 0xb8, 0x01, 0x00, 0x00, 0x00, 22 | // JMP -16(SI) 23 | 0xff, 0x66, 0xf0, 24 | } 25 | d, executable, err := mmap.Executable(len(funcCode)) 26 | if err != nil { 27 | t.Errorf("mmap executable: %v", err) 28 | } 29 | copy(executable, funcCode) 30 | defer munmap(t, d) 31 | 32 | checkStack := func() { 33 | if env.slots[0].scalar != 1 { 34 | t.Errorf("stack[0] mismatch:\nhave: %d\nwant: 1", env.slots[0].scalar) 35 | } 36 | if env.slots[1].scalar != 777 { 37 | t.Errorf("stack[1] mismatch:\nhave: %d\nwant: 777", env.slots[1].scalar) 38 | } 39 | env.slots[0].scalar = 0 40 | env.slots[1].scalar = 0 41 | } 42 | 43 | // Call in a same goroutine, on a same frame. 44 | jcallScalar(&env, &executable[0]) 45 | checkStack() 46 | 47 | // Should not panic nor corrupt the stack. 48 | for i := 0; i < 10; i++ { 49 | nestedCall(&env, executable) 50 | checkStack() 51 | } 52 | 53 | // Now test it inside a new goroutine. 54 | var wg sync.WaitGroup 55 | wg.Add(1) 56 | go func() { 57 | jcallScalar(&env, &executable[0]) 58 | checkStack() 59 | wg.Done() 60 | }() 61 | wg.Wait() 62 | } 63 | 64 | //go:noinline 65 | func nestedCall(env *Env, code []byte) { 66 | jcallScalar(env, &code[0]) 67 | } 68 | 69 | func BenchmarkJcall(b *testing.B) { 70 | var env Env 71 | env.slots = make([]stackSlot, 16) 72 | env.stack = &env.slots[0] 73 | 74 | funcCode := []byte{ 75 | // JMP -16(SI) 76 | 0xff, 0x66, 0xf0, 77 | } 78 | d, executable, err := mmap.Executable(len(funcCode)) 79 | if err != nil { 80 | b.Errorf("mmap executable: %v", err) 81 | } 82 | copy(executable, funcCode) 83 | defer munmap(b, d) 84 | executablePtr := &executable[0] 85 | 86 | b.ResetTimer() 87 | for i := 0; i < b.N; i++ { 88 | jcallScalar(&env, executablePtr) 89 | } 90 | b.StopTimer() 91 | } 92 | 93 | func munmap(tb testing.TB, d mmap.Descriptor) { 94 | if err := mmap.Free(d); err != nil { 95 | tb.Errorf("mmap free: %v", err) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /jruntime/env.go: -------------------------------------------------------------------------------- 1 | package jruntime 2 | 3 | import ( 4 | "sync/atomic" 5 | "unsafe" 6 | 7 | "github.com/quasilyte/go-jdk/vmdat" 8 | ) 9 | 10 | // TODO(quasilyte): decide whether Env is thread-safe and in what contexts. 11 | // This can wait for until we run VM inside a concurrent program. 12 | 13 | // TODO(quasilyte): define "VM execution is terminated" more precisely. 14 | 15 | // EnvConfig describes VM execution unit settings. 16 | type EnvConfig struct { 17 | // AllocBytesLimit describes max alloc bytes limit for a single invocation. 18 | // 19 | // When VM function is called, alloc counter is set to this value. 20 | // Every time a new object is allocated, it's size is subtracted from that counter. 21 | // If it becomes negative, VM execution is terminated. 22 | // 23 | // Zero value means "default value" which is big enough for most use cases. 24 | AllocBytesLimit int64 25 | 26 | // StackMemory is a stack size in bytes that can be used by a running program. 27 | // 28 | // Stack is used to allocate local stack slots, call frames, etc. 29 | // Setting lower value effectively limits how deep the recursion can go. 30 | // If there is not enough stack memory for another function call, 31 | // VM execution is terminated. 32 | // 33 | // Value of 0 will result in in a default stack size allocation. 34 | StackMemory int64 35 | } 36 | 37 | // NewEnv returns configured execution unit for the VM. 38 | func NewEnv(vm *VM, cfg *EnvConfig) *Env { 39 | const megabyte = 1024 * 1024 40 | 41 | var env Env 42 | env.vm = vm 43 | 44 | stackMemory := cfg.StackMemory 45 | if stackMemory == 0 { 46 | stackMemory = megabyte 47 | } 48 | env.slots = make([]stackSlot, stackMemory) 49 | env.stack = &env.slots[0] 50 | 51 | env.allocBytesLimit = cfg.AllocBytesLimit 52 | if env.allocBytesLimit == 0 { 53 | env.allocBytesLimit = megabyte 54 | } 55 | 56 | return &env 57 | } 58 | 59 | // Env is context-like structure for VM code execution. 60 | type Env struct { 61 | envFixed // Should be the first struct member 62 | 63 | allocBytesLimit int64 64 | 65 | slots []stackSlot 66 | } 67 | 68 | type stackSlot struct { 69 | scalar int64 70 | ptr *Object 71 | } 72 | 73 | type envFixed struct { 74 | // Note: please don't re-arrange members of this struct 75 | // as they are sometimes accessed via computed offsets manually. 76 | // Everything that doesn't need to be aligned carefully can go 77 | // into Env struct itself instead. 78 | // FIXME: this layout implies 64-bit platform. 79 | 80 | allocBytesLeft int64 // offset=0 81 | stack *stackSlot // offset=8 82 | _ uint64 // offset=16 (reserved) 83 | tmp uint64 // offset=24 84 | 85 | vm *VM 86 | } 87 | 88 | func (env *Env) GC() { 89 | for _, slot := range env.slots { 90 | slot.ptr = nil 91 | } 92 | } 93 | 94 | func (env *Env) IntCall(m *vmdat.Method) (int64, error) { 95 | env.allocBytesLeft = env.allocBytesLimit 96 | jcallScalar(env, &m.Code[0]) 97 | return env.stack.scalar, nil 98 | } 99 | 100 | func (env *Env) IntArg(i int, v int64) { 101 | ptr := unsafe.Pointer(uintptr(unsafe.Pointer(env.stack)) + uintptr(i*16) + 16) 102 | (*stackSlot)(ptr).scalar = v 103 | } 104 | 105 | // trackAllocations checks whether we can allocate size bytes. 106 | // If memory limit is reached, it panics. 107 | func (env *Env) trackAllocation(size int64) { 108 | n := atomic.AddInt64(&env.allocBytesLeft, -size) 109 | if n < 0 { 110 | // TODO(quasilyte): provide JVM stack trace info. 111 | panic("allocations limit reached") 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /jruntime/new_object.go: -------------------------------------------------------------------------------- 1 | package jruntime 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | var IntArrayInfo = ObjectInfo{ 8 | Kind: KindArray, 9 | Size: 4, 10 | } 11 | 12 | func NewIntArray(env *Env, length int32) *Object { 13 | env.trackAllocation(int64(length)*4 + int64(unsafe.Sizeof(IntArrayObject{}))) 14 | var data *int32 15 | if length != 0 { 16 | elems := make([]int32, length) 17 | data = &elems[0] 18 | } 19 | return (*Object)(unsafe.Pointer(&IntArrayObject{ 20 | Info: &IntArrayInfo, 21 | Data: data, 22 | Len: length, 23 | })) 24 | } 25 | -------------------------------------------------------------------------------- /jruntime/object.go: -------------------------------------------------------------------------------- 1 | package jruntime 2 | 3 | import ( 4 | "unsafe" 5 | 6 | "github.com/quasilyte/go-jdk/goreflect" 7 | ) 8 | 9 | type Object struct { 10 | Info *ObjectInfo 11 | Ptrdata unsafe.Pointer 12 | } 13 | 14 | type ObjectInfo struct { 15 | Kind ObjectKind 16 | Size int 17 | } 18 | 19 | type ObjectKind int 20 | 21 | const ( 22 | KindArray ObjectKind = iota 23 | KindObject 24 | ) 25 | 26 | type IntArrayObject struct { 27 | Info *ObjectInfo 28 | Data *int32 29 | Len int32 30 | } 31 | 32 | func (o *IntArrayObject) AsSlice() []int32 { 33 | header := goreflect.SliceHeader{ 34 | Data: uintptr(unsafe.Pointer(o.Data)), 35 | Len: int(o.Len), 36 | Cap: int(o.Len), 37 | } 38 | return *(*[]int32)(unsafe.Pointer(&header)) 39 | } 40 | 41 | func (o *Object) AsIntArray() *IntArrayObject { 42 | return (*IntArrayObject)(unsafe.Pointer(o)) 43 | } 44 | -------------------------------------------------------------------------------- /jruntime/vm.go: -------------------------------------------------------------------------------- 1 | package jruntime 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/quasilyte/go-jdk/jit" 7 | "github.com/quasilyte/go-jdk/jit/compiler/x64" 8 | "github.com/quasilyte/go-jdk/mmap" 9 | "github.com/quasilyte/go-jdk/vmdat" 10 | ) 11 | 12 | type VM struct { 13 | State vmdat.State 14 | Mmap mmap.Manager 15 | Compiler jit.Compiler 16 | } 17 | 18 | func OpenVM(arch string) (*VM, error) { 19 | var vm VM 20 | switch arch { 21 | case "amd64": 22 | vm.Compiler = x64.NewCompiler() 23 | default: 24 | return nil, fmt.Errorf("arch %s is not supported", arch) 25 | } 26 | vm.State.Init() 27 | return &vm, nil 28 | } 29 | 30 | // Close instructs the VM to free all associated resources. 31 | func (vm *VM) Close() error { 32 | if err := vm.Mmap.Close(); err != nil { 33 | return err 34 | } 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /loader/loader.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/quasilyte/go-jdk/ir" 8 | "github.com/quasilyte/go-jdk/jclass" 9 | "github.com/quasilyte/go-jdk/symbol" 10 | "github.com/quasilyte/go-jdk/vmdat" 11 | ) 12 | 13 | type Config struct { 14 | ClassPath []string 15 | } 16 | 17 | func LoadClass(st *vmdat.State, filename string, cfg *Config) ([]*ir.Package, error) { 18 | classfile, err := decodeClassFile(filename) 19 | if err != nil { 20 | return nil, err 21 | } 22 | _, pkgName := splitName(classfile.ThisClassName) 23 | pkgFiles := []*jclass.File{classfile} 24 | return loadPackageSet(st, pkgName, pkgFiles, cfg) 25 | } 26 | 27 | // LoadPackage loads a package with name pkgName as well as all its dependencies. 28 | // Loaded packages are returned to be compiled. 29 | // All of these packages are also added to a provided VM state. 30 | // 31 | // If a package was already loaded, nil is returned. 32 | func LoadPackage(st *vmdat.State, pkgName string, cfg *Config) ([]*ir.Package, error) { 33 | if st.FindPackage(pkgName) != nil { 34 | return nil, nil // Already loaded 35 | } 36 | 37 | pkgFiles, err := readClassFiles(pkgName, cfg) 38 | if err != nil { 39 | return nil, fmt.Errorf("read %q class files: %v", pkgName, err) 40 | } 41 | return loadPackageSet(st, pkgName, pkgFiles, cfg) 42 | } 43 | 44 | func loadPackageSet(st *vmdat.State, pkgName string, initial []*jclass.File, cfg *Config) ([]*ir.Package, error) { 45 | pkg := createPackage(st, pkgName, initial) 46 | deps := findDependencies(st, initial) 47 | 48 | toLoad := make([]*ir.Package, 0, len(deps)+1) 49 | toLoad = append(toLoad, pkg) 50 | for len(deps) > 0 { 51 | d := deps[len(deps)-1] 52 | if d == "java/lang" { 53 | // We don't support stdlib packages yet. 54 | // FIXME: remove this after we handle them properly. 55 | deps = deps[:len(deps)-1] 56 | continue 57 | } 58 | files, err := readClassFiles(d, cfg) 59 | if err != nil { 60 | return nil, fmt.Errorf("find %q package: %v", d, err) 61 | } 62 | pkg := createPackage(st, d, files) 63 | toLoad = append(toLoad, pkg) 64 | depDeps := findDependencies(st, files) 65 | deps = append(deps[:len(deps)-1], depDeps...) 66 | } 67 | 68 | return toLoad, nil 69 | } 70 | 71 | func createPackage(st *vmdat.State, name string, files []*jclass.File) *ir.Package { 72 | pkg := ir.Package{Out: st.NewPackage(name)} 73 | 74 | for _, f := range files { 75 | sort.Slice(f.Methods, func(i, j int) bool { 76 | return f.Methods[i].Name < f.Methods[j].Name 77 | }) 78 | 79 | switch { 80 | case f.AccessFlags.IsEnum(): 81 | panic("enums types are not implemented") 82 | case f.AccessFlags.IsInterface(): 83 | panic("interface types are not implemented") 84 | case f.AccessFlags.IsAnnotation(): 85 | panic("annotation types are not implemented") 86 | default: // Otherwise it's a normal class 87 | methods := make([]ir.Method, len(f.Methods)) 88 | className, _ := splitName(f.ThisClassName) 89 | pkg.Classes = append(pkg.Classes, ir.Class{ 90 | Name: className, 91 | File: f, 92 | Methods: methods, 93 | }) 94 | } 95 | } 96 | 97 | sort.Slice(pkg.Classes, func(i, j int) bool { 98 | return pkg.Classes[i].Name < pkg.Classes[j].Name 99 | }) 100 | 101 | pkg.Out.Classes = make([]vmdat.Class, len(pkg.Classes)) 102 | for i := range pkg.Classes { 103 | irClass := &pkg.Classes[i] 104 | c := &pkg.Out.Classes[i] 105 | c.Name = irClass.Name 106 | c.Methods = make([]vmdat.Method, len(irClass.Methods)) 107 | f := irClass.File 108 | for j := range irClass.Methods { 109 | m := &c.Methods[j] 110 | m.Name = f.Methods[j].Name 111 | m.Descriptor = f.Methods[j].Descriptor 112 | m.AccessFlags = f.Methods[j].AccessFlags 113 | m.ID = symbol.NewID(uint64(pkg.Out.ID), uint64(i), uint64(j)) 114 | irClass.Methods[j].Out = m 115 | } 116 | irClass.Out = c 117 | } 118 | 119 | return &pkg 120 | } 121 | -------------------------------------------------------------------------------- /loader/util.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/quasilyte/go-jdk/jclass" 11 | "github.com/quasilyte/go-jdk/jdeps" 12 | "github.com/quasilyte/go-jdk/vmdat" 13 | ) 14 | 15 | func readClassFiles(name string, cfg *Config) ([]*jclass.File, error) { 16 | var pkgPath string 17 | var files []os.FileInfo 18 | fsname := strings.ReplaceAll(name, ".", string(os.PathSeparator)) 19 | for _, cp := range cfg.ClassPath { 20 | var err error 21 | pkgPath = filepath.Join(cp, fsname) 22 | files, err = dirClassFiles(pkgPath) 23 | if err == nil && len(files) != 0 { 24 | break 25 | } 26 | } 27 | if len(files) == 0 { 28 | return nil, errors.New("none of the class paths contained the specified package") 29 | } 30 | out := make([]*jclass.File, len(files)) 31 | for i, f := range files { 32 | var err error 33 | out[i], err = decodeClassFile(filepath.Join(pkgPath, f.Name())) 34 | if err != nil { 35 | return nil, fmt.Errorf("decode %s: %v", f.Name(), err) 36 | } 37 | } 38 | 39 | return out, nil 40 | } 41 | 42 | func dirClassFiles(name string) ([]os.FileInfo, error) { 43 | f, err := os.Open(name) 44 | if err != nil { 45 | return nil, err 46 | } 47 | list, err := f.Readdir(-1) 48 | filesOnly := list[:0] 49 | for _, f := range list { 50 | if !f.IsDir() && strings.HasSuffix(f.Name(), ".class") { 51 | filesOnly = append(filesOnly, f) 52 | } 53 | } 54 | f.Close() 55 | return filesOnly, err 56 | } 57 | 58 | func decodeClassFile(filename string) (*jclass.File, error) { 59 | f, err := os.Open(filename) 60 | if err != nil { 61 | return nil, err 62 | } 63 | defer f.Close() 64 | var dec jclass.Decoder 65 | return dec.Decode(f) 66 | } 67 | 68 | // findDependencies returns non-loaded dependencies for the given package. 69 | func findDependencies(st *vmdat.State, files []*jclass.File) []string { 70 | var nonLoaded []string 71 | for _, f := range files { 72 | deps := jdeps.ClassDependencies(f) 73 | for _, d := range deps { 74 | if st.FindPackage(d) == nil { 75 | nonLoaded = append(nonLoaded, d) 76 | } 77 | } 78 | } 79 | return nonLoaded 80 | } 81 | 82 | // FIXME: duplicated from irgen package. 83 | func splitName(full string) (name, pkg string) { 84 | delim := strings.LastIndexByte(full, '/') 85 | if delim == -1 { 86 | return full, "" 87 | } 88 | return full[delim+1:], full[:delim] 89 | } 90 | -------------------------------------------------------------------------------- /mmap/manager.go: -------------------------------------------------------------------------------- 1 | package mmap 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Manager struct { 8 | mapped []Descriptor 9 | } 10 | 11 | func (m *Manager) Close() error { 12 | for _, d := range m.mapped { 13 | if err := Free(d); err != nil { 14 | return fmt.Errorf("free mapped memory: %v", err) 15 | } 16 | } 17 | return nil 18 | } 19 | 20 | func (m *Manager) AllocateExecutable(length int) ([]byte, error) { 21 | // TODO(quasilyte): re-use mapped regions. 22 | d, buf, err := Executable(length) 23 | if err != nil { 24 | return nil, err 25 | } 26 | m.mapped = append(m.mapped, d) 27 | return buf, nil 28 | } 29 | -------------------------------------------------------------------------------- /mmap/mmap.go: -------------------------------------------------------------------------------- 1 | package mmap 2 | 3 | type Descriptor struct { 4 | Addr uintptr 5 | Size int 6 | } 7 | 8 | // Executable allocates a mapped memory buffer that can be 9 | // used to store runnable machine code. 10 | // 11 | // Descriptor should be used to free (unmap) the memory. 12 | func Executable(length int) (Descriptor, []byte, error) { 13 | return mmapExecutable(length) 14 | } 15 | 16 | // Free unmaps the memory that is associated with the given descriptor. 17 | func Free(d Descriptor) error { 18 | return munmap(d) 19 | } 20 | -------------------------------------------------------------------------------- /mmap/mmap_linux.go: -------------------------------------------------------------------------------- 1 | package mmap 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | 7 | "github.com/quasilyte/go-jdk/goreflect" 8 | ) 9 | 10 | func mmapExecutable(length int) (Descriptor, []byte, error) { 11 | prot := syscall.PROT_READ | syscall.PROT_WRITE | syscall.PROT_EXEC 12 | flags := syscall.MAP_PRIVATE | syscall.MAP_ANON 13 | return mmapLinux(0, length, prot, flags, 0, 0) 14 | } 15 | 16 | func munmap(d Descriptor) error { 17 | return munmapLinux(d) 18 | } 19 | 20 | func mmapLinux(addr uintptr, length, prot, flags int, fd uintptr, offset int64) (Descriptor, []byte, error) { 21 | // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 22 | ptr, _, err := syscall.Syscall6( 23 | syscall.SYS_MMAP, 24 | addr, 25 | uintptr(length), 26 | uintptr(prot), 27 | uintptr(flags), 28 | fd, 29 | uintptr(offset)) 30 | d := Descriptor{Addr: ptr, Size: length} 31 | if err != 0 { 32 | return d, nil, err 33 | } 34 | slice := *(*[]byte)(unsafe.Pointer(&goreflect.SliceHeader{ 35 | Data: ptr, 36 | Len: length, 37 | Cap: length, 38 | })) 39 | return d, slice, nil 40 | } 41 | 42 | func munmapLinux(d Descriptor) error { 43 | // int munmap(void *addr, size_t length); 44 | _, _, err := syscall.Syscall( 45 | syscall.SYS_MUNMAP, 46 | d.Addr, 47 | uintptr(d.Size), 48 | 0) 49 | if err != 0 { 50 | return err 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /symbol/symbol.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | // ID is a compact and globally unique symbol identifier. 4 | // 5 | // ID layout: 6 | // - 3 bytes for package index (within VM) 7 | // - 3 bytes for class index (within package) 8 | // - 2 bytes for member index (within class) 9 | // 10 | // Limits: 11 | // - 16_777_215 packages 12 | // - 16_777_215 classes within package 13 | // - 65_535 class members 14 | type ID uint64 15 | 16 | // NewID constructs a symbol ID out of its components. 17 | func NewID(pkg, class, member uint64) ID { 18 | return ID((pkg << (8 * 5)) | (class << (8 * 2)) | (member << (8 * 0))) 19 | } 20 | 21 | func (id ID) PackageIndex() uint { return uint(id >> (8 * 5)) } 22 | 23 | func (id ID) ClassIndex() uint { return uint((id << (8 * 3)) >> (8 * 5)) } 24 | 25 | func (id ID) MemberIndex() uint { return uint(uint16(id)) } 26 | -------------------------------------------------------------------------------- /symbol/symbol_test.go: -------------------------------------------------------------------------------- 1 | package symbol 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | func TestID(t *testing.T) { 9 | for i := 0; i < 1000; i++ { 10 | pkg := uint64(rand.Intn(16777215)) 11 | class := uint64(rand.Intn(16777215)) 12 | member := uint64(rand.Intn(65535)) 13 | id := NewID(pkg, class, member) 14 | if id.PackageIndex() != uint(pkg) { 15 | t.Fatalf("id(%x,%x,%x) package mismatch:\nhave: %d\nwant: %d\nbits: %x", 16 | pkg, class, member, id.PackageIndex(), pkg, id) 17 | } 18 | if id.ClassIndex() != uint(class) { 19 | t.Fatalf("id(%x,%x,%x) class mismatch:\nhave: %d\nwant: %d\nbits: %x", 20 | pkg, class, member, id.ClassIndex(), class, id) 21 | } 22 | if id.MemberIndex() != uint(member) { 23 | t.Fatalf("id(%x,%x,%x) member mismatch:\nhave: %d\nwant: %d\nbits: %x", 24 | pkg, class, member, id.MemberIndex(), member, id) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /vmdat/vmdat.go: -------------------------------------------------------------------------------- 1 | package vmdat 2 | 3 | import ( 4 | "sort" 5 | "unsafe" 6 | 7 | "github.com/quasilyte/go-jdk/jclass" 8 | "github.com/quasilyte/go-jdk/symbol" 9 | ) 10 | 11 | type State struct { 12 | Packages []*Package 13 | pkgname2index map[string]uint32 14 | GoFuncs map[string]uintptr 15 | } 16 | 17 | func (st *State) Init() { 18 | st.Packages = make([]*Package, 0, 32) 19 | st.GoFuncs = map[string]uintptr{} 20 | st.pkgname2index = map[string]uint32{} 21 | } 22 | 23 | func (st *State) FindPackage(name string) *Package { 24 | index, ok := st.pkgname2index[name] 25 | if !ok { 26 | return nil 27 | } 28 | return st.Packages[index] 29 | } 30 | 31 | func (st *State) BindGoFunc(name string, fn interface{}) { 32 | // emptyInterface is the header for an interface{} value. 33 | type emptyInterface struct { 34 | typ uintptr 35 | value *uintptr 36 | } 37 | e := (*emptyInterface)(unsafe.Pointer(&fn)) 38 | addr := *e.value 39 | st.GoFuncs[name] = addr 40 | } 41 | 42 | func (st *State) NewPackage(name string) *Package { 43 | index := uint32(len(st.Packages)) 44 | pkg := &Package{ 45 | ID: index, 46 | Name: name, 47 | } 48 | st.pkgname2index[name] = index 49 | st.Packages = append(st.Packages, pkg) 50 | return pkg 51 | } 52 | 53 | type Package struct { 54 | ID uint32 55 | Name string 56 | Classes []Class 57 | } 58 | 59 | type Class struct { 60 | Name string 61 | Methods []Method 62 | } 63 | 64 | type Method struct { 65 | Name string 66 | Descriptor string 67 | AccessFlags jclass.MethodAccessFlags 68 | FrameSlots int 69 | ID symbol.ID 70 | Code []byte 71 | } 72 | 73 | func (p *Package) FindClass(name string) *Class { 74 | i := sort.Search(len(p.Classes), func(i int) bool { 75 | return p.Classes[i].Name >= name 76 | }) 77 | if i >= len(p.Classes) || p.Classes[i].Name != name { 78 | return nil // Not found 79 | } 80 | return &p.Classes[i] 81 | } 82 | 83 | func (c *Class) FindMethod(name, descriptor string) *Method { 84 | // Can use binary search because methods are sorted by name. 85 | i := sort.Search(len(c.Methods), func(i int) bool { 86 | return c.Methods[i].Name >= name 87 | }) 88 | if i >= len(c.Methods) || c.Methods[i].Name != name { 89 | return nil // Not found 90 | } 91 | // Prefer a method that matches a specified descriptor. 92 | for j := i; j < len(c.Methods) && c.Methods[j].Name == name; j++ { 93 | if c.Methods[j].Descriptor == descriptor { 94 | return &c.Methods[j] 95 | } 96 | } 97 | // As a special case, allow "" to match any signature. 98 | if descriptor == "" { 99 | return &c.Methods[i] 100 | } 101 | return nil 102 | } 103 | --------------------------------------------------------------------------------