lib/choir/src/dialects/aarch64.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const std = @import("std");
   2 const alloc_arena = @import("alloc_arena");
   3 const ir = @import("../core/root.zig");
   4 
   5 pub const registers = @import("../backends/aarch64/registers/root.zig");
   6 pub const encoding = @import("../backends/aarch64/encoding/root.zig");
   7 
   8 pub const GPR = registers.GPR;
   9 pub const FPR = registers.FPR;
  10 pub const GPRSize = registers.GPRSize;
  11 pub const Condition = encoding.Condition;
  12 
  13 pub const type_names = struct {
  14     pub const gpr = "aarch64.gpr";
  15     pub const fpr = "aarch64.fpr";
  16 };
  17 
  18 pub const attr_names = struct {
  19     pub const gpr_reg = "aarch64.gpr_reg";
  20     pub const fpr_reg = "aarch64.fpr_reg";
  21     pub const imm12 = "aarch64.imm12";
  22     pub const imm16 = "aarch64.imm16";
  23     pub const imm8 = "aarch64.imm8";
  24     pub const condition = "aarch64.condition";
  25     pub const gpr_size = "aarch64.gpr_size";
  26     pub const fp_type = "aarch64.fp_type";
  27     pub const frame_offset = "aarch64.frame_offset";
  28 };
  29 
  30 pub const AArch64Dialect = struct {
  31     pub const name = "aarch64";
  32     const op_specs = ir.dialects.opSpec.dialect(@This());
  33     pub const spec = ir.dialects.dialectSpec(@This(), .{
  34         .types = ir.dialects.typeNames(type_names),
  35     });
  36 
  37     pub const GetRegOp = struct {
  38         op: *ir.Operation,
  39 
  40         pub const operation_spec = op_specs.define(.{
  41             .mnemonic = "get_reg",
  42             .required_attrs = &.{"reg"},
  43         });
  44         pub const operation_name = operation_spec.name;
  45 
  46         pub fn createGPR(ctx: *ir.Context, loc: ir.Location, reg: GPR) !GetRegOp {
  47             var builder = ir.OperationBuilder.init(ctx);
  48             const gpr_type = try getGPRType(ctx);
  49             var state = op_specs.state(@This(), loc);
  50             state.addTypes(&.{gpr_type});
  51 
  52             const op = try builder.create(state);
  53             try op.setAttr("reg", try getGPRAttr(ctx, reg));
  54             return .{ .op = op };
  55         }
  56 
  57         pub fn createFPR(ctx: *ir.Context, loc: ir.Location, reg: FPR) !GetRegOp {
  58             var builder = ir.OperationBuilder.init(ctx);
  59             const fpr_type = try getFPRType(ctx);
  60             var state = op_specs.state(@This(), loc);
  61             state.addTypes(&.{fpr_type});
  62 
  63             const op = try builder.create(state);
  64             try op.setAttr("reg", try getFPRAttr(ctx, reg));
  65             return .{ .op = op };
  66         }
  67 
  68         pub fn getResult(self: *const GetRegOp) *ir.Value {
  69             return self.op.getResult(0).?;
  70         }
  71 
  72         pub fn getGPR(self: GetRegOp) ?GPR {
  73             if (self.op.getAttr("reg")) |attr| {
  74                 return getGPRValue(attr);
  75             }
  76             return null;
  77         }
  78 
  79         pub fn getFPR(self: GetRegOp) ?FPR {
  80             if (self.op.getAttr("reg")) |attr| {
  81                 return getFPRValue(attr);
  82             }
  83             return null;
  84         }
  85     };
  86 
  87     pub const AddOp = struct {
  88         op: *ir.Operation,
  89 
  90         pub const operation_spec = op_specs.define(.{
  91             .mnemonic = "add",
  92             .required_attrs = &.{"size"},
  93         });
  94         pub const operation_name = operation_spec.name;
  95 
  96         pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value, size: GPRSize) !AddOp {
  97             var builder = ir.OperationBuilder.init(ctx);
  98             const gpr_type = try getGPRType(ctx);
  99             var state = op_specs.state(@This(), loc);
 100             state.addOperands(&.{ lhs, rhs });
 101             state.addTypes(&.{gpr_type});
 102 
 103             const op = try builder.create(state);
 104             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 105             return .{ .op = op };
 106         }
 107 
 108         pub fn getResult(self: *const AddOp) *ir.Value {
 109             return self.op.getResult(0).?;
 110         }
 111 
 112         pub fn getLhs(self: AddOp) *ir.Value {
 113             return self.op.operands.items[0].value;
 114         }
 115 
 116         pub fn getRhs(self: AddOp) *ir.Value {
 117             return self.op.operands.items[1].value;
 118         }
 119 
 120         pub fn getSize(self: AddOp) GPRSize {
 121             if (self.op.getAttr("size")) |attr| {
 122                 return getGPRSizeValue(attr) orelse .x;
 123             }
 124             return .x;
 125         }
 126     };
 127 
 128     pub const AddImmOp = struct {
 129         op: *ir.Operation,
 130 
 131         pub const operation_spec = op_specs.define(.{
 132             .mnemonic = "add_imm",
 133             .required_attrs = &.{ "imm", "size" },
 134         });
 135         pub const operation_name = operation_spec.name;
 136 
 137         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, imm: u12, size: GPRSize) !AddImmOp {
 138             var builder = ir.OperationBuilder.init(ctx);
 139             const gpr_type = try getGPRType(ctx);
 140             var state = op_specs.state(@This(), loc);
 141             state.addOperands(&.{src});
 142             state.addTypes(&.{gpr_type});
 143 
 144             const op = try builder.create(state);
 145             try op.setAttr("imm", try getImm12Attr(ctx, imm));
 146             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 147             return .{ .op = op };
 148         }
 149 
 150         pub fn getResult(self: *const AddImmOp) *ir.Value {
 151             return self.op.getResult(0).?;
 152         }
 153 
 154         pub fn getSrc(self: AddImmOp) *ir.Value {
 155             return self.op.operands.items[0].value;
 156         }
 157 
 158         pub fn getImm(self: AddImmOp) u12 {
 159             if (self.op.getAttr("imm")) |attr| {
 160                 return getImm12Value(attr) orelse 0;
 161             }
 162             return 0;
 163         }
 164 
 165         pub fn getSize(self: AddImmOp) GPRSize {
 166             if (self.op.getAttr("size")) |attr| {
 167                 return getGPRSizeValue(attr) orelse .x;
 168             }
 169             return .x;
 170         }
 171     };
 172 
 173     pub const SubOp = struct {
 174         op: *ir.Operation,
 175 
 176         pub const operation_spec = op_specs.define(.{
 177             .mnemonic = "sub",
 178             .required_attrs = &.{"size"},
 179         });
 180         pub const operation_name = operation_spec.name;
 181 
 182         pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value, size: GPRSize) !SubOp {
 183             var builder = ir.OperationBuilder.init(ctx);
 184             const gpr_type = try getGPRType(ctx);
 185             var state = op_specs.state(@This(), loc);
 186             state.addOperands(&.{ lhs, rhs });
 187             state.addTypes(&.{gpr_type});
 188 
 189             const op = try builder.create(state);
 190             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 191             return .{ .op = op };
 192         }
 193 
 194         pub fn getResult(self: *const SubOp) *ir.Value {
 195             return self.op.getResult(0).?;
 196         }
 197 
 198         pub fn getLhs(self: SubOp) *ir.Value {
 199             return self.op.operands.items[0].value;
 200         }
 201 
 202         pub fn getRhs(self: SubOp) *ir.Value {
 203             return self.op.operands.items[1].value;
 204         }
 205 
 206         pub fn getSize(self: SubOp) GPRSize {
 207             if (self.op.getAttr("size")) |attr| {
 208                 return getGPRSizeValue(attr) orelse .x;
 209             }
 210             return .x;
 211         }
 212     };
 213 
 214     pub const MulOp = struct {
 215         op: *ir.Operation,
 216 
 217         pub const operation_spec = op_specs.define(.{
 218             .mnemonic = "mul",
 219             .required_attrs = &.{"size"},
 220         });
 221         pub const operation_name = operation_spec.name;
 222 
 223         pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value, size: GPRSize) !MulOp {
 224             var builder = ir.OperationBuilder.init(ctx);
 225             const gpr_type = try getGPRType(ctx);
 226             var state = op_specs.state(@This(), loc);
 227             state.addOperands(&.{ lhs, rhs });
 228             state.addTypes(&.{gpr_type});
 229 
 230             const op = try builder.create(state);
 231             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 232             return .{ .op = op };
 233         }
 234 
 235         pub fn getResult(self: *const MulOp) *ir.Value {
 236             return self.op.getResult(0).?;
 237         }
 238     };
 239 
 240     pub const SDivOp = struct {
 241         op: *ir.Operation,
 242 
 243         pub const operation_spec = op_specs.define(.{
 244             .mnemonic = "sdiv",
 245             .required_attrs = &.{"size"},
 246         });
 247         pub const operation_name = operation_spec.name;
 248 
 249         pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value, size: GPRSize) !SDivOp {
 250             var builder = ir.OperationBuilder.init(ctx);
 251             const gpr_type = try getGPRType(ctx);
 252             var state = op_specs.state(@This(), loc);
 253             state.addOperands(&.{ lhs, rhs });
 254             state.addTypes(&.{gpr_type});
 255 
 256             const op = try builder.create(state);
 257             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 258             return .{ .op = op };
 259         }
 260 
 261         pub fn getResult(self: *const SDivOp) *ir.Value {
 262             return self.op.getResult(0).?;
 263         }
 264     };
 265 
 266     pub const UDivOp = struct {
 267         op: *ir.Operation,
 268 
 269         pub const operation_spec = op_specs.define(.{
 270             .mnemonic = "udiv",
 271             .required_attrs = &.{"size"},
 272         });
 273         pub const operation_name = operation_spec.name;
 274 
 275         pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value, size: GPRSize) !UDivOp {
 276             var builder = ir.OperationBuilder.init(ctx);
 277             const gpr_type = try getGPRType(ctx);
 278             var state = op_specs.state(@This(), loc);
 279             state.addOperands(&.{ lhs, rhs });
 280             state.addTypes(&.{gpr_type});
 281 
 282             const op = try builder.create(state);
 283             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 284             return .{ .op = op };
 285         }
 286 
 287         pub fn getResult(self: *const UDivOp) *ir.Value {
 288             return self.op.getResult(0).?;
 289         }
 290     };
 291 
 292     pub const CmpOp = struct {
 293         op: *ir.Operation,
 294 
 295         pub const operation_spec = op_specs.define(.{
 296             .mnemonic = "cmp",
 297             .required_attrs = &.{"size"},
 298         });
 299         pub const operation_name = operation_spec.name;
 300 
 301         pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value, size: GPRSize) !CmpOp {
 302             var builder = ir.OperationBuilder.init(ctx);
 303             var state = op_specs.state(@This(), loc);
 304             state.addOperands(&.{ lhs, rhs });
 305 
 306             const op = try builder.create(state);
 307             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 308             return .{ .op = op };
 309         }
 310     };
 311 
 312     pub const CSetOp = struct {
 313         op: *ir.Operation,
 314 
 315         pub const operation_spec = op_specs.define(.{
 316             .mnemonic = "cset",
 317             .required_attrs = &.{ "cond", "size" },
 318         });
 319         pub const operation_name = operation_spec.name;
 320 
 321         pub fn create(ctx: *ir.Context, loc: ir.Location, cond: Condition, size: GPRSize) !CSetOp {
 322             var builder = ir.OperationBuilder.init(ctx);
 323             const gpr_type = try getGPRType(ctx);
 324             var state = op_specs.state(@This(), loc);
 325             state.addTypes(&.{gpr_type});
 326 
 327             const op = try builder.create(state);
 328             try op.setAttr("cond", try getConditionAttr(ctx, cond));
 329             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 330             return .{ .op = op };
 331         }
 332 
 333         pub fn getResult(self: *const CSetOp) *ir.Value {
 334             return self.op.getResult(0).?;
 335         }
 336 
 337         pub fn getCondition(self: CSetOp) Condition {
 338             if (self.op.getAttr("cond")) |attr| {
 339                 return getConditionValue(attr) orelse .al;
 340             }
 341             return .al;
 342         }
 343     };
 344 
 345     pub const CSelOp = struct {
 346         op: *ir.Operation,
 347 
 348         pub const operation_spec = op_specs.define(.{
 349             .mnemonic = "csel",
 350             .required_attrs = &.{ "cond", "size" },
 351         });
 352         pub const operation_name = operation_spec.name;
 353 
 354         pub fn create(ctx: *ir.Context, loc: ir.Location, rn: *ir.Value, rm: *ir.Value, cond: Condition, size: GPRSize) !CSelOp {
 355             var builder = ir.OperationBuilder.init(ctx);
 356             const gpr_type = try getGPRType(ctx);
 357             var state = op_specs.state(@This(), loc);
 358             state.addOperands(&.{ rn, rm });
 359             state.addTypes(&.{gpr_type});
 360 
 361             const op = try builder.create(state);
 362             try op.setAttr("cond", try getConditionAttr(ctx, cond));
 363             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 364             return .{ .op = op };
 365         }
 366 
 367         pub fn getResult(self: *const CSelOp) *ir.Value {
 368             return self.op.getResult(0).?;
 369         }
 370 
 371         pub fn getRn(self: CSelOp) *ir.Value {
 372             return self.op.operands.items[0].value;
 373         }
 374 
 375         pub fn getRm(self: CSelOp) *ir.Value {
 376             return self.op.operands.items[1].value;
 377         }
 378 
 379         pub fn getCondition(self: CSelOp) Condition {
 380             if (self.op.getAttr("cond")) |attr| {
 381                 return getConditionValue(attr) orelse .al;
 382             }
 383             return .al;
 384         }
 385     };
 386 
 387     pub const FAddOp = struct {
 388         op: *ir.Operation,
 389 
 390         pub const operation_spec = op_specs.define(.{
 391             .mnemonic = "fadd",
 392             .required_attrs = &.{"ftype"},
 393         });
 394         pub const operation_name = operation_spec.name;
 395 
 396         pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value, ftype: encoding.FP2Source.FPType) !FAddOp {
 397             var builder = ir.OperationBuilder.init(ctx);
 398             const fpr_type = try getFPRType(ctx);
 399             var state = op_specs.state(@This(), loc);
 400             state.addOperands(&.{ lhs, rhs });
 401             state.addTypes(&.{fpr_type});
 402 
 403             const op = try builder.create(state);
 404             const type_payload = [_]u8{@backingInt(ftype)};
 405             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 406             return .{ .op = op };
 407         }
 408 
 409         pub fn getResult(self: *const FAddOp) *ir.Value {
 410             return self.op.getResult(0).?;
 411         }
 412     };
 413 
 414     pub const FSubOp = struct {
 415         op: *ir.Operation,
 416 
 417         pub const operation_spec = op_specs.define(.{
 418             .mnemonic = "fsub",
 419             .required_attrs = &.{"ftype"},
 420         });
 421         pub const operation_name = operation_spec.name;
 422 
 423         pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value, ftype: encoding.FP2Source.FPType) !FSubOp {
 424             var builder = ir.OperationBuilder.init(ctx);
 425             const fpr_type = try getFPRType(ctx);
 426             var state = op_specs.state(@This(), loc);
 427             state.addOperands(&.{ lhs, rhs });
 428             state.addTypes(&.{fpr_type});
 429 
 430             const op = try builder.create(state);
 431             const type_payload = [_]u8{@backingInt(ftype)};
 432             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 433             return .{ .op = op };
 434         }
 435 
 436         pub fn getResult(self: *const FSubOp) *ir.Value {
 437             return self.op.getResult(0).?;
 438         }
 439     };
 440 
 441     pub const FMulOp = struct {
 442         op: *ir.Operation,
 443 
 444         pub const operation_spec = op_specs.define(.{
 445             .mnemonic = "fmul",
 446             .required_attrs = &.{"ftype"},
 447         });
 448         pub const operation_name = operation_spec.name;
 449 
 450         pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value, ftype: encoding.FP2Source.FPType) !FMulOp {
 451             var builder = ir.OperationBuilder.init(ctx);
 452             const fpr_type = try getFPRType(ctx);
 453             var state = op_specs.state(@This(), loc);
 454             state.addOperands(&.{ lhs, rhs });
 455             state.addTypes(&.{fpr_type});
 456 
 457             const op = try builder.create(state);
 458             const type_payload = [_]u8{@backingInt(ftype)};
 459             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 460             return .{ .op = op };
 461         }
 462 
 463         pub fn getResult(self: *const FMulOp) *ir.Value {
 464             return self.op.getResult(0).?;
 465         }
 466     };
 467 
 468     pub const FDivOp = struct {
 469         op: *ir.Operation,
 470 
 471         pub const operation_spec = op_specs.define(.{
 472             .mnemonic = "fdiv",
 473             .required_attrs = &.{"ftype"},
 474         });
 475         pub const operation_name = operation_spec.name;
 476 
 477         pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value, ftype: encoding.FP2Source.FPType) !FDivOp {
 478             var builder = ir.OperationBuilder.init(ctx);
 479             const fpr_type = try getFPRType(ctx);
 480             var state = op_specs.state(@This(), loc);
 481             state.addOperands(&.{ lhs, rhs });
 482             state.addTypes(&.{fpr_type});
 483 
 484             const op = try builder.create(state);
 485             const type_payload = [_]u8{@backingInt(ftype)};
 486             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 487             return .{ .op = op };
 488         }
 489 
 490         pub fn getResult(self: *const FDivOp) *ir.Value {
 491             return self.op.getResult(0).?;
 492         }
 493     };
 494 
 495     pub const FCmpOp = struct {
 496         op: *ir.Operation,
 497 
 498         pub const operation_spec = op_specs.define(.{
 499             .mnemonic = "fcmp",
 500             .required_attrs = &.{"ftype"},
 501         });
 502         pub const operation_name = operation_spec.name;
 503 
 504         pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value, ftype: encoding.FP2Source.FPType) !FCmpOp {
 505             var builder = ir.OperationBuilder.init(ctx);
 506             var state = op_specs.state(@This(), loc);
 507             state.addOperands(&.{ lhs, rhs });
 508 
 509             const op = try builder.create(state);
 510             const type_payload = [_]u8{@backingInt(ftype)};
 511             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 512             return .{ .op = op };
 513         }
 514     };
 515 
 516     pub const FMovImmOp = struct {
 517         op: *ir.Operation,
 518 
 519         pub const operation_spec = op_specs.define(.{
 520             .mnemonic = "fmov_imm",
 521             .required_attrs = &.{ "ftype", "imm8" },
 522         });
 523         pub const operation_name = operation_spec.name;
 524 
 525         pub fn create(ctx: *ir.Context, loc: ir.Location, imm8: u8, ftype: encoding.FP2Source.FPType) !FMovImmOp {
 526             var builder = ir.OperationBuilder.init(ctx);
 527             const fpr_type = try getFPRType(ctx);
 528             var state = op_specs.state(@This(), loc);
 529             state.addTypes(&.{fpr_type});
 530 
 531             const op = try builder.create(state);
 532             const imm_payload = [_]u8{imm8};
 533             try op.setAttr("imm8", try ctx.getDialectAttr(attr_names.imm8, &imm_payload));
 534             const type_payload = [_]u8{@backingInt(ftype)};
 535             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 536             return .{ .op = op };
 537         }
 538 
 539         pub fn getResult(self: *const FMovImmOp) *ir.Value {
 540             return self.op.getResult(0).?;
 541         }
 542 
 543         pub fn getImm8(self: *const FMovImmOp) u8 {
 544             if (self.op.getAttrAs(ir.Attribute.DialectAttr, "imm8")) |dialect_attr| {
 545                 if (dialect_attr.payload.len > 0) {
 546                     return dialect_attr.payload[0];
 547                 }
 548             }
 549             return 0;
 550         }
 551     };
 552 
 553     pub const FMovFromGPROp = struct {
 554         op: *ir.Operation,
 555 
 556         pub const operation_spec = op_specs.define(.{
 557             .mnemonic = "fmov_gpr",
 558             .required_attrs = &.{"ftype"},
 559         });
 560         pub const operation_name = operation_spec.name;
 561 
 562         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, ftype: encoding.FP2Source.FPType) !FMovFromGPROp {
 563             var builder = ir.OperationBuilder.init(ctx);
 564             const fpr_type = try getFPRType(ctx);
 565             var state = op_specs.state(@This(), loc);
 566             state.addOperands(&.{src});
 567             state.addTypes(&.{fpr_type});
 568 
 569             const op = try builder.create(state);
 570             const type_payload = [_]u8{@backingInt(ftype)};
 571             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 572             return .{ .op = op };
 573         }
 574 
 575         pub fn getResult(self: *const FMovFromGPROp) *ir.Value {
 576             return self.op.getResult(0).?;
 577         }
 578 
 579         pub fn getSrc(self: *const FMovFromGPROp) *ir.Value {
 580             return self.op.getOperand(0).?;
 581         }
 582     };
 583 
 584     pub const FMovFromFPROp = struct {
 585         op: *ir.Operation,
 586 
 587         pub const operation_spec = op_specs.define(.{
 588             .mnemonic = "fmov_fpr",
 589             .required_attrs = &.{"ftype"},
 590         });
 591         pub const operation_name = operation_spec.name;
 592 
 593         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, ftype: encoding.FP2Source.FPType) !FMovFromFPROp {
 594             var builder = ir.OperationBuilder.init(ctx);
 595             const gpr_type = try getGPRType(ctx);
 596             var state = op_specs.state(@This(), loc);
 597             state.addOperands(&.{src});
 598             state.addTypes(&.{gpr_type});
 599 
 600             const op = try builder.create(state);
 601             const type_payload = [_]u8{@backingInt(ftype)};
 602             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 603             return .{ .op = op };
 604         }
 605 
 606         pub fn getResult(self: *const FMovFromFPROp) *ir.Value {
 607             return self.op.getResult(0).?;
 608         }
 609 
 610         pub fn getSrc(self: *const FMovFromFPROp) *ir.Value {
 611             return self.op.getOperand(0).?;
 612         }
 613     };
 614 
 615     pub const FMovOp = struct {
 616         op: *ir.Operation,
 617 
 618         pub const operation_spec = op_specs.define(.{
 619             .mnemonic = "fmov",
 620             .required_attrs = &.{"ftype"},
 621         });
 622         pub const operation_name = operation_spec.name;
 623 
 624         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, ftype: encoding.FP2Source.FPType) !FMovOp {
 625             var builder = ir.OperationBuilder.init(ctx);
 626             const fpr_type = try getFPRType(ctx);
 627             var state = op_specs.state(@This(), loc);
 628             state.addOperands(&.{src});
 629             state.addTypes(&.{fpr_type});
 630 
 631             const op = try builder.create(state);
 632             const type_payload = [_]u8{@backingInt(ftype)};
 633             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 634             return .{ .op = op };
 635         }
 636 
 637         pub fn getResult(self: *const FMovOp) *ir.Value {
 638             return self.op.getResult(0).?;
 639         }
 640 
 641         pub fn getSrc(self: *const FMovOp) *ir.Value {
 642             return self.op.getOperand(0).?;
 643         }
 644 
 645         pub fn getFType(self: FMovOp) encoding.FP2Source.FPType {
 646             if (self.op.getAttrAs(ir.Attribute.DialectAttr, "ftype")) |dialect_attr| {
 647                 if (dialect_attr.payload.len > 0) {
 648                     return @fromBackingInt(@intCast(dialect_attr.payload[0]));
 649                 }
 650             }
 651             return .single;
 652         }
 653     };
 654 
 655     pub const FSqrtOp = struct {
 656         op: *ir.Operation,
 657 
 658         pub const operation_spec = op_specs.define(.{
 659             .mnemonic = "fsqrt",
 660             .required_attrs = &.{"ftype"},
 661         });
 662         pub const operation_name = operation_spec.name;
 663 
 664         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, ftype: encoding.FP2Source.FPType) !FSqrtOp {
 665             var builder = ir.OperationBuilder.init(ctx);
 666             const fpr_type = try getFPRType(ctx);
 667             var state = op_specs.state(@This(), loc);
 668             state.addOperands(&.{src});
 669             state.addTypes(&.{fpr_type});
 670 
 671             const op = try builder.create(state);
 672             const type_payload = [_]u8{@backingInt(ftype)};
 673             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 674             return .{ .op = op };
 675         }
 676 
 677         pub fn getResult(self: *const FSqrtOp) *ir.Value {
 678             return self.op.getResult(0).?;
 679         }
 680 
 681         pub fn getSrc(self: *const FSqrtOp) *ir.Value {
 682             return self.op.getOperand(0).?;
 683         }
 684     };
 685 
 686     pub const FNegOp = struct {
 687         op: *ir.Operation,
 688 
 689         pub const operation_spec = op_specs.define(.{
 690             .mnemonic = "fneg",
 691             .required_attrs = &.{"ftype"},
 692         });
 693         pub const operation_name = operation_spec.name;
 694 
 695         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, ftype: encoding.FP2Source.FPType) !FNegOp {
 696             var builder = ir.OperationBuilder.init(ctx);
 697             const fpr_type = try getFPRType(ctx);
 698             var state = op_specs.state(@This(), loc);
 699             state.addOperands(&.{src});
 700             state.addTypes(&.{fpr_type});
 701 
 702             const op = try builder.create(state);
 703             const type_payload = [_]u8{@backingInt(ftype)};
 704             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 705             return .{ .op = op };
 706         }
 707 
 708         pub fn getResult(self: *const FNegOp) *ir.Value {
 709             return self.op.getResult(0).?;
 710         }
 711 
 712         pub fn getSrc(self: *const FNegOp) *ir.Value {
 713             return self.op.getOperand(0).?;
 714         }
 715     };
 716 
 717     pub const FAbsOp = struct {
 718         op: *ir.Operation,
 719 
 720         pub const operation_spec = op_specs.define(.{
 721             .mnemonic = "fabs",
 722             .required_attrs = &.{"ftype"},
 723         });
 724         pub const operation_name = operation_spec.name;
 725 
 726         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, ftype: encoding.FP2Source.FPType) !FAbsOp {
 727             var builder = ir.OperationBuilder.init(ctx);
 728             const fpr_type = try getFPRType(ctx);
 729             var state = op_specs.state(@This(), loc);
 730             state.addOperands(&.{src});
 731             state.addTypes(&.{fpr_type});
 732 
 733             const op = try builder.create(state);
 734             const type_payload = [_]u8{@backingInt(ftype)};
 735             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
 736             return .{ .op = op };
 737         }
 738 
 739         pub fn getResult(self: *const FAbsOp) *ir.Value {
 740             return self.op.getResult(0).?;
 741         }
 742 
 743         pub fn getSrc(self: *const FAbsOp) *ir.Value {
 744             return self.op.getOperand(0).?;
 745         }
 746     };
 747 
 748     pub const LdrOp = struct {
 749         op: *ir.Operation,
 750 
 751         pub const operation_spec = op_specs.define(.{
 752             .mnemonic = "ldr",
 753             .required_attrs = &.{ "offset", "size" },
 754         });
 755         pub const operation_name = operation_spec.name;
 756 
 757         pub fn create(ctx: *ir.Context, loc: ir.Location, base: *ir.Value, offset: u12, size: GPRSize) !LdrOp {
 758             var builder = ir.OperationBuilder.init(ctx);
 759             const gpr_type = try getGPRType(ctx);
 760             var state = op_specs.state(@This(), loc);
 761             state.addOperands(&.{base});
 762             state.addTypes(&.{gpr_type});
 763 
 764             const op = try builder.create(state);
 765             try op.setAttr("offset", try getImm12Attr(ctx, offset));
 766             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 767             return .{ .op = op };
 768         }
 769 
 770         pub fn getResult(self: *const LdrOp) *ir.Value {
 771             return self.op.getResult(0).?;
 772         }
 773 
 774         pub fn getBase(self: LdrOp) *ir.Value {
 775             return self.op.operands.items[0].value;
 776         }
 777 
 778         pub fn getOffset(self: LdrOp) u12 {
 779             if (self.op.getAttr("offset")) |attr| {
 780                 return getImm12Value(attr) orelse 0;
 781             }
 782             return 0;
 783         }
 784     };
 785 
 786     pub const StrOp = struct {
 787         op: *ir.Operation,
 788 
 789         pub const operation_spec = op_specs.define(.{
 790             .mnemonic = "str",
 791             .required_attrs = &.{ "offset", "size" },
 792         });
 793         pub const operation_name = operation_spec.name;
 794 
 795         pub fn create(ctx: *ir.Context, loc: ir.Location, value: *ir.Value, base: *ir.Value, offset: u12, size: GPRSize) !StrOp {
 796             var builder = ir.OperationBuilder.init(ctx);
 797             var state = op_specs.state(@This(), loc);
 798             state.addOperands(&.{ value, base });
 799 
 800             const op = try builder.create(state);
 801             try op.setAttr("offset", try getImm12Attr(ctx, offset));
 802             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 803             return .{ .op = op };
 804         }
 805 
 806         pub fn getValue(self: StrOp) *ir.Value {
 807             return self.op.operands.items[0].value;
 808         }
 809 
 810         pub fn getBase(self: StrOp) *ir.Value {
 811             return self.op.operands.items[1].value;
 812         }
 813 
 814         pub fn getOffset(self: StrOp) u12 {
 815             if (self.op.getAttr("offset")) |attr| {
 816                 return getImm12Value(attr) orelse 0;
 817             }
 818             return 0;
 819         }
 820     };
 821 
 822     pub const BOp = struct {
 823         op: *ir.Operation,
 824 
 825         pub const operation_spec = op_specs.define(.{ .mnemonic = "b" });
 826         pub const operation_name = operation_spec.name;
 827 
 828         pub fn create(ctx: *ir.Context, loc: ir.Location, target: *ir.Block) !BOp {
 829             var builder = ir.OperationBuilder.init(ctx);
 830             var state = op_specs.state(@This(), loc);
 831             state.addSuccessors(&.{target});
 832 
 833             const op = try builder.create(state);
 834             return .{ .op = op };
 835         }
 836 
 837         pub fn getTarget(self: BOp) *ir.Block {
 838             return self.op.getSuccessor(0).?;
 839         }
 840     };
 841 
 842     pub const BCondOp = struct {
 843         op: *ir.Operation,
 844 
 845         pub const operation_spec = op_specs.define(.{
 846             .mnemonic = "b_cond",
 847             .required_attrs = &.{"cond"},
 848         });
 849         pub const operation_name = operation_spec.name;
 850 
 851         pub fn create(ctx: *ir.Context, loc: ir.Location, cond: Condition, true_target: *ir.Block, false_target: *ir.Block) !BCondOp {
 852             var builder = ir.OperationBuilder.init(ctx);
 853             var state = op_specs.state(@This(), loc);
 854             state.addSuccessors(&.{ true_target, false_target });
 855 
 856             const op = try builder.create(state);
 857             try op.setAttr("cond", try getConditionAttr(ctx, cond));
 858             return .{ .op = op };
 859         }
 860 
 861         pub fn getCondition(self: BCondOp) Condition {
 862             if (self.op.getAttr("cond")) |attr| {
 863                 return getConditionValue(attr) orelse .al;
 864             }
 865             return .al;
 866         }
 867 
 868         pub fn getTrueTarget(self: BCondOp) *ir.Block {
 869             return self.op.getSuccessor(0).?;
 870         }
 871 
 872         pub fn getFalseTarget(self: BCondOp) *ir.Block {
 873             return self.op.getSuccessor(1).?;
 874         }
 875     };
 876 
 877     pub const RetOp = struct {
 878         op: *ir.Operation,
 879 
 880         pub const operation_spec = op_specs.define(.{ .mnemonic = "ret" });
 881         pub const operation_name = operation_spec.name;
 882 
 883         pub fn create(ctx: *ir.Context, loc: ir.Location) !RetOp {
 884             var builder = ir.OperationBuilder.init(ctx);
 885             const state = op_specs.state(@This(), loc);
 886 
 887             const op = try builder.create(state);
 888             return .{ .op = op };
 889         }
 890     };
 891 
 892     pub const BlOp = struct {
 893         op: *ir.Operation,
 894 
 895         pub const operation_spec = op_specs.define(.{
 896             .mnemonic = "bl",
 897             .required_attrs = &.{"callee"},
 898         });
 899         pub const operation_name = operation_spec.name;
 900 
 901         pub fn create(ctx: *ir.Context, loc: ir.Location, callee: []const u8, args: []const *ir.Value, result_types: []const ir.Type) !BlOp {
 902             var builder = ir.OperationBuilder.init(ctx);
 903             var state = op_specs.state(@This(), loc);
 904             state.addOperands(args);
 905             state.addTypes(result_types);
 906 
 907             const op = try builder.create(state);
 908             const name_attr = try ctx.getDialectAttr("choir.string", callee);
 909             try op.setAttr("callee", name_attr);
 910             return .{ .op = op };
 911         }
 912     };
 913 
 914     pub const PrologueOp = struct {
 915         op: *ir.Operation,
 916 
 917         pub const operation_spec = op_specs.define(.{
 918             .mnemonic = "prologue",
 919             .required_attrs = &.{"frame_size"},
 920         });
 921         pub const operation_name = operation_spec.name;
 922 
 923         pub fn create(ctx: *ir.Context, loc: ir.Location, frame_size: i32) !PrologueOp {
 924             try loadSpec(ctx);
 925             var builder = ir.OperationBuilder.init(ctx);
 926             const state = op_specs.state(@This(), loc);
 927 
 928             const op = try builder.create(state);
 929             try op.setAttr("frame_size", try getFrameOffsetAttr(ctx, frame_size));
 930             return .{ .op = op };
 931         }
 932 
 933         pub fn getFrameSize(self: PrologueOp) i32 {
 934             if (self.op.getAttr("frame_size")) |attr| {
 935                 return getFrameOffsetValue(attr) orelse 0;
 936             }
 937             return 0;
 938         }
 939     };
 940 
 941     pub const EpilogueOp = struct {
 942         op: *ir.Operation,
 943 
 944         pub const operation_spec = op_specs.define(.{
 945             .mnemonic = "epilogue",
 946             .required_attrs = &.{"frame_size"},
 947         });
 948         pub const operation_name = operation_spec.name;
 949 
 950         pub fn create(ctx: *ir.Context, loc: ir.Location, frame_size: i32) !EpilogueOp {
 951             var builder = ir.OperationBuilder.init(ctx);
 952             const state = op_specs.state(@This(), loc);
 953 
 954             const op = try builder.create(state);
 955             try op.setAttr("frame_size", try getFrameOffsetAttr(ctx, frame_size));
 956             return .{ .op = op };
 957         }
 958 
 959         pub fn getFrameSize(self: EpilogueOp) i32 {
 960             if (self.op.getAttr("frame_size")) |attr| {
 961                 return getFrameOffsetValue(attr) orelse 0;
 962             }
 963             return 0;
 964         }
 965     };
 966 
 967     pub const MovOp = struct {
 968         op: *ir.Operation,
 969 
 970         pub const operation_spec = op_specs.define(.{
 971             .mnemonic = "mov",
 972             .required_attrs = &.{"size"},
 973         });
 974         pub const operation_name = operation_spec.name;
 975 
 976         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, size: GPRSize) !MovOp {
 977             var builder = ir.OperationBuilder.init(ctx);
 978             const gpr_type = try getGPRType(ctx);
 979             var state = op_specs.state(@This(), loc);
 980             state.addOperands(&.{src});
 981             state.addTypes(&.{gpr_type});
 982 
 983             const op = try builder.create(state);
 984             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
 985             return .{ .op = op };
 986         }
 987 
 988         pub fn getResult(self: *const MovOp) *ir.Value {
 989             return self.op.getResult(0).?;
 990         }
 991 
 992         pub fn getSrc(self: MovOp) *ir.Value {
 993             return self.op.operands.items[0].value;
 994         }
 995     };
 996 
 997     pub const MovzOp = struct {
 998         op: *ir.Operation,
 999 
1000         pub const operation_spec = op_specs.define(.{
1001             .mnemonic = "movz",
1002             .required_attrs = &.{ "imm", "shift", "size" },
1003         });
1004         pub const operation_name = operation_spec.name;
1005 
1006         pub fn create(ctx: *ir.Context, loc: ir.Location, imm: u16, shift: u2, size: GPRSize) !MovzOp {
1007             var builder = ir.OperationBuilder.init(ctx);
1008             const gpr_type = try getGPRType(ctx);
1009             var state = op_specs.state(@This(), loc);
1010             state.addTypes(&.{gpr_type});
1011 
1012             const op = try builder.create(state);
1013             var buf: [8]u8 = undefined;
1014             const imm_payload = try ir.format.intPayload(buf[0..], imm);
1015             try op.setAttr("imm", try ctx.getDialectAttr(attr_names.imm16, imm_payload));
1016             const shift_payload = [_]u8{shift};
1017             try op.setAttr("shift", try ctx.getDialectAttr("aarch64.shift", &shift_payload));
1018             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
1019             return .{ .op = op };
1020         }
1021 
1022         pub fn getResult(self: *const MovzOp) *ir.Value {
1023             return self.op.getResult(0).?;
1024         }
1025     };
1026 
1027     pub const MovnOp = struct {
1028         op: *ir.Operation,
1029 
1030         pub const operation_spec = op_specs.define(.{
1031             .mnemonic = "movn",
1032             .required_attrs = &.{ "imm", "shift", "size" },
1033         });
1034         pub const operation_name = operation_spec.name;
1035 
1036         pub fn create(ctx: *ir.Context, loc: ir.Location, imm: u16, shift: u2, size: GPRSize) !MovnOp {
1037             var builder = ir.OperationBuilder.init(ctx);
1038             const gpr_type = try getGPRType(ctx);
1039             var state = op_specs.state(@This(), loc);
1040             state.addTypes(&.{gpr_type});
1041 
1042             const op = try builder.create(state);
1043             var buf: [8]u8 = undefined;
1044             const imm_payload = try ir.format.intPayload(buf[0..], imm);
1045             try op.setAttr("imm", try ctx.getDialectAttr(attr_names.imm16, imm_payload));
1046             const shift_payload = [_]u8{shift};
1047             try op.setAttr("shift", try ctx.getDialectAttr("aarch64.shift", &shift_payload));
1048             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
1049             return .{ .op = op };
1050         }
1051 
1052         pub fn getResult(self: *const MovnOp) *ir.Value {
1053             return self.op.getResult(0).?;
1054         }
1055     };
1056 
1057     pub const MovkOp = struct {
1058         op: *ir.Operation,
1059 
1060         pub const operation_spec = op_specs.define(.{
1061             .mnemonic = "movk",
1062             .required_attrs = &.{ "imm", "shift", "size" },
1063         });
1064         pub const operation_name = operation_spec.name;
1065 
1066         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, imm: u16, shift: u2, size: GPRSize) !MovkOp {
1067             var builder = ir.OperationBuilder.init(ctx);
1068             const gpr_type = try getGPRType(ctx);
1069             var state = op_specs.state(@This(), loc);
1070             state.addOperands(&.{src});
1071             state.addTypes(&.{gpr_type});
1072 
1073             const op = try builder.create(state);
1074             var buf: [8]u8 = undefined;
1075             const imm_payload = try ir.format.intPayload(buf[0..], imm);
1076             try op.setAttr("imm", try ctx.getDialectAttr(attr_names.imm16, imm_payload));
1077             const shift_payload = [_]u8{shift};
1078             try op.setAttr("shift", try ctx.getDialectAttr("aarch64.shift", &shift_payload));
1079             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
1080             return .{ .op = op };
1081         }
1082 
1083         pub fn getResult(self: *const MovkOp) *ir.Value {
1084             return self.op.getResult(0).?;
1085         }
1086 
1087         pub fn getSrc(self: *const MovkOp) *ir.Value {
1088             return self.op.getOperand(0).?;
1089         }
1090     };
1091 
1092     pub const CopyToRegOp = struct {
1093         op: *ir.Operation,
1094 
1095         pub const operation_spec = op_specs.define(.{
1096             .mnemonic = "copy_to_reg",
1097             .required_attrs = &.{ "dest_reg", "size" },
1098         });
1099         pub const operation_name = operation_spec.name;
1100 
1101         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, dest_reg: u8, size: GPRSize) !CopyToRegOp {
1102             var builder = ir.OperationBuilder.init(ctx);
1103             const gpr_type = try getGPRType(ctx);
1104             var state = op_specs.state(@This(), loc);
1105             state.addOperands(&.{src});
1106             state.addTypes(&.{gpr_type});
1107 
1108             const op = try builder.create(state);
1109             const reg_payload = [_]u8{dest_reg};
1110             try op.setAttr("dest_reg", try ctx.getDialectAttr("aarch64.reg_num", &reg_payload));
1111             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
1112             return .{ .op = op };
1113         }
1114 
1115         pub fn getResult(self: *const CopyToRegOp) *ir.Value {
1116             return self.op.getResult(0).?;
1117         }
1118 
1119         pub fn getSrc(self: CopyToRegOp) *ir.Value {
1120             return self.op.operands.items[0].value;
1121         }
1122 
1123         pub fn getDestReg(self: CopyToRegOp) u8 {
1124             if (self.op.getAttrAs(ir.Attribute.DialectAttr, "dest_reg")) |dialect_attr| {
1125                 if (dialect_attr.payload.len > 0) {
1126                     return dialect_attr.payload[0];
1127                 }
1128             }
1129             return 0;
1130         }
1131 
1132         pub fn getSize(self: CopyToRegOp) GPRSize {
1133             if (self.op.getAttr("size")) |attr| {
1134                 return getGPRSizeValue(attr) orelse .x;
1135             }
1136             return .x;
1137         }
1138     };
1139 
1140     pub const CopyToSameRegAsOp = struct {
1141         op: *ir.Operation,
1142 
1143         pub const operation_spec = op_specs.define(.{
1144             .mnemonic = "copy_to_same_reg",
1145             .required_attrs = &.{"size"},
1146         });
1147         pub const operation_name = operation_spec.name;
1148 
1149         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, target_ref: *ir.Value, size: GPRSize) !CopyToSameRegAsOp {
1150             var builder = ir.OperationBuilder.init(ctx);
1151             const gpr_type = try getGPRType(ctx);
1152             var state = op_specs.state(@This(), loc);
1153             state.addOperands(&.{ src, target_ref });
1154             state.addTypes(&.{gpr_type});
1155 
1156             const op = try builder.create(state);
1157             try op.setAttr("size", try getGPRSizeAttr(ctx, size));
1158             return .{ .op = op };
1159         }
1160 
1161         pub fn getResult(self: *const CopyToSameRegAsOp) *ir.Value {
1162             return self.op.getResult(0).?;
1163         }
1164 
1165         pub fn getSrc(self: CopyToSameRegAsOp) *ir.Value {
1166             return self.op.operands.items[0].value;
1167         }
1168 
1169         pub fn getTargetRef(self: CopyToSameRegAsOp) *ir.Value {
1170             return self.op.operands.items[1].value;
1171         }
1172 
1173         pub fn getSize(self: CopyToSameRegAsOp) GPRSize {
1174             if (self.op.getAttr("size")) |attr| {
1175                 return getGPRSizeValue(attr) orelse .x;
1176             }
1177             return .x;
1178         }
1179     };
1180 
1181     pub const CopyToSameFPRegAsOp = struct {
1182         op: *ir.Operation,
1183 
1184         pub const operation_spec = op_specs.define(.{
1185             .mnemonic = "copy_to_same_fpreg",
1186             .required_attrs = &.{"ftype"},
1187         });
1188         pub const operation_name = operation_spec.name;
1189 
1190         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, target_ref: *ir.Value, ftype: encoding.FP2Source.FPType) !CopyToSameFPRegAsOp {
1191             var builder = ir.OperationBuilder.init(ctx);
1192             const fpr_type = try getFPRType(ctx);
1193             var state = op_specs.state(@This(), loc);
1194             state.addOperands(&.{ src, target_ref });
1195             state.addTypes(&.{fpr_type});
1196 
1197             const op = try builder.create(state);
1198             const type_payload = [_]u8{@backingInt(ftype)};
1199             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
1200             return .{ .op = op };
1201         }
1202 
1203         pub fn getResult(self: *const CopyToSameFPRegAsOp) *ir.Value {
1204             return self.op.getResult(0).?;
1205         }
1206 
1207         pub fn getSrc(self: CopyToSameFPRegAsOp) *ir.Value {
1208             return self.op.operands.items[0].value;
1209         }
1210 
1211         pub fn getTargetRef(self: CopyToSameFPRegAsOp) *ir.Value {
1212             return self.op.operands.items[1].value;
1213         }
1214 
1215         pub fn getFType(self: CopyToSameFPRegAsOp) encoding.FP2Source.FPType {
1216             if (self.op.getAttrAs(ir.Attribute.DialectAttr, "ftype")) |dialect_attr| {
1217                 if (dialect_attr.payload.len > 0) {
1218                     return @fromBackingInt(@intCast(dialect_attr.payload[0]));
1219                 }
1220             }
1221             return .single;
1222         }
1223     };
1224 
1225     pub const CopyToFPRegOp = struct {
1226         op: *ir.Operation,
1227 
1228         pub const operation_spec = op_specs.define(.{
1229             .mnemonic = "copy_to_fpreg",
1230             .required_attrs = &.{ "dest_reg", "ftype" },
1231         });
1232         pub const operation_name = operation_spec.name;
1233 
1234         pub fn create(ctx: *ir.Context, loc: ir.Location, src: *ir.Value, dest_reg: u8, ftype: encoding.FP2Source.FPType) !CopyToFPRegOp {
1235             var builder = ir.OperationBuilder.init(ctx);
1236             const fpr_type = try getFPRType(ctx);
1237             var state = op_specs.state(@This(), loc);
1238             state.addOperands(&.{src});
1239             state.addTypes(&.{fpr_type});
1240 
1241             const op = try builder.create(state);
1242             const reg_payload = [_]u8{dest_reg};
1243             try op.setAttr("dest_reg", try ctx.getDialectAttr("aarch64.fpreg_num", &reg_payload));
1244             const type_payload = [_]u8{@backingInt(ftype)};
1245             try op.setAttr("ftype", try ctx.getDialectAttr(attr_names.fp_type, &type_payload));
1246             return .{ .op = op };
1247         }
1248 
1249         pub fn getResult(self: *const CopyToFPRegOp) *ir.Value {
1250             return self.op.getResult(0).?;
1251         }
1252 
1253         pub fn getSrc(self: CopyToFPRegOp) *ir.Value {
1254             return self.op.operands.items[0].value;
1255         }
1256 
1257         pub fn getDestReg(self: CopyToFPRegOp) u8 {
1258             if (self.op.getAttrAs(ir.Attribute.DialectAttr, "dest_reg")) |dialect_attr| {
1259                 if (dialect_attr.payload.len > 0) {
1260                     return dialect_attr.payload[0];
1261                 }
1262             }
1263             return 0;
1264         }
1265 
1266         pub fn getFType(self: CopyToFPRegOp) encoding.FP2Source.FPType {
1267             if (self.op.getAttrAs(ir.Attribute.DialectAttr, "ftype")) |dialect_attr| {
1268                 if (dialect_attr.payload.len > 0) {
1269                     return @fromBackingInt(@intCast(dialect_attr.payload[0]));
1270                 }
1271             }
1272             return .single;
1273         }
1274     };
1275 
1276     fn loadSpec(ctx: *ir.Context) !void {
1277         try ir.dialects.loadDialectSpec(ctx, spec);
1278     }
1279 
1280     pub fn getGPRType(ctx: *ir.Context) !ir.Type {
1281         try loadSpec(ctx);
1282         return ctx.getDialectTypeFromName(type_names.gpr);
1283     }
1284 
1285     pub fn getFPRType(ctx: *ir.Context) !ir.Type {
1286         try loadSpec(ctx);
1287         return ctx.getDialectTypeFromName(type_names.fpr);
1288     }
1289 
1290     pub fn getGPRAttr(ctx: *ir.Context, reg: GPR) !ir.Attribute {
1291         var buf: [4]u8 = undefined;
1292         const payload = try ir.format.intPayload(buf[0..], @backingInt(reg));
1293         return ctx.getDialectAttr(attr_names.gpr_reg, payload);
1294     }
1295 
1296     pub fn getGPRValue(attr: ir.Attribute) ?GPR {
1297         if (!std.mem.eql(u8, attr.abstract.name, attr_names.gpr_reg)) return null;
1298         const dialect_attr = attr.cast(ir.Attribute.DialectAttr) orelse return null;
1299         const val = std.fmt.parseInt(u5, dialect_attr.payload, 10) catch return null;
1300         return @fromBackingInt(@intCast(val));
1301     }
1302 
1303     pub fn getFPRAttr(ctx: *ir.Context, reg: FPR) !ir.Attribute {
1304         var buf: [4]u8 = undefined;
1305         const payload = try ir.format.intPayload(buf[0..], @backingInt(reg));
1306         return ctx.getDialectAttr(attr_names.fpr_reg, payload);
1307     }
1308 
1309     pub fn getFPRValue(attr: ir.Attribute) ?FPR {
1310         if (!std.mem.eql(u8, attr.abstract.name, attr_names.fpr_reg)) return null;
1311         const dialect_attr = attr.cast(ir.Attribute.DialectAttr) orelse return null;
1312         const val = std.fmt.parseInt(u5, dialect_attr.payload, 10) catch return null;
1313         return @fromBackingInt(@intCast(val));
1314     }
1315 
1316     pub fn getImm12Attr(ctx: *ir.Context, value: u12) !ir.Attribute {
1317         var buf: [8]u8 = undefined;
1318         const payload = try ir.format.intPayload(buf[0..], value);
1319         return ctx.getDialectAttr(attr_names.imm12, payload);
1320     }
1321 
1322     pub fn getImm12Value(attr: ir.Attribute) ?u12 {
1323         if (!std.mem.eql(u8, attr.abstract.name, attr_names.imm12)) return null;
1324         const dialect_attr = attr.cast(ir.Attribute.DialectAttr) orelse return null;
1325         return std.fmt.parseInt(u12, dialect_attr.payload, 10) catch null;
1326     }
1327 
1328     pub fn getConditionAttr(ctx: *ir.Context, cond: Condition) !ir.Attribute {
1329         var buf: [4]u8 = undefined;
1330         const payload = try ir.format.intPayload(buf[0..], @backingInt(cond));
1331         return ctx.getDialectAttr(attr_names.condition, payload);
1332     }
1333 
1334     pub fn getConditionValue(attr: ir.Attribute) ?Condition {
1335         if (!std.mem.eql(u8, attr.abstract.name, attr_names.condition)) return null;
1336         const dialect_attr = attr.cast(ir.Attribute.DialectAttr) orelse return null;
1337         const val = std.fmt.parseInt(u4, dialect_attr.payload, 10) catch return null;
1338         return @fromBackingInt(@intCast(val));
1339     }
1340 
1341     pub fn getGPRSizeAttr(ctx: *ir.Context, size: GPRSize) !ir.Attribute {
1342         const payload = [_]u8{@backingInt(size)};
1343         return ctx.getDialectAttr(attr_names.gpr_size, &payload);
1344     }
1345 
1346     pub fn getGPRSizeValue(attr: ir.Attribute) ?GPRSize {
1347         if (!std.mem.eql(u8, attr.abstract.name, attr_names.gpr_size)) return null;
1348         const dialect_attr = attr.cast(ir.Attribute.DialectAttr) orelse return null;
1349         if (dialect_attr.payload.len != 1) return null;
1350         return @fromBackingInt(@intCast(dialect_attr.payload[0]));
1351     }
1352 
1353     pub fn getFrameOffsetAttr(ctx: *ir.Context, offset: i32) !ir.Attribute {
1354         var buf: [16]u8 = undefined;
1355         const payload = try ir.format.intPayload(buf[0..], offset);
1356         return ctx.getDialectAttr(attr_names.frame_offset, payload);
1357     }
1358 
1359     pub fn getFrameOffsetValue(attr: ir.Attribute) ?i32 {
1360         if (!std.mem.eql(u8, attr.abstract.name, attr_names.frame_offset)) return null;
1361         const dialect_attr = attr.cast(ir.Attribute.DialectAttr) orelse return null;
1362         return std.fmt.parseInt(i32, dialect_attr.payload, 10) catch null;
1363     }
1364 };
1365 
1366 test "AArch64Dialect.AddOp creates addition" {
1367     const testing = std.testing;
1368     var arena = alloc_arena.Arena.init(std.testing.allocator);
1369     defer arena.deinit();
1370     const allocator = arena.allocator();
1371 
1372     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1373     defer ctx.deinit(allocator);
1374 
1375     const loc = ir.Location.getUnknown();
1376 
1377     var r1 = try AArch64Dialect.GetRegOp.createGPR(&ctx, loc, .x1);
1378     var r2 = try AArch64Dialect.GetRegOp.createGPR(&ctx, loc, .x2);
1379 
1380     var add = try AArch64Dialect.AddOp.create(&ctx, loc, r1.getResult(), r2.getResult(), .x);
1381 
1382     try testing.expectEqualStrings("aarch64.add", add.op.name.name);
1383     try testing.expectEqual(GPRSize.x, add.getSize());
1384 }
1385 
1386 test "AArch64Dialect.GetRegOp materializes register" {
1387     const testing = std.testing;
1388     var arena = alloc_arena.Arena.init(std.testing.allocator);
1389     defer arena.deinit();
1390     const allocator = arena.allocator();
1391 
1392     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1393     defer ctx.deinit(allocator);
1394 
1395     const loc = ir.Location.getUnknown();
1396 
1397     var get_reg = try AArch64Dialect.GetRegOp.createGPR(&ctx, loc, .x19);
1398 
1399     try testing.expectEqualStrings("aarch64.get_reg", get_reg.op.name.name);
1400     try testing.expectEqual(GPR.x19, get_reg.getGPR().?);
1401 }
1402 
1403 test "AArch64Dialect registers inherent attribute names" {
1404     const testing = std.testing;
1405     var arena = alloc_arena.Arena.init(std.testing.allocator);
1406     defer arena.deinit();
1407     const allocator = arena.allocator();
1408 
1409     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1410     defer ctx.deinit(allocator);
1411     try ir.dialects.loadDialectSpec(&ctx, AArch64Dialect.spec);
1412 
1413     const add_info = ctx.lookupOperation(AArch64Dialect.AddOp.operation_name) orelse
1414         return error.TestExpectedOperationInfo;
1415     try testing.expect(add_info.hasInherentAttributeName("size"));
1416 
1417     const movk_info = ctx.lookupOperation(AArch64Dialect.MovkOp.operation_name) orelse
1418         return error.TestExpectedOperationInfo;
1419     try testing.expect(movk_info.hasInherentAttributeName("imm"));
1420     try testing.expect(movk_info.hasInherentAttributeName("shift"));
1421     try testing.expect(movk_info.hasInherentAttributeName("size"));
1422 
1423     const branch_info = ctx.lookupOperation(AArch64Dialect.BlOp.operation_name) orelse
1424         return error.TestExpectedOperationInfo;
1425     try testing.expect(branch_info.hasInherentAttributeName("callee"));
1426 
1427     const loc = ir.Location.getUnknown();
1428     var r1 = try AArch64Dialect.GetRegOp.createGPR(&ctx, loc, .x1);
1429     var r2 = try AArch64Dialect.GetRegOp.createGPR(&ctx, loc, .x2);
1430     const add = try AArch64Dialect.AddOp.create(&ctx, loc, r1.getResult(), r2.getResult(), .x);
1431 
1432     try testing.expect(!add.op.isDiscardableAttrName("size"));
1433     try testing.expectEqual(@as(usize, 0), add.op.countDiscardableAttrs());
1434 }
1435 
1436 test "AArch64Dialect.PrologueOp stores frame size" {
1437     const testing = std.testing;
1438     var arena = alloc_arena.Arena.init(std.testing.allocator);
1439     defer arena.deinit();
1440     const allocator = arena.allocator();
1441 
1442     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1443     defer ctx.deinit(allocator);
1444 
1445     const loc = ir.Location.getUnknown();
1446 
1447     var prologue = try AArch64Dialect.PrologueOp.create(&ctx, loc, 32);
1448 
1449     try testing.expectEqual(@as(i32, 32), prologue.getFrameSize());
1450 }
1451 
1452 test "AArch64Dialect attribute helpers round trip" {
1453     const testing = std.testing;
1454     var arena = alloc_arena.Arena.init(std.testing.allocator);
1455     defer arena.deinit();
1456     const allocator = arena.allocator();
1457 
1458     var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1459     defer ctx.deinit(allocator);
1460 
1461     const gpr_attr = try AArch64Dialect.getGPRAttr(&ctx, .x7);
1462     try testing.expectEqual(GPR.x7, AArch64Dialect.getGPRValue(gpr_attr).?);
1463 
1464     const fpr_attr = try AArch64Dialect.getFPRAttr(&ctx, .v3);
1465     try testing.expectEqual(FPR.v3, AArch64Dialect.getFPRValue(fpr_attr).?);
1466 
1467     const imm_attr = try AArch64Dialect.getImm12Attr(&ctx, 4095);
1468     try testing.expectEqual(@as(u12, 4095), AArch64Dialect.getImm12Value(imm_attr).?);
1469 
1470     const cond_attr = try AArch64Dialect.getConditionAttr(&ctx, .ne);
1471     try testing.expectEqual(Condition.ne, AArch64Dialect.getConditionValue(cond_attr).?);
1472 }