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 }