implements PR-033

This commit is contained in:
bQUARKz 2026-03-07 16:21:28 +00:00
parent 29efbe05bb
commit a9304f2515
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
3 changed files with 176 additions and 1 deletions

View File

@ -0,0 +1,93 @@
package p.studio.compiler.backend.bytecode;
import p.studio.utilities.structures.ReadOnlyList;
import java.util.ArrayList;
import java.util.Objects;
public final class BytecodeFunctionLayoutBuilder {
private BytecodeFunctionLayoutBuilder() {
}
public static LayoutResult build(
final ReadOnlyList<FunctionFragment> functions) {
final var inputFunctions = functions == null ? ReadOnlyList.<FunctionFragment>empty() : functions;
final var metas = new ArrayList<BytecodeModule.FunctionMeta>(inputFunctions.size());
final var names = new ArrayList<BytecodeModule.FunctionName>(inputFunctions.size());
final var spans = new ArrayList<BytecodeModule.PcToSpan>();
var totalCodeLen = 0;
for (var i = 0; i < inputFunctions.size(); i++) {
final var fn = inputFunctions.get(i);
final var fnCode = fn.code();
metas.add(new BytecodeModule.FunctionMeta(
totalCodeLen,
fnCode.length,
fn.paramSlots(),
fn.localSlots(),
fn.returnSlots(),
fn.maxStackSlots()));
names.add(new BytecodeModule.FunctionName(i, fn.name()));
for (final var span : fn.instructionSpans()) {
if (span.functionPc() < 0 || span.functionPc() >= fnCode.length) {
throw new BytecodeMarshalingException(
BytecodeMarshalingErrorCode.MARSHAL_FORMAT_PC_SPAN_OUT_OF_BOUNDS,
"pc_to_span offset is out of bounds for function: " + fn.name());
}
spans.add(new BytecodeModule.PcToSpan(
totalCodeLen + span.functionPc(),
span.span()));
}
totalCodeLen += fnCode.length;
}
final var flattened = new byte[totalCodeLen];
var cursor = 0;
for (final var fn : inputFunctions) {
final var fnCode = fn.code();
System.arraycopy(fnCode, 0, flattened, cursor, fnCode.length);
cursor += fnCode.length;
}
return new LayoutResult(
flattened,
ReadOnlyList.wrap(metas),
new BytecodeModule.DebugInfo(
ReadOnlyList.wrap(spans),
ReadOnlyList.wrap(names)));
}
public record FunctionFragment(
String name,
byte[] code,
int paramSlots,
int localSlots,
int returnSlots,
int maxStackSlots,
ReadOnlyList<InstructionSpan> instructionSpans) {
public FunctionFragment {
Objects.requireNonNull(name, "name");
code = code == null ? new byte[0] : code.clone();
instructionSpans = instructionSpans == null ? ReadOnlyList.empty() : instructionSpans;
}
}
public record InstructionSpan(
int functionPc,
BytecodeModule.SourceSpan span) {
public InstructionSpan {
Objects.requireNonNull(span, "span");
}
}
public record LayoutResult(
byte[] code,
ReadOnlyList<BytecodeModule.FunctionMeta> functions,
BytecodeModule.DebugInfo debugInfo) {
public LayoutResult {
code = code == null ? new byte[0] : code.clone();
functions = functions == null ? ReadOnlyList.empty() : functions;
}
}
}

View File

@ -4,5 +4,5 @@ public enum BytecodeMarshalingErrorCode {
MARSHAL_FORMAT_STRING_TOO_LONG,
MARSHAL_FORMAT_SYSC_MODULE_TOO_LONG,
MARSHAL_FORMAT_SYSC_NAME_TOO_LONG,
MARSHAL_FORMAT_PC_SPAN_OUT_OF_BOUNDS,
}

View File

@ -0,0 +1,82 @@
package p.studio.compiler.backend.bytecode;
import org.junit.jupiter.api.Test;
import p.studio.utilities.structures.ReadOnlyList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class BytecodeFunctionLayoutBuilderTest {
@Test
void buildMustProduceMonotonicOffsets() {
final var layout = BytecodeFunctionLayoutBuilder.build(ReadOnlyList.from(
new BytecodeFunctionLayoutBuilder.FunctionFragment(
"main",
new byte[] { 1, 2, 3 },
1,
2,
0,
4,
ReadOnlyList.empty()),
new BytecodeFunctionLayoutBuilder.FunctionFragment(
"aux",
new byte[] { 4, 5 },
0,
1,
1,
2,
ReadOnlyList.empty())));
assertEquals(2, layout.functions().size());
assertEquals(0, layout.functions().get(0).codeOffset());
assertEquals(3, layout.functions().get(0).codeLen());
assertEquals(3, layout.functions().get(1).codeOffset());
assertEquals(2, layout.functions().get(1).codeLen());
assertEquals(5, layout.code().length);
}
@Test
void buildMustEmitDebugMinimum() {
final var layout = BytecodeFunctionLayoutBuilder.build(ReadOnlyList.from(
new BytecodeFunctionLayoutBuilder.FunctionFragment(
"main",
new byte[] { 8, 9 },
0,
0,
0,
1,
ReadOnlyList.from(
new BytecodeFunctionLayoutBuilder.InstructionSpan(
0,
new BytecodeModule.SourceSpan(10, 0, 1)),
new BytecodeFunctionLayoutBuilder.InstructionSpan(
1,
new BytecodeModule.SourceSpan(10, 2, 3))))));
assertEquals(1, layout.debugInfo().functionNames().size());
assertEquals("main", layout.debugInfo().functionNames().getFirst().name());
assertEquals(2, layout.debugInfo().pcToSpan().size());
assertEquals(0, layout.debugInfo().pcToSpan().get(0).pc());
assertEquals(1, layout.debugInfo().pcToSpan().get(1).pc());
}
@Test
void buildMustRejectOutOfBoundsDebugPc() {
final var thrown = assertThrows(BytecodeMarshalingException.class, () -> BytecodeFunctionLayoutBuilder.build(ReadOnlyList.from(
new BytecodeFunctionLayoutBuilder.FunctionFragment(
"main",
new byte[] { 1 },
0,
0,
0,
1,
ReadOnlyList.from(
new BytecodeFunctionLayoutBuilder.InstructionSpan(
2,
new BytecodeModule.SourceSpan(1, 0, 1)))))));
assertEquals(BytecodeMarshalingErrorCode.MARSHAL_FORMAT_PC_SPAN_OUT_OF_BOUNDS, thrown.code());
}
}