kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

native_asm.c (20401B)


      1 #include "cg/native_asm.h"
      2 
      3 #include <kit/asm_constraints.h>
      4 
      5 #include "arch/mc.h"
      6 #include "asm/asm.h"
      7 #include "asm/asm_lex.h"
      8 #include "cg/native_direct_target.h" /* NativeDirectTarget, for the direct binder */
      9 #include "cg/type.h"                 /* builtin_id */
     10 #include "core/arena.h"              /* arena_zarray */
     11 #include "core/pool.h"               /* pool_slice for native_asm_resolve_pin */
     12 
     13 void native_file_scope_asm(NativeTarget* t, const char* src, size_t len) {
     14   AsmLexer* lex = asm_lex_open_mem(t->c, "<file-scope-asm>", src, len);
     15   asm_parse(t->c, lex, t->mc);
     16   asm_lex_close(lex);
     17 }
     18 
     19 void native_finalize(NativeTarget* t) {
     20   if (t->mc) mc_emit_eh_frame(t->mc);
     21 }
     22 
     23 const char* native_asm_constraint_body(const char* s) {
     24   return kit_cg_asm_constraint_body(s);
     25 }
     26 
     27 int native_asm_constraint_early(const char* s) {
     28   return kit_cg_asm_constraint_early(s);
     29 }
     30 
     31 int native_asm_match_index(const char* s) {
     32   return kit_cg_asm_constraint_match_index(s);
     33 }
     34 
     35 void native_asm_abi_clobber_masks(NativeTarget* t, u32 abi_sets, u32* int_mask,
     36                                   u32* fp_mask) {
     37   *int_mask = 0;
     38   *fp_mask = 0;
     39   if (abi_sets & KIT_CG_ASM_CLOBBER_ABI_CALLER_SAVED) {
     40     *int_mask |= native_target_caller_saved_mask(t, NATIVE_REG_INT);
     41     *fp_mask |= native_target_caller_saved_mask(t, NATIVE_REG_FP);
     42   }
     43   if (abi_sets & KIT_CG_ASM_CLOBBER_ABI_CALLEE_SAVED) {
     44     *int_mask |= native_target_callee_saved_mask(t, NATIVE_REG_INT);
     45     *fp_mask |= native_target_callee_saved_mask(t, NATIVE_REG_FP);
     46   }
     47 }
     48 
     49 u32 native_asm_known_callee_saves(
     50     NativeTarget* t, SrcLoc loc, const NativeKnownFrameDesc* frame, u32* out,
     51     u32 cap, NativeAsmClobberMasksFn clobber_masks,
     52     NativeAsmCalleeSavedRegFn is_callee_saved) {
     53   u32 ncls = frame ? frame->ncallee_classes : 0u;
     54   u32 clob_int = 0, clob_fp = 0, abi_int = 0, abi_fp = 0;
     55   if (ncls > cap) ncls = cap;
     56   for (u32 c = 0; c < cap; ++c) out[c] = 0;
     57   for (u32 c = 0; c < ncls; ++c)
     58     out[c] = frame->callee_saved_used ? frame->callee_saved_used[c] : 0u;
     59   if (frame && frame->asm_clobbers && frame->nasm_clobbers && clobber_masks)
     60     clobber_masks(t->c, loc, frame->asm_clobbers, frame->nasm_clobbers,
     61                   &clob_int, &clob_fp);
     62   if (frame)
     63     native_asm_abi_clobber_masks(t, frame->asm_clobber_abi_sets, &abi_int,
     64                                  &abi_fp);
     65   clob_int |= abi_int;
     66   clob_fp |= abi_fp;
     67   for (Reg r = 0; r < NATIVE_MAX_HARD_REGS; ++r) {
     68     if (NATIVE_REG_INT < ncls && (clob_int & (1u << r)) &&
     69         is_callee_saved(t, NATIVE_REG_INT, r))
     70       out[NATIVE_REG_INT] |= 1u << r;
     71     if (NATIVE_REG_FP < ncls && (clob_fp & (1u << r)) &&
     72         is_callee_saved(t, NATIVE_REG_FP, r))
     73       out[NATIVE_REG_FP] |= 1u << r;
     74   }
     75   return ncls;
     76 }
     77 
     78 int native_asm_constraint_reg_class(const char* constraint,
     79                                     NativeAllocClass* cls_out) {
     80   const char* body = native_asm_constraint_body(constraint);
     81   if (!body || !body[0]) return 0;
     82   if (body[0] == 'r') {
     83     if (cls_out) *cls_out = NATIVE_REG_INT;
     84     return 1;
     85   }
     86   if (body[0] == 'f' || body[0] == 'x' || body[0] == 'w') {
     87     if (cls_out) *cls_out = NATIVE_REG_FP;
     88     return 1;
     89   }
     90   return 0;
     91 }
     92 
     93 static int native_asm_default_operand_reg_ok(const NativeRegInfo* ri,
     94                                              NativeAllocClass cls, Reg reg) {
     95   const NativeAllocClassInfo* ci = native_reg_info_class_info(ri, cls);
     96   if (!ci) return 0;
     97   for (u32 i = 0; i < ci->nphys; ++i) {
     98     const NativePhysRegInfo* pi = &ci->phys[i];
     99     if (pi->reg != reg) continue;
    100     return (pi->flags & NATIVE_REG_RESERVED) == 0;
    101   }
    102   return 0;
    103 }
    104 
    105 int native_asm_constraint_reg_info(NativeTarget* t, const char* constraint,
    106                                    NativeAsmConstraintInfo* out) {
    107   NativeAsmConstraintInfo info;
    108   const char* body = native_asm_constraint_body(constraint);
    109   memset(&info, 0, sizeof info);
    110   info.fixed_reg = REG_NONE;
    111   if (!body || !body[0]) return 0;
    112   if (t && t->regs && t->regs->asm_constraint_reg) {
    113     Reg fixed = REG_NONE;
    114     NativeAllocClass cls = NATIVE_REG_INT;
    115     u32 allowed_mask = 0;
    116     if (t->regs->asm_constraint_reg(t->regs, body, &cls, &fixed,
    117                                     &allowed_mask)) {
    118       if (allowed_mask) {
    119         u32 filtered = 0;
    120         for (Reg r = 0; r < 32; ++r) {
    121           if ((allowed_mask & (1u << r)) == 0) continue;
    122           if (t->regs->asm_operand_reg_ok) {
    123             if (!t->regs->asm_operand_reg_ok(t->regs, cls, r)) continue;
    124           } else if (!native_asm_default_operand_reg_ok(t->regs, cls, r)) {
    125             continue;
    126           }
    127           filtered |= 1u << r;
    128         }
    129         allowed_mask = filtered;
    130         if (!allowed_mask) return 0;
    131       }
    132       if (fixed != REG_NONE) {
    133         if (t->regs->asm_operand_reg_ok) {
    134           if (!t->regs->asm_operand_reg_ok(t->regs, cls, fixed)) return 0;
    135         } else if (!native_asm_default_operand_reg_ok(t->regs, cls, fixed)) {
    136           return 0;
    137         }
    138         if (fixed >= 32) return 0;
    139         if (allowed_mask && (allowed_mask & (1u << fixed)) == 0) return 0;
    140       }
    141       info.cls = cls;
    142       info.fixed_reg = fixed;
    143       info.allowed_mask = allowed_mask;
    144       if (out) *out = info;
    145       return 1;
    146     }
    147     return 0;
    148   }
    149   if (!native_asm_constraint_reg_class(constraint, &info.cls)) return 0;
    150   if (out) *out = info;
    151   return 1;
    152 }
    153 
    154 int native_asm_constraint_is_reg(NativeTarget* t, const char* constraint) {
    155   return native_asm_constraint_reg_info(t, constraint, NULL);
    156 }
    157 
    158 NativeAsmRegPinStatus native_asm_resolve_pin(NativeTarget* t, Sym reg,
    159                                              const char* constraint,
    160                                              NativeAsmRegPin* out) {
    161   Reg r;
    162   NativeAllocClass cls;
    163   NativeAsmConstraintInfo info;
    164   if (!reg) return NATIVE_ASM_REG_PIN_ABSENT;
    165   if (!t || !t->regs || !t->regs->resolve_name)
    166     return NATIVE_ASM_REG_PIN_UNKNOWN;
    167   if (t->regs->resolve_name(t->regs, pool_slice(t->c->global, reg), &r, &cls) !=
    168       0)
    169     return NATIVE_ASM_REG_PIN_UNKNOWN;
    170   if (t->regs->asm_operand_reg_ok) {
    171     if (!t->regs->asm_operand_reg_ok(t->regs, cls, r))
    172       return NATIVE_ASM_REG_PIN_FORBIDDEN;
    173   } else if (!native_asm_default_operand_reg_ok(t->regs, cls, r)) {
    174     return NATIVE_ASM_REG_PIN_FORBIDDEN;
    175   }
    176   if (!native_asm_constraint_reg_info(t, constraint, &info))
    177     return NATIVE_ASM_REG_PIN_BAD_CONSTRAINT;
    178   if (info.cls != cls) return NATIVE_ASM_REG_PIN_CLASS_MISMATCH;
    179   if (info.fixed_reg != REG_NONE && info.fixed_reg != r)
    180     return NATIVE_ASM_REG_PIN_FIXED_MISMATCH;
    181   if (info.allowed_mask && (r >= 32 || (info.allowed_mask & (1u << r)) == 0))
    182     return NATIVE_ASM_REG_PIN_FIXED_MISMATCH;
    183   if (out) {
    184     out->reg = r;
    185     out->cls = cls;
    186   }
    187   return NATIVE_ASM_REG_PIN_OK;
    188 }
    189 
    190 const char* native_asm_pin_status_message(NativeAsmRegPinStatus st) {
    191   switch (st) {
    192     case NATIVE_ASM_REG_PIN_ABSENT:
    193       return "no hard register pin";
    194     case NATIVE_ASM_REG_PIN_OK:
    195       return "hard register pin resolved";
    196     case NATIVE_ASM_REG_PIN_UNKNOWN:
    197       return "unknown asm register variable name";
    198     case NATIVE_ASM_REG_PIN_FORBIDDEN:
    199       return "asm register variable names an unsupported register";
    200     case NATIVE_ASM_REG_PIN_BAD_CONSTRAINT:
    201       return "asm register variable requires a register constraint";
    202     case NATIVE_ASM_REG_PIN_CLASS_MISMATCH:
    203       return "asm register variable class does not match its constraint";
    204     case NATIVE_ASM_REG_PIN_FIXED_MISMATCH:
    205       return "asm register variable conflicts with register constraint";
    206   }
    207   return "invalid asm register variable";
    208 }
    209 
    210 /* Resolve an operand's explicit hard-register pin, panicking (through the
    211  * arch prefix) on any invalid pin. Returns 1 when a valid pin exists, 0 when
    212  * the operand has no pin -- the same contract the former per-arch
    213  * *_asm_resolve_pin_or_panic helpers had. */
    214 static int native_asm_direct_resolve_pin(NativeDirectTarget* d,
    215                                          const NativeAsmDirectHooks* h, Sym reg,
    216                                          const char* constraint,
    217                                          NativeAsmRegPin* pin) {
    218   NativeAsmRegPinStatus st =
    219       native_asm_resolve_pin(d->native, reg, constraint, pin);
    220   if (st == NATIVE_ASM_REG_PIN_ABSENT) return 0;
    221   if (st != NATIVE_ASM_REG_PIN_OK)
    222     h->panic(d, native_asm_pin_status_message(st));
    223   return 1;
    224 }
    225 
    226 static Reg native_asm_direct_alloc_reg(NativeDirectTarget* d,
    227                                        const NativeAsmDirectHooks* h,
    228                                        NativeAllocClass cls, u32 allowed_mask,
    229                                        u32* used_int, u32* used_fp) {
    230   const NativeAllocClassInfo* ci = native_target_class_info(d->native, cls);
    231   u32* used = cls == NATIVE_REG_FP ? used_fp : used_int;
    232   for (u32 i = 0; ci && i < ci->ndirect_asm_allocable; ++i) {
    233     Reg reg = ci->direct_asm_allocable[i];
    234     if (reg >= NATIVE_MAX_HARD_REGS) continue;
    235     if (allowed_mask && (allowed_mask & (1u << reg)) == 0) continue;
    236     if ((*used & (1u << reg)) != 0) continue;
    237     *used |= 1u << reg;
    238     return reg;
    239   }
    240   h->panic(d, "out of registers for asm operands");
    241   return REG_NONE;
    242 }
    243 
    244 static KitCgTypeId native_asm_pointer_carrier_type(NativeTarget* t) {
    245   if (t->c->target.ptr_size == 4u) return builtin_id(KIT_CG_BUILTIN_I32);
    246   if (t->c->target.ptr_size == 8u) return builtin_id(KIT_CG_BUILTIN_I64);
    247   compiler_panic(t->c, (SrcLoc){0, 0, 0},
    248                  "native asm: unsupported pointer carrier width %u",
    249                  (unsigned)t->c->target.ptr_size);
    250 }
    251 
    252 void native_asm_bind_direct_operands(NativeDirectTarget* d, const char* tmpl,
    253                                      const AsmConstraint* outs, u32 nout,
    254                                      Operand* out_ops, const AsmConstraint* ins,
    255                                      u32 nin, const Operand* in_ops,
    256                                      const Sym* clobbers, u32 nclob,
    257                                      u32 clobber_abi_sets,
    258                                      const NativeAsmDirectHooks* h) {
    259   Compiler* c = d->base.c;
    260   Operand* bound_outs = nout ? arena_zarray(c->tu, Operand, nout) : NULL;
    261   Operand* bound_ins = nin ? arena_zarray(c->tu, Operand, nin) : NULL;
    262   u32 clob_int, clob_fp, abi_int, abi_fp, used_int, used_fp;
    263   void* saved;
    264   u32 nsaved, i;
    265 
    266   h->clobber_masks(c, d->loc, clobbers, nclob, &clob_int, &clob_fp);
    267   native_asm_abi_clobber_masks(d->native, clobber_abi_sets, &abi_int, &abi_fp);
    268   clob_int |= abi_int;
    269   clob_fp |= abi_fp;
    270   used_int = clob_int;
    271   used_fp = clob_fp;
    272 
    273   for (i = 0; i < nout; ++i) {
    274     const char* body = native_asm_constraint_body(outs[i].str);
    275     KitCgTypeId type = outs[i].type ? outs[i].type : out_ops[i].type;
    276     NativeAsmRegPin pin;
    277     if (native_asm_direct_resolve_pin(d, h, outs[i].reg, outs[i].str, &pin)) {
    278       /* GNU local register variable: pin to the named hard register. */
    279       if (pin.cls == NATIVE_REG_FP) {
    280         used_fp |= 1u << pin.reg;
    281         clob_fp |= 1u << pin.reg;
    282       } else {
    283         used_int |= 1u << pin.reg;
    284         clob_int |= 1u << pin.reg;
    285       }
    286       h->bound_reg(&bound_outs[i], type, pin.cls, pin.reg);
    287     } else {
    288       NativeAsmConstraintInfo info;
    289       if (native_asm_constraint_reg_info(d->native, outs[i].str, &info)) {
    290         Reg reg = info.fixed_reg != REG_NONE
    291                       ? info.fixed_reg
    292                       : native_asm_direct_alloc_reg(
    293                             d, h, info.cls, info.allowed_mask, &used_int,
    294                             &used_fp);
    295         if (info.cls == NATIVE_REG_FP) {
    296           used_fp |= 1u << reg;
    297           /* Binding itself overwrites the staging register, independently of
    298            * whether the template names it as a clobber.  Include every bound
    299            * register in the preservation set so a target may safely draw a
    300            * high-arity direct-asm operand from its callee-saved pool. */
    301           clob_fp |= 1u << reg;
    302         } else {
    303           used_int |= 1u << reg;
    304           clob_int |= 1u << reg;
    305         }
    306         h->bound_reg(&bound_outs[i], type, info.cls, reg);
    307       } else if (body[0] == 'm') {
    308         Reg reg = native_asm_direct_alloc_reg(
    309             d, h, NATIVE_REG_INT, 0, &used_int, &used_fp);
    310         clob_int |= 1u << reg;
    311         h->bound_mem(&bound_outs[i], type, reg);
    312       } else {
    313         h->panic(d, "unsupported output constraint");
    314       }
    315     }
    316   }
    317 
    318   for (i = 0; i < nin; ++i) {
    319     const char* body = native_asm_constraint_body(ins[i].str);
    320     int matched = native_asm_match_index(body);
    321     KitCgTypeId type = ins[i].type ? ins[i].type : in_ops[i].type;
    322     if (matched >= 0) {
    323       if ((u32)matched >= nout) h->panic(d, "matching constraint out of range");
    324       if (native_asm_constraint_early(outs[matched].str))
    325         h->panic(d, "matching input names early-clobber output");
    326       if (bound_outs[matched].kind != h->opk_reg)
    327         h->panic(d, "matching constraint requires register output");
    328       bound_ins[i] = bound_outs[matched];
    329       continue;
    330     }
    331     NativeAsmRegPin pin;
    332     if (native_asm_direct_resolve_pin(d, h, ins[i].reg, ins[i].str, &pin)) {
    333       /* GNU local register variable: pin to the named hard register. */
    334       if (pin.cls == NATIVE_REG_FP) {
    335         used_fp |= 1u << pin.reg;
    336         clob_fp |= 1u << pin.reg;
    337       } else {
    338         used_int |= 1u << pin.reg;
    339         clob_int |= 1u << pin.reg;
    340       }
    341       h->bound_reg(&bound_ins[i], type, pin.cls, pin.reg);
    342     } else {
    343       NativeAsmConstraintInfo info;
    344       if (native_asm_constraint_reg_info(d->native, ins[i].str, &info)) {
    345         Reg reg = info.fixed_reg != REG_NONE
    346                       ? info.fixed_reg
    347                       : native_asm_direct_alloc_reg(
    348                             d, h, info.cls, info.allowed_mask, &used_int,
    349                             &used_fp);
    350         if (info.cls == NATIVE_REG_FP) {
    351           used_fp |= 1u << reg;
    352           clob_fp |= 1u << reg;
    353         } else {
    354           used_int |= 1u << reg;
    355           clob_int |= 1u << reg;
    356         }
    357         h->bound_reg(&bound_ins[i], type, info.cls, reg);
    358       } else if (body[0] == 'i') {
    359         if (in_ops[i].kind != OPK_IMM)
    360           h->panic(d, "immediate constraint requires immediate operand");
    361         bound_ins[i] = in_ops[i];
    362       } else if (body[0] == 'm') {
    363         Reg reg = native_asm_direct_alloc_reg(
    364             d, h, NATIVE_REG_INT, 0, &used_int, &used_fp);
    365         clob_int |= 1u << reg;
    366         h->bound_mem(&bound_ins[i], type, reg);
    367       } else {
    368         h->panic(d, "unsupported input constraint");
    369       }
    370     }
    371   }
    372 
    373   saved = h->save_callee_clobbers(d, clob_int, clob_fp, &nsaved);
    374   for (i = 0; i < nout; ++i) {
    375     if (bound_outs[i].kind == h->opk_reg) {
    376       NativeAllocClass cls =
    377           bound_outs[i].pad[0] == h->opcls_fp ? NATIVE_REG_FP : NATIVE_REG_INT;
    378       if (outs[i].dir == KIT_CG_ASM_INOUT) {
    379         h->load_operand_to_reg(d, out_ops[i],
    380                                native_loc_reg(bound_outs[i].type, cls,
    381                                               (Reg)bound_outs[i].v.local));
    382       }
    383     } else if (bound_outs[i].kind == OPK_INDIRECT) {
    384       NativeLoc loc = native_loc_reg(native_asm_pointer_carrier_type(d->native),
    385                                      NATIVE_REG_INT,
    386                                      (Reg)bound_outs[i].v.ind.base);
    387       h->load_address_to_reg(d, out_ops[i], loc);
    388     }
    389   }
    390   for (i = 0; i < nin; ++i) {
    391     if (bound_ins[i].kind == h->opk_reg) {
    392       NativeAllocClass cls =
    393           bound_ins[i].pad[0] == h->opcls_fp ? NATIVE_REG_FP : NATIVE_REG_INT;
    394       h->load_operand_to_reg(
    395           d, in_ops[i],
    396           native_loc_reg(bound_ins[i].type, cls, (Reg)bound_ins[i].v.local));
    397     } else if (bound_ins[i].kind == OPK_INDIRECT) {
    398       NativeLoc loc = native_loc_reg(native_asm_pointer_carrier_type(d->native),
    399                                      NATIVE_REG_INT,
    400                                      (Reg)bound_ins[i].v.ind.base);
    401       h->load_address_to_reg(d, in_ops[i], loc);
    402     }
    403   }
    404 
    405   h->run_template(d, tmpl, outs, nout, bound_outs, ins, nin, bound_ins,
    406                   clobbers, nclob);
    407 
    408   for (i = 0; i < nout; ++i) {
    409     NativeAllocClass cls;
    410     NativeLoc src;
    411     if (bound_outs[i].kind != h->opk_reg) continue;
    412     cls = bound_outs[i].pad[0] == h->opcls_fp ? NATIVE_REG_FP : NATIVE_REG_INT;
    413     src = native_loc_reg(bound_outs[i].type, cls, (Reg)bound_outs[i].v.local);
    414     h->store_reg_to_operand(d, out_ops[i], src);
    415   }
    416   for (i = nsaved; i > 0; --i) h->restore_one(d, saved, i - 1u);
    417 }
    418 
    419 static void native_asm_validate_native_pin(
    420     NativeTarget* t, SrcLoc loc, const AsmConstraint* constraint, NativeLoc src,
    421     const NativeAsmNativeHooks* h) {
    422   NativeAsmRegPin pin;
    423   NativeAsmRegPinStatus status;
    424   if (!constraint->reg) return;
    425   status = native_asm_resolve_pin(t, constraint->reg, constraint->str, &pin);
    426   if (status != NATIVE_ASM_REG_PIN_OK)
    427     h->panic(t, loc, native_asm_pin_status_message(status));
    428   if (src.kind != NATIVE_LOC_REG || src.cls != (u8)pin.cls ||
    429       src.v.reg != pin.reg)
    430     h->panic(t, loc, "hard-register asm operand in wrong register");
    431 }
    432 
    433 static void native_asm_bind_native_one(
    434     NativeTarget* t, SrcLoc loc, Operand* out,
    435     const AsmConstraint* constraint, KitCgTypeId type, NativeLoc src,
    436     u32* ntmp, const NativeAsmNativeHooks* h) {
    437   const char* body = native_asm_constraint_body(constraint->str);
    438   NativeAsmConstraintInfo info;
    439   native_asm_validate_native_pin(t, loc, constraint, src, h);
    440   if (native_asm_constraint_reg_info(t, constraint->str, &info)) {
    441     if (src.kind != NATIVE_LOC_REG)
    442       h->panic(t, loc, "register asm operand not in a register");
    443     if (src.cls != (u8)info.cls)
    444       h->panic(t, loc, "register asm operand has wrong register class");
    445     if (info.fixed_reg != REG_NONE && info.fixed_reg != (Reg)src.v.reg)
    446       h->panic(t, loc, "fixed-register asm operand in wrong register");
    447     if (info.allowed_mask &&
    448         ((Reg)src.v.reg >= 32 ||
    449          (info.allowed_mask & (1u << (Reg)src.v.reg)) == 0))
    450       h->panic(t, loc, "register asm operand violates constraint register set");
    451     h->bound_reg(out, type, info.cls, (Reg)src.v.reg);
    452   } else if (body[0] == 'i') {
    453     if (src.kind != NATIVE_LOC_IMM)
    454       h->panic(t, loc, "immediate asm operand is not immediate");
    455     memset(out, 0, sizeof *out);
    456     out->kind = OPK_IMM;
    457     out->type = type;
    458     out->v.imm = src.v.imm;
    459   } else if (body[0] == 'm') {
    460     h->bound_mem(out, type, h->mem_base(t, loc, src, ntmp));
    461   } else {
    462     h->panic(t, loc, "unsupported asm constraint");
    463   }
    464 }
    465 
    466 void native_asm_bind_native_operands(
    467     NativeTarget* t, SrcLoc loc, const char* tmpl, const AsmConstraint* outs,
    468     u32 nout, NativeLoc* out_locs, const AsmConstraint* ins, u32 nin,
    469     const NativeLoc* in_locs, const Sym* clobbers, u32 nclob,
    470     const NativeAsmNativeHooks* h) {
    471   Compiler* c = t->c;
    472   Operand* bound_outs = nout ? arena_zarray(c->tu, Operand, nout) : NULL;
    473   Operand* bound_ins = nin ? arena_zarray(c->tu, Operand, nin) : NULL;
    474   u32 ntmp = 0;
    475 
    476   for (u32 i = 0; i < nout; ++i) {
    477     KitCgTypeId type = outs[i].type ? outs[i].type : out_locs[i].type;
    478     native_asm_bind_native_one(t, loc, &bound_outs[i], &outs[i], type,
    479                                out_locs[i], &ntmp, h);
    480   }
    481 
    482   for (u32 i = 0; i < nin; ++i) {
    483     const char* body = native_asm_constraint_body(ins[i].str);
    484     int matched = native_asm_match_index(body);
    485     KitCgTypeId type;
    486     NativeLoc inloc;
    487     if (matched >= 0) {
    488       if ((u32)matched >= nout)
    489         h->panic(t, loc, "matching constraint out of range");
    490       if (native_asm_constraint_early(outs[matched].str))
    491         h->panic(t, loc, "matching input names early-clobber output");
    492       if (out_locs[matched].kind != NATIVE_LOC_REG ||
    493           in_locs[i].kind != NATIVE_LOC_REG ||
    494           out_locs[matched].cls != in_locs[i].cls ||
    495           out_locs[matched].v.reg != in_locs[i].v.reg)
    496         h->panic(t, loc, "matching asm operands are in different registers");
    497       bound_ins[i] = bound_outs[matched];
    498       continue;
    499     }
    500     type = ins[i].type ? ins[i].type : in_locs[i].type;
    501     inloc = in_locs[i];
    502     native_asm_bind_native_one(t, loc, &bound_ins[i], &ins[i], type, inloc,
    503                                &ntmp, h);
    504   }
    505 
    506   h->run_template(t, tmpl, outs, nout, bound_outs, ins, nin, bound_ins, clobbers,
    507                   nclob);
    508 }