Commit a1486e1e1e

Andrew Kelley <andrew@ziglang.org>
2022-10-16 18:29:03
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.
1 parent 1e96305
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,
             } },
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;
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) {
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`.