implements PR-033
This commit is contained in:
parent
29efbe05bb
commit
a9304f2515
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user