| author | |
| committer | |
| log | 524345b6353c5aa4a848f4e579ab81f8ac126319 |
| tree | 962c991f936cbafebc954932c013a0a781176387 |
| parent | 2d237da50b38a242e31d95adee18b4e1dcfa67b7 |
Inline assembly now rejects output types that don't have a well-defined
in-memory layout and correctly resolves the layout of the ones that do.4 files changed, 139 insertions(+), 11 deletions(-)
src/Sema.zig+28-11| ... | ... | @@ -15593,18 +15593,35 @@ fn zirAsm( |
| 15593 | 15593 | |
| 15594 | 15594 | const name = sema.code.nullTerminatedString(output.data.name); |
| 15595 | 15595 | |
| 15596 | if (is_type) { | |
| 15597 | // Indicate the output is the asm instruction return value. | |
| 15598 | arg.* = .none; | |
| 15599 | const out_ty = try sema.resolveType(block, ret_ty_src, output.data.operand); | |
| 15600 | expr_ty = Air.internedToRef(out_ty.toIntern()); | |
| 15601 | } else { | |
| 15602 | const inst = sema.resolveInst(output.data.operand); | |
| 15603 | if (!sema.checkRuntimeValue(inst)) { | |
| 15604 | const output_name = try ip.getOrPutString(gpa, io, pt.tid, name, .no_embedded_nulls); | |
| 15605 | return sema.failWithContainsReferenceToComptimeVar(block, output_src, output_name, "assembly output", .fromInterned(inst.toInterned().?)); | |
| 15596 | const out_ty: Type = out_ty: { | |
| 15597 | if (is_type) { | |
| 15598 | // Indicate the output is the asm instruction return value. | |
| 15599 | arg.* = .none; | |
| 15600 | ||
| 15601 | const out_ty = try sema.resolveType(block, ret_ty_src, output.data.operand); | |
| 15602 | try sema.ensureLayoutResolved(out_ty, ret_ty_src, .asm_out_type); | |
| 15603 | expr_ty = .fromType(out_ty); | |
| 15604 | break :out_ty out_ty; | |
| 15605 | } else { | |
| 15606 | const inst = sema.resolveInst(output.data.operand); | |
| 15607 | arg.* = inst; | |
| 15608 | ||
| 15609 | if (!sema.checkRuntimeValue(inst)) { | |
| 15610 | const output_name = try ip.getOrPutString(gpa, io, pt.tid, name, .no_embedded_nulls); | |
| 15611 | return sema.failWithContainsReferenceToComptimeVar(block, output_src, output_name, "assembly output", .fromInterned(inst.toInterned().?)); | |
| 15612 | } | |
| 15613 | break :out_ty sema.typeOf(inst).childType(zcu); | |
| 15606 | 15614 | } |
| 15607 | arg.* = inst; | |
| 15615 | }; | |
| 15616 | if (!out_ty.hasWellDefinedLayout(zcu)) { | |
| 15617 | return sema.failWithOwnedErrorMsg(block, msg: { | |
| 15618 | const msg = try sema.errMsg(output_src, "invalid inline assembly output type; '{f}' does not have a guaranteed in-memory layout", .{ | |
| 15619 | out_ty.fmt(pt), | |
| 15620 | }); | |
| 15621 | errdefer msg.destroy(gpa); | |
| 15622 | try sema.addDeclaredHereNote(msg, out_ty); | |
| 15623 | break :msg msg; | |
| 15624 | }); | |
| 15608 | 15625 | } |
| 15609 | 15626 | |
| 15610 | 15627 | const constraint = sema.code.nullTerminatedString(output.data.constraint); |
src/Sema/type_resolution.zig+2| ... | ... | @@ -34,6 +34,7 @@ pub const LayoutResolveReason = enum { |
| 34 | 34 | bit_ptr_child, |
| 35 | 35 | @"export", |
| 36 | 36 | @"extern", |
| 37 | asm_out_type, | |
| 37 | 38 | builtin_type, |
| 38 | 39 | |
| 39 | 40 | /// Written after string: "while resolving type 'T' " |
| ... | ... | @@ -60,6 +61,7 @@ pub const LayoutResolveReason = enum { |
| 60 | 61 | .bit_ptr_child => "for bit size check here", |
| 61 | 62 | .@"export" => "for export here", |
| 62 | 63 | .@"extern" => "for extern declaration here", |
| 64 | .asm_out_type => "for inline assembly output type declared here", | |
| 63 | 65 | .builtin_type => "from 'std.builtin'", |
| 64 | 66 | // zig fmt: on |
| 65 | 67 | }; |
test/behavior/asm.zig+71| ... | ... | @@ -175,3 +175,74 @@ test "asm modifiers (AArch64)" { |
| 175 | 175 | ); |
| 176 | 176 | try expectEqual(2 * x, double); |
| 177 | 177 | } |
| 178 | ||
| 179 | test "packed output types (x86_64)" { | |
| 180 | if (builtin.target.cpu.arch != .x86_64) return error.SkipZigTest; | |
| 181 | if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support inline assembly | |
| 182 | ||
| 183 | const S = packed struct(u32) { x: u32 }; | |
| 184 | { | |
| 185 | const s: S = asm volatile ("mov $123, %[ret]" | |
| 186 | : [ret] "=r" (-> S), | |
| 187 | ); | |
| 188 | try expect(s.x == 123); | |
| 189 | } | |
| 190 | { | |
| 191 | var s: S = undefined; | |
| 192 | asm volatile ("mov $123, %[ret]" | |
| 193 | : [ret] "=r" (s), | |
| 194 | ); | |
| 195 | try expect(s.x == 123); | |
| 196 | } | |
| 197 | ||
| 198 | const U = packed union(u32) { x: u32 }; | |
| 199 | { | |
| 200 | const u: U = asm volatile ("mov $123, %[ret]" | |
| 201 | : [ret] "=r" (-> U), | |
| 202 | ); | |
| 203 | try expect(u.x == 123); | |
| 204 | } | |
| 205 | { | |
| 206 | var u: U = undefined; | |
| 207 | asm volatile ("mov $123, %[ret]" | |
| 208 | : [ret] "=r" (u), | |
| 209 | ); | |
| 210 | try expect(u.x == 123); | |
| 211 | } | |
| 212 | } | |
| 213 | ||
| 214 | test "extern output types (x86_64)" { | |
| 215 | if (builtin.target.cpu.arch != .x86_64) return error.SkipZigTest; | |
| 216 | if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support inline assembly | |
| 217 | if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/31531 | |
| 218 | ||
| 219 | const S = extern struct { x: u32 }; | |
| 220 | { | |
| 221 | const s: S = asm volatile ("mov $123, %[ret]" | |
| 222 | : [ret] "=r" (-> S), | |
| 223 | ); | |
| 224 | try expect(s.x == 123); | |
| 225 | } | |
| 226 | { | |
| 227 | var s: S = undefined; | |
| 228 | asm volatile ("mov $123, %[ret]" | |
| 229 | : [ret] "=r" (s), | |
| 230 | ); | |
| 231 | try expect(s.x == 123); | |
| 232 | } | |
| 233 | ||
| 234 | const U = extern union { x: u32 }; | |
| 235 | { | |
| 236 | const u: U = asm volatile ("mov $123, %[ret]" | |
| 237 | : [ret] "=r" (-> U), | |
| 238 | ); | |
| 239 | try expect(u.x == 123); | |
| 240 | } | |
| 241 | { | |
| 242 | var u: U = undefined; | |
| 243 | asm volatile ("mov $123, %[ret]" | |
| 244 | : [ret] "=r" (u), | |
| 245 | ); | |
| 246 | try expect(u.x == 123); | |
| 247 | } | |
| 248 | } |
test/cases/compile_errors/asm_output_type_no_guaranteed_in_memory_layout.zig created+38| ... | ... | @@ -0,0 +1,38 @@ |
| 1 | const S = struct { x: u32 }; | |
| 2 | export fn entry1() void { | |
| 3 | const s = asm volatile ("" | |
| 4 | : [_] "=r" (-> S), | |
| 5 | ); | |
| 6 | _ = s; | |
| 7 | } | |
| 8 | export fn entry2() void { | |
| 9 | var s: S = undefined; | |
| 10 | asm volatile ("" | |
| 11 | : [_] "=r" (s), | |
| 12 | ); | |
| 13 | } | |
| 14 | ||
| 15 | const U = union { x: u32 }; | |
| 16 | export fn entry3() void { | |
| 17 | const u = asm volatile ("" | |
| 18 | : [_] "=r" (-> U), | |
| 19 | ); | |
| 20 | _ = u; | |
| 21 | } | |
| 22 | export fn entry4() void { | |
| 23 | var u: U = undefined; | |
| 24 | asm volatile ("" | |
| 25 | : [_] "=r" (u), | |
| 26 | ); | |
| 27 | } | |
| 28 | ||
| 29 | // error | |
| 30 | // | |
| 31 | // :4:24: error: invalid inline assembly output type; 'tmp.S' does not have a guaranteed in-memory layout | |
| 32 | // :1:11: note: struct declared here | |
| 33 | // :11:21: error: invalid inline assembly output type; 'tmp.S' does not have a guaranteed in-memory layout | |
| 34 | // :1:11: note: struct declared here | |
| 35 | // :18:24: error: invalid inline assembly output type; 'tmp.U' does not have a guaranteed in-memory layout | |
| 36 | // :15:11: note: union declared here | |
| 37 | // :25:21: error: invalid inline assembly output type; 'tmp.U' does not have a guaranteed in-memory layout | |
| 38 | // :15:11: note: union declared here |