authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-02 20:19:31-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-02 22:14:17-07:00
log65389dc280b97365605bc3f7f4038c1972534b9a
tree37ac5852f019fe10e68f4853a7db2d83a46eafc9
parentb95942744c7ded279f5695ed20fdbbc806323cba

stage2: improve inline asm stage1 compatibility

* outputs can have names and be referenced with template replacements the same as inputs. * fix print_air.zig not decoding correctly. * LLVM backend: use a table for template names for simplicity

10 files changed, 82 insertions(+), 53 deletions(-)

src/Air.zig+2
......@@ -815,6 +815,8 @@ pub const VectorCmp = struct {
815815/// 1. `Inst.Ref` for every inputs_len
816816/// 2. for every outputs_len
817817/// - constraint: memory at this position is reinterpreted as a null
818/// terminated string.
819/// - name: memory at this position is reinterpreted as a null
818820/// terminated string. pad to the next u32 after the null byte.
819821/// 3. for every inputs_len
820822/// - constraint: memory at this position is reinterpreted as a null
src/Sema.zig+13-5
......@@ -10535,7 +10535,11 @@ fn zirAsm(
1053510535 var output_type_bits = extra.data.output_type_bits;
1053610536 var needed_capacity: usize = @typeInfo(Air.Asm).Struct.fields.len + outputs_len + inputs_len;
1053710537
10538 const Output = struct { constraint: []const u8, ty: Type };
10538 const Output = struct {
10539 constraint: []const u8,
10540 name: []const u8,
10541 ty: Type,
10542 };
1053910543 const output: ?Output = if (outputs_len == 0) null else blk: {
1054010544 const output = sema.code.extraData(Zir.Inst.Asm.Output, extra_i);
1054110545 extra_i = output.end;
......@@ -10548,10 +10552,12 @@ fn zirAsm(
1054810552 }
1054910553
1055010554 const constraint = sema.code.nullTerminatedString(output.data.constraint);
10551 needed_capacity += constraint.len / 4 + 1;
10555 const name = sema.code.nullTerminatedString(output.data.name);
10556 needed_capacity += (constraint.len + name.len + (2 + 3)) / 4;
1055210557
1055310558 break :blk Output{
1055410559 .constraint = constraint,
10560 .name = name,
1055510561 .ty = try sema.resolveType(block, ret_ty_src, output.data.operand),
1055610562 };
1055710563 };
......@@ -10573,7 +10579,7 @@ fn zirAsm(
1057310579
1057410580 const constraint = sema.code.nullTerminatedString(input.data.constraint);
1057510581 const name = sema.code.nullTerminatedString(input.data.name);
10576 needed_capacity += (constraint.len + name.len + 1) / 4 + 1;
10582 needed_capacity += (constraint.len + name.len + (2 + 3)) / 4;
1057710583 inputs[arg_i] = .{ .c = constraint, .n = name };
1057810584 }
1057910585
......@@ -10611,7 +10617,9 @@ fn zirAsm(
1061110617 const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice());
1061210618 mem.copy(u8, buffer, o.constraint);
1061310619 buffer[o.constraint.len] = 0;
10614 sema.air_extra.items.len += o.constraint.len / 4 + 1;
10620 mem.copy(u8, buffer[o.constraint.len + 1 ..], o.name);
10621 buffer[o.constraint.len + 1 + o.name.len] = 0;
10622 sema.air_extra.items.len += (o.constraint.len + o.name.len + (2 + 3)) / 4;
1061510623 }
1061610624 for (inputs) |input| {
1061710625 const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice());
......@@ -10619,7 +10627,7 @@ fn zirAsm(
1061910627 buffer[input.c.len] = 0;
1062010628 mem.copy(u8, buffer[input.c.len + 1 ..], input.n);
1062110629 buffer[input.c.len + 1 + input.n.len] = 0;
10622 sema.air_extra.items.len += (input.c.len + input.n.len + 1) / 4 + 1;
10630 sema.air_extra.items.len += (input.c.len + input.n.len + (2 + 3)) / 4;
1062310631 }
1062410632 for (clobbers) |clobber| {
1062510633 const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice());
src/arch/aarch64/CodeGen.zig+5-3
......@@ -3272,10 +3272,12 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
32723272 if (output != .none) {
32733273 return self.fail("TODO implement codegen for non-expr asm", .{});
32743274 }
3275 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
32753276 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);
3277 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
32763278 // This equation accounts for the fact that even if we have exactly 4 bytes
32773279 // for the string, we still use the next u32 for the null terminator.
3278 extra_i += constraint.len / 4 + 1;
3280 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
32793281
32803282 break constraint;
32813283 } else null;
......@@ -3283,10 +3285,10 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
32833285 for (inputs) |input| {
32843286 const input_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
32853287 const constraint = std.mem.sliceTo(input_bytes, 0);
3286 const input_name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
3288 const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
32873289 // This equation accounts for the fact that even if we have exactly 4 bytes
32883290 // for the string, we still use the next u32 for the null terminator.
3289 extra_i += (constraint.len + input_name.len + 1) / 4 + 1;
3291 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
32903292
32913293 if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') {
32923294 return self.fail("unrecognized asm input constraint: '{s}'", .{constraint});
src/arch/arm/CodeGen.zig+5-3
......@@ -4078,10 +4078,12 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
40784078 if (output != .none) {
40794079 return self.fail("TODO implement codegen for non-expr asm", .{});
40804080 }
4081 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
40814082 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);
4083 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
40824084 // This equation accounts for the fact that even if we have exactly 4 bytes
40834085 // for the string, we still use the next u32 for the null terminator.
4084 extra_i += constraint.len / 4 + 1;
4086 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
40854087
40864088 break constraint;
40874089 } else null;
......@@ -4089,10 +4091,10 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
40894091 for (inputs) |input| {
40904092 const input_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
40914093 const constraint = std.mem.sliceTo(input_bytes, 0);
4092 const input_name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
4094 const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
40934095 // This equation accounts for the fact that even if we have exactly 4 bytes
40944096 // for the string, we still use the next u32 for the null terminator.
4095 extra_i += (constraint.len + input_name.len + 1) / 4 + 1;
4097 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
40964098
40974099 if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') {
40984100 return self.fail("unrecognized asm input constraint: '{s}'", .{constraint});
src/arch/riscv64/CodeGen.zig+5-3
......@@ -2098,10 +2098,12 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
20982098 if (output != .none) {
20992099 return self.fail("TODO implement codegen for non-expr asm", .{});
21002100 }
2101 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
21012102 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);
2103 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
21022104 // This equation accounts for the fact that even if we have exactly 4 bytes
21032105 // for the string, we still use the next u32 for the null terminator.
2104 extra_i += constraint.len / 4 + 1;
2106 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
21052107
21062108 break constraint;
21072109 } else null;
......@@ -2109,10 +2111,10 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
21092111 for (inputs) |input| {
21102112 const input_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
21112113 const constraint = std.mem.sliceTo(input_bytes, 0);
2112 const input_name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
2114 const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
21132115 // This equation accounts for the fact that even if we have exactly 4 bytes
21142116 // for the string, we still use the next u32 for the null terminator.
2115 extra_i += (constraint.len + input_name.len + 1) / 4 + 1;
2117 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
21162118
21172119 if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') {
21182120 return self.fail("unrecognized asm input constraint: '{s}'", .{constraint});
src/arch/sparcv9/CodeGen.zig+5-3
......@@ -642,10 +642,12 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
642642 if (output != .none) {
643643 return self.fail("TODO implement codegen for non-expr asm", .{});
644644 }
645 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
645646 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);
647 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
646648 // This equation accounts for the fact that even if we have exactly 4 bytes
647649 // for the string, we still use the next u32 for the null terminator.
648 extra_i += constraint.len / 4 + 1;
650 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
649651
650652 break constraint;
651653 } else null;
......@@ -653,10 +655,10 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
653655 for (inputs) |input| {
654656 const input_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
655657 const constraint = std.mem.sliceTo(input_bytes, 0);
656 const input_name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
658 const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
657659 // This equation accounts for the fact that even if we have exactly 4 bytes
658660 // for the string, we still use the next u32 for the null terminator.
659 extra_i += (constraint.len + input_name.len + 1) / 4 + 1;
661 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
660662
661663 if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') {
662664 return self.fail("unrecognized asm input constraint: '{s}'", .{constraint});
src/arch/x86_64/CodeGen.zig+5-3
......@@ -4739,10 +4739,12 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
47394739 if (output != .none) {
47404740 return self.fail("TODO implement codegen for non-expr asm", .{});
47414741 }
4742 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
47424743 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);
4744 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
47434745 // This equation accounts for the fact that even if we have exactly 4 bytes
47444746 // for the string, we still use the next u32 for the null terminator.
4745 extra_i += constraint.len / 4 + 1;
4747 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
47464748
47474749 break constraint;
47484750 } else null;
......@@ -4750,10 +4752,10 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
47504752 for (inputs) |input| {
47514753 const input_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
47524754 const constraint = std.mem.sliceTo(input_bytes, 0);
4753 const input_name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
4755 const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
47544756 // This equation accounts for the fact that even if we have exactly 4 bytes
47554757 // for the string, we still use the next u32 for the null terminator.
4756 extra_i += (constraint.len + input_name.len + 1) / 4 + 1;
4758 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
47574759
47584760 if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') {
47594761 return self.fail("unrecognized asm input constraint: '{s}'", .{constraint});
src/codegen/c.zig+7-3
......@@ -3018,10 +3018,12 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
30183018 if (output != .none) {
30193019 return f.fail("TODO implement codegen for non-expr asm", .{});
30203020 }
3021 const extra_bytes = std.mem.sliceAsBytes(f.air.extra[extra_i..]);
30213022 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(f.air.extra[extra_i..]), 0);
3023 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
30223024 // This equation accounts for the fact that even if we have exactly 4 bytes
30233025 // for the string, we still use the next u32 for the null terminator.
3024 extra_i += constraint.len / 4 + 1;
3026 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
30253027
30263028 break constraint;
30273029 } else null;
......@@ -3031,10 +3033,12 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
30313033
30323034 const inputs_extra_begin = extra_i;
30333035 for (inputs) |input, i| {
3034 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(f.air.extra[extra_i..]), 0);
3036 const input_bytes = std.mem.sliceAsBytes(f.air.extra[extra_i..]);
3037 const constraint = std.mem.sliceTo(input_bytes, 0);
3038 const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
30353039 // This equation accounts for the fact that even if we have exactly 4 bytes
30363040 // for the string, we still use the next u32 for the null terminator.
3037 extra_i += constraint.len / 4 + 1;
3041 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
30383042
30393043 if (constraint[0] == '{' and constraint[constraint.len - 1] == '}') {
30403044 const reg = constraint[1 .. constraint.len - 1];
src/codegen/llvm.zig+17-20
......@@ -4638,14 +4638,19 @@ pub const FuncGen = struct {
46384638 var llvm_param_i: usize = 0;
46394639 var total_i: usize = 0;
46404640
4641 var name_map: std.StringArrayHashMapUnmanaged(void) = .{};
4642 try name_map.ensureUnusedCapacity(arena, outputs.len + inputs.len);
4643
46414644 for (outputs) |output| {
46424645 if (output != .none) {
46434646 return self.todo("implement inline asm with non-returned output", .{});
46444647 }
4648 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
46454649 const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);
4650 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
46464651 // This equation accounts for the fact that even if we have exactly 4 bytes
46474652 // for the string, we still use the next u32 for the null terminator.
4648 extra_i += constraint.len / 4 + 1;
4653 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
46494654
46504655 try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 1);
46514656 if (total_i != 0) {
......@@ -4654,17 +4659,17 @@ pub const FuncGen = struct {
46544659 llvm_constraints.appendAssumeCapacity('=');
46554660 llvm_constraints.appendSliceAssumeCapacity(constraint[1..]);
46564661
4662 name_map.putAssumeCapacityNoClobber(name, {});
46574663 total_i += 1;
46584664 }
46594665
4660 const input_start_extra_i = extra_i;
46614666 for (inputs) |input| {
4662 const input_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
4663 const constraint = std.mem.sliceTo(input_bytes, 0);
4664 const input_name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
4667 const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
4668 const constraint = std.mem.sliceTo(extra_bytes, 0);
4669 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
46654670 // This equation accounts for the fact that even if we have exactly 4 bytes
46664671 // for the string, we still use the next u32 for the null terminator.
4667 extra_i += (constraint.len + input_name.len + 1) / 4 + 1;
4672 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
46684673
46694674 const arg_llvm_value = try self.resolveInst(input);
46704675
......@@ -4677,6 +4682,7 @@ pub const FuncGen = struct {
46774682 }
46784683 llvm_constraints.appendSliceAssumeCapacity(constraint);
46794684
4685 name_map.putAssumeCapacityNoClobber(name, {});
46804686 llvm_param_i += 1;
46814687 total_i += 1;
46824688 }
......@@ -4739,20 +4745,11 @@ pub const FuncGen = struct {
47394745 const name = asm_source[name_start..i];
47404746 state = .start;
47414747
4742 extra_i = input_start_extra_i;
4743 for (inputs) |_, input_i| {
4744 const input_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
4745 const constraint = std.mem.sliceTo(input_bytes, 0);
4746 const input_name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0);
4747 extra_i += (constraint.len + input_name.len + 1) / 4 + 1;
4748
4749 if (std.mem.eql(u8, name, input_name)) {
4750 try rendered_template.writer().print("{d}", .{input_i});
4751 break;
4752 }
4753 } else {
4754 return self.todo("TODO validate asm in Sema", .{});
4755 }
4748 const index = name_map.getIndex(name) orelse {
4749 // we should validate the assembly in Sema; by now it is too late
4750 return self.todo("unknown input or output name: '{s}'", .{name});
4751 };
4752 try rendered_template.writer().print("{d}", .{index});
47564753 },
47574754 else => {},
47584755 },
src/print_air.zig+18-10
......@@ -542,15 +542,19 @@ const Writer = struct {
542542 extra_i += inputs.len;
543543
544544 for (outputs) |output| {
545 const constraint = w.air.nullTerminatedString(extra_i);
545 const extra_bytes = std.mem.sliceAsBytes(w.air.extra[extra_i..]);
546 const constraint = std.mem.sliceTo(extra_bytes, 0);
547 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
548
546549 // This equation accounts for the fact that even if we have exactly 4 bytes
547 // for the string, we still use the next u32 for the null terminator.
548 extra_i += constraint.len / 4 + 1;
550 // for the strings and their null terminators, we still use the next u32
551 // for the null terminator.
552 extra_i += (constraint.len + name.len + (2 + 3)) / 4;
549553
550554 if (output == .none) {
551 try s.print(", -> {s}", .{constraint});
555 try s.print(", [{s}] -> {s}", .{ name, constraint });
552556 } else {
553 try s.print(", out {s} = (", .{constraint});
557 try s.print(", [{s}] out {s} = (", .{ name, constraint });
554558 try w.writeOperand(s, inst, op_index, output);
555559 op_index += 1;
556560 try s.writeByte(')');
......@@ -558,12 +562,15 @@ const Writer = struct {
558562 }
559563
560564 for (inputs) |input| {
561 const constraint = w.air.nullTerminatedString(extra_i);
565 const extra_bytes = std.mem.sliceAsBytes(w.air.extra[extra_i..]);
566 const constraint = std.mem.sliceTo(extra_bytes, 0);
567 const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
562568 // This equation accounts for the fact that even if we have exactly 4 bytes
563 // for the string, we still use the next u32 for the null terminator.
564 extra_i += constraint.len / 4 + 1;
569 // for the strings and their null terminators, we still use the next u32
570 // for the null terminator.
571 extra_i += (constraint.len + name.len + 1) / 4 + 1;
565572
566 try s.print(", in {s} = (", .{constraint});
573 try s.print(", [{s}] in {s} = (", .{ name, constraint });
567574 try w.writeOperand(s, inst, op_index, input);
568575 op_index += 1;
569576 try s.writeByte(')');
......@@ -572,7 +579,8 @@ const Writer = struct {
572579 {
573580 var clobber_i: u32 = 0;
574581 while (clobber_i < clobbers_len) : (clobber_i += 1) {
575 const clobber = w.air.nullTerminatedString(extra_i);
582 const extra_bytes = std.mem.sliceAsBytes(w.air.extra[extra_i..]);
583 const clobber = std.mem.sliceTo(extra_bytes, 0);
576584 // This equation accounts for the fact that even if we have exactly 4 bytes
577585 // for the string, we still use the next u32 for the null terminator.
578586 extra_i += clobber.len / 4 + 1;