From 4630e3891c7c833d8a8f42e3755099b478dce3f3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 19 Apr 2021 18:44:59 -0700 Subject: [PATCH] AstGen: implement inline asm output --- BRANCH_TODO | 5 +++++ src/AstGen.zig | 42 +++++++++++++++++++++++------------ src/Sema.zig | 24 ++++++++++---------- src/Zir.zig | 56 ++++++++++++++++++++++++++++++++++++++++++----- src/codegen.zig | 8 +++---- src/codegen/c.zig | 6 ++--- src/ir.zig | 3 +-- src/main.zig | 31 +++++++++++++++++++------- 8 files changed, 126 insertions(+), 49 deletions(-) diff --git a/BRANCH_TODO b/BRANCH_TODO index d944ff9983..e1cddc607a 100644 --- a/BRANCH_TODO +++ b/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) diff --git a/src/AstGen.zig b/src/AstGen.zig index f9cfd7eeb1..dc989b70bd 100644 --- a/src/AstGen.zig +++ b/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); + const asm_source = try comptimeExpr(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", .{}); + // 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); diff --git a/src/Sema.zig b/src/Sema.zig index dea89ffef7..1d13261458 100644 --- a/src/Sema.zig +++ b/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( diff --git a/src/Zir.zig b/src/Zir.zig index 554bcf86c2..5e72e96799 100644 --- a/src/Zir.zig +++ b/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; diff --git a/src/codegen.zig b/src/codegen.zig index f77de4c87d..deb1e842ba 100644 --- a/src/codegen.zig +++ b/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}); } diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 0fdf5cb01b..441449707a 100644 --- a/src/codegen/c.zig +++ b/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(": "); diff --git a/src/ir.zig b/src/ir.zig index 85d4175c43..2026297026 100644 --- a/src/ir.zig +++ b/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, diff --git a/src/main.zig b/src/main.zig index d6c9e0b641..3edabca95f 100644 --- a/src/main.zig +++ b/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()) {