1 | /* |
2 | |
3 | Derby - Class org.apache.derby.impl.services.bytecode.BCMethod |
4 | |
5 | Copyright 1997, 2005 The Apache Software Foundation or its licensors, as applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | package org.apache.derby.impl.services.bytecode; |
22 | |
23 | import org.apache.derby.iapi.services.compiler.ClassBuilder; |
24 | import org.apache.derby.iapi.services.compiler.MethodBuilder; |
25 | import org.apache.derby.iapi.services.classfile.ClassFormatOutput; |
26 | import org.apache.derby.iapi.services.compiler.LocalField; |
27 | import org.apache.derby.iapi.services.classfile.ClassHolder; |
28 | import org.apache.derby.iapi.services.classfile.ClassMember; |
29 | |
30 | import org.apache.derby.iapi.services.sanity.SanityManager; |
31 | |
32 | import org.apache.derby.iapi.services.classfile.VMDescriptor; |
33 | import org.apache.derby.iapi.services.classfile.VMOpcode; |
34 | |
35 | import java.lang.reflect.Modifier; |
36 | import java.util.Vector; |
37 | import java.io.IOException; |
38 | |
39 | /** |
40 | * MethodBuilder is used to piece together a method when |
41 | * building a java class definition. |
42 | * <p> |
43 | * When a method is first created, it has: |
44 | * <ul> |
45 | * <li> a return type |
46 | * <li> modifiers |
47 | * <li> a name |
48 | * <li> an empty parameter list |
49 | * <li> an empty throws list |
50 | * <li> an empty statement block |
51 | * </ul> |
52 | * <p> |
53 | * MethodBuilder implementations are required to supply a way for |
54 | * Statements and Expressions to give them code. Most typically, they may have |
55 | * a stream to which their contents writes the code that is of |
56 | * the type to satisfy what the contents represent. |
57 | * MethodBuilder implementations also have to have a way to supply |
58 | * ClassBuilders with their code, that satisfies the type of class |
59 | * builder they are implemented with. This is implementation-dependent, |
60 | * so ClassBuilders, MethodBuilders, Statements, and Expressions all have |
61 | * to be of the same implementation in order to interact to generate a class. |
62 | * <p> |
63 | * Method Builder implementation for generating bytecode. |
64 | * |
65 | */ |
66 | class BCMethod implements MethodBuilder { |
67 | |
68 | /** |
69 | * Code length at which to split into sub-methods. |
70 | * Normally set to the maximim code length the |
71 | * JVM can support, but for testing the split code |
72 | * it can be reduced so that the standard tests |
73 | * cause some splitting. Tested with value set to 2000. |
74 | */ |
75 | static final int CODE_SPLIT_LENGTH = VMOpcode.MAX_CODE_LENGTH; |
76 | |
77 | final BCClass cb; |
78 | protected final ClassHolder modClass; // the class it is in (modifiable fmt) |
79 | private final String myReturnType; |
80 | |
81 | /** |
82 | * The original name of the method, this |
83 | * represents how any user would call this method. |
84 | */ |
85 | private final String myName; |
86 | |
87 | /** |
88 | * Fast access for the parametes, will be null |
89 | * if the method has no parameters. |
90 | */ |
91 | BCLocalField[] parameters; |
92 | |
93 | /** |
94 | * List of parameter types with java language class names. |
95 | * Can be null or zero length for no parameters. |
96 | */ |
97 | private final String[] parameterTypes; |
98 | |
99 | |
100 | Vector thrownExceptions; // expected to be names of Classes under Throwable |
101 | |
102 | CodeChunk myCode; |
103 | protected ClassMember myEntry; |
104 | |
105 | private int currentVarNum; |
106 | private int statementNum; |
107 | |
108 | /** |
109 | * True if we are currently switching control |
110 | * over to a sub method to avoid hitting the code generation |
111 | * limit of 65535 bytes per method. |
112 | */ |
113 | private boolean handlingOverflow; |
114 | |
115 | /** |
116 | * How many sub-methods we have overflowed to. |
117 | */ |
118 | private int subMethodCount; |
119 | |
120 | BCMethod(ClassBuilder cb, |
121 | String returnType, |
122 | String methodName, |
123 | int modifiers, |
124 | String[] parms, |
125 | BCJava factory) { |
126 | |
127 | this.cb = (BCClass) cb; |
128 | modClass = this.cb.modify(); |
129 | myReturnType = returnType; |
130 | myName = methodName; |
131 | |
132 | if (SanityManager.DEBUG) { |
133 | this.cb.validateType(returnType); |
134 | } |
135 | |
136 | // if the method is not static, allocate for "this". |
137 | if ((modifiers & Modifier.STATIC) == 0 ) |
138 | currentVarNum = 1; |
139 | |
140 | String[] vmParamterTypes; |
141 | |
142 | if (parms != null && parms.length != 0) { |
143 | int len = parms.length; |
144 | vmParamterTypes = new String[len]; |
145 | parameters = new BCLocalField[len]; |
146 | for (int i = 0; i < len; i++) { |
147 | Type t = factory.type(parms[i]); |
148 | parameters[i] = new BCLocalField(t, currentVarNum); |
149 | currentVarNum += t.width(); |
150 | |
151 | // convert to vmname for the BCMethodDescriptor.get() call |
152 | vmParamterTypes[i] = t.vmName(); |
153 | } |
154 | } |
155 | else |
156 | vmParamterTypes = BCMethodDescriptor.EMPTY; |
157 | |
158 | // create a code attribute |
159 | String sig = BCMethodDescriptor.get(vmParamterTypes, factory.type(returnType).vmName(), factory); |
160 | |
161 | // stuff the completed information into the class. |
162 | myEntry = modClass.addMember(methodName, sig, modifiers); |
163 | |
164 | // get code chunk |
165 | myCode = new CodeChunk(); |
166 | |
167 | parameterTypes = parms; |
168 | } |
169 | // |
170 | // MethodBuilder interface |
171 | // |
172 | |
173 | /** |
174 | * Return the logical name of the method. The current |
175 | * myEntry refers to the sub method we are currently |
176 | * overflowing to. Those sub-methods are hidden from any caller. |
177 | */ |
178 | public String getName() { |
179 | return myName; |
180 | } |
181 | |
182 | public void getParameter(int id) { |
183 | |
184 | int num = parameters[id].cpi; |
185 | short typ = parameters[id].type.vmType(); |
186 | if (num < 4) |
187 | myCode.addInstr((short) (CodeChunk.LOAD_VARIABLE_FAST[typ]+num)); |
188 | else |
189 | myCode.addInstrWide(CodeChunk.LOAD_VARIABLE[typ], num); |
190 | |
191 | growStack(parameters[id].type); |
192 | } |
193 | |
194 | /** |
195 | * a throwable can be added to the end of |
196 | * the list of thrownExceptions. |
197 | */ |
198 | public void addThrownException(String exceptionClass) { |
199 | |
200 | // cannot add exceptions after code generation has started. |
201 | // Allowing this would cause the method overflow/split to |
202 | // break as the top-level method would not have the exception |
203 | // added in the sub method. |
204 | if (SanityManager.DEBUG) |
205 | { |
206 | if (myCode.getPC() != 0) |
207 | SanityManager.THROWASSERT("Adding exception after code generation " + exceptionClass |
208 | + " to method " + getName()); |
209 | } |
210 | |
211 | if (thrownExceptions == null) |
212 | thrownExceptions = new Vector(); |
213 | thrownExceptions.addElement(exceptionClass); |
214 | } |
215 | |
216 | /** |
217 | * when the method has had all of its parameters |
218 | * and thrown exceptions defined, and its statement |
219 | * block has been completed, it can be completed and |
220 | * its class file information generated. |
221 | * <p> |
222 | * further alterations of the method will not be |
223 | * reflected in the code generated for it. |
224 | */ |
225 | public void complete() { |
226 | |
227 | // myCode.getPC() gives the code length since |
228 | // the program counter will be positioned after |
229 | // the last instruction. Note this value can |
230 | // be changed by the splitMethod call. |
231 | |
232 | if (myCode.getPC() > CODE_SPLIT_LENGTH) |
233 | splitMethod(); |
234 | |
235 | // write exceptions attribute info |
236 | writeExceptions(); |
237 | |
238 | // get the code attribute to put itself into the class |
239 | // provide the final header information needed |
240 | myCode.complete(this, modClass, myEntry, maxStack, currentVarNum); |
241 | } |
242 | |
243 | /** |
244 | * Attempt to split a large method by pushing code out to several |
245 | * sub-methods. Performs a number of steps. |
246 | * <OL> |
247 | * <LI> Split at zero stack depth. |
248 | * <LI> Split at non-zero stack depth (FUTURE) |
249 | * </OL> |
250 | * |
251 | * If the class has already exceeded some limit in building the |
252 | * class file format structures then don't attempt to split. |
253 | * Most likely the number of constant pool entries has been exceeded |
254 | * and thus the built class file no longer has integrity. |
255 | * The split code relies on being able to read the in-memory |
256 | * version of the class file in order to determine descriptors |
257 | * for methods and fields. |
258 | */ |
259 | private void splitMethod() { |
260 | |
261 | int split_pc = 0; |
262 | for (int codeLength = myCode.getPC(); |
263 | (cb.limitMsg == null) && |
264 | (codeLength > CODE_SPLIT_LENGTH); |
265 | codeLength = myCode.getPC()) |
266 | { |
267 | int lengthToCheck = codeLength - split_pc; |
268 | |
269 | int optimalMinLength; |
270 | if (codeLength < CODE_SPLIT_LENGTH * 2) { |
271 | // minimum required |
272 | optimalMinLength = codeLength - CODE_SPLIT_LENGTH; |
273 | } else { |
274 | // try to split as much as possible |
275 | // need one for the return instruction |
276 | optimalMinLength = CODE_SPLIT_LENGTH - 1; |
277 | } |
278 | |
279 | if (optimalMinLength > lengthToCheck) |
280 | optimalMinLength = lengthToCheck; |
281 | |
282 | split_pc = myCode.splitZeroStack(this, modClass, split_pc, |
283 | lengthToCheck, optimalMinLength); |
284 | |
285 | // Negative split point returned means that no split |
286 | // was possible. Give up on this approach and goto |
287 | // the next approach (none-yet). |
288 | if (split_pc < 0) { |
289 | break; |
290 | } |
291 | |
292 | // success, continue on splitting after the call to the |
293 | // sub-method if the method still execeeds the maximum length. |
294 | } |
295 | } |
296 | |
297 | /* |
298 | * class interface |
299 | */ |
300 | |
301 | /** |
302 | * In their giveCode methods, the parts of the method body will want to get |
303 | * to the constant pool to add their constants. We really only want them |
304 | * treating it like a constant pool inclusion mechanism, we could write a |
305 | * wrapper to limit it to that. |
306 | */ |
307 | ClassHolder constantPool() { |
308 | return modClass; |
309 | } |
310 | |
311 | |
312 | // |
313 | // Class implementation |
314 | // |
315 | |
316 | |
317 | /** |
318 | * sets exceptionBytes to the attribute_info needed |
319 | * for a method's Exceptions attribute. |
320 | * The ClassUtilities take care of the header 6 bytes for us, |
321 | * so they are not included here. |
322 | * See The Java Virtual Machine Specification Section 4.7.5, |
323 | * Exceptions attribute. |
324 | */ |
325 | protected void writeExceptions() { |
326 | if (thrownExceptions == null) |
327 | return; |
328 | |
329 | int numExc = thrownExceptions.size(); |
330 | |
331 | // don't write an Exceptions attribute if there are no exceptions. |
332 | if (numExc != 0) { |
333 | |
334 | try{ |
335 | ClassFormatOutput eout = new ClassFormatOutput((numExc * 2) + 2); |
336 | |
337 | eout.putU2(numExc); // number_of_exceptions |
338 | |
339 | for (int i = 0; i < numExc; i++) { |
340 | // put each exception into the constant pool |
341 | String e = thrownExceptions.elementAt(i).toString(); |
342 | int ei2 = modClass.addClassReference(e); |
343 | |
344 | // add constant pool index to exception attribute_info |
345 | eout.putU2(ei2); |
346 | } |
347 | |
348 | myEntry.addAttribute("Exceptions", eout); |
349 | |
350 | } catch (IOException ioe) { |
351 | } |
352 | } |
353 | } |
354 | |
355 | /* |
356 | ** New push compiler api. |
357 | */ |
358 | |
359 | /** |
360 | * Array of the current types of the values on the stack. |
361 | * A type that types up two words on the stack, e.g. double |
362 | * will only occupy one element in this array. |
363 | * This array is dynamically re-sized as needed. |
364 | */ |
365 | private Type[] stackTypes = new Type[8]; |
366 | |
367 | /** |
368 | * Points to the next array offset in stackTypes |
369 | * to be used. Really it's the number of valid entries |
370 | * in stackTypes. |
371 | */ |
372 | private int stackTypeOffset; |
373 | |
374 | /** |
375 | * Maximum stack depth seen in this method, measured in words. |
376 | * Corresponds to max_stack in the Code attribute of section 4.7.3 |
377 | * of the vm spec. |
378 | */ |
379 | int maxStack; |
380 | |
381 | /** |
382 | * Current stack depth in this method, measured in words. |
383 | */ |
384 | private int stackDepth; |
385 | |
386 | private void growStack(int size, Type type) { |
387 | stackDepth += size; |
388 | if (stackDepth > maxStack) |
389 | maxStack = stackDepth; |
390 | |
391 | if (stackTypeOffset >= stackTypes.length) { |
392 | |
393 | Type[] newStackTypes = new Type[stackTypes.length + 8]; |
394 | System.arraycopy(stackTypes, 0, newStackTypes, 0, stackTypes.length); |
395 | stackTypes = newStackTypes; |
396 | } |
397 | |
398 | stackTypes[stackTypeOffset++] = type; |
399 | |
400 | if (SanityManager.DEBUG) { |
401 | |
402 | int sum = 0; |
403 | for (int i = 0 ; i < stackTypeOffset; i++) { |
404 | sum += stackTypes[i].width(); |
405 | } |
406 | if (sum != stackDepth) { |
407 | SanityManager.THROWASSERT("invalid stack depth " + stackDepth + " calc " + sum); |
408 | } |
409 | } |
410 | } |
411 | |
412 | private void growStack(Type type) { |
413 | growStack(type.width(), type); |
414 | } |
415 | |
416 | private Type popStack() { |
417 | stackTypeOffset--; |
418 | Type topType = stackTypes[stackTypeOffset]; |
419 | stackDepth -= topType.width(); |
420 | return topType; |
421 | |
422 | } |
423 | |
424 | private Type[] copyStack() |
425 | { |
426 | Type[] stack = new Type[stackTypeOffset]; |
427 | System.arraycopy(stackTypes, 0, stack, 0, stackTypeOffset); |
428 | return stack; |
429 | } |
430 | |
431 | public void pushThis() { |
432 | myCode.addInstr(VMOpcode.ALOAD_0); |
433 | growStack(1, cb.classType); |
434 | } |
435 | |
436 | public void push(byte value) { |
437 | push(value, Type.BYTE); |
438 | } |
439 | |
440 | public void push(boolean value) { |
441 | push(value ? 1 : 0, Type.BOOLEAN); |
442 | } |
443 | |
444 | public void push(short value) { |
445 | push(value, Type.SHORT); |
446 | } |
447 | |
448 | public void push(int value) { |
449 | push(value, Type.INT); |
450 | } |
451 | |
452 | public void dup() { |
453 | Type dup = popStack(); |
454 | myCode.addInstr(dup.width() == 2 ? VMOpcode.DUP2 : VMOpcode.DUP); |
455 | growStack(dup); |
456 | growStack(dup); |
457 | |
458 | } |
459 | |
460 | public void swap() { |
461 | |
462 | // have A,B |
463 | // want B,A |
464 | |
465 | Type wB = popStack(); |
466 | Type wA = popStack(); |
467 | growStack(wB); |
468 | growStack(wA); |
469 | |
470 | if (wB.width() == 1) { |
471 | // top value is one word |
472 | if (wA.width() == 1) { |
473 | myCode.addInstr(VMOpcode.SWAP); |
474 | return; |
475 | } else { |
476 | myCode.addInstr(VMOpcode.DUP_X2); |
477 | myCode.addInstr(VMOpcode.POP); |
478 | } |
479 | } else { |
480 | // top value is two words |
481 | if (wA.width() == 1) { |
482 | myCode.addInstr(VMOpcode.DUP2_X1); |
483 | myCode.addInstr(VMOpcode.POP2); |
484 | } else { |
485 | myCode.addInstr(VMOpcode.DUP2_X2); |
486 | myCode.addInstr(VMOpcode.POP2); |
487 | } |
488 | } |
489 | |
490 | // all except the simple swap push an extra |
491 | // copy of B which needs to be popped. |
492 | growStack(wB); |
493 | popStack(); |
494 | |
495 | } |
496 | |
497 | /** |
498 | * Push an integer value. Uses the special integer opcodes |
499 | * for the constants -1 to 5, BIPUSH for values that fit in |
500 | * a byte and SIPUSH for values that fit in a short. Otherwise |
501 | * uses LDC with a constant pool entry. |
502 | * |
503 | * @param value Value to be pushed |
504 | * @param type Final type of the value. |
505 | */ |
506 | private void push(int value, Type type) { |
507 | |
508 | CodeChunk chunk = myCode; |
509 | |
510 | if (value >= -1 && value <= 5) |
511 | chunk.addInstr((short)(VMOpcode.ICONST_0+value)); |
512 | else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) |
513 | chunk.addInstrU1(VMOpcode.BIPUSH,value); |
514 | else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) |
515 | chunk.addInstrU2(VMOpcode.SIPUSH,value); |
516 | else { |
517 | int cpe = modClass.addConstant(value); |
518 | addInstrCPE(VMOpcode.LDC, cpe); |
519 | } |
520 | growStack(type.width(), type); |
521 | |
522 | } |
523 | |
524 | /** |
525 | * Push a long value onto the stack. |
526 | * For the values zero and one the LCONST_0 and |
527 | * LCONST_1 instructions are used. |
528 | * For values betwee Short.MIN_VALUE and Short.MAX_VALUE |
529 | * inclusive an byte/short/int value is pushed |
530 | * using push(int, Type) followed by an I2L instruction. |
531 | * This saves using a constant pool entry for such values. |
532 | * All other values use a constant pool entry. For values |
533 | * in the range of an Integer an integer constant pool |
534 | * entry is created to allow sharing with integer constants |
535 | * and to reduce constant pool slot entries. |
536 | */ |
537 | public void push(long value) { |
538 | CodeChunk chunk = myCode; |
539 | |
540 | if (value == 0L || value == 1L) { |
541 | short opcode = value == 0L ? VMOpcode.LCONST_0 : VMOpcode.LCONST_1; |
542 | chunk.addInstr(opcode); |
543 | } else if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) { |
544 | // the push(int, Type) method grows the stack for us. |
545 | push((int) value, Type.LONG); |
546 | chunk.addInstr(VMOpcode.I2L); |
547 | return; |
548 | } else { |
549 | int cpe = modClass.addConstant(value); |
550 | chunk.addInstrU2(VMOpcode.LDC2_W, cpe); |
551 | } |
552 | growStack(2, Type.LONG); |
553 | } |
554 | public void push(float value) { |
555 | |
556 | CodeChunk chunk = myCode; |
557 | |
558 | if (value == 0.0) |
559 | { |
560 | chunk.addInstr(VMOpcode.FCONST_0); |
561 | } |
562 | else if (value == 1.0) |
563 | { |
564 | chunk.addInstr(VMOpcode.FCONST_1); |
565 | } |
566 | else if (value == 2.0) |
567 | { |
568 | chunk.addInstr(VMOpcode.FCONST_2); |
569 | } |
570 | else |
571 | { |
572 | int cpe = modClass.addConstant(value); |
573 | addInstrCPE(VMOpcode.LDC, cpe); |
574 | } |
575 | growStack(1, Type.FLOAT); |
576 | } |
577 | |
578 | public void push(double value) { |
579 | CodeChunk chunk = myCode; |
580 | |
581 | if (value == 0.0) { |
582 | chunk.addInstr(VMOpcode.DCONST_0); |
583 | } |
584 | else { |
585 | int cpe = modClass.addConstant(value); |
586 | chunk.addInstrU2(VMOpcode.LDC2_W, cpe); |
587 | } |
588 | growStack(2, Type.DOUBLE); |
589 | } |
590 | public void push(String value) { |
591 | int cpe = modClass.addConstant(value); |
592 | addInstrCPE(VMOpcode.LDC, cpe); |
593 | growStack(1, Type.STRING); |
594 | } |
595 | |
596 | |
597 | public void methodReturn() { |
598 | |
599 | short opcode; |
600 | if (stackDepth != 0) { |
601 | Type topType = popStack(); |
602 | opcode = CodeChunk.RETURN_OPCODE[topType.vmType()]; |
603 | } else { |
604 | opcode = VMOpcode.RETURN; |
605 | } |
606 | |
607 | myCode.addInstr(opcode); |
608 | |
609 | if (SanityManager.DEBUG) { |
610 | if (stackDepth != 0) |
611 | SanityManager.THROWASSERT("items left on stack " + stackDepth); |
612 | } |
613 | } |
614 | |
615 | public Object describeMethod(short opcode, String declaringClass, String methodName, String returnType) { |
616 | |
617 | Type rt = cb.factory.type(returnType); |
618 | |
619 | String methodDescriptor = BCMethodDescriptor.get(BCMethodDescriptor.EMPTY, rt.vmName(), cb.factory); |
620 | |
621 | if ((declaringClass == null) && (opcode != VMOpcode.INVOKESTATIC)) { |
622 | |
623 | Type dt = stackTypes[stackTypeOffset - 1]; |
624 | |
625 | if (declaringClass == null) |
626 | declaringClass = dt.javaName(); |
627 | } |
628 | |
629 | int cpi = modClass.addMethodReference(declaringClass, methodName, |
630 | methodDescriptor, opcode == VMOpcode.INVOKEINTERFACE); |
631 | |
632 | return new BCMethodCaller(opcode, rt, cpi); |
633 | } |
634 | |
635 | public int callMethod(Object methodDescriptor) { |
636 | |
637 | // pop the reference off the stack |
638 | popStack(); |
639 | |
640 | BCMethodCaller mc = (BCMethodCaller) methodDescriptor; |
641 | |
642 | int cpi = mc.cpi; |
643 | short opcode = mc.opcode; |
644 | |
645 | if (opcode == VMOpcode.INVOKEINTERFACE) { |
646 | myCode.addInstrU2U1U1(opcode, cpi, (short) 1, (short) 0); |
647 | } |
648 | else |
649 | myCode.addInstrU2(opcode, cpi); |
650 | |
651 | // this is the return type of the method |
652 | Type rt = mc.type; |
653 | int rw = rt.width(); |
654 | if (rw != 0) |
655 | growStack(rw, rt); |
656 | else |
657 | { |
658 | if (stackDepth == 0) |
659 | overflowMethodCheck(); |
660 | } |
661 | return cpi; |
662 | } |
663 | |
664 | public int callMethod(short opcode, String declaringClass, String methodName, |
665 | String returnType, int numArgs) { |
666 | |
667 | Type rt = cb.factory.type(returnType); |
668 | |
669 | int initialStackDepth = stackDepth; |
670 | |
671 | // get the array of parameter types |
672 | |
673 | String [] debugParameterTypes = null; |
674 | String[] vmParameterTypes; |
675 | if (numArgs == 0) { |
676 | vmParameterTypes = BCMethodDescriptor.EMPTY; |
677 | } else { |
678 | if (SanityManager.DEBUG) { |
679 | debugParameterTypes = new String[numArgs]; |
680 | } |
681 | vmParameterTypes = new String[numArgs]; |
682 | for (int i = numArgs - 1; i >= 0; i--) { |
683 | Type at = popStack(); |
684 | |
685 | vmParameterTypes[i] = at.vmName(); |
686 | if (SanityManager.DEBUG) { |
687 | debugParameterTypes[i] = at.javaName(); |
688 | } |
689 | } |
690 | } |
691 | |
692 | String methodDescriptor = BCMethodDescriptor.get(vmParameterTypes, rt.vmName(), cb.factory); |
693 | |
694 | Type dt = null; |
695 | if (opcode != VMOpcode.INVOKESTATIC) { |
696 | |
697 | dt = popStack(); |
698 | } |
699 | Type dtu = vmNameDeclaringClass(declaringClass); |
700 | if (dtu != null) |
701 | dt = dtu; |
702 | |
703 | int cpi = modClass.addMethodReference(dt.vmNameSimple, methodName, |
704 | methodDescriptor, opcode == VMOpcode.INVOKEINTERFACE); |
705 | |
706 | if (opcode == VMOpcode.INVOKEINTERFACE) { |
707 | short callArgCount = (short) (initialStackDepth - stackDepth); |
708 | myCode.addInstrU2U1U1(opcode, cpi, callArgCount, (short) 0); |
709 | } |
710 | else |
711 | myCode.addInstrU2(opcode, cpi); |
712 | |
713 | // this is the return type of the method |
714 | int rw = rt.width(); |
715 | if (rw != 0) |
716 | growStack(rw, rt); |
717 | else |
718 | { |
719 | if (stackDepth == 0) |
720 | overflowMethodCheck(); |
721 | } |
722 | // Check the declared type of the method |
723 | if (SanityManager.DEBUG) { |
724 | |
725 | d_BCValidate.checkMethod(opcode, dt, methodName, debugParameterTypes, rt); |
726 | } |
727 | |
728 | return cpi; |
729 | } |
730 | |
731 | private Type vmNameDeclaringClass(String declaringClass) { |
732 | if (declaringClass == null) |
733 | return null; |
734 | return cb.factory.type(declaringClass); |
735 | } |
736 | |
737 | public void callSuper() { |
738 | |
739 | pushThis(); |
740 | callMethod(VMOpcode.INVOKESPECIAL, cb.getSuperClassName(), "<init>", "void", 0); |
741 | } |
742 | |
743 | public void pushNewStart(String className) { |
744 | |
745 | int cpi = modClass.addClassReference(className); |
746 | |
747 | // Use U2, not CPE, since only wide form exists. |
748 | myCode.addInstrU2(VMOpcode.NEW, cpi); |
749 | myCode.addInstr(VMOpcode.DUP); |
750 | |
751 | // Grow the stack twice as we are pushing |
752 | // two instances of newly created reference |
753 | Type nt = cb.factory.type(className); |
754 | growStack(1, nt); |
755 | growStack(1, nt); |
756 | } |
757 | |
758 | public void pushNewComplete(int numArgs) { |
759 | callMethod(VMOpcode.INVOKESPECIAL, (String) null, "<init>", "void", numArgs); |
760 | } |
761 | |
762 | public void upCast(String className) { |
763 | Type uct = cb.factory.type(className); |
764 | |
765 | stackTypes[stackTypeOffset - 1] = uct; |
766 | //popStack(); |
767 | //growStack(1, uct); |
768 | } |
769 | |
770 | public void cast(String className) { |
771 | |
772 | // Perform a simple optimization to not |
773 | // insert a checkcast when the classname |
774 | // of the cast exactly matches the type name |
775 | // currently on the stack. |
776 | // This can reduce the amount of generated code. |
777 | // This compiler/class generator does not load |
778 | // classes to check relationships or any other |
779 | // information. Thus other optimizations where a cast |
780 | // is not required are not implemented. |
781 | Type tbc = stackTypes[stackTypeOffset - 1]; |
782 | |
783 | short sourceType = tbc.vmType(); |
784 | |
785 | if (sourceType == BCExpr.vm_reference) |
786 | { |
787 | // Simple optimize step |
788 | if (className.equals(tbc.javaName())) |
789 | { |
790 | // do nothing, exact matching type |
791 | return; |
792 | } |
793 | } |
794 | |
795 | Type ct = cb.factory.type(className); |
796 | popStack(); |
797 | |
798 | short targetType = ct.vmType(); |
799 | |
800 | if (SanityManager.DEBUG) { |
801 | |
802 | if (!((sourceType == BCExpr.vm_reference && |
803 | targetType == BCExpr.vm_reference) || |
804 | (sourceType != BCExpr.vm_reference && |
805 | targetType != BCExpr.vm_reference))) { |
806 | SanityManager.THROWASSERT("Both or neither must be object types " + ct.javaName() + " " + tbc.javaName()); |
807 | } |
808 | } |
809 | |
810 | // if it is an object type, do a checkcast on it. |
811 | if (sourceType == BCExpr.vm_reference) { |
812 | |
813 | int cpi = modClass.addClassReference(ct.vmNameSimple); |
814 | myCode.addInstrU2(VMOpcode.CHECKCAST, cpi); |
815 | } |
816 | // otherwise, try to convert it. |
817 | else { |
818 | short opcode = VMOpcode.NOP; |
819 | |
820 | // we use the conversionInfo array |
821 | // to determine how to convert; if |
822 | // the result type of the conversion |
823 | // is not our target type, we are not done |
824 | // yet. Make sure there are no |
825 | // infinite loop possibilities in the |
826 | // conversionInfo array! |
827 | while (sourceType!=targetType && opcode!=VMOpcode.BAD) { |
828 | short[] currentConversion = |
829 | CodeChunk.CAST_CONVERSION_INFO[sourceType][targetType]; |
830 | sourceType = currentConversion[1]; |
831 | opcode = currentConversion[0]; |
832 | if (opcode != VMOpcode.NOP) { |
833 | myCode.addInstr(opcode); |
834 | } |
835 | } |
836 | if (SanityManager.DEBUG) { |
837 | SanityManager.ASSERT(opcode != VMOpcode.BAD, |
838 | "BAD VMOpcode not expected in cast"); |
839 | } |
840 | } |
841 | growStack(ct); |
842 | } |
843 | |
844 | public void isInstanceOf(String className) { |
845 | int cpi = modClass.addClassReference(className); |
846 | myCode.addInstrU2(VMOpcode.INSTANCEOF, cpi); |
847 | popStack(); |
848 | growStack(1, Type.BOOLEAN); |
849 | } |
850 | |
851 | public void pushNull(String type) { |
852 | myCode.addInstr(VMOpcode.ACONST_NULL); |
853 | growStack(1, cb.factory.type(type)); |
854 | } |
855 | |
856 | |
857 | public void getField(LocalField field) { |
858 | |
859 | BCLocalField lf = (BCLocalField) field; |
860 | Type lt = lf.type; |
861 | |
862 | pushThis(); |
863 | myCode.addInstrU2(VMOpcode.GETFIELD, lf.cpi); |
864 | |
865 | popStack(); |
866 | growStack(lt); |
867 | |
868 | } |
869 | |
870 | public void getField(String declaringClass, String fieldName, String fieldType) { |
871 | Type dt = popStack(); |
872 | |
873 | Type dtu = vmNameDeclaringClass(declaringClass); |
874 | if (dtu != null) |
875 | dt = dtu; |
876 | |
877 | getField(VMOpcode.GETFIELD, dt.vmNameSimple, fieldName, fieldType); |
878 | } |
879 | /** |
880 | Push the contents of the described static field onto the stack. |
881 | */ |
882 | public void getStaticField(String declaringClass, String fieldName, String fieldType) { |
883 | getField(VMOpcode.GETSTATIC, declaringClass, fieldName, fieldType); |
884 | } |
885 | |
886 | private void getField(short opcode, String declaringClass, String fieldName, String fieldType) { |
887 | |
888 | Type ft = cb.factory.type(fieldType); |
889 | int cpi = modClass.addFieldReference(vmNameDeclaringClass(declaringClass).vmNameSimple, fieldName, ft.vmName()); |
890 | myCode.addInstrU2(opcode, cpi); |
891 | |
892 | growStack(ft); |
893 | } |
894 | |
895 | /** |
896 | * Set the field but don't duplicate its value so |
897 | * nothing is left on the stack after this call. |
898 | */ |
899 | public void setField(LocalField field) { |
900 | BCLocalField lf = (BCLocalField) field; |
901 | Type lt = lf.type; |
902 | |
903 | putField(lf.type, lf.cpi, false); |
904 | |
905 | if (stackDepth == 0) |
906 | overflowMethodCheck(); |
907 | } |
908 | |
909 | /** |
910 | Upon entry the top word(s) on the stack is |
911 | the value to be put into the field. Ie. |
912 | we have |
913 | <PRE> |
914 | word |
915 | </PRE> |
916 | |
917 | Before the call we need |
918 | <PRE> |
919 | word |
920 | this |
921 | word |
922 | </PRE> |
923 | word2,word1 -> word2, word1, word2 |
924 | |
925 | So that we are left with word after the put. |
926 | |
927 | */ |
928 | public void putField(LocalField field) { |
929 | BCLocalField lf = (BCLocalField) field; |
930 | Type lt = lf.type; |
931 | |
932 | putField(lf.type, lf.cpi, true); |
933 | } |
934 | |
935 | /** |
936 | Pop the top stack value and store it in the instance field of this class. |
937 | */ |
938 | public void putField(String fieldName, String fieldType) { |
939 | |
940 | Type ft = cb.factory.type(fieldType); |
941 | int cpi = modClass.addFieldReference(cb.classType.vmNameSimple, fieldName, ft.vmName()); |
942 | |
943 | putField(ft, cpi, true); |
944 | } |
945 | |
946 | private void putField(Type fieldType, int cpi, boolean dup) { |
947 | |
948 | // now have ...,value |
949 | if (dup) |
950 | { |
951 | myCode.addInstr(fieldType.width() == 2 ? VMOpcode.DUP2 : VMOpcode.DUP); |
952 | growStack(fieldType); |
953 | } |
954 | // now have |
955 | // dup true: ...,value,value |
956 | // dup false: ...,value, |
957 | |
958 | pushThis(); |
959 | // now have |
960 | // dup true: ...,value,value,this |
961 | // dup false: ...,value,this |
962 | |
963 | swap(); |
964 | // now have |
965 | // dup true: ...,value,this,value |
966 | // dup false: ...,this,value |
967 | |
968 | myCode.addInstrU2(VMOpcode.PUTFIELD, cpi); |
969 | popStack(); // the value |
970 | popStack(); // this |
971 | |
972 | // now have |
973 | // dup true: ...,value |
974 | // dup false: ... |
975 | } |
976 | /** |
977 | Pop the top stack value and store it in the field. |
978 | This call requires the instance to be pushed by the caller. |
979 | */ |
980 | public void putField(String declaringClass, String fieldName, String fieldType) { |
981 | Type vt = popStack(); |
982 | Type dt = popStack(); |
983 | |
984 | if (SanityManager.DEBUG) { |
985 | if (dt.width() != 1) |
986 | SanityManager.THROWASSERT("reference expected for field access - is " + dt.javaName()); |
987 | } |
988 | |
989 | // have objectref,value |
990 | // need value,objectref,value |
991 | |
992 | myCode.addInstr(vt.width() == 2 ? VMOpcode.DUP2_X1 : VMOpcode.DUP_X1); |
993 | growStack(vt); |
994 | growStack(dt); |
995 | growStack(vt); |
996 | |
997 | Type dtu = vmNameDeclaringClass(declaringClass); |
998 | if (dtu != null) |
999 | dt = dtu; |
1000 | |
1001 | Type ft = cb.factory.type(fieldType); |
1002 | int cpi = modClass.addFieldReference(dt.vmNameSimple, fieldName, ft.vmName()); |
1003 | myCode.addInstrU2(VMOpcode.PUTFIELD, cpi); |
1004 | |
1005 | popStack(); // value |
1006 | popStack(); // reference |
1007 | } |
1008 | |
1009 | public void conditionalIfNull() { |
1010 | |
1011 | conditionalIf(VMOpcode.IFNONNULL); |
1012 | } |
1013 | |
1014 | public void conditionalIf() { |
1015 | conditionalIf(VMOpcode.IFEQ); |
1016 | } |
1017 | |
1018 | private Conditional condition; |
1019 | |
1020 | private void conditionalIf(short opcode) { |
1021 | popStack(); |
1022 | |
1023 | // Save the stack upon entry to the 'then' block of the |
1024 | // 'if' so that we can set up the 'else' block with the |
1025 | // correct stack on entry. |
1026 | |
1027 | condition = new Conditional(condition, myCode, opcode, copyStack()); |
1028 | } |
1029 | |
1030 | public void startElseCode() { |
1031 | |
1032 | // start the else code |
1033 | Type[] entryStack = condition.startElse(this, myCode, copyStack()); |
1034 | |
1035 | for (int i = stackDepth = 0; i < entryStack.length; i++) |
1036 | { |
1037 | stackDepth += (stackTypes[i] = entryStack[i]).width(); |
1038 | } |
1039 | this.stackTypeOffset = entryStack.length; |
1040 | |
1041 | } |
1042 | public void completeConditional() { |
1043 | condition = condition.end(this, myCode, stackTypes, stackTypeOffset); |
1044 | } |
1045 | |
1046 | public void pop() { |
1047 | if (SanityManager.DEBUG) { |
1048 | if (stackDepth == 0) |
1049 | SanityManager.THROWASSERT("pop when stack is empty!"); |
1050 | } |
1051 | Type toPop = popStack(); |
1052 | |
1053 | myCode.addInstr(toPop.width() == 2 ? VMOpcode.POP2 : VMOpcode.POP); |
1054 | |
1055 | if (stackDepth == 0) |
1056 | overflowMethodCheck(); |
1057 | } |
1058 | |
1059 | public void endStatement() { |
1060 | if (stackDepth != 0) { |
1061 | pop(); |
1062 | } |
1063 | |
1064 | //if (SanityManager.DEBUG) { |
1065 | // if (stackDepth != 0) |
1066 | // SanityManager.THROWASSERT("items left on stack " + stackDepth); |
1067 | // } |
1068 | } |
1069 | |
1070 | /** |
1071 | */ |
1072 | public void getArrayElement(int element) { |
1073 | |
1074 | push(element); |
1075 | popStack(); // int just pushed will be popped by array access |
1076 | |
1077 | Type arrayType = popStack(); |
1078 | |
1079 | String arrayJava = arrayType.javaName(); |
1080 | String componentString = arrayJava.substring(0,arrayJava.length()-2); |
1081 | |
1082 | Type componentType = cb.factory.type(componentString); |
1083 | |
1084 | short typ = componentType.vmType(); |
1085 | |
1086 | // boolean has a type id of integer, here it needs to be byte. |
1087 | if ((typ == BCExpr.vm_int) && (componentType.vmName().equals("Z"))) |
1088 | typ = BCExpr.vm_byte; |
1089 | myCode.addInstr(CodeChunk.ARRAY_ACCESS[typ]); |
1090 | |
1091 | growStack(componentType); |
1092 | |
1093 | } |
1094 | // come in with ref, value |
1095 | |
1096 | public void setArrayElement(int element) { |
1097 | |
1098 | // ref, value |
1099 | |
1100 | push(element); |
1101 | |
1102 | // ref, value, index |
1103 | swap(); |
1104 | |
1105 | Type componentType = popStack(); // value |
1106 | popStack(); // int just pushed will be popped by array access |
1107 | |
1108 | popStack(); // array ref. |
1109 | |
1110 | short typ = componentType.vmType(); |
1111 | |
1112 | // boolean has a type id of integer, here it needs to be byte. |
1113 | if ((typ == BCExpr.vm_int) && (componentType.vmName().equals("Z"))) |
1114 | typ = BCExpr.vm_byte; |
1115 | |
1116 | myCode.addInstr(CodeChunk.ARRAY_STORE[typ]); |
1117 | } |
1118 | /** |
1119 | this array maps the BCExpr vm_* constants 0..6 to |
1120 | the expected VM type constants for the newarray instruction. |
1121 | <p> |
1122 | Because boolean was mapped to integer for general instructions, |
1123 | it will have to be specially matched and mapped to its value |
1124 | directly (4). |
1125 | */ |
1126 | private static final byte newArrayElementTypeMap[] = { 8, 9, 10, 11, 6, 7, 5 }; |
1127 | static final byte T_BOOLEAN = 4; |
1128 | /** |
1129 | Create an array instance |
1130 | |
1131 | Stack ... => |
1132 | ...,arrayref |
1133 | */ |
1134 | public void pushNewArray(String className, int size) { |
1135 | |
1136 | push(size); |
1137 | popStack(); // int just pushed will be popped by array creation |
1138 | |
1139 | Type elementType = cb.factory.type(className); |
1140 | |
1141 | // determine the instruction to use based on the element type |
1142 | if (elementType.vmType() == BCExpr.vm_reference) { |
1143 | |
1144 | // For an array of Java class/interface elements, generate: |
1145 | // ANEWARRAY #cpei ; where cpei is a constant pool index for the class |
1146 | |
1147 | int cpi = modClass.addClassReference(elementType.javaName()); |
1148 | // Use U2, not CPE, since only wide form exists. |
1149 | myCode.addInstrU2(VMOpcode.ANEWARRAY, cpi); |
1150 | } else { |
1151 | byte atype; |
1152 | |
1153 | // get the argument for the array type |
1154 | // if the element type is boolean, we can't use the map |
1155 | // because the type id will say integer. |
1156 | // but we can use vm_int test to weed out some tests |
1157 | if (elementType.vmType() == BCExpr.vm_int && |
1158 | VMDescriptor.C_BOOLEAN == elementType.vmName().charAt(0)) |
1159 | atype = T_BOOLEAN; |
1160 | else |
1161 | atype = newArrayElementTypeMap[elementType.vmType()]; |
1162 | |
1163 | // For an array of Java builtin type elements, generate: |
1164 | // NEWARRAY #atype ; where atype is a constant for the builtin type |
1165 | |
1166 | myCode.addInstrU1(VMOpcode.NEWARRAY, atype); |
1167 | } |
1168 | |
1169 | // an array reference is an object, hence width of 1 |
1170 | growStack(1, cb.factory.type(className.concat("[]"))); |
1171 | } |
1172 | |
1173 | /** |
1174 | * Write a instruction that uses a constant pool entry |
1175 | * as an operand, add a limit exceeded message if |
1176 | * the number of constant pool entries has exceeded |
1177 | * the limit. |
1178 | */ |
1179 | private void addInstrCPE(short opcode, int cpe) |
1180 | { |
1181 | if (cpe >= VMOpcode.MAX_CONSTANT_POOL_ENTRIES) |
1182 | cb.addLimitExceeded(this, "constant_pool_count", |
1183 | VMOpcode.MAX_CONSTANT_POOL_ENTRIES, cpe); |
1184 | |
1185 | myCode.addInstrCPE(opcode, cpe); |
1186 | } |
1187 | |
1188 | /** |
1189 | Tell if statement number in this method builder hits limit. This |
1190 | method builder keeps a counter of how many statements are added to it. |
1191 | Caller should call this function every time it tries to add a statement |
1192 | to this method builder (counter is increased by 1), then the function |
1193 | returns whether the accumulated statement number hits a limit. |
1194 | The reason of doing this is that Java compiler has a limit of 64K code |
1195 | size for each method. We might hit this limit if an extremely long |
1196 | insert statement is issued, for example (see beetle 4293). Counting |
1197 | statement number is an approximation without too much overhead. |
1198 | */ |
1199 | public boolean statementNumHitLimit(int noStatementsAdded) |
1200 | { |
1201 | if (statementNum > 2048) // 2K limit |
1202 | { |
1203 | return true; |
1204 | } |
1205 | else |
1206 | { |
1207 | statementNum = statementNum + noStatementsAdded; |
1208 | return false; |
1209 | } |
1210 | } |
1211 | |
1212 | /** |
1213 | * Check to see if the current method byte code is nearing the |
1214 | * limit of 65535. If it is start overflowing to a new method. |
1215 | * <P> |
1216 | * Overflow is handled for a method named e23 as: |
1217 | * <CODE> |
1218 | public Object e23() |
1219 | { |
1220 | ... existing code |
1221 | // split point |
1222 | return e23_0(); |
1223 | } |
1224 | private Object e23_0() |
1225 | { |
1226 | ... first set overflowed code |
1227 | // split point |
1228 | return e23_1(); |
1229 | } |
1230 | private Object e23_1() |
1231 | { |
1232 | ... second set overflowed code |
1233 | // method complete |
1234 | return result; |
1235 | } |
1236 | </CODE> |
1237 | <P> |
1238 | |
1239 | These overflow methods are hidden from the code using this MethodBuilder, |
1240 | it continues to think that it is building a single method with the |
1241 | original name. |
1242 | |
1243 | |
1244 | * <BR> Restrictions: |
1245 | * <UL> |
1246 | * <LI> Only handles methods with no arguments |
1247 | * <LI> Stack depth must be zero |
1248 | * </UL> |
1249 | * |
1250 | * |
1251 | */ |
1252 | private void overflowMethodCheck() |
1253 | { |
1254 | if (handlingOverflow) |
1255 | return; |
1256 | |
1257 | // don't sub method in the middle of a conditional |
1258 | if (condition != null) |
1259 | return; |
1260 | |
1261 | int currentCodeSize = myCode.getPC(); |
1262 | |
1263 | // Overflow at >= 55,000 bytes which is someway |
1264 | // below the limit of 65,535. Ideally overflow |
1265 | // would occur at 65535 minus the few bytes needed |
1266 | // to call the sub-method, but the issue is at this level |
1267 | // we don't know frequently we are called given the restriction |
1268 | // of only being called when the stack depth is zero. |
1269 | // Thus split earlier to try ensure most cases are caught. |
1270 | // Only downside is that we may split into N methods when N-1 would suffice. |
1271 | if (currentCodeSize < 55000) |
1272 | return; |
1273 | |
1274 | // only handle no-arg methods at the moment. |
1275 | if (parameters != null) |
1276 | { |
1277 | if (parameters.length != 0) |
1278 | return; |
1279 | } |
1280 | |
1281 | BCMethod subMethod = getNewSubMethod(myReturnType, false); |
1282 | |
1283 | // stop any recursion |
1284 | handlingOverflow = true; |
1285 | |
1286 | // in this method make a call to the sub method we will |
1287 | // be transferring control to. |
1288 | callSubMethod(subMethod); |
1289 | |
1290 | // and return its value, works just as well for a void method! |
1291 | this.methodReturn(); |
1292 | this.complete(); |
1293 | |
1294 | handlingOverflow = false; |
1295 | |
1296 | // now the tricky bit, make this object take over the |
1297 | // code etc. from the sub method. This is done so |
1298 | // that any code that has a reference to this MethodBuilder |
1299 | // will continue to work. They will be writing code into the |
1300 | // new sub method. |
1301 | |
1302 | this.myEntry = subMethod.myEntry; |
1303 | this.myCode = subMethod.myCode; |
1304 | this.currentVarNum = subMethod.currentVarNum; |
1305 | this.statementNum = subMethod.statementNum; |
1306 | |
1307 | // copy stack info |
1308 | this.stackTypes = subMethod.stackTypes; |
1309 | this.stackTypeOffset = subMethod.stackTypeOffset; |
1310 | this.maxStack = subMethod.maxStack; |
1311 | this.stackDepth = subMethod.stackDepth; |
1312 | } |
1313 | |
1314 | /** |
1315 | * Create a sub-method from this method to allow the code builder to split a |
1316 | * single logical method into multiple methods to avoid the 64k per-method |
1317 | * code size limit. The sub method with inherit the thrown exceptions of |
1318 | * this method. |
1319 | * |
1320 | * @param returnType |
1321 | * Return type of the new method |
1322 | * @param withParameters |
1323 | * True to define the method with matching parameters false to |
1324 | * define it with no parameters. |
1325 | * @return A valid empty sub method. |
1326 | */ |
1327 | final BCMethod getNewSubMethod(String returnType, boolean withParameters) { |
1328 | int modifiers = myEntry.getModifier(); |
1329 | |
1330 | // the sub-method can be private to ensure that no-one |
1331 | // can call it accidentally from outside the class. |
1332 | modifiers &= ~(Modifier.PROTECTED | Modifier.PUBLIC); |
1333 | modifiers |= Modifier.PRIVATE; |
1334 | |
1335 | String subMethodName = myName + "_s" |
1336 | + Integer.toString(subMethodCount++); |
1337 | BCMethod subMethod = (BCMethod) cb.newMethodBuilder(modifiers, |
1338 | returnType, subMethodName, withParameters ? parameterTypes |
1339 | : null); |
1340 | subMethod.thrownExceptions = this.thrownExceptions; |
1341 | |
1342 | return subMethod; |
1343 | } |
1344 | |
1345 | /** |
1346 | * Call a sub-method created by getNewSubMethod handling parameters |
1347 | * correctly. |
1348 | */ |
1349 | final void callSubMethod(BCMethod subMethod) { |
1350 | // in this method make a call to the sub method we will |
1351 | // be transferring control to. |
1352 | short op; |
1353 | if ((myEntry.getModifier() & Modifier.STATIC) == 0) { |
1354 | op = VMOpcode.INVOKEVIRTUAL; |
1355 | this.pushThis(); |
1356 | } else { |
1357 | op = VMOpcode.INVOKESTATIC; |
1358 | } |
1359 | |
1360 | int parameterCount = subMethod.parameters == null ? 0 |
1361 | : subMethod.parameters.length; |
1362 | |
1363 | // push my parameter values for the call. |
1364 | for (int pi = 0; pi < parameterCount; pi++) |
1365 | this.getParameter(pi); |
1366 | |
1367 | this.callMethod(op, modClass.getName(), subMethod.getName(), |
1368 | subMethod.myReturnType, parameterCount); |
1369 | } |
1370 | } |
1371 | |