authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-12-09 14:31:08-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-12-09 14:31:08-05:00
logae3fd86dcc6f39601aa26ce1a6dc51a0db5cbf30
tree2fe661f8de3e9e8660a25dac92b916408062e4d9
parentf2911948341e1ba1fc03ba2678eaf0a3c4508fa3
parent2082c275577163a3c3006ae15b0c8af51a414f7d
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7367 from kubkon/aarch64-stp-ldp

stage2: add aarch64 load/store pair of registers instructions

2 files changed, 272 insertions(+), 153 deletions(-)

src/codegen.zig+29-28
...@@ -2730,16 +2730,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2730,16 +2730,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2730 // For MachO, the binary, with the exception of object files, has to be a PIE.2730 // For MachO, the binary, with the exception of object files, has to be a PIE.
2731 // Therefore we cannot load an absolute address.2731 // Therefore we cannot load an absolute address.
2732 // Instead, we need to make use of PC-relative addressing.2732 // Instead, we need to make use of PC-relative addressing.
2733 // TODO This needs to be optimised in the stack usage (perhaps use a shadow stack
2734 // like described here:
2735 // https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/using-the-stack-in-aarch64-implementing-push-and-pop)
2736 // TODO As far as branching is concerned, instead of saving the return address
2737 // in a register, I'm thinking here of immitating x86_64, and having the address
2738 // passed on the stack.
2739 if (reg.id() == 0) { // x0 is special-cased2733 if (reg.id() == 0) { // x0 is special-cased
2734 // TODO This needs to be optimised in the stack usage (perhaps use a shadow stack
2735 // like described here:
2736 // https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/using-the-stack-in-aarch64-implementing-push-and-pop)
2740 // str x28, [sp, #-16]2737 // str x28, [sp, #-16]
2741 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x28, Register.sp, .{2738 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x28, Register.sp, .{
2742 .offset = Instruction.Offset.imm_pre_index(-16),2739 .offset = Instruction.LoadStoreOffset.imm_pre_index(-16),
2743 }).toU32());2740 }).toU32());
2744 // adr x28, #82741 // adr x28, #8
2745 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32());2742 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32());
...@@ -2755,21 +2752,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2755,21 +2752,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2755 // b [label]2752 // b [label]
2756 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32());2753 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32());
2757 // mov r, x02754 // mov r, x0
2758 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(reg, .x0, Instruction.RegisterShift.none()).toU32());2755 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(
2756 reg,
2757 .x0,
2758 Instruction.RegisterShift.none(),
2759 ).toU32());
2759 // ldr x28, [sp], #162760 // ldr x28, [sp], #16
2760 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{2761 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{
2761 .rn = Register.sp,2762 .rn = Register.sp,
2762 .offset = Instruction.Offset.imm_post_index(16),2763 .offset = Instruction.LoadStoreOffset.imm_post_index(16),
2763 }).toU32());2764 }).toU32());
2764 } else {2765 } else {
2765 // str x28, [sp, #-16]2766 // stp x0, x28, [sp, #-16]
2766 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x28, Register.sp, .{2767 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.stp(
2767 .offset = Instruction.Offset.imm_pre_index(-16),2768 .x0,
2768 }).toU32());2769 .x28,
2769 // str x0, [sp, #-16]2770 Register.sp,
2770 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.str(.x0, Register.sp, .{2771 Instruction.LoadStorePairOffset.pre_index(-16),
2771 .offset = Instruction.Offset.imm_pre_index(-16),2772 ).toU32());
2772 }).toU32());
2773 // adr x28, #82773 // adr x28, #8
2774 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32());2774 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.adr(.x28, 8).toU32());
2775 if (self.bin_file.cast(link.File.MachO)) |macho_file| {2775 if (self.bin_file.cast(link.File.MachO)) |macho_file| {
...@@ -2784,17 +2784,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2784,17 +2784,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2784 // b [label]2784 // b [label]
2785 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32());2785 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.b(0).toU32());
2786 // mov r, x02786 // mov r, x0
2787 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(reg, .x0, Instruction.RegisterShift.none()).toU32());2787 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.orr(
2788 // ldr x0, [sp], #162788 reg,
2789 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x0, .{2789 .x0,
2790 .rn = Register.sp,2790 Instruction.RegisterShift.none(),
2791 .offset = Instruction.Offset.imm_post_index(16),2791 ).toU32());
2792 }).toU32());2792 // ldp x0, x28, [sp, #16]
2793 // ldr x28, [sp], #162793 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldp(
2794 mem.writeIntLittle(u32, try self.code.addManyAsArray(4), Instruction.ldr(.x28, .{2794 .x0,
2795 .rn = Register.sp,2795 .x28,
2796 .offset = Instruction.Offset.imm_post_index(16),2796 Register.sp,
2797 }).toU32());2797 Instruction.LoadStorePairOffset.post_index(16),
2798 ).toU32());
2798 }2799 }
2799 } else {2800 } else {
2800 // The value is in memory at a hard-coded address.2801 // The value is in memory at a hard-coded address.
src/codegen/aarch64.zig+243-125
...@@ -19,7 +19,7 @@ pub const Register = enum(u6) {...@@ -19,7 +19,7 @@ pub const Register = enum(u6) {
19 w16, w17, w18, w19, w20, w21, w22, w23,19 w16, w17, w18, w19, w20, w21, w22, w23,
20 w24, w25, w26, w27, w28, w29, w30, wzr,20 w24, w25, w26, w27, w28, w29, w30, wzr,
2121
22 pub const sp = .xzr;22 pub const sp = Register.xzr;
2323
24 pub fn id(self: Register) u5 {24 pub fn id(self: Register) u5 {
25 return @truncate(u5, @enumToInt(self));25 return @truncate(u5, @enumToInt(self));
...@@ -72,6 +72,9 @@ test "Register.id" {...@@ -72,6 +72,9 @@ test "Register.id" {
7272
73 testing.expectEqual(@as(u5, 31), Register.xzr.id());73 testing.expectEqual(@as(u5, 31), Register.xzr.id());
74 testing.expectEqual(@as(u5, 31), Register.wzr.id());74 testing.expectEqual(@as(u5, 31), Register.wzr.id());
75
76 testing.expectEqual(@as(u5, 31), Register.sp.id());
77 testing.expectEqual(@as(u5, 31), Register.sp.id());
75}78}
7679
77test "Register.size" {80test "Register.size" {
...@@ -232,6 +235,16 @@ pub const Instruction = union(enum) {...@@ -232,6 +235,16 @@ pub const Instruction = union(enum) {
232 fixed: u4 = 0b111_0,235 fixed: u4 = 0b111_0,
233 size: u2,236 size: u2,
234 },237 },
238 LoadStorePairOfRegisters: packed struct {
239 rt1: u5,
240 rn: u5,
241 rt2: u5,
242 imm7: u7,
243 load: u1,
244 encoding: u2,
245 fixed: u5 = 0b101_0_0,
246 opc: u2,
247 },
235 LoadLiteral: packed struct {248 LoadLiteral: packed struct {
236 rt: u5,249 rt: u5,
237 imm19: u19,250 imm19: u19,
...@@ -268,6 +281,7 @@ pub const Instruction = union(enum) {...@@ -268,6 +281,7 @@ pub const Instruction = union(enum) {
268 .MoveWideImmediate => |v| @bitCast(u32, v),281 .MoveWideImmediate => |v| @bitCast(u32, v),
269 .PCRelativeAddress => |v| @bitCast(u32, v),282 .PCRelativeAddress => |v| @bitCast(u32, v),
270 .LoadStoreRegister => |v| @bitCast(u32, v),283 .LoadStoreRegister => |v| @bitCast(u32, v),
284 .LoadStorePairOfRegisters => |v| @bitCast(u32, v),
271 .LoadLiteral => |v| @bitCast(u32, v),285 .LoadLiteral => |v| @bitCast(u32, v),
272 .ExceptionGeneration => |v| @bitCast(u32, v),286 .ExceptionGeneration => |v| @bitCast(u32, v),
273 .UnconditionalBranchRegister => |v| @bitCast(u32, v),287 .UnconditionalBranchRegister => |v| @bitCast(u32, v),
...@@ -276,123 +290,6 @@ pub const Instruction = union(enum) {...@@ -276,123 +290,6 @@ pub const Instruction = union(enum) {
276 };290 };
277 }291 }
278292
279 /// Represents the offset operand of a load or store instruction.
280 /// Data can be loaded from memory with either an immediate offset
281 /// or an offset that is stored in some register.
282 pub const Offset = union(enum) {
283 Immediate: union(enum) {
284 PostIndex: i9,
285 PreIndex: i9,
286 Unsigned: u12,
287 },
288 Register: struct {
289 rm: u5,
290 shift: union(enum) {
291 Uxtw: u2,
292 Lsl: u2,
293 Sxtw: u2,
294 Sxtx: u2,
295 },
296 },
297
298 pub const none = Offset{
299 .Immediate = .{ .Unsigned = 0 },
300 };
301
302 pub fn toU12(self: Offset) u12 {
303 return switch (self) {
304 .Immediate => |imm_type| switch (imm_type) {
305 .PostIndex => |v| (@intCast(u12, @bitCast(u9, v)) << 2) + 1,
306 .PreIndex => |v| (@intCast(u12, @bitCast(u9, v)) << 2) + 3,
307 .Unsigned => |v| v,
308 },
309 .Register => |r| switch (r.shift) {
310 .Uxtw => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 16 + 2050,
311 .Lsl => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 24 + 2050,
312 .Sxtw => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 48 + 2050,
313 .Sxtx => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 56 + 2050,
314 },
315 };
316 }
317
318 pub fn imm(offset: u12) Offset {
319 return Offset{
320 .Immediate = .{ .Unsigned = offset },
321 };
322 }
323
324 pub fn imm_post_index(offset: i9) Offset {
325 return Offset{
326 .Immediate = .{ .PostIndex = offset },
327 };
328 }
329
330 pub fn imm_pre_index(offset: i9) Offset {
331 return Offset{
332 .Immediate = .{ .PreIndex = offset },
333 };
334 }
335
336 pub fn reg(rm: Register) Offset {
337 return Offset{
338 .Register = .{
339 .rm = rm.id(),
340 .shift = .{
341 .Lsl = 0,
342 },
343 },
344 };
345 }
346
347 pub fn reg_uxtw(rm: Register, shift: u2) Offset {
348 assert(rm.size() == 32 and (shift == 0 or shift == 2));
349 return Offset{
350 .Register = .{
351 .rm = rm.id(),
352 .shift = .{
353 .Uxtw = shift,
354 },
355 },
356 };
357 }
358
359 pub fn reg_lsl(rm: Register, shift: u2) Offset {
360 assert(rm.size() == 64 and (shift == 0 or shift == 3));
361 return Offset{
362 .Register = .{
363 .rm = rm.id(),
364 .shift = .{
365 .Lsl = shift,
366 },
367 },
368 };
369 }
370
371 pub fn reg_sxtw(rm: Register, shift: u2) Offset {
372 assert(rm.size() == 32 and (shift == 0 or shift == 2));
373 return Offset{
374 .Register = .{
375 .rm = rm.id(),
376 .shift = .{
377 .Sxtw = shift,
378 },
379 },
380 };
381 }
382
383 pub fn reg_sxtx(rm: Register, shift: u2) Offset {
384 assert(rm.size() == 64 and (shift == 0 or shift == 3));
385 return Offset{
386 .Register = .{
387 .rm = rm.id(),
388 .shift = .{
389 .Sxtx = shift,
390 },
391 },
392 };
393 }
394 };
395
396 pub const RegisterShift = struct {293 pub const RegisterShift = struct {
397 rn: u5,294 rn: u5,
398 imm6: u6,295 imm6: u6,
...@@ -500,7 +397,124 @@ pub const Instruction = union(enum) {...@@ -500,7 +397,124 @@ pub const Instruction = union(enum) {
500 };397 };
501 }398 }
502399
503 fn loadStoreRegister(rt: Register, rn: Register, offset: Offset, load: bool) Instruction {400 /// Represents the offset operand of a load or store instruction.
401 /// Data can be loaded from memory with either an immediate offset
402 /// or an offset that is stored in some register.
403 pub const LoadStoreOffset = union(enum) {
404 Immediate: union(enum) {
405 PostIndex: i9,
406 PreIndex: i9,
407 Unsigned: u12,
408 },
409 Register: struct {
410 rm: u5,
411 shift: union(enum) {
412 Uxtw: u2,
413 Lsl: u2,
414 Sxtw: u2,
415 Sxtx: u2,
416 },
417 },
418
419 pub const none = LoadStoreOffset{
420 .Immediate = .{ .Unsigned = 0 },
421 };
422
423 pub fn toU12(self: LoadStoreOffset) u12 {
424 return switch (self) {
425 .Immediate => |imm_type| switch (imm_type) {
426 .PostIndex => |v| (@intCast(u12, @bitCast(u9, v)) << 2) + 1,
427 .PreIndex => |v| (@intCast(u12, @bitCast(u9, v)) << 2) + 3,
428 .Unsigned => |v| v,
429 },
430 .Register => |r| switch (r.shift) {
431 .Uxtw => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 16 + 2050,
432 .Lsl => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 24 + 2050,
433 .Sxtw => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 48 + 2050,
434 .Sxtx => |v| (@intCast(u12, r.rm) << 6) + (@intCast(u12, v) << 2) + 56 + 2050,
435 },
436 };
437 }
438
439 pub fn imm(offset: u12) LoadStoreOffset {
440 return .{
441 .Immediate = .{ .Unsigned = offset },
442 };
443 }
444
445 pub fn imm_post_index(offset: i9) LoadStoreOffset {
446 return .{
447 .Immediate = .{ .PostIndex = offset },
448 };
449 }
450
451 pub fn imm_pre_index(offset: i9) LoadStoreOffset {
452 return .{
453 .Immediate = .{ .PreIndex = offset },
454 };
455 }
456
457 pub fn reg(rm: Register) LoadStoreOffset {
458 return .{
459 .Register = .{
460 .rm = rm.id(),
461 .shift = .{
462 .Lsl = 0,
463 },
464 },
465 };
466 }
467
468 pub fn reg_uxtw(rm: Register, shift: u2) LoadStoreOffset {
469 assert(rm.size() == 32 and (shift == 0 or shift == 2));
470 return .{
471 .Register = .{
472 .rm = rm.id(),
473 .shift = .{
474 .Uxtw = shift,
475 },
476 },
477 };
478 }
479
480 pub fn reg_lsl(rm: Register, shift: u2) LoadStoreOffset {
481 assert(rm.size() == 64 and (shift == 0 or shift == 3));
482 return .{
483 .Register = .{
484 .rm = rm.id(),
485 .shift = .{
486 .Lsl = shift,
487 },
488 },
489 };
490 }
491
492 pub fn reg_sxtw(rm: Register, shift: u2) LoadStoreOffset {
493 assert(rm.size() == 32 and (shift == 0 or shift == 2));
494 return .{
495 .Register = .{
496 .rm = rm.id(),
497 .shift = .{
498 .Sxtw = shift,
499 },
500 },
501 };
502 }
503
504 pub fn reg_sxtx(rm: Register, shift: u2) LoadStoreOffset {
505 assert(rm.size() == 64 and (shift == 0 or shift == 3));
506 return .{
507 .Register = .{
508 .rm = rm.id(),
509 .shift = .{
510 .Sxtx = shift,
511 },
512 },
513 };
514 }
515 };
516
517 fn loadStoreRegister(rt: Register, rn: Register, offset: LoadStoreOffset, load: bool) Instruction {
504 const off = offset.toU12();518 const off = offset.toU12();
505 const op1: u2 = blk: {519 const op1: u2 = blk: {
506 switch (offset) {520 switch (offset) {
...@@ -542,6 +556,49 @@ pub const Instruction = union(enum) {...@@ -542,6 +556,49 @@ pub const Instruction = union(enum) {
542 }556 }
543 }557 }
544558
559 fn loadStorePairOfRegisters(
560 rt1: Register,
561 rt2: Register,
562 rn: Register,
563 offset: i9,
564 encoding: u2,
565 load: bool,
566 ) Instruction {
567 switch (rt1.size()) {
568 32 => {
569 assert(-256 <= offset and offset <= 252);
570 const imm7 = @truncate(u7, @bitCast(u9, offset >> 2));
571 return Instruction{
572 .LoadStorePairOfRegisters = .{
573 .rt1 = rt1.id(),
574 .rn = rn.id(),
575 .rt2 = rt2.id(),
576 .imm7 = imm7,
577 .load = @boolToInt(load),
578 .encoding = encoding,
579 .opc = 0b00,
580 },
581 };
582 },
583 64 => {
584 assert(-512 <= offset and offset <= 504);
585 const imm7 = @truncate(u7, @bitCast(u9, offset >> 3));
586 return Instruction{
587 .LoadStorePairOfRegisters = .{
588 .rt1 = rt1.id(),
589 .rn = rn.id(),
590 .rt2 = rt2.id(),
591 .imm7 = imm7,
592 .load = @boolToInt(load),
593 .encoding = encoding,
594 .opc = 0b10,
595 },
596 };
597 },
598 else => unreachable, // unexpected register size
599 }
600 }
601
545 fn loadLiteral(rt: Register, imm19: u19) Instruction {602 fn loadLiteral(rt: Register, imm19: u19) Instruction {
546 switch (rt.size()) {603 switch (rt.size()) {
547 32 => {604 32 => {
...@@ -652,9 +709,10 @@ pub const Instruction = union(enum) {...@@ -652,9 +709,10 @@ pub const Instruction = union(enum) {
652709
653 pub const LdrArgs = struct {710 pub const LdrArgs = struct {
654 rn: ?Register = null,711 rn: ?Register = null,
655 offset: Offset = Offset.none,712 offset: LoadStoreOffset = LoadStoreOffset.none,
656 literal: ?u19 = null,713 literal: ?u19 = null,
657 };714 };
715
658 pub fn ldr(rt: Register, args: LdrArgs) Instruction {716 pub fn ldr(rt: Register, args: LdrArgs) Instruction {
659 if (args.rn) |rn| {717 if (args.rn) |rn| {
660 return loadStoreRegister(rt, rn, args.offset, true);718 return loadStoreRegister(rt, rn, args.offset, true);
...@@ -664,12 +722,56 @@ pub const Instruction = union(enum) {...@@ -664,12 +722,56 @@ pub const Instruction = union(enum) {
664 }722 }
665723
666 pub const StrArgs = struct {724 pub const StrArgs = struct {
667 offset: Offset = Offset.none,725 offset: LoadStoreOffset = LoadStoreOffset.none,
668 };726 };
727
669 pub fn str(rt: Register, rn: Register, args: StrArgs) Instruction {728 pub fn str(rt: Register, rn: Register, args: StrArgs) Instruction {
670 return loadStoreRegister(rt, rn, args.offset, false);729 return loadStoreRegister(rt, rn, args.offset, false);
671 }730 }
672731
732 // Load or store pair of registers
733
734 pub const LoadStorePairOffset = struct {
735 encoding: enum(u2) {
736 PostIndex = 0b01,
737 Signed = 0b10,
738 PreIndex = 0b11,
739 },
740 offset: i9,
741
742 pub fn none() LoadStorePairOffset {
743 return .{ .encoding = .Signed, .offset = 0 };
744 }
745
746 pub fn post_index(imm: i9) LoadStorePairOffset {
747 return .{ .encoding = .PostIndex, .offset = imm };
748 }
749
750 pub fn pre_index(imm: i9) LoadStorePairOffset {
751 return .{ .encoding = .PreIndex, .offset = imm };
752 }
753
754 pub fn signed(imm: i9) LoadStorePairOffset {
755 return .{ .encoding = .Signed, .offset = imm };
756 }
757 };
758
759 pub fn ldp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction {
760 return loadStorePairOfRegisters(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), true);
761 }
762
763 pub fn ldnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction {
764 return loadStorePairOfRegisters(rt1, rt2, rn, offset, 0, true);
765 }
766
767 pub fn stp(rt1: Register, rt2: Register, rn: Register, offset: LoadStorePairOffset) Instruction {
768 return loadStorePairOfRegisters(rt1, rt2, rn, offset.offset, @enumToInt(offset.encoding), false);
769 }
770
771 pub fn stnp(rt1: Register, rt2: Register, rn: Register, offset: i9) Instruction {
772 return loadStorePairOfRegisters(rt1, rt2, rn, offset, 0, false);
773 }
774
673 // Exception generation775 // Exception generation
674776
675 pub fn svc(imm16: u16) Instruction {777 pub fn svc(imm16: u16) Instruction {
...@@ -787,15 +889,15 @@ test "serialize instructions" {...@@ -787,15 +889,15 @@ test "serialize instructions" {
787 .expected = 0b11_111_0_01_01_000000000000_00001_00010,889 .expected = 0b11_111_0_01_01_000000000000_00001_00010,
788 },890 },
789 .{ // ldr x2, [x1, #1]!891 .{ // ldr x2, [x1, #1]!
790 .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.imm_pre_index(1) }),892 .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.imm_pre_index(1) }),
791 .expected = 0b11_111_0_00_01_0_000000001_11_00001_00010,893 .expected = 0b11_111_0_00_01_0_000000001_11_00001_00010,
792 },894 },
793 .{ // ldr x2, [x1], #-1895 .{ // ldr x2, [x1], #-1
794 .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.imm_post_index(-1) }),896 .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.imm_post_index(-1) }),
795 .expected = 0b11_111_0_00_01_0_111111111_01_00001_00010,897 .expected = 0b11_111_0_00_01_0_111111111_01_00001_00010,
796 },898 },
797 .{ // ldr x2, [x1], (x3)899 .{ // ldr x2, [x1], (x3)
798 .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.Offset.reg(.x3) }),900 .inst = Instruction.ldr(.x2, .{ .rn = .x1, .offset = Instruction.LoadStoreOffset.reg(.x3) }),
799 .expected = 0b11_111_0_00_01_1_00011_011_0_10_00001_00010,901 .expected = 0b11_111_0_00_01_1_00011_011_0_10_00001_00010,
800 },902 },
801 .{ // ldr x2, label903 .{ // ldr x2, label
...@@ -807,7 +909,7 @@ test "serialize instructions" {...@@ -807,7 +909,7 @@ test "serialize instructions" {
807 .expected = 0b11_111_0_01_00_000000000000_00001_00010,909 .expected = 0b11_111_0_01_00_000000000000_00001_00010,
808 },910 },
809 .{ // str x2, [x1], (x3)911 .{ // str x2, [x1], (x3)
810 .inst = Instruction.str(.x2, .x1, .{ .offset = Instruction.Offset.reg(.x3) }),912 .inst = Instruction.str(.x2, .x1, .{ .offset = Instruction.LoadStoreOffset.reg(.x3) }),
811 .expected = 0b11_111_0_00_00_1_00011_011_0_10_00001_00010,913 .expected = 0b11_111_0_00_00_1_00011_011_0_10_00001_00010,
812 },914 },
813 .{ // adr x2, #0x8915 .{ // adr x2, #0x8
...@@ -826,6 +928,22 @@ test "serialize instructions" {...@@ -826,6 +928,22 @@ test "serialize instructions" {
826 .inst = Instruction.adrp(.x2, -0x8),928 .inst = Instruction.adrp(.x2, -0x8),
827 .expected = 0b1_00_10000_1111111111111111110_00010,929 .expected = 0b1_00_10000_1111111111111111110_00010,
828 },930 },
931 .{ // stp x1, x2, [sp, #8]
932 .inst = Instruction.stp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.signed(8)),
933 .expected = 0b10_101_0_010_0_0000001_00010_11111_00001,
934 },
935 .{ // ldp x1, x2, [sp, #8]
936 .inst = Instruction.ldp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.signed(8)),
937 .expected = 0b10_101_0_010_1_0000001_00010_11111_00001,
938 },
939 .{ // stp x1, x2, [sp, #-16]!
940 .inst = Instruction.stp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.pre_index(-16)),
941 .expected = 0b10_101_0_011_0_1111110_00010_11111_00001,
942 },
943 .{ // ldp x1, x2, [sp], #16
944 .inst = Instruction.ldp(.x1, .x2, Register.sp, Instruction.LoadStorePairOffset.post_index(16)),
945 .expected = 0b10_101_0_001_1_0000010_00010_11111_00001,
946 },
829 };947 };
830948
831 for (testcases) |case| {949 for (testcases) |case| {