From a1486e1e1e52175d3e1b26049ea960b9c00edeb7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 16 Oct 2022 09:29:03 -0700 Subject: [PATCH] stage2: allow comptime expressions for inline asm It is not yet determined whether the Zig language will land on text-based string concatenation for inline assembly, as Zig 0.9.1 allows, and as this commit allows, or whether it will introduce a new assembly syntax more integrated with the rest of the language. Until this decision is made, this commit relaxes the restriction which was preventing inline assembly expressions from using comptime expressions for the assembly source code. --- src/AstGen.zig | 34 +++++++++++++++++----------------- src/Sema.zig | 16 ++++++++-------- src/Zir.zig | 5 +++++ src/print_zir.zig | 19 +++++++++++++++---- 4 files changed, 45 insertions(+), 29 deletions(-) diff --git a/src/AstGen.zig b/src/AstGen.zig index 7bb2ef765c..49c5b80967 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7194,21 +7194,19 @@ fn asmExpr( const node_tags = tree.nodes.items(.tag); const token_tags = tree.tokens.items(.tag); - const asm_source = switch (node_tags[full.ast.template]) { - .string_literal => try astgen.strLitAsString(main_tokens[full.ast.template]), - .multiline_string_literal => try astgen.strLitNodeAsString(full.ast.template), - else => blk: { - // stage1 allows this, and until we do another design iteration on inline assembly - // in stage2 to improve support for the various needed use cases, we allow inline - // assembly templates to be an expression. Once stage2 addresses the real world needs - // of people using inline assembly (primarily OS developers) then we can re-institute - // the rule into AstGen that assembly code must use string literal syntax. - //return astgen.failNode(full.ast.template, "assembly code must use string literal syntax", .{}), - // We still need to trigger all the expr() calls here to avoid errors for unused things. - // So we pass 0 as the asm source and stage2 Sema will notice this and - // report the error. - _ = try comptimeExpr(gz, scope, .none, full.ast.template); - break :blk IndexSlice{ .index = 0, .len = 0 }; + const TagAndTmpl = struct { tag: Zir.Inst.Extended, tmpl: u32 }; + const tag_and_tmpl: TagAndTmpl = switch (node_tags[full.ast.template]) { + .string_literal => .{ + .tag = .@"asm", + .tmpl = (try astgen.strLitAsString(main_tokens[full.ast.template])).index, + }, + .multiline_string_literal => .{ + .tag = .@"asm", + .tmpl = (try astgen.strLitNodeAsString(full.ast.template)).index, + }, + else => .{ + .tag = .asm_expr, + .tmpl = @enumToInt(try comptimeExpr(gz, scope, .none, full.ast.template)), }, }; @@ -7312,8 +7310,9 @@ fn asmExpr( } const result = try gz.addAsm(.{ + .tag = tag_and_tmpl.tag, .node = node, - .asm_source = asm_source.index, + .asm_source = tag_and_tmpl.tmpl, .is_volatile = full.volatile_token != null, .output_type_bits = output_type_bits, .outputs = outputs, @@ -11314,6 +11313,7 @@ const GenZir = struct { fn addAsm( gz: *GenZir, args: struct { + tag: Zir.Inst.Extended, /// Absolute node index. This function does the conversion to offset from Decl. node: Ast.Node.Index, asm_source: u32, @@ -11360,7 +11360,7 @@ const GenZir = struct { astgen.instructions.appendAssumeCapacity(.{ .tag = .extended, .data = .{ .extended = .{ - .opcode = .@"asm", + .opcode = args.tag, .small = small, .operand = payload_index, } }, diff --git a/src/Sema.zig b/src/Sema.zig index 08f3b59661..4807e68f48 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -953,7 +953,8 @@ fn analyzeBodyInner( .frame_address => try sema.zirFrameAddress( block, extended), .alloc => try sema.zirAllocExtended( block, extended), .builtin_extern => try sema.zirBuiltinExtern( block, extended), - .@"asm" => try sema.zirAsm( block, extended), + .@"asm" => try sema.zirAsm( block, extended, false), + .asm_expr => try sema.zirAsm( block, extended, true), .typeof_peer => try sema.zirTypeofPeer( block, extended), .compile_log => try sema.zirCompileLog( block, extended), .add_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode), @@ -13846,6 +13847,7 @@ fn zirAsm( sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, + tmpl_is_expr: bool, ) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -13859,13 +13861,11 @@ fn zirAsm( const is_volatile = @truncate(u1, extended.small >> 15) != 0; const is_global_assembly = sema.func == null; - if (extra.data.asm_source == 0) { - // This can move to become an AstGen error after inline assembly improvements land - // and stage1 code matches stage2 code. - return sema.fail(block, src, "assembly code must use string literal syntax", .{}); - } - - const asm_source = sema.code.nullTerminatedString(extra.data.asm_source); + const asm_source: []const u8 = if (tmpl_is_expr) blk: { + const tmpl = @intToEnum(Zir.Inst.Ref, extra.data.asm_source); + const s: []const u8 = try sema.resolveConstString(block, src, tmpl, "assembly code must be comptime-known"); + break :blk s; + } else sema.code.nullTerminatedString(extra.data.asm_source); if (is_global_assembly) { if (outputs_len != 0) { diff --git a/src/Zir.zig b/src/Zir.zig index 351330b7c4..01b52a8a7c 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -1883,6 +1883,11 @@ pub const Inst = struct { /// * 0bX0000000_00000000 - is volatile /// `operand` is payload index to `Asm`. @"asm", + /// Same as `asm` except the assembly template is not a string literal but a comptime + /// expression. + /// The `asm_source` field of the Asm is not a null-terminated string + /// but instead a Ref. + asm_expr, /// Log compile time variables and emit an error message. /// `operand` is payload index to `NodeMultiOp`. /// `small` is `operands_len`. diff --git a/src/print_zir.zig b/src/print_zir.zig index f2a79d53a4..14183d458c 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -469,7 +469,8 @@ const Writer = struct { try stream.print(":{d}:{d}", .{ inst_data.line + 1, inst_data.column + 1 }); }, - .@"asm" => try self.writeAsm(stream, extended), + .@"asm" => try self.writeAsm(stream, extended, false), + .asm_expr => try self.writeAsm(stream, extended, true), .variable => try self.writeVarExtended(stream, extended), .alloc => try self.writeAllocExtended(stream, extended), @@ -1062,17 +1063,27 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } - fn writeAsm(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void { + fn writeAsm( + self: *Writer, + stream: anytype, + extended: Zir.Inst.Extended.InstData, + tmpl_is_expr: bool, + ) !void { const extra = self.code.extraData(Zir.Inst.Asm, extended.operand); const src = LazySrcLoc.nodeOffset(extra.data.src_node); const outputs_len = @truncate(u5, extended.small); const inputs_len = @truncate(u5, extended.small >> 5); const clobbers_len = @truncate(u5, extended.small >> 10); const is_volatile = @truncate(u1, extended.small >> 15) != 0; - const asm_source = self.code.nullTerminatedString(extra.data.asm_source); try self.writeFlag(stream, "volatile, ", is_volatile); - try stream.print("\"{}\", ", .{std.zig.fmtEscapes(asm_source)}); + if (tmpl_is_expr) { + try self.writeInstRef(stream, @intToEnum(Zir.Inst.Ref, extra.data.asm_source)); + try stream.writeAll(", "); + } else { + const asm_source = self.code.nullTerminatedString(extra.data.asm_source); + try stream.print("\"{}\", ", .{std.zig.fmtEscapes(asm_source)}); + } try stream.writeAll(", "); var extra_i: usize = extra.end;