From 0df936e36fadcc6febdc2ea2164b4919131c2bc2 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Sat, 7 Mar 2026 18:29:54 +0000 Subject: [PATCH] implements PR-O3.2 --- .../backend/bytecode/BytecodeEmitter.java | 35 +++++++++++- .../backend/bytecode/BytecodeEmitterTest.java | 55 +++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java index 0b1510ee..76fc7964 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/bytecode/BytecodeEmitter.java @@ -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(inputPlan.functions().size()); final var orderedSyscalls = new LinkedHashMap(); final var syscallIndexByIdentity = new LinkedHashMap(); @@ -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 dedupeConstPool( + final ReadOnlyList input) { + if (input == null || input.isEmpty()) { + return ReadOnlyList.empty(); + } + final var ordered = new LinkedHashSet(input.size()); + for (final var constant : input) { + if (constant != null) { + ordered.add(constant); + } + } + return ReadOnlyList.wrap(ordered.stream().toList()); + } + + private ReadOnlyList dedupeExports( + final ReadOnlyList input) { + if (input == null || input.isEmpty()) { + return ReadOnlyList.empty(); + } + final var ordered = new LinkedHashSet(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, diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeEmitterTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeEmitterTest.java index 197506e5..3d2889e5 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeEmitterTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/backend/bytecode/BytecodeEmitterTest.java @@ -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; }