Skip to content

Convert func to x86 func

convert_func_to_x86_func

ARG_PASSING_REGISTER_INDICES = [7, 6, 2, 1, 8, 9] module-attribute

ABI-specified function argument registers: RDI, RSI, RDX, RCX, R8, R9.

RETURN_PASSING_REGISTER = 0 module-attribute

ABI-specified return register: RAX.

MAX_REG_PASSING_INPUTS = 6 module-attribute

STACK_SLOT_SIZE_BYTES = 8 module-attribute

LowerFuncOp dataclass

Bases: RewritePattern

Source code in xdsl/backend/x86/lowering/convert_func_to_x86_func.py
@dataclass
class LowerFuncOp(RewritePattern):
    arch: X86Arch

    @op_type_rewrite_pattern
    def match_and_rewrite(self, op: func.FuncOp, rewriter: PatternRewriter):
        if op.body.blocks.first is None:
            raise DiagnosticException(
                "Cannot lower external functions (not implemented)"
            )

        for ty in op.function_type.inputs.data:
            if isinstance(ty, builtin.ShapedType):
                raise DiagnosticException(
                    "Cannot lower shaped function parameters (not implemented)"
                )
            elif isinstance(ty, builtin.FixedBitwidthType) and ty.bitwidth > 64:
                raise DiagnosticException(
                    "Cannot lower function parameters bigger than 64 bits (not implemented)"
                )

        num_inputs = len(op.function_type.inputs.data)
        new_region = rewriter.move_region_contents_to_new_regions(op.body)
        first_block = new_region.blocks.first
        assert isinstance(first_block, Block)

        reg_args_types = tuple(
            self.arch.register_type_for_type(arg.type).from_index(register_index)
            for register_index, arg in zip(
                ARG_PASSING_REGISTER_INDICES, first_block.args
            )
        )

        insertion_point = InsertPoint.at_start(first_block)

        # Load the register-carried parameters
        for i, register_type in enumerate(reg_args_types):
            arg = first_block.args[i]
            register = first_block.insert_arg(register_type, i)
            assert isinstance(register_type, GeneralRegisterType)
            mov_op = x86.DS_MovOp(
                source=register, destination=register_type.unallocated()
            )
            cast_op = asm.FromRegOp.get(mov_op.destination, arg.type)
            rewriter.insert([mov_op, cast_op], insertion_point)
            arg.replace_all_uses_with(cast_op.value)
            first_block.erase_arg(arg)

        # The last argument of the basic block should be the stack pointer
        sp = first_block.insert_arg(
            x86.registers.RSP, min(num_inputs, MAX_REG_PASSING_INPUTS)
        )

        # If needed, load the stack-carried parameters by iteratively
        # consuming the 7th argument of the basic block. Once the 7th argument
        # has been read from the stack, it is removed from the
        # basic block arguments, and the former 8th becomes the 7th.
        for i in range(num_inputs - MAX_REG_PASSING_INPUTS):
            arg = first_block.args[MAX_REG_PASSING_INPUTS + 1]
            assert sp != arg
            destination_reg = self.arch.register_type_for_type(arg.type).unallocated()
            assert isinstance(destination_reg, GeneralRegisterType)
            mov_op = x86.DM_MovOp(
                memory=sp,
                memory_offset=STACK_SLOT_SIZE_BYTES * (i + 1),
                destination=destination_reg,
                comment=f"Load the {i + MAX_REG_PASSING_INPUTS + 1}th argument of the function",
            )
            cast_op = asm.FromRegOp.get(mov_op.destination, arg.type)
            rewriter.insert([mov_op, cast_op], insertion_point)
            arg.replace_all_uses_with(cast_op.results[0])
            first_block.erase_arg(arg)

        outputs_types: list[Attribute] = []
        if len(op.function_type.outputs.data) == 1:
            output_reg = self.arch.register_type_for_type(
                op.function_type.outputs.data[0]
            ).from_index(RETURN_PASSING_REGISTER)
            outputs_types.append(output_reg)

        new_func = x86_func.FuncOp(
            op.sym_name,
            new_region,
            ((*reg_args_types, x86.registers.RSP), outputs_types),
            visibility=op.sym_visibility,
        )

        rewriter.replace(op, new_func)

