Commit fcafc63f3d

Andrew Kelley <andrew@ziglang.org>
2025-07-12 07:03:00
inline assembly: use types
until now these were stringly typed. it's kinda obvious when you think about it.
1 parent 6002514
lib/std/builtin/assembly.zig
@@ -0,0 +1,1026 @@
+pub const Clobbers = switch (@import("builtin").cpu.arch) {
+    .x86, .x86_64 => packed struct {
+        /// Whether the inline assembly code may perform stores to memory
+        /// addresses other than those derived from input pointer provenance.
+        memory: bool = false,
+
+        cc: bool = false,
+        dirflag: bool = false,
+        eflags: bool = false,
+        flags: bool = false,
+        fpcr: bool = false,
+        fpsr: bool = false,
+        mxcsr: bool = false,
+        rflags: bool = false,
+
+        rax: bool = false,
+        rcx: bool = false,
+        rdx: bool = false,
+        rbx: bool = false,
+        rsp: bool = false,
+        rbp: bool = false,
+        rsi: bool = false,
+        rdi: bool = false,
+        r8: bool = false,
+        r9: bool = false,
+        r10: bool = false,
+        r11: bool = false,
+        r12: bool = false,
+        r13: bool = false,
+        r14: bool = false,
+        r15: bool = false,
+        eax: bool = false,
+        ecx: bool = false,
+        edx: bool = false,
+        ebx: bool = false,
+        esp: bool = false,
+        ebp: bool = false,
+        esi: bool = false,
+        edi: bool = false,
+        r8d: bool = false,
+        r9d: bool = false,
+        r10d: bool = false,
+        r11d: bool = false,
+        r12d: bool = false,
+        r13d: bool = false,
+        r14d: bool = false,
+        r15d: bool = false,
+        ax: bool = false,
+        cx: bool = false,
+        dx: bool = false,
+        bx: bool = false,
+        sp: bool = false,
+        bp: bool = false,
+        si: bool = false,
+        di: bool = false,
+        r8w: bool = false,
+        r9w: bool = false,
+        r10w: bool = false,
+        r11w: bool = false,
+        r12w: bool = false,
+        r13w: bool = false,
+        r14w: bool = false,
+        r15w: bool = false,
+        al: bool = false,
+        cl: bool = false,
+        dl: bool = false,
+        bl: bool = false,
+        spl: bool = false,
+        bpl: bool = false,
+        sil: bool = false,
+        dil: bool = false,
+        r8b: bool = false,
+        r9b: bool = false,
+        r10b: bool = false,
+        r11b: bool = false,
+        r12b: bool = false,
+        r13b: bool = false,
+        r14b: bool = false,
+        r15b: bool = false,
+        ah: bool = false,
+        ch: bool = false,
+        dh: bool = false,
+        bh: bool = false,
+        zmm0: bool = false,
+        zmm1: bool = false,
+        zmm2: bool = false,
+        zmm3: bool = false,
+        zmm4: bool = false,
+        zmm5: bool = false,
+        zmm6: bool = false,
+        zmm7: bool = false,
+        zmm8: bool = false,
+        zmm9: bool = false,
+        zmm10: bool = false,
+        zmm11: bool = false,
+        zmm12: bool = false,
+        zmm13: bool = false,
+        zmm14: bool = false,
+        zmm15: bool = false,
+        zmm16: bool = false,
+        zmm17: bool = false,
+        zmm18: bool = false,
+        zmm19: bool = false,
+        zmm20: bool = false,
+        zmm21: bool = false,
+        zmm22: bool = false,
+        zmm23: bool = false,
+        zmm24: bool = false,
+        zmm25: bool = false,
+        zmm26: bool = false,
+        zmm27: bool = false,
+        zmm28: bool = false,
+        zmm29: bool = false,
+        zmm30: bool = false,
+        zmm31: bool = false,
+        ymm0: bool = false,
+        ymm1: bool = false,
+        ymm2: bool = false,
+        ymm3: bool = false,
+        ymm4: bool = false,
+        ymm5: bool = false,
+        ymm6: bool = false,
+        ymm7: bool = false,
+        ymm8: bool = false,
+        ymm9: bool = false,
+        ymm10: bool = false,
+        ymm11: bool = false,
+        ymm12: bool = false,
+        ymm13: bool = false,
+        ymm14: bool = false,
+        ymm15: bool = false,
+        ymm16: bool = false,
+        ymm17: bool = false,
+        ymm18: bool = false,
+        ymm19: bool = false,
+        ymm20: bool = false,
+        ymm21: bool = false,
+        ymm22: bool = false,
+        ymm23: bool = false,
+        ymm24: bool = false,
+        ymm25: bool = false,
+        ymm26: bool = false,
+        ymm27: bool = false,
+        ymm28: bool = false,
+        ymm29: bool = false,
+        ymm30: bool = false,
+        ymm31: bool = false,
+        xmm0: bool = false,
+        xmm1: bool = false,
+        xmm2: bool = false,
+        xmm3: bool = false,
+        xmm4: bool = false,
+        xmm5: bool = false,
+        xmm6: bool = false,
+        xmm7: bool = false,
+        xmm8: bool = false,
+        xmm9: bool = false,
+        xmm10: bool = false,
+        xmm11: bool = false,
+        xmm12: bool = false,
+        xmm13: bool = false,
+        xmm14: bool = false,
+        xmm15: bool = false,
+        xmm16: bool = false,
+        xmm17: bool = false,
+        xmm18: bool = false,
+        xmm19: bool = false,
+        xmm20: bool = false,
+        xmm21: bool = false,
+        xmm22: bool = false,
+        xmm23: bool = false,
+        xmm24: bool = false,
+        xmm25: bool = false,
+        xmm26: bool = false,
+        xmm27: bool = false,
+        xmm28: bool = false,
+        xmm29: bool = false,
+        xmm30: bool = false,
+        xmm31: bool = false,
+        mm0: bool = false,
+        mm1: bool = false,
+        mm2: bool = false,
+        mm3: bool = false,
+        mm4: bool = false,
+        mm5: bool = false,
+        mm6: bool = false,
+        mm7: bool = false,
+        st0: bool = false,
+        st1: bool = false,
+        st2: bool = false,
+        st3: bool = false,
+        st4: bool = false,
+        st5: bool = false,
+        st6: bool = false,
+        st7: bool = false,
+        es: bool = false,
+        cs: bool = false,
+        ss: bool = false,
+        ds: bool = false,
+        fs: bool = false,
+        gs: bool = false,
+        rip: bool = false,
+        eip: bool = false,
+        ip: bool = false,
+        cr0: bool = false,
+        cr1: bool = false,
+        cr2: bool = false,
+        cr3: bool = false,
+        cr4: bool = false,
+        cr5: bool = false,
+        cr6: bool = false,
+        cr7: bool = false,
+        cr8: bool = false,
+        cr9: bool = false,
+        cr10: bool = false,
+        cr11: bool = false,
+        cr12: bool = false,
+        cr13: bool = false,
+        cr14: bool = false,
+        cr15: bool = false,
+        dr0: bool = false,
+        dr1: bool = false,
+        dr2: bool = false,
+        dr3: bool = false,
+        dr4: bool = false,
+        dr5: bool = false,
+        dr6: bool = false,
+        dr7: bool = false,
+        dr8: bool = false,
+        dr9: bool = false,
+        dr10: bool = false,
+        dr11: bool = false,
+        dr12: bool = false,
+        dr13: bool = false,
+        dr14: bool = false,
+        dr15: bool = false,
+    },
+    .aarch64, .aarch64_be => packed struct {
+        /// Whether the inline assembly code may perform stores to memory
+        /// addresses other than those derived from input pointer provenance.
+        memory: bool = false,
+
+        nzcv: bool = false,
+
+        x0: bool = false,
+        x1: bool = false,
+        x2: bool = false,
+        x3: bool = false,
+        x4: bool = false,
+        x5: bool = false,
+        x6: bool = false,
+        x7: bool = false,
+        x8: bool = false,
+        x9: bool = false,
+        x10: bool = false,
+        x11: bool = false,
+        x12: bool = false,
+        x13: bool = false,
+        x14: bool = false,
+        x15: bool = false,
+        x16: bool = false,
+        x17: bool = false,
+        x18: bool = false,
+        x19: bool = false,
+        x20: bool = false,
+        x21: bool = false,
+        x22: bool = false,
+        x23: bool = false,
+        x24: bool = false,
+        x25: bool = false,
+        x26: bool = false,
+        x27: bool = false,
+        x28: bool = false,
+        x29: bool = false,
+        x30: bool = false,
+
+        w0: bool = false,
+        w1: bool = false,
+        w2: bool = false,
+        w3: bool = false,
+        w4: bool = false,
+        w5: bool = false,
+        w6: bool = false,
+        w7: bool = false,
+        w8: bool = false,
+        w9: bool = false,
+        w10: bool = false,
+        w11: bool = false,
+        w12: bool = false,
+        w13: bool = false,
+        w14: bool = false,
+        w15: bool = false,
+        w16: bool = false,
+        w17: bool = false,
+        w18: bool = false,
+        w19: bool = false,
+        w20: bool = false,
+        w21: bool = false,
+        w22: bool = false,
+        w23: bool = false,
+        w24: bool = false,
+        w25: bool = false,
+        w26: bool = false,
+        w27: bool = false,
+        w28: bool = false,
+        w29: bool = false,
+        w30: bool = false,
+
+        lr: bool = false,
+        sp: bool = false,
+        wsp: bool = false,
+        fpcr: bool = false,
+        fpmr: bool = false,
+        fpsr: bool = false,
+        ffr: bool = false,
+
+        p0: bool = false,
+        p1: bool = false,
+        p2: bool = false,
+        p3: bool = false,
+        p4: bool = false,
+        p5: bool = false,
+        p6: bool = false,
+        p7: bool = false,
+        p8: bool = false,
+        p9: bool = false,
+        p10: bool = false,
+        p11: bool = false,
+        p12: bool = false,
+        p13: bool = false,
+        p14: bool = false,
+        p15: bool = false,
+
+        z0: bool = false,
+        z1: bool = false,
+        z2: bool = false,
+        z3: bool = false,
+        z4: bool = false,
+        z5: bool = false,
+        z6: bool = false,
+        z7: bool = false,
+        z8: bool = false,
+        z9: bool = false,
+        z10: bool = false,
+        z11: bool = false,
+        z12: bool = false,
+        z13: bool = false,
+        z14: bool = false,
+        z15: bool = false,
+        z16: bool = false,
+        z17: bool = false,
+        z18: bool = false,
+        z19: bool = false,
+        z20: bool = false,
+        z21: bool = false,
+        z22: bool = false,
+        z23: bool = false,
+        z24: bool = false,
+        z25: bool = false,
+        z26: bool = false,
+        z27: bool = false,
+        z28: bool = false,
+        z29: bool = false,
+        z30: bool = false,
+        z31: bool = false,
+
+        v0: bool = false,
+        v1: bool = false,
+        v2: bool = false,
+        v3: bool = false,
+        v4: bool = false,
+        v5: bool = false,
+        v6: bool = false,
+        v7: bool = false,
+        v8: bool = false,
+        v9: bool = false,
+        v10: bool = false,
+        v11: bool = false,
+        v12: bool = false,
+        v13: bool = false,
+        v14: bool = false,
+        v15: bool = false,
+        v16: bool = false,
+        v17: bool = false,
+        v18: bool = false,
+        v19: bool = false,
+        v20: bool = false,
+        v21: bool = false,
+        v22: bool = false,
+        v23: bool = false,
+        v24: bool = false,
+        v25: bool = false,
+        v26: bool = false,
+        v27: bool = false,
+        v28: bool = false,
+        v29: bool = false,
+        v30: bool = false,
+        v31: bool = false,
+
+        d0: bool = false,
+        d1: bool = false,
+        d2: bool = false,
+        d3: bool = false,
+        d4: bool = false,
+        d5: bool = false,
+        d6: bool = false,
+        d7: bool = false,
+        d8: bool = false,
+        d9: bool = false,
+        d10: bool = false,
+        d11: bool = false,
+        d12: bool = false,
+        d13: bool = false,
+        d14: bool = false,
+        d15: bool = false,
+        d16: bool = false,
+        d17: bool = false,
+        d18: bool = false,
+        d19: bool = false,
+        d20: bool = false,
+        d21: bool = false,
+        d22: bool = false,
+        d23: bool = false,
+        d24: bool = false,
+        d25: bool = false,
+        d26: bool = false,
+        d27: bool = false,
+        d28: bool = false,
+        d29: bool = false,
+        d30: bool = false,
+        d31: bool = false,
+
+        s0: bool = false,
+        s1: bool = false,
+        s2: bool = false,
+        s3: bool = false,
+        s4: bool = false,
+        s5: bool = false,
+        s6: bool = false,
+        s7: bool = false,
+        s8: bool = false,
+        s9: bool = false,
+        s10: bool = false,
+        s11: bool = false,
+        s12: bool = false,
+        s13: bool = false,
+        s14: bool = false,
+        s15: bool = false,
+        s16: bool = false,
+        s17: bool = false,
+        s18: bool = false,
+        s19: bool = false,
+        s20: bool = false,
+        s21: bool = false,
+        s22: bool = false,
+        s23: bool = false,
+        s24: bool = false,
+        s25: bool = false,
+        s26: bool = false,
+        s27: bool = false,
+        s28: bool = false,
+        s29: bool = false,
+        s30: bool = false,
+        s31: bool = false,
+
+        h0: bool = false,
+        h1: bool = false,
+        h2: bool = false,
+        h3: bool = false,
+        h4: bool = false,
+        h5: bool = false,
+        h6: bool = false,
+        h7: bool = false,
+        h8: bool = false,
+        h9: bool = false,
+        h10: bool = false,
+        h11: bool = false,
+        h12: bool = false,
+        h13: bool = false,
+        h14: bool = false,
+        h15: bool = false,
+        h16: bool = false,
+        h17: bool = false,
+        h18: bool = false,
+        h19: bool = false,
+        h20: bool = false,
+        h21: bool = false,
+        h22: bool = false,
+        h23: bool = false,
+        h24: bool = false,
+        h25: bool = false,
+        h26: bool = false,
+        h27: bool = false,
+        h28: bool = false,
+        h29: bool = false,
+        h30: bool = false,
+        h31: bool = false,
+
+        b0: bool = false,
+        b1: bool = false,
+        b2: bool = false,
+        b3: bool = false,
+        b4: bool = false,
+        b5: bool = false,
+        b6: bool = false,
+        b7: bool = false,
+        b8: bool = false,
+        b9: bool = false,
+        b10: bool = false,
+        b11: bool = false,
+        b12: bool = false,
+        b13: bool = false,
+        b14: bool = false,
+        b15: bool = false,
+        b16: bool = false,
+        b17: bool = false,
+        b18: bool = false,
+        b19: bool = false,
+        b20: bool = false,
+        b21: bool = false,
+        b22: bool = false,
+        b23: bool = false,
+        b24: bool = false,
+        b25: bool = false,
+        b26: bool = false,
+        b27: bool = false,
+        b28: bool = false,
+        b29: bool = false,
+        b30: bool = false,
+        b31: bool = false,
+
+        za0: bool = false,
+        za1: bool = false,
+        za2: bool = false,
+        za3: bool = false,
+        za4: bool = false,
+        za5: bool = false,
+        za6: bool = false,
+        za7: bool = false,
+        za8: bool = false,
+        za9: bool = false,
+        za10: bool = false,
+        za11: bool = false,
+        za12: bool = false,
+        za13: bool = false,
+        za14: bool = false,
+        za15: bool = false,
+
+        zt0: bool = false,
+    },
+    .arm, .armeb => packed struct {
+        /// Whether the inline assembly code may perform stores to memory
+        /// addresses other than those derived from input pointer provenance.
+        memory: bool = false,
+
+        apsr: bool = false,
+        cpsr: bool = false,
+        spsr: bool = false,
+        r0: bool = false,
+        r1: bool = false,
+        r2: bool = false,
+        r3: bool = false,
+        r4: bool = false,
+        r5: bool = false,
+        r6: bool = false,
+        r7: bool = false,
+        r8: bool = false,
+        r9: bool = false,
+        r10: bool = false,
+        r11: bool = false,
+        r12: bool = false,
+        r13: bool = false,
+        r14: bool = false,
+        r15: bool = false,
+
+        fpscr: bool = false,
+        vpr: bool = false,
+
+        d0: bool = false,
+        d1: bool = false,
+        d2: bool = false,
+        d3: bool = false,
+        d4: bool = false,
+        d5: bool = false,
+        d6: bool = false,
+        d7: bool = false,
+        d8: bool = false,
+        d9: bool = false,
+        d10: bool = false,
+        d11: bool = false,
+        d12: bool = false,
+        d13: bool = false,
+        d14: bool = false,
+        d15: bool = false,
+        d16: bool = false,
+        d17: bool = false,
+        d18: bool = false,
+        d19: bool = false,
+        d20: bool = false,
+        d21: bool = false,
+        d22: bool = false,
+        d23: bool = false,
+        d24: bool = false,
+        d25: bool = false,
+        d26: bool = false,
+        d27: bool = false,
+        d28: bool = false,
+        d29: bool = false,
+        d30: bool = false,
+        d31: bool = false,
+
+        s0: bool = false,
+        s1: bool = false,
+        s2: bool = false,
+        s3: bool = false,
+        s4: bool = false,
+        s5: bool = false,
+        s6: bool = false,
+        s7: bool = false,
+        s8: bool = false,
+        s9: bool = false,
+        s10: bool = false,
+        s11: bool = false,
+        s12: bool = false,
+        s13: bool = false,
+        s14: bool = false,
+        s15: bool = false,
+        s16: bool = false,
+        s17: bool = false,
+        s18: bool = false,
+        s19: bool = false,
+        s20: bool = false,
+        s21: bool = false,
+        s22: bool = false,
+        s23: bool = false,
+        s24: bool = false,
+        s25: bool = false,
+        s26: bool = false,
+        s27: bool = false,
+        s28: bool = false,
+        s29: bool = false,
+        s30: bool = false,
+        s31: bool = false,
+
+        q0: bool = false,
+        q1: bool = false,
+        q2: bool = false,
+        q3: bool = false,
+        q4: bool = false,
+        q5: bool = false,
+        q6: bool = false,
+        q7: bool = false,
+        q8: bool = false,
+        q9: bool = false,
+        q10: bool = false,
+        q11: bool = false,
+        q12: bool = false,
+        q13: bool = false,
+        q14: bool = false,
+        q15: bool = false,
+    },
+    .riscv32, .riscv64 => packed struct {
+        /// Whether the inline assembly code may perform stores to memory
+        /// addresses other than those derived from input pointer provenance.
+        memory: bool = false,
+
+        ssp: bool = false,
+
+        x1: bool = false,
+        x2: bool = false,
+        x3: bool = false,
+        x4: bool = false,
+        x5: bool = false,
+        x6: bool = false,
+        x7: bool = false,
+        x8: bool = false,
+        x9: bool = false,
+        x10: bool = false,
+        x11: bool = false,
+        x12: bool = false,
+        x13: bool = false,
+        x14: bool = false,
+        x15: bool = false,
+        x16: bool = false,
+        x17: bool = false,
+        x18: bool = false,
+        x19: bool = false,
+        x20: bool = false,
+        x21: bool = false,
+        x22: bool = false,
+        x23: bool = false,
+        x24: bool = false,
+        x25: bool = false,
+        x26: bool = false,
+        x27: bool = false,
+        x28: bool = false,
+        x29: bool = false,
+        x30: bool = false,
+        x31: bool = false,
+
+        fflags: bool = false,
+        frm: bool = false,
+
+        f0: bool = false,
+        f1: bool = false,
+        f2: bool = false,
+        f3: bool = false,
+        f4: bool = false,
+        f5: bool = false,
+        f6: bool = false,
+        f7: bool = false,
+        f8: bool = false,
+        f9: bool = false,
+        f10: bool = false,
+        f11: bool = false,
+        f12: bool = false,
+        f13: bool = false,
+        f14: bool = false,
+        f15: bool = false,
+        f16: bool = false,
+        f17: bool = false,
+        f18: bool = false,
+        f19: bool = false,
+        f20: bool = false,
+        f21: bool = false,
+        f22: bool = false,
+        f23: bool = false,
+        f24: bool = false,
+        f25: bool = false,
+        f26: bool = false,
+        f27: bool = false,
+        f28: bool = false,
+        f29: bool = false,
+        f30: bool = false,
+        f31: bool = false,
+
+        vtype: bool = false,
+        vl: bool = false,
+        vxsat: bool = false,
+        vxrm: bool = false,
+        vcsr: bool = false,
+
+        v0: bool = false,
+        v1: bool = false,
+        v2: bool = false,
+        v3: bool = false,
+        v4: bool = false,
+        v5: bool = false,
+        v6: bool = false,
+        v7: bool = false,
+        v8: bool = false,
+        v9: bool = false,
+        v10: bool = false,
+        v11: bool = false,
+        v12: bool = false,
+        v13: bool = false,
+        v14: bool = false,
+        v15: bool = false,
+        v16: bool = false,
+        v17: bool = false,
+        v18: bool = false,
+        v19: bool = false,
+        v20: bool = false,
+        v21: bool = false,
+        v22: bool = false,
+        v23: bool = false,
+        v24: bool = false,
+        v25: bool = false,
+        v26: bool = false,
+        v27: bool = false,
+        v28: bool = false,
+        v29: bool = false,
+        v30: bool = false,
+        v31: bool = false,
+    },
+    .xcore => packed struct {
+        /// Whether the inline assembly code may perform stores to memory
+        /// addresses other than those derived from input pointer provenance.
+        memory: bool = false,
+
+        r0: bool = false,
+        r1: bool = false,
+        r2: bool = false,
+        r3: bool = false,
+        r4: bool = false,
+        r5: bool = false,
+        r6: bool = false,
+        r7: bool = false,
+        r8: bool = false,
+        r9: bool = false,
+        r10: bool = false,
+        r11: bool = false,
+
+        cp: bool = false,
+        dp: bool = false,
+        sp: bool = false,
+        lr: bool = false,
+        sr: bool = false,
+    },
+    .xtensa => packed struct {
+        /// Whether the inline assembly code may perform stores to memory
+        /// addresses other than those derived from input pointer provenance.
+        memory: bool = false,
+
+        sar: bool = false,
+        lbeg: bool = false,
+        lend: bool = false,
+        lcount: bool = false,
+        atomctl: bool = false,
+        scompare1: bool = false,
+        threadptr: bool = false,
+        litbase: bool = false,
+        windowbase: bool = false,
+        windowstart: bool = false,
+        ps: bool = false,
+
+        a0: bool = false,
+        a1: bool = false,
+        a2: bool = false,
+        a3: bool = false,
+        a4: bool = false,
+        a5: bool = false,
+        a6: bool = false,
+        a7: bool = false,
+        a8: bool = false,
+        a9: bool = false,
+        a10: bool = false,
+        a11: bool = false,
+        a12: bool = false,
+        a13: bool = false,
+        a14: bool = false,
+        a15: bool = false,
+
+        b0: bool = false,
+        b1: bool = false,
+        b2: bool = false,
+        b3: bool = false,
+        b4: bool = false,
+        b5: bool = false,
+        b6: bool = false,
+        b7: bool = false,
+        b8: bool = false,
+        b9: bool = false,
+        b10: bool = false,
+        b11: bool = false,
+        b12: bool = false,
+        b13: bool = false,
+        b14: bool = false,
+        b15: bool = false,
+
+        br: bool = false,
+        acchi: bool = false,
+        acclo: bool = false,
+        m0: bool = false,
+        m1: bool = false,
+        m2: bool = false,
+        m3: bool = false,
+        fcr: bool = false,
+        fsr: bool = false,
+
+        f0: bool = false,
+        f1: bool = false,
+        f2: bool = false,
+        f3: bool = false,
+        f4: bool = false,
+        f5: bool = false,
+        f6: bool = false,
+        f7: bool = false,
+        f8: bool = false,
+        f9: bool = false,
+        f10: bool = false,
+        f11: bool = false,
+        f12: bool = false,
+        f13: bool = false,
+        f14: bool = false,
+        f15: bool = false,
+    },
+    .lanai => packed struct {
+        /// Whether the inline assembly code may perform stores to memory
+        /// addresses other than those derived from input pointer provenance.
+        memory: bool = false,
+        /// Condition flags which aren't accessible outside of conditional execution.
+        sw: bool = false,
+
+        r3: bool = false,
+        r4: bool = false,
+        r5: bool = false,
+        r6: bool = false,
+        r7: bool = false,
+        r8: bool = false,
+        r9: bool = false,
+        r10: bool = false,
+        r11: bool = false,
+        r12: bool = false,
+        r13: bool = false,
+        r14: bool = false,
+        r15: bool = false,
+        r16: bool = false,
+        r17: bool = false,
+        r18: bool = false,
+        r19: bool = false,
+        r20: bool = false,
+        r21: bool = false,
+        r22: bool = false,
+        r23: bool = false,
+        r24: bool = false,
+        r25: bool = false,
+        r26: bool = false,
+        r27: bool = false,
+        r28: bool = false,
+        r29: bool = false,
+        r30: bool = false,
+        r31: bool = false,
+    },
+    .avr => packed struct {
+        /// Whether the inline assembly code may perform stores to memory
+        /// addresses other than those derived from input pointer provenance.
+        memory: bool = false,
+        flags: bool = false,
+        r0: bool = false,
+        r1: bool = false,
+        r2: bool = false,
+        r3: bool = false,
+        r4: bool = false,
+        r5: bool = false,
+        r6: bool = false,
+        r7: bool = false,
+        r8: bool = false,
+        r9: bool = false,
+        r10: bool = false,
+        r11: bool = false,
+        r12: bool = false,
+        r13: bool = false,
+        r14: bool = false,
+        r15: bool = false,
+        r16: bool = false,
+        r17: bool = false,
+        r18: bool = false,
+        r19: bool = false,
+        r20: bool = false,
+        r21: bool = false,
+        r22: bool = false,
+        r23: bool = false,
+        r24: bool = false,
+        r25: bool = false,
+        r26: bool = false,
+        r27: bool = false,
+        r28: bool = false,
+        r29: bool = false,
+        r30: bool = false,
+        r31: bool = false,
+    },
+    .msp430 => packed struct {
+        /// Whether the inline assembly code may perform stores to memory
+        /// addresses other than those derived from input pointer provenance.
+        memory: bool = false,
+
+        r0: bool = false,
+        r1: bool = false,
+        r2: bool = false,
+
+        r4: bool = false,
+        r5: bool = false,
+        r6: bool = false,
+        r7: bool = false,
+        r8: bool = false,
+        r9: bool = false,
+        r10: bool = false,
+        r11: bool = false,
+        r12: bool = false,
+        r13: bool = false,
+        r14: bool = false,
+        r15: bool = false,
+    },
+    .m68k => packed struct {
+        /// Whether the inline assembly code may perform stores to memory
+        /// addresses other than those derived from input pointer provenance.
+        memory: bool = false,
+
+        ccr: bool = false,
+
+        d0: bool = false,
+        d1: bool = false,
+        d2: bool = false,
+        d3: bool = false,
+        d4: bool = false,
+        d5: bool = false,
+        d6: bool = false,
+        d7: bool = false,
+
+        a0: bool = false,
+        a1: bool = false,
+        a2: bool = false,
+        a3: bool = false,
+        a4: bool = false,
+        a5: bool = false,
+        a6: bool = false,
+        a7: bool = false,
+
+        macsr: bool = false,
+        acc: bool = false,
+
+        acc0: bool = false,
+        acc1: bool = false,
+        acc2: bool = false,
+        acc3: bool = false,
+
+        mask: bool = false,
+        fpcr: bool = false,
+        fpsr: bool = false,
+
+        fp0: bool = false,
+        fp1: bool = false,
+        fp2: bool = false,
+        fp3: bool = false,
+        fp4: bool = false,
+        fp5: bool = false,
+        fp6: bool = false,
+        fp7: bool = false,
+    },
+    else => packed struct {
+        /// Whether the inline assembly code may perform stores to memory
+        /// addresses other than those derived from input pointer provenance.
+        memory: bool = false,
+    },
+};
lib/std/zig/Ast.zig
@@ -634,6 +634,7 @@ pub fn firstToken(tree: Ast, node: Node.Index) TokenIndex {
         .@"nosuspend",
         .asm_simple,
         .@"asm",
+        .asm_legacy,
         .array_type,
         .array_type_sentinel,
         .error_value,
@@ -1047,6 +1048,11 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex {
                 n = @enumFromInt(tree.extra_data[@intFromEnum(members.end) - 1]); // last parameter
             }
         },
