Add support for some LLVM intrinsic

This commit is contained in:
Dmitry Stogov 2023-11-15 15:17:36 +03:00
parent 3f14ac4252
commit a8e514266f
3 changed files with 263 additions and 32 deletions

13
ir.h
View File

@ -136,6 +136,18 @@ typedef enum _ir_type {
IR_LAST_TYPE
} ir_type;
#ifdef IR_64
# define IR_SIZE_T IR_U64
# define IR_SSIZE_T IR_I64
# define IR_UINTPTR_T IR_U64
# define IR_INTPTR_T IR_I64
#else
# define IR_SIZE_T IR_U32
# define IR_SSIZE_T IR_I32
# define IR_UINTPTR_T IR_U32
# define IR_INTPTR_T IR_I32
#endif
/* List of IR opcodes
* ==================
*
@ -399,6 +411,7 @@ typedef union _ir_val {
#define IR_CONST_EMIT (1<<0)
#define IR_CONST_FASTCALL_FUNC (1<<1)
#define IR_CONST_VARARG_FUNC (1<<2)
#define IR_CONST_BUILTIN_FUNC (1<<3)
/* IR Instruction */
typedef struct _ir_insn {

View File

@ -422,9 +422,93 @@ static void ir_emit_switch(ir_ctx *ctx, FILE *f, uint32_t b, ir_ref def, ir_insn
fprintf(f, "\t]\n");
}
static const char *ir_builtin_func_name(const char *name, ir_ref *last_arg)
{
if (strcmp(name, "memset")) {
*last_arg = IR_FALSE;
return (IR_SIZE_T == IR_I32) ? "llvm.memset.p0.i32" : "llvm.memset.p0.i64";
} else if (strcmp(name, "memcpy")) {
*last_arg = IR_FALSE;
return (IR_SIZE_T == IR_I32) ? "llvm.memcpy.p0.p0.i32" : "llvm.memcpy.p0.p0.i64";
} else if (strcmp(name, "memmove")) {
*last_arg = IR_FALSE;
return (IR_SIZE_T == IR_I32) ? "llvm.memmove.p0.p0.i32" : "llvm.memmve.p0.p0.i64";
} else if (strcmp(name, "sqrt")) {
return "llvm.sqrt.f64";
} else if (strcmp(name, "sqrtf")) {
return "llvm.sqrt.f32";
} else if (strcmp(name, "sin")) {
return "llvm.sin.f64";
} else if (strcmp(name, "sinf")) {
return "llvm.sin.f32";
} else if (strcmp(name, "cos")) {
return "llvm.cos.f64";
} else if (strcmp(name, "cosf")) {
return "llvm.cos.f32";
} else if (strcmp(name, "pow")) {
return "llvm.pow.f64";
} else if (strcmp(name, "powf")) {
return "llvm.pow.f32";
} else if (strcmp(name, "exp")) {
return "llvm.exp.f64";
} else if (strcmp(name, "expf")) {
return "llvm.exp.f32";
} else if (strcmp(name, "exp2")) {
return "llvm.exp2.f64";
} else if (strcmp(name, "exp2f")) {
return "llvm.exp2.f32";
} else if (strcmp(name, "exp10")) {
return "llvm.exp10.f64";
} else if (strcmp(name, "exp10f")) {
return "llvm.exp10.f32";
} else if (strcmp(name, "ldexp")) {
return "llvm.ldexp.f64.i32";
} else if (strcmp(name, "ldexpf")) {
return "llvm.ldexp.f32.i32";
} else if (strcmp(name, "frexp")) {
return "llvm.frexp.f64.i32";
} else if (strcmp(name, "frexpf")) {
return "llvm.frexp.f32.i32";
} else if (strcmp(name, "log")) {
return "llvm.log.f64";
} else if (strcmp(name, "logf")) {
return "llvm.log.f32";
} else if (strcmp(name, "log2")) {
return "llvm.log2.f64";
} else if (strcmp(name, "log2f")) {
return "llvm.log2.f32";
} else if (strcmp(name, "log10")) {
return "llvm.log10.f64";
} else if (strcmp(name, "log10f")) {
return "llvm.log10.f32";
} else if (strcmp(name, "copysign")) {
return "llvm.copysign.f64";
} else if (strcmp(name, "copysignf")) {
return "llvm.copysign.f32";
} else if (strcmp(name, "floor")) {
return "llvm.floor.f64";
} else if (strcmp(name, "floorf")) {
return "llvm.floor.f32";
} else if (strcmp(name, "ceil")) {
return "llvm.ceil.f64";
} else if (strcmp(name, "ceilf")) {
return "llvm.ceil.f32";
} else if (strcmp(name, "trunc")) {
return "llvm.trunc.f64";
} else if (strcmp(name, "truncf")) {
return "llvm.trunc.f32";
} else if (strcmp(name, "round")) {
return "llvm.round.f64";
} else if (strcmp(name, "roundf")) {
return "llvm.round.f32";
}
return name;
}
static void ir_emit_call(ir_ctx *ctx, FILE *f, ir_ref def, ir_insn *insn)
{
int j, k, n;
ir_ref last_arg = IR_UNUSED;
if (insn->type != IR_VOID) {
ir_emit_def_ref(ctx, f, def);
@ -446,7 +530,11 @@ static void ir_emit_call(ir_ctx *ctx, FILE *f, ir_ref def, ir_insn *insn)
// TODO: function prototype ???
if (IR_IS_CONST_REF(insn->op2)) {
fprintf(f, "@%s", ir_get_str(ctx, ctx->ir_base[insn->op2].val.i32));
const char *name = ir_get_str(ctx, ctx->ir_base[insn->op2].val.i32);
if (ctx->ir_base[insn->op2].const_flags & IR_CONST_BUILTIN_FUNC) {
name = ir_builtin_func_name(name, &last_arg);
}
fprintf(f, "@%s", name);
} else {
ir_emit_ref(ctx, f, insn->op2);
}
@ -460,6 +548,11 @@ static void ir_emit_call(ir_ctx *ctx, FILE *f, ir_ref def, ir_insn *insn)
fprintf(f, "%s ", ir_type_llvm_name[ctx->ir_base[k].type]);
ir_emit_ref(ctx, f, k);
}
if (last_arg) {
fprintf(f, ", ");
fprintf(f, "%s ", ir_type_llvm_name[ctx->ir_base[last_arg].type]);
ir_emit_ref(ctx, f, last_arg);
}
fprintf(f, ")\n");
if (insn->op == IR_TAILCALL) {
if (insn->type != IR_VOID) {
@ -833,8 +926,13 @@ static int ir_emit_func(ir_ctx *ctx, const char *name, FILE *f)
for (i = IR_UNUSED + 1, insn = ctx->ir_base - i; i < ctx->consts_count; i++, insn--) {
if (insn->op == IR_FUNC) {
const char *name = ir_get_str(ctx, insn->val.i32);
if (insn->const_flags & IR_CONST_BUILTIN_FUNC) {
ir_ref dummy;
name = ir_builtin_func_name(name, &dummy);
}
// TODO: function prototype ???
fprintf(f, "declare void @%s()\n", ir_get_str(ctx, insn->val.i32));
fprintf(f, "declare void @%s()\n", name);
} else if (insn->op == IR_SYM) {
// TODO: symbol "global" or "constant" ???
// TODO: symbol type ???

View File

@ -16,6 +16,12 @@
#define IR_BAD_TYPE IR_LAST_TYPE
#define BUILTIN_FUNC(name) \
ir_const_func(ctx, ir_strl(ctx, name, strlen(name)), IR_CONST_BUILTIN_FUNC)
#define BUILTIN_FP_FUNC(type, dname, fname) \
((type == IR_DOUBLE) ? BUILTIN_FUNC(dname) : BUILTIN_FUNC(fname))
static ir_ref llvm2ir_const_expr(ir_ctx *ctx, LLVMValueRef expr);
static ir_ref llvm2ir_auto_cast(ir_ctx *ctx, ir_ref ref, ir_type src_type, ir_type type);
@ -303,6 +309,26 @@ static ir_ref llvm2ir_icmp_op(ir_ctx *ctx, LLVMValueRef expr)
return ref;
}
static ir_ref llvm2ir_fcmp_op_isnan(ir_ctx *ctx, LLVMValueRef expr, ir_type type,
LLVMRealPredicate predicate, LLVMValueRef op0, LLVMValueRef op1)
{
ir_ref func, ref;
func = BUILTIN_FP_FUNC(type, "isnan", "isnanf");
if (LLVMGetValueKind(op1) == LLVMConstantIntValueKind) {
ref = ir_CALL_1(IR_BOOL, func, llvm2ir_op(ctx, op0, type));
} else {
ref = ir_OR_B(
ir_CALL_1(IR_BOOL, func, llvm2ir_op(ctx, op0, type)),
ir_CALL_1(IR_BOOL, func, llvm2ir_op(ctx, op1, type)));
}
if (predicate == LLVMRealORD) {
ref = ir_NOT_B(ref);
}
ir_addrtab_add(ctx->binding, (uintptr_t)expr, ref);
return ref;
}
static ir_ref llvm2ir_fcmp_op(ir_ctx *ctx, LLVMValueRef expr)
{
LLVMValueRef op0 = LLVMGetOperand(expr, 0);
@ -310,8 +336,9 @@ static ir_ref llvm2ir_fcmp_op(ir_ctx *ctx, LLVMValueRef expr)
ir_type type = llvm2ir_type(LLVMTypeOf(op0));
ir_ref ref;
ir_op op;
LLVMRealPredicate predicate = LLVMGetFCmpPredicate(expr);
switch (LLVMGetFCmpPredicate(expr)) {
switch (predicate) {
case LLVMRealOEQ:
case LLVMRealUEQ: op = IR_EQ; break;
case LLVMRealONE:
@ -324,8 +351,9 @@ static ir_ref llvm2ir_fcmp_op(ir_ctx *ctx, LLVMValueRef expr)
case LLVMRealOGE: op = IR_GE; break;
case LLVMRealOLT: op = IR_LT; break;
case LLVMRealOLE: op = IR_LE; break;
case LLVMRealUNO: op = IR_NE; break; // TODO: isnan() upport. IR_NE is invalid ???
case LLVMRealORD: op = IR_NE; break; // TODO: isnan() upport. IR_NE is invalid ???
case LLVMRealUNO:
case LLVMRealORD:
return llvm2ir_fcmp_op_isnan(ctx, expr, type, predicate, op0, op1);
default: IR_ASSERT(0); return 0;
}
ref = ir_fold2(ctx, IR_OPT(op, IR_BOOL), llvm2ir_op(ctx, op0, type), llvm2ir_op(ctx, op1, type));
@ -449,6 +477,7 @@ static ir_type llvm2ir_overflow_type(LLVMTypeRef stype)
static ir_ref llvm2ir_intrinsic(ir_ctx *ctx, LLVMValueRef insn, LLVMTypeRef ftype, uint32_t count, const char *name, size_t name_len)
{
ir_type type;
ir_ref func;
if (STR_START(name, name_len, "llvm.lifetime.")) {
/* skip */
@ -636,56 +665,147 @@ static ir_ref llvm2ir_intrinsic(ir_ctx *ctx, LLVMValueRef insn, LLVMTypeRef ftyp
} else if (STR_START(name, name_len, "llvm.cttz.")) {
// TODO:
} else if (STR_START(name, name_len, "llvm.memset.")) {
// TODO:
IR_ASSERT(count == 3 || count == 4);
func = BUILTIN_FUNC("memset");
return ir_CALL_3(IR_VOID, func,
llvm2ir_op(ctx, LLVMGetOperand(insn, 0), IR_ADDR),
llvm2ir_op(ctx, LLVMGetOperand(insn, 1), IR_I8),
llvm2ir_op(ctx, LLVMGetOperand(insn, 2), IR_SIZE_T));
} else if (STR_START(name, name_len, "llvm.memcpy.")) {
// TODO:
IR_ASSERT(count == 3 || count == 4);
func = BUILTIN_FUNC("memcpy");
return ir_CALL_3(IR_VOID, func,
llvm2ir_op(ctx, LLVMGetOperand(insn, 0), IR_ADDR),
llvm2ir_op(ctx, LLVMGetOperand(insn, 1), IR_ADDR),
llvm2ir_op(ctx, LLVMGetOperand(insn, 2), IR_SIZE_T));
} else if (STR_START(name, name_len, "llvm.memmove.")) {
// TODO:
IR_ASSERT(count == 3 || count == 4);
func = BUILTIN_FUNC("memmove");
return ir_CALL_3(IR_VOID, func,
llvm2ir_op(ctx, LLVMGetOperand(insn, 0), IR_ADDR),
llvm2ir_op(ctx, LLVMGetOperand(insn, 1), IR_ADDR),
llvm2ir_op(ctx, LLVMGetOperand(insn, 2), IR_SIZE_T));
} else if (STR_START(name, name_len, "llvm.frameaddress.")) {
// TODO:
} else if (STR_EQUAL(name, name_len, "llvm.debugtrap")) {
ir_TRAP();
return ctx->control;
} else if (STR_START(name, name_len, "llvm.sqrt.")) {
// TODO:
} else if (STR_START(name, name_len, "llvm.powi.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "sqrt", "sqrtf");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.sin.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "sin", "sinf");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.cos.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "cos", "cosf");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.pow.")) {
// TODO:
IR_ASSERT(count == 2);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "pow", "powf");
return ir_CALL_2(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type),
llvm2ir_op(ctx, LLVMGetOperand(insn, 1), type));
} else if (STR_START(name, name_len, "llvm.exp.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "exp", "expf");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.exp2.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "exp2", "exp2f");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.exp10.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "exp10", "exp10f");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.ldexp.")) {
// TODO:
IR_ASSERT(count == 2);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "ldexp", "ldexpf");
return ir_CALL_2(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type),
llvm2ir_op(ctx, LLVMGetOperand(insn, 1), IR_I32));
} else if (STR_START(name, name_len, "llvm.frexp.")) {
// TODO:
IR_ASSERT(count == 2);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "frexp", "frexpf");
return ir_CALL_2(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type),
llvm2ir_op(ctx, LLVMGetOperand(insn, 1), IR_ADDR));
} else if (STR_START(name, name_len, "llvm.log.")) {
// TODO:
} else if (STR_START(name, name_len, "llvm.log10.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "log", "logf");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.log2.")) {
// TODO:
} else if (STR_START(name, name_len, "llvm.frexp.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "log2", "log2f");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.log10.")) {
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "log10", "log10f");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.copysign.")) {
// TODO:
IR_ASSERT(count == 2);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "copysign", "copysignf");
return ir_CALL_2(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type),
llvm2ir_op(ctx, LLVMGetOperand(insn, 1), type));
} else if (STR_START(name, name_len, "llvm.floor.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "floor", "floorf");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.ceil.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "ceil", "ceilf");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.trunc.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "trunc", "truncf");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.round.")) {
// TODO:
} else if (STR_START(name, name_len, "llvm.fmuladd.")) {
// TODO:
IR_ASSERT(count == 1);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
func = BUILTIN_FP_FUNC(type, "round", "roundf");
return ir_CALL_1(type, func, llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type));
} else if (STR_START(name, name_len, "llvm.fmuladd.")
|| STR_START(name, name_len, "llvm.fma.")) {
IR_ASSERT(count == 3);
type = llvm2ir_type(LLVMGetReturnType(ftype));
IR_ASSERT(IR_IS_TYPE_FP(type));
return ir_fold2(ctx, IR_OPT(IR_ADD, type),
ir_fold2(ctx, IR_OPT(IR_MUL, type),
llvm2ir_op(ctx, LLVMGetOperand(insn, 0), type),
llvm2ir_op(ctx, LLVMGetOperand(insn, 1), type)),
llvm2ir_op(ctx, LLVMGetOperand(insn, 2), type));
} else {
fprintf(stderr, "Unsupported LLVM intrinsic: %s\n", name);
IR_ASSERT(0);