diff --git a/src/main/java/am/ik/bf/codegen/JvmByteCode6Generator.java b/src/main/java/am/ik/bf/codegen/JvmByteCode6Generator.java index bfbf0b3..13f6d13 100644 --- a/src/main/java/am/ik/bf/codegen/JvmByteCode6Generator.java +++ b/src/main/java/am/ik/bf/codegen/JvmByteCode6Generator.java @@ -1,5 +1,6 @@ package am.ik.bf.codegen; +import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -31,6 +32,8 @@ public class JvmByteCode6Generator implements CodeGenerator { private final ClassConstant targetClass; + private final List branchTargets = new ArrayList<>(); + private final ClassConstant javaLangSystemClass = constantPool.addClass(constantPool.addUtf8("java/lang/System")); private final FieldrefConstant systemOutFieldRef = constantPool.addFieldref(javaLangSystemClass, @@ -59,16 +62,26 @@ public void begin() { Opcode.NEWARRAY, ArrayType.T_INT, Opcode.ASTORE_1, // memory Opcode.ICONST_0, Opcode.ISTORE_2 // pointer )); + + // Clear any previous data when reusing the generator + this.branchTargets.clear(); } @Override public void end() { this.code.add(Opcode.RETURN); + generateStackMapTableAndWriteClass(); + } + private void generateStackMapTableAndWriteClass() { final ClassConstant javaLangObjectClass = constantPool.addClass(constantPool.addUtf8("java/lang/Object")); final Utf8Constant mainUt8 = constantPool.addUtf8("main"); final Utf8Constant javaLangStringArrayType = constantPool.addUtf8("([Ljava/lang/String;)V"); final Utf8Constant codeUtf8 = constantPool.addUtf8("Code"); + final Utf8Constant stackMapTableUtf8 = constantPool.addUtf8("StackMapTable"); + + final ClassConstant intArrayClass = constantPool.addClass(constantPool.addUtf8("[I")); + final ByteArrayOutputStream stackMapTableData = generateMinimalStackMapTable(intArrayClass); new ByteCodeWriter(this.out) // .write(0xca, 0xfe, 0xba, 0xbe) // cafebabe @@ -81,38 +94,92 @@ public void end() { }) .writeMethods(methods -> methods.add(AccessFlag.ACC_PUBLIC + AccessFlag.ACC_STATIC, mainUt8, javaLangStringArrayType, - method -> method.writeAttributes(attributes -> attributes.add(codeUtf8, - attribute -> attribute.writeU2(4) // max_stack - .writeU2(3) // max_locals - .writeCode((Object[]) code.toArray(code.toArray(new Integer[0]))) - .writeU2(0) // exception_table_length - .writeU2(0) // attributes_count - )))) // + method -> method.writeAttributes(attributes -> attributes.add(codeUtf8, attribute -> { + attribute.writeU2(4) // max_stack + .writeU2(3) // max_locals + .writeCode((Object[]) code.toArray(code.toArray(new Integer[0]))) + .writeU2(0) // exception_table_length + .writeAttributes(attrs -> attrs.add(stackMapTableUtf8, attr -> { + attr.write(stackMapTableData.toByteArray()); + })); + })))) // .writeAttributes(attributesDef -> { }); } + private ByteArrayOutputStream generateMinimalStackMapTable(ClassConstant intArrayClass) { + ByteArrayOutputStream stackMapTableData = new ByteArrayOutputStream(); + ByteCodeWriter stackMapWriter = new ByteCodeWriter(stackMapTableData); + + // Simple StackMapTable: initial frame + branch targets + var sortedTargets = branchTargets.stream() + .distinct() + .filter(target -> target > 8) // Only targets after initial setup + .sorted() + .limit(99) // Limit to prevent excessive frames + .toList(); + + // Write the number of frames (initial frame + branch targets) + stackMapWriter.writeU2(sortedTargets.size() + 1); + + // First frame: APPEND frame to establish local variables + stackMapWriter.write(253); // frame_type = append_frame (2 locals) + stackMapWriter.writeU2(6); // offset_delta = 7-1 (at istore_2 instruction) + stackMapWriter.write(7); // ITEM_Object for int[] + stackMapWriter.writeU2(intArrayClass.index()); + stackMapWriter.write(1); // ITEM_Integer for pointer + + // Add frames for all branch targets + int previousOffset = 6; + for (Integer target : sortedTargets) { + int offsetDelta = target - previousOffset - 1; + if (offsetDelta >= 0 && offsetDelta <= 65535) { + stackMapWriter.write(251); // frame_type = same_frame_extended + stackMapWriter.writeU2(offsetDelta); + previousOffset = target; + } + } + + return stackMapTableData; + } + @Override public void generateLoopStatement(LoopStatement statement) { + // Use GOTO_W from the start to handle large offsets + final int loopStart = this.code.size(); + this.code.addAll(List.of( // Opcode.ALOAD_1, // memory Opcode.ILOAD_2, // pointer Opcode.IALOAD, // memory[pointer] - Opcode.IFEQ // + Opcode.IFNE, 0, 8 // jump 8 bytes to skip the GOTO_W if non-zero )); - this.writeBytes(toU2(0)); // replace later - final int indexToReplace = this.code.size() - 2; - final int beforeSize = this.code.size(); + + // Add GOTO_W to exit the loop when value is zero + this.code.add(Opcode.GOTO_W); + final int gotoWExitPosition = this.code.size() - 1; + this.writeBytes(toU4(0)); // Placeholder for exit offset + + // Generate loop body statement.statements().forEach(s -> s.generate(this)); - final int codeLen = this.code.size() - beforeSize; - // ifeq + branchbyte1 + branchbyte2 + goto + branchbyte1 + branchbyte2 = 6 - final int indexIfeq = codeLen + 6; - final byte[] ifeq = toU2(indexIfeq); - for (int i = 0; i < ifeq.length; i++) { - this.code.set(indexToReplace + i, (int) ifeq[i]); - } - this.code.add(Opcode.GOTO); - this.writeBytes(toU2(-indexIfeq)); + + // Add GOTO_W back to loop start + this.code.add(Opcode.GOTO_W); + final int gotoWBackPosition = this.code.size() - 1; + final int gotoWBackOffset = loopStart - gotoWBackPosition; + this.writeBytes(toU4(gotoWBackOffset)); + + // Fix the GOTO_W exit offset + final int endPosition = this.code.size(); + final int gotoWExitOffset = endPosition - gotoWExitPosition; + final byte[] exitBytes = toU4(gotoWExitOffset); + this.code.set(gotoWExitPosition + 1, (int) exitBytes[0]); + this.code.set(gotoWExitPosition + 2, (int) exitBytes[1]); + this.code.set(gotoWExitPosition + 3, (int) exitBytes[2]); + this.code.set(gotoWExitPosition + 4, (int) exitBytes[3]); + + // Record branch target for StackMapTable + this.branchTargets.add(endPosition); } @Override @@ -180,8 +247,9 @@ public void generateIncrementPointerExpression(IncrementPointerExpression expres public void generateSetValueToZeroExpression(ResetValueExpression expression) { this.code.addAll(List.of( // Opcode.ALOAD_1, // memory - Opcode.ILOAD_2, // pointer, - Opcode.IASTORE, Opcode.ICONST_0)); + Opcode.ILOAD_2, // pointer + Opcode.ICONST_0, // value 0 + Opcode.IASTORE)); // memory[pointer] = 0 } private void writeBytes(byte[] bytes) { @@ -190,8 +258,8 @@ private void writeBytes(byte[] bytes) { } } - private static byte[] toU2(int i) { - return ByteBuffer.allocate(2).putShort((short) i).array(); + private static byte[] toU4(int i) { + return ByteBuffer.allocate(4).putInt(i).array(); } }