arch: X86Arch instance-attribute

__init__(arch: X86Arch) -> None

match_and_rewrite(op: func.FuncOp, rewriter: PatternRewriter)

Source code in xdsl/backend/x86/lowering/convert_func_to_x86_func.py
@op_type_rewrite_pattern
def match_and_rewrite(self, op: func.FuncOp, rewriter: PatternRewriter):
    if op.body.blocks.first is None:
        raise DiagnosticException(
            "Cannot lower external functions (not implemented)"
        )

    for ty in op.function_type.inputs.data:
        if isinstance(ty, builtin.ShapedType):
            raise DiagnosticException(
                "Cannot lower shaped function parameters (not implemented)"
            )
        elif isinstance(ty, builtin.FixedBitwidthType) and ty.bitwidth > 64:
            raise DiagnosticException(
                "Cannot lower function parameters bigger than 64 bits (not implemented)"
            )

    num_inputs = len(op.function_type.inputs.data)
    new_region = rewriter.move_region_contents_to_new_regions(op.body)
    first_block = new_region.blocks.first
    assert isinstance(first_block, Block)

    reg_args_types = tuple(
        self.arch.register_type_for_type(arg.type).from_index(register_index)
        for register_index, arg in zip(
            ARG_PASSING_REGISTER_INDICES, first_block.args
        )
    )

    insertion_point = InsertPoint.at_start(first_block)

    # Load the register-carried parameters
    for i, register_type in enumerate(reg_args_types):
        arg = first_block.args[i]
        register = first_block.insert_arg(register_type, i)
        assert isinstance(register_type, GeneralRegisterType)
        mov_op = x86.DS_MovOp(
            source=register, destination=register_type.unallocated()
        )
        cast_op = asm.FromRegOp.get(mov_op.destination, arg.type)
        rewriter.insert([mov_op, cast_op], insertion_point)
        arg.replace_all_uses_with(cast_op.value)
        first_block.erase_arg(arg)

    # The last argument of the basic block should be the stack pointer
    sp = first_block.insert_arg(
        x86.registers.RSP, min(num_inputs, MAX_REG_PASSING_INPUTS)
    )

    # If needed, load the stack-carried parameters by iteratively
    # consuming the 7th argument of the basic block. Once the 7th argument
    # has been read from the stack, it is removed from the
    # basic block arguments, and the former 8th becomes the 7th.
    for i in range(num_inputs - MAX_REG_PASSING_INPUTS):
        arg = first_block.args[MAX_REG_PASSING_INPUTS + 1]
        assert sp != arg
        destination_reg = self.arch.register_type_for_type(arg.type).unallocated()
        assert isinstance(destination_reg, GeneralRegisterType)
        mov_op = x86.DM_MovOp(
            memory=sp,
            memory_offset=STACK_SLOT_SIZE_BYTES * (i + 1),
            destination=destination_reg,
            comment=f"Load the {i + MAX_REG_PASSING_INPUTS + 1}th argument of the function",
        )
        cast_op = asm.FromRegOp.get(mov_op.destination, arg.type)
        rewriter.insert([mov_op, cast_op], insertion_point)
        arg.replace_all_uses_with(cast_op.results[0])
        first_block.erase_arg(arg)

    outputs_types: list[Attribute] = []
    if len(op.function_type.outputs.data) == 1:
        output_reg = self.arch.register_type_for_type(
            op.function_type.outputs.data[0]
        ).from_index(RETURN_PASSING_REGISTER)
        outputs_types.append(output_reg)

    new_func = x86_func.FuncOp(
        op.sym_name,
        new_region,
        ((*reg_args_types, x86.registers.RSP), outputs_types),
        visibility=op.sym_visibility,
    )

    rewriter.replace(op, new_func)

LowerReturnOp dataclass

Bases: RewritePattern

