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:
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;
+}