+        .asm_legacy => {
+            _, const extra_index = tree.nodeData(n).node_and_extra;
+            const extra = tree.extraData(extra_index, Node.AsmLegacy);
+            return extra.rparen + end_offset;
+        },
         .@"asm" => {
             _, const extra_index = tree.nodeData(n).node_and_extra;
             const extra = tree.extraData(extra_index, Node.Asm);
@@ -1885,6 +1891,19 @@ pub fn asmSimple(tree: Ast, node: Node.Index) full.Asm {
         .template = template,
         .items = &.{},
         .rparen = rparen,
+        .clobbers = .none,
+    });
+}
+
+pub fn asmLegacy(tree: Ast, node: Node.Index) full.AsmLegacy {
+    const template, const extra_index = tree.nodeData(node).node_and_extra;
+    const extra = tree.extraData(extra_index, Node.AsmLegacy);
+    const items = tree.extraDataSlice(.{ .start = extra.items_start, .end = extra.items_end }, Node.Index);
+    return tree.legacyAsmComponents(.{
+        .asm_token = tree.nodeMainToken(node),
+        .template = template,
+        .items = items,
+        .rparen = extra.rparen,
     });
 }
 
@@ -1896,6 +1915,7 @@ pub fn asmFull(tree: Ast, node: Node.Index) full.Asm {
         .asm_token = tree.nodeMainToken(node),
         .template = template,
         .items = items,
+        .clobbers = extra.clobbers,
         .rparen = extra.rparen,
     });
 }