Source code in xdsl/backend/x86/lowering/convert_func_to_x86_func.py
@dataclass
class LowerReturnOp(RewritePattern):
    arch: X86Arch

    @op_type_rewrite_pattern
    def match_and_rewrite(self, op: func.ReturnOp, rewriter: PatternRewriter):
        if not op.arguments:
            rewriter.replace(op, [x86_func.RetOp()])
            return
        elif len(op.arguments) > 1:
            raise DiagnosticException(
                "Cannot lower func.return with more than 1 argument (not implemented)"
            )

        return_value = op.arguments[0]

        if isinstance(return_value.type, builtin.ShapedType):
            raise DiagnosticException(
                "Cannot lower shaped function output (not implemented)"
            )
        elif (
            isinstance(return_value.type, builtin.FixedBitwidthType)
            and return_value.type.bitwidth > 64
        ):
            raise DiagnosticException(
                "Cannot lower function return values bigger than 64 bits (not implemented)"
            )

        ret_unalloc = self.arch.register_type_for_type(return_value.type).unallocated()
        assert isinstance(ret_unalloc, GeneralRegisterType)
        cast_op = asm.ToRegOp.get(return_value, ret_unalloc)
        mov_op = x86.ops.DS_MovOp(
            cast_op, destination=ret_unalloc.from_index(RETURN_PASSING_REGISTER)
        )
        ret_op = x86_func.RetOp()

        rewriter.replace(op, [cast_op, mov_op, ret_op])

arch: X86Arch instance-attribute

__init__(arch: X86Arch) -> None

match_and_rewrite(op: func.ReturnOp, rewriter: PatternRewriter)

Source code in xdsl/backend/x86/lowering/convert_func_to_x86_func.py
@op_type_rewrite_pattern
def match_and_rewrite(self, op: func.ReturnOp, rewriter: PatternRewriter):
    if not op.arguments:
        rewriter.replace(op, [x86_func.RetOp()])
        return
    elif len(op.arguments) > 1:
        raise DiagnosticException(
            "Cannot lower func.return with more than 1 argument (not implemented)"
        )

    return_value = op.arguments[0]

    if isinstance(return_value.type, builtin.ShapedType):
        raise DiagnosticException(
            "Cannot lower shaped function output (not implemented)"
        )
    elif (
        isinstance(return_value.type, builtin.FixedBitwidthType)
        and return_value.type.bitwidth > 64
    ):
        raise DiagnosticException(
            "Cannot lower function return values bigger than 64 bits (not implemented)"
        )

    ret_unalloc = self.arch.register_type_for_type(return_value.type).unallocated()
    assert isinstance(ret_unalloc, GeneralRegisterType)
    cast_op = asm.ToRegOp.get(return_value, ret_unalloc)
    mov_op = x86.ops.DS_MovOp(
        cast_op, destination=ret_unalloc.from_index(RETURN_PASSING_REGISTER)
    )
    ret_op = x86_func.RetOp()

    rewriter.replace(op, [cast_op, mov_op, ret_op])

ConvertFuncToX86FuncPass dataclass

Bases: ModulePass

Source code in xdsl/backend/x86/lowering/convert_func_to_x86_func.py
@dataclass(frozen=True)
class ConvertFuncToX86FuncPass(ModulePass):
    name = "convert-func-to-x86-func"
    arch: str | None = None

    def apply(self, ctx: Context, op: ModuleOp) -> None:
        arch = X86Arch.arch_for_name(self.arch)
        PatternRewriteWalker(
            GreedyRewritePatternApplier(
                [
                    LowerFuncOp(arch),
                    LowerReturnOp(arch),
                ]
            ),
            apply_recursively=False,
        ).rewrite_module(op)

name = 'convert-func-to-x86-func' class-attribute instance-attribute

arch: str | None = None class-attribute instance-attribute

__init__(arch: str | None = None) -> None

apply(ctx: Context, op: ModuleOp) -> None

Source code in xdsl/backend/x86/lowering/convert_func_to_x86_func.py
def apply(self, ctx: Context, op: ModuleOp) -> None:
    arch = X86Arch.arch_for_name(self.arch)
    PatternRewriteWalker(
        GreedyRewritePatternApplier(
            [
                LowerFuncOp(arch),
                LowerReturnOp(arch),
            ]
        ),
        apply_recursively=False,
    ).rewrite_module(op)