implements PR-O3.2

This commit is contained in:
bQUARKz 2026-03-07 18:29:54 +00:00
parent a38fedc591
commit 0df936e36f
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 88 additions and 2 deletions

View File

@ -6,6 +6,7 @@ import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.Objects;
@ -23,6 +24,8 @@ public class BytecodeEmitter {
public BytecodeModule emit(
final EmissionPlan plan) {
final var inputPlan = plan == null ? EmissionPlan.empty() : plan;
final var canonicalConstPool = dedupeConstPool(inputPlan.constPool());
final var canonicalExports = dedupeExports(inputPlan.exports());
final var functionFragments = new ArrayList<BytecodeFunctionLayoutBuilder.FunctionFragment>(inputPlan.functions().size());
final var orderedSyscalls = new LinkedHashMap<SyscallIdentity, BytecodeModule.SyscallDecl>();
final var syscallIndexByIdentity = new LinkedHashMap<SyscallIdentity, Integer>();
@ -101,11 +104,11 @@ public class BytecodeEmitter {
final var layout = BytecodeFunctionLayoutBuilder.build(ReadOnlyList.wrap(functionFragments));
return new BytecodeModule(
inputPlan.version(),
inputPlan.constPool(),
canonicalConstPool,
layout.functions(),
layout.code(),
layout.debugInfo(),
inputPlan.exports(),
canonicalExports,
ReadOnlyList.wrap(orderedSyscalls.values()));
}
@ -125,6 +128,34 @@ public class BytecodeEmitter {
return span;
}
private ReadOnlyList<BytecodeModule.ConstantPoolEntry> dedupeConstPool(
final ReadOnlyList<BytecodeModule.ConstantPoolEntry> input) {
if (input == null || input.isEmpty()) {
return ReadOnlyList.empty();
}
final var ordered = new LinkedHashSet<BytecodeModule.ConstantPoolEntry>(input.size());
for (final var constant : input) {
if (constant != null) {
ordered.add(constant);
}
}
return ReadOnlyList.wrap(ordered.stream().toList());
}
private ReadOnlyList<BytecodeModule.Export> dedupeExports(
final ReadOnlyList<BytecodeModule.Export> input) {
if (input == null || input.isEmpty()) {
return ReadOnlyList.empty();
}
final var ordered = new LinkedHashSet<BytecodeModule.Export>(input.size());
for (final var export : input) {
if (export != null) {
ordered.add(export);
}
}
return ReadOnlyList.wrap(ordered.stream().toList());
}
private record SyscallIdentity(
String module,
String name,

View File

@ -6,6 +6,7 @@ import p.studio.utilities.structures.ReadOnlyList;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -167,6 +168,60 @@ class BytecodeEmitterTest {
assertEquals(2, readU32(module.code(), 8));
}
@Test
void emitMustDeduplicateConstPoolAndExportsByFirstOccurrence() {
final var emitter = new BytecodeEmitter();
final var module = emitter.emit(new BytecodeEmitter.EmissionPlan(
0,
ReadOnlyList.from(
new BytecodeModule.Int32Constant(7),
new BytecodeModule.Int32Constant(7),
new BytecodeModule.StringConstant("abc"),
new BytecodeModule.StringConstant("abc")),
ReadOnlyList.from(
new BytecodeModule.Export("main", 0),
new BytecodeModule.Export("main", 0),
new BytecodeModule.Export("aux", 1)),
ReadOnlyList.from(
new BytecodeEmitter.FunctionPlan(
"main",
0,
0,
0,
1,
ReadOnlyList.from(BytecodeEmitter.Operation.ret())))));
assertEquals(2, module.constPool().size());
assertEquals(2, module.exports().size());
assertEquals("main", module.exports().get(0).symbol());
assertEquals("aux", module.exports().get(1).symbol());
}
@Test
void emitMustRemainDeterministicAfterInterning() {
final var emitter = new BytecodeEmitter();
final var plan = new BytecodeEmitter.EmissionPlan(
0,
ReadOnlyList.from(
new BytecodeModule.Int32Constant(1),
new BytecodeModule.Int32Constant(1)),
ReadOnlyList.from(
new BytecodeModule.Export("main", 0),
new BytecodeModule.Export("main", 0)),
ReadOnlyList.from(
new BytecodeEmitter.FunctionPlan(
"main",
0,
0,
0,
1,
ReadOnlyList.from(BytecodeEmitter.Operation.halt()))));
final var first = emitter.emit(plan).serialize();
final var second = emitter.emit(plan).serialize();
assertArrayEquals(first, second);
}
private static int readU16(final byte[] bytes, final int offset) {
return ByteBuffer.wrap(bytes, offset, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
}