@@ -2192,8 +2212,8 @@ fn fullSwitchCaseComponents(tree: Ast, info: full.SwitchCase.Components, node: N
     return result;
 }
 
-fn fullAsmComponents(tree: Ast, info: full.Asm.Components) full.Asm {
-    var result: full.Asm = .{
+fn legacyAsmComponents(tree: Ast, info: full.AsmLegacy.Components) full.AsmLegacy {
+    var result: full.AsmLegacy = .{
         .ast = info,
         .volatile_token = null,
         .inputs = &.{},
@@ -2253,6 +2273,29 @@ fn fullAsmComponents(tree: Ast, info: full.Asm.Components) full.Asm {
     return result;
 }
 
+fn fullAsmComponents(tree: Ast, info: full.Asm.Components) full.Asm {
+    var result: full.Asm = .{
+        .ast = info,
+        .volatile_token = null,
+        .inputs = &.{},
+        .outputs = &.{},
+    };
+    if (tree.tokenTag(info.asm_token + 1) == .keyword_volatile) {
+        result.volatile_token = info.asm_token + 1;
+    }
+    const outputs_end: usize = for (info.items, 0..) |item, i| {
+        switch (tree.nodeTag(item)) {
+            .asm_output => continue,
+            else => break i,
+        }
+    } else info.items.len;
+
+    result.outputs = info.items[0..outputs_end];
+    result.inputs = info.items[outputs_end..];
+
+    return result;
+}
+
 fn fullWhileComponents(tree: Ast, info: full.While.Components) full.While {
     var result: full.While = .{
         .ast = info,
@@ -2447,6 +2490,14 @@ pub fn fullAsm(tree: Ast, node: Node.Index) ?full.Asm {
     };
 }
 
+/// To be deleted after 0.15.0 is tagged
+pub fn legacyAsm(tree: Ast, node: Node.Index) ?full.AsmLegacy {
+    return switch (tree.nodeTag(node)) {
+        .asm_legacy => tree.asmLegacy(node),
+        else => null,
+    };
+}
+
 pub fn fullCall(tree: Ast, buffer: *[1]Ast.Node.Index, node: Node.Index) ?full.Call {
     return switch (tree.nodeTag(node)) {
         .call, .call_comma => tree.callFull(node),
@@ -2827,6 +2878,21 @@ pub const full = struct {
     };
 
     pub const Asm = struct {
+        ast: Components,
+        volatile_token: ?TokenIndex,
+        outputs: []const Node.Index,
+        inputs: []const Node.Index,
+
+        pub const Components = struct {
+            asm_token: TokenIndex,
+            template: Node.Index,
+            items: []const Node.Index,
+            clobbers: Node.OptionalIndex,
+            rparen: TokenIndex,
+        };
+    };
+
+    pub const AsmLegacy = struct {
         ast: Components,
         volatile_token: ?TokenIndex,
         first_clobber: ?TokenIndex,
@@ -3833,15 +3899,22 @@ pub const Node = struct {
         /// Same as `block` except there is known to be a trailing comma before
         /// the final rbrace.
         block_semicolon,
-        /// `asm(lhs)`.
+        /// `asm(a)`.
         ///
-        /// rhs is a `Token.Index` to the `)` token.
         /// The `main_token` field is the `asm` token.
         asm_simple,
         /// `asm(lhs, a)`.
         ///
         /// The `data` field is a `.node_and_extra`:
         ///   1. a `Node.Index` to lhs.
+        ///   2. a `ExtraIndex` to `AsmLegacy`.
+        ///
+        /// The `main_token` field is the `asm` token.
+        asm_legacy,
+        /// `asm(a, b)`.
+        ///
+        /// The `data` field is a `.node_and_extra`:
+        ///   1. a `Node.Index` to a.
         ///   2. a `ExtraIndex` to `Asm`.
         ///
         /// The `main_token` field is the `asm` token.
@@ -4014,9 +4087,18 @@ pub const Node = struct {
         callconv_expr: OptionalIndex,
     };
 
+    /// To be removed after 0.15.0 is tagged
+    pub const AsmLegacy = struct {
+        items_start: ExtraIndex,
+        items_end: ExtraIndex,
+        /// Needed to make lastToken() work.
+        rparen: TokenIndex,
+    };
+
     pub const Asm = struct {
         items_start: ExtraIndex,
         items_end: ExtraIndex,
+        clobbers: OptionalIndex,
         /// Needed to make lastToken() work.
         rparen: TokenIndex,
     };
lib/std/zig/AstGen.zig
@@ -505,6 +505,7 @@ fn lvalExpr(gz: *GenZir, scope: *Scope, node: Ast.Node.Index) InnerError!Zir.Ins
         .bool_or,
         .@"asm",
         .asm_simple,
+        .asm_legacy,
         .string_literal,
         .number_literal,
         .call,
@@ -811,6 +812,12 @@ fn expr(gz: *GenZir, scope: *Scope, ri: ResultInfo, node: Ast.Node.Index) InnerE
         .@"asm",
         => return asmExpr(gz, scope, ri, node, tree.fullAsm(node).?),
 
+        .asm_legacy => {
+            return astgen.failNodeNotes(node, "legacy asm clobbers syntax", .{}, &[_]u32{
+                try astgen.errNoteNode(node, "use 'zig fmt' to auto-upgrade", .{}),
+            });
+        },
+
         .string_literal           => return stringLiteral(gz, ri, node),
         .multiline_string_literal => return multilineStringLiteral(gz, ri, node),
 
@@ -8774,7 +8781,7 @@ fn asmExpr(
     if (is_container_asm) {
         if (full.volatile_token) |t|
             return astgen.failTok(t, "volatile is meaningless on global assembly", .{});
-        if (full.outputs.len != 0 or full.inputs.len != 0 or full.first_clobber != null)
+        if (full.outputs.len != 0 or full.inputs.len != 0 or full.ast.clobbers != .none)
             return astgen.failNode(node, "global assembly cannot have inputs, outputs, or clobbers", .{});
     } else {
         if (full.outputs.len == 0 and full.volatile_token == null) {
@@ -8839,32 +8846,12 @@ fn asmExpr(
         };
     }
 
-    var clobbers_buffer: [63]u32 = undefined;
-    var clobber_i: usize = 0;
-    if (full.first_clobber) |first_clobber| clobbers: {
-        // asm ("foo" ::: "a", "b")
-        // asm ("foo" ::: "a", "b",)
-        var tok_i = first_clobber;
-        while (true) : (tok_i += 1) {
-            if (clobber_i >= clobbers_buffer.len) {
-                return astgen.failTok(tok_i, "too many asm clobbers", .{});
-            }
-            clobbers_buffer[clobber_i] = @intFromEnum((try astgen.strLitAsString(tok_i)).index);
-            clobber_i += 1;
-            tok_i += 1;
-            switch (tree.tokenTag(tok_i)) {
-                .r_paren => break :clobbers,
-                .comma => {
-                    if (tree.tokenTag(tok_i + 1) == .r_paren) {
-                        break :clobbers;
-                    } else {
-                        continue;
-                    }
-                },
-                else => unreachable,
-            }
-        }
-    }
+    const clobbers: Zir.Inst.Ref = if (full.ast.clobbers.unwrap()) |clobbers_node|
+        try comptimeExpr(gz, scope, .{ .rl = .{
+            .coerced_ty = try gz.addBuiltinValue(clobbers_node, .clobbers),
+        } }, clobbers_node, .clobber)
+    else
+        .none;
 
     const result = try gz.addAsm(.{
         .tag = tag_and_tmpl.tag,
@@ -8874,7 +8861,7 @@ fn asmExpr(
         .output_type_bits = output_type_bits,
         .outputs = outputs,
         .inputs = inputs,
-        .clobbers = clobbers_buffer[0..clobber_i],
+        .clobbers = clobbers,
     });
     return rvalue(gz, ri, result, node);
 }
@@ -10332,6 +10319,7 @@ fn nodeMayEvalToError(tree: *const Ast, start_node: Ast.Node.Index) BuiltinFn.Ev
 
             .@"asm",
             .asm_simple,
+            .asm_legacy,
             .identifier,
             .field_access,
             .deref,
@@ -10575,6 +10563,7 @@ fn nodeImpliesMoreThanOnePossibleValue(tree: *const Ast, start_node: Ast.Node.In
             .tagged_union_enum_tag_trailing,
             .@"asm",
             .asm_simple,
+            .asm_legacy,
             .add,
             .add_wrap,
             .add_sat,
@@ -10813,6 +10802,7 @@ fn nodeImpliesComptimeOnly(tree: *const Ast, start_node: Ast.Node.Index) bool {
             .tagged_union_enum_tag_trailing,
             .@"asm",
             .asm_simple,
+            .asm_legacy,
             .add,
             .add_wrap,
             .add_sat,
@@ -12806,7 +12796,7 @@ const GenZir = struct {
             is_volatile: bool,
             outputs: []const Zir.Inst.Asm.Output,
             inputs: []const Zir.Inst.Asm.Input,
-            clobbers: []const u32,
+            clobbers: Zir.Inst.Ref,
         },
     ) !Zir.Inst.Ref {
         const astgen = gz.astgen;
@@ -12816,13 +12806,13 @@ const GenZir = struct {
         try astgen.instructions.ensureUnusedCapacity(gpa, 1);
         try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Asm).@"struct".fields.len +
             args.outputs.len * @typeInfo(Zir.Inst.Asm.Output).@"struct".fields.len +
-            args.inputs.len * @typeInfo(Zir.Inst.Asm.Input).@"struct".fields.len +
-            args.clobbers.len);
+            args.inputs.len * @typeInfo(Zir.Inst.Asm.Input).@"struct".fields.len);
 
         const payload_index = gz.astgen.addExtraAssumeCapacity(Zir.Inst.Asm{
             .src_node = gz.nodeIndexToRelative(args.node),
             .asm_source = args.asm_source,
             .output_type_bits = args.output_type_bits,
+            .clobbers = args.clobbers,
         });
         for (args.outputs) |output| {
             _ = gz.astgen.addExtraAssumeCapacity(output);
@@ -12830,23 +12820,19 @@ const GenZir = struct {
         for (args.inputs) |input| {
             _ = gz.astgen.addExtraAssumeCapacity(input);
         }
-        gz.astgen.extra.appendSliceAssumeCapacity(args.clobbers);
 
-        //  * 0b00000000_0000XXXX - `outputs_len`.
-        //  * 0b0000000X_XXXX0000 - `inputs_len`.
-        //  * 0b0XXXXXX0_00000000 - `clobbers_len`.
-        //  * 0bX0000000_00000000 - is volatile
-        const small: u16 = @as(u16, @as(u4, @intCast(args.outputs.len))) << 0 |
-            @as(u16, @as(u5, @intCast(args.inputs.len))) << 4 |
-            @as(u16, @as(u6, @intCast(args.clobbers.len))) << 9 |
-            @as(u16, @intFromBool(args.is_volatile)) << 15;
+        const small: Zir.Inst.Asm.Small = .{
+            .outputs_len = @intCast(args.outputs.len),
+            .inputs_len = @intCast(args.inputs.len),
+            .is_volatile = args.is_volatile,
+        };
 
         const new_index: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
         astgen.instructions.appendAssumeCapacity(.{
             .tag = .extended,
             .data = .{ .extended = .{
                 .opcode = args.tag,
-                .small = small,
+                .small = @bitCast(small),
                 .operand = payload_index,
             } },
         });
lib/std/zig/AstRlAnnotate.zig
@@ -310,6 +310,7 @@ fn expr(astrl: *AstRlAnnotate, node: Ast.Node.Index, block: ?*Block, ri: ResultI
         .unreachable_literal,
         .asm_simple,
         .@"asm",
+        .asm_legacy,
         .enum_literal,
         .error_value,
         .anyframe_literal,
lib/std/zig/Parse.zig
@@ -2801,7 +2801,7 @@ fn expectSwitchSuffix(p: *Parse, main_token: TokenIndex) !Node.Index {
 ///
 /// AsmInput <- COLON AsmInputList AsmClobbers?
 ///
-/// AsmClobbers <- COLON StringList
+/// AsmClobbers <- COLON Expr
 ///
 /// StringList <- (STRINGLITERAL COMMA)* STRINGLITERAL?
 ///
@@ -2841,7 +2841,8 @@ fn expectAsmExpr(p: *Parse) !Node.Index {
             else => try p.warnExpected(.comma),
         }
     }
-    if (p.eatToken(.colon)) |_| {
+
+    const clobbers: Node.OptionalIndex = if (p.eatToken(.colon)) |_| clobbers: {
         while (true) {
             const input_item = try p.parseAsmInputItem() orelse break;
             try p.scratch.append(p.gpa, input_item);
@@ -2853,7 +2854,11 @@ fn expectAsmExpr(p: *Parse) !Node.Index {
                 else => try p.warnExpected(.comma),
             }
         }
-        if (p.eatToken(.colon)) |_| {
+
+        _ = p.eatToken(.colon) orelse break :clobbers .none;
+
+        // For automatic upgrades; delete after 0.15.0 released.
+        if (p.tokenTag(p.tok_i) == .string_literal) {
             while (p.eatToken(.string_literal)) |_| {
                 switch (p.tokenTag(p.tok_i)) {
                     .comma => p.tok_i += 1,
@@ -2862,8 +2867,25 @@ fn expectAsmExpr(p: *Parse) !Node.Index {
                     else => try p.warnExpected(.comma),
                 }
             }
+            const rparen = try p.expectToken(.r_paren);
+            const span = try p.listToSpan(p.scratch.items[scratch_top..]);
+            return p.addNode(.{
+                .tag = .asm_legacy,
+                .main_token = asm_token,
+                .data = .{ .node_and_extra = .{
+                    template,
+                    try p.addExtra(Node.AsmLegacy{
+                        .items_start = span.start,
+                        .items_end = span.end,
+                        .rparen = rparen,
+                    }),
+                } },
+            });
         }
-    }
+
+        break :clobbers (try p.expectExpr()).toOptional();
+    } else .none;
+
     const rparen = try p.expectToken(.r_paren);
     const span = try p.listToSpan(p.scratch.items[scratch_top..]);
     return p.addNode(.{
@@ -2874,6 +2896,7 @@ fn expectAsmExpr(p: *Parse) !Node.Index {
             try p.addExtra(Node.Asm{
                 .items_start = span.start,
                 .items_end = span.end,
+                .clobbers = clobbers,
                 .rparen = rparen,
             }),
         } },
lib/std/zig/render.zig
@@ -852,6 +852,9 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
         .@"asm",
         => return renderAsm(r, tree.fullAsm(node).?, space),
 
+        // To be removed after 0.15.0 is tagged
+        .asm_legacy => return renderAsmLegacy(r, tree.legacyAsm(node).?, space),
+
         .enum_literal => {
             try renderToken(r, tree.nodeMainToken(node) - 1, .none); // .
             return renderIdentifier(r, tree.nodeMainToken(node), space, .eagerly_unquote); // name
@@ -2363,9 +2366,9 @@ fn renderContainerDecl(
     return renderToken(r, rbrace, space); // rbrace
 }
 
-fn renderAsm(
+fn renderAsmLegacy(
     r: *Render,
-    asm_node: Ast.full.Asm,
+    asm_node: Ast.full.AsmLegacy,
     space: Space,
 ) Error!void {
     const tree = r.tree;
@@ -2391,12 +2394,17 @@ fn renderAsm(
             try renderToken(r, first_clobber - 2, .none);
             try renderToken(r, first_clobber - 1, .space);
 
+            try ais.writer().writeAll(".{ ");
+
             var tok_i = first_clobber;
             while (true) : (tok_i += 1) {
-                try renderToken(r, tok_i, .none);
+                try ais.writer().writeAll(".@");
+                try ais.writer().writeAll(tokenSliceForRender(tree, tok_i));
+
                 tok_i += 1;
                 switch (tree.tokenTag(tok_i)) {
                     .r_paren => {
+                        try ais.writer().writeAll(" }");
                         ais.popIndent();
                         return renderToken(r, tok_i, space);
                     },
@@ -2412,10 +2420,7 @@ fn renderAsm(
                 }
             }
         } else {
-            // asm ("foo")
-            try renderExpression(r, asm_node.ast.template, .none);
-            ais.popIndent();
-            return renderToken(r, asm_node.ast.rparen, space); // rparen
+            unreachable;
         }
     }
 
@@ -2499,13 +2504,18 @@ fn renderAsm(
     };
 
     try renderToken(r, colon3, .space); // :
+    try ais.writer().writeAll(".{ ");
     const first_clobber = asm_node.first_clobber.?;
     var tok_i = first_clobber;
     while (true) {
         switch (tree.tokenTag(tok_i + 1)) {
             .r_paren => {
                 ais.setIndentDelta(indent_delta);
-                try renderToken(r, tok_i, .newline);
+                try ais.writer().writeAll(".@");
+                const lexeme = tokenSliceForRender(tree, tok_i);
+                try ais.writer().writeAll(lexeme);
+                try ais.writer().writeAll(" }");
+                try renderSpace(r, tok_i, lexeme.len, .newline);
                 ais.popIndent();
                 return renderToken(r, tok_i + 1, space);
             },
@@ -2513,12 +2523,17 @@ fn renderAsm(
                 switch (tree.tokenTag(tok_i + 2)) {
                     .r_paren => {
                         ais.setIndentDelta(indent_delta);
-                        try renderToken(r, tok_i, .newline);
+                        try ais.writer().writeAll(".@");
+                        const lexeme = tokenSliceForRender(tree, tok_i);
+                        try ais.writer().writeAll(lexeme);
+                        try ais.writer().writeAll(" }");
+                        try renderSpace(r, tok_i, lexeme.len, .newline);
                         ais.popIndent();
                         return renderToken(r, tok_i + 2, space);
                     },
                     else => {
-                        try renderToken(r, tok_i, .none);
+                        try ais.writer().writeAll(".@");
+                        try ais.writer().writeAll(tokenSliceForRender(tree, tok_i));
                         try renderToken(r, tok_i + 1, .space);
                         tok_i += 2;
                     },
@@ -2529,6 +2544,131 @@ fn renderAsm(
     }
 }
 
+fn renderAsm(
+    r: *Render,
+    asm_node: Ast.full.Asm,
+    space: Space,
+) Error!void {
+    const tree = r.tree;
+    const ais = r.ais;
+
+    try renderToken(r, asm_node.ast.asm_token, .space); // asm
+
+    if (asm_node.volatile_token) |volatile_token| {
+        try renderToken(r, volatile_token, .space); // volatile
+        try renderToken(r, volatile_token + 1, .none); // lparen
+    } else {
+        try renderToken(r, asm_node.ast.asm_token + 1, .none); // lparen
+    }
+
+    if (asm_node.ast.items.len == 0) {
+        try ais.forcePushIndent(.normal);
+        if (asm_node.ast.clobbers.unwrap()) |clobbers| {
+            // asm ("foo" ::: clobbers)
+            try renderExpression(r, asm_node.ast.template, .space);
+            // Render the three colons.
+            const first_clobber = tree.firstToken(clobbers);
+            try renderToken(r, first_clobber - 3, .none);
+            try renderToken(r, first_clobber - 2, .none);
+            try renderToken(r, first_clobber - 1, .space);
+            try renderExpression(r, clobbers, .none);
+            ais.popIndent();
+            return renderToken(r, asm_node.ast.rparen, space); // rparen
+        }
+
+        // asm ("foo")
+        try renderExpression(r, asm_node.ast.template, .none);
+        ais.popIndent();
+        return renderToken(r, asm_node.ast.rparen, space); // rparen
+    }
+
+    try ais.forcePushIndent(.normal);
+    try renderExpression(r, asm_node.ast.template, .newline);
+    ais.setIndentDelta(asm_indent_delta);
+    const colon1 = tree.lastToken(asm_node.ast.template) + 1;
+
+    const colon2 = if (asm_node.outputs.len == 0) colon2: {
+        try renderToken(r, colon1, .newline); // :
+        break :colon2 colon1 + 1;
+    } else colon2: {
+        try renderToken(r, colon1, .space); // :
+
+        try ais.forcePushIndent(.normal);
+        for (asm_node.outputs, 0..) |asm_output, i| {
+            if (i + 1 < asm_node.outputs.len) {
+                const next_asm_output = asm_node.outputs[i + 1];
+                try renderAsmOutput(r, asm_output, .none);
+
+                const comma = tree.firstToken(next_asm_output) - 1;
+                try renderToken(r, comma, .newline); // ,
+                try renderExtraNewlineToken(r, tree.firstToken(next_asm_output));
+            } else if (asm_node.inputs.len == 0 and asm_node.ast.clobbers == .none) {
+                try ais.pushSpace(.comma);
+                try renderAsmOutput(r, asm_output, .comma);
+                ais.popSpace();
+                ais.popIndent();
+                ais.setIndentDelta(indent_delta);
+                ais.popIndent();
+                return renderToken(r, asm_node.ast.rparen, space); // rparen
+            } else {
+                try ais.pushSpace(.comma);
+                try renderAsmOutput(r, asm_output, .comma);
+                ais.popSpace();
+                const comma_or_colon = tree.lastToken(asm_output) + 1;
+                ais.popIndent();
+                break :colon2 switch (tree.tokenTag(comma_or_colon)) {
+                    .comma => comma_or_colon + 1,
+                    else => comma_or_colon,
+                };
+            }
+        } else unreachable;
+    };
+
+    const colon3 = if (asm_node.inputs.len == 0) colon3: {
+        try renderToken(r, colon2, .newline); // :
+        break :colon3 colon2 + 1;
+    } else colon3: {
+        try renderToken(r, colon2, .space); // :
+        try ais.forcePushIndent(.normal);
+        for (asm_node.inputs, 0..) |asm_input, i| {
+            if (i + 1 < asm_node.inputs.len) {
+                const next_asm_input = asm_node.inputs[i + 1];
+                try renderAsmInput(r, asm_input, .none);
+
+                const first_token = tree.firstToken(next_asm_input);
+                try renderToken(r, first_token - 1, .newline); // ,
+                try renderExtraNewlineToken(r, first_token);
+            } else if (asm_node.ast.clobbers == .none) {
+                try ais.pushSpace(.comma);
+                try renderAsmInput(r, asm_input, .comma);
+                ais.popSpace();
+                ais.popIndent();
+                ais.setIndentDelta(indent_delta);
+                ais.popIndent();
+                return renderToken(r, asm_node.ast.rparen, space); // rparen
+            } else {
+                try ais.pushSpace(.comma);
+                try renderAsmInput(r, asm_input, .comma);
+                ais.popSpace();
+                const comma_or_colon = tree.lastToken(asm_input) + 1;
+                ais.popIndent();
+                break :colon3 switch (tree.tokenTag(comma_or_colon)) {
+                    .comma => comma_or_colon + 1,
+                    else => comma_or_colon,
+                };
+            }
+        }
+        unreachable;
+    };
+
+    try renderToken(r, colon3, .space); // :
+    const clobbers = asm_node.ast.clobbers.unwrap().?;
+    try renderExpression(r, clobbers, .none);
+    ais.setIndentDelta(indent_delta);
+    ais.popIndent();
+    return renderToken(r, asm_node.ast.rparen, space); // rparen
+}
+
 fn renderCall(
     r: *Render,
     call: Ast.full.Call,
lib/std/zig/Zir.zig
@@ -1939,11 +1939,6 @@ pub const Inst = struct {
         /// `operand` is payload index to `BinNode`.
         builtin_extern,
         /// Inline assembly.
-        /// `small`:
-        ///  * 0b00000000_000XXXXX - `outputs_len`.
-        ///  * 0b000000XX_XXX00000 - `inputs_len`.
-        ///  * 0b0XXXXX00_00000000 - `clobbers_len`.
-        ///  * 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
@@ -2495,7 +2490,6 @@ pub const Inst = struct {
     /// Trailing:
     /// 0. Output for every outputs_len
     /// 1. Input for every inputs_len
-    /// 2. clobber: NullTerminatedString // index into string_bytes (null terminated) for every clobbers_len.
     pub const Asm = struct {
         src_node: Ast.Node.Offset,
         // null-terminated string index
@@ -2505,6 +2499,13 @@ pub const Inst = struct {
         ///   0b1 - operand is a type; asm expression has the output as the result.
         /// 0b0X is the first output, 0bX0 is the second, etc.
         output_type_bits: u32,
+        clobbers: Ref,
+
+        pub const Small = packed struct(u16) {
+            is_volatile: bool,
+            outputs_len: u7,
+            inputs_len: u8,
+        };
 
         pub const Output = struct {
             /// index into string_bytes (null terminated)
@@ -3482,6 +3483,7 @@ pub const Inst = struct {
         extern_options,
         type_info,
         branch_hint,
+        clobbers,
         // Values
         calling_convention_c,
         calling_convention_inline,
lib/std/zig/ZonGen.zig
@@ -227,7 +227,7 @@ fn expr(zg: *ZonGen, node: Ast.Node.Index, dest_node: Zoir.Node.Index) Allocator
         => try zg.addErrorNode(node, "control flow is not allowed in ZON", .{}),
 
         .@"comptime" => try zg.addErrorNode(node, "keyword 'comptime' is not allowed in ZON", .{}),
-        .asm_simple, .@"asm" => try zg.addErrorNode(node, "inline asm is not allowed in ZON", .{}),
+        .asm_simple, .@"asm", .asm_legacy => try zg.addErrorNode(node, "inline asm is not allowed in ZON", .{}),
 
         .builtin_call_two,
         .builtin_call_two_comma,
lib/std/builtin.zig
@@ -1,6 +1,10 @@
 //! Types and values provided by the Zig language.
 
 const builtin = @import("builtin");
+const std = @import("std.zig");
+const root = @import("root");
+
+pub const assembly = @import("builtin/assembly.zig");
 
 /// `explicit_subsystem` is missing when the subsystem is automatically detected,
 /// so Zig standard library has the subsystem detection logic here. This should generally be
@@ -1100,6 +1104,3 @@ pub noinline fn returnError() void {
         st.instruction_addresses[st.index] = @returnAddress();
     st.index += 1;
 }
-
-const std = @import("std.zig");
-const root = @import("root");
lib/std/zig.zig
@@ -740,6 +740,7 @@ pub const SimpleComptimeReason = enum(u32) {
     generic_call_target,
     wasm_memory_index,
     work_group_dim_index,
+    clobber,
 
     // Evaluating at comptime because types must be comptime-known.
     // Reasons other than `.type` are just more specific messages.
@@ -820,6 +821,7 @@ pub const SimpleComptimeReason = enum(u32) {
             .generic_call_target  => "generic function being called must be comptime-known",
             .wasm_memory_index    => "wasm memory index must be comptime-known",
             .work_group_dim_index => "work group dimension index must be comptime-known",
+            .clobber              => "clobber must be comptime-known",
 
             .type                => "types must be comptime-known",
             .array_sentinel      => "array sentinel value must be comptime-known",
src/Air/Liveness/Verify.zig
@@ -366,16 +366,11 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
             .assembly => {
                 const ty_pl = data[@intFromEnum(inst)].ty_pl;
                 const extra = self.air.extraData(Air.Asm, ty_pl.payload);
+                const outputs_len = extra.data.flags.outputs_len;
                 var extra_i = extra.end;
-                const outputs = @as(
-                    []const Air.Inst.Ref,
-                    @ptrCast(self.air.extra.items[extra_i..][0..extra.data.outputs_len]),
-                );
+                const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]);
                 extra_i += outputs.len;
-                const inputs = @as(
-                    []const Air.Inst.Ref,
-                    @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]),
-                );
+                const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]);
                 extra_i += inputs.len;
 
                 var bt = self.liveness.iterateBigTomb(inst);
src/Air/Liveness.zig
@@ -1208,8 +1208,9 @@ fn analyzeInst(
 
         .assembly => {
             const extra = a.air.extraData(Air.Asm, inst_datas[@intFromEnum(inst)].ty_pl.payload);
+            const outputs_len = extra.data.flags.outputs_len;
             var extra_i: usize = extra.end;
-            const outputs = @as([]const Air.Inst.Ref, @ptrCast(a.air.extra.items[extra_i..][0..extra.data.outputs_len]));
+            const outputs = @as([]const Air.Inst.Ref, @ptrCast(a.air.extra.items[extra_i..][0..outputs_len]));
             extra_i += outputs.len;
             const inputs = @as([]const Air.Inst.Ref, @ptrCast(a.air.extra.items[extra_i..][0..extra.data.inputs_len]));
             extra_i += inputs.len;
src/Air/print.zig
@@ -1,5 +1,6 @@
 const std = @import("std");
 const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
 
 const build_options = @import("build_options");
 const Zcu = @import("../Zcu.zig");
@@ -9,7 +10,7 @@ const Air = @import("../Air.zig");
 const InternPool = @import("../InternPool.zig");
 
 pub fn write(air: Air, stream: *std.io.Writer, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
-    comptime std.debug.assert(build_options.enable_debug_extensions);
+    comptime assert(build_options.enable_debug_extensions);
     const instruction_bytes = air.instructions.len *
         // Here we don't use @sizeOf(Air.Inst.Data) because it would include
         // the debug safety tag but we want to measure release size.
@@ -59,7 +60,7 @@ pub fn writeInst(
     pt: Zcu.PerThread,
     liveness: ?Air.Liveness,
 ) void {
-    comptime std.debug.assert(build_options.enable_debug_extensions);
+    comptime assert(build_options.enable_debug_extensions);
     var writer: Writer = .{
         .pt = pt,
         .gpa = pt.zcu.gpa,
@@ -643,8 +644,8 @@ const Writer = struct {
     fn writeAssembly(w: *Writer, s: *std.io.Writer, inst: Air.Inst.Index) Error!void {
         const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
         const extra = w.air.extraData(Air.Asm, ty_pl.payload);
-        const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
-        const clobbers_len = @as(u31, @truncate(extra.data.flags));
+        const is_volatile = extra.data.flags.is_volatile;
+        const outputs_len = extra.data.flags.outputs_len;
         var extra_i: usize = extra.end;
         var op_index: usize = 0;
 
@@ -655,7 +656,7 @@ const Writer = struct {
             try s.writeAll(", volatile");
         }
 
-        const outputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..extra.data.outputs_len]));
+        const outputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..outputs_len]));
         extra_i += outputs.len;
         const inputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..extra.data.inputs_len]));
         extra_i += inputs.len;
@@ -695,19 +696,35 @@ const Writer = struct {
             try s.writeByte(')');
         }
 
-        {
-            var clobber_i: u32 = 0;
-            while (clobber_i < clobbers_len) : (clobber_i += 1) {
-                const extra_bytes = std.mem.sliceAsBytes(w.air.extra.items[extra_i..]);
-                const clobber = std.mem.sliceTo(extra_bytes, 0);
-                // This equation accounts for the fact that even if we have exactly 4 bytes
-                // for the string, we still use the next u32 for the null terminator.
-                extra_i += clobber.len / 4 + 1;
-
-                try s.writeAll(", ~{");
-                try s.writeAll(clobber);
-                try s.writeAll("}");
-            }
+        const zcu = w.pt.zcu;
+        const ip = &zcu.intern_pool;
+        const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
+        const struct_type: Type = .fromInterned(aggregate.ty);
+        switch (aggregate.storage) {
+            .elems => |elems| for (elems, 0..) |elem, i| {
+                switch (elem) {
+                    .bool_true => {
+                        const clobber = struct_type.structFieldName(i, zcu).toSlice(ip).?;
+                        assert(clobber.len != 0);
+                        try s.writeAll(", ~{");
+                        try s.writeAll(clobber);
+                        try s.writeAll("}");
+                    },
+                    .bool_false => continue,
+                    else => unreachable,
+                }
+            },
+            .repeated_elem => |elem| {
+                try s.writeAll(", ");
+                try s.writeAll(switch (elem) {
+                    .bool_true => "<all clobbers>",
+                    .bool_false => "<no clobbers>",
+                    else => unreachable,
+                });
+            },
+            .bytes => |bytes| {
+                try s.print(", {x}", .{bytes});
+            },
         }
         const asm_source = std.mem.sliceAsBytes(w.air.extra.items[extra_i..])[0..extra.data.source_len];
         try s.print(", \"{f}\"", .{std.zig.fmtString(asm_source)});
src/Air/types_resolved.zig
@@ -416,8 +416,9 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
                 if (!checkType(data.ty_pl.ty.toType(), zcu)) return false;
                 // Luckily, we only care about the inputs and outputs, so we don't have to do
                 // the whole null-terminated string dance.
-                const outputs: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end..][0..extra.data.outputs_len]);
-                const inputs: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end + extra.data.outputs_len ..][0..extra.data.inputs_len]);
+                const outputs_len = extra.data.flags.outputs_len;
+                const outputs: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end..][0..outputs_len]);
+                const inputs: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end + outputs_len ..][0..extra.data.inputs_len]);
                 for (outputs) |output| if (output != .none and !checkRef(output, zcu)) return false;
                 for (inputs) |input| if (input != .none and !checkRef(input, zcu)) return false;
             },
src/arch/riscv64/CodeGen.zig
@@ -6047,10 +6047,10 @@ fn airBoolOp(func: *Func, inst: Air.Inst.Index) !void {
 fn airAsm(func: *Func, inst: Air.Inst.Index) !void {
     const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = func.air.extraData(Air.Asm, ty_pl.payload);
-    const clobbers_len: u31 = @truncate(extra.data.flags);
+    const outputs_len = extra.data.flags.outputs_len;
     var extra_i: usize = extra.end;
     const outputs: []const Air.Inst.Ref =
-        @ptrCast(func.air.extra.items[extra_i..][0..extra.data.outputs_len]);
+        @ptrCast(func.air.extra.items[extra_i..][0..outputs_len]);
     extra_i += outputs.len;
     const inputs: []const Air.Inst.Ref = @ptrCast(func.air.extra.items[extra_i..][0..extra.data.inputs_len]);
     extra_i += inputs.len;
@@ -6161,21 +6161,33 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void {
         args.appendAssumeCapacity(arg_mcv);
     }
 
-    {
-        var clobber_i: u32 = 0;
-        while (clobber_i < clobbers_len) : (clobber_i += 1) {
-            const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(func.air.extra.items[extra_i..]), 0);
-            // This equation accounts for the fact that even if we have exactly 4 bytes
-            // for the string, we still use the next u32 for the null terminator.
-            extra_i += clobber.len / 4 + 1;
-
-            if (std.mem.eql(u8, clobber, "") or std.mem.eql(u8, clobber, "memory")) {
-                // nothing really to do
-            } else {
-                try func.register_manager.getReg(parseRegName(clobber) orelse
-                    return func.fail("invalid clobber: '{s}'", .{clobber}), null);
+    const zcu = func.pt.zcu;
+    const ip = &zcu.intern_pool;
+    const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
+    const struct_type: Type = .fromInterned(aggregate.ty);
+    switch (aggregate.storage) {
+        .elems => |elems| for (elems, 0..) |elem, i| {
+            switch (elem) {
+                .bool_true => {
+                    const clobber = struct_type.structFieldName(i, zcu).toSlice(ip).?;
+                    assert(clobber.len != 0);
+                    if (std.mem.eql(u8, clobber, "memory")) {
+                        // nothing really to do
+                    } else {
+                        try func.register_manager.getReg(parseRegName(clobber) orelse
+                            return func.fail("invalid clobber: '{s}'", .{clobber}), null);
+                    }
+                },
+                .bool_false => continue,
+                else => unreachable,
             }
-        }
+        },
+        .repeated_elem => |elem| switch (elem) {
+            .bool_true => @panic("TODO"),
+            .bool_false => {},
+            else => unreachable,
+        },
+        .bytes => @panic("TODO"),
     }
 
     const Label = struct {
src/arch/sparc64/CodeGen.zig
@@ -873,10 +873,10 @@ fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void {
 fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.Asm, ty_pl.payload);
-    const is_volatile = (extra.data.flags & 0x80000000) != 0;
-    const clobbers_len: u31 = @truncate(extra.data.flags);
+    const is_volatile = extra.data.flags.is_volatile;
+    const outputs_len = extra.data.flags.outputs_len;
     var extra_i: usize = extra.end;
-    const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i .. extra_i + extra.data.outputs_len]);
+    const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i .. extra_i + outputs_len]);
     extra_i += outputs.len;
     const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i .. extra_i + extra.data.inputs_len]);
     extra_i += inputs.len;
@@ -921,17 +921,8 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void {
             try self.genSetReg(self.typeOf(input), reg, arg_mcv);
         }
 
-        {
-            var clobber_i: u32 = 0;
-            while (clobber_i < clobbers_len) : (clobber_i += 1) {
-                const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
-                // This equation accounts for the fact that even if we have exactly 4 bytes
-                // for the string, we still use the next u32 for the null terminator.
-                extra_i += clobber.len / 4 + 1;
-
-                // TODO honor these
-            }
-        }
+        // TODO honor the clobbers
+        _ = extra.data.clobbers;
 
         const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len];
 
src/arch/x86_64/CodeGen.zig
@@ -179788,9 +179788,9 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void {
     const zcu = pt.zcu;
     const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = self.air.extraData(Air.Asm, ty_pl.payload);
-    const clobbers_len: u31 = @truncate(extra.data.flags);
+    const outputs_len = extra.data.flags.outputs_len;
     var extra_i: usize = extra.end;
-    const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.outputs_len]);
+    const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]);
     extra_i += outputs.len;
     const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]);
     extra_i += inputs.len;
@@ -179981,30 +179981,42 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void {
         args.appendAssumeCapacity(arg_mcv);
     }
 
-    {
-        var clobber_i: u32 = 0;
-        while (clobber_i < clobbers_len) : (clobber_i += 1) {
-            const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
-            // This equation accounts for the fact that even if we have exactly 4 bytes
-            // for the string, we still use the next u32 for the null terminator.
-            extra_i += clobber.len / 4 + 1;
-
-            if (std.mem.eql(u8, clobber, "") or std.mem.eql(u8, clobber, "memory") or
-                std.mem.eql(u8, clobber, "fpsr") or std.mem.eql(u8, clobber, "fpcr") or
-                std.mem.eql(u8, clobber, "mxcsr") or std.mem.eql(u8, clobber, "dirflag"))
-            {
-                // ok, sure
-            } else if (std.mem.eql(u8, clobber, "cc") or
-                std.mem.eql(u8, clobber, "flags") or
-                std.mem.eql(u8, clobber, "eflags") or
-                std.mem.eql(u8, clobber, "rflags"))
-            {
-                try self.spillEflagsIfOccupied();
-            } else {
-                try self.register_manager.getReg(parseRegName(clobber) orelse
-                    return self.fail("invalid clobber: '{s}'", .{clobber}), null);
-            }
-        }
+    const ip = &zcu.intern_pool;
+    const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
+    const struct_type: Type = .fromInterned(aggregate.ty);
+    switch (aggregate.storage) {
+        .elems => |elems| for (elems, 0..) |elem, i| switch (elem) {
+            .bool_true => {
+                const clobber = struct_type.structFieldName(i, zcu).toSlice(ip).?;
+                assert(clobber.len != 0);
+
+                if (std.mem.eql(u8, clobber, "memory") or
+                    std.mem.eql(u8, clobber, "fpsr") or
+                    std.mem.eql(u8, clobber, "fpcr") or
+                    std.mem.eql(u8, clobber, "mxcsr") or
+                    std.mem.eql(u8, clobber, "dirflag"))
+                {
+                    // ok, sure
+                } else if (std.mem.eql(u8, clobber, "cc") or
+                    std.mem.eql(u8, clobber, "flags") or
+                    std.mem.eql(u8, clobber, "eflags") or
+                    std.mem.eql(u8, clobber, "rflags"))
+                {
+                    try self.spillEflagsIfOccupied();
+                } else {
+                    try self.register_manager.getReg(parseRegName(clobber) orelse
+                        return self.fail("invalid clobber: '{s}'", .{clobber}), null);
+                }
+            },
+            .bool_false => continue,
+            else => unreachable,
+        },
+        .repeated_elem => |elem| switch (elem) {
+            .bool_true => @panic("TODO"),
+            .bool_false => {},
+            else => unreachable,
+        },
+        .bytes => @panic("TODO"),
     }
 
     const Label = struct {
src/codegen/c.zig
@@ -5545,11 +5545,11 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
     const zcu = pt.zcu;
     const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
     const extra = f.air.extraData(Air.Asm, ty_pl.payload);
-    const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
-    const clobbers_len: u31 = @truncate(extra.data.flags);
+    const is_volatile = extra.data.flags.is_volatile;
+    const outputs_len = extra.data.flags.outputs_len;
     const gpa = f.object.dg.gpa;
     var extra_i: usize = extra.end;
-    const outputs: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra_i..][0..extra.data.outputs_len]);
+    const outputs: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra_i..][0..outputs_len]);
     extra_i += outputs.len;
     const inputs: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra_i..][0..extra.data.inputs_len]);
     extra_i += inputs.len;
@@ -5645,12 +5645,6 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
                 try f.object.newline();
             }
         }
-        for (0..clobbers_len) |_| {
-            const clobber = mem.sliceTo(mem.sliceAsBytes(f.air.extra.items[extra_i..]), 0);
-            // This equation accounts for the fact that even if we have exactly 4 bytes
-            // for the string, we still use the next u32 for the null terminator.
-            extra_i += clobber.len / 4 + 1;
-        }
 
         {
             const asm_source = mem.sliceAsBytes(f.air.extra.items[extra_i..])[0..extra.data.source_len];
@@ -5757,17 +5751,28 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue {
             try w.writeByte(')');
         }
         try w.writeByte(':');
-        for (0..clobbers_len) |clobber_i| {
-            const clobber = mem.sliceTo(mem.sliceAsBytes(f.air.extra.items[extra_i..]), 0);
-            // This equation accounts for the fact that even if we have exactly 4 bytes
-            // for the string, we still use the next u32 for the null terminator.
-            extra_i += clobber.len / 4 + 1;
-
-            if (clobber.len == 0) continue;
-
-            if (clobber_i > 0) try w.writeByte(',');
-            try w.print(" {f}", .{fmtStringLiteral(clobber, null)});
+        const ip = &zcu.intern_pool;
+        const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
+        const struct_type: Type = .fromInterned(aggregate.ty);
+        switch (aggregate.storage) {
+            .elems => |elems| for (elems, 0..) |elem, i| switch (elem) {
+                .bool_true => {
+                    const name = struct_type.structFieldName(i, zcu).toSlice(ip).?;
+                    assert(name.len != 0);
+                    try w.print(" {f}", .{fmtStringLiteral(name, null)});
+                    (try w.writableArray(1))[0] = ',';
+                },
+                .bool_false => continue,
+                else => unreachable,
+            },
+            .repeated_elem => |elem| switch (elem) {
+                .bool_true => @panic("TODO"),
+                .bool_false => {},
+                else => unreachable,
+            },
+            .bytes => @panic("TODO"),
         }
+        w.undo(1); // erase the last comma
         try w.writeAll(");");
         try f.object.newline();
 
src/codegen/llvm.zig
@@ -7241,19 +7241,20 @@ pub const FuncGen = struct {
         const o = self.ng.object;
         const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
         const extra = self.air.extraData(Air.Asm, ty_pl.payload);
-        const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
-        const clobbers_len: u31 = @truncate(extra.data.flags);
+        const is_volatile = extra.data.flags.is_volatile;
+        const outputs_len = extra.data.flags.outputs_len;
+        const gpa = self.gpa;
         var extra_i: usize = extra.end;
 
-        const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.outputs_len]);
+        const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]);
         extra_i += outputs.len;
         const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]);
         extra_i += inputs.len;
 
         var llvm_constraints: std.ArrayListUnmanaged(u8) = .empty;
