kit

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

commit 37e9c50a8723354b323469550c4a38943eace07f
parent 5f7afa1ded17f2d15a63ca6031de148bdf6828fc
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 13 May 2026 07:41:23 -0700

Complete Cg inline asm API coverage

Diffstat:
Mdoc/cg-api-status.md | 4+++-
Minclude/cfree/cg.h | 7++++---
Mlang/toy/toy.c | 188+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/api/cg.c | 65+++++++++++++++++++++++++++++++++++++++++++++--------------------
Atest/toy/cases/20_cg_api_inline_asm_full.expected | 1+
Atest/toy/cases/20_cg_api_inline_asm_full.toy | 23+++++++++++++++++++++++
6 files changed, 264 insertions(+), 24 deletions(-)

diff --git a/doc/cg-api-status.md b/doc/cg-api-status.md @@ -49,7 +49,9 @@ Toy currently supports: `memset`, `memcpy`, `atomic_load`, `atomic_store`, `atomic_add`, `atomic_sub`, `atomic_cas_ok`, `fence`, `popcount`, `ctz`, `clz`, `bswap`, `expect`, `fieldtest()`, `target()`, `target_os()`, `va_start`, `va_arg`, - `va_end`, `va_copy`, `asm(...)`, and `asm_int(...)`. + `va_end`, `va_copy`, `asm(...)`, `asm_int(...)`, `asm_imm(...)`, + `asm_mem(...)`, `asm_inout(...)`, `asm_early(...)`, `asm_memory(...)`, and + `asm_clobber(...)`. - Lowering uses the explicit value-category API: `push_symbol + indirect + load/store`, `push_bytes + indirect + load`, `cfree_cg_field`, `cfree_cg_va_*`, `cfree_cg_inline_asm`, statement-like diff --git a/include/cfree/cg.h b/include/cfree/cg.h @@ -336,7 +336,6 @@ typedef enum CfreeCgAsmDir { typedef enum CfreeCgAsmFlag { CFREE_CG_ASM_NONE = 0, CFREE_CG_ASM_VOLATILE = 1u << 0, - CFREE_CG_ASM_GOTO = 1u << 1, } CfreeCgAsmFlag; typedef struct CfreeCgAsmOperand { @@ -348,8 +347,10 @@ typedef struct CfreeCgAsmOperand { } CfreeCgAsmOperand; /* Inputs are popped in declaration order. Outputs are pushed in declaration - * order as fresh values after the asm block. Template, constraints, and - * clobbers are pre-interned strings. */ + * order as fresh values after the asm block. INOUT outputs consume one + * initial value each after the explicit inputs, in output declaration order; + * the implementation binds those values with matching constraints. Template, + * constraints, and clobbers are pre-interned strings. */ void cfree_cg_inline_asm(CfreeCg*, CfreeSym tmpl, const CfreeCgAsmOperand* outputs, uint32_t noutputs, const CfreeCgAsmOperand* inputs, uint32_t ninputs, diff --git a/lang/toy/toy.c b/lang/toy/toy.c @@ -691,6 +691,23 @@ static int toy_emit_var_addr(ToyParser* p, CfreeSym name) { return 0; } +static CfreeCgTypeId toy_emit_var_lvalue(ToyParser* p, CfreeSym name) { + ToyVar* v = toy_find_var(p, name); + if (v) { + cfree_cg_push_local(p->cg, v->slot); + return v->type; + } + { + ToyGlobal* g = toy_find_global(p, name); + if (g) { + cfree_cg_push_symbol(p->cg, name, g->type, CFREE_CG_SYMREF_ADDR, 0); + cfree_cg_indirect(p->cg); + return g->type; + } + } + return CFREE_CG_TYPE_NONE; +} + static int toy_parse_va_list_addr_arg(ToyParser* p) { CfreeSym name; ToyVar* v; @@ -1000,6 +1017,177 @@ static CfreeCgTypeId toy_parse_builtin_call(ToyParser* p, CfreeSym name, return p->int_type; } + if (toy_sym_is(p, name, "asm_imm")) { + CfreeSym tmpl; + size_t tmpl_len; + int64_t imm; + CfreeCgAsmOperand outputs[1]; + CfreeCgAsmOperand inputs[1]; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_arch_string(p, &tmpl, &tmpl_len) || + !toy_expect_comma(p) || !toy_parse_number_arg(p, &imm) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (tmpl_len) { + memset(outputs, 0, sizeof outputs); + memset(inputs, 0, sizeof inputs); + outputs[0].constraint = cfree_sym_intern(p->c, "=r"); + outputs[0].type = p->int_type; + outputs[0].dir = CFREE_CG_ASM_OUT; + inputs[0].constraint = cfree_sym_intern(p->c, "i"); + inputs[0].type = p->int_type; + inputs[0].dir = CFREE_CG_ASM_IN; + cfree_cg_push_int(p->cg, (uint64_t)imm, p->int_type); + cfree_cg_inline_asm(p->cg, tmpl, outputs, 1, inputs, 1, NULL, 0, + CFREE_CG_ASM_VOLATILE); + } else { + cfree_cg_push_int(p->cg, 0, p->int_type); + } + return p->int_type; + } + + if (toy_sym_is(p, name, "asm_mem")) { + CfreeSym tmpl; + size_t tmpl_len; + CfreeSym var_name; + CfreeCgTypeId var_ty; + CfreeCgAsmOperand outputs[1]; + CfreeCgAsmOperand inputs[1]; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_arch_string(p, &tmpl, &tmpl_len) || + !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + if (p->cur.kind != TOK_IDENT) { + toy_error(p, p->cur.loc, "asm_mem expects an identifier"); + return CFREE_CG_TYPE_NONE; + } + var_name = toy_tok_sym(p, p->cur); + toy_parser_advance(p); + if (!toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + var_ty = toy_emit_var_lvalue(p, var_name); + if (var_ty != p->int_type) { + toy_error(p, p->cur.loc, "asm_mem expects an int lvalue"); + return CFREE_CG_TYPE_NONE; + } + if (tmpl_len) { + memset(outputs, 0, sizeof outputs); + memset(inputs, 0, sizeof inputs); + outputs[0].constraint = cfree_sym_intern(p->c, "=r"); + outputs[0].type = p->int_type; + outputs[0].dir = CFREE_CG_ASM_OUT; + inputs[0].constraint = cfree_sym_intern(p->c, "m"); + inputs[0].type = p->int_type; + inputs[0].dir = CFREE_CG_ASM_IN; + cfree_cg_inline_asm(p->cg, tmpl, outputs, 1, inputs, 1, NULL, 0, + CFREE_CG_ASM_VOLATILE); + } else { + cfree_cg_load(p->cg); + } + return p->int_type; + } + + if (toy_sym_is(p, name, "asm_inout")) { + CfreeSym tmpl; + size_t tmpl_len; + CfreeCgTypeId ty; + CfreeCgAsmOperand outputs[1]; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_arch_string(p, &tmpl, &tmpl_len) || + !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_expr(p); + if (ty == CFREE_CG_TYPE_NONE || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (ty != p->int_type) { + toy_error(p, p->cur.loc, "asm_inout expects an int input"); + return CFREE_CG_TYPE_NONE; + } + if (tmpl_len) { + memset(outputs, 0, sizeof outputs); + outputs[0].constraint = cfree_sym_intern(p->c, "+r"); + outputs[0].type = p->int_type; + outputs[0].dir = CFREE_CG_ASM_INOUT; + cfree_cg_inline_asm(p->cg, tmpl, outputs, 1, NULL, 0, NULL, 0, + CFREE_CG_ASM_VOLATILE); + } + return p->int_type; + } + + if (toy_sym_is(p, name, "asm_early")) { + CfreeSym tmpl; + size_t tmpl_len; + CfreeCgTypeId a, b; + CfreeCgAsmOperand outputs[1]; + CfreeCgAsmOperand inputs[2]; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_arch_string(p, &tmpl, &tmpl_len) || + !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + a = toy_parse_expr(p); + if (a == CFREE_CG_TYPE_NONE || !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + b = toy_parse_expr(p); + if (b == CFREE_CG_TYPE_NONE || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (a != p->int_type || b != p->int_type) { + toy_error(p, p->cur.loc, "asm_early expects int inputs"); + return CFREE_CG_TYPE_NONE; + } + if (tmpl_len) { + memset(outputs, 0, sizeof outputs); + memset(inputs, 0, sizeof inputs); + outputs[0].constraint = cfree_sym_intern(p->c, "=&r"); + outputs[0].type = p->int_type; + outputs[0].dir = CFREE_CG_ASM_OUT; + inputs[0].constraint = cfree_sym_intern(p->c, "r"); + inputs[0].type = p->int_type; + inputs[0].dir = CFREE_CG_ASM_IN; + inputs[1] = inputs[0]; + cfree_cg_inline_asm(p->cg, tmpl, outputs, 1, inputs, 2, NULL, 0, + CFREE_CG_ASM_VOLATILE); + } else { + cfree_cg_binop(p->cg, CFREE_CG_ADD); + } + return p->int_type; + } + + if (toy_sym_is(p, name, "asm_memory")) { + CfreeSym tmpl; + CfreeSym clobber; + size_t tmpl_len; + CfreeCgTypeId ty; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_arch_string(p, &tmpl, &tmpl_len) || + !toy_expect_comma(p)) return CFREE_CG_TYPE_NONE; + ty = toy_parse_expr(p); + if (ty == CFREE_CG_TYPE_NONE || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (ty != p->int_type) { + toy_error(p, p->cur.loc, "asm_memory expects an int input"); + return CFREE_CG_TYPE_NONE; + } + if (tmpl_len) { + clobber = cfree_sym_intern(p->c, "memory"); + cfree_cg_inline_asm(p->cg, tmpl, NULL, 0, NULL, 0, &clobber, 1, + CFREE_CG_ASM_VOLATILE); + } + return p->int_type; + } + + if (toy_sym_is(p, name, "asm_clobber")) { + CfreeSym tmpl; + CfreeSym clobber; + size_t tmpl_len; + size_t clobber_len; + if (!toy_parser_expect(p, TOK_LPAREN) || + !toy_parse_arch_string(p, &tmpl, &tmpl_len) || + !toy_expect_comma(p) || + !toy_parse_arch_string(p, &clobber, &clobber_len) || + !toy_parser_expect(p, TOK_RPAREN)) return CFREE_CG_TYPE_NONE; + if (tmpl_len) { + cfree_cg_inline_asm(p->cg, tmpl, NULL, 0, NULL, 0, + clobber_len ? &clobber : NULL, + clobber_len ? 1u : 0u, CFREE_CG_ASM_VOLATILE); + } + cfree_cg_push_int(p->cg, 11, p->int_type); + return p->int_type; + } + if (toy_sym_is(p, name, "typecheck")) { CfreeCgTypeId arr, qual, alias, enm, fnty; CfreeCgEnumValue ev; diff --git a/src/api/cg.c b/src/api/cg.c @@ -1996,6 +1996,8 @@ void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl, const CfreeCgAsmOperand* inputs, uint32_t ninputs, const CfreeSym* clobbers, uint32_t nclobbers, uint32_t flags) { + static const char* const match_strs[10] = { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; CGTarget* T; Heap* h; const Type* fallback_ty; @@ -2009,12 +2011,15 @@ void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl, const char* tmpl_str; Sym sym_memory; int has_memory_clobber; + uint32_t ninout; + uint32_t total_inputs; (void)flags; if (!g) return; T = g->target; h = g->c->env->heap; fallback_ty = type_prim(g->c->global, TY_LLONG); tmpl_str = api_sym_cstr(g, tmpl); + ninout = 0; outs = NULL; ins = NULL; @@ -2034,6 +2039,14 @@ void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl, outs[i].type = resolve_type(g->c, outputs[i].type); outs[i].dir = (u8)api_map_asm_dir(outputs[i].dir); if (!outs[i].type) outs[i].type = fallback_ty; + if (outs[i].dir == ASM_INOUT) { + if (i >= 10) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: asm inout output index exceeds matching " + "constraint range"); + } + ninout++; + } } out_ops = (Operand*)h->alloc(h, sizeof(*out_ops) * noutputs, _Alignof(Operand)); @@ -2042,22 +2055,34 @@ void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl, memset(out_reg_owned, 0, noutputs); } - if (ninputs) { - ins = (AsmConstraint*)h->alloc(h, sizeof(*ins) * ninputs, + total_inputs = ninputs + ninout; + if (total_inputs) { + uint32_t inout_index; + ins = (AsmConstraint*)h->alloc(h, sizeof(*ins) * total_inputs, _Alignof(AsmConstraint)); - memset(ins, 0, sizeof(*ins) * ninputs); - in_svs = (ApiSValue*)h->alloc(h, sizeof(*in_svs) * ninputs, + memset(ins, 0, sizeof(*ins) * total_inputs); + in_svs = (ApiSValue*)h->alloc(h, sizeof(*in_svs) * total_inputs, _Alignof(ApiSValue)); - in_ops = (Operand*)h->alloc(h, sizeof(*in_ops) * ninputs, + in_ops = (Operand*)h->alloc(h, sizeof(*in_ops) * total_inputs, _Alignof(Operand)); - memset(in_ops, 0, sizeof(*in_ops) * ninputs); + memset(in_ops, 0, sizeof(*in_ops) * total_inputs); for (u32 i = 0; i < ninputs; ++i) { - u32 idx = ninputs - 1u - i; - ins[idx].str = api_sym_cstr(g, inputs[idx].constraint); - ins[idx].name = (Sym)inputs[idx].name; - ins[idx].type = resolve_type(g->c, inputs[idx].type); - ins[idx].dir = (u8)api_map_asm_dir(inputs[idx].dir); - if (!ins[idx].type) ins[idx].type = fallback_ty; + ins[i].str = api_sym_cstr(g, inputs[i].constraint); + ins[i].name = (Sym)inputs[i].name; + ins[i].type = resolve_type(g->c, inputs[i].type); + ins[i].dir = (u8)api_map_asm_dir(inputs[i].dir); + if (!ins[i].type) ins[i].type = fallback_ty; + } + inout_index = ninputs; + for (u32 i = 0; i < noutputs; ++i) { + if (outs[i].dir != ASM_INOUT) continue; + ins[inout_index].str = match_strs[i]; + ins[inout_index].type = outs[i].type ? outs[i].type : fallback_ty; + ins[inout_index].dir = ASM_IN; + inout_index++; + } + for (u32 i = 0; i < total_inputs; ++i) { + u32 idx = total_inputs - 1u - i; in_svs[idx] = api_pop(g); api_ensure_reg(g, &in_svs[idx]); } @@ -2082,7 +2107,7 @@ void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl, } } - for (u32 i = 0; i < ninputs; ++i) { + for (u32 i = 0; i < total_inputs; ++i) { const char* s = ins[i].str ? ins[i].str : ""; int matched = api_asm_parse_match_index(s); const Type* ity = api_sv_type(&in_svs[i]); @@ -2149,7 +2174,7 @@ void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl, } oty = outs[i].type ? outs[i].type : fallback_ty; r = api_alloc_reg_or_spill(g, api_type_class(oty), oty); - for (u32 k = 0; k < ninputs; ++k) { + for (u32 k = 0; k < total_inputs; ++k) { if ((in_ops[k].kind == OPK_REG && in_ops[k].v.reg == r) || (in_ops[k].kind == OPK_INDIRECT && in_ops[k].v.ind.base == r)) { compiler_panic(g->c, g->cur_loc, @@ -2187,7 +2212,7 @@ void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl, "CfreeCg: asm clobber overlaps output"); } } - for (u32 k = 0; k < ninputs; ++k) { + for (u32 k = 0; k < total_inputs; ++k) { if (in_ops[k].kind == OPK_REG && in_ops[k].cls == cls && (Reg)in_ops[k].v.reg == phys) { compiler_panic(g->c, g->cur_loc, @@ -2204,10 +2229,10 @@ void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl, } } - T->asm_block(T, tmpl_str, outs, noutputs, out_ops, ins, ninputs, in_ops, + T->asm_block(T, tmpl_str, outs, noutputs, out_ops, ins, total_inputs, in_ops, clobs, nclobbers); - for (u32 i = 0; i < ninputs; ++i) api_release(g, &in_svs[i]); + for (u32 i = 0; i < total_inputs; ++i) api_release(g, &in_svs[i]); for (u32 i = 0; i < noutputs; ++i) { const Type* oty = outs[i].type ? outs[i].type : fallback_ty; ApiSValue sv = api_make_sv(out_ops[i], oty); @@ -2216,10 +2241,10 @@ void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl, } if (outs) h->free(h, outs, sizeof(*outs) * noutputs); - if (ins) h->free(h, ins, sizeof(*ins) * ninputs); + if (ins) h->free(h, ins, sizeof(*ins) * total_inputs); if (clobs) h->free(h, clobs, sizeof(*clobs) * nclobbers); - if (in_svs) h->free(h, in_svs, sizeof(*in_svs) * ninputs); - if (in_ops) h->free(h, in_ops, sizeof(*in_ops) * ninputs); + if (in_svs) h->free(h, in_svs, sizeof(*in_svs) * total_inputs); + if (in_ops) h->free(h, in_ops, sizeof(*in_ops) * total_inputs); if (out_ops) h->free(h, out_ops, sizeof(*out_ops) * noutputs); if (out_reg_owned) h->free(h, out_reg_owned, noutputs); } diff --git a/test/toy/cases/20_cg_api_inline_asm_full.expected b/test/toy/cases/20_cg_api_inline_asm_full.expected @@ -0,0 +1 @@ +81 diff --git a/test/toy/cases/20_cg_api_inline_asm_full.toy b/test/toy/cases/20_cg_api_inline_asm_full.toy @@ -0,0 +1,23 @@ +fn main(): int { + let slot: int = 31; + + let imm: int = asm_imm(arch("mov %w0, %1", "", ""), 7); + let mem: int = asm_mem(arch("ldr %w0, %a1", "", ""), slot); + let inout: int = asm_inout(arch("add %w0, %w0, #5", "", ""), 4); + let early: int = asm_early(arch("add %w0, %w1, %w2", "", ""), 8, 9); + let memory: int = asm_memory(arch("nop", "", ""), 6); + let clobber: int = asm_clobber( + arch("mov x19, #1", "", ""), + arch("x19", "", "")); + + if target() != 1 { + imm = 7; + mem = 31; + inout = 9; + early = 17; + memory = 6; + clobber = 11; + } + + return imm + mem + inout + early + memory + clobber; +}