dev/multi-frontend-serializable-ir #18

Merged
bquarkz merged 7 commits from dev/multi-frontend-serializable-ir into master 2026-07-15 12:47:13 +00:00
4 changed files with 205 additions and 3 deletions
Showing only changes of commit f42c83bdca - Show all commits

View File

@ -6,7 +6,7 @@
{"type":"discussion","id":"DSC-0061","status":"open","ticket":"multi-frontend-sdk-canonical-definition","title":"Auditoria e centralizacao gradual da definicao canonica do SDK","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","sdk","stdlib","hostcalls","intrinsics","multi-frontend"],"agendas":[{"id":"AGD-0064","file":"AGD-0064-multi-frontend-sdk-canonical-definition.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0060","status":"open","ticket":"multi-frontend-validation-boundaries","title":"Separar validacoes de linguagem e validacoes de plataforma","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","backend","validation","multi-frontend"],"agendas":[{"id":"AGD-0063","file":"AGD-0063-multi-frontend-validation-boundaries.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0059","status":"open","ticket":"multi-frontend-common-lifecycle","title":"Extrair lifecycle comum das responsabilidades do frontend PBS","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","lifecycle","backend","multi-frontend"],"agendas":[{"id":"AGD-0062","file":"AGD-0062-multi-frontend-common-lifecycle.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0058","status":"in_progress","ticket":"multi-frontend-serializable-ir","title":"Manter a IR comum serializavel por design","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","ir","backend","serialization","multi-frontend"],"agendas":[{"id":"AGD-0061","file":"AGD-0061-multi-frontend-serializable-ir.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0044","file":"DEC-0044-serializable-common-ir-boundary.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0061"}],"plans":[{"id":"PLN-0119","file":"PLN-0119-document-irbackend-serializability-invariants.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0120","file":"PLN-0120-audit-public-irbackend-contract-shape.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0121","file":"PLN-0121-add-serializable-irbackend-reflection-guardrails.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0122","file":"PLN-0122-prove-deterministic-irbackend-ordering.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0123","file":"PLN-0123-fix-concrete-irbackend-serialization-leaks.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0058","status":"in_progress","ticket":"multi-frontend-serializable-ir","title":"Manter a IR comum serializavel por design","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","ir","backend","serialization","multi-frontend"],"agendas":[{"id":"AGD-0061","file":"AGD-0061-multi-frontend-serializable-ir.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0044","file":"DEC-0044-serializable-common-ir-boundary.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0061"}],"plans":[{"id":"PLN-0119","file":"PLN-0119-document-irbackend-serializability-invariants.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0120","file":"PLN-0120-audit-public-irbackend-contract-shape.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0121","file":"PLN-0121-add-serializable-irbackend-reflection-guardrails.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0122","file":"PLN-0122-prove-deterministic-irbackend-ordering.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]},{"id":"PLN-0123","file":"PLN-0123-fix-concrete-irbackend-serialization-leaks.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0044"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0057","status":"done","ticket":"multi-frontend-frontend-backend-contract","title":"Estabilizar contrato entre frontend e backend comum","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","ir","backend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0059","file":"discussion/lessons/DSC-0057-multi-frontend-frontend-backend-contract/LSN-0059-common-irbackend-handoff-and-backend-guardrails.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
{"type":"discussion","id":"DSC-0056","status":"done","ticket":"multi-frontend-remove-pbs-branches","title":"Generalizar o contrato LSP/editorial para frontends","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","studio","frontend","coupling","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0058","file":"discussion/lessons/DSC-0056-multi-frontend-remove-pbs-branches/LSN-0058-generic-frontend-editorial-contract-for-lsp.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
{"type":"discussion","id":"DSC-0055","status":"done","ticket":"multi-frontend-compiler-vs-language-services","title":"Separar compilacao de servicos editoriais de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","lsp","editor","frontend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0056","file":"discussion/lessons/DSC-0055-multi-frontend-compiler-vs-language-services/LSN-0056-compile-first-frontends-with-optional-editorial-capabilities.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}

View File

@ -2,7 +2,7 @@
id: PLN-0122
ticket: multi-frontend-serializable-ir
title: Prove deterministic IRBackend ordering
status: open
status: done
created: 2026-07-15
ref_decisions: [DEC-0044]
tags: [compiler, compiler-general, ir, backend, serialization, multi-frontend]

View File

@ -43,7 +43,7 @@ to concrete positive/negative test evidence and current status.
| G20-4.1.1 | `IRBackend` MUST be the common frontend-to-backend executable handoff. | `LowerToIRVMServiceTest#lowerMustAcceptManuallyConstructedCommonIRBackend` | N/A | pass | Direct backend lowering from manually constructed common IRBackend proves PBS parser/frontend services are not required. |
| G20-4.1.2 | Common backend code MUST NOT depend on `p.studio.compiler.pbs`. | `CommonBackendArchitectureTest#commonBackendMustNotImportPbsFrontendPackages` | N/A | pass | Source-level architectural guard rejects PBS frontend imports in common backend packages. |
| G20-4.1.3 | Public `IRBackend` contract MUST NOT expose PBS AST, token, parser, semantic, or editorial types. | `IRBackendExecutableContractTest#publicIRBackendContractMustNotExposePbsTypes` | N/A | pass | Reflection guard covers public contract fields, constructors, methods, and record components. |
| G20-4.2.1 | Public `IRBackend` contract types MUST remain modelable as an acyclic, deterministic data graph. | `IRBackendExecutableContractTest#publicIRBackendContractMustExposeOnlySerializableShapes`; planned deterministic ordering tests from `PLN-0122` | N/A | partial | Public shape guardrails exist; deterministic ordering evidence is still planned before this row can move to pass. |
| G20-4.2.1 | Public `IRBackend` contract types MUST remain modelable as an acyclic, deterministic data graph. | `IRBackendExecutableContractTest#publicIRBackendContractMustExposeOnlySerializableShapes`; `IRBackendExecutableContractTest#aggregatorMustEmitDeterministicOrderedPublicContractShape` | N/A | pass | Covers public shape guardrails, generic type arguments, ordered aggregate emission, table remapping, and capability de-duplication order. |
| G20-4.2.2 | Public `IRBackend` contract types MUST NOT expose callbacks, services, mutable/unordered collection contracts, frontend-owned objects, process-dependent lazy values, cyclic public references, or wire-format commitments. | `IRBackendExecutableContractTest#publicIRBackendContractMustExposeOnlySerializableShapes` | N/A | pass | Reflection guard covers public contract fields, constructors, methods, record components, and generic type arguments. |
| G20-4.2.3 | Public `IRBackend` cross-object references MUST use explicit ids or stable symbolic keys. | Planned: public contract audit from `PLN-0120`; targeted leak fixes from `PLN-0123` when needed | N/A | missing | Existing `FileId`, `ModuleId`, `CallableId`, and `IntrinsicId` are expected allowed values when table scope is deterministic. |
| G20-6.2 | `IRVM_EXT` MUST declare structural metadata (`pops/pushes/is_branch/is_terminator`). | `IRVMValidatorTest#validateMustApplyStructuralMetadataForCustomInternalExtension`; `IRVMValidatorTest#validateMustRejectCustomInternalExtensionWhenStructuralMetadataUnderflowsStack`; `IRVMOp` record contract (`pops/pushes/branch/terminator/internal`) | N/A | pass | Dedicated extension fixtures now assert structural metadata is consumed by validation behavior. |

View File

@ -559,6 +559,208 @@ class IRBackendExecutableContractTest {
assertEquals(1, backend.getCallableSignatures().get(1).moduleId().getIndex());
}
@Test
void aggregatorMustEmitDeterministicOrderedPublicContractShape() {
final var first = emitDeterministicAggregateFixture();
final var second = emitDeterministicAggregateFixture();
assertEquals(
List.of("alpha", "beta"),
first.getFunctions().asList().stream().map(IRFunction::name).toList());
assertEquals(
List.of("frameWrapper", "initWrapper"),
first.getSyntheticFunctions().asList().stream().map(IRSyntheticFunction::callableName).toList());
assertEquals(
List.of("state", "timer"),
first.getGlobals().asList().stream().map(IRGlobal::name).toList());
assertEquals(
List.of("frame", "init"),
first.getExecutableFunctions().asList().stream().map(IRBackendExecutableFunction::callableName).toList());
assertEquals(
List.of("app:alpha", "app:beta"),
first.getModulePool().asList().stream()
.map(module -> module.project() + ":" + String.join(".", module.pathSegments().asList()))
.toList());
assertEquals(
List.of("frame", "helper", "init"),
first.getCallableSignatures().asList().stream().map(CallableSignatureRef::callableName).toList());
assertEquals(
List.of("core.draw", "core.color.pack"),
first.getIntrinsicPool().asList().stream().map(IntrinsicReference::canonicalName).toList());
assertEquals(
List.of("draw", "play"),
first.getReservedMetadata().hostMethodBindings().asList().stream()
.map(IRReservedMetadata.HostMethodBinding::abiMethod)
.toList());
assertEquals(
List.of("Sprite", "Sound"),
first.getReservedMetadata().builtinTypeSurfaces().asList().stream()
.map(IRReservedMetadata.BuiltinTypeSurface::sourceTypeName)
.toList());
assertEquals(
List.of("WHITE", "BLACK"),
first.getReservedMetadata().builtinConstSurfaces().asList().stream()
.map(IRReservedMetadata.BuiltinConstSurface::sourceConstName)
.toList());
assertEquals(
List.of("gfx.draw", "audio.play", "input.read"),
first.getReservedMetadata().requiredCapabilities().asList());
assertEquals(first.toString(), second.toString());
assertEquals(
first.getReservedMetadata().requiredCapabilities().asList(),
second.getReservedMetadata().requiredCapabilities().asList());
assertEquals(
first.getExecutableFunctions().get(0).moduleId(),
second.getExecutableFunctions().get(0).moduleId());
assertEquals(
first.getExecutableFunctions().get(1).callableId(),
second.getExecutableFunctions().get(1).callableId());
}
private static IRBackend emitDeterministicAggregateFixture() {
final var aggregator = IRBackend.aggregator();
aggregator.merge(new IRBackendFile(
new FileId(10),
ReadOnlyList.from(new IRFunction(new FileId(10), "alpha", 0, false, new Span(new FileId(10), 0, 5))),
ReadOnlyList.from(new IRSyntheticFunction(
new ModuleId(0),
"frameWrapper",
IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
new IRSyntheticOrigin(new FileId(10), "frame", new Span(new FileId(10), 0, 5)))),
ReadOnlyList.from(new IRGlobal(
new FileId(10),
new ModuleId(0),
"state",
"int",
new Span(new FileId(10), 1, 2))),
ReadOnlyList.from(new IRBackendExecutableFunction(
new FileId(10),
new ModuleId(0),
"frame",
new CallableId(0),
0,
10,
0,
0,
0,
1,
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
new ModuleId(0),
"",
null,
null,
new IRBackendExecutableFunction.IntrinsicCallMetadata("core.draw", 1, new IntrinsicId(0)),
Span.none())),
Span.none())),
new IRReservedMetadata(
ReadOnlyList.from(new IRReservedMetadata.HostMethodBinding(
"Graphics",
"draw",
"gfx",
"draw",
1,
0,
null,
true,
"gfx.draw",
Span.none())),
ReadOnlyList.from(new IRReservedMetadata.BuiltinTypeSurface(
"Sprite",
"core.Sprite",
1,
ReadOnlyList.from(new IRReservedMetadata.BuiltinFieldSurface(
"x",
"int",
0,
Span.none())),
ReadOnlyList.empty(),
Span.none())),
ReadOnlyList.from(new IRReservedMetadata.BuiltinConstSurface(
"WHITE",
"color",
"core.color.white",
1,
Span.none())),
ReadOnlyList.from("gfx.draw", "audio.play")),
ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("alpha"))),
ReadOnlyList.from(
new CallableSignatureRef(new ModuleId(0), "frame", 0, "() -> unit"),
new CallableSignatureRef(new ModuleId(0), "helper", 0, "() -> unit")),
ReadOnlyList.from(new IntrinsicReference("core.draw", 1))));
aggregator.merge(new IRBackendFile(
new FileId(20),
ReadOnlyList.from(new IRFunction(new FileId(20), "beta", 1, true, new Span(new FileId(20), 0, 7))),
ReadOnlyList.from(new IRSyntheticFunction(
new ModuleId(0),
"initWrapper",
IRSyntheticCallableKind.PROJECT_INIT,
new IRSyntheticOrigin(new FileId(20), "init", new Span(new FileId(20), 0, 7)))),
ReadOnlyList.from(new IRGlobal(
new FileId(20),
new ModuleId(0),
"timer",
"int",
new Span(new FileId(20), 1, 3))),
ReadOnlyList.from(new IRBackendExecutableFunction(
new FileId(20),
new ModuleId(0),
"init",
new CallableId(0),
0,
8,
0,
0,
0,
1,
ReadOnlyList.from(new IRBackendExecutableFunction.Instruction(
IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
new ModuleId(0),
"",
null,
null,
new IRBackendExecutableFunction.IntrinsicCallMetadata("core.color.pack", 1, new IntrinsicId(0)),
Span.none())),
Span.none())),
new IRReservedMetadata(
ReadOnlyList.from(new IRReservedMetadata.HostMethodBinding(
"Audio",
"play",
"audio",
"play",
1,
0,
null,
true,
"audio.play",
Span.none())),
ReadOnlyList.from(new IRReservedMetadata.BuiltinTypeSurface(
"Sound",
"core.Sound",
1,
ReadOnlyList.empty(),
ReadOnlyList.from(new IRReservedMetadata.IntrinsicSurface(
"volume",
"core.sound.volume",
1,
1,
1,
Span.none())),
Span.none())),
ReadOnlyList.from(new IRReservedMetadata.BuiltinConstSurface(
"BLACK",
"color",
"core.color.black",
1,
Span.none())),
ReadOnlyList.from("gfx.draw", "input.read")),
ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("beta"))),
ReadOnlyList.from(new CallableSignatureRef(new ModuleId(0), "init", 0, "() -> unit")),
ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1))));
return aggregator.emit();
}
private static void inspectType(
final String owner,
final Type type,