-        defer llvm_constraints.deinit(self.gpa);
+        defer llvm_constraints.deinit(gpa);
 
-        var arena_allocator = std.heap.ArenaAllocator.init(self.gpa);
+        var arena_allocator = std.heap.ArenaAllocator.init(gpa);
         defer arena_allocator.deinit();
         const arena = arena_allocator.allocator();
 
@@ -7290,7 +7291,7 @@ pub const FuncGen = struct {
             // for the string, we still use the next u32 for the null terminator.
             extra_i += (constraint.len + name.len + (2 + 3)) / 4;
 
-            try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 3);
+            try llvm_constraints.ensureUnusedCapacity(gpa, constraint.len + 3);
             if (total_i != 0) {
                 llvm_constraints.appendAssumeCapacity(',');
             }
@@ -7399,7 +7400,7 @@ pub const FuncGen = struct {
                 }
             }
 
-            try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 1);
+            try llvm_constraints.ensureUnusedCapacity(gpa, constraint.len + 1);
             if (total_i != 0) {
                 llvm_constraints.appendAssumeCapacity(',');
             }
@@ -7456,7 +7457,7 @@ pub const FuncGen = struct {
                 llvm_param_types[llvm_param_i] = llvm_elem_ty;
             }
 
-            try llvm_constraints.print(self.gpa, ",{d}", .{output_index});
+            try llvm_constraints.print(gpa, ",{d}", .{output_index});
 
             // In the case of indirect inputs, LLVM requires the callsite to have
             // an elementtype(<ty>) attribute.
