Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 92 additions & 24 deletions src/main/java/am/ik/bf/codegen/JvmByteCode6Generator.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -31,6 +32,8 @@ public class JvmByteCode6Generator implements CodeGenerator {

private final ClassConstant targetClass;

private final List<Integer> branchTargets = new ArrayList<>();

private final ClassConstant javaLangSystemClass = constantPool.addClass(constantPool.addUtf8("java/lang/System"));

private final FieldrefConstant systemOutFieldRef = constantPool.addFieldref(javaLangSystemClass,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
}

}
Loading