Commit 4630e3891c

Andrew Kelley <andrew@ziglang.org>
2021-04-20 03:44:59
AstGen: implement inline asm output
1 parent a136c09
src/codegen/c.zig
@@ -1036,11 +1036,11 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
     }
     const volatile_string: []const u8 = if (as.is_volatile) "volatile " else "";
     try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source });
-    if (as.output) |_| {
-        return o.dg.fail(.{ .node_offset = 0 }, "TODO inline asm output", .{});
+    if (as.output_constraint) |_| {
+        return o.dg.fail(.{ .node_offset = 0 }, "TODO: CBE inline asm output", .{});
     }
     if (as.inputs.len > 0) {
-        if (as.output == null) {
+        if (as.output_constraint == null) {
             try writer.writeAll(" :");
         }
         try writer.writeAll(": ");
src/AstGen.zig
@@ -4875,39 +4875,53 @@ fn asmExpr(
     const main_tokens = tree.nodes.items(.main_token);
     const node_datas = tree.nodes.items(.data);
 
-    const asm_source = try expr(gz, scope, .{ .ty = .const_slice_u8_type }, full.ast.template);
-
-    if (full.outputs.len != 0) {
-        // when implementing this be sure to add test coverage for the asm return type
-        // not resolving into a type (the node_offset_asm_ret_ty  field of LazySrcLoc)
-        return astgen.failTok(full.ast.asm_token, "TODO implement asm with an output", .{});
+    const asm_source = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, full.ast.template);
+
+    // See https://github.com/ziglang/zig/issues/215 and related issues discussing
+    // possible inline assembly improvements. Until this is settled, I am avoiding
+    // potentially wasting time implementing status quo assembly that is not used by
+    // any of the standard library.
+    if (full.outputs.len > 1) {
+        return astgen.failNode(node, "TODO more than 1 asm output", .{});
     }
+    const output: struct {
+        ty: Zir.Inst.Ref = .none,
+        constraint: u32 = 0,
+    } = if (full.outputs.len == 0) .{} else blk: {
+        const output_node = full.outputs[0];
+        const out_type_node = node_datas[output_node].lhs;
+        if (out_type_node == 0) {
+            return astgen.failNode(out_type_node, "TODO asm with non -> output", .{});
+        }
+        const constraint_token = main_tokens[output_node] + 2;
+        break :blk .{
+            .ty = try typeExpr(gz, scope, out_type_node),
+            .constraint = (try gz.strLitAsString(constraint_token)).index,
+        };
+    };
 
     const constraints = try arena.alloc(u32, full.inputs.len);
     const args = try arena.alloc(Zir.Inst.Ref, full.inputs.len);
 
     for (full.inputs) |input, i| {
         const constraint_token = main_tokens[input] + 2;
-        const string_bytes = &astgen.string_bytes;
-        constraints[i] = @intCast(u32, string_bytes.items.len);
-        const token_bytes = tree.tokenSlice(constraint_token);
-        try astgen.parseStrLit(constraint_token, string_bytes, token_bytes, 0);
-        try string_bytes.append(astgen.gpa, 0);
-
+        constraints[i] = (try gz.strLitAsString(constraint_token)).index;
         args[i] = try expr(gz, scope, .{ .ty = .usize_type }, node_datas[input].lhs);
     }
 
     const tag: Zir.Inst.Tag = if (full.volatile_token != null) .asm_volatile else .@"asm";
     const result = try gz.addPlNode(tag, node, Zir.Inst.Asm{
         .asm_source = asm_source,
-        .return_type = .void_type,
-        .output = .none,
+        .output_type = output.ty,
         .args_len = @intCast(u32, full.inputs.len),
         .clobbers_len = 0, // TODO implement asm clobbers
     });
 
     try astgen.extra.ensureCapacity(astgen.gpa, astgen.extra.items.len +
-        args.len + constraints.len);
+        args.len + constraints.len + @boolToInt(output.ty != .none));
+    if (output.ty != .none) {
+        astgen.extra.appendAssumeCapacity(output.constraint);
+    }
     astgen.appendRefsAssumeCapacity(args);
     astgen.extra.appendSliceAssumeCapacity(constraints);
 
src/codegen.zig
@@ -2754,7 +2754,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         return self.fail(inst.base.src, "TODO implement support for more arm assembly instructions", .{});
                     }
 
-                    if (inst.output_name) |output| {
+                    if (inst.output_constraint) |output| {
                         if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
                             return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
                         }
@@ -2789,7 +2789,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         return self.fail(inst.base.src, "TODO implement support for more aarch64 assembly instructions", .{});
                     }
 
-                    if (inst.output_name) |output| {
+                    if (inst.output_constraint) |output| {
                         if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
                             return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
                         }
@@ -2822,7 +2822,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         return self.fail(inst.base.src, "TODO implement support for more riscv64 assembly instructions", .{});
                     }
 
-                    if (inst.output_name) |output| {
+                    if (inst.output_constraint) |output| {
                         if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
                             return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
                         }
@@ -2855,7 +2855,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         return self.fail(inst.base.src, "TODO implement support for more x86 assembly instructions", .{});
                     }
 
-                    if (inst.output_name) |output| {
+                    if (inst.output_constraint) |output| {
                         if (output.len < 4 or output[0] != '=' or output[1] != '{' or output[output.len - 1] != '}') {
                             return self.fail(inst.base.src, "unrecognized asm output constraint: '{s}'", .{output});
                         }
src/ir.zig
@@ -372,8 +372,7 @@ pub const Inst = struct {
         base: Inst,
         asm_source: []const u8,
         is_volatile: bool,
-        output: ?*Inst,
-        output_name: ?[]const u8,
+        output_constraint: ?[]const u8,
         inputs: []const []const u8,
         clobbers: []const []const u8,
         args: []const *Inst,
src/main.zig
@@ -3561,26 +3561,41 @@ pub fn cmdAstgen(
     defer file.zir.deinit(gpa);
 
     {
+        const token_bytes = @sizeOf(std.zig.ast.TokenList) +
+            file.tree.tokens.len * (@sizeOf(std.zig.Token.Tag) + @sizeOf(std.zig.ast.ByteOffset));
+        const tree_bytes = @sizeOf(std.zig.ast.Tree) + file.tree.nodes.len *
+            (@sizeOf(std.zig.ast.Node.Tag) +
+            @sizeOf(std.zig.ast.Node.Data) +
+            @sizeOf(std.zig.ast.TokenIndex));
         const instruction_bytes = file.zir.instructions.len *
-            (@sizeOf(Zir.Inst.Tag) + @sizeOf(Zir.Inst.Data));
+            // Here we don't use @sizeOf(Zir.Inst.Data) because it would include
+            // the debug safety tag but we want to measure release size.
+            (@sizeOf(Zir.Inst.Tag) + 8);
         const extra_bytes = file.zir.extra.len * @sizeOf(u32);
         const total_bytes = @sizeOf(Zir) + instruction_bytes + extra_bytes +
             file.zir.string_bytes.len * @sizeOf(u8);
         const stdout = io.getStdOut();
+        const fmtIntSizeBin = std.fmt.fmtIntSizeBin;
+        // zig fmt: off
         try stdout.writer().print(
-            \\# Total bytes:        {}
+            \\# Source bytes:       {}
+            \\# Tokens:             {} ({})
+            \\# AST Nodes:          {} ({})
+            \\# Total ZIR bytes:    {}
             \\# Instructions:       {d} ({})
             \\# String Table Bytes: {}
             \\# Extra Data Items:   {d} ({})
             \\
         , .{
-            std.fmt.fmtIntSizeBin(total_bytes),
-            file.zir.instructions.len,
-            std.fmt.fmtIntSizeBin(instruction_bytes),
-            std.fmt.fmtIntSizeBin(file.zir.string_bytes.len),
-            file.zir.extra.len,
-            std.fmt.fmtIntSizeBin(extra_bytes),
+            fmtIntSizeBin(source.len),
+            file.tree.tokens.len, fmtIntSizeBin(token_bytes),
+            file.tree.nodes.len, fmtIntSizeBin(tree_bytes),
+            fmtIntSizeBin(total_bytes),
+            file.zir.instructions.len, fmtIntSizeBin(instruction_bytes),
+            fmtIntSizeBin(file.zir.string_bytes.len),
+            file.zir.extra.len, fmtIntSizeBin(extra_bytes),
         });
+        // zig fmt: on
     }
 
     if (file.zir.hasCompileErrors()) {
src/Sema.zig
@@ -4337,17 +4337,16 @@ fn zirAsm(
     const asm_source_src: LazySrcLoc = .{ .node_offset_asm_source = inst_data.src_node };
     const ret_ty_src: LazySrcLoc = .{ .node_offset_asm_ret_ty = inst_data.src_node };
     const extra = sema.code.extraData(Zir.Inst.Asm, inst_data.payload_index);
-    const return_type = try sema.resolveType(block, ret_ty_src, extra.data.return_type);
     const asm_source = try sema.resolveConstString(block, asm_source_src, extra.data.asm_source);
 
     var extra_i = extra.end;
-    const Output = struct { name: []const u8, inst: *Inst };
-    const output: ?Output = if (extra.data.output != .none) blk: {
-        const name = sema.code.nullTerminatedString(sema.code.extra[extra_i]);
+    const Output = struct { constraint: []const u8, ty: Type };
+    const output: ?Output = if (extra.data.output_type != .none) blk: {
+        const constraint = sema.code.nullTerminatedString(sema.code.extra[extra_i]);
         extra_i += 1;
         break :blk Output{
-            .name = name,
-            .inst = try sema.resolveInst(extra.data.output),
+            .constraint = constraint,
+            .ty = try sema.resolveType(block, ret_ty_src, extra.data.output_type),
         };
     } else null;
 
@@ -4369,23 +4368,22 @@ fn zirAsm(
     }
 
     try sema.requireRuntimeBlock(block, src);
-    const asm_tzir = try sema.arena.create(Inst.Assembly);
-    asm_tzir.* = .{
+    const asm_air = try sema.arena.create(Inst.Assembly);
+    asm_air.* = .{
         .base = .{
             .tag = .assembly,
-            .ty = return_type,
+            .ty = if (output) |o| o.ty else Type.initTag(.void),
             .src = src,
         },
         .asm_source = asm_source,
         .is_volatile = is_volatile,
-        .output = if (output) |o| o.inst else null,
-        .output_name = if (output) |o| o.name else null,
+        .output_constraint = if (output) |o| o.constraint else null,
         .inputs = inputs,
         .clobbers = clobbers,
         .args = args,
     };
-    try block.instructions.append(sema.gpa, &asm_tzir.base);
-    return &asm_tzir.base;
+    try block.instructions.append(sema.gpa, &asm_air.base);
+    return &asm_air.base;
 }
 
 fn zirCmp(
src/Zir.zig
@@ -1760,15 +1760,14 @@ pub const Inst = struct {
     };
 
     /// Stored in extra. Trailing is:
-    /// * output_name: u32 // index into string_bytes (null terminated) if output is present
+    /// * output_constraint: u32 // index into string_bytes (null terminated) if output is present
     /// * arg: Ref // for every args_len.
     /// * constraint: u32 // index into string_bytes (null terminated) for every args_len.
     /// * clobber: u32 // index into string_bytes (null terminated) for every clobbers_len.
     pub const Asm = struct {
         asm_source: Ref,
-        return_type: Ref,
         /// May be omitted.
-        output: Ref,
+        output_type: Ref,
         args_len: u32,
         clobbers_len: u32,
     };
@@ -2308,8 +2307,6 @@ const Writer = struct {
             .break_inline,
             => try self.writeBreak(stream, inst),
 
-            .@"asm",
-            .asm_volatile,
             .elem_ptr_node,
             .elem_val_node,
             .field_ptr_named,
@@ -2337,6 +2334,10 @@ const Writer = struct {
             .builtin_async_call,
             => try self.writePlNode(stream, inst),
 
+            .@"asm",
+            .asm_volatile,
+            => try self.writePlNodeAsm(stream, inst),
+
             .error_set_decl => try self.writePlNodeErrorSetDecl(stream, inst),
 
             .add_with_overflow,
@@ -2642,6 +2643,51 @@ const Writer = struct {
         try self.writeSrc(stream, inst_data.src());
     }
 
+    fn writePlNodeAsm(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+        const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+        const extra = self.code.extraData(Inst.Asm, inst_data.payload_index);
+        var extra_i: usize = extra.end;
+
+        if (extra.data.output_type != .none) {
+            const constraint_str_index = self.code.extra[extra_i];
+            extra_i += 1;
+            const constraint = self.code.nullTerminatedString(constraint_str_index);
+            try stream.print("\"{}\"->", .{std.zig.fmtEscapes(constraint)});
+            try self.writeInstRef(stream, extra.data.output_type);
+            try stream.writeAll(", ");
+        }
+        {
+            var i: usize = 0;
+            while (i < extra.data.args_len) : (i += 1) {
+                const arg = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_i]);
+                extra_i += 1;
+                try self.writeInstRef(stream, arg);
+                try stream.writeAll(", ");
+            }
+        }
+        {
+            var i: usize = 0;
+            while (i < extra.data.args_len) : (i += 1) {
+                const str_index = self.code.extra[extra_i];
+                extra_i += 1;
+                const constraint = self.code.nullTerminatedString(str_index);
+                try stream.print("\"{}\", ", .{std.zig.fmtEscapes(constraint)});
+            }
+        }
+        {
+            var i: usize = 0;
+            while (i < extra.data.clobbers_len) : (i += 1) {
+                const str_index = self.code.extra[extra_i];
+                extra_i += 1;
+                const clobber = self.code.nullTerminatedString(str_index);
+                try stream.print("{}, ", .{std.zig.fmtId(clobber)});
+            }
+        }
+        try self.writeInstRef(stream, extra.data.asm_source);
+        try stream.writeAll(") ");
+        try self.writeSrc(stream, inst_data.src());
+    }
+
     fn writePlNodeOverflowArithmetic(self: *Writer, stream: anytype, inst: Inst.Index) !void {
         const inst_data = self.code.instructions.items(.data)[inst].pl_node;
         const extra = self.code.extraData(Inst.OverflowArithmetic, inst_data.payload_index).data;
BRANCH_TODO
@@ -711,3 +711,8 @@ fn astgenAndSemaVarDecl(
     const decl_index = try mod.declareDeclDependency(astgen.decl, new_decl);
     const result = try gz.addDecl(.decl_val, decl_index, node);
     return rvalue(gz, scope, rl, result, node);
+
+
+
+        // when implementing this be sure to add test coverage for the asm return type
+        // not resolving into a type (the node_offset_asm_ret_ty  field of LazySrcLoc)