@@ -7466,24 +7467,41 @@ pub const FuncGen = struct {
             total_i += 1;
         }
 
-        {
-            var clobber_i: u32 = 0;
-            while (clobber_i < clobbers_len) : (clobber_i += 1) {
-                const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
-                // This equation accounts for the fact that even if we have exactly 4 bytes
-                // for the string, we still use the next u32 for the null terminator.
-                extra_i += clobber.len / 4 + 1;
-
-                try llvm_constraints.ensureUnusedCapacity(self.gpa, clobber.len + 4);
-                if (total_i != 0) {
-                    llvm_constraints.appendAssumeCapacity(',');
+        const ip = &zcu.intern_pool;
+        const aggregate = ip.indexToKey(extra.data.clobbers).aggregate;
+        const struct_type: Type = .fromInterned(aggregate.ty);
+        switch (aggregate.storage) {
+            .elems => |elems| for (elems, 0..) |elem, i| {
+                switch (elem) {
+                    .bool_true => {
+                        const name = struct_type.structFieldName(i, zcu).toSlice(ip).?;
+                        try llvm_constraints.ensureUnusedCapacity(gpa, name.len + 4);
+                        if (total_i != 0) llvm_constraints.appendAssumeCapacity(',');
+                        llvm_constraints.appendSliceAssumeCapacity("~{");
+                        llvm_constraints.appendSliceAssumeCapacity(name);
+                        llvm_constraints.appendSliceAssumeCapacity("}");
+
+                        total_i += 1;
+                    },
+                    .bool_false => continue,
+                    else => unreachable,
                 }
-                llvm_constraints.appendSliceAssumeCapacity("~{");
-                llvm_constraints.appendSliceAssumeCapacity(clobber);
-                llvm_constraints.appendSliceAssumeCapacity("}");
+            },
+            .repeated_elem => |elem| switch (elem) {
+                .bool_true => for (0..struct_type.structFieldCount(zcu)) |i| {
+                    const name = struct_type.structFieldName(i, zcu).toSlice(ip).?;
+                    try llvm_constraints.ensureUnusedCapacity(gpa, name.len + 4);
+                    if (total_i != 0) llvm_constraints.appendAssumeCapacity(',');
+                    llvm_constraints.appendSliceAssumeCapacity("~{");
+                    llvm_constraints.appendSliceAssumeCapacity(name);
+                    llvm_constraints.appendSliceAssumeCapacity("}");
 
-                total_i += 1;
-            }
+                    total_i += 1;
+                },
+                .bool_false => {},
+                else => unreachable,
+            },
+            .bytes => @panic("TODO"),
         }
 
         // We have finished scanning through all inputs/outputs, so the number of
@@ -7497,13 +7515,13 @@ pub const FuncGen = struct {
         // to be buggy and regress often.
         switch (target.cpu.arch) {
             .x86_64, .x86 => {
-                if (total_i != 0) try llvm_constraints.append(self.gpa, ',');
-                try llvm_constraints.appendSlice(self.gpa, "~{dirflag},~{fpsr},~{flags}");
+                if (total_i != 0) try llvm_constraints.append(gpa, ',');
+                try llvm_constraints.appendSlice(gpa, "~{dirflag},~{fpsr},~{flags}");
                 total_i += 3;
             },
             .mips, .mipsel, .mips64, .mips64el => {
-                if (total_i != 0) try llvm_constraints.append(self.gpa, ',');
-                try llvm_constraints.appendSlice(self.gpa, "~{$1}");
+                if (total_i != 0) try llvm_constraints.append(gpa, ',');
+                try llvm_constraints.appendSlice(gpa, "~{$1}");
                 total_i += 1;
             },
             else => {},
@@ -7512,7 +7530,7 @@ pub const FuncGen = struct {
         const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len];
 
         // hackety hacks until stage2 has proper inline asm in the frontend.
-        var rendered_template = std.ArrayList(u8).init(self.gpa);
+        var rendered_template = std.ArrayList(u8).init(gpa);
         defer rendered_template.deinit();
 
         const State = enum { start, percent, input, modifier };
src/codegen/spirv.zig
@@ -6387,13 +6387,13 @@ const NavGen = struct {
         const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
         const extra = self.air.extraData(Air.Asm, ty_pl.payload);
 
-        const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
-        const clobbers_len: u31 = @truncate(extra.data.flags);
+        const is_volatile = extra.data.flags.is_volatile;
+        const outputs_len = extra.data.flags.outputs_len;
 
         if (!is_volatile and self.liveness.isUnused(inst)) return null;
 
         var extra_i: usize = extra.end;
-        const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.outputs_len]);
+        const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]);
         extra_i += outputs.len;
         const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]);
         extra_i += inputs.len;
