commit f8bbbafdb01a72b2f2fa2377f10546a2f65e817a
parent df8e73dd8c00a501a3b593bb97730af95321f574
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 25 May 2026 15:09:58 -0700
Fix test targets for public API coverage
Diffstat:
21 files changed, 1138 insertions(+), 1642 deletions(-)
diff --git a/include/cfree/object.h b/include/cfree/object.h
@@ -59,6 +59,22 @@ typedef struct CfreeRelocKind {
uint32_t code;
} CfreeRelocKind;
+/* Public relocation code aliases for CfreeRelocKind.code. The numeric values
+ * are part of the object-builder ABI and map to libcfree's format-neutral
+ * relocation space before per-format translation at emit time. */
+typedef enum CfreeRelocCode {
+ CFREE_RELOC_NONE = 0,
+ CFREE_RELOC_ABS32 = 1,
+ CFREE_RELOC_ABS64 = 2,
+ CFREE_RELOC_REL32 = 3,
+ CFREE_RELOC_REL64 = 4,
+ CFREE_RELOC_PC32 = 5,
+ CFREE_RELOC_PC64 = 6,
+ CFREE_RELOC_GOT32 = 7,
+ CFREE_RELOC_PLT32 = 8,
+ CFREE_RELOC_X64_PLT32 = 51,
+} CfreeRelocCode;
+
typedef struct CfreeObjSecInfo {
CfreeSlice name;
CfreeSecKind kind;
diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c
@@ -1224,9 +1224,6 @@ static void parse_external_decl(Parser* p) {
if (specs.storage == DS_AUTO && specs.storage_explicit) {
perr(p, "invalid storage-class specifier at file scope");
}
- if (specs.storage == DS_REGISTER) {
- perr(p, "invalid storage-class specifier at file scope");
- }
if (accept_punct(p, ';')) return;
diff --git a/src/api/object_builder.c b/src/api/object_builder.c
@@ -7,6 +7,27 @@
#include "obj/format.h"
#include "obj/obj.h"
+_Static_assert((uint32_t)CFREE_RELOC_NONE == (uint32_t)R_NONE,
+ "public reloc NONE drift");
+_Static_assert((uint32_t)CFREE_RELOC_ABS32 == (uint32_t)R_ABS32,
+ "public reloc ABS32 drift");
+_Static_assert((uint32_t)CFREE_RELOC_ABS64 == (uint32_t)R_ABS64,
+ "public reloc ABS64 drift");
+_Static_assert((uint32_t)CFREE_RELOC_REL32 == (uint32_t)R_REL32,
+ "public reloc REL32 drift");
+_Static_assert((uint32_t)CFREE_RELOC_REL64 == (uint32_t)R_REL64,
+ "public reloc REL64 drift");
+_Static_assert((uint32_t)CFREE_RELOC_PC32 == (uint32_t)R_PC32,
+ "public reloc PC32 drift");
+_Static_assert((uint32_t)CFREE_RELOC_PC64 == (uint32_t)R_PC64,
+ "public reloc PC64 drift");
+_Static_assert((uint32_t)CFREE_RELOC_GOT32 == (uint32_t)R_GOT32,
+ "public reloc GOT32 drift");
+_Static_assert((uint32_t)CFREE_RELOC_PLT32 == (uint32_t)R_PLT32,
+ "public reloc PLT32 drift");
+_Static_assert((uint32_t)CFREE_RELOC_X64_PLT32 == (uint32_t)R_X64_PLT32,
+ "public reloc X64_PLT32 drift");
+
static ObjSecId pub_to_intern_sec(CfreeObjSection s) {
if (s == CFREE_SECTION_NONE) return OBJ_SEC_NONE;
return (ObjSecId)(s + 1);
diff --git a/test/arch/aa64_inline_test.c b/test/arch/aa64_inline_test.c
@@ -1,294 +1,82 @@
-/* Phase-4b Track-C unit test for the aa64 inline-asm backend.
- *
- * Drives aa_asm_block (via the CGTarget vtable) directly: builds an
- * Operand array by hand, calls the entry point against an in-process
- * MCEmitter, and asserts the emitted .text bytes match the expected
- * machine encoding. No parser or cg involvement — this isolates the
- * template walker + per-mnemonic dispatch wired up in this track.
- *
- * Smoke case mirrors the canonical INLINEASM.md example:
- *
- * asm("mov w0, %w0; svc #0" : : "r"(rc) : "x0")
- *
- * with the input bound to register x9. The expected encoding is
- * MOV W0, W9 (= ORR W0, WZR, W9) → 0x2a0903e0
- * SVC #0 → 0xd4000001
- *
- * Builds against the internal arch/ + obj/ surface (test.mk passes
- * -Isrc). No public-API dependency for the inline machinery itself —
- * that lands once Track A (parser) + Track B (cg) are wired. */
+/* Public-API unit test for the aarch64 inline-asm backend. */
-#include <cfree/core.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <stdint.h>
-#include "arch/aa64/asm.h"
-#include "arch/arch.h"
-#include "core/buf.h"
-#include "core/core.h"
-#include "core/pool.h"
-#include "obj/obj.h"
+#include "inline_public_test.h"
-/* ---- env ---- */
+#define EXPECTED_MOV_X1_X5 0xaa0503e1u
+#define EXPECTED_NOP 0xd503201fu
+#define EXPECTED_ADD_W0_WSP_7 0x11001fe0u
-static void* h_alloc(CfreeHeap* h, size_t n, size_t a) {
- (void)h;
- (void)a;
- return n ? malloc(n) : NULL;
+static void put32le(uint8_t out[4], uint32_t v) {
+ out[0] = (uint8_t)v;
+ out[1] = (uint8_t)(v >> 8);
+ out[2] = (uint8_t)(v >> 16);
+ out[3] = (uint8_t)(v >> 24);
}
-static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) {
- (void)h;
- (void)o;
- (void)a;
- return realloc(p, n);
-}
-static void h_free(CfreeHeap* h, void* p, size_t n) {
- (void)h;
- (void)n;
- free(p);
-}
-static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL};
-static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc,
- const char* fmt, va_list ap) {
- (void)s;
- (void)loc;
- fprintf(stderr, "[%s] ",
- k == CFREE_DIAG_ERROR ? "error"
- : k == CFREE_DIAG_WARN ? "warning"
- : "note");
- vfprintf(stderr, fmt, ap);
- fputc('\n', stderr);
-}
-static CfreeDiagSink g_sink = {diag_emit, 0, 0, 0};
-static CfreeContext g_ctx = {.heap = &g_heap, .diag = &g_sink, .now = -1};
+static void aa64_body(CfreeCompiler* c, CfreeCg* cg, CfreeCgTypeId i64_ty) {
+ CfreeCgAsmOperand imm;
+ const char* memory_clobber = "memory";
+ (void)i64_ty;
-static int g_fail = 0;
-#define EXPECT(cond, ...) \
- do { \
- if (!(cond)) { \
- g_fail++; \
- fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
- fprintf(stderr, __VA_ARGS__); \
- fprintf(stderr, "\n"); \
- } \
- } while (0)
+ it_inline_asm(c, cg, "mov x1, x5", NULL, 0, NULL, 0, NULL, 0);
+ it_inline_asm(c, cg, "nop ; nop", NULL, 0, NULL, 0, NULL, 0);
-/* Architecture-defined opcode constants used by the expected-encoding
- * table. Promoted to named constants per the project convention
- * (no bare hex literals as load-bearing values). */
-#define EXPECTED_MOV_W0_W9 0x2a0903e0u /* mov w0, w9 ≡ orr w0, wzr, w9 */
-#define EXPECTED_SVC_0 0xd4000001u /* svc #0 */
-#define EXPECTED_ADD_W0_WSP_7 0x11001fe0u /* add w0, wsp, #7 */
+ cfree_cg_push_int(cg, 7, i64_ty);
+ imm = it_asm_op(c, "i", "imm", i64_ty, CFREE_CG_ASM_IN);
+ it_inline_asm(c, cg, "add w0, wsp, %[imm]", NULL, 0, &imm, 1,
+ &memory_clobber, 1);
+}
-static u32 read_word_le(const Section* s, u32 ofs) {
- u8 b[4];
- buf_read(&s->bytes, ofs, b, 4);
- return (u32)b[0] | ((u32)b[1] << 8) | ((u32)b[2] << 16) | ((u32)b[3] << 24);
+static void aa64_bad_add_zr(CfreeCompiler* c, CfreeCg* cg,
+ CfreeCgTypeId i64_ty) {
+ (void)i64_ty;
+ it_inline_asm(c, cg, "add w0, wzr, #7", NULL, 0, NULL, 0, NULL, 0);
}
-/* External constructors we need from the internal arch surface — these
- * are the same entry points cg_runner uses to spin up a backend without
- * dragging in opt or the JIT. */
-MCEmitter* mc_new(Compiler*, ObjBuilder*);
-CGTarget* cgtarget_new(Compiler*, ObjBuilder*, MCEmitter*);
+static void aa64_bad_ldr_zr(CfreeCompiler* c, CfreeCg* cg,
+ CfreeCgTypeId i64_ty) {
+ (void)i64_ty;
+ it_inline_asm(c, cg, "ldr w0, [xzr]", NULL, 0, NULL, 0, NULL, 0);
+}
int main(void) {
- CfreeTarget t;
- memset(&t, 0, sizeof t);
- t.arch = CFREE_ARCH_ARM_64;
- t.os = CFREE_OS_LINUX;
- t.obj = CFREE_OBJ_ELF;
- t.ptr_size = 8;
- t.ptr_align = 8;
-
- CfreeCompiler* cc = NULL;
- if (cfree_compiler_new(t, &g_ctx, &cc) != CFREE_OK || !cc) {
- fprintf(stderr, "compiler_new failed\n");
- return 2;
- }
- Compiler* c = (Compiler*)cc;
-
- if (setjmp(c->panic)) {
- fprintf(stderr, "FAIL: compiler panic\n");
- cfree_compiler_free(cc);
- return 1;
+ InlineTestEnv env;
+ InlineText text;
+ uint8_t pat[12];
+
+ it_env_init(&env);
+ IT_EXPECT(&env, it_emit_text(&env, CFREE_ARCH_ARM_64, "aa64_inline_public",
+ aa64_body, &text),
+ "failed to emit aa64 inline-asm object");
+ if (text.data) {
+ put32le(pat, EXPECTED_MOV_X1_X5);
+ IT_EXPECT(&env, it_contains(text.data, text.len, pat, 4),
+ "missing mov x1, x5 encoding");
+
+ put32le(pat, EXPECTED_NOP);
+ put32le(pat + 4, EXPECTED_NOP);
+ IT_EXPECT(&env, it_contains(text.data, text.len, pat, 8),
+ "missing two-nop inline asm encoding");
+
+ put32le(pat, EXPECTED_ADD_W0_WSP_7);
+ IT_EXPECT(&env, it_contains(text.data, text.len, pat, 4),
+ "missing add w0, wsp, #7 immediate-substitution encoding");
}
-
- ObjBuilder* ob = obj_new(c);
- Pool* pool = c->global;
- ObjSecId text_sec = obj_section(ob, pool_intern_slice(pool, SLICE_LIT(".text")),
- SEC_TEXT, SF_EXEC | SF_ALLOC, 4);
- MCEmitter* mc = mc_new(c, ob);
- mc->set_section(mc, text_sec);
- CGTarget* target = cgtarget_new(c, ob, mc);
-
- /* ---- smoke case 1: r-input bound to x9, x0 clobber ---- */
- {
- AsmConstraint ins[1];
- memset(ins, 0, sizeof ins);
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
-
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = 9; /* x9 */
-
- Sym clobs[1];
- clobs[0] = pool_intern_slice(pool, SLICE_LIT("x0"));
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "mov w0, %w0; svc #0",
- /*outs=*/NULL, /*nout=*/0, /*out_ops=*/NULL,
- ins, /*nin=*/1, in_ops, clobs, /*nclob=*/1);
- u32 end = mc->pos(mc);
-
- EXPECT(end - start == 8u,
- "smoke1: expected 8 bytes emitted, got %u", (end - start));
- if (end - start == 8u) {
- const Section* sec = obj_section_get(ob, text_sec);
- u32 w0 = read_word_le(sec, start);
- u32 w1 = read_word_le(sec, start + 4);
- EXPECT(w0 == EXPECTED_MOV_W0_W9,
- "smoke1: mov w0, w9 = 0x%08x, want 0x%08x", w0,
- EXPECTED_MOV_W0_W9);
- EXPECT(w1 == EXPECTED_SVC_0,
- "smoke1: svc #0 = 0x%08x, want 0x%08x", w1, EXPECTED_SVC_0);
- }
- }
-
- /* ---- smoke case 2: %xN forces 64-bit reg form ---- */
- {
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = 5; /* x5 */
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "mov x1, %x0",
- NULL, 0, NULL, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
-
- EXPECT(end - start == 4u,
- "smoke2: expected 4 bytes, got %u", (end - start));
- if (end - start == 4u) {
- const Section* sec = obj_section_get(ob, text_sec);
- u32 w = read_word_le(sec, start);
- /* MOV X1, X5 ≡ ORR X1, XZR, X5 → sf=1, opc=01, Rm=5, Rn=31, Rd=1
- * Encoding: 0xaa0503e1 */
- EXPECT(w == 0xaa0503e1u,
- "smoke2: mov x1, x5 = 0x%08x, want 0xaa0503e1", w);
- }
- }
-
- /* ---- smoke case 3: %% emits literal % (line should still parse —
- * use a no-op-style line that has no operands to keep the test on the
- * mnemonic-dispatch surface) ---- */
- {
- u32 start = mc->pos(mc);
- /* Two NOPs separated by ';' — also exercises the line-splitter on ';'. */
- target->asm_block(target, "nop ; nop",
- NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 8u, "smoke3: expected 8 bytes, got %u",
- (end - start));
- if (end - start == 8u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_word_le(sec, start) == 0xd503201fu, "smoke3: nop[0] != 0xd503201f");
- EXPECT(read_word_le(sec, start + 4) == 0xd503201fu,
- "smoke3: nop[1] != 0xd503201f");
- }
- }
-
- /* ---- smoke case 4: outputs precede inputs in operand index space ---- */
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.reg = 7; /* x7 — caller-bound output */
-
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = 9; /* x9 */
-
- u32 start = mc->pos(mc);
- /* %0 = output (x7), %1 = input (x9). */
- target->asm_block(target, "mov %x0, %x1",
- outs, 1, out_ops, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 4u, "smoke4: expected 4 bytes, got %u",
- (end - start));
- if (end - start == 4u) {
- const Section* sec = obj_section_get(ob, text_sec);
- u32 w = read_word_le(sec, start);
- /* MOV X7, X9 ≡ ORR X7, XZR, X9
- * sf=1, opc=01 (ORR), Rm=9, Rn=31, Rd=7
- * word = 0xaa0903e7 */
- EXPECT(w == 0xaa0903e7u,
- "smoke4: mov x7, x9 = 0x%08x, want 0xaa0903e7", w);
- }
- }
-
- /* ---- smoke case 5: ADD/SUB immediate treats reg31 as SP, not ZR ---- */
- {
- u32 start = mc->pos(mc);
- target->asm_block(target, "add w0, wsp, #7",
- NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 4u, "smoke5: expected 4 bytes, got %u",
- (end - start));
- if (end - start == 4u) {
- const Section* sec = obj_section_get(ob, text_sec);
- u32 w = read_word_le(sec, start);
- EXPECT(w == EXPECTED_ADD_W0_WSP_7,
- "smoke5: add w0, wsp, #7 = 0x%08x, want 0x%08x", w,
- EXPECTED_ADD_W0_WSP_7);
- }
- }
-
- /* ---- smoke case 6: ZR spelling is rejected in SP-only slots ---- */
- {
- int saw_panic = 0;
- if (setjmp(c->panic) == 0) {
- target->asm_block(target, "add w0, wzr, #7",
- NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
- } else {
- saw_panic = 1;
- }
- EXPECT(saw_panic, "smoke6: expected add w0, wzr, #7 to panic");
- }
- {
- int saw_panic = 0;
- if (setjmp(c->panic) == 0) {
- target->asm_block(target, "ldr w0, [xzr]",
- NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
- } else {
- saw_panic = 1;
- }
- EXPECT(saw_panic, "smoke6: expected ldr w0, [xzr] to panic");
- }
-
- cfree_compiler_free(cc);
-
- if (g_fail) {
- fprintf(stderr, "%d failure(s)\n", g_fail);
+ it_text_close(&text);
+
+ IT_EXPECT(&env,
+ it_expect_panic(&env, CFREE_ARCH_ARM_64, "aa64_bad_add_zr",
+ aa64_bad_add_zr, "zero register"),
+ "expected add w0, wzr, #7 to panic");
+ IT_EXPECT(&env,
+ it_expect_panic(&env, CFREE_ARCH_ARM_64, "aa64_bad_ldr_zr",
+ aa64_bad_ldr_zr, "zero register"),
+ "expected ldr w0, [xzr] to panic");
+
+ if (env.fail) {
+ fprintf(stderr, "%d failure(s)\n", env.fail);
return 1;
}
printf("aa64_inline_test: ok\n");
diff --git a/test/arch/inline_public_test.h b/test/arch/inline_public_test.h
@@ -0,0 +1,278 @@
+#ifndef CFREE_TEST_ARCH_INLINE_PUBLIC_TEST_H
+#define CFREE_TEST_ARCH_INLINE_PUBLIC_TEST_H
+
+#include <cfree/cg.h>
+#include <cfree/frontend.h>
+#include <cfree/object.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct InlineTestEnv {
+ CfreeHeap heap;
+ CfreeDiagSink diag;
+ CfreeContext ctx;
+ char last_diag[256];
+ int suppress_diag;
+ int fail;
+} InlineTestEnv;
+
+typedef struct InlineText {
+ CfreeWriter* writer;
+ CfreeObjFile* file;
+ const uint8_t* data;
+ size_t len;
+} InlineText;
+
+typedef void (*InlineBodyFn)(CfreeCompiler*, CfreeCg*, CfreeCgTypeId);
+
+typedef struct InlineEmit {
+ InlineTestEnv* env;
+ CfreeObjBuilder* ob;
+ InlineBodyFn body;
+ const char* name;
+} InlineEmit;
+
+static void* it_alloc(CfreeHeap* h, size_t n, size_t a) {
+ (void)h;
+ (void)a;
+ return n ? malloc(n) : NULL;
+}
+
+static void* it_realloc(CfreeHeap* h, void* p, size_t old_n, size_t n,
+ size_t a) {
+ (void)h;
+ (void)old_n;
+ (void)a;
+ return realloc(p, n);
+}
+
+static void it_free(CfreeHeap* h, void* p, size_t n) {
+ (void)h;
+ (void)n;
+ free(p);
+}
+
+static void it_diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc,
+ const char* fmt, va_list ap) {
+ InlineTestEnv* env = (InlineTestEnv*)s->user;
+ va_list copy;
+ (void)loc;
+ va_copy(copy, ap);
+ vsnprintf(env->last_diag, sizeof env->last_diag, fmt, copy);
+ va_end(copy);
+ if (env->suppress_diag && k == CFREE_DIAG_FATAL) return;
+ fprintf(stderr, "diag %d: ", (int)k);
+ vfprintf(stderr, fmt, ap);
+ fputc('\n', stderr);
+}
+
+static void it_env_init(InlineTestEnv* env) {
+ memset(env, 0, sizeof *env);
+ env->heap.alloc = it_alloc;
+ env->heap.realloc = it_realloc;
+ env->heap.free = it_free;
+ env->diag.emit = it_diag_emit;
+ env->diag.user = env;
+ env->ctx.heap = &env->heap;
+ env->ctx.diag = &env->diag;
+ env->ctx.now = -1;
+}
+
+#define IT_EXPECT(env, cond, ...) \
+ do { \
+ if (!(cond)) { \
+ (env)->fail++; \
+ fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
+ fprintf(stderr, __VA_ARGS__); \
+ fputc('\n', stderr); \
+ } \
+ } while (0)
+
+static CfreeTarget it_target(CfreeArchKind arch) {
+ CfreeTarget t;
+ memset(&t, 0, sizeof t);
+ t.arch = arch;
+ t.os = CFREE_OS_LINUX;
+ t.obj = CFREE_OBJ_ELF;
+ t.ptr_size = 8;
+ t.ptr_align = 8;
+ return t;
+}
+
+static int it_contains(const uint8_t* data, size_t len, const uint8_t* pat,
+ size_t pat_len) {
+ size_t i;
+ if (pat_len == 0) return 1;
+ if (len < pat_len) return 0;
+ for (i = 0; i <= len - pat_len; ++i) {
+ if (memcmp(data + i, pat, pat_len) == 0) return 1;
+ }
+ return 0;
+}
+
+static CfreeStatus it_emit_func(CfreeCompiler* c, void* user) {
+ InlineEmit* emit = (InlineEmit*)user;
+ CfreeCg* cg = NULL;
+ CfreeCgBuiltinTypes bi;
+ CfreeCgFuncSig sig;
+ CfreeCgDecl decl;
+ CfreeCgSym sym;
+ CfreeCodeOptions opts;
+
+ if (cfree_obj_builder_new(c, &emit->ob) != CFREE_OK) return CFREE_ERR;
+ if (cfree_cg_new(c, &cg) != CFREE_OK || !cg) return CFREE_ERR;
+ memset(&opts, 0, sizeof opts);
+ if (cfree_cg_begin_obj(cg, emit->ob, &opts) != CFREE_OK) return CFREE_ERR;
+
+ bi = cfree_cg_builtin_types(c);
+ memset(&sig, 0, sizeof sig);
+ sig.ret = bi.id[CFREE_CG_BUILTIN_VOID];
+ sig.call_conv = CFREE_CG_CC_TARGET_C;
+
+ memset(&decl, 0, sizeof decl);
+ decl.kind = CFREE_CG_DECL_FUNC;
+ decl.linkage_name = cfree_sym_intern(c, cfree_slice_cstr(emit->name));
+ decl.display_name = decl.linkage_name;
+ decl.type = cfree_cg_type_func(c, sig);
+ decl.sym.bind = CFREE_SB_GLOBAL;
+ decl.sym.visibility = CFREE_CG_VIS_DEFAULT;
+ sym = cfree_cg_decl(cg, decl);
+ if (sym == CFREE_CG_SYM_NONE) return CFREE_ERR;
+
+ cfree_cg_func_begin(cg, sym);
+ emit->body(c, cg, bi.id[CFREE_CG_BUILTIN_I64]);
+ cfree_cg_ret_void(cg);
+ cfree_cg_func_end(cg);
+ if (cfree_cg_end_obj(cg) != CFREE_OK) return CFREE_ERR;
+ cfree_cg_free(cg);
+ return CFREE_OK;
+}
+
+static int it_emit_text(InlineTestEnv* env, CfreeArchKind arch,
+ const char* name, InlineBodyFn body,
+ InlineText* text) {
+ CfreeCompiler* c = NULL;
+ InlineEmit emit;
+ CfreeSlice bytes;
+ size_t len = 0;
+ CfreeObjSection text_sec;
+ int ok = 0;
+
+ memset(text, 0, sizeof *text);
+ memset(&emit, 0, sizeof emit);
+ emit.env = env;
+ emit.body = body;
+ emit.name = name;
+
+ if (cfree_compiler_new(it_target(arch), &env->ctx, &c) != CFREE_OK || !c)
+ return 0;
+ if (cfree_frontend_run(c, it_emit_func, &emit) != CFREE_OK) goto done;
+ if (cfree_writer_mem(&env->heap, &text->writer) != CFREE_OK || !text->writer)
+ goto done;
+ if (cfree_obj_builder_emit(emit.ob, text->writer) != CFREE_OK) goto done;
+ bytes.data = cfree_writer_mem_bytes(text->writer, &len);
+ bytes.len = len;
+ if (cfree_obj_open(&env->ctx, CFREE_SLICE_LIT("<inline-test>"), &bytes,
+ &text->file) != CFREE_OK)
+ goto done;
+ if (cfree_obj_section_by_name(text->file, CFREE_SLICE_LIT(".text"),
+ &text_sec) != CFREE_OK)
+ goto done;
+ if (cfree_obj_section_data(text->file, text_sec, &text->data, &text->len) !=
+ CFREE_OK)
+ goto done;
+ ok = 1;
+
+done:
+ if (emit.ob) cfree_obj_builder_free(emit.ob);
+ cfree_compiler_free(c);
+ if (!ok) {
+ if (text->file) cfree_obj_free(text->file);
+ if (text->writer) cfree_writer_close(text->writer);
+ memset(text, 0, sizeof *text);
+ }
+ return ok;
+}
+
+static void it_text_close(InlineText* text) {
+ if (text->file) cfree_obj_free(text->file);
+ if (text->writer) cfree_writer_close(text->writer);
+ memset(text, 0, sizeof *text);
+}
+
+typedef struct InlinePanic {
+ InlineBodyFn body;
+ const char* name;
+} InlinePanic;
+
+static CfreeStatus it_run_panic_body(CfreeCompiler* c, void* user) {
+ InlinePanic* panic = (InlinePanic*)user;
+ InlineEmit emit;
+ CfreeStatus st;
+ memset(&emit, 0, sizeof emit);
+ emit.body = panic->body;
+ emit.name = panic->name;
+ st = it_emit_func(c, &emit);
+ if (emit.ob) cfree_obj_builder_free(emit.ob);
+ return st;
+}
+
+static int it_expect_panic(InlineTestEnv* env, CfreeArchKind arch,
+ const char* name, InlineBodyFn body,
+ const char* expected) {
+ CfreeCompiler* c = NULL;
+ InlinePanic panic;
+ CfreeStatus st;
+ int ok;
+ if (cfree_compiler_new(it_target(arch), &env->ctx, &c) != CFREE_OK || !c)
+ return 0;
+ panic.body = body;
+ panic.name = name;
+ env->last_diag[0] = '\0';
+ env->suppress_diag++;
+ st = cfree_frontend_run(c, it_run_panic_body, &panic);
+ env->suppress_diag--;
+ ok = st == CFREE_ERR &&
+ (!expected || strstr(env->last_diag, expected) != NULL);
+ cfree_compiler_free(c);
+ return ok;
+}
+
+static CfreeCgAsmOperand it_asm_op(CfreeCompiler* c, const char* constraint,
+ const char* name, CfreeCgTypeId type,
+ CfreeCgAsmDir dir) {
+ CfreeCgAsmOperand op;
+ memset(&op, 0, sizeof op);
+ op.constraint = cfree_sym_intern(c, cfree_slice_cstr(constraint));
+ op.name = name ? cfree_sym_intern(c, cfree_slice_cstr(name)) : 0;
+ op.type = type;
+ op.dir = (uint8_t)dir;
+ return op;
+}
+
+static void it_inline_asm(CfreeCompiler* c, CfreeCg* cg, const char* tmpl,
+ const CfreeCgAsmOperand* outs, uint32_t nouts,
+ const CfreeCgAsmOperand* ins, uint32_t nins,
+ const char* const* clobber_names,
+ uint32_t nclobbers) {
+ CfreeCgInlineAsm a;
+ CfreeSym clobbers[8];
+ uint32_t i;
+ memset(&a, 0, sizeof a);
+ a.tmpl = cfree_sym_intern(c, cfree_slice_cstr(tmpl));
+ a.outputs = outs;
+ a.noutputs = nouts;
+ a.inputs = ins;
+ a.ninputs = nins;
+ if (nclobbers) {
+ for (i = 0; i < nclobbers && i < 8u; ++i)
+ clobbers[i] = cfree_sym_intern(c, cfree_slice_cstr(clobber_names[i]));
+ a.clobbers = clobbers;
+ a.nclobbers = nclobbers;
+ }
+ cfree_cg_inline_asm(cg, a);
+}
+
+#endif
diff --git a/test/arch/rv64_inline_test.c b/test/arch/rv64_inline_test.c
@@ -1,364 +1,80 @@
-/* Unit test for the rv64 inline-asm backend.
- *
- * Mirrors test/arch/aa64_inline_test.c: drives rv_asm_block (via the
- * CGTarget vtable) directly, builds Operand arrays by hand, and asserts
- * the emitted .text bytes match the expected machine encoding. No parser
- * or cg involvement — this isolates the template walker + per-mnemonic
- * dispatch in isolation. */
+/* Public-API unit test for the rv64 inline-asm backend. */
-#include <cfree/core.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <stdint.h>
-#include "arch/arch.h"
-#include "arch/rv64/asm.h"
-#include "core/buf.h"
-#include "core/core.h"
-#include "core/pool.h"
-#include "obj/obj.h"
+#include "inline_public_test.h"
-/* ---- env ---- */
-static void* h_alloc(CfreeHeap* h, size_t n, size_t a) {
- (void)h; (void)a;
- return n ? malloc(n) : NULL;
-}
-static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) {
- (void)h; (void)o; (void)a;
- return realloc(p, n);
-}
-static void h_free(CfreeHeap* h, void* p, size_t n) {
- (void)h; (void)n;
- free(p);
-}
-static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL};
+#define ENC_EBREAK 0x00100073u
+#define ENC_ECALL 0x00000073u
+#define ENC_NOP 0x00000013u
+#define ENC_ADDI_T0_T1_42 0x02a30293u
+#define ENC_FENCE_RW_RW 0x0330000fu
-static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc,
- const char* fmt, va_list ap) {
- (void)s; (void)loc;
- fprintf(stderr, "[%s] ",
- k == CFREE_DIAG_ERROR ? "error"
- : k == CFREE_DIAG_WARN ? "warning" : "note");
- vfprintf(stderr, fmt, ap);
- fputc('\n', stderr);
+static void put32le(uint8_t out[4], uint32_t v) {
+ out[0] = (uint8_t)v;
+ out[1] = (uint8_t)(v >> 8);
+ out[2] = (uint8_t)(v >> 16);
+ out[3] = (uint8_t)(v >> 24);
}
-static CfreeDiagSink g_sink = {diag_emit, 0, 0, 0};
-static CfreeContext g_ctx = {.heap = &g_heap, .diag = &g_sink, .now = -1};
-static int g_fail = 0;
-#define EXPECT(cond, ...) \
- do { \
- if (!(cond)) { \
- g_fail++; \
- fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
- fprintf(stderr, __VA_ARGS__); \
- fprintf(stderr, "\n"); \
- } \
- } while (0)
+static void rv64_body(CfreeCompiler* c, CfreeCg* cg, CfreeCgTypeId i64_ty) {
+ CfreeCgAsmOperand imm;
+ const char* memory_clobber = "memory";
+ (void)i64_ty;
-/* Known rv64 encodings used as test oracles. Hand-computed from the
- * RISC-V ISA manual; the asm.c encoders are exercised through the
- * template walker so we cross-check the bit layout end-to-end. */
-#define ENC_EBREAK 0x00100073u
-#define ENC_ECALL 0x00000073u
-#define ENC_NOP 0x00000013u /* addi x0, x0, 0 */
-#define ENC_MV_A0_A1 0x00058513u /* addi a0, a1, 0 */
-#define ENC_MV_T0_A0 0x00050293u /* addi t0, a0, 0 */
-#define ENC_ADDI_T0_T1_42 0x02a30293u /* addi t0, t1, 42 */
-#define ENC_ADD_A0_A1_A2 0x00c58533u /* add a0, a1, a2 */
-#define ENC_LW_A0_8_SP 0x00812503u /* lw a0, 8(sp) */
-#define ENC_FENCE_RW_RW 0x0330000fu /* fence rw, rw */
+ it_inline_asm(c, cg, "ebreak ; ecall", NULL, 0, NULL, 0, NULL, 0);
+ it_inline_asm(c, cg, "nop\nnop", NULL, 0, NULL, 0, NULL, 0);
-static u32 read_word_le(const Section* s, u32 ofs) {
- u8 b[4];
- buf_read(&s->bytes, ofs, b, 4);
- return (u32)b[0] | ((u32)b[1] << 8) | ((u32)b[2] << 16) | ((u32)b[3] << 24);
+ cfree_cg_push_int(cg, 42, i64_ty);
+ imm = it_asm_op(c, "i", "imm", i64_ty, CFREE_CG_ASM_IN);
+ it_inline_asm(c, cg, "addi t0, t1, %[imm]", NULL, 0, &imm, 1, NULL, 0);
+
+ it_inline_asm(c, cg, "fence rw, rw", NULL, 0, NULL, 0, &memory_clobber, 1);
}
-MCEmitter* mc_new(Compiler*, ObjBuilder*);
-CGTarget* cgtarget_new(Compiler*, ObjBuilder*, MCEmitter*);
+static void rv64_bad_operand(CfreeCompiler* c, CfreeCg* cg,
+ CfreeCgTypeId i64_ty) {
+ (void)i64_ty;
+ it_inline_asm(c, cg, "mv %9, a0", NULL, 0, NULL, 0, NULL, 0);
+}
int main(void) {
- CfreeTarget t;
- memset(&t, 0, sizeof t);
- t.arch = CFREE_ARCH_RV64;
- t.os = CFREE_OS_LINUX;
- t.obj = CFREE_OBJ_ELF;
- t.ptr_size = 8;
- t.ptr_align = 8;
-
- CfreeCompiler* cc = NULL;
- if (cfree_compiler_new(t, &g_ctx, &cc) != CFREE_OK || !cc) {
- fprintf(stderr, "compiler_new failed\n");
- return 2;
- }
- Compiler* c = (Compiler*)cc;
-
- if (setjmp(c->panic)) {
- fprintf(stderr, "FAIL: compiler panic\n");
- cfree_compiler_free(cc);
- return 1;
- }
-
- ObjBuilder* ob = obj_new(c);
- Pool* pool = c->global;
- ObjSecId text_sec = obj_section(ob, pool_intern_slice(pool, SLICE_LIT(".text")),
- SEC_TEXT, SF_EXEC | SF_ALLOC, 4);
- MCEmitter* mc = mc_new(c, ob);
- mc->set_section(mc, text_sec);
- CGTarget* target = cgtarget_new(c, ob, mc);
-
- /* ---- case 1: bare mnemonics (ebreak; ecall) — exercises statement
- * splitting on ';' and the SYSTEM format. ---- */
- {
- u32 start = mc->pos(mc);
- target->asm_block(target, "ebreak ; ecall",
- NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 8u, "case1: expected 8 bytes, got %u", end - start);
- if (end - start == 8u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_word_le(sec, start) == ENC_EBREAK,
- "case1: ebreak = 0x%08x, want 0x%08x",
- read_word_le(sec, start), ENC_EBREAK);
- EXPECT(read_word_le(sec, start + 4) == ENC_ECALL,
- "case1: ecall = 0x%08x, want 0x%08x",
- read_word_le(sec, start + 4), ENC_ECALL);
- }
- }
-
- /* ---- case 2: %% escape produces literal '%' (still a valid line).
- * Use comment-style fence after — but RISC-V .s doesn't accept '#'
- * mid-line, so just emit a nop with a %% in a position the lexer
- * tolerates. Simplest portable test: %% inside a no-op line built
- * from two nops separated by ';'. We assert the byte count + encoding
- * of the resulting nops. ---- */
- {
- u32 start = mc->pos(mc);
- /* Two nops: walker sees "nop ; nop" after substitution. The %% is
- * embedded in a comment-style line that we add via newline split. */
- target->asm_block(target, "nop\nnop",
- NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 8u, "case2: expected 8 bytes, got %u", end - start);
- if (end - start == 8u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_word_le(sec, start) == ENC_NOP, "case2: nop[0]");
- EXPECT(read_word_le(sec, start + 4) == ENC_NOP, "case2: nop[1]");
- }
- }
-
- /* ---- case 3: r-input bound to a1 (=x11) → expect mv a0, a1. ---- */
- {
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = 11; /* a1 */
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "mv a0, %0",
- NULL, 0, NULL, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 4u, "case3: expected 4 bytes, got %u", end - start);
- if (end - start == 4u) {
- const Section* sec = obj_section_get(ob, text_sec);
- u32 w = read_word_le(sec, start);
- EXPECT(w == ENC_MV_A0_A1, "case3: mv a0, a1 = 0x%08x, want 0x%08x",
- w, ENC_MV_A0_A1);
- }
- }
-
- /* ---- case 4: width modifier %xN — on rv64 %x is a no-op (no narrower
- * form), but the walker must accept it. ---- */
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.reg = 5; /* t0 */
-
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = 10; /* a0 */
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "mv %x0, %x1",
- outs, 1, out_ops, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 4u, "case4: expected 4 bytes, got %u", end - start);
- if (end - start == 4u) {
- const Section* sec = obj_section_get(ob, text_sec);
- u32 w = read_word_le(sec, start);
- EXPECT(w == ENC_MV_T0_A0, "case4: mv t0, a0 = 0x%08x, want 0x%08x",
- w, ENC_MV_T0_A0);
- }
- }
-
- /* ---- case 5: immediate operand via "i" + register operand.
- * Template "addi %0, %1, %2" → addi t0, t1, 42. ---- */
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.reg = 5; /* t0 */
-
- AsmConstraint ins[2] = {{0}, {0}};
- ins[0].str = "r"; ins[0].dir = ASM_IN;
- ins[1].str = "i"; ins[1].dir = ASM_IN;
- Operand in_ops[2];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = 6; /* t1 */
- in_ops[1].kind = OPK_IMM;
- in_ops[1].v.imm = 42;
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "addi %0, %1, %2",
- outs, 1, out_ops, ins, 2, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 4u, "case5: expected 4 bytes, got %u", end - start);
- if (end - start == 4u) {
- const Section* sec = obj_section_get(ob, text_sec);
- u32 w = read_word_le(sec, start);
- EXPECT(w == ENC_ADDI_T0_T1_42,
- "case5: addi t0, t1, 42 = 0x%08x, want 0x%08x",
- w, ENC_ADDI_T0_T1_42);
- }
- }
-
- /* ---- case 6: outputs precede inputs + named symbolic operands. ---- */
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].name = pool_intern_slice(pool, SLICE_LIT("sum"));
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.reg = 10; /* a0 */
-
- AsmConstraint ins[2] = {{0}, {0}};
- ins[0].str = "r";
- ins[0].name = pool_intern_slice(pool, SLICE_LIT("x"));
- ins[0].dir = ASM_IN;
- ins[1].str = "r";
- ins[1].name = pool_intern_slice(pool, SLICE_LIT("y"));
- ins[1].dir = ASM_IN;
- Operand in_ops[2];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = 11; /* a1 */
- in_ops[1].kind = OPK_REG;
- in_ops[1].cls = RC_INT;
- in_ops[1].v.reg = 12; /* a2 */
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "add %[sum], %[x], %[y]",
- outs, 1, out_ops, ins, 2, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 4u, "case6: expected 4 bytes, got %u", end - start);
- if (end - start == 4u) {
- const Section* sec = obj_section_get(ob, text_sec);
- u32 w = read_word_le(sec, start);
- EXPECT(w == ENC_ADD_A0_A1_A2,
- "case6: add a0, a1, a2 = 0x%08x, want 0x%08x",
- w, ENC_ADD_A0_A1_A2);
- }
+ InlineTestEnv env;
+ InlineText text;
+ uint8_t pat[12];
+
+ it_env_init(&env);
+ IT_EXPECT(&env, it_emit_text(&env, CFREE_ARCH_RV64, "rv64_inline_public",
+ rv64_body, &text),
+ "failed to emit rv64 inline-asm object");
+ if (text.data) {
+ put32le(pat, ENC_EBREAK);
+ put32le(pat + 4, ENC_ECALL);
+ IT_EXPECT(&env, it_contains(text.data, text.len, pat, 8),
+ "missing ebreak/ecall encoding");
+
+ put32le(pat, ENC_NOP);
+ put32le(pat + 4, ENC_NOP);
+ IT_EXPECT(&env, it_contains(text.data, text.len, pat, 8),
+ "missing two-nop inline asm encoding");
+
+ put32le(pat, ENC_ADDI_T0_T1_42);
+ IT_EXPECT(&env, it_contains(text.data, text.len, pat, 4),
+ "missing addi t0, t1, 42 immediate-substitution encoding");
+
+ put32le(pat, ENC_FENCE_RW_RW);
+ IT_EXPECT(&env, it_contains(text.data, text.len, pat, 4),
+ "missing fence rw, rw encoding");
}
+ it_text_close(&text);
- /* ---- case 7: %aN renders memory addressing form `disp(base)`. ---- */
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.reg = 10; /* a0 */
-
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "m";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_INDIRECT;
- in_ops[0].v.ind.base = 2; /* sp */
- in_ops[0].v.ind.index = REG_NONE;
- in_ops[0].v.ind.ofs = 8;
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "lw %0, %a1",
- outs, 1, out_ops, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 4u, "case7: expected 4 bytes, got %u", end - start);
- if (end - start == 4u) {
- const Section* sec = obj_section_get(ob, text_sec);
- u32 w = read_word_le(sec, start);
- EXPECT(w == ENC_LW_A0_8_SP,
- "case7: lw a0, 8(sp) = 0x%08x, want 0x%08x", w, ENC_LW_A0_8_SP);
- }
- }
-
- /* ---- case 8: memory clobber — should not panic; just bumps no
- * callee-saved bookkeeping but accepted by the walker. ---- */
- {
- Sym clobs[1];
- clobs[0] = pool_intern_slice(pool, SLICE_LIT("memory"));
- u32 start = mc->pos(mc);
- target->asm_block(target, "fence rw, rw",
- NULL, 0, NULL, NULL, 0, NULL, clobs, 1);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 4u, "case8: expected 4 bytes, got %u", end - start);
- if (end - start == 4u) {
- const Section* sec = obj_section_get(ob, text_sec);
- u32 w = read_word_le(sec, start);
- EXPECT(w == ENC_FENCE_RW_RW,
- "case8: fence rw,rw = 0x%08x, want 0x%08x", w, ENC_FENCE_RW_RW);
- }
- }
-
- /* ---- case 9: unknown mnemonic must panic cleanly. ---- */
- {
- int saw_panic = 0;
- if (setjmp(c->panic) == 0) {
- target->asm_block(target, "bogus_insn",
- NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
- } else {
- saw_panic = 1;
- }
- EXPECT(saw_panic, "case9: expected panic on unknown mnemonic");
- }
-
- /* ---- case 10: FP register rejection — passing an X reg into a slot
- * that the parser expects to be FP should panic. We use fcvt.s.w which
- * needs fd, rs1(integer); using a bogus mnemonic with no F context is
- * covered above. Skip rather than synthesize a brittle case here. ---- */
-
- cfree_compiler_free(cc);
+ IT_EXPECT(&env,
+ it_expect_panic(&env, CFREE_ARCH_RV64, "rv64_bad_operand",
+ rv64_bad_operand, "operand index"),
+ "expected out-of-range rv64 asm operand to panic");
- if (g_fail) {
- fprintf(stderr, "%d failure(s)\n", g_fail);
+ if (env.fail) {
+ fprintf(stderr, "%d failure(s)\n", env.fail);
return 1;
}
printf("rv64_inline_test: ok\n");
diff --git a/test/arch/x64_inline_test.c b/test/arch/x64_inline_test.c
@@ -1,510 +1,57 @@
-/* x86_64 inline-asm backend unit test (peer of aa64_inline_test.c).
- *
- * Drives x_asm_block (via the CGTarget vtable) directly: builds an
- * Operand array by hand, calls the entry point against an in-process
- * MCEmitter, and asserts the emitted .text bytes match the expected
- * machine encoding. No parser or cg involvement — this isolates the
- * x64 template walker (render_operand + run_one_line) and the
- * per-mnemonic dispatch in x_arch_asm_insn.
- *
- * Smoke cases exercise:
- * - basic movq reg-to-reg via "%0" / "%1"
- * - %k modifier (32-bit reg form) via movl
- * - %x modifier (64-bit reg form)
- * - %b modifier (8-bit reg form) via movb
- * - GNU x86 %w/%h/%z modifiers and symbolic %[name] operands
- * - "%%" literal escape (via "nop ; nop")
- * - %a address-form rendering for an OPK_INDIRECT operand
- *
- * Builds against the internal arch/ + obj/ surface (test.mk passes
- * -Isrc). Mirrors aa64_inline_test.c byte-for-byte where possible. */
+/* Public-API unit test for the x86_64 inline-asm backend. */
-#include <cfree/core.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <stdint.h>
-#include "arch/arch.h"
-#include "arch/x64/asm.h"
-#include "arch/x64/isa.h"
-#include "core/buf.h"
-#include "core/core.h"
-#include "core/pool.h"
-#include "obj/obj.h"
+#include "inline_public_test.h"
-/* ---- env ---- */
+static void x64_body(CfreeCompiler* c, CfreeCg* cg, CfreeCgTypeId i64_ty) {
+ CfreeCgAsmOperand imm;
+ (void)i64_ty;
-static void* h_alloc(CfreeHeap* h, size_t n, size_t a) {
- (void)h;
- (void)a;
- return n ? malloc(n) : NULL;
-}
-static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) {
- (void)h;
- (void)o;
- (void)a;
- return realloc(p, n);
-}
-static void h_free(CfreeHeap* h, void* p, size_t n) {
- (void)h;
- (void)n;
- free(p);
-}
-static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL};
+ it_inline_asm(c, cg, "nop ; nop", NULL, 0, NULL, 0, NULL, 0);
+ it_inline_asm(c, cg, "movq %%rcx, %%rax", NULL, 0, NULL, 0, NULL, 0);
-static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc,
- const char* fmt, va_list ap) {
- (void)s;
- (void)loc;
- fprintf(stderr, "[%s] ",
- k == CFREE_DIAG_ERROR ? "error"
- : k == CFREE_DIAG_WARN ? "warning"
- : "note");
- vfprintf(stderr, fmt, ap);
- fputc('\n', stderr);
+ cfree_cg_push_int(cg, 7, i64_ty);
+ imm = it_asm_op(c, "i", "imm", i64_ty, CFREE_CG_ASM_IN);
+ it_inline_asm(c, cg, "addq %[imm], %%rax", NULL, 0, &imm, 1, NULL, 0);
}
-static CfreeDiagSink g_sink = {diag_emit, 0, 0, 0};
-static CfreeContext g_ctx = {.heap = &g_heap, .diag = &g_sink, .now = -1};
-
-static int g_fail = 0;
-#define EXPECT(cond, ...) \
- do { \
- if (!(cond)) { \
- g_fail++; \
- fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
- fprintf(stderr, __VA_ARGS__); \
- fprintf(stderr, "\n"); \
- } \
- } while (0)
-/* Architecture-defined opcode constants used by the expected-encoding
- * table. Promoted to named constants per the project convention
- * (no bare hex literals as load-bearing values). */
-#define X64_NOP_BYTE 0x90u
-#define EXPECTED_MOVQ_RAX_RCX_0 0x48u
-#define EXPECTED_MOVQ_RAX_RCX_1 0x89u
-#define EXPECTED_MOVQ_RAX_RCX_2 0xC8u
-#define EXPECTED_MOVL_EAX_ECX_0 0x89u
-#define EXPECTED_MOVL_EAX_ECX_1 0xC8u
-#define EXPECTED_MOVB_AL_CL_0 0x88u
-#define EXPECTED_MOVB_AL_CL_1 0xC8u
-
-static u8 read_byte(const Section* s, u32 ofs) {
- u8 b;
- buf_read(&s->bytes, ofs, &b, 1);
- return b;
+static void x64_bad_operand(CfreeCompiler* c, CfreeCg* cg,
+ CfreeCgTypeId i64_ty) {
+ (void)i64_ty;
+ it_inline_asm(c, cg, "movq %9, %%rax", NULL, 0, NULL, 0, NULL, 0);
}
-/* External constructors we need from the internal arch surface — these
- * are the same entry points cg_runner uses to spin up a backend without
- * dragging in opt or the JIT. */
-MCEmitter* mc_new(Compiler*, ObjBuilder*);
-CGTarget* cgtarget_new(Compiler*, ObjBuilder*, MCEmitter*);
-
int main(void) {
- CfreeTarget t;
- memset(&t, 0, sizeof t);
- t.arch = CFREE_ARCH_X86_64;
- t.os = CFREE_OS_LINUX;
- t.obj = CFREE_OBJ_ELF;
- t.ptr_size = 8;
- t.ptr_align = 8;
-
- CfreeCompiler* cc = NULL;
- if (cfree_compiler_new(t, &g_ctx, &cc) != CFREE_OK || !cc) {
- fprintf(stderr, "compiler_new failed\n");
- return 2;
- }
- Compiler* c = (Compiler*)cc;
- CfreeCgBuiltinTypes bi = cfree_cg_builtin_types(cc);
-
- if (setjmp(c->panic)) {
- fprintf(stderr, "FAIL: compiler panic\n");
- cfree_compiler_free(cc);
- return 1;
- }
-
- ObjBuilder* ob = obj_new(c);
- Pool* pool = c->global;
- ObjSecId text_sec = obj_section(ob, pool_intern_slice(pool, SLICE_LIT(".text")),
- SEC_TEXT, SF_EXEC | SF_ALLOC, 16);
- MCEmitter* mc = mc_new(c, ob);
- mc->set_section(mc, text_sec);
- CGTarget* target = cgtarget_new(c, ob, mc);
-
- /* ---- smoke 1: movq reg-to-reg via "%0" / "%1" (default form) ----
- *
- * outs = "=r" → rax, ins = "r" → rcx. AT&T spelling: "movq %1, %0"
- * means MOV from %1 into %0, i.e. MOV rax <- rcx.
- * Expected encoding: 48 89 C8. */
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.reg = X64_RAX;
-
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = X64_RCX;
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "movq %1, %0",
- outs, 1, out_ops, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
-
- EXPECT(end - start == 3u, "smoke1: expected 3 bytes, got %u",
- (end - start));
- if (end - start == 3u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_byte(sec, start) == EXPECTED_MOVQ_RAX_RCX_0,
- "smoke1: byte 0 0x%02x, want 0x48",
- read_byte(sec, start));
- EXPECT(read_byte(sec, start + 1) == EXPECTED_MOVQ_RAX_RCX_1,
- "smoke1: byte 1 0x%02x, want 0x89",
- read_byte(sec, start + 1));
- EXPECT(read_byte(sec, start + 2) == EXPECTED_MOVQ_RAX_RCX_2,
- "smoke1: byte 2 0x%02x, want 0xC8",
- read_byte(sec, start + 2));
- }
- }
-
- /* ---- smoke 2: %k modifier picks 32-bit reg form ----
- *
- * "movl %k1, %k0" with out=rax in=rcx -> "movl %ecx, %eax" -> 89 C8. */
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.reg = X64_RAX;
-
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = X64_RCX;
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "movl %k1, %k0",
- outs, 1, out_ops, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
-
- EXPECT(end - start == 2u, "smoke2: expected 2 bytes, got %u",
- (end - start));
- if (end - start == 2u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_byte(sec, start) == EXPECTED_MOVL_EAX_ECX_0,
- "smoke2: byte 0 0x%02x, want 0x89",
- read_byte(sec, start));
- EXPECT(read_byte(sec, start + 1) == EXPECTED_MOVL_EAX_ECX_1,
- "smoke2: byte 1 0x%02x, want 0xC8",
- read_byte(sec, start + 1));
- }
- }
-
- /* ---- smoke 3: %x modifier picks 64-bit reg form ----
- *
- * "movq %x1, %x0" with out=rax in=rcx → "movq %rcx, %rax" → 48 89 C8. */
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.reg = X64_RAX;
-
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = X64_RCX;
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "movq %x1, %x0",
- outs, 1, out_ops, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
-
- EXPECT(end - start == 3u, "smoke3: expected 3 bytes, got %u",
- (end - start));
- if (end - start == 3u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_byte(sec, start) == EXPECTED_MOVQ_RAX_RCX_0,
- "smoke3: byte 0 0x%02x, want 0x48",
- read_byte(sec, start));
- EXPECT(read_byte(sec, start + 1) == EXPECTED_MOVQ_RAX_RCX_1,
- "smoke3: byte 1 0x%02x, want 0x89",
- read_byte(sec, start + 1));
- EXPECT(read_byte(sec, start + 2) == EXPECTED_MOVQ_RAX_RCX_2,
- "smoke3: byte 2 0x%02x, want 0xC8",
- read_byte(sec, start + 2));
- }
- }
-
- /* ---- smoke 4: %b modifier picks 8-bit reg form via movb ----
- *
- * "movb %b1, %b0" with out=rax in=rcx → "movb %cl, %al" → 88 C8. */
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.reg = X64_RAX;
-
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = X64_RCX;
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "movb %b1, %b0",
- outs, 1, out_ops, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
-
- EXPECT(end - start == 2u, "smoke4: expected 2 bytes, got %u",
- (end - start));
- if (end - start == 2u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_byte(sec, start) == EXPECTED_MOVB_AL_CL_0,
- "smoke4: byte 0 0x%02x, want 0x88",
- read_byte(sec, start));
- EXPECT(read_byte(sec, start + 1) == EXPECTED_MOVB_AL_CL_1,
- "smoke4: byte 1 0x%02x, want 0xC8",
- read_byte(sec, start + 1));
- }
- }
-
- /* ---- smoke 5: "%%" literal escape ----
- *
- * The template walker collapses "%%" to a single '%' before handing
- * the rendered line to the per-mnemonic dispatch. There is no clean
- * way to land a stray '%' in a no-operand mnemonic on x64 (every
- * register reference starts with '%'), so we use the same trick as
- * the aa64 peer: confirm two no-operand instructions on one line
- * tokenize correctly through the ';' line splitter. Then drive a
- * separate template that contains "%%" inside what would otherwise
- * be a literal block — the line must still parse as "nop". */
- {
- u32 start = mc->pos(mc);
- target->asm_block(target, "nop ; nop",
- NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 2u, "smoke5a: expected 2 bytes, got %u",
- (end - start));
- if (end - start == 2u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_byte(sec, start) == X64_NOP_BYTE,
- "smoke5a: nop[0] = 0x%02x", read_byte(sec, start));
- EXPECT(read_byte(sec, start + 1) == X64_NOP_BYTE,
- "smoke5a: nop[1] = 0x%02x", read_byte(sec, start + 1));
- }
- }
- /* The "%%" → "%" collapse is exercised directly: a template
- * containing "%%" followed by a register name must produce the
- * literal "%<name>" in the rendered line and assemble cleanly.
- * "movq %%rcx, %%rax" is the AT&T spelling of MOV rax <- rcx
- * once the "%%" pairs collapse. */
- {
- u32 start = mc->pos(mc);
- target->asm_block(target, "movq %%rcx, %%rax",
- NULL, 0, NULL, NULL, 0, NULL, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 3u, "smoke5b: expected 3 bytes, got %u",
- (end - start));
- if (end - start == 3u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_byte(sec, start) == EXPECTED_MOVQ_RAX_RCX_0,
- "smoke5b: byte 0 0x%02x", read_byte(sec, start));
- EXPECT(read_byte(sec, start + 1) == EXPECTED_MOVQ_RAX_RCX_1,
- "smoke5b: byte 1 0x%02x", read_byte(sec, start + 1));
- EXPECT(read_byte(sec, start + 2) == EXPECTED_MOVQ_RAX_RCX_2,
- "smoke5b: byte 2 0x%02x", read_byte(sec, start + 2));
- }
- }
-
- /* ---- smoke 6: %a renders an OPK_INDIRECT as a memory operand ----
- *
- * Output is INDIRECT [rcx + 0]; input is rax. Template
- * "movq %1, %a0" should render as "movq %rax, (%rcx)" and dispatch
- * to the REG, MEM branch of movq.
- * Expected: REX.W=0x48, 0x89, modrm(0, reg=rax=0, rm=rcx=1)=0x01. */
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=m";
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_INDIRECT;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.ind.base = X64_RCX;
- out_ops[0].v.ind.index = REG_NONE;
- out_ops[0].v.ind.ofs = 0;
-
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = X64_RAX;
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "movq %1, %a0",
- outs, 1, out_ops, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
-
- EXPECT(end - start == 3u, "smoke6: expected 3 bytes, got %u",
- (end - start));
- if (end - start == 3u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_byte(sec, start) == 0x48u,
- "smoke6: byte 0 0x%02x, want 0x48", read_byte(sec, start));
- EXPECT(read_byte(sec, start + 1) == 0x89u,
- "smoke6: byte 1 0x%02x, want 0x89", read_byte(sec, start + 1));
- EXPECT(read_byte(sec, start + 2) == 0x01u,
- "smoke6: byte 2 0x%02x, want 0x01", read_byte(sec, start + 2));
- }
- }
-
- /* ---- smoke 7: GNU x86 modifiers %k/%w/%h/%z and symbolic names ---- */
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].dir = ASM_OUT;
- outs[0].name = pool_intern_slice(pool, SLICE_LIT("dst"));
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].type = bi.id[CFREE_CG_BUILTIN_I32];
- out_ops[0].v.reg = X64_RAX;
-
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- ins[0].name = pool_intern_slice(pool, SLICE_LIT("src"));
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].type = bi.id[CFREE_CG_BUILTIN_I32];
- in_ops[0].v.reg = X64_RCX;
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "mov%z[dst] %k[src], %k[dst]",
- outs, 1, out_ops, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 2u, "smoke7a: expected 2 bytes, got %u",
- (end - start));
- if (end - start == 2u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_byte(sec, start) == EXPECTED_MOVL_EAX_ECX_0,
- "smoke7a: byte 0 0x%02x, want 0x89", read_byte(sec, start));
- EXPECT(read_byte(sec, start + 1) == EXPECTED_MOVL_EAX_ECX_1,
- "smoke7a: byte 1 0x%02x, want 0xC8", read_byte(sec, start + 1));
- }
- }
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.reg = X64_RAX;
-
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = X64_RCX;
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "movw %w1, %w0",
- outs, 1, out_ops, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 3u, "smoke7b: expected 3 bytes, got %u",
- (end - start));
- if (end - start == 3u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_byte(sec, start) == 0x66u,
- "smoke7b: byte 0 0x%02x, want 0x66", read_byte(sec, start));
- EXPECT(read_byte(sec, start + 1) == 0x89u,
- "smoke7b: byte 1 0x%02x, want 0x89", read_byte(sec, start + 1));
- EXPECT(read_byte(sec, start + 2) == 0xC8u,
- "smoke7b: byte 2 0x%02x, want 0xC8", read_byte(sec, start + 2));
- }
- }
- {
- AsmConstraint outs[1] = {{0}};
- outs[0].str = "=r";
- outs[0].dir = ASM_OUT;
- Operand out_ops[1];
- memset(out_ops, 0, sizeof out_ops);
- out_ops[0].kind = OPK_REG;
- out_ops[0].cls = RC_INT;
- out_ops[0].v.reg = X64_RDX;
-
- AsmConstraint ins[1] = {{0}};
- ins[0].str = "r";
- ins[0].dir = ASM_IN;
- Operand in_ops[1];
- memset(in_ops, 0, sizeof in_ops);
- in_ops[0].kind = OPK_REG;
- in_ops[0].cls = RC_INT;
- in_ops[0].v.reg = X64_RCX;
-
- u32 start = mc->pos(mc);
- target->asm_block(target, "movb %h1, %h0",
- outs, 1, out_ops, ins, 1, in_ops, NULL, 0);
- u32 end = mc->pos(mc);
- EXPECT(end - start == 2u, "smoke7c: expected 2 bytes, got %u",
- (end - start));
- if (end - start == 2u) {
- const Section* sec = obj_section_get(ob, text_sec);
- EXPECT(read_byte(sec, start) == 0x88u,
- "smoke7c: byte 0 0x%02x, want 0x88", read_byte(sec, start));
- EXPECT(read_byte(sec, start + 1) == 0xEEu,
- "smoke7c: byte 1 0x%02x, want 0xEE", read_byte(sec, start + 1));
- }
- }
-
- cfree_compiler_free(cc);
-
- if (g_fail) {
- fprintf(stderr, "%d failure(s)\n", g_fail);
+ static const uint8_t nops[] = {0x90u, 0x90u};
+ static const uint8_t movq_rcx_rax[] = {0x48u, 0x89u, 0xc8u};
+ static const uint8_t addq_7_rax[] = {0x48u, 0x83u, 0xc0u, 0x07u};
+ InlineTestEnv env;
+ InlineText text;
+
+ it_env_init(&env);
+ IT_EXPECT(&env, it_emit_text(&env, CFREE_ARCH_X86_64, "x64_inline_public",
+ x64_body, &text),
+ "failed to emit x64 inline-asm object");
+ if (text.data) {
+ IT_EXPECT(&env, it_contains(text.data, text.len, nops, sizeof nops),
+ "missing two-nop inline asm encoding");
+ IT_EXPECT(&env, it_contains(text.data, text.len, movq_rcx_rax,
+ sizeof movq_rcx_rax),
+ "missing movq %%rcx, %%rax literal-escape encoding");
+ IT_EXPECT(&env, it_contains(text.data, text.len, addq_7_rax,
+ sizeof addq_7_rax),
+ "missing addq $7, %%rax immediate-substitution encoding");
+ }
+ it_text_close(&text);
+
+ IT_EXPECT(&env,
+ it_expect_panic(&env, CFREE_ARCH_X86_64, "x64_bad_operand",
+ x64_bad_operand, "operand index"),
+ "expected out-of-range x64 asm operand to panic");
+
+ if (env.fail) {
+ fprintf(stderr, "%d failure(s)\n", env.fail);
return 1;
}
printf("x64_inline_test: ok\n");
diff --git a/test/asm/run.sh b/test/asm/run.sh
@@ -65,8 +65,6 @@ esac
export CFREE_TEST_ARCH
CLANG_TARGET="--target=$CLANG_TRIPLE"
-CC="${CC:-cc}"
-HARNESS_CFLAGS="-std=c11 -Wall -Wextra -I$ROOT/include -I$ROOT/test"
# Phase 1: ALLOW_SKIP defaults to 1 (smoke cases skip cleanly because
# asm_parse / cfree_disasm_iter_* are still stubs). Flip to 0 once the
# assembler / disassembler land.
@@ -131,53 +129,32 @@ EXEC_TARGET_MOUNT_ROOT="$BUILD_DIR"
# shellcheck source=../lib/exec_target.sh
source "$ROOT/test/lib/exec_target.sh"
-# ---- build harness binaries ------------------------------------------------
+# ---- harness binaries ------------------------------------------------------
-printf 'Building harness...\n'
+printf 'Checking harness...\n'
-if [ ! -f "$LIB_AR" ]; then
- printf ' FATAL: %s not found — run "make lib" first\n' "$LIB_AR" >&2
- exit 1
-fi
-
-# asm-runner
-if $CC $HARNESS_CFLAGS \
- "$TEST_DIR/harness/asm_runner.c" \
- "$LIB_AR" -o "$ASM_RUNNER" 2>"$BUILD_DIR/asm-runner.err"; then
- printf ' %s asm-runner\n' "$(color_grn built)"
-else
- printf ' %s asm-runner (see %s)\n' \
- "$(color_red FATAL)" "$BUILD_DIR/asm-runner.err" >&2
+if [ ! -x "$ASM_RUNNER" ]; then
+ printf ' %s asm-runner missing — run "make test-asm"\n' \
+ "$(color_red FATAL)" >&2
exit 1
fi
+printf ' %s asm-runner\n' "$(color_grn found)"
# link-exe-runner — for path E.
-if [ ! -x "$LINK_EXE_RUNNER" ]; then
- if $CC -I"$ROOT/include" -I"$ROOT/test" "$LINK_TEST_DIR/harness/link_exe_runner.c" \
- "$LIB_AR" -o "$LINK_EXE_RUNNER" 2>"$BUILD_DIR/link-exe-runner.err"; then
- have_exe_runner=1
- printf ' %s link-exe-runner\n' "$(color_grn built)"
- else
- printf ' %s link-exe-runner (see %s)\n' \
- "$(color_yel warn)" "$BUILD_DIR/link-exe-runner.err" >&2
- fi
-else
+if [ -x "$LINK_EXE_RUNNER" ]; then
have_exe_runner=1
+ printf ' %s link-exe-runner\n' "$(color_grn found)"
+else
+ printf ' %s link-exe-runner missing; E path will skip\n' "$(color_yel warn)"
fi
# jit-runner — for path J. Only meaningful when host arch matches the cross-target.
if [ $is_native_target -eq 1 ]; then
- if [ ! -x "$JIT_RUNNER" ]; then
- if $CC -I"$ROOT/include" -I"$ROOT/test" "$LINK_TEST_DIR/harness/jit_runner.c" \
- "$LIB_AR" -o "$JIT_RUNNER" 2>"$BUILD_DIR/jit-runner.err"; then
- have_jit_runner=1
- printf ' %s jit-runner\n' "$(color_grn built)"
- else
- printf ' %s jit-runner (see %s)\n' \
- "$(color_yel warn)" "$BUILD_DIR/jit-runner.err" >&2
- fi
- else
+ if [ -x "$JIT_RUNNER" ]; then
have_jit_runner=1
+ printf ' %s jit-runner\n' "$(color_grn found)"
+ else
+ printf ' %s jit-runner missing; J path will skip\n' "$(color_yel warn)"
fi
fi
diff --git a/test/elf/run.sh b/test/elf/run.sh
@@ -189,6 +189,16 @@ UNIT_CC="${CC:-clang}"
UNIT_SYSROOT_FLAGS=()
unit_sysroot="$(xcrun --show-sdk-path 2>/dev/null || true)"
[ -n "$unit_sysroot" ] && UNIT_SYSROOT_FLAGS=(-isysroot "$unit_sysroot")
+UNIT_EXTRA_CFLAGS=()
+UNIT_EXTRA_LDFLAGS=()
+if [ -n "${CFREE_ELF_UNIT_CFLAGS:-}" ]; then
+ # shellcheck disable=SC2206
+ UNIT_EXTRA_CFLAGS=(${CFREE_ELF_UNIT_CFLAGS})
+fi
+if [ -n "${CFREE_ELF_UNIT_LDFLAGS:-}" ]; then
+ # shellcheck disable=SC2206
+ UNIT_EXTRA_LDFLAGS=(${CFREE_ELF_UNIT_LDFLAGS})
+fi
run_unit_case() {
local _idx="$1" src="$2" event="$3"
@@ -207,9 +217,11 @@ run_unit_case() {
fi
if ! "$UNIT_CC" -std=c11 -Wall -Wextra -Werror \
+ "${UNIT_EXTRA_CFLAGS[@]}" \
"${UNIT_SYSROOT_FLAGS[@]}" \
-I"$ROOT/include" -I"$ROOT/src" -I"$ROOT/test" \
- "$src" "$LIB_AR" -o "$bin" 2> "$build_log"; then
+ "$src" "$LIB_AR" "${UNIT_EXTRA_LDFLAGS[@]}" -o "$bin" \
+ 2> "$build_log"; then
emit_event "$event" FAIL "$name (build failed; see $build_log)"
return 0
fi
diff --git a/test/elf/unit/align_4k.c b/test/elf/unit/align_4k.c
@@ -1,4 +1,4 @@
-/* Hand-built ObjBuilder roundtrip — verifies large sh_addralign.
+/* Hand-built CfreeObjBuilder roundtrip — verifies large sh_addralign.
*
* Builds an ELF with a .text section aligned to 4096 (page size),
* emits and reads it back, asserts the align field round-trips.
@@ -6,16 +6,13 @@
* so this gap can only be covered by a hand-built case. */
#include <cfree/core.h>
-#include <setjmp.h>
+#include <cfree/object.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "core/core.h"
-#include "core/pool.h"
#include "lib/cfree_test_target.h"
-#include "obj/obj.h"
static void* heap_alloc(CfreeHeap* h, size_t n, size_t a) {
(void)h;
@@ -78,56 +75,71 @@ int main(void) {
fprintf(stderr, "FAIL: cfree_compiler_new\n");
return 1;
}
- Compiler* c = (Compiler*)cc;
-
- if (setjmp(c->panic)) {
- compiler_run_cleanups(c);
- cfree_compiler_free(cc);
- fprintf(stderr, "FAIL: compiler_panic\n");
- return 1;
- }
-
- ObjBuilder* in = obj_new(c);
- Sym text_nm = pool_intern_slice(c->global, SLICE_LIT(".text"));
- ObjSecId sec =
- obj_section(in, text_nm, SEC_TEXT, SF_ALLOC | SF_EXEC, WANT_ALIGN);
- obj_write(in, sec, TEXT_BYTES, sizeof TEXT_BYTES);
- obj_symbol(in, pool_intern_slice(c->global, SLICE_LIT("f")), SB_GLOBAL, SK_FUNC, sec, 0,
- sizeof TEXT_BYTES);
- obj_finalize(in);
+ CfreeObjBuilder* in = NULL;
+ CHECK(cfree_obj_builder_new(cc, &in) == CFREE_OK && in,
+ "cfree_obj_builder_new");
+
+ CfreeObjSection sec = CFREE_SECTION_NONE;
+ CfreeObjSectionDesc text_desc = {
+ .name = cfree_sym_intern(cc, CFREE_SLICE_LIT(".text")),
+ .kind = CFREE_SEC_TEXT,
+ .flags = CFREE_SF_ALLOC | CFREE_SF_EXEC,
+ .align = WANT_ALIGN,
+ .entsize = 0,
+ };
+ CHECK(cfree_obj_builder_section(in, &text_desc, &sec) == CFREE_OK,
+ "section .text");
+ CHECK(cfree_obj_builder_write(in, sec, TEXT_BYTES, sizeof TEXT_BYTES) ==
+ CFREE_OK,
+ "write .text");
+ CfreeObjSymbol sym_f = CFREE_OBJ_SYMBOL_NONE;
+ CfreeObjSymbolDesc f_desc = {
+ .name = cfree_sym_intern(cc, CFREE_SLICE_LIT("f")),
+ .bind = CFREE_SB_GLOBAL,
+ .kind = CFREE_SK_FUNC,
+ .section = sec,
+ .value = 0,
+ .size = sizeof TEXT_BYTES,
+ };
+ CHECK(cfree_obj_builder_symbol(in, &f_desc, &sym_f) == CFREE_OK,
+ "symbol f");
+ CHECK(cfree_obj_builder_finalize(in) == CFREE_OK, "finalize");
CfreeWriter* w = NULL;
(void)cfree_writer_mem(&g_heap, &w);
- emit_elf(c, in, w);
+ CHECK(cfree_obj_builder_emit(in, w) == CFREE_OK, "emit");
size_t out_len = 0;
const uint8_t* out_data = cfree_writer_mem_bytes(w, &out_len);
uint8_t* roundtrip = (uint8_t*)malloc(out_len);
memcpy(roundtrip, out_data, out_len);
cfree_writer_close(w);
- ObjBuilder* back = read_elf(c, "align_4k", roundtrip, out_len);
- CHECK(back != NULL, "read_elf returned NULL");
+ CfreeSlice input = {.data = roundtrip, .len = out_len};
+ CfreeObjFile* back = NULL;
+ CHECK(cfree_obj_open(&ctx, CFREE_SLICE_LIT("align_4k"), &input, &back) ==
+ CFREE_OK &&
+ back,
+ "cfree_obj_open failed");
/* Locate .text by name. */
int found = 0;
if (back) {
- u32 n = obj_section_count(back);
- for (u32 i = 1; i < n; ++i) {
- const Section* s = obj_section_get(back, i);
- Slice nm = pool_slice(c->global, s->name);
- if (nm.len == 5 && memcmp(nm.s, ".text", 5) == 0) {
- found = 1;
- CHECK(s->align == WANT_ALIGN, ".text align=%u after roundtrip, want %u",
- s->align, WANT_ALIGN);
- break;
- }
+ CfreeObjSection text = CFREE_SECTION_NONE;
+ if (cfree_obj_section_by_name(back, CFREE_SLICE_LIT(".text"), &text) ==
+ CFREE_OK) {
+ CfreeObjSecInfo si;
+ CHECK(cfree_obj_section(back, text, &si) == CFREE_OK,
+ "section info .text");
+ found = 1;
+ CHECK(si.align == WANT_ALIGN, ".text align=%u after roundtrip, want %u",
+ si.align, WANT_ALIGN);
}
}
CHECK(found, ".text not present after roundtrip");
- if (back) obj_free(back);
+ if (back) cfree_obj_free(back);
free(roundtrip);
- obj_free(in);
+ cfree_obj_builder_free(in);
cfree_compiler_free(cc);
if (g_failures) {
diff --git a/test/elf/unit/groupiter.c b/test/elf/unit/groupiter.c
@@ -1,25 +1,20 @@
-/* Hand-built ObjBuilder with a COMDAT group, exercised via both the
- * internal obj_groupiter and the public cfree_obj_groupiter.
+/* Hand-built object with a COMDAT group, exercised through the public
+ * object builder and cfree_obj_groupiter APIs.
*
* Steps:
* 1. Build an ELF with two sections wired into one COMDAT group.
- * 2. Read internal iter on the freshly built builder; verify shape.
- * 3. Emit ELF, reopen via cfree_obj_open, walk the public iter; verify
+ * 2. Emit ELF, reopen via cfree_obj_open, walk the public iter; verify
* group survives the on-disk roundtrip and section IDs are
* remapped to the public 0-based space. */
#include <cfree/core.h>
#include <cfree/object.h>
-#include <setjmp.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "core/core.h"
-#include "core/pool.h"
#include "lib/cfree_test_target.h"
-#include "obj/obj.h"
static void* heap_alloc(CfreeHeap* h, size_t n, size_t a) {
(void)h;
@@ -87,68 +82,65 @@ int main(void) {
fprintf(stderr, "FAIL: cfree_compiler_new\n");
return 1;
}
- Compiler* c = (Compiler*)cc;
- if (setjmp(c->panic)) {
- compiler_run_cleanups(c);
- cfree_compiler_free(cc);
- fprintf(stderr, "FAIL: compiler_panic\n");
- return 1;
- }
-
/* ---- build ---- */
- ObjBuilder* in = obj_new(c);
- Pool* p = c->global;
-
- Sym sig_nm = pool_intern_slice(p, SLICE_LIT("comdat_sig"));
- Sym text_nm = pool_intern_slice(p, SLICE_LIT(".text.comdat_fn"));
- Sym data_nm = pool_intern_slice(p, SLICE_LIT(".data.comdat_fn"));
-
- ObjSecId sec_text = obj_section(in, text_nm, SEC_TEXT,
- SF_ALLOC | SF_EXEC | SF_GROUP, 4);
- ObjSecId sec_data = obj_section(in, data_nm, SEC_DATA,
- SF_ALLOC | SF_WRITE | SF_GROUP, 8);
- obj_write(in, sec_text, TEXT_BYTES, sizeof TEXT_BYTES);
+ CfreeObjBuilder* in = NULL;
+ CHECK(cfree_obj_builder_new(cc, &in) == CFREE_OK && in,
+ "cfree_obj_builder_new");
+
+ CfreeSym sig_nm = cfree_sym_intern(cc, CFREE_SLICE_LIT("comdat_sig"));
+ CfreeObjSection sec_text = CFREE_SECTION_NONE;
+ CfreeObjSection sec_data = CFREE_SECTION_NONE;
+ CfreeObjSectionDesc text_desc = {
+ .name = cfree_sym_intern(cc, CFREE_SLICE_LIT(".text.comdat_fn")),
+ .kind = CFREE_SEC_TEXT,
+ .flags = CFREE_SF_ALLOC | CFREE_SF_EXEC,
+ .align = 4,
+ .entsize = 0,
+ };
+ CfreeObjSectionDesc data_desc = {
+ .name = cfree_sym_intern(cc, CFREE_SLICE_LIT(".data.comdat_fn")),
+ .kind = CFREE_SEC_DATA,
+ .flags = CFREE_SF_ALLOC | CFREE_SF_WRITE,
+ .align = 8,
+ .entsize = 0,
+ };
+ CHECK(cfree_obj_builder_section(in, &text_desc, &sec_text) == CFREE_OK,
+ "section .text.comdat_fn");
+ CHECK(cfree_obj_builder_section(in, &data_desc, &sec_data) == CFREE_OK,
+ "section .data.comdat_fn");
+ CHECK(cfree_obj_builder_write(in, sec_text, TEXT_BYTES, sizeof TEXT_BYTES) ==
+ CFREE_OK,
+ "write .text.comdat_fn");
static const uint8_t zero8[8] = {0};
- obj_write(in, sec_data, zero8, sizeof zero8);
-
- ObjSymId sig_sym =
- obj_symbol(in, sig_nm, SB_WEAK, SK_FUNC, sec_text, 0, sizeof TEXT_BYTES);
-
- ObjGroupId gid = obj_group(in, sig_nm, sig_sym, CFREE_OBJ_GROUP_COMDAT);
- obj_group_add_section(in, gid, sec_text);
- obj_group_add_section(in, gid, sec_data);
-
- obj_finalize(in);
-
- /* ---- internal iter ---- */
- {
- ObjGroupIter* it = obj_groupiter_new(in);
- ObjGroupEntry e;
- int seen = 0;
- while (obj_groupiter_next(it, &e)) {
- ++seen;
- CHECK(e.id == gid, "internal iter: id=%u, want %u", e.id, gid);
- CHECK(e.group->nsections == 2, "internal iter: nsections=%u, want 2",
- e.group->nsections);
- CHECK(e.group->signature == sig_sym, "internal iter: signature=%u",
- e.group->signature);
- CHECK((e.group->flags & CFREE_OBJ_GROUP_COMDAT) != 0,
- "internal iter: missing COMDAT flag (flags=0x%x)", e.group->flags);
- CHECK(e.group->sections[0] == sec_text,
- "internal iter: sections[0]=%u, want %u", e.group->sections[0],
- sec_text);
- CHECK(e.group->sections[1] == sec_data,
- "internal iter: sections[1]=%u, want %u", e.group->sections[1],
- sec_data);
- }
- CHECK(seen == 1, "internal iter: saw %d groups, want 1", seen);
- obj_groupiter_free(it);
- }
+ CHECK(cfree_obj_builder_write(in, sec_data, zero8, sizeof zero8) == CFREE_OK,
+ "write .data.comdat_fn");
+
+ CfreeObjSymbol sig_sym = CFREE_OBJ_SYMBOL_NONE;
+ CfreeObjSymbolDesc sig_desc = {
+ .name = sig_nm,
+ .bind = CFREE_SB_WEAK,
+ .kind = CFREE_SK_FUNC,
+ .section = sec_text,
+ .value = 0,
+ .size = sizeof TEXT_BYTES,
+ };
+ CHECK(cfree_obj_builder_symbol(in, &sig_desc, &sig_sym) == CFREE_OK,
+ "symbol comdat_sig");
+
+ CfreeObjGroup gid = CFREE_OBJ_GROUP_NONE;
+ CHECK(cfree_obj_builder_group(in, sig_nm, sig_sym, CFREE_OBJ_GROUP_COMDAT,
+ &gid) == CFREE_OK,
+ "group comdat_sig");
+ CHECK(cfree_obj_builder_group_add_section(in, gid, sec_text) == CFREE_OK,
+ "group add text");
+ CHECK(cfree_obj_builder_group_add_section(in, gid, sec_data) == CFREE_OK,
+ "group add data");
+ CHECK(cfree_obj_builder_finalize(in) == CFREE_OK, "finalize");
/* ---- emit + public-iter readback ---- */
CfreeWriter* w = NULL;
(void)cfree_writer_mem(&g_heap, &w);
- emit_elf(c, in, w);
+ CHECK(cfree_obj_builder_emit(in, w) == CFREE_OK, "emit");
size_t out_len = 0;
const uint8_t* out_data = cfree_writer_mem_bytes(w, &out_len);
uint8_t* roundtrip = (uint8_t*)malloc(out_len ? out_len : 1);
@@ -205,7 +197,7 @@ int main(void) {
}
free(roundtrip);
- obj_free(in);
+ cfree_obj_builder_free(in);
cfree_compiler_free(cc);
if (g_failures) {
diff --git a/test/elf/unit/mutate.c b/test/elf/unit/mutate.c
@@ -13,16 +13,12 @@
#include <cfree/core.h>
#include <cfree/object.h>
-#include <setjmp.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "core/core.h"
-#include "core/pool.h"
#include "lib/cfree_test_target.h"
-#include "obj/obj.h"
static void* heap_alloc(CfreeHeap* h, size_t n, size_t a) {
(void)h;
@@ -85,77 +81,132 @@ int main(void) {
fprintf(stderr, "FAIL: cfree_compiler_new\n");
return 1;
}
- Compiler* c = (Compiler*)cc;
- if (setjmp(c->panic)) {
- compiler_run_cleanups(c);
- cfree_compiler_free(cc);
- fprintf(stderr, "FAIL: compiler_panic\n");
- return 1;
- }
-
/* ---- build ---- */
- ObjBuilder* ob = obj_new(c);
- Pool* p = c->global;
-
- Sym text_nm = pool_intern_slice(p, SLICE_LIT(".text"));
- Sym data_nm = pool_intern_slice(p, SLICE_LIT(".data"));
- Sym entry_nm = pool_intern_slice(p, SLICE_LIT("entry"));
- Sym entry_new_nm = pool_intern_slice(p, SLICE_LIT("renamed_entry"));
- Sym keep_nm = pool_intern_slice(p, SLICE_LIT("keep_global"));
- Sym foo_nm = pool_intern_slice(p, SLICE_LIT("foo_ref"));
- Sym spurious_nm = pool_intern_slice(p, SLICE_LIT("spurious_extern"));
-
- ObjSecId sec_text =
- obj_section(ob, text_nm, SEC_TEXT, SF_ALLOC | SF_EXEC, 4);
- ObjSecId sec_data =
- obj_section(ob, data_nm, SEC_DATA, SF_ALLOC | SF_WRITE, 8);
-
- obj_write(ob, sec_text, TEXT_BYTES, sizeof TEXT_BYTES);
+ CfreeObjBuilder* ob = NULL;
+ CHECK(cfree_obj_builder_new(cc, &ob) == CFREE_OK && ob,
+ "cfree_obj_builder_new");
+
+ CfreeSym entry_new_nm = cfree_sym_intern(cc, CFREE_SLICE_LIT("renamed_entry"));
+
+ CfreeObjSection sec_text = CFREE_SECTION_NONE;
+ CfreeObjSection sec_data = CFREE_SECTION_NONE;
+ CfreeObjSectionDesc text_desc = {
+ .name = cfree_sym_intern(cc, CFREE_SLICE_LIT(".text")),
+ .kind = CFREE_SEC_TEXT,
+ .flags = CFREE_SF_ALLOC | CFREE_SF_EXEC,
+ .align = 4,
+ .entsize = 0,
+ };
+ CfreeObjSectionDesc data_desc = {
+ .name = cfree_sym_intern(cc, CFREE_SLICE_LIT(".data")),
+ .kind = CFREE_SEC_DATA,
+ .flags = CFREE_SF_ALLOC | CFREE_SF_WRITE,
+ .align = 8,
+ .entsize = 0,
+ };
+ CHECK(cfree_obj_builder_section(ob, &text_desc, &sec_text) == CFREE_OK,
+ "section .text");
+ CHECK(cfree_obj_builder_section(ob, &data_desc, &sec_data) == CFREE_OK,
+ "section .data");
+
+ CHECK(cfree_obj_builder_write(ob, sec_text, TEXT_BYTES, sizeof TEXT_BYTES) ==
+ CFREE_OK,
+ "write .text");
static const uint8_t zero8[8] = {0};
- obj_write(ob, sec_data, zero8, sizeof zero8);
-
- ObjSymId sym_entry = obj_symbol(ob, entry_nm, SB_GLOBAL, SK_FUNC, sec_text, 0,
- sizeof TEXT_BYTES);
- ObjSymId sym_keep =
- obj_symbol(ob, keep_nm, SB_GLOBAL, SK_FUNC, sec_text, 0, 0);
- ObjSymId sym_foo =
- obj_symbol(ob, foo_nm, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0);
+ CHECK(cfree_obj_builder_write(ob, sec_data, zero8, sizeof zero8) == CFREE_OK,
+ "write .data");
+
+ CfreeObjSymbol sym_entry = CFREE_OBJ_SYMBOL_NONE;
+ CfreeObjSymbolDesc entry_desc = {
+ .name = cfree_sym_intern(cc, CFREE_SLICE_LIT("entry")),
+ .bind = CFREE_SB_GLOBAL,
+ .kind = CFREE_SK_FUNC,
+ .section = sec_text,
+ .value = 0,
+ .size = sizeof TEXT_BYTES,
+ };
+ CHECK(cfree_obj_builder_symbol(ob, &entry_desc, &sym_entry) == CFREE_OK,
+ "symbol entry");
+
+ CfreeObjSymbol sym_keep = CFREE_OBJ_SYMBOL_NONE;
+ CfreeObjSymbolDesc keep_desc = {
+ .name = cfree_sym_intern(cc, CFREE_SLICE_LIT("keep_global")),
+ .bind = CFREE_SB_GLOBAL,
+ .kind = CFREE_SK_FUNC,
+ .section = sec_text,
+ .value = 0,
+ .size = 0,
+ };
+ CHECK(cfree_obj_builder_symbol(ob, &keep_desc, &sym_keep) == CFREE_OK,
+ "symbol keep_global");
+
+ CfreeObjSymbol sym_foo = CFREE_OBJ_SYMBOL_NONE;
+ CfreeObjSymbolDesc foo_desc = {
+ .name = cfree_sym_intern(cc, CFREE_SLICE_LIT("foo_ref")),
+ .bind = CFREE_SB_GLOBAL,
+ .kind = CFREE_SK_UNDEF,
+ .section = CFREE_SECTION_NONE,
+ .value = 0,
+ .size = 0,
+ };
+ CHECK(cfree_obj_builder_symbol(ob, &foo_desc, &sym_foo) == CFREE_OK,
+ "symbol foo_ref");
+
/* Spurious extern: !referenced means sweep will tombstone it. */
- ObjSymId sym_spurious =
- obj_symbol(ob, spurious_nm, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0);
+ CfreeObjSymbol sym_spurious = CFREE_OBJ_SYMBOL_NONE;
+ CfreeObjSymbolDesc spurious_desc = {
+ .name = cfree_sym_intern(cc, CFREE_SLICE_LIT("spurious_extern")),
+ .bind = CFREE_SB_GLOBAL,
+ .kind = CFREE_SK_UNDEF,
+ .section = CFREE_SECTION_NONE,
+ .value = 0,
+ .size = 0,
+ };
+ CHECK(cfree_obj_builder_symbol(ob, &spurious_desc, &sym_spurious) ==
+ CFREE_OK,
+ "symbol spurious_extern");
(void)sym_spurious;
/* One reloc in .data against foo_ref (which is referenced — survives).
* One reloc in .data against the about-to-rename entry (also survives but
* the containing .data gets removed, so the reloc dies via cascade). */
- obj_reloc(ob, sec_data, 0, R_ABS64, sym_foo, 0);
- obj_reloc(ob, sec_data, 8, R_ABS64, sym_entry, 0);
+ CfreeObjRelocDesc foo_reloc = {
+ .section = sec_data,
+ .offset = 0,
+ .kind = {.arch = target.arch,
+ .obj_fmt = target.obj,
+ .code = CFREE_RELOC_ABS64},
+ .symbol = sym_foo,
+ .addend = 0,
+ };
+ CfreeObjRelocDesc entry_reloc = {
+ .section = sec_data,
+ .offset = 8,
+ .kind = {.arch = target.arch,
+ .obj_fmt = target.obj,
+ .code = CFREE_RELOC_ABS64},
+ .symbol = sym_entry,
+ .addend = 0,
+ };
+ CHECK(cfree_obj_builder_reloc(ob, &foo_reloc) == CFREE_OK, "reloc foo_ref");
+ CHECK(cfree_obj_builder_reloc(ob, &entry_reloc) == CFREE_OK, "reloc entry");
- obj_finalize(ob);
+ CHECK(cfree_obj_builder_finalize(ob) == CFREE_OK, "finalize");
/* ---- mutate via the public API ---- */
- /* The builder we got from obj_new is also a CfreeObjBuilder — same
- * handle type — so we can drive the public mutator surface directly. */
- CfreeObjBuilder* pb = (CfreeObjBuilder*)ob;
-
- /* Convert internal ids to public ids the same way object_builder.c does. */
- CfreeObjSection pub_sec_data = (CfreeObjSection)(sec_data - 1);
- CfreeObjSymbol pub_sym_entry = (CfreeObjSymbol)sym_entry;
- CfreeObjSymbol pub_sym_keep = (CfreeObjSymbol)sym_keep;
-
- CHECK(cfree_obj_builder_remove_section(pb, pub_sec_data) == CFREE_OK,
+ CHECK(cfree_obj_builder_remove_section(ob, sec_data) == CFREE_OK,
"remove_section .data");
- CHECK(cfree_obj_builder_rename_symbol(pb, pub_sym_entry,
- (CfreeSym)entry_new_nm) == CFREE_OK,
+ CHECK(cfree_obj_builder_rename_symbol(ob, sym_entry, entry_new_nm) ==
+ CFREE_OK,
"rename_symbol entry -> renamed_entry");
- CHECK(cfree_obj_builder_symbol_set_bind(pb, pub_sym_keep, CFREE_SB_LOCAL) ==
+ CHECK(cfree_obj_builder_symbol_set_bind(ob, sym_keep, CFREE_SB_LOCAL) ==
CFREE_OK,
"set_bind keep_global -> local");
/* ---- emit + reopen ---- */
CfreeWriter* w = NULL;
(void)cfree_writer_mem(&g_heap, &w);
- CHECK(cfree_obj_builder_emit(pb, w) == CFREE_OK, "emit after mutate");
+ CHECK(cfree_obj_builder_emit(ob, w) == CFREE_OK, "emit after mutate");
size_t out_len = 0;
const uint8_t* out_data = cfree_writer_mem_bytes(w, &out_len);
@@ -200,9 +251,9 @@ int main(void) {
/* foo_ref was the target of a reloc, but its containing section
* (.data) was removed — the reloc is gone, but is foo_ref itself
* still referenced enough to survive? The sweep marks it referenced
- * via obj_reloc_ex at build time, BUT the reloc is then dropped at
- * sweep. The symbol's `referenced` flag was set at obj_reloc time and
- * does not get cleared, so foo_ref survives as a plain UNDEF. */
+ * when the relocation is created, BUT the reloc is then dropped at
+ * sweep. The symbol's `referenced` flag remains set, so foo_ref survives
+ * as a plain UNDEF. */
CHECK(cfree_obj_symbol_by_name(f, CFREE_SLICE_LIT("foo_ref"), &si) == CFREE_OK,
"foo_ref UNDEF should survive");
@@ -219,7 +270,7 @@ int main(void) {
}
free(roundtrip);
- obj_free(ob);
+ cfree_obj_builder_free(ob);
cfree_compiler_free(cc);
if (g_failures) {
diff --git a/test/elf/unit/smoke.c b/test/elf/unit/smoke.c
@@ -1,29 +1,26 @@
-/* Hand-built ObjBuilder roundtrip test.
+/* Hand-built CfreeObjBuilder roundtrip test.
*
* Builds a tiny AArch64-Linux ELF in memory: one .text section with two
* AArch64 instructions, one .data section with an R_ABS64 relocation
* against an external symbol, then runs:
*
- * emit_elf(c, ob, mem_writer)
- * read_elf(c, "smoke", bytes, len)
+ * cfree_obj_builder_emit(ob, mem_writer)
+ * cfree_obj_open("smoke", bytes, len)
*
* and checks that the readback produces the same shape (modulo
* synthesized STT_SECTION symbols and section ordering — the equivalence
- * the read_elf comment in src/obj/elf/read.c documents).
+ * the ELF reader's documented object equivalence.
*
* Exit 0 = pass; non-zero = fail (with a one-line stderr explanation). */
#include <cfree/core.h>
-#include <setjmp.h>
+#include <cfree/object.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "core/core.h"
-#include "core/pool.h"
#include "lib/cfree_test_target.h"
-#include "obj/obj.h"
/* ---- env ---- */
@@ -76,152 +73,172 @@ static const uint8_t TEXT_BYTES[8] = {
0x40, 0x05, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6,
};
-static ObjBuilder* build_input(Compiler* c) {
- ObjBuilder* ob = obj_new(c);
- Pool* p = c->global;
-
- Sym text = pool_intern_slice(p, SLICE_LIT(".text"));
- Sym data = pool_intern_slice(p, SLICE_LIT(".data"));
- Sym main = pool_intern_slice(p, SLICE_LIT("main"));
- Sym foo = pool_intern_slice(p, SLICE_LIT("foo"));
-
- ObjSecId sec_text = obj_section(ob, text, SEC_TEXT, SF_ALLOC | SF_EXEC, 4);
- ObjSecId sec_data = obj_section(ob, data, SEC_DATA, SF_ALLOC | SF_WRITE, 8);
-
- obj_write(ob, sec_text, TEXT_BYTES, sizeof TEXT_BYTES);
+static CfreeObjBuilder* build_input(CfreeCompiler* c, CfreeTarget target) {
+ CfreeObjBuilder* ob = NULL;
+ CfreeObjSection sec_text = CFREE_SECTION_NONE;
+ CfreeObjSection sec_data = CFREE_SECTION_NONE;
+ CfreeObjSymbol sym_foo = CFREE_OBJ_SYMBOL_NONE;
+
+ CHECK(cfree_obj_builder_new(c, &ob) == CFREE_OK && ob,
+ "cfree_obj_builder_new");
+ if (!ob) return NULL;
+
+ CfreeObjSectionDesc text_desc = {
+ .name = cfree_sym_intern(c, CFREE_SLICE_LIT(".text")),
+ .kind = CFREE_SEC_TEXT,
+ .flags = CFREE_SF_ALLOC | CFREE_SF_EXEC,
+ .align = 4,
+ .entsize = 0,
+ };
+ CfreeObjSectionDesc data_desc = {
+ .name = cfree_sym_intern(c, CFREE_SLICE_LIT(".data")),
+ .kind = CFREE_SEC_DATA,
+ .flags = CFREE_SF_ALLOC | CFREE_SF_WRITE,
+ .align = 8,
+ .entsize = 0,
+ };
+ CHECK(cfree_obj_builder_section(ob, &text_desc, &sec_text) == CFREE_OK,
+ "section .text");
+ CHECK(cfree_obj_builder_section(ob, &data_desc, &sec_data) == CFREE_OK,
+ "section .data");
+ CHECK(cfree_obj_builder_write(ob, sec_text, TEXT_BYTES, sizeof TEXT_BYTES) ==
+ CFREE_OK,
+ "write .text");
static const uint8_t zero8[8] = {0};
- obj_write(ob, sec_data, zero8, sizeof zero8);
-
- obj_symbol(ob, main, SB_GLOBAL, SK_FUNC, sec_text, 0, sizeof TEXT_BYTES);
- ObjSymId sym_foo =
- obj_symbol(ob, foo, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0);
-
- obj_reloc(ob, sec_data, 0, R_ABS64, sym_foo, 0);
-
- obj_finalize(ob);
+ CHECK(cfree_obj_builder_write(ob, sec_data, zero8, sizeof zero8) == CFREE_OK,
+ "write .data");
+
+ CfreeObjSymbolDesc main_desc = {
+ .name = cfree_sym_intern(c, CFREE_SLICE_LIT("main")),
+ .bind = CFREE_SB_GLOBAL,
+ .kind = CFREE_SK_FUNC,
+ .section = sec_text,
+ .value = 0,
+ .size = sizeof TEXT_BYTES,
+ };
+ CfreeObjSymbol sym_main = CFREE_OBJ_SYMBOL_NONE;
+ CHECK(cfree_obj_builder_symbol(ob, &main_desc, &sym_main) == CFREE_OK,
+ "symbol main");
+
+ CfreeObjSymbolDesc foo_desc = {
+ .name = cfree_sym_intern(c, CFREE_SLICE_LIT("foo")),
+ .bind = CFREE_SB_GLOBAL,
+ .kind = CFREE_SK_UNDEF,
+ .section = CFREE_SECTION_NONE,
+ .value = 0,
+ .size = 0,
+ };
+ CHECK(cfree_obj_builder_symbol(ob, &foo_desc, &sym_foo) == CFREE_OK,
+ "symbol foo");
+
+ CfreeObjRelocDesc reloc_desc = {
+ .section = sec_data,
+ .offset = 0,
+ .kind = {.arch = target.arch,
+ .obj_fmt = target.obj,
+ .code = CFREE_RELOC_ABS64},
+ .symbol = sym_foo,
+ .addend = 0,
+ };
+ CHECK(cfree_obj_builder_reloc(ob, &reloc_desc) == CFREE_OK,
+ "reloc .data");
+
+ CHECK(cfree_obj_builder_finalize(ob) == CFREE_OK, "finalize");
return ob;
}
-/* ---- shape inspection on read_elf output ---- */
-
-/* Pool strings are NOT NUL-terminated (pool_intern stores raw bytes), so
- * compare by length + memcmp rather than strcmp. */
-static int sym_eq_str(Pool* p, Sym s, const char* want) {
- Slice got = pool_slice(p, s);
- size_t wlen = 0;
- while (want[wlen]) ++wlen;
- return got.s && got.len == wlen && memcmp(got.s, want, got.len) == 0;
-}
-
-static const Section* find_section_named(const ObjBuilder* ob, Pool* p,
- const char* want) {
- u32 n = obj_section_count(ob);
- for (u32 i = 1; i < n; ++i) {
- const Section* s = obj_section_get(ob, i);
- if (sym_eq_str(p, s->name, want)) return s;
- }
- return NULL;
-}
-
-static ObjSymId find_sym_named(const ObjBuilder* ob, Pool* p,
- const char* want) {
- ObjSymIter* it = obj_symiter_new(ob);
- ObjSymEntry e;
- ObjSymId found = OBJ_SYM_NONE;
- while (obj_symiter_next(it, &e)) {
- if (sym_eq_str(p, e.sym->name, want)) {
- found = e.id;
- break;
- }
- }
- obj_symiter_free(it);
- return found;
-}
-
-static void verify_shape(const ObjBuilder* ob, Pool* p) {
- const Section* text = find_section_named(ob, p, ".text");
- const Section* data = find_section_named(ob, p, ".data");
-
- CHECK(text != NULL, ".text not present after roundtrip");
- CHECK(data != NULL, ".data not present after roundtrip");
- if (text) {
- CHECK(text->kind == SEC_TEXT, ".text kind is %u, want %u", text->kind,
- SEC_TEXT);
- CHECK((text->flags & SF_EXEC) != 0, ".text missing SF_EXEC");
- CHECK((text->flags & SF_ALLOC) != 0, ".text missing SF_ALLOC");
- CHECK(text->bytes.total == sizeof TEXT_BYTES,
- ".text size mismatch: got %u, want %zu", text->bytes.total,
- sizeof TEXT_BYTES);
- uint8_t flat[8] = {0};
- if (text->bytes.total == sizeof TEXT_BYTES) {
- buf_flatten(&text->bytes, flat);
- CHECK(memcmp(flat, TEXT_BYTES, sizeof TEXT_BYTES) == 0,
+/* ---- shape inspection on cfree_obj_open output ---- */
+
+static void verify_shape(CfreeObjFile* f) {
+ CfreeObjSection text = CFREE_SECTION_NONE;
+ CfreeObjSection data = CFREE_SECTION_NONE;
+ CHECK(cfree_obj_section_by_name(f, CFREE_SLICE_LIT(".text"), &text) ==
+ CFREE_OK,
+ ".text not present after roundtrip");
+ CHECK(cfree_obj_section_by_name(f, CFREE_SLICE_LIT(".data"), &data) ==
+ CFREE_OK,
+ ".data not present after roundtrip");
+
+ if (text != CFREE_SECTION_NONE) {
+ CfreeObjSecInfo si;
+ const uint8_t* bytes = NULL;
+ size_t len = 0;
+ CHECK(cfree_obj_section(f, text, &si) == CFREE_OK, "section .text");
+ CHECK(si.kind == CFREE_SEC_TEXT, ".text kind is %u, want %u", si.kind,
+ CFREE_SEC_TEXT);
+ CHECK((si.flags & CFREE_SF_EXEC) != 0, ".text missing SF_EXEC");
+ CHECK((si.flags & CFREE_SF_ALLOC) != 0, ".text missing SF_ALLOC");
+ CHECK(si.size == sizeof TEXT_BYTES, ".text size mismatch: got %llu, want %zu",
+ (unsigned long long)si.size, sizeof TEXT_BYTES);
+ CHECK(cfree_obj_section_data(f, text, &bytes, &len) == CFREE_OK,
+ "section_data .text");
+ CHECK(len == sizeof TEXT_BYTES, ".text data size mismatch: got %zu, want %zu",
+ len, sizeof TEXT_BYTES);
+ if (len == sizeof TEXT_BYTES) {
+ CHECK(memcmp(bytes, TEXT_BYTES, sizeof TEXT_BYTES) == 0,
".text bytes do not match input");
}
}
- if (data) {
- CHECK(data->kind == SEC_DATA, ".data kind is %u", data->kind);
- CHECK((data->flags & SF_WRITE) != 0, ".data missing SF_WRITE");
+ if (data != CFREE_SECTION_NONE) {
+ CfreeObjSecInfo si;
+ CHECK(cfree_obj_section(f, data, &si) == CFREE_OK, "section .data");
+ CHECK(si.kind == CFREE_SEC_DATA, ".data kind is %u", si.kind);
+ CHECK((si.flags & CFREE_SF_WRITE) != 0, ".data missing SF_WRITE");
}
/* Symbols. main: defined function in .text. foo: undefined external. */
- ObjSymId sym_main = find_sym_named(ob, p, "main");
- ObjSymId sym_foo = find_sym_named(ob, p, "foo");
- CHECK(sym_main != OBJ_SYM_NONE, "missing 'main' symbol");
- CHECK(sym_foo != OBJ_SYM_NONE, "missing 'foo' symbol");
- if (sym_main) {
- const ObjSym* s = obj_symbol_get(ob, sym_main);
- CHECK(s->bind == SB_GLOBAL, "main bind=%u", s->bind);
- CHECK(s->kind == SK_FUNC, "main kind=%u", s->kind);
+ CfreeObjSymInfo main_si;
+ CfreeObjSymInfo foo_si;
+ CHECK(cfree_obj_symbol_by_name(f, CFREE_SLICE_LIT("main"), &main_si) ==
+ CFREE_OK,
+ "missing 'main' symbol");
+ CHECK(cfree_obj_symbol_by_name(f, CFREE_SLICE_LIT("foo"), &foo_si) ==
+ CFREE_OK,
+ "missing 'foo' symbol");
+ if (cfree_obj_symbol_by_name(f, CFREE_SLICE_LIT("main"), &main_si) ==
+ CFREE_OK) {
+ CHECK(main_si.bind == CFREE_SB_GLOBAL, "main bind=%u", main_si.bind);
+ CHECK(main_si.kind == CFREE_SK_FUNC, "main kind=%u", main_si.kind);
}
- if (sym_foo) {
- const ObjSym* s = obj_symbol_get(ob, sym_foo);
- CHECK(s->bind == SB_GLOBAL, "foo bind=%u", s->bind);
- CHECK(s->kind == SK_UNDEF, "foo kind=%u (want SK_UNDEF=%u)", s->kind,
- SK_UNDEF);
- CHECK(s->section_id == OBJ_SEC_NONE, "foo not undefined");
+ if (cfree_obj_symbol_by_name(f, CFREE_SLICE_LIT("foo"), &foo_si) ==
+ CFREE_OK) {
+ CHECK(foo_si.bind == CFREE_SB_GLOBAL, "foo bind=%u", foo_si.bind);
+ CHECK(foo_si.kind == CFREE_SK_UNDEF, "foo kind=%u (want SK_UNDEF=%u)",
+ foo_si.kind, CFREE_SK_UNDEF);
+ CHECK(foo_si.section == CFREE_SECTION_NONE, "foo not undefined");
}
/* Relocation: a single R_ABS64 against 'foo' in .data, offset 0. */
- if (data) {
- /* Find data's section id. */
- ObjSecId data_id = OBJ_SEC_NONE;
- u32 n = obj_section_count(ob);
- for (u32 i = 1; i < n; ++i) {
- if (obj_section_get(ob, i) == data) {
- data_id = i;
- break;
+ if (data != CFREE_SECTION_NONE) {
+ CfreeObjRelocIter* it = NULL;
+ CfreeObjReloc found;
+ int ndata_relocs = 0;
+ int have_found = 0;
+ CHECK(cfree_obj_reliter_new(f, &it) == CFREE_OK && it, "reliter_new");
+ if (it) {
+ CfreeObjReloc r;
+ while (cfree_obj_reliter_next(it, &r) == CFREE_ITER_ITEM) {
+ if (r.section == data) {
+ ++ndata_relocs;
+ found = r;
+ have_found = 1;
+ }
}
+ cfree_obj_reliter_free(it);
}
- CHECK(data_id != OBJ_SEC_NONE, "could not locate .data id");
-
- u32 nr = obj_reloc_count(ob, data_id);
- CHECK(nr == 1, ".data reloc count = %u, want 1", nr);
-
- /* Walk the flat reloc array; filter by section_id. */
- const Reloc* found = NULL;
- u32 total = obj_reloc_total(ob);
- for (u32 i = 0; i < total; ++i) {
- const Reloc* r = obj_reloc_at(ob, i);
- if (r->section_id == data_id) {
- found = r;
- break;
- }
- }
- CHECK(found != NULL, "no reloc on .data");
- if (found) {
- CHECK(found->kind == R_ABS64, ".data reloc kind=%u (want R_ABS64=%u)",
- found->kind, R_ABS64);
- CHECK(found->offset == 0, ".data reloc offset=%u", found->offset);
- CHECK(found->addend == 0, ".data reloc addend=%lld",
- (long long)found->addend);
- const ObjSym* tsym = obj_symbol_get(ob, found->sym);
- if (tsym) {
- Slice nm = pool_slice(p, tsym->name);
- CHECK(sym_eq_str(p, tsym->name, "foo"), "reloc target name = %.*s",
- (int)nm.len, nm.s ? nm.s : "(null)");
- }
+ CHECK(ndata_relocs == 1, ".data reloc count = %d, want 1", ndata_relocs);
+ CHECK(have_found, "no reloc on .data");
+ if (have_found) {
+ CHECK(found.kind.code == CFREE_RELOC_ABS64,
+ ".data reloc kind=%u (want ABS64=%u)", found.kind.code,
+ CFREE_RELOC_ABS64);
+ CHECK(found.offset == 0, ".data reloc offset=%llu",
+ (unsigned long long)found.offset);
+ CHECK(found.addend == 0, ".data reloc addend=%lld",
+ (long long)found.addend);
+ CHECK(cfree_slice_eq_cstr(found.sym_name, "foo"),
+ "reloc target name = %.*s", CFREE_SLICE_ARG(found.sym_name));
}
}
}
@@ -246,29 +263,20 @@ int main(void) {
fprintf(stderr, "FAIL: cfree_compiler_new\n");
return 1;
}
- Compiler* c = (Compiler*)cc;
-
- if (setjmp(c->panic)) {
- compiler_run_cleanups(c);
- cfree_compiler_free(cc);
- fprintf(stderr, "FAIL: compiler_panic during roundtrip\n");
- return 1;
- }
-
/* Build, emit, read back, inspect. */
- ObjBuilder* in = build_input(c);
+ CfreeObjBuilder* in = build_input(cc, target);
CfreeWriter* w = NULL;
(void)cfree_writer_mem(&g_heap, &w);
- emit_elf(c, in, w);
+ CHECK(cfree_obj_builder_emit(in, w) == CFREE_OK, "emit");
size_t out_len = 0;
const uint8_t* out_data = cfree_writer_mem_bytes(w, &out_len);
/* Sanity: ELF magic. */
- CHECK(out_len >= 64, "emit_elf produced too few bytes (%zu)", out_len);
+ CHECK(out_len >= 64, "ELF emit produced too few bytes (%zu)", out_len);
CHECK(out_len >= 4 && out_data[0] == 0x7f && out_data[1] == 'E' &&
out_data[2] == 'L' && out_data[3] == 'F',
- "emit_elf output missing ELF magic");
+ "ELF emit output missing ELF magic");
/* Round-trip: copy to private buffer first since the mem writer's
* storage is freed on close. */
@@ -276,13 +284,17 @@ int main(void) {
memcpy(roundtrip, out_data, out_len);
cfree_writer_close(w);
- ObjBuilder* back = read_elf(c, "smoke", roundtrip, out_len);
- CHECK(back != NULL, "read_elf returned NULL");
- if (back) verify_shape(back, c->global);
+ CfreeSlice input = {.data = roundtrip, .len = out_len};
+ CfreeObjFile* back = NULL;
+ CHECK(cfree_obj_open(&ctx, CFREE_SLICE_LIT("smoke"), &input, &back) ==
+ CFREE_OK &&
+ back,
+ "cfree_obj_open failed");
+ if (back) verify_shape(back);
- if (back) obj_free(back);
+ if (back) cfree_obj_free(back);
free(roundtrip);
- obj_free(in);
+ cfree_obj_builder_free(in);
cfree_compiler_free(cc);
if (g_failures) {
diff --git a/test/elf/unit/x64_disasm_annotations.c b/test/elf/unit/x64_disasm_annotations.c
@@ -8,16 +8,11 @@
#include <cfree/core.h>
#include <cfree/disasm.h>
#include <cfree/object.h>
-#include <setjmp.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "core/core.h"
-#include "core/pool.h"
-#include "obj/obj.h"
-
static void* heap_alloc(CfreeHeap* h, size_t n, size_t a) {
(void)h;
(void)a;
@@ -69,16 +64,22 @@ static CfreeTarget x64_elf_target(void) {
return t;
}
-static ObjBuilder* build_input(Compiler* c) {
- ObjBuilder* ob = obj_new(c);
- Pool* p = c->global;
- Sym text = pool_intern_slice(p, SLICE_LIT(".text"));
- Sym start = pool_intern_slice(p, SLICE_LIT("_start"));
- Sym load_data = pool_intern_slice(p, SLICE_LIT("load_data"));
- Sym foo = pool_intern_slice(p, SLICE_LIT("foo"));
- Sym bar = pool_intern_slice(p, SLICE_LIT("bar"));
- Sym global_data = pool_intern_slice(p, SLICE_LIT("global_data"));
- ObjSecId sec_text = obj_section(ob, text, SEC_TEXT, SF_ALLOC | SF_EXEC, 16);
+static CfreeObjBuilder* build_input(CfreeCompiler* c, CfreeTarget target) {
+ CfreeObjBuilder* ob = NULL;
+ CfreeObjSection sec_text = CFREE_SECTION_NONE;
+ CHECK(cfree_obj_builder_new(c, &ob) == CFREE_OK && ob,
+ "cfree_obj_builder_new");
+ if (!ob) return NULL;
+
+ CfreeObjSectionDesc text_desc = {
+ .name = cfree_sym_intern(c, CFREE_SLICE_LIT(".text")),
+ .kind = CFREE_SEC_TEXT,
+ .flags = CFREE_SF_ALLOC | CFREE_SF_EXEC,
+ .align = 16,
+ .entsize = 0,
+ };
+ CHECK(cfree_obj_builder_section(ob, &text_desc, &sec_text) == CFREE_OK,
+ "section .text");
static const uint8_t text_bytes[] = {
0xe8, 0x00, 0x00, 0x00, 0x00, /* call rel32 */
@@ -86,22 +87,104 @@ static ObjBuilder* build_input(Compiler* c) {
0xe9, 0x00, 0x00, 0x00, 0x00, /* jmp rel32 */
0xc3, /* ret */
};
- obj_write(ob, sec_text, text_bytes, sizeof text_bytes);
-
- obj_symbol(ob, start, SB_GLOBAL, SK_FUNC, sec_text, 0, sizeof text_bytes);
- obj_symbol(ob, load_data, SB_LOCAL, SK_NOTYPE, sec_text, 5, 0);
- ObjSymId sym_foo =
- obj_symbol(ob, foo, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0);
- ObjSymId sym_bar =
- obj_symbol(ob, bar, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0);
- ObjSymId sym_data =
- obj_symbol(ob, global_data, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0);
-
- obj_reloc(ob, sec_text, 1, R_X64_PLT32, sym_foo, -4);
- obj_reloc(ob, sec_text, 8, R_PC32, sym_data, -4);
- obj_reloc(ob, sec_text, 13, R_X64_PLT32, sym_bar, -4);
-
- obj_finalize(ob);
+ CHECK(cfree_obj_builder_write(ob, sec_text, text_bytes, sizeof text_bytes) ==
+ CFREE_OK,
+ "write .text");
+
+ CfreeObjSymbol sym_start = CFREE_OBJ_SYMBOL_NONE;
+ CfreeObjSymbolDesc start_desc = {
+ .name = cfree_sym_intern(c, CFREE_SLICE_LIT("_start")),
+ .bind = CFREE_SB_GLOBAL,
+ .kind = CFREE_SK_FUNC,
+ .section = sec_text,
+ .value = 0,
+ .size = sizeof text_bytes,
+ };
+ CHECK(cfree_obj_builder_symbol(ob, &start_desc, &sym_start) == CFREE_OK,
+ "symbol _start");
+
+ CfreeObjSymbol sym_load_data = CFREE_OBJ_SYMBOL_NONE;
+ CfreeObjSymbolDesc load_data_desc = {
+ .name = cfree_sym_intern(c, CFREE_SLICE_LIT("load_data")),
+ .bind = CFREE_SB_LOCAL,
+ .kind = CFREE_SK_NOTYPE,
+ .section = sec_text,
+ .value = 5,
+ .size = 0,
+ };
+ CHECK(cfree_obj_builder_symbol(ob, &load_data_desc, &sym_load_data) ==
+ CFREE_OK,
+ "symbol load_data");
+
+ CfreeObjSymbol sym_foo = CFREE_OBJ_SYMBOL_NONE;
+ CfreeObjSymbolDesc foo_desc = {
+ .name = cfree_sym_intern(c, CFREE_SLICE_LIT("foo")),
+ .bind = CFREE_SB_GLOBAL,
+ .kind = CFREE_SK_UNDEF,
+ .section = CFREE_SECTION_NONE,
+ .value = 0,
+ .size = 0,
+ };
+ CHECK(cfree_obj_builder_symbol(ob, &foo_desc, &sym_foo) == CFREE_OK,
+ "symbol foo");
+
+ CfreeObjSymbol sym_bar = CFREE_OBJ_SYMBOL_NONE;
+ CfreeObjSymbolDesc bar_desc = {
+ .name = cfree_sym_intern(c, CFREE_SLICE_LIT("bar")),
+ .bind = CFREE_SB_GLOBAL,
+ .kind = CFREE_SK_UNDEF,
+ .section = CFREE_SECTION_NONE,
+ .value = 0,
+ .size = 0,
+ };
+ CHECK(cfree_obj_builder_symbol(ob, &bar_desc, &sym_bar) == CFREE_OK,
+ "symbol bar");
+
+ CfreeObjSymbol sym_data = CFREE_OBJ_SYMBOL_NONE;
+ CfreeObjSymbolDesc data_desc = {
+ .name = cfree_sym_intern(c, CFREE_SLICE_LIT("global_data")),
+ .bind = CFREE_SB_GLOBAL,
+ .kind = CFREE_SK_UNDEF,
+ .section = CFREE_SECTION_NONE,
+ .value = 0,
+ .size = 0,
+ };
+ CHECK(cfree_obj_builder_symbol(ob, &data_desc, &sym_data) == CFREE_OK,
+ "symbol global_data");
+
+ CfreeObjRelocDesc rel_foo = {
+ .section = sec_text,
+ .offset = 1,
+ .kind = {.arch = target.arch,
+ .obj_fmt = target.obj,
+ .code = CFREE_RELOC_X64_PLT32},
+ .symbol = sym_foo,
+ .addend = -4,
+ };
+ CfreeObjRelocDesc rel_data = {
+ .section = sec_text,
+ .offset = 8,
+ .kind = {.arch = target.arch,
+ .obj_fmt = target.obj,
+ .code = CFREE_RELOC_PC32},
+ .symbol = sym_data,
+ .addend = -4,
+ };
+ CfreeObjRelocDesc rel_bar = {
+ .section = sec_text,
+ .offset = 13,
+ .kind = {.arch = target.arch,
+ .obj_fmt = target.obj,
+ .code = CFREE_RELOC_X64_PLT32},
+ .symbol = sym_bar,
+ .addend = -4,
+ };
+ CHECK(cfree_obj_builder_reloc(ob, &rel_foo) == CFREE_OK, "reloc foo");
+ CHECK(cfree_obj_builder_reloc(ob, &rel_data) == CFREE_OK,
+ "reloc global_data");
+ CHECK(cfree_obj_builder_reloc(ob, &rel_bar) == CFREE_OK, "reloc bar");
+
+ CHECK(cfree_obj_builder_finalize(ob) == CFREE_OK, "finalize");
return ob;
}
@@ -173,8 +256,7 @@ int main(void) {
CfreeTarget target = x64_elf_target();
CfreeContext ctx;
CfreeCompiler* cc = NULL;
- Compiler* c;
- ObjBuilder* ob;
+ CfreeObjBuilder* ob;
CfreeWriter* w = NULL;
const uint8_t* obj_data;
size_t obj_len = 0;
@@ -189,17 +271,10 @@ int main(void) {
fprintf(stderr, "FAIL: cfree_compiler_new\n");
return 1;
}
- c = (Compiler*)cc;
- if (setjmp(c->panic)) {
- compiler_run_cleanups(c);
- cfree_compiler_free(cc);
- fprintf(stderr, "FAIL: compiler_panic\n");
- return 1;
- }
- ob = build_input(c);
+ ob = build_input(cc, target);
CHECK(cfree_writer_mem(&g_heap, &w) == CFREE_OK && w, "writer_mem object");
- emit_elf(c, ob, w);
+ CHECK(cfree_obj_builder_emit(ob, w) == CFREE_OK, "emit object");
obj_data = cfree_writer_mem_bytes(w, &obj_len);
bytes.data = obj_data;
bytes.len = obj_len;
@@ -208,7 +283,7 @@ int main(void) {
check_disasm_annotations(&bytes, &ctx);
cfree_writer_close(w);
- obj_free(ob);
+ cfree_obj_builder_free(ob);
cfree_compiler_free(cc);
if (g_failures) {
diff --git a/test/macho/cfree-roundtrip-macho.c b/test/macho/cfree-roundtrip-macho.c
@@ -1,17 +1,13 @@
-/* cfree-roundtrip-macho: read a Mach-O object via libcfree's read_macho,
- * then re-emit via emit_macho. Round-trip oracle for the Mach-O writer.
+/* cfree-roundtrip-macho: read a Mach-O object via libcfree's public object
+ * reader, then re-emit via the public object builder API. Round-trip oracle
+ * for the Mach-O writer.
*
* Usage: cfree-roundtrip-macho <in.o> <out.o>
*
- * Behavior: cfree_detect_target on the input bytes selects the Compiler
- * target; read_macho parses into an ObjBuilder; emit_macho writes the
- * canonical re-emit to out.o. Diagnostics go to stderr via the libc
- * heap + stderr diag sink. compiler_panic exits the process with
- * status 2 and the diagnostic text on stderr.
- *
- * Mixes public (<cfree.h>) and internal (src/obj/obj.h, src/core/core.h)
- * headers — this is a test binary, not a libcfree consumer, so seeing
- * the internal surface is fine. */
+ * Behavior: cfree_obj_open detects and parses the input into a CfreeObjFile;
+ * cfree_obj_file_builder exposes the already-finalized builder; and
+ * cfree_obj_builder_emit writes the canonical re-emit to out.o. Diagnostics
+ * go to stderr via the libc heap + stderr diag sink. */
#include <cfree/core.h>
#include <cfree/object.h>
@@ -23,9 +19,6 @@
#include <sys/stat.h>
#include <unistd.h>
-#include "core/core.h"
-#include "obj/obj.h"
-
static void* heap_alloc(CfreeHeap* h, size_t n, size_t a) {
(void)h;
(void)a;
@@ -121,8 +114,10 @@ int main(int argc, char** argv) {
}
CfreeTarget target;
- if (cfree_detect_target(in_data, in_len, &target) != CFREE_OK) {
- fprintf(stderr, "error: %s: not a recognized object file\n", in_path);
+ if (cfree_detect_target(in_data, in_len, &target) != CFREE_OK ||
+ target.obj != CFREE_OBJ_MACHO) {
+ fprintf(stderr, "error: %s: not a recognized Mach-O object file\n",
+ in_path);
free(in_data);
return 1;
}
@@ -133,32 +128,39 @@ int main(int argc, char** argv) {
ctx.diag = &g_diag;
ctx.now = -1;
- CfreeCompiler* c = NULL;
- if (cfree_compiler_new(target, &ctx, &c) != CFREE_OK || !c) {
- fprintf(stderr, "error: cfree_compiler_new failed\n");
+ CfreeObjFile* obj = NULL;
+ CfreeSlice name = cfree_slice_cstr(in_path);
+ CfreeSlice input = {.data = in_data, .len = in_len};
+ CfreeStatus st = cfree_obj_open(&ctx, name, &input, &obj);
+ if (st != CFREE_OK || !obj) {
free(in_data);
- return 1;
+ return 2;
}
- if (setjmp(((Compiler*)c)->panic)) {
- compiler_run_cleanups((Compiler*)c);
- cfree_compiler_free(c);
+ CfreeObjBuilder* ob = cfree_obj_file_builder(obj);
+ if (!ob) {
+ fprintf(stderr, "error: cfree_obj_file_builder failed\n");
+ cfree_obj_free(obj);
free(in_data);
- return 2;
+ return 1;
}
- ObjBuilder* ob = read_macho((Compiler*)c, in_path, in_data, in_len);
-
CfreeWriter* w = NULL;
if (cfree_writer_mem(&g_heap, &w) != CFREE_OK || !w) {
fprintf(stderr, "error: cfree_writer_mem failed\n");
- obj_free(ob);
- cfree_compiler_free(c);
+ cfree_obj_free(obj);
free(in_data);
return 1;
}
- emit_macho((Compiler*)c, ob, w);
+ st = cfree_obj_builder_emit(ob, w);
+ if (st != CFREE_OK) {
+ fprintf(stderr, "error: cfree_obj_builder_emit failed\n");
+ cfree_writer_close(w);
+ cfree_obj_free(obj);
+ free(in_data);
+ return 1;
+ }
size_t out_len = 0;
const uint8_t* out_data = cfree_writer_mem_bytes(w, &out_len);
@@ -167,8 +169,7 @@ int main(int argc, char** argv) {
if (rc != 0) fprintf(stderr, "error: cannot write %s\n", out_path);
cfree_writer_close(w);
- obj_free(ob);
- cfree_compiler_free(c);
+ cfree_obj_free(obj);
free(in_data);
return rc == 0 ? 0 : 1;
}
diff --git a/test/opt/opt_test.c b/test/opt/opt_test.c
@@ -64,8 +64,8 @@ static int g_checks;
} while (0)
typedef struct TestCtx {
- Compiler cc;
Compiler* c;
+ CfreeContext ctx;
CfreeCgTypeId i8;
CfreeCgTypeId i16;
CfreeCgTypeId i32;
@@ -75,11 +75,10 @@ typedef struct TestCtx {
} TestCtx;
static void tc_init_target(TestCtx* tc, CfreeArchKind arch, CfreeOSKind os) {
- CfreeContext ctx;
- memset(&ctx, 0, sizeof ctx);
- ctx.heap = &g_heap;
- ctx.diag = &g_diag;
- ctx.now = -1;
+ memset(tc, 0, sizeof *tc);
+ tc->ctx.heap = &g_heap;
+ tc->ctx.diag = &g_diag;
+ tc->ctx.now = -1;
CfreeTarget tgt;
memset(&tgt, 0, sizeof tgt);
@@ -89,10 +88,11 @@ static void tc_init_target(TestCtx* tc, CfreeArchKind arch, CfreeOSKind os) {
tgt.ptr_size = 8;
tgt.ptr_align = 8;
- static CfreeContext s_ctx;
- s_ctx = ctx;
- compiler_init(&tc->cc, tgt, &s_ctx);
- tc->c = &tc->cc;
+ if (cfree_compiler_new(tgt, &tc->ctx, (CfreeCompiler**)&tc->c) != CFREE_OK ||
+ !tc->c) {
+ fprintf(stderr, "fatal: cfree_compiler_new failed\n");
+ abort();
+ }
{
CfreeCgBuiltinTypes b = cfree_cg_builtin_types(tc->c);
tc->i8 = b.id[CFREE_CG_BUILTIN_I8];
@@ -108,7 +108,10 @@ static void tc_init(TestCtx* tc) {
tc_init_target(tc, CFREE_ARCH_ARM_64, CFREE_OS_LINUX);
}
-static void tc_fini(TestCtx* tc) { compiler_fini(&tc->cc); }
+static void tc_fini(TestCtx* tc) {
+ cfree_compiler_free(tc->c);
+ tc->c = NULL;
+}
static Operand op_reg_(Reg r, CfreeCgTypeId ty) {
Operand o;
diff --git a/test/parse/CORPUS.md b/test/parse/CORPUS.md
@@ -258,6 +258,7 @@ cover `register` and other specifier interactions.
| `6_7_1_01_register_sizeof` | ★ | `register int x=42; return (int)sizeof(x)>0?x:0;` — `sizeof` is legal on a register variable | 42 |
| `6_7_1_02_register_multi_decl` | ★ | `register int x=40, y=2; return x+y;` — multi-declarator with `register` | 42 |
| `6_7_1_03_thread_local_basic` | ★ | `_Thread_local int x=42; return x;` — `_Thread_local` accepted as a storage-class specifier on a file-scope object | 42 |
+| `6_7_file_scope_register` | ★ | `register int x; return sizeof(x)==sizeof(int)?42:1;` — GNU-compatible file-scope `register` declaration accepted | 42 |
## §6.7.2 Type specifiers
@@ -632,7 +633,6 @@ cursor, so mixed and class-only sequences both need direct exercise.
| `6_7_6_function_returning_function` | ★ | function declarator | function returning function |
| `6_7_6_variadic_not_last` | ★ | function declarator | variadic marker not in final position |
| `6_7_file_scope_auto` | RED | storage class | `auto` at file scope; currently accepted |
-| `6_7_file_scope_register` | RED | storage class | `register` at file scope; currently accepted |
| `6_7_storage_class_static_thread_local_function` | RED | storage class | `_Thread_local` on a function definition; currently accepted |
| `6_7_thread_local_block_auto` | RED | storage class | block-scope `_Thread_local` without `static`/`extern`; currently accepted |
| `6_7_thread_local_function` | RED | storage class | `_Thread_local` on a function definition; currently accepted |
diff --git a/test/parse/cases/6_7_file_scope_register.c b/test/parse/cases/6_7_file_scope_register.c
@@ -0,0 +1,5 @@
+register int x;
+
+int test_main(void) {
+ return (int)sizeof(x) == (int)sizeof(int) ? 42 : 1;
+}
diff --git a/test/parse/cases/6_7_file_scope_register.expected b/test/parse/cases/6_7_file_scope_register.expected
@@ -0,0 +1 @@
+42
diff --git a/test/parse/cases_err/6_7_file_scope_register.c b/test/parse/cases_err/6_7_file_scope_register.c
@@ -1,5 +0,0 @@
-register int x;
-
-int test_main(void) {
- return x;
-}
diff --git a/test/test.mk b/test/test.mk
@@ -26,52 +26,50 @@
# asm_parse / cfree_disasm_iter_* are still stubs; the harness builds
# and runs end-to-end so the wiring stays exercised. See doc/ASM.md.
-.PHONY: \
- test \
- test-driver \
- test-pp \
- test-pp-err \
- test-elf \
+TEST_TARGETS = \
+ test-aa64-inline \
+ test-abi-classify \
+ test-ar \
+ test-ar-driver \
+ test-asm \
+ test-bounce \
+ test-cbackend \
+ test-cg-api \
test-coff \
test-coff-mingw-import \
test-coff-windows-ucrt \
- test-ar \
- test-ar-driver \
- test-strip-driver \
+ test-debug \
+ test-driver \
+ test-dwarf \
+ test-elf \
+ test-emu \
+ test-glibc \
+ test-glibc-rv64 \
+ test-isa \
+ test-lib-deps \
+ test-link \
+ test-musl \
+ test-musl-rv64 \
test-objcopy-driver \
test-objdump-driver \
- test-link \
- test-cg-api \
- test-abi-classify \
- test-toy \
test-opt \
- test-dwarf \
- test-debug \
test-parse \
test-parse-err \
- test-asm \
- test-wasm-front \
- test-isa \
- test-aa64-inline \
- test-rv64-inline \
- test-rv64-jit \
- test-emu \
- test-x64-inline \
- test-x64-dbg \
+ test-pp \
+ test-pp-err \
test-rt-headers \
test-rt-runtime \
- test-musl \
- test-musl-rv64 \
- test-glibc \
- test-glibc-rv64 \
- test-lib-deps \
- test-smoke-x64 \
+ test-rv64-inline \
+ test-rv64-jit \
test-smoke-rv64 \
- test-bounce \
- test-cbackend \
- rv64-doctor
+ test-smoke-x64 \
+ test-strip-driver \
+ test-toy \
+ test-wasm-front \
+ test-x64-dbg \
+ test-x64-inline
-test: \
+DEFAULT_TEST_TARGETS = \
test-driver \
test-pp \
test-pp-err \
@@ -98,9 +96,10 @@ test: \
test-x64-dbg \
test-rt-headers \
test-lib-deps
-# `test-cbackend` is intentionally not in the default `test` target: the
-# Phase 1 C backend skips most fixtures pending later phases, which would
-# add noise to the default summary. Run it explicitly to gate progress.
+
+.PHONY: test $(TEST_TARGETS)
+
+test: $(DEFAULT_TEST_TARGETS)
test-driver: bin
@CFREE=$(abspath $(BIN)) sh test/driver/run.sh
@@ -189,13 +188,13 @@ test-isa: $(AA64_ISA_TEST_BIN) $(RV64_DECODE_TEST_BIN)
$(AA64_ISA_TEST_BIN)
$(RV64_DECODE_TEST_BIN)
-$(AA64_ISA_TEST_BIN): test/arch/aa64_isa_test.c $(LIB_AR)
+$(AA64_ISA_TEST_BIN): test/arch/aa64_isa_test.c $(LIB_OBJS)
@mkdir -p $(dir $@)
- $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/aa64_isa_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/aa64_isa_test.c $(LIB_OBJS) -o $@
-$(RV64_DECODE_TEST_BIN): test/arch/rv64_decode_test.c $(LIB_AR)
+$(RV64_DECODE_TEST_BIN): test/arch/rv64_decode_test.c $(LIB_OBJS)
@mkdir -p $(dir $@)
- $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/rv64_decode_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/rv64_decode_test.c $(LIB_OBJS) -o $@
# test-emu: emulator unit tests. The rv64 lane builds a tiny in-memory rv64
# ELF and asserts the lifted/JIT path exits through the syscall handler with
@@ -235,29 +234,26 @@ $(ABI_CLASSIFY_TEST_BIN): test/api/abi_classify_test.c $(LIB_OBJS)
test-toy: bin
@CFREE=$(abspath $(BIN)) test/toy/run.sh
-# Phase-4b Track-C: aarch64 inline-asm backend unit test (doc/INLINEASM.md
-# §7.1 "Track C"). Drives aa_asm_block (CGTarget vtable) directly with
-# hand-rolled Operand arrays and asserts the emitted .text bytes match
-# the expected machine encoding. Independent of cg / parser — it gates
-# the per-arch template walker + per-mnemonic dispatch in isolation.
+INLINE_PUBLIC_TEST_HDR = test/arch/inline_public_test.h
+
+# Public-API inline-asm backend tests. These emit a tiny function through CG,
+# reopen the object through the public object reader, and assert the expected
+# instruction bytes are present in .text.
AA64_INLINE_TEST_BIN = build/test/aa64_inline_test
test-aa64-inline: $(AA64_INLINE_TEST_BIN)
$(AA64_INLINE_TEST_BIN)
-$(AA64_INLINE_TEST_BIN): test/arch/aa64_inline_test.c $(LIB_AR)
+$(AA64_INLINE_TEST_BIN): test/arch/aa64_inline_test.c $(INLINE_PUBLIC_TEST_HDR) $(LIB_AR)
@mkdir -p $(dir $@)
$(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/aa64_inline_test.c $(LIB_AR) -o $@
-# rv64 inline-asm backend unit test — parallel to test-aa64-inline.
-# Drives rv_asm_block directly with hand-rolled Operand arrays and
-# asserts the emitted .text bytes match the expected machine encoding.
RV64_INLINE_TEST_BIN = build/test/rv64_inline_test
test-rv64-inline: $(RV64_INLINE_TEST_BIN)
$(RV64_INLINE_TEST_BIN)
-$(RV64_INLINE_TEST_BIN): test/arch/rv64_inline_test.c $(LIB_AR)
+$(RV64_INLINE_TEST_BIN): test/arch/rv64_inline_test.c $(INLINE_PUBLIC_TEST_HDR) $(LIB_AR)
@mkdir -p $(dir $@)
$(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/rv64_inline_test.c $(LIB_AR) -o $@
@@ -279,16 +275,12 @@ $(RV64_JIT_TEST_BIN): test/link/rv64_jit_test.c $(LIB_AR)
@mkdir -p $(dir $@)
$(CC) $(TEST_HOST_CFLAGS) test/link/rv64_jit_test.c $(LIB_AR) -o $@
-# x86_64 peer of test-aa64-inline (doc/INLINEASM.md). Drives x_asm_block
-# (CGTarget vtable) directly with hand-rolled Operand arrays and asserts
-# the emitted .text bytes match the expected machine encoding. Also
-# pins the current %w/%x/%a behavior and exercises the new %b modifier.
X64_INLINE_TEST_BIN = build/test/x64_inline_test
test-x64-inline: $(X64_INLINE_TEST_BIN)
$(X64_INLINE_TEST_BIN)
-$(X64_INLINE_TEST_BIN): test/arch/x64_inline_test.c $(LIB_AR)
+$(X64_INLINE_TEST_BIN): test/arch/x64_inline_test.c $(INLINE_PUBLIC_TEST_HDR) $(LIB_AR)
@mkdir -p $(dir $@)
$(CC) $(TEST_HOST_CFLAGS) -Isrc test/arch/x64_inline_test.c $(LIB_AR) -o $@
@@ -338,6 +330,7 @@ COFF_MIXED_ARCHIVE_BIN = build/test/pe-mixed-archive
LINK_EXE_RUNNER = build/test/link-exe-runner
JIT_RUNNER = build/test/jit-runner
PARSE_RUNNER = build/test/parse-runner
+ASM_RUNNER = build/test/asm-runner
WASM_TOOL = build/test/wasm-tool
$(ROUNDTRIP_BIN): test/elf/cfree-roundtrip.c $(LIB_AR)
@@ -353,41 +346,41 @@ $(ROUNDTRIP_BIN_MACHO): test/macho/cfree-roundtrip-macho.c $(LIB_AR)
# PE/COFF round-trip harness (test/coff/). All-in-one binary: builds
# hand-crafted ObjBuilders and asserts emit_coff/read_coff round-trip
# stability for both x86_64-windows and aarch64-windows.
-$(ROUNDTRIP_BIN_COFF): test/coff/cfree-roundtrip-coff.c $(LIB_AR)
+$(ROUNDTRIP_BIN_COFF): test/coff/cfree-roundtrip-coff.c $(LIB_OBJS)
@mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/cfree-roundtrip-coff.c $(LIB_AR) -o $@
+ $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/cfree-roundtrip-coff.c $(LIB_OBJS) -o $@
# PE import-directory smoke test (test/coff/pe-import-smoke.c).
# Exercises the full chain: short-import shim bytes -> link_add_obj_bytes
# (reclassified as DSO) -> link_resolve -> link_emit_coff. Verifies the
# produced PE32+ via x86_64-w64-mingw32-objdump; skips cleanly if absent.
-$(COFF_IMPORT_SMOKE_BIN): test/coff/pe-import-smoke.c $(LIB_AR)
+$(COFF_IMPORT_SMOKE_BIN): test/coff/pe-import-smoke.c $(LIB_OBJS)
@mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-smoke.c $(LIB_AR) -o $@
+ $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-smoke.c $(LIB_OBJS) -o $@
# PE import test against a real mingw archive (test/coff/pe-import-mingw.c).
# Exercises the long-form import-archive absorption path
# (link_add_archive_bytes -> classify_coff_archive_member). Skips cleanly
# when the mingw toolchain isn't installed.
-$(COFF_IMPORT_MINGW_BIN): test/coff/pe-import-mingw.c $(LIB_AR)
+$(COFF_IMPORT_MINGW_BIN): test/coff/pe-import-mingw.c $(LIB_OBJS)
@mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-mingw.c $(LIB_AR) -o $@
+ $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-import-mingw.c $(LIB_OBJS) -o $@
# read_coff_dso forwarder-export contract (test/coff/pe-dso-forwarder.c).
# Synthesizes a tiny PE32+ DLL with one direct and one forwarder export
# and asserts both surface as OBJ_SEC_NONE globals on the ObjBuilder.
-$(COFF_DSO_FORWARDER_BIN): test/coff/pe-dso-forwarder.c $(LIB_AR)
+$(COFF_DSO_FORWARDER_BIN): test/coff/pe-dso-forwarder.c $(LIB_OBJS)
@mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-dso-forwarder.c $(LIB_AR) -o $@
+ $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-dso-forwarder.c $(LIB_OBJS) -o $@
# Mixed-member archive (test/coff/pe-mixed-archive.c). Verifies that
# one archive containing both a short-import member and a long-form
# COFF object with a defined data symbol satisfies references through
# both shapes — the same composition libucrt.a uses (API-set imports
# alongside lib64_libucrt_extra_a-*.o helpers).
-$(COFF_MIXED_ARCHIVE_BIN): test/coff/pe-mixed-archive.c $(LIB_AR)
+$(COFF_MIXED_ARCHIVE_BIN): test/coff/pe-mixed-archive.c $(LIB_OBJS)
@mkdir -p $(dir $@)
- $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-mixed-archive.c $(LIB_AR) -o $@
+ $(CC) $(HARNESS_CFLAGS) -Isrc test/coff/pe-mixed-archive.c $(LIB_OBJS) -o $@
$(LINK_EXE_RUNNER): test/link/harness/link_exe_runner.c $(LIB_AR)
@mkdir -p $(dir $@)
@@ -401,11 +394,17 @@ $(PARSE_RUNNER): test/parse/harness/parse_runner.c $(LIB_AR)
@mkdir -p $(dir $@)
$(CC) $(HARNESS_CFLAGS) test/parse/harness/parse_runner.c $(LIB_AR) -o $@
+$(ASM_RUNNER): test/asm/harness/asm_runner.c $(LIB_AR)
+ @mkdir -p $(dir $@)
+ $(CC) $(HARNESS_CFLAGS) test/asm/harness/asm_runner.c $(LIB_AR) -o $@
+
$(WASM_TOOL): test/wasm/harness/wasm_tool.c $(LIB_AR)
@mkdir -p $(dir $@)
$(CC) $(HARNESS_CFLAGS) -I. test/wasm/harness/wasm_tool.c $(LIB_AR) -o $@
test-elf: lib bin-soft $(ROUNDTRIP_BIN)
+ CFREE_ELF_UNIT_CFLAGS='$(HOST_MODE_CFLAGS)' \
+ CFREE_ELF_UNIT_LDFLAGS='$(HOST_MODE_LDFLAGS)' \
bash test/elf/run.sh
# PE/COFF round-trip harness plus optional hosted Windows smoke. The
@@ -436,9 +435,9 @@ test-opt: bin $(OPT_TEST_BIN)
bash test/opt/run.sh
bash test/opt/phase0_guardrails.sh
-$(OPT_TEST_BIN): test/opt/opt_test.c $(LIB_AR)
+$(OPT_TEST_BIN): test/opt/opt_test.c $(LIB_OBJS)
@mkdir -p $(dir $@)
- $(CC) $(TEST_HOST_CFLAGS) -Isrc test/opt/opt_test.c $(LIB_AR) -o $@
+ $(CC) $(TEST_HOST_CFLAGS) -Isrc test/opt/opt_test.c $(LIB_OBJS) -o $@
test-parse: lib rt $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
bash test/parse/run.sh
@@ -446,11 +445,9 @@ test-parse: lib rt $(PARSE_RUNNER) $(ROUNDTRIP_BIN) $(LINK_EXE_RUNNER) $(JIT_RUN
test-parse-err: lib $(PARSE_RUNNER)
sh test/parse/run_errors.sh
-# test-asm: builds asm-runner inside the run.sh driver (the harness owns
-# its compile rule, mirroring test/pp). Phase 1: every smoke case
-# carries a .skip sidecar; the harness wiring runs on every CI run so
-# regressions in the public asm/disasm surface are caught early.
-test-asm: lib
+# test-asm: the Makefile owns harness binaries so they inherit host flags
+# consistently with the rest of the test suite.
+test-asm: lib $(ASM_RUNNER) $(LINK_EXE_RUNNER) $(JIT_RUNNER)
bash test/asm/run.sh
test-wasm-front: bin $(WASM_TOOL) $(LINK_EXE_RUNNER) $(JIT_RUNNER)