commit 04a7d100199049a9550ce78eaee8a5835c571c64
parent f5937502ab2ef9f881c5611c98f62ce0288b7202
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 18 May 2026 21:12:24 -0700
Build runtime with cfree
Diffstat:
16 files changed, 643 insertions(+), 38 deletions(-)
diff --git a/include/cfree/cg.h b/include/cfree/cg.h
@@ -888,6 +888,8 @@ typedef struct CfreeCgInlineAsm {
* are pre-interned strings. clobber_abi_sets names target-defined ABI register
* sets such as all caller-saved registers. */
void cfree_cg_inline_asm(CfreeCg*, CfreeCgInlineAsm asm_block);
+void cfree_cg_file_scope_asm(CfreeCg*, const char* asm_source,
+ size_t asm_source_len);
/* ============================================================
* Data Definitions
diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c
@@ -1141,6 +1141,8 @@ static void parse_external_decl(Parser* p) {
}
static void parse_file_scope_asm(Parser* p) {
+ u8* bytes;
+ size_t nbytes;
advance(p); /* asm / __asm__ */
for (;;) {
if (is_kw(p, &p->cur, KW_VOLATILE)) {
@@ -1157,10 +1159,14 @@ static void parse_file_scope_asm(Parser* p) {
if (p->cur.kind != TOK_STR) {
perr(p, "expected string literal in file-scope asm");
}
+ bytes = decode_string_literal(p, &p->cur, &nbytes);
advance(p);
expect_punct(p, ')', "')' after file-scope asm");
expect_punct(p, ';', "';' after file-scope asm");
- perr(p, "file-scope asm is disabled");
+ if (nbytes > 0) --nbytes; /* drop decode_string_literal's trailing NUL */
+ if (pcg_emit_enabled(p)) {
+ cfree_cg_file_scope_asm(p->cg, (const char*)bytes, nbytes);
+ }
}
static void parse_translation_unit(Parser* p) {
diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c
@@ -458,6 +458,7 @@ static const Type* offsetof_designator(Parser* p, const Type* base, u32* off);
static u32 cint_bits(Parser* p, const Type* ty) {
u32 sz = ty ? c_abi_sizeof(p->abi, ty) : 8u;
+ if (ty && (ty->kind == TY_INT128 || ty->kind == TY_UINT128)) return 128;
if (sz >= 8) return 64;
return sz * 8u;
}
@@ -2390,6 +2391,29 @@ static void coerce_fp_cmp_operands(Parser* p, const Type* common) {
cg_swap(p->cg);
}
+static void coerce_arith_operands(Parser* p, const Type* common) {
+ if (!common) return;
+ if (cg_top_type(p->cg) != common) cg_convert(p->cg, common);
+ cg_swap(p->cg);
+ if (cg_top_type(p->cg) != common) cg_convert(p->cg, common);
+ cg_swap(p->cg);
+}
+
+static CmpOp unsigned_rel_cmp(CmpOp cop) {
+ switch (cop) {
+ case CMP_LT_S:
+ return CMP_LT_U;
+ case CMP_LE_S:
+ return CMP_LE_U;
+ case CMP_GT_S:
+ return CMP_GT_U;
+ case CMP_GE_S:
+ return CMP_GE_U;
+ default:
+ return cop;
+ }
+}
+
static void parse_mul(Parser* p) {
parse_unary(p);
for (;;) {
@@ -2420,6 +2444,8 @@ static void parse_mul(Parser* p) {
if (common) {
emit_fp_binop(p, bop, common);
} else {
+ const Type* icommon = cint_common_type(p, lt, rt);
+ coerce_arith_operands(p, icommon);
cg_binop(p->cg, bop);
}
}
@@ -2487,6 +2513,8 @@ static void emit_add_or_sub(Parser* p, BinOp bop) {
emit_fp_binop(p, bop, common);
return;
}
+ common = cint_common_type(p, lt, rt);
+ coerce_arith_operands(p, common);
cg_binop(p->cg, bop);
}
@@ -2573,6 +2601,10 @@ static void parse_rel(Parser* p) {
case CMP_GE_S: cop = CMP_GE_F; break;
default: break;
}
+ } else if (type_is_arith(lt) && type_is_arith(rt)) {
+ common = cint_common_type(p, lt, rt);
+ coerce_arith_operands(p, common);
+ if (!cint_signed(p, common)) cop = unsigned_rel_cmp(cop);
}
}
cg_cmp(p->cg, cop);
@@ -2613,6 +2645,10 @@ static void parse_eq(Parser* p) {
perr(p, "equality operator requires scalar operands");
}
if (common) coerce_fp_cmp_operands(p, common);
+ else if (type_is_arith(lt) && type_is_arith(rt)) {
+ common = cint_common_type(p, lt, rt);
+ coerce_arith_operands(p, common);
+ }
}
cg_cmp(p->cg, cop);
}
@@ -2628,6 +2664,8 @@ static void parse_band(Parser* p) {
if (!type_is_int(cg_top2_type(p->cg)) || !type_is_int(cg_top_type(p->cg))) {
perr(p, "bitwise operator requires integer operands");
}
+ coerce_arith_operands(p, cint_common_type(p, cg_top2_type(p->cg),
+ cg_top_type(p->cg)));
cg_binop(p->cg, BO_AND);
}
}
@@ -2642,6 +2680,8 @@ static void parse_bxor(Parser* p) {
if (!type_is_int(cg_top2_type(p->cg)) || !type_is_int(cg_top_type(p->cg))) {
perr(p, "bitwise operator requires integer operands");
}
+ coerce_arith_operands(p, cint_common_type(p, cg_top2_type(p->cg),
+ cg_top_type(p->cg)));
cg_binop(p->cg, BO_XOR);
}
}
@@ -2656,6 +2696,8 @@ static void parse_bor(Parser* p) {
if (!type_is_int(cg_top2_type(p->cg)) || !type_is_int(cg_top_type(p->cg))) {
perr(p, "bitwise operator requires integer operands");
}
+ coerce_arith_operands(p, cint_common_type(p, cg_top2_type(p->cg),
+ cg_top_type(p->cg)));
cg_binop(p->cg, BO_OR);
}
}
@@ -2949,6 +2991,8 @@ void parse_assign_expr(Parser* p) {
} else {
cg_binop(p->cg, compound);
}
+ coerce_top_to_type(p, lhs);
+ if (p->cg_type_sp >= 2) p->cg_type_stack[p->cg_type_sp - 2u] = lhs;
cg_store(p->cg);
}
diff --git a/rt/Makefile b/rt/Makefile
@@ -1,11 +1,12 @@
# Included by the repository-root Makefile. Paths are root-relative.
-RT_AR ?= llvm-ar
-RT_AS ?= $(CC)
-RT_AS_COMPILE_FLAGS ?= -c
+RT_CC ?= $(BIN) cc
+RT_AR ?= $(BIN) ar
+RT_AS ?= $(BIN) as
+RT_AS_COMPILE_FLAGS ?=
RT_BUILD_DIR = rt/build
-RT_COMMON_CFLAGS = -ffreestanding -fno-builtin -std=c11 -Wpedantic -Wall -Wextra -Werror
+RT_COMMON_CFLAGS = -isystem rt/include -isystem rt/include/libc -Werror
RT_LIB_INCS = -Irt/lib/include/common -Irt/lib/impl
RT_VARIANTS = \
@@ -157,8 +158,7 @@ RT_CORO_SRCS_riscv32 = rt/lib/coro/riscv32.c rt/lib/coro/coro.c
RT_CORO_SRCS_riscv64 = rt/lib/coro/riscv64.c rt/lib/coro/coro.c
RT_LDBL128_SRCS = rt/lib/fp_tf/fp_tf.c rt/lib/fp_ti/fp_ti.c
-RT_LDBL128_FLAGS = -Irt/lib/include/lp64_le_ldbl128 \
- -include rt/lib/include/lp64_le_ldbl128/tf_supplement.h
+RT_LDBL128_FLAGS = -Irt/lib/include/lp64_le_ldbl128 -DCFREERT_LDBL128=1
RT_SAVE_RESTORE_SRCS_lp64 = rt/lib/riscv/rv64.S
RT_SAVE_RESTORE_SRCS_ilp32 = rt/lib/riscv/rv32.S
@@ -183,30 +183,34 @@ RT_CFLAGS_$(1) := \
-target $$(RT_$(1)_TARGET) \
-DHAS_INT128=$$(RT_$(1)_INT128) \
$$(RT_ABI_INC_$$(RT_$(1)_ABI)) \
- $$(RT_$(1)_ARCH_FLAGS) \
$$(if $$(RT_$(1)_LDBL128),$$(RT_LDBL128_FLAGS)) \
- $$(if $$(RT_$(1)_CORO),-Irt/include) \
- $$(if $$(RT_$(1)_SAVE_RESTORE),$$(RT_SAVE_RESTORE_FLAGS)) \
- $$(RT_AEABI_FLAGS_$$(RT_$(1)_AEABI))
+ $$(if $$(RT_$(1)_CORO),-Irt/include)
+RT_ASFLAGS_$(1) := \
+ -target $$(RT_$(1)_TARGET) \
+ -DHAS_INT128=$$(RT_$(1)_INT128) \
+ -D__ASSEMBLER__=1 \
+ $$(RT_ABI_INC_$$(RT_$(1)_ABI)) \
+ $$(if $$(RT_$(1)_LDBL128),$$(RT_LDBL128_FLAGS)) \
+ $$(if $$(RT_$(1)_CORO),-Irt/include)
RT_OBJS_$(1) := $$(patsubst rt/lib/%,$$(RT_BUILD_DIR)/$(1)/%.o,$$(RT_SRCS_$(1)))
rt-$(1): $$(RT_BUILD_DIR)/$(1)/libcfree_rt.a
-$$(RT_BUILD_DIR)/$(1)/libcfree_rt.a: $$(RT_OBJS_$(1))
+$$(RT_BUILD_DIR)/$(1)/libcfree_rt.a: $$(RT_OBJS_$(1)) | $$(BIN)
@mkdir -p $$(dir $$@)
$$(RT_AR) rcs $$@ $$^
-$$(RT_BUILD_DIR)/$(1)/%.s.o: rt/lib/%.s
+$$(RT_BUILD_DIR)/$(1)/%.s.o: rt/lib/%.s | $$(BIN)
@mkdir -p $$(dir $$@)
- $$(RT_AS) $$(RT_CFLAGS_$(1)) $$(RT_AS_COMPILE_FLAGS) $$< -o $$@
+ $$(RT_AS) $$(RT_ASFLAGS_$(1)) $$(RT_AS_COMPILE_FLAGS) $$< -o $$@
-$$(RT_BUILD_DIR)/$(1)/%.S.o: rt/lib/%.S
+$$(RT_BUILD_DIR)/$(1)/%.S.o: rt/lib/%.S | $$(BIN)
@mkdir -p $$(dir $$@)
- $$(RT_AS) $$(RT_CFLAGS_$(1)) $$(RT_AS_COMPILE_FLAGS) $$< -o $$@
+ $$(RT_AS) $$(RT_ASFLAGS_$(1)) $$(RT_AS_COMPILE_FLAGS) $$< -o $$@
-$$(RT_BUILD_DIR)/$(1)/%.o: rt/lib/%
+$$(RT_BUILD_DIR)/$(1)/%.o: rt/lib/% | $$(BIN)
@mkdir -p $$(dir $$@)
- $$(CC) $$(RT_CFLAGS_$(1)) -c $$< -o $$@
+ $$(RT_CC) $$(RT_CFLAGS_$(1)) -c $$< -o $$@
endef
$(foreach variant,$(RT_VARIANTS),$(eval $(call RT_VARIANT_template,$(variant))))
diff --git a/rt/lib/atomic/atomic_common.inc b/rt/lib/atomic/atomic_common.inc
@@ -108,11 +108,8 @@ void __atomic_store(int size, void *dest, void *src, int model) {
int __atomic_compare_exchange(int size, void *ptr, void *expected,
void *desired, int success, int failure) {
-#define LOCK_FREE_ACTION(type) \
- return __atomic_compare_exchange_n((type *)ptr, (type *)expected, \
- *(type *)desired, 0, success, failure);
- LOCK_FREE_CASES(ptr);
-#undef LOCK_FREE_ACTION
+ (void)success;
+ (void)failure;
Lock *l = lock_for_pointer(ptr);
lock(l);
if (__builtin_memcmp(ptr, expected, size) == 0) {
@@ -174,14 +171,13 @@ OPTIMISED_CASES
OPTIMISED_CASES
#undef OPTIMISED_CASE
-#define OPTIMISED_CASE(n, lockfree, type) \
- bool __atomic_compare_exchange_##n(type *ptr, type *expected, type desired, \
- int success, int failure) { \
- if (lockfree(ptr)) \
- return __atomic_compare_exchange_n(ptr, expected, desired, 0, success, \
- failure); \
- Lock *l = lock_for_pointer(ptr); \
- lock(l); \
+ #define OPTIMISED_CASE(n, lockfree, type) \
+ bool __atomic_compare_exchange_##n(type *ptr, type *expected, type desired, \
+ int success, int failure) { \
+ (void)success; \
+ (void)failure; \
+ Lock *l = lock_for_pointer(ptr); \
+ lock(l); \
if (*ptr == *expected) { \
*ptr = desired; \
unlock(l); \
diff --git a/rt/lib/atomic/atomic_freestanding.c b/rt/lib/atomic/atomic_freestanding.c
@@ -27,10 +27,8 @@ static inline void unlock(Lock* l) {
}
static inline void lock(Lock* l) {
- uintptr_t old = 0;
- while (!__atomic_compare_exchange_n((uintptr_t*)l, &old, 1, 1,
- __ATOMIC_ACQUIRE, __ATOMIC_RELAXED))
- old = 0;
+ while (__atomic_exchange_n((uintptr_t*)l, 1, __ATOMIC_ACQUIRE))
+ ;
}
#pragma clang diagnostic pop
diff --git a/rt/lib/fp_tf/fp_tf.c b/rt/lib/fp_tf/fp_tf.c
@@ -9,6 +9,10 @@
// ============================================================
// Section 1: QUAD precision arith / compare / conv / fix
// ============================================================
+#ifdef CFREERT_LDBL128
+#include "tf_supplement.h"
+#endif
+
// ---- addtf3.c ----
#define QUAD_PRECISION
#include "fp_add_impl.inc"
diff --git a/src/api/cg.c b/src/api/cg.c
@@ -9,6 +9,8 @@
#include "api/cg_type.h"
#include "arch/arch.h"
#include "arch/regalloc.h"
+#include "asm/asm.h"
+#include "asm/asm_lex.h"
#include "core/arena.h"
#include "core/heap.h"
#include "core/pool.h"
@@ -4806,6 +4808,19 @@ void cfree_cg_inline_asm(CfreeCg *g, CfreeCgInlineAsm asm_block) {
h->free(h, out_reg_owned, noutputs);
}
+void cfree_cg_file_scope_asm(CfreeCg *g, const char *asm_source,
+ size_t asm_source_len) {
+ AsmLexer *lex;
+ if (!g || !asm_source)
+ return;
+ api_local_const_memory_boundary(g);
+ lex = asm_lex_open_mem(g->c, "<file-scope asm>", asm_source, asm_source_len);
+ if (!lex)
+ compiler_panic(g->c, api_no_loc(), "CfreeCg: file-scope asm out of memory");
+ asm_parse(g->c, lex, g->mc);
+ asm_lex_close(lex);
+}
+
/* ============================================================
* Labels / branches
* ============================================================ */
diff --git a/src/arch/rv64/arch.c b/src/arch/rv64/arch.c
@@ -1,6 +1,7 @@
#include "arch/arch.h"
#include "abi/abi_internal.h"
+#include "arch/rv64/asm.h"
#include "arch/rv64/rv64.h"
#include "core/bytes.h"
#include "link/link_arch.h"
@@ -65,7 +66,7 @@ const ArchImpl arch_impl_rv64 = {
.name = "rv64",
.abi_vtable = rv64_abi_vtable,
.cgtarget_new = rv64_cgtarget_new,
- .asm_new = NULL,
+ .asm_new = rv64_arch_asm_new,
.disasm_new = NULL,
.apply_label_fixup = rv64_apply_label_fixup,
.link = &link_arch_rv64,
diff --git a/src/arch/rv64/asm.c b/src/arch/rv64/asm.c
@@ -0,0 +1,167 @@
+#include "arch/rv64/asm.h"
+
+#include <string.h>
+
+#include "arch/rv64/internal.h"
+#include "asm/asm_helpers.h"
+#include "core/arena.h"
+
+typedef struct Rv64Asm {
+ ArchAsm base;
+ Compiler* c;
+} Rv64Asm;
+
+typedef struct Rv64Mem {
+ u32 base;
+ i32 disp;
+} Rv64Mem;
+
+static int sym_eq(AsmDriver* d, Sym s, const char* lit) {
+ size_t n = 0;
+ const char* p = pool_str(asm_driver_pool(d), s, &n);
+ return p && strlen(lit) == n && memcmp(p, lit, n) == 0;
+}
+
+static int rv_reg_from_name(AsmDriver* d, Sym s, u32* reg_out, int* fp_out) {
+ size_t n = 0;
+ const char* p = pool_str(asm_driver_pool(d), s, &n);
+ u32 r;
+ int fp = 0;
+ if (!p || !n) return 0;
+ if (n == 2 && p[0] == 'r' && p[1] == 'a')
+ r = RV_RA;
+ else if (n == 2 && p[0] == 's' && p[1] == 'p')
+ r = RV_SP;
+ else if (n == 2 && p[0] == 't' && p[1] == '0')
+ r = RV_T0;
+ else if (n == 2 && p[0] == 'a' && p[1] >= '0' && p[1] <= '7')
+ r = RV_A0 + (u32)(p[1] - '0');
+ else if (n == 2 && p[0] == 's' && p[1] >= '0' && p[1] <= '1')
+ r = RV_S0 + (u32)(p[1] - '0');
+ else if (n == 3 && p[0] == 's' && p[1] == '1' && p[2] >= '0' &&
+ p[2] <= '1')
+ r = 26u + (u32)(p[2] - '0');
+ else if (n == 2 && p[0] == 's' && p[1] >= '2' && p[1] <= '9')
+ r = RV_S2 + (u32)(p[1] - '2');
+ else if (n == 3 && p[0] == 'f' && p[1] == 's' && p[2] >= '0' &&
+ p[2] <= '1') {
+ r = RV_S0 + (u32)(p[2] - '0');
+ fp = 1;
+ } else if (n == 4 && p[0] == 'f' && p[1] == 's' && p[2] == '1' &&
+ p[3] >= '0' && p[3] <= '1') {
+ r = 26u + (u32)(p[3] - '0');
+ fp = 1;
+ } else if (n == 3 && p[0] == 'f' && p[1] == 's' && p[2] >= '2' &&
+ p[2] <= '9') {
+ r = RV_S2 + (u32)(p[2] - '2');
+ fp = 1;
+ } else {
+ return 0;
+ }
+ if (reg_out) *reg_out = r;
+ if (fp_out) *fp_out = fp;
+ return 1;
+}
+
+static u32 parse_reg(AsmDriver* d, int* fp_out) {
+ AsmTok t = asm_driver_next(d);
+ u32 r;
+ if (t.kind != ASM_TOK_IDENT || !rv_reg_from_name(d, t.v.ident, &r, fp_out))
+ asm_driver_panic(d, "rv64 asm: bad register");
+ return r;
+}
+
+static Rv64Mem parse_mem(AsmDriver* d) {
+ Rv64Mem m;
+ m.disp = (i32)asm_driver_parse_const(d);
+ asm_driver_expect_punct(d, '(', "'(' in rv64 memory operand");
+ m.base = parse_reg(d, NULL);
+ asm_driver_expect_punct(d, ')', "')' in rv64 memory operand");
+ return m;
+}
+
+static void expect_comma(AsmDriver* d) {
+ if (!asm_driver_eat_comma(d)) asm_driver_panic(d, "rv64 asm: expected ','");
+}
+
+static void rv64_arch_asm_insn(ArchAsm* base, AsmDriver* d, Sym mnemonic) {
+ MCEmitter* mc = asm_driver_mc(d);
+ u32 rd;
+ u32 rs1;
+ u32 rs2;
+ Rv64Mem mem;
+ int fp = 0;
+ (void)base;
+ (void)asm_driver_cur_section(d);
+
+ if (sym_eq(d, mnemonic, "ret")) {
+ rv64_emit32(mc, rv_i(0, RV_RA, 0, RV_ZERO, RV_JALR));
+ return;
+ }
+ if (sym_eq(d, mnemonic, "ebreak")) {
+ rv64_emit32(mc, 0x00100073u);
+ return;
+ }
+
+ if (sym_eq(d, mnemonic, "li")) {
+ rd = parse_reg(d, NULL);
+ expect_comma(d);
+ rv64_emit_load_imm(mc, 1, rd, asm_driver_parse_const(d));
+ return;
+ }
+ if (sym_eq(d, mnemonic, "seqz")) {
+ rd = parse_reg(d, NULL);
+ expect_comma(d);
+ rs1 = parse_reg(d, NULL);
+ rv64_emit32(mc, rv_sltiu(rd, rs1, 1));
+ return;
+ }
+ if (sym_eq(d, mnemonic, "mv")) {
+ rd = parse_reg(d, NULL);
+ expect_comma(d);
+ rs1 = parse_reg(d, NULL);
+ rv64_emit32(mc, rv_addi(rd, rs1, 0));
+ return;
+ }
+ if (sym_eq(d, mnemonic, "add")) {
+ rd = parse_reg(d, NULL);
+ expect_comma(d);
+ rs1 = parse_reg(d, NULL);
+ expect_comma(d);
+ rs2 = parse_reg(d, NULL);
+ rv64_emit32(mc, rv_add(rd, rs1, rs2));
+ return;
+ }
+ if (sym_eq(d, mnemonic, "jalr")) {
+ rs1 = parse_reg(d, NULL);
+ rv64_emit32(mc, rv_i(0, rs1, 0, RV_RA, RV_JALR));
+ return;
+ }
+ if (sym_eq(d, mnemonic, "sd") || sym_eq(d, mnemonic, "fsd")) {
+ rs2 = parse_reg(d, &fp);
+ expect_comma(d);
+ mem = parse_mem(d);
+ rv64_emit32(mc, rv_s(mem.disp, rs2, mem.base, 0x3, fp ? RV_STORE_FP : RV_STORE));
+ return;
+ }
+ if (sym_eq(d, mnemonic, "ld") || sym_eq(d, mnemonic, "fld")) {
+ rd = parse_reg(d, &fp);
+ expect_comma(d);
+ mem = parse_mem(d);
+ rv64_emit32(mc, rv_i(mem.disp, mem.base, 0x3, rd, fp ? RV_LOAD_FP : RV_LOAD));
+ return;
+ }
+
+ asm_driver_panic(d, "rv64 asm: unsupported instruction");
+}
+
+static void rv64_arch_asm_destroy(ArchAsm* base) { (void)base; }
+
+ArchAsm* rv64_arch_asm_new(Compiler* c) {
+ Rv64Asm* a = arena_new(c->tu, Rv64Asm);
+ memset(a, 0, sizeof *a);
+ a->base.insn = rv64_arch_asm_insn;
+ a->base.destroy = rv64_arch_asm_destroy;
+ a->c = c;
+ return &a->base;
+}
diff --git a/src/arch/rv64/asm.h b/src/arch/rv64/asm.h
@@ -0,0 +1,8 @@
+#ifndef CFREE_ARCH_RV64_ASM_H
+#define CFREE_ARCH_RV64_ASM_H
+
+#include "arch/arch.h"
+
+ArchAsm* rv64_arch_asm_new(Compiler*);
+
+#endif
diff --git a/src/arch/x64/arch.c b/src/arch/x64/arch.c
@@ -1,10 +1,12 @@
#include "arch/arch.h"
#include "abi/abi_internal.h"
+#include "arch/x64/asm.h"
#include "arch/x64/x64.h"
#include "core/bytes.h"
#include "link/link_arch.h"
#include "obj/elf.h"
+#include "obj/macho.h"
#include "obj/obj.h"
extern const LinkArchDesc link_arch_x64;
@@ -22,6 +24,15 @@ static const ArchElfOps x64_elf_ops = {
.reloc_from = elf_x86_64_reloc_from,
};
+static const ArchMachoOps x64_macho_ops = {
+ .cputype = CPU_TYPE_X86_64,
+ .cpusubtype = CPU_SUBTYPE_X86_64_ALL,
+ .reloc_to = macho_x86_64_reloc_to,
+ .reloc_pcrel = macho_x86_64_reloc_pcrel,
+ .reloc_length = macho_x86_64_reloc_length,
+ .reloc_from = macho_x86_64_reloc_from,
+};
+
static int x64_apply_label_fixup(Compiler* c, const ArchLabelFixup* fx) {
(void)c;
if (!fx || fx->kind != R_PC32 || fx->width != 4) return 1;
@@ -38,12 +49,12 @@ const ArchImpl arch_impl_x64 = {
.name = "x64",
.abi_vtable = x64_abi_vtable,
.cgtarget_new = x64_cgtarget_new,
- .asm_new = NULL,
+ .asm_new = x64_arch_asm_new,
.disasm_new = NULL,
.apply_label_fixup = x64_apply_label_fixup,
.link = &link_arch_x64,
.elf = &x64_elf_ops,
- .macho = NULL,
+ .macho = &x64_macho_ops,
.register_name = NULL,
.register_index = NULL,
.register_count = NULL,
diff --git a/src/arch/x64/asm.c b/src/arch/x64/asm.c
@@ -0,0 +1,253 @@
+#include "arch/x64/asm.h"
+
+#include <string.h>
+
+#include "arch/x64/internal.h"
+#include "asm/asm_helpers.h"
+#include "core/arena.h"
+
+typedef struct X64Asm {
+ ArchAsm base;
+ Compiler* c;
+} X64Asm;
+
+typedef enum X64AsmOperandKind {
+ X64_ASM_OP_REG,
+ X64_ASM_OP_IMM,
+ X64_ASM_OP_MEM,
+ X64_ASM_OP_IND_REG,
+} X64AsmOperandKind;
+
+typedef struct X64AsmOperand {
+ u8 kind;
+ u8 width;
+ u8 reg;
+ u8 base;
+ i64 imm;
+ i32 disp;
+} X64AsmOperand;
+
+static int sym_eq(AsmDriver* d, Sym s, const char* lit) {
+ size_t n = 0;
+ const char* p = pool_str(asm_driver_pool(d), s, &n);
+ return p && strlen(lit) == n && memcmp(p, lit, n) == 0;
+}
+
+static int x64_reg_from_name(AsmDriver* d, Sym s, u32* reg_out,
+ u32* width_out) {
+ size_t n = 0;
+ const char* p = pool_str(asm_driver_pool(d), s, &n);
+ u32 width = 8;
+ u32 reg;
+ if (!p || n < 2) return 0;
+ if (p[0] == 'e') {
+ width = 4;
+ ++p;
+ --n;
+ } else if (p[0] == 'r') {
+ width = 8;
+ ++p;
+ --n;
+ } else {
+ return 0;
+ }
+ if (n == 2 && p[0] == 'a' && p[1] == 'x')
+ reg = X64_RAX;
+ else if (n == 2 && p[0] == 'c' && p[1] == 'x')
+ reg = X64_RCX;
+ else if (n == 2 && p[0] == 'd' && p[1] == 'x')
+ reg = X64_RDX;
+ else if (n == 2 && p[0] == 'b' && p[1] == 'x')
+ reg = X64_RBX;
+ else if (n == 2 && p[0] == 's' && p[1] == 'p')
+ reg = X64_RSP;
+ else if (n == 2 && p[0] == 'b' && p[1] == 'p')
+ reg = X64_RBP;
+ else if (n == 2 && p[0] == 's' && p[1] == 'i')
+ reg = X64_RSI;
+ else if (n == 2 && p[0] == 'd' && p[1] == 'i')
+ reg = X64_RDI;
+ else if (n >= 2 && p[0] == '1' && p[1] >= '2' && p[1] <= '5' &&
+ (n == 2 || (n == 3 && p[2] == 'd')))
+ reg = X64_R12 + (u32)(p[1] - '2');
+ else
+ return 0;
+ if (reg_out) *reg_out = reg;
+ if (width_out) *width_out = width;
+ return 1;
+}
+
+static u32 parse_reg(AsmDriver* d, u32* width_out) {
+ AsmTok t;
+ u32 reg;
+ if (!asm_driver_eat_punct(d, '%')) asm_driver_panic(d, "x64 asm: expected register");
+ t = asm_driver_next(d);
+ if (t.kind != ASM_TOK_IDENT ||
+ !x64_reg_from_name(d, t.v.ident, ®, width_out)) {
+ asm_driver_panic(d, "x64 asm: bad register");
+ }
+ return reg;
+}
+
+static X64AsmOperand parse_operand(AsmDriver* d) {
+ X64AsmOperand op;
+ AsmTok t;
+ memset(&op, 0, sizeof op);
+ t = asm_driver_peek(d);
+ if (asm_driver_eat_punct(d, '*')) {
+ op.kind = X64_ASM_OP_IND_REG;
+ op.reg = (u8)parse_reg(d, NULL);
+ return op;
+ }
+ if (asm_driver_eat_punct(d, '$')) {
+ op.kind = X64_ASM_OP_IMM;
+ op.imm = asm_driver_parse_const(d);
+ return op;
+ }
+ if (asm_driver_tok_is_punct(t, '%')) {
+ u32 width = 8;
+ op.kind = X64_ASM_OP_REG;
+ op.reg = (u8)parse_reg(d, &width);
+ op.width = (u8)width;
+ return op;
+ }
+ op.kind = X64_ASM_OP_MEM;
+ op.disp = 0;
+ if (!asm_driver_tok_is_punct(t, '(')) {
+ op.disp = (i32)asm_driver_parse_const(d);
+ }
+ asm_driver_expect_punct(d, '(', "'(' in x64 memory operand");
+ op.base = (u8)parse_reg(d, NULL);
+ asm_driver_expect_punct(d, ')', "')' in x64 memory operand");
+ return op;
+}
+
+static void expect_comma(AsmDriver* d) {
+ if (!asm_driver_eat_comma(d)) asm_driver_panic(d, "x64 asm: expected ','");
+}
+
+static void emit_cmov_eq(MCEmitter* mc, u32 dst, u32 src) {
+ u8 op[2] = {0x0f, 0x44};
+ emit_rex(mc, 1, dst, 0, src);
+ mc->emit_bytes(mc, op, 2);
+ {
+ u8 mr = modrm(3u, dst, src);
+ mc->emit_bytes(mc, &mr, 1);
+ }
+}
+
+static void emit_indirect_branch(MCEmitter* mc, u32 sub, u32 reg) {
+ u8 op = 0xff;
+ emit_rex(mc, 0, 0, 0, reg);
+ mc->emit_bytes(mc, &op, 1);
+ {
+ u8 mr = modrm(3u, sub, reg);
+ mc->emit_bytes(mc, &mr, 1);
+ }
+}
+
+static void emit_ud2(MCEmitter* mc) {
+ u8 op[2] = {0x0f, 0x0b};
+ mc->emit_bytes(mc, op, 2);
+}
+
+static void x64_arch_asm_insn(ArchAsm* base, AsmDriver* d, Sym mnemonic) {
+ X64Asm* a = (X64Asm*)base;
+ MCEmitter* mc = asm_driver_mc(d);
+ X64AsmOperand src;
+ X64AsmOperand dst;
+ (void)a;
+ (void)asm_driver_cur_section(d);
+
+ if (sym_eq(d, mnemonic, "ret")) {
+ emit_ret(mc);
+ return;
+ }
+ if (sym_eq(d, mnemonic, "ud2")) {
+ emit_ud2(mc);
+ return;
+ }
+
+ src = parse_operand(d);
+ if (sym_eq(d, mnemonic, "jmpq")) {
+ if (src.kind != X64_ASM_OP_IND_REG) asm_driver_panic(d, "x64 asm: jmpq form");
+ emit_indirect_branch(mc, 4u, src.reg);
+ return;
+ }
+ if (sym_eq(d, mnemonic, "callq")) {
+ if (src.kind != X64_ASM_OP_IND_REG) asm_driver_panic(d, "x64 asm: callq form");
+ emit_indirect_branch(mc, 2u, src.reg);
+ return;
+ }
+
+ expect_comma(d);
+ dst = parse_operand(d);
+
+ if (sym_eq(d, mnemonic, "movq")) {
+ if (src.kind == X64_ASM_OP_REG && dst.kind == X64_ASM_OP_MEM) {
+ emit_mov_store(mc, 8, src.reg, dst.base, dst.disp);
+ return;
+ }
+ if (src.kind == X64_ASM_OP_MEM && dst.kind == X64_ASM_OP_REG) {
+ emit_mov_load(mc, 8, 0, dst.reg, src.base, src.disp);
+ return;
+ }
+ if (src.kind == X64_ASM_OP_REG && dst.kind == X64_ASM_OP_REG) {
+ emit_mov_rr(mc, 1, dst.reg, src.reg);
+ return;
+ }
+ } else if (sym_eq(d, mnemonic, "movl")) {
+ if (src.kind == X64_ASM_OP_IMM && dst.kind == X64_ASM_OP_REG) {
+ x64_emit_load_imm(mc, 0, dst.reg, src.imm);
+ return;
+ }
+ } else if (sym_eq(d, mnemonic, "movslq")) {
+ if (src.kind == X64_ASM_OP_REG && dst.kind == X64_ASM_OP_REG) {
+ emit_extend_rr(mc, 1, 1, 4, dst.reg, src.reg);
+ return;
+ }
+ } else if (sym_eq(d, mnemonic, "leaq")) {
+ if (src.kind == X64_ASM_OP_MEM && dst.kind == X64_ASM_OP_REG) {
+ emit_lea(mc, dst.reg, src.base, src.disp);
+ return;
+ }
+ } else if (sym_eq(d, mnemonic, "xorl")) {
+ if (src.kind == X64_ASM_OP_REG && dst.kind == X64_ASM_OP_REG) {
+ emit_alu_rr(mc, 0, 0x31, dst.reg, src.reg);
+ return;
+ }
+ } else if (sym_eq(d, mnemonic, "testq")) {
+ if (src.kind == X64_ASM_OP_REG && dst.kind == X64_ASM_OP_REG) {
+ emit_alu_rr(mc, 1, 0x85, dst.reg, src.reg);
+ return;
+ }
+ } else if (sym_eq(d, mnemonic, "cmoveq")) {
+ if (src.kind == X64_ASM_OP_REG && dst.kind == X64_ASM_OP_REG) {
+ emit_cmov_eq(mc, dst.reg, src.reg);
+ return;
+ }
+ } else if (sym_eq(d, mnemonic, "andq")) {
+ if (src.kind == X64_ASM_OP_IMM && dst.kind == X64_ASM_OP_REG) {
+ if (imm_fits_i8(src.imm))
+ emit_alu_imm8(mc, 1, 4u, dst.reg, (i8)src.imm);
+ else if (imm_fits_i32(src.imm))
+ emit_alu_imm32(mc, 1, 4u, dst.reg, (i32)src.imm);
+ else
+ asm_driver_panic(d, "x64 asm: andq immediate out of range");
+ return;
+ }
+ }
+
+ asm_driver_panic(d, "x64 asm: unsupported instruction form");
+}
+
+static void x64_arch_asm_destroy(ArchAsm* base) { (void)base; }
+
+ArchAsm* x64_arch_asm_new(Compiler* c) {
+ X64Asm* a = arena_new(c->tu, X64Asm);
+ memset(a, 0, sizeof *a);
+ a->base.insn = x64_arch_asm_insn;
+ a->base.destroy = x64_arch_asm_destroy;
+ a->c = c;
+ return &a->base;
+}
diff --git a/src/arch/x64/asm.h b/src/arch/x64/asm.h
@@ -0,0 +1,8 @@
+#ifndef CFREE_ARCH_X64_ASM_H
+#define CFREE_ARCH_X64_ASM_H
+
+#include "arch/arch.h"
+
+ArchAsm* x64_arch_asm_new(Compiler*);
+
+#endif
diff --git a/src/obj/macho.h b/src/obj/macho.h
@@ -258,5 +258,9 @@ u32 macho_aarch64_reloc_to(u32 kind /* RelocKind */);
u32 macho_aarch64_reloc_pcrel(u32 kind /* RelocKind */);
u32 macho_aarch64_reloc_length(u32 kind /* RelocKind */);
u32 macho_aarch64_reloc_from(u32 macho_type);
+u32 macho_x86_64_reloc_to(u32 kind /* RelocKind */);
+u32 macho_x86_64_reloc_pcrel(u32 kind /* RelocKind */);
+u32 macho_x86_64_reloc_length(u32 kind /* RelocKind */);
+u32 macho_x86_64_reloc_from(u32 macho_type);
#endif
diff --git a/src/obj/macho_reloc_x86_64.c b/src/obj/macho_reloc_x86_64.c
@@ -0,0 +1,84 @@
+#include "core/util.h"
+#include "obj/macho.h"
+
+u32 macho_x86_64_reloc_to(u32 kind /* RelocKind */) {
+ switch (kind) {
+ case R_NONE:
+ return (u32)-1;
+ case R_ABS64:
+ case R_ABS32:
+ return X86_64_RELOC_UNSIGNED;
+ case R_PC32:
+ case R_REL32:
+ case R_PC64:
+ case R_REL64:
+ case R_X64_PC8:
+ return X86_64_RELOC_SIGNED;
+ case R_PLT32:
+ case R_X64_PLT32:
+ return X86_64_RELOC_BRANCH;
+ case R_X64_GOTPCRELX:
+ case R_X64_REX_GOTPCRELX:
+ return X86_64_RELOC_GOT_LOAD;
+ case R_X64_GOTPCREL:
+ return X86_64_RELOC_GOT;
+ case R_X64_TPOFF32:
+ return X86_64_RELOC_TLV;
+ default:
+ return (u32)-1;
+ }
+}
+
+u32 macho_x86_64_reloc_pcrel(u32 kind /* RelocKind */) {
+ switch (kind) {
+ case R_PC32:
+ case R_REL32:
+ case R_PC64:
+ case R_REL64:
+ case R_X64_PC8:
+ case R_PLT32:
+ case R_X64_PLT32:
+ case R_X64_GOTPCREL:
+ case R_X64_GOTPCRELX:
+ case R_X64_REX_GOTPCRELX:
+ case R_X64_TPOFF32:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+u32 macho_x86_64_reloc_length(u32 kind /* RelocKind */) {
+ switch (kind) {
+ case R_ABS64:
+ case R_PC64:
+ case R_REL64:
+ return 3;
+ case R_X64_PC8:
+ return 0;
+ default:
+ return 2;
+ }
+}
+
+u32 macho_x86_64_reloc_from(u32 macho_type) {
+ switch (macho_type) {
+ case X86_64_RELOC_UNSIGNED:
+ return R_ABS64;
+ case X86_64_RELOC_SIGNED:
+ case X86_64_RELOC_SIGNED_1:
+ case X86_64_RELOC_SIGNED_2:
+ case X86_64_RELOC_SIGNED_4:
+ return R_PC32;
+ case X86_64_RELOC_BRANCH:
+ return R_X64_PLT32;
+ case X86_64_RELOC_GOT_LOAD:
+ return R_X64_REX_GOTPCRELX;
+ case X86_64_RELOC_GOT:
+ return R_X64_GOTPCREL;
+ case X86_64_RELOC_TLV:
+ return R_X64_TPOFF32;
+ default:
+ return (u32)-1;
+ }
+}