authorgravatar for pentuppup@noreply.codeberg.orgpentuppup <pentuppup@noreply.codeberg.org> 2025-10-15 00:33:12-04:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-12-06 09:42:51+01:00
log28c5cc390c909db06782eb2366d315c5da501a5f
treea3edbd02ec35d150cb6848c7783843182265a17e
parentdbb4c8d1514f351bbef4a6977a0b188a3c6b81dc

detect `comptime var` references in asm input/output and improve errors


6 files changed, 100 insertions(+), 45 deletions(-)

lib/std/zig/AstGen.zig+1-1
...@@ -13000,9 +13000,9 @@ const GenZir = struct {...@@ -13000,9 +13000,9 @@ const GenZir = struct {
13000 }13000 }
1300113001
13002 const small: Zir.Inst.Asm.Small = .{13002 const small: Zir.Inst.Asm.Small = .{
13003 .is_volatile = args.is_volatile,
13003 .outputs_len = @intCast(args.outputs.len),13004 .outputs_len = @intCast(args.outputs.len),
13004 .inputs_len = @intCast(args.inputs.len),13005 .inputs_len = @intCast(args.inputs.len),
13005 .is_volatile = args.is_volatile,
13006 };13006 };
1300713007
13008 const new_index: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);13008 const new_index: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
src/Sema.zig+25-9
...@@ -16388,6 +16388,7 @@ fn zirAsm(...@@ -16388,6 +16388,7 @@ fn zirAsm(
1638816388
16389 const pt = sema.pt;16389 const pt = sema.pt;
16390 const zcu = pt.zcu;16390 const zcu = pt.zcu;
16391 const ip = &zcu.intern_pool;
16391 const extra = sema.code.extraData(Zir.Inst.Asm, extended.operand);16392 const extra = sema.code.extraData(Zir.Inst.Asm, extended.operand);
16392 const src = block.nodeOffset(extra.data.src_node);16393 const src = block.nodeOffset(extra.data.src_node);
16393 const ret_ty_src = block.src(.{ .node_offset_asm_ret_ty = extra.data.src_node });16394 const ret_ty_src = block.src(.{ .node_offset_asm_ret_ty = extra.data.src_node });
...@@ -16396,7 +16397,6 @@ fn zirAsm(...@@ -16396,7 +16397,6 @@ fn zirAsm(
16396 const inputs_len = small.inputs_len;16397 const inputs_len = small.inputs_len;
16397 const is_volatile = small.is_volatile;16398 const is_volatile = small.is_volatile;
16398 const is_global_assembly = sema.func_index == .none;16399 const is_global_assembly = sema.func_index == .none;
16399 const zir_tags = sema.code.instructions.items(.tag);
1640016400
16401 const asm_source: []const u8 = if (tmpl_is_expr) s: {16401 const asm_source: []const u8 = if (tmpl_is_expr) s: {
16402 const tmpl: Zir.Inst.Ref = @enumFromInt(@intFromEnum(extra.data.asm_source));16402 const tmpl: Zir.Inst.Ref = @enumFromInt(@intFromEnum(extra.data.asm_source));
...@@ -16426,29 +16426,37 @@ fn zirAsm(...@@ -16426,29 +16426,37 @@ fn zirAsm(
1642616426
16427 for (out_args, 0..) |*arg, out_i| {16427 for (out_args, 0..) |*arg, out_i| {
16428 const output = sema.code.extraData(Zir.Inst.Asm.Output, extra_i);16428 const output = sema.code.extraData(Zir.Inst.Asm.Output, extra_i);
16429 const output_src = block.src(.{ .asm_output = .{
16430 .offset = src.offset.node_offset.x,
16431 .output_index = @intCast(out_i),
16432 } });
16429 extra_i = output.end;16433 extra_i = output.end;
1643016434
16431 const is_type = @as(u1, @truncate(output_type_bits)) != 0;16435 const is_type = @as(u1, @truncate(output_type_bits)) != 0;
16432 output_type_bits >>= 1;16436 output_type_bits >>= 1;
1643316437
16438 const name = sema.code.nullTerminatedString(output.data.name);
16439
16434 if (is_type) {16440 if (is_type) {
16435 // Indicate the output is the asm instruction return value.16441 // Indicate the output is the asm instruction return value.
16436 arg.* = .none;16442 arg.* = .none;
16437 const out_ty = try sema.resolveType(block, ret_ty_src, output.data.operand);16443 const out_ty = try sema.resolveType(block, ret_ty_src, output.data.operand);
16438 expr_ty = Air.internedToRef(out_ty.toIntern());16444 expr_ty = Air.internedToRef(out_ty.toIntern());
16439 } else {16445 } else {
16440 arg.* = try sema.resolveInst(output.data.operand);16446 const inst = try sema.resolveInst(output.data.operand);
16447 if (!sema.checkRuntimeValue(inst)) {
16448 const output_name = try ip.getOrPutString(sema.gpa, pt.tid, name, .no_embedded_nulls);
16449 return sema.failWithContainsReferenceToComptimeVar(block, output_src, output_name, "assembly output", .fromInterned(inst.toInterned().?));
16450 }
16451 arg.* = inst;
16441 }16452 }
1644216453
16443 const constraint = sema.code.nullTerminatedString(output.data.constraint);16454 const constraint = sema.code.nullTerminatedString(output.data.constraint);
16444 const name = sema.code.nullTerminatedString(output.data.name);
16445 needed_capacity += (constraint.len + name.len + (2 + 3)) / 4;16455 needed_capacity += (constraint.len + name.len + (2 + 3)) / 4;
1644616456
16447 if (output.data.operand.toIndex()) |index| {16457 // AstGen gives us a reference to a variable
16448 if (zir_tags[@intFromEnum(index)] == .ref) {16458 if (arg.* != .none and sema.typeOf(arg.*).isConstPtr(zcu)) {
16449 // TODO: better error location; it would be even nicer if there were notes that pointed at the output and the variable definition16459 return sema.fail(block, output_src, "asm cannot output to const '{s}'", .{name});
16450 return sema.fail(block, src, "asm cannot output to const local '{s}'", .{name});
16451 }
16452 }16460 }
1645316461
16454 outputs[out_i] = .{ .c = constraint, .n = name };16462 outputs[out_i] = .{ .c = constraint, .n = name };
...@@ -16459,9 +16467,18 @@ fn zirAsm(...@@ -16459,9 +16467,18 @@ fn zirAsm(
1645916467
16460 for (args, 0..) |*arg, arg_i| {16468 for (args, 0..) |*arg, arg_i| {
16461 const input = sema.code.extraData(Zir.Inst.Asm.Input, extra_i);16469 const input = sema.code.extraData(Zir.Inst.Asm.Input, extra_i);
16470 const input_src = block.src(.{ .asm_input = .{
16471 .offset = src.offset.node_offset.x,
16472 .input_index = @intCast(arg_i),
16473 } });
16462 extra_i = input.end;16474 extra_i = input.end;
1646316475
16464 const uncasted_arg = try sema.resolveInst(input.data.operand);16476 const uncasted_arg = try sema.resolveInst(input.data.operand);
16477 const name = sema.code.nullTerminatedString(input.data.name);
16478 if (!sema.checkRuntimeValue(uncasted_arg)) {
16479 const input_name = try ip.getOrPutString(sema.gpa, pt.tid, name, .no_embedded_nulls);
16480 return sema.failWithContainsReferenceToComptimeVar(block, input_src, input_name, "assembly input", .fromInterned(uncasted_arg.toInterned().?));
16481 }
16465 const uncasted_arg_ty = sema.typeOf(uncasted_arg);16482 const uncasted_arg_ty = sema.typeOf(uncasted_arg);
16466 switch (uncasted_arg_ty.zigTypeTag(zcu)) {16483 switch (uncasted_arg_ty.zigTypeTag(zcu)) {
16467 .comptime_int => arg.* = try sema.coerce(block, .usize, uncasted_arg, src),16484 .comptime_int => arg.* = try sema.coerce(block, .usize, uncasted_arg, src),
...@@ -16472,7 +16489,6 @@ fn zirAsm(...@@ -16472,7 +16489,6 @@ fn zirAsm(
16472 }16489 }
1647316490
16474 const constraint = sema.code.nullTerminatedString(input.data.constraint);16491 const constraint = sema.code.nullTerminatedString(input.data.constraint);
16475 const name = sema.code.nullTerminatedString(input.data.name);
16476 needed_capacity += (constraint.len + name.len + (2 + 3)) / 4;16492 needed_capacity += (constraint.len + name.len + (2 + 3)) / 4;
16477 inputs[arg_i] = .{ .c = constraint, .n = name };16493 inputs[arg_i] = .{ .c = constraint, .n = name };
16478 }16494 }
src/Zcu.zig+31
...@@ -1542,6 +1542,25 @@ pub const SrcLoc = struct {...@@ -1542,6 +1542,25 @@ pub const SrcLoc = struct {
1542 };1542 };
1543 return tree.nodeToSpan(src_node);1543 return tree.nodeToSpan(src_node);
1544 },1544 },
1545 .asm_input => |input| {
1546 const tree = try src_loc.file_scope.getTree(zcu);
1547 const node = input.offset.toAbsolute(src_loc.base_node);
1548 const full = tree.fullAsm(node).?;
1549 const asm_input = full.inputs[input.input_index];
1550 return tree.nodeToSpan(tree.nodeData(asm_input).node_and_token[0]);
1551 },
1552 .asm_output => |output| {
1553 const tree = try src_loc.file_scope.getTree(zcu);
1554 const node = output.offset.toAbsolute(src_loc.base_node);
1555 const full = tree.fullAsm(node).?;
1556 const asm_output = full.outputs[output.output_index];
1557 const data = tree.nodeData(asm_output).opt_node_and_token;
1558 return if (data[0].unwrap()) |output_node|
1559 tree.nodeToSpan(output_node)
1560 else
1561 // token points to the ')'
1562 tree.tokenToSpan(data[1] - 1);
1563 },
1545 .for_input => |for_input| {1564 .for_input => |for_input| {
1546 const tree = try src_loc.file_scope.getTree(zcu);1565 const tree = try src_loc.file_scope.getTree(zcu);
1547 const node = for_input.for_node_offset.toAbsolute(src_loc.base_node);1566 const node = for_input.for_node_offset.toAbsolute(src_loc.base_node);
...@@ -2507,6 +2526,18 @@ pub const LazySrcLoc = struct {...@@ -2507,6 +2526,18 @@ pub const LazySrcLoc = struct {
2507 /// The source location points to the operand of a `return` statement, or2526 /// The source location points to the operand of a `return` statement, or
2508 /// the `return` itself if there is no explicit operand.2527 /// the `return` itself if there is no explicit operand.
2509 node_offset_return_operand: Ast.Node.Offset,2528 node_offset_return_operand: Ast.Node.Offset,
2529 /// The source location points to an assembly input
2530 asm_input: struct {
2531 /// Points to the assembly node
2532 offset: Ast.Node.Offset,
2533 input_index: u32,
2534 },
2535 /// The source location points to an assembly output
2536 asm_output: struct {
2537 /// Points to the assembly node
2538 offset: Ast.Node.Offset,
2539 output_index: u32,
2540 },
2510 /// The source location points to a for loop input.2541 /// The source location points to a for loop input.
2511 for_input: struct {2542 for_input: struct {
2512 /// Points to the for loop AST node.2543 /// Points to the for loop AST node.
src/print_zir.zig+11-30
...@@ -1267,18 +1267,14 @@ const Writer = struct {...@@ -1267,18 +1267,14 @@ const Writer = struct {
1267 tmpl_is_expr: bool,1267 tmpl_is_expr: bool,
1268 ) !void {1268 ) !void {
1269 const extra = self.code.extraData(Zir.Inst.Asm, extended.operand);1269 const extra = self.code.extraData(Zir.Inst.Asm, extended.operand);
1270 const outputs_len = @as(u5, @truncate(extended.small));1270 const small: Zir.Inst.Asm.Small = @bitCast(extended.small);
1271 const inputs_len = @as(u5, @truncate(extended.small >> 5));
1272 const clobbers_len = @as(u5, @truncate(extended.small >> 10));
1273 const is_volatile = @as(u1, @truncate(extended.small >> 15)) != 0;
12741271
1275 try self.writeFlag(stream, "volatile, ", is_volatile);1272 try self.writeFlag(stream, "volatile, ", small.is_volatile);
1276 if (tmpl_is_expr) {1273 if (tmpl_is_expr) {
1277 try self.writeInstRef(stream, @enumFromInt(@intFromEnum(extra.data.asm_source)));1274 try self.writeInstRef(stream, @enumFromInt(@intFromEnum(extra.data.asm_source)));
1278 try stream.writeAll(", ");
1279 } else {1275 } else {
1280 const asm_source = self.code.nullTerminatedString(extra.data.asm_source);1276 const asm_source = self.code.nullTerminatedString(extra.data.asm_source);
1281 try stream.print("\"{f}\", ", .{std.zig.fmtString(asm_source)});1277 try stream.print("\"{f}\"", .{std.zig.fmtString(asm_source)});
1282 }1278 }
1283 try stream.writeAll(", ");1279 try stream.writeAll(", ");
12841280
...@@ -1286,7 +1282,7 @@ const Writer = struct {...@@ -1286,7 +1282,7 @@ const Writer = struct {
1286 var output_type_bits = extra.data.output_type_bits;1282 var output_type_bits = extra.data.output_type_bits;
1287 {1283 {
1288 var i: usize = 0;1284 var i: usize = 0;
1289 while (i < outputs_len) : (i += 1) {1285 while (i < small.outputs_len) : (i += 1) {
1290 const output = self.code.extraData(Zir.Inst.Asm.Output, extra_i);1286 const output = self.code.extraData(Zir.Inst.Asm.Output, extra_i);
1291 extra_i = output.end;1287 extra_i = output.end;
12921288
...@@ -1298,17 +1294,14 @@ const Writer = struct {...@@ -1298,17 +1294,14 @@ const Writer = struct {
1298 try stream.print("output({f}, \"{f}\", ", .{1294 try stream.print("output({f}, \"{f}\", ", .{
1299 std.zig.fmtIdP(name), std.zig.fmtString(constraint),1295 std.zig.fmtIdP(name), std.zig.fmtString(constraint),
1300 });1296 });
1301 try self.writeFlag(stream, "->", is_type);1297 try self.writeFlag(stream, "-> ", is_type);
1302 try self.writeInstRef(stream, output.data.operand);1298 try self.writeInstRef(stream, output.data.operand);
1303 try stream.writeAll(")");1299 try stream.writeAll("), ");
1304 if (i + 1 < outputs_len) {
1305 try stream.writeAll("), ");
1306 }
1307 }1300 }
1308 }1301 }
1309 {1302 {
1310 var i: usize = 0;1303 var i: usize = 0;
1311 while (i < inputs_len) : (i += 1) {1304 while (i < small.inputs_len) : (i += 1) {
1312 const input = self.code.extraData(Zir.Inst.Asm.Input, extra_i);1305 const input = self.code.extraData(Zir.Inst.Asm.Input, extra_i);
1313 extra_i = input.end;1306 extra_i = input.end;
13141307
...@@ -1318,24 +1311,12 @@ const Writer = struct {...@@ -1318,24 +1311,12 @@ const Writer = struct {
1318 std.zig.fmtIdP(name), std.zig.fmtString(constraint),1311 std.zig.fmtIdP(name), std.zig.fmtString(constraint),
1319 });1312 });
1320 try self.writeInstRef(stream, input.data.operand);1313 try self.writeInstRef(stream, input.data.operand);
1321 try stream.writeAll(")");1314 try stream.writeAll("), ");
1322 if (i + 1 < inputs_len) {
1323 try stream.writeAll(", ");
1324 }
1325 }
1326 }
1327 {
1328 var i: usize = 0;
1329 while (i < clobbers_len) : (i += 1) {
1330 const str_index = self.code.extra[extra_i];
1331 extra_i += 1;
1332 const clobber = self.code.nullTerminatedString(@enumFromInt(str_index));
1333 try stream.print("{f}", .{std.zig.fmtIdP(clobber)});
1334 if (i + 1 < clobbers_len) {
1335 try stream.writeAll(", ");
1336 }
1337 }1315 }
1338 }1316 }
1317
1318 try self.writeInstRef(stream, extra.data.clobbers);
1319
1339 try stream.writeAll(")) ");1320 try stream.writeAll(")) ");
1340 try self.writeSrcNode(stream, extra.data.src_node);1321 try self.writeSrcNode(stream, extra.data.src_node);
1341 }1322 }
test/cases/compile_errors/asm_output_to_const.zig+11-5
...@@ -1,12 +1,18 @@...@@ -1,12 +1,18 @@
1export fn foo() void {1export fn foo() void {
2 const f: i64 = 1000;2 const local: usize = 0;
3 asm volatile (""
4 : [_] "=r" (local),
5 );
6}
37
4 asm volatile (8const global: usize = 0;
5 \\ movq $10, %[f]9export fn bar() void {
6 : [f] "=r" (f),10 asm volatile (""
11 : [_] "=r" (global),
7 );12 );
8}13}
914
10// error15// error
11//16//
12// :4:5: error: asm cannot output to const local 'f'17// :4:21: error: asm cannot output to const '_'
18// :11:21: error: asm cannot output to const '_'
test/cases/compile_errors/comptime_var_referenced_by_asm.zig created+21
...@@ -0,0 +1,21 @@
1export fn foo() void {
2 comptime var a: u32 = 0;
3 asm volatile (""
4 :
5 : [in] "r" (&a),
6 );
7}
8
9export fn bar() void {
10 comptime var a: u32 = 0;
11 asm volatile (""
12 : [out] "=r" (a),
13 );
14}
15
16// error
17//
18// :5:21: error: assembly input contains reference to comptime var
19// :2:14: note: 'in' points to comptime var declared here
20// :12:23: error: assembly output contains reference to comptime var
21// :10:14: note: 'out' points to comptime var declared here