@@ -6402,7 +6402,7 @@ const NavGen = struct {
             return self.todo("implement inline asm with more than 1 output", .{});
         }
 
-        var as = SpvAssembler{
+        var as: SpvAssembler = .{
             .gpa = self.gpa,
             .spv = self.spv,
             .func = &self.func,
@@ -6486,14 +6486,8 @@ const NavGen = struct {
             }
         }
 
-        {
-            var clobber_i: u32 = 0;
-            while (clobber_i < clobbers_len) : (clobber_i += 1) {
-                const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0);
-                extra_i += clobber.len / 4 + 1;
-                // TODO: Record clobber and use it somewhere.
-            }
-        }
+        // TODO: do something with clobbers
+        _ = extra.data.clobbers;
 
         const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len];
 
src/Zcu/PerThread.zig
@@ -635,6 +635,7 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized
             .main => .Type,
             .panic => .panic,
             .va_list => .VaList,
+            .assembly => .assembly,
         };
         if (zcu.builtin_decl_values.get(to_check) != .none) return;
     }
src/Air.zig
@@ -1413,19 +1413,20 @@ pub const ShuffleTwoMask = enum(u32) {
 ///      terminated string.
 ///    - name: memory at this position is reinterpreted as a null
 ///      terminated string. pad to the next u32 after the null byte.
-/// 4. for every clobbers_len
-///    - clobber_name: memory at this position is reinterpreted as a null
-///      terminated string. pad to the next u32 after the null byte.
-/// 5. A number of u32 elements follow according to the equation `(source_len + 3) / 4`.
+/// 4. A number of u32 elements follow according to the equation `(source_len + 3) / 4`.
 ///    Memory starting at this position is reinterpreted as the source bytes.
 pub const Asm = struct {
     /// Length of the assembly source in bytes.
     source_len: u32,
-    outputs_len: u32,
     inputs_len: u32,
-    /// The MSB is `is_volatile`.
-    /// The rest of the bits are `clobbers_len`.
-    flags: u32,
+    /// A comptime `std.builtin.assembly.Clobbers` value for the target architecture.
+    clobbers: InternPool.Index,
+    flags: Flags,
+
+    pub const Flags = packed struct(u32) {
+        outputs_len: u31,
+        is_volatile: bool,
+    };
 };
 
 pub const Cmpxchg = struct {
@@ -1749,7 +1750,7 @@ pub fn extraData(air: Air, comptime T: type, index: usize) struct { data: T, end
         @field(result, field.name) = switch (field.type) {
             u32 => air.extra.items[i],
             InternPool.Index, Inst.Ref => @enumFromInt(air.extra.items[i]),
-            i32, CondBr.BranchHints => @bitCast(air.extra.items[i]),
+            i32, CondBr.BranchHints, Asm.Flags => @bitCast(air.extra.items[i]),
             else => @compileError("bad field type: " ++ @typeName(field.type)),
         };
         i += 1;
src/InternPool.zig
@@ -54,6 +54,7 @@ namespace_name_deps: std.AutoArrayHashMapUnmanaged(NamespaceNameKey, DepEntry.In
 memoized_state_main_deps: DepEntry.Index.Optional,
 memoized_state_panic_deps: DepEntry.Index.Optional,
 memoized_state_va_list_deps: DepEntry.Index.Optional,
+memoized_state_assembly_deps: DepEntry.Index.Optional,
 
 /// Given a `Depender`, points to an entry in `dep_entries` whose `depender`
 /// matches. The `next_dependee` field can be used to iterate all such entries
@@ -96,6 +97,7 @@ pub const empty: InternPool = .{
     .memoized_state_main_deps = .none,
     .memoized_state_panic_deps = .none,
     .memoized_state_va_list_deps = .none,
+    .memoized_state_assembly_deps = .none,
     .first_dependency = .empty,
     .dep_entries = .empty,
     .free_dep_entries = .empty,
@@ -458,6 +460,8 @@ pub const MemoizedStateStage = enum(u32) {
     panic,
     /// Specifically `std.builtin.VaList`. See `Zcu.BuiltinDecl.stage`.
     va_list,
+    /// Everything within `std.builtin.assembly`. See `Zcu.BuiltinDecl.stage`.
+    assembly,
 };
 
 pub const ComptimeUnit = extern struct {
@@ -880,6 +884,7 @@ pub fn dependencyIterator(ip: *const InternPool, dependee: Dependee) DependencyI
             .main => ip.memoized_state_main_deps.unwrap(),
             .panic => ip.memoized_state_panic_deps.unwrap(),
             .va_list => ip.memoized_state_va_list_deps.unwrap(),
+            .assembly => ip.memoized_state_assembly_deps.unwrap(),
         },
     } orelse return .{
         .ip = ip,
@@ -915,6 +920,7 @@ pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalUnit, depend
                 .main => &ip.memoized_state_main_deps,
                 .panic => &ip.memoized_state_panic_deps,
                 .va_list => &ip.memoized_state_va_list_deps,
+                .assembly => &ip.memoized_state_assembly_deps,
             };
 
             if (deps.unwrap()) |first| {
src/Sema.zig
@@ -16413,10 +16413,10 @@ fn zirAsm(
     const extra = sema.code.extraData(Zir.Inst.Asm, extended.operand);
     const src = block.nodeOffset(extra.data.src_node);
     const ret_ty_src = block.src(.{ .node_offset_asm_ret_ty = extra.data.src_node });
-    const outputs_len: u4 = @truncate(extended.small);
-    const inputs_len: u5 = @truncate(extended.small >> 4);
-    const clobbers_len: u6 = @truncate(extended.small >> 9);
-    const is_volatile = @as(u1, @truncate(extended.small >> 15)) != 0;
+    const small: Zir.Inst.Asm.Small = @bitCast(extended.small);
+    const outputs_len = small.outputs_len;
+    const inputs_len = small.inputs_len;
+    const is_volatile = small.is_volatile;
     const is_global_assembly = sema.func_index == .none;
     const zir_tags = sema.code.instructions.items(.tag);
 
@@ -16432,7 +16432,7 @@ fn zirAsm(
         if (inputs_len != 0) {
             return sema.fail(block, src, "module-level assembly does not support inputs", .{});
         }
-        if (clobbers_len != 0) {
+        if (extra.data.clobbers != .none) {
             return sema.fail(block, src, "module-level assembly does not support clobbers", .{});
         }
         if (is_volatile) {
@@ -16506,15 +16506,11 @@ fn zirAsm(
         inputs[arg_i] = .{ .c = constraint, .n = name };
     }
 
-    const clobbers = try sema.arena.alloc([]const u8, clobbers_len);
-    for (clobbers) |*name| {
-        const name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_i]);
-        name.* = sema.code.nullTerminatedString(name_index);
-        extra_i += 1;
-
-        needed_capacity += name.*.len / 4 + 1;
-    }
-
+    const clobbers = if (extra.data.clobbers == .none) empty: {
+        const clobbers_ty = try sema.getBuiltinType(src, .@"assembly.Clobbers");
+        break :empty try sema.structInitEmpty(block, clobbers_ty, src, src);
+    } else try sema.resolveInst(extra.data.clobbers); // Already coerced by AstGen.
+    const clobbers_val = try sema.resolveConstDefinedValue(block, src, clobbers, .{ .simple = .clobber });
     needed_capacity += (asm_source.len + 3) / 4;
 
     const gpa = sema.gpa;
@@ -16525,9 +16521,12 @@ fn zirAsm(
             .ty = expr_ty,
             .payload = sema.addExtraAssumeCapacity(Air.Asm{
                 .source_len = @intCast(asm_source.len),
-                .outputs_len = outputs_len,
                 .inputs_len = @intCast(args.len),
-                .flags = (@as(u32, @intFromBool(is_volatile)) << 31) | @as(u32, @intCast(clobbers.len)),
+                .clobbers = clobbers_val.toIntern(),
+                .flags = .{
+                    .is_volatile = is_volatile,
+                    .outputs_len = outputs_len,
+                },
             }),
         } },
     });
@@ -16549,12 +16548,6 @@ fn zirAsm(
         buffer[input.c.len + 1 + input.n.len] = 0;
         sema.air_extra.items.len += (input.c.len + input.n.len + (2 + 3)) / 4;
     }
-    for (clobbers) |clobber| {
-        const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice());
-        @memcpy(buffer[0..clobber.len], clobber);
-        buffer[clobber.len] = 0;
-        sema.air_extra.items.len += clobber.len / 4 + 1;
-    }
     {
         const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice());
         @memcpy(buffer[0..asm_source.len], asm_source);
@@ -26197,6 +26190,7 @@ fn zirBuiltinValue(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstD
         .extern_options     => try sema.getBuiltinType(src, .ExternOptions),
         .type_info          => try sema.getBuiltinType(src, .Type),
         .branch_hint        => try sema.getBuiltinType(src, .BranchHint),
+        .clobbers           => try sema.getBuiltinType(src, .@"assembly.Clobbers"),
         // zig fmt: on
 
         // Values are handled here.
@@ -36546,7 +36540,7 @@ fn payloadToExtraItems(data: anytype) [@typeInfo(@TypeOf(data)).@"struct".fields
     inline for (&result, fields) |*val, field| {
         val.* = switch (field.type) {
             u32 => @field(data, field.name),
-            i32, Air.CondBr.BranchHints => @bitCast(@field(data, field.name)),
+            i32, Air.CondBr.BranchHints, Air.Asm.Flags => @bitCast(@field(data, field.name)),
             Air.Inst.Ref, InternPool.Index => @intFromEnum(@field(data, field.name)),
             else => @compileError("bad field type: " ++ @typeName(field.type)),
         };
src/Zcu.zig
@@ -460,6 +460,9 @@ pub const BuiltinDecl = enum {
 
     VaList,
 
+    assembly,
+    @"assembly.Clobbers",
+
     /// Determines what kind of validation will be done to the decl's value.
     pub fn kind(decl: BuiltinDecl) enum { type, func, string } {
         return switch (decl) {
@@ -480,6 +483,8 @@ pub const BuiltinDecl = enum {
             .ExportOptions,
             .ExternOptions,
             .BranchHint,
+            .assembly,
+            .@"assembly.Clobbers",
             => .type,
 
             .Type,
@@ -540,6 +545,7 @@ pub const BuiltinDecl = enum {
     /// Resolution of these values is done in three distinct stages:
     /// * Resolution of `std.builtin.Panic` and everything under it
     /// * Resolution of `VaList`
+    /// * Resolution of `assembly`
     /// * Everything else
     ///
     /// Panics are separated because they are provided by the user, so must be able to use
@@ -548,14 +554,20 @@ pub const BuiltinDecl = enum {
     /// `VaList` is separate because its value depends on the target, so it needs some reflection
     /// machinery to work; additionally, it is `@compileError` on some targets, so must be referenced
     /// by itself.
+    ///
+    /// `assembly` is separate because its value depends on the target.
     pub fn stage(decl: BuiltinDecl) InternPool.MemoizedStateStage {
-        if (decl == .VaList) return .va_list;
-
-        if (@intFromEnum(decl) <= @intFromEnum(BuiltinDecl.@"Type.Declaration")) {
-            return .main;
-        } else {
-            return .panic;
-        }
+        return switch (decl) {
+            .VaList => .va_list,
+            .assembly, .@"assembly.Clobbers" => .assembly,
+            else => {
+                if (@intFromEnum(decl) <= @intFromEnum(BuiltinDecl.@"Type.Declaration")) {
+                    return .main;
+                } else {
+                    return .panic;
+                }
+            },
+        };
     }
 
     /// Based on the tag name, determines how to access this decl; either as a direct child of the