package p.studio.compiler.backend.irvm;

import org.junit.jupiter.api.Test;
import p.studio.compiler.models.IRBackend;
import p.studio.compiler.models.IRBackendExecutableFunction;
import p.studio.compiler.models.IRGlobal;
import p.studio.compiler.models.IRGlobalOrigin;
import p.studio.compiler.models.IRGlobalVisibility;
import p.studio.compiler.models.IRHiddenGlobalKind;
import p.studio.compiler.models.IRSyntheticCallableKind;
import p.studio.compiler.models.IRSyntheticFunction;
import p.studio.compiler.models.IRSyntheticOrigin;
import p.studio.compiler.source.Span;
import p.studio.compiler.source.identifiers.CallableId;
import p.studio.compiler.source.identifiers.FileId;
import p.studio.compiler.source.identifiers.IntrinsicId;
import p.studio.compiler.source.identifiers.ModuleId;
import p.studio.compiler.source.tables.IntrinsicReference;
import p.studio.compiler.source.tables.ModuleReference;
import p.studio.utilities.structures.ReadOnlyList;

import static org.junit.jupiter.api.Assertions.*;

class LowerToIRVMServiceTest {

    @Test
    void lowerMustAssignEntrypointIdZeroAndSortRemainingDeterministically() {
        final var backend = IRBackend.builder()
                .entryPointModuleId(new ModuleId(0))
                .executableFunctions(ReadOnlyList.from(
                        fn("aux", "app", 11, ReadOnlyList.from(
                                callFunc("app", "main", 10),
                                ret())),
                        fn("main", "app", 10, ReadOnlyList.from(
                                ret()))))
                .build();

        final var lowered = new LowerToIRVMService().lower(backend);

        assertEquals(2, lowered.module().functions().size());
        assertEquals("main", lowered.module().functions().get(0).name());
        assertEquals("aux", lowered.module().functions().get(1).name());
        assertEquals(IRVMOp.CALL, lowered.module().functions().get(1).instructions().get(0).op());
        assertEquals(0, lowered.module().functions().get(1).instructions().get(0).immediate());
    }

    @Test
    void lowerMustUseFrontendDeclaredEntrypointCallable() {
        final var backend = IRBackend.builder()
                .entryPointCallableName("frame")
                .entryPointModuleId(new ModuleId(0))
                .executableFunctions(ReadOnlyList.from(
                        fn("aux", "app", 11, ReadOnlyList.from(
                                callFunc("app", "frame", 10),
                                ret())),
                        fn("frame", "app", 10, ReadOnlyList.from(
                                ret()))))
                .build();

        final var lowered = new LowerToIRVMService().lower(backend);

        assertEquals("frame", lowered.module().functions().get(0).name());
        assertEquals("aux", lowered.module().functions().get(1).name());
        assertEquals(0, lowered.module().functions().get(1).instructions().get(0).immediate());
    }

    @Test
    void lowerMustPublishSyntheticWrapperAtFunctionZeroAndKeepFinalRetInWrapperPath() {
        final var backend = IRBackend.builder()
                .entryPointCallableName("__pbs.frame_wrapper$m0")
                .entryPointModuleId(new ModuleId(0))
                .globals(ReadOnlyList.from(hiddenBootGuard(0)))
                .syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
                        new ModuleId(0),
                        "__pbs.frame_wrapper$m0",
                        IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
                        new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
                .executableFunctions(ReadOnlyList.from(
                        fn("__pbs.file_init$f1", "app", 30, ReadOnlyList.from(ret())),
                        fn("__pbs.module_init$m0", "app", 31, ReadOnlyList.from(
                                callFunc("app", "__pbs.file_init$f1", 30),
                                ret())),
                        fn("__pbs.project_init$m0", "app", 32, ReadOnlyList.from(
                                callFunc("app", "boot", 10),
                                ret())),
                        fn("boot", "app", 10, ReadOnlyList.from(ret())),
                        fn("frame", "app", 20, ReadOnlyList.from(ret())),
                        fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
                                getGlobal(0),
                                pushBool(true),
                                eq(),
                                jmpIfTrue("bootstrap_done"),
                                callFunc("app", "__pbs.module_init$m0", 31),
                                callFunc("app", "__pbs.project_init$m0", 32),
                                pushBool(true),
                                setGlobal(0),
                                label("bootstrap_done"),
                                callFunc("app", "frame", 20),
                                ret()))))
                .build();

        final var lowered = new LowerToIRVMService().lower(backend);

        assertEquals("__pbs.frame_wrapper$m0", lowered.module().functions().get(0).name());
        assertEquals(IRVMOp.GET_GLOBAL, lowered.module().functions().get(0).instructions().get(0).op());
        assertEquals(IRVMOp.PUSH_BOOL, lowered.module().functions().get(0).instructions().get(1).op());
        assertEquals(IRVMOp.EQ, lowered.module().functions().get(0).instructions().get(2).op());
        assertEquals(IRVMOp.JMP_IF_TRUE, lowered.module().functions().get(0).instructions().get(3).op());
        assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(4).op());
        assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(5).op());
        assertEquals(IRVMOp.PUSH_BOOL, lowered.module().functions().get(0).instructions().get(6).op());
        assertEquals(IRVMOp.SET_GLOBAL, lowered.module().functions().get(0).instructions().get(7).op());
        assertEquals(IRVMOp.CALL, lowered.module().functions().get(0).instructions().get(8).op());
        assertEquals(IRVMOp.RET, lowered.module().functions().get(0).instructions().get(9).op());
        assertEquals(IRVMOp.RET, lowered.module().functions().stream()
                .filter(function -> "frame".equals(function.name()))
                .findFirst()
                .orElseThrow()
                .instructions()
                .getLast()
                .op());
    }

    @Test
    void lowerMustRejectWhenSyntheticWrapperEntrypointIsMissing() {
        final var backend = IRBackend.builder()
                .entryPointCallableName("__pbs.frame_wrapper$m0")
                .entryPointModuleId(new ModuleId(0))
                .globals(ReadOnlyList.from(hiddenBootGuard(0)))
                .syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
                        new ModuleId(0),
                        "__pbs.frame_wrapper$m0",
                        IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
                        new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
                .executableFunctions(ReadOnlyList.from(
                        fn("frame", "app", 20, ReadOnlyList.from(ret()))))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_SYNTHETIC_WRAPPER_ENTRYPOINT_MISSING, thrown.code());
    }

    @Test
    void lowerMustRejectWhenPublishedWrapperWouldNotOccupyFunctionZero() {
        final var backend = IRBackend.builder()
                .entryPointCallableName("frame")
                .entryPointModuleId(new ModuleId(0))
                .globals(ReadOnlyList.from(hiddenBootGuard(0)))
                .syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
                        new ModuleId(0),
                        "__pbs.frame_wrapper$m0",
                        IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
                        new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
                .executableFunctions(ReadOnlyList.from(
                        fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
                                callFunc("app", "frame", 20),
                                ret())),
                        fn("frame", "app", 20, ReadOnlyList.from(ret()))))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_PUBLISHED_ENTRYPOINT_NOT_ZERO, thrown.code());
    }

    @Test
    void lowerMustRejectWhenHiddenBootGuardIsMissing() {
        final var backend = IRBackend.builder()
                .entryPointCallableName("__pbs.frame_wrapper$m0")
                .entryPointModuleId(new ModuleId(0))
                .syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
                        new ModuleId(0),
                        "__pbs.frame_wrapper$m0",
                        IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
                        new IRSyntheticOrigin(new FileId(0), "frame", new Span(new FileId(0), 1, 10)))))
                .executableFunctions(ReadOnlyList.from(
                        fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
                                callFunc("app", "frame", 20),
                                ret())),
                        fn("frame", "app", 20, ReadOnlyList.from(ret()))))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_HIDDEN_BOOT_GUARD_MISSING, thrown.code());
    }

    @Test
    void lowerMustRejectWhenSyntheticCallableOriginIsMissing() {
        final var backend = IRBackend.builder()
                .entryPointCallableName("__pbs.frame_wrapper$m0")
                .entryPointModuleId(new ModuleId(0))
                .globals(ReadOnlyList.from(userVisibleGlobal("__pbs.boot_guard$m0", 0)))
                .syntheticFunctions(ReadOnlyList.from(new IRSyntheticFunction(
                        new ModuleId(0),
                        "__pbs.frame_wrapper$m0",
                        IRSyntheticCallableKind.PUBLISHED_FRAME_WRAPPER,
                        new IRSyntheticOrigin(new FileId(0), "", Span.none()))))
                .executableFunctions(ReadOnlyList.from(
                        fn("__pbs.frame_wrapper$m0", "app", 40, ReadOnlyList.from(
                                callFunc("app", "frame", 20),
                                ret())),
                        fn("frame", "app", 20, ReadOnlyList.from(ret()))))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_SYNTHETIC_ORIGIN_MISSING, thrown.code());
    }

    @Test
    void lowerMustMapHostAndIntrinsicCallsites() {
        final var backend = IRBackend.builder()
                .entryPointModuleId(new ModuleId(0))
                .executableFunctions(ReadOnlyList.from(
                        fn("main", "app", 10, ReadOnlyList.from(
                                callHost("gfx", "draw_pixel", 1, 0, 0),
                                callIntrinsic("input.pad", 1, 0),
                                ret()))))
                .intrinsicPool(ReadOnlyList.from(new IntrinsicReference("input.pad", 1)))
                .build();

        final var lowered = new LowerToIRVMService().lower(backend);

        final var instructions = lowered.module().functions().getFirst().instructions();
        assertEquals(IRVMOp.HOSTCALL, instructions.get(0).op());
        assertEquals(IRVMOp.INTRINSIC, instructions.get(1).op());
        assertEquals(0x2000, instructions.get(1).immediate());
        final var emissionOps = lowered.emissionPlan().functions().getFirst().operations();
        assertEquals(p.studio.compiler.backend.bytecode.BytecodeEmitter.OperationKind.HOSTCALL, emissionOps.get(0).kind());
        assertEquals(p.studio.compiler.backend.bytecode.BytecodeEmitter.OperationKind.INTRINSIC, emissionOps.get(1).kind());
        assertTrue(emissionOps.stream().allMatch(op -> op.span() != null));
    }

    @Test
    void lowerMustRejectUnterminatedFunction() {
        final var backend = IRBackend.builder()
                .entryPointModuleId(new ModuleId(0))
                .executableFunctions(ReadOnlyList.from(
                        fn("main", "app", 10, ReadOnlyList.from(
                                callFunc("app", "main", 10)))))
                .build();

        final var thrown = assertThrows(IRVMValidationException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMValidationErrorCode.MARSHAL_VERIFY_PRECHECK_UNTERMINATED_PATH, thrown.code());
    }

    @Test
    void lowerMustRejectMissingCallee() {
        final var backend = IRBackend.builder()
                .entryPointModuleId(new ModuleId(0))
                .executableFunctions(ReadOnlyList.from(
                        fn("main", "app", 10, ReadOnlyList.from(
                                callFunc("app", "missing", 77),
                                ret()))))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_MISSING_CALLEE, thrown.code());
    }

    @Test
    void lowerMustRejectUnknownCanonicalIntrinsicIdentity() {
        final var backend = IRBackend.builder()
                .entryPointModuleId(new ModuleId(0))
                .executableFunctions(ReadOnlyList.from(
                        fn("main", "app", 10, ReadOnlyList.from(
                                callIntrinsic("core.color.pack", 1, 0),
                                ret()))))
                .intrinsicPool(ReadOnlyList.from(new IntrinsicReference("core.color.pack", 1)))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_INVALID_INTRINSIC_ID, thrown.code());
        assertTrue(thrown.getMessage().contains("unknown VM-owned intrinsic identity"));
    }

    @Test
    void lowerMustRejectCallArgSlotMismatch() {
        final var backend = IRBackend.builder()
                .entryPointModuleId(new ModuleId(0))
                .executableFunctions(ReadOnlyList.from(
                        fn("callee", "app", 20, ReadOnlyList.from(ret())),
                        fn("main", "app", 10, ReadOnlyList.from(
                                callFuncWithExpected("app", "callee", 20, 2, 0),
                                ret()))))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_CALL_ARG_SLOTS_MISMATCH, thrown.code());
    }

    @Test
    void lowerMustResolveJumpTargetsFromLabels() {
        final var backend = IRBackend.builder()
                .entryPointModuleId(new ModuleId(0))
                .executableFunctions(ReadOnlyList.from(
                        fn("main", "app", 10, ReadOnlyList.from(
                                label("entry"),
                                jmp("exit"),
                                label("exit"),
                                ret()))))
                .build();

        final var lowered = new LowerToIRVMService().lower(backend);
        final var emitted = lowered.module().functions().getFirst().instructions();

        assertEquals(IRVMOp.JMP, emitted.get(0).op());
        assertEquals(6, emitted.get(0).immediate());
        assertEquals(IRVMOp.RET, emitted.get(1).op());
    }

    @Test
    void lowerMustRejectMissingJumpTargetLabel() {
        final var backend = IRBackend.builder()
                .entryPointModuleId(new ModuleId(0))
                .executableFunctions(ReadOnlyList.from(
                        fn("main", "app", 10, ReadOnlyList.from(
                                jmp("missing"),
                                ret()))))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_MISSING_JUMP_TARGET, thrown.code());
    }

    @Test
    void lowerMustRejectWhenEntrypointIsMissing() {
        final var backend = IRBackend.builder()
                .entryPointModuleId(new ModuleId(0))
                .executableFunctions(ReadOnlyList.from(
                        fn("helper", "app", 10, ReadOnlyList.from(ret()))))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING, thrown.code());
    }

    @Test
    void lowerMustRejectWhenEntrypointDeclarationIsMissing() {
        final var backend = IRBackend.builder()
                .entryPointCallableName(" ")
                .executableFunctions(ReadOnlyList.from(
                        fn("main", "app", 10, ReadOnlyList.from(ret()))))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_DECLARATION_MISSING, thrown.code());
    }

    @Test
    void lowerMustRejectWhenEntrypointIsAmbiguous() {
        final var backend = IRBackend.builder()
                .entryPointModuleId(new ModuleId(0))
                .executableFunctions(ReadOnlyList.from(
                        fn("main", "app", 10, ReadOnlyList.from(ret())),
                        fn("main", "sdk", 11, ReadOnlyList.from(ret()))))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_AMBIGUOUS, thrown.code());
    }

    @Test
    void lowerMustUseModulePoolCanonicalIdentityForNonEntrypointOrdering() {
        final var backend = IRBackend.builder()
                .entryPointCallableName("frame")
                .entryPointModuleId(new ModuleId(1))
                .modulePool(ReadOnlyList.from(
                        new ModuleReference("app", ReadOnlyList.from("alpha")),
                        new ModuleReference("app", ReadOnlyList.from("zeta"))))
                .executableFunctions(ReadOnlyList.from(
                        fnWithModule("helper_in_zeta", "legacy/zeta", 1, 21, ReadOnlyList.from(ret())),
                        fnWithModule("frame", "legacy/zeta", 1, 20, ReadOnlyList.from(ret())),
                        fnWithModule("helper_in_alpha", "legacy/alpha", 0, 22, ReadOnlyList.from(ret()))))
                .build();

        final var lowered = new LowerToIRVMService().lower(backend);

        assertEquals(3, lowered.module().functions().size());
        assertEquals("frame", lowered.module().functions().get(0).name());
        assertEquals("helper_in_alpha", lowered.module().functions().get(1).name());
        assertEquals("helper_in_zeta", lowered.module().functions().get(2).name());
    }

    @Test
    void lowerMustUseQualifiedEntrypointIdentityWhenProvided() {
        final var backend = IRBackend.builder()
                .entryPointCallableName("main")
                .entryPointModuleId(new ModuleId(1))
                .modulePool(ReadOnlyList.from(
                        new ModuleReference("app", ReadOnlyList.from("alpha")),
                        new ModuleReference("app", ReadOnlyList.from("beta"))))
                .executableFunctions(ReadOnlyList.from(
                        fnWithModuleAndLocals("main", "legacy/alpha", 0, 30, 1, ReadOnlyList.from(ret())),
                        fnWithModuleAndLocals("main", "legacy/beta", 1, 31, 9, ReadOnlyList.from(ret())),
                        fnWithModule("helper", "legacy/alpha", 0, 32, ReadOnlyList.from(ret()))))
                .build();

        final var lowered = new LowerToIRVMService().lower(backend);

        assertEquals("main", lowered.module().functions().get(0).name());
        assertEquals(9, lowered.module().functions().get(0).localSlots());
    }

    @Test
    void lowerMustRejectMissingQualifiedEntrypointIdentity() {
        final var backend = IRBackend.builder()
                .entryPointCallableName("main")
                .entryPointModuleId(new ModuleId(5))
                .modulePool(ReadOnlyList.from(new ModuleReference("app", ReadOnlyList.from("alpha"))))
                .executableFunctions(ReadOnlyList.from(
                        fnWithModule("main", "legacy/alpha", 0, 40, ReadOnlyList.from(ret()))))
                .build();

        final var thrown = assertThrows(IRVMLoweringException.class, () -> new LowerToIRVMService().lower(backend));
        assertEquals(IRVMLoweringErrorCode.LOWER_IRVM_ENTRYPOINT_MISSING, thrown.code());
        assertTrue(thrown.getMessage().contains("qualified entrypoint"));
    }

    private static IRGlobal hiddenBootGuard(final int moduleId) {
        final var fileId = new FileId(0);
        final var ownerModuleId = new ModuleId(moduleId);
        return new IRGlobal(
                fileId,
                ownerModuleId,
                "__pbs.boot_guard$m" + moduleId,
                "bool",
                0,
                IRGlobalVisibility.MODULE,
                true,
                IRHiddenGlobalKind.BOOT_GUARD,
                new IRGlobalOrigin(fileId, ownerModuleId, "frame", Span.none(), "BOOT_GUARD"),
                Span.none());
    }

    private static IRGlobal userVisibleGlobal(final String name, final int moduleId) {
        final var fileId = new FileId(0);
        final var ownerModuleId = new ModuleId(moduleId);
        return new IRGlobal(
                fileId,
                ownerModuleId,
                name,
                "bool",
                0,
                IRGlobalVisibility.MODULE,
                false,
                IRHiddenGlobalKind.NONE,
                new IRGlobalOrigin(fileId, ownerModuleId, name, Span.none(), name),
                Span.none());
    }

    private static IRBackendExecutableFunction fn(
            final String name,
            final String moduleKey,
            final int callableId,
            final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions) {
        return new IRBackendExecutableFunction(
                new FileId(0),
                new ModuleId(0),
                name,
                new CallableId(callableId),
                0,
                10,
                0,
                0,
                0,
                4,
                instructions,
                Span.none());
    }

    private static IRBackendExecutableFunction fnWithModule(
            final String name,
            final String moduleKey,
            final int moduleId,
            final int callableId,
            final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions) {
        return fnWithModuleAndLocals(name, moduleKey, moduleId, callableId, 0, instructions);
    }

    private static IRBackendExecutableFunction fnWithModuleAndLocals(
            final String name,
            final String moduleKey,
            final int moduleId,
            final int callableId,
            final int localSlots,
            final ReadOnlyList<IRBackendExecutableFunction.Instruction> instructions) {
        return new IRBackendExecutableFunction(
                new FileId(0),
                new ModuleId(moduleId),
                name,
                new CallableId(callableId),
                0,
                10,
                0,
                localSlots,
                0,
                4,
                instructions,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction ret() {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.RET,
                "",
                null,
                null,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction callFunc(
            final String moduleKey,
            final String name,
            final int calleeCallableId) {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
                ModuleId.none(),
                name,
                new CallableId(calleeCallableId),
                null,
                null,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction callFuncWithExpected(
            final String moduleKey,
            final String name,
            final int calleeCallableId,
            final int expectedArgSlots,
            final int expectedRetSlots) {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.CALL_FUNC,
                ModuleId.none(),
                name,
                new CallableId(calleeCallableId),
                null,
                null,
                expectedArgSlots,
                expectedRetSlots,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction callHost(
            final String module,
            final String name,
            final long version,
            final int argSlots,
            final int retSlots) {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.CALL_HOST,
                "",
                new IRBackendExecutableFunction.HostCallMetadata(module, name, version, argSlots, retSlots),
                null,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction callIntrinsic(
            final String canonicalName,
            final long canonicalVersion,
            final int intrinsicId) {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.CALL_INTRINSIC,
                "",
                null,
                new IRBackendExecutableFunction.IntrinsicCallMetadata(canonicalName, canonicalVersion, new IntrinsicId(intrinsicId)),
                0,
                0,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction label(final String label) {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.LABEL,
                ModuleId.none(),
                "",
                null,
                null,
                null,
                label,
                "",
                null,
                null,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction jmp(final String target) {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.JMP,
                ModuleId.none(),
                "",
                null,
                null,
                null,
                "",
                target,
                null,
                null,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction jmpIfTrue(final String target) {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.JMP_IF_TRUE,
                ModuleId.none(),
                "",
                null,
                null,
                null,
                "",
                target,
                null,
                null,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction getGlobal(final int slot) {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.GET_GLOBAL,
                "",
                null,
                null,
                null,
                slot,
                null,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction setGlobal(final int slot) {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.SET_GLOBAL,
                "",
                null,
                null,
                null,
                slot,
                null,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction pushBool(final boolean value) {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.PUSH_BOOL,
                "",
                null,
                null,
                null,
                value ? "true" : "false",
                null,
                null,
                null,
                Span.none());
    }

    private static IRBackendExecutableFunction.Instruction eq() {
        return new IRBackendExecutableFunction.Instruction(
                IRBackendExecutableFunction.InstructionKind.EQ,
                "",
                null,
                null,
                null,
                Span.none());
    }
}
