mirror of
https://github.com/danog/ir.git
synced 2024-12-02 17:55:40 +01:00
812 lines
22 KiB
C
812 lines
22 KiB
C
/*
|
|
* IR - Lightweight JIT Compilation Framework
|
|
* (LLVM code generator)
|
|
* Copyright (C) 2022 Zend by Perforce.
|
|
* Authors: Dmitry Stogov <dmitry@php.net>
|
|
*/
|
|
|
|
#include "ir.h"
|
|
#include "ir_private.h"
|
|
|
|
#include <math.h>
|
|
|
|
static const char *ir_type_llvm_name[IR_LAST_TYPE] = {
|
|
"void", // IR_VOID
|
|
"i1", // IR_BOOL
|
|
"i8", // IR_U8
|
|
"i16", // IR_U16
|
|
"i32", // IR_U32
|
|
"i64", // IR_U63
|
|
"ptr", // IR_ADDR
|
|
"i8", // IR_CHAR
|
|
"i8", // IR_I8
|
|
"i16", // IR_I16
|
|
"i32", // IR_I32
|
|
"i64", // IR_I64
|
|
"double", // IR_DOUBLE
|
|
"float", // IR_FLOAT
|
|
};
|
|
|
|
static void ir_emit_ref(ir_ctx *ctx, FILE *f, ir_ref ref)
|
|
{
|
|
if (IR_IS_CONST_REF(ref)) {
|
|
ir_insn *insn = &ctx->ir_base[ref];
|
|
if (insn->op == IR_FUNC) {
|
|
fprintf(f, "@%s", ir_get_str(ctx, insn->val.i32));
|
|
} else if (insn->op == IR_STR) {
|
|
fprintf(f, "@.str%d", -ref);
|
|
} else if (IR_IS_TYPE_FP(insn->type)) {
|
|
if (insn->type == IR_DOUBLE) {
|
|
if (isnan(insn->val.d)) {
|
|
fprintf(f, "nan");
|
|
} else if (insn->val.d == 0.0) {
|
|
fprintf(f, "0.0");
|
|
} else {
|
|
fprintf(f, "%g", insn->val.d);
|
|
}
|
|
} else {
|
|
IR_ASSERT(insn->type == IR_FLOAT);
|
|
if (isnan(insn->val.f)) {
|
|
fprintf(f, "nan");
|
|
} else if (insn->val.f == 0.0) {
|
|
fprintf(f, "0.0");
|
|
} else {
|
|
fprintf(f, "%e", insn->val.f);
|
|
}
|
|
}
|
|
} else {
|
|
ir_print_const(ctx, &ctx->ir_base[ref], f, true);
|
|
}
|
|
} else {
|
|
fprintf(f, "%%d%d", ref);
|
|
}
|
|
}
|
|
|
|
static void ir_emit_def_ref(ir_ctx *ctx, FILE *f, ir_ref def)
|
|
{
|
|
fprintf(f, "\t%%d%d = ", def);
|
|
}
|
|
|
|
static void ir_emit_phi(ir_ctx *ctx, FILE *f, int def, ir_insn *insn, ir_block *bb)
|
|
{
|
|
bool first = 1;
|
|
uint32_t j, n, *p;
|
|
|
|
ir_emit_def_ref(ctx, f, def);
|
|
fprintf(f, "phi %s ", ir_type_llvm_name[insn->type]);
|
|
n = insn->inputs_count;
|
|
p = ctx->cfg_edges + bb->predecessors;
|
|
for (j = 2; j <= n; j++) {
|
|
if (first) {
|
|
first = 0;
|
|
} else {
|
|
fprintf(f, ", ");
|
|
}
|
|
fprintf(f, "[");
|
|
ir_emit_ref(ctx, f, ir_insn_op(insn, j));
|
|
fprintf(f, ", %%l%d", *p);
|
|
p++;
|
|
fprintf(f, "]");
|
|
}
|
|
fprintf(f, "\n");
|
|
}
|
|
|
|
static void ir_emit_unary_neg(ir_ctx *ctx, FILE *f, int def, ir_insn *insn)
|
|
{
|
|
ir_type type = insn->type;
|
|
|
|
ir_emit_def_ref(ctx, f, def);
|
|
if (IR_IS_TYPE_FP(type)) {
|
|
fprintf(f, "fneg %s ", ir_type_llvm_name[type]);
|
|
} else {
|
|
fprintf(f, "sub %s 0, ", ir_type_llvm_name[type]);
|
|
}
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, "\n");
|
|
}
|
|
|
|
static void ir_emit_unary_not(ir_ctx *ctx, FILE *f, int def, ir_insn *insn)
|
|
{
|
|
ir_type type = ctx->ir_base[insn->op1].type;
|
|
|
|
ir_emit_def_ref(ctx, f, def);
|
|
if (insn->type == IR_BOOL) {
|
|
if (IR_IS_TYPE_FP(type)) {
|
|
fprintf(f, "fcmp oeq %s ", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ", 0.0\n");
|
|
} else {
|
|
fprintf(f, "icmp eq %s ", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ", 0\n");
|
|
}
|
|
} else {
|
|
IR_ASSERT(IR_IS_TYPE_INT(type) && type == insn->type);
|
|
fprintf(f, "xor %s ", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ", -1\n");
|
|
}
|
|
}
|
|
|
|
static void ir_emit_binary_op(ir_ctx *ctx, FILE *f, int def, ir_insn *insn, const char *op, const char *uop, const char *fop)
|
|
{
|
|
ir_type type = ctx->ir_base[insn->op1].type;
|
|
|
|
ir_emit_def_ref(ctx, f, def);
|
|
if (fop && IR_IS_TYPE_FP(type)) {
|
|
fprintf(f, "%s ", fop);
|
|
} else {
|
|
IR_ASSERT(IR_IS_TYPE_INT(type));
|
|
if (uop && IR_IS_TYPE_UNSIGNED(type)) {
|
|
fprintf(f, "%s ", uop);
|
|
} else {
|
|
fprintf(f, "%s ", op);
|
|
}
|
|
}
|
|
fprintf(f, "%s ", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ", ");
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, "\n");
|
|
}
|
|
|
|
static void ir_emit_binary_overflow_op(ir_ctx *ctx, FILE *f, int def, ir_insn *insn, const char *op, const char *uop)
|
|
{
|
|
ir_type type = insn->type;
|
|
|
|
IR_ASSERT(IR_IS_TYPE_INT(type));
|
|
fprintf(f, "\t%%t%d = call {%s, i1} @llvm.%s.with.overflow.%s(%s ", def, ir_type_llvm_name[type],
|
|
IR_IS_TYPE_UNSIGNED(type) ? uop : op,
|
|
ir_type_llvm_name[type], ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ", %s ", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, ")\n");
|
|
|
|
ir_emit_def_ref(ctx, f, def);
|
|
fprintf(f, "extractvalue {%s, i1} %%t%d, 0\n", ir_type_llvm_name[type], def);
|
|
}
|
|
|
|
static void ir_emit_rol_ror(ir_ctx *ctx, FILE *f, int def, ir_insn *insn, const char *op1, const char *op2)
|
|
{
|
|
ir_type type = ctx->ir_base[insn->op1].type;
|
|
|
|
ir_emit_def_ref(ctx, f, def);
|
|
fprintf(f, "call %s @llvm.%s.%s(%s ",
|
|
ir_type_llvm_name[type], insn->op == IR_ROL ? "fshl" : "fshr", ir_type_llvm_name[type], ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ", %s ", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ", %s ", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, ")\n");
|
|
}
|
|
|
|
static void ir_emit_bswap(ir_ctx *ctx, FILE *f, int def, ir_insn *insn)
|
|
{
|
|
ir_type type = insn->type;
|
|
|
|
ir_emit_def_ref(ctx, f, def);
|
|
fprintf(f, "call %s @llvm.bswap.%s(%s ",
|
|
ir_type_llvm_name[type], ir_type_llvm_name[type], ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ")\n");
|
|
}
|
|
|
|
static void ir_emit_conv(ir_ctx *ctx, FILE *f, int def, ir_insn *insn, const char *op)
|
|
{
|
|
ir_emit_def_ref(ctx, f, def);
|
|
fprintf(f, "%s %s ", op, ir_type_llvm_name[ctx->ir_base[insn->op1].type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, " to %s\n", ir_type_llvm_name[insn->type]);
|
|
}
|
|
|
|
static void ir_emit_minmax_op(ir_ctx *ctx, FILE *f, int def, ir_insn *insn)
|
|
{
|
|
ir_type type = insn->type;
|
|
|
|
ir_emit_def_ref(ctx, f, def);
|
|
if (IR_IS_TYPE_FP(type)) {
|
|
fprintf(f, "call %s @llvm.%s.%s(%s ", ir_type_llvm_name[type],
|
|
insn->op == IR_MIN ? "minnum" : "maxnum", type == IR_DOUBLE ? "f64" : "f32",
|
|
ir_type_llvm_name[type]);
|
|
} else {
|
|
IR_ASSERT(IR_IS_TYPE_INT(type));
|
|
if (IR_IS_TYPE_UNSIGNED(type)) {
|
|
fprintf(f, "call %s @llvm.%s.%s(%s ", ir_type_llvm_name[type],
|
|
insn->op == IR_MIN ? "umin" : "umax", ir_type_llvm_name[type], ir_type_llvm_name[type]);
|
|
} else {
|
|
fprintf(f, "call %s @llvm.%s.%s(%s ", ir_type_llvm_name[type],
|
|
insn->op == IR_MIN ? "smin" : "smax", ir_type_llvm_name[type], ir_type_llvm_name[type]);
|
|
}
|
|
}
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ", %s ", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, ")\n");
|
|
}
|
|
|
|
static void ir_emit_conditional_op(ir_ctx *ctx, FILE *f, int def, ir_insn *insn)
|
|
{
|
|
ir_type type = ctx->ir_base[insn->op1].type;
|
|
|
|
ir_emit_def_ref(ctx, f, def);
|
|
if (type == IR_BOOL) {
|
|
fprintf(f, "select i1 ");
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
} else if (IR_IS_TYPE_FP(type)) {
|
|
fprintf(f, "\t%%t%d = fcmp une %s ", def, ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ", 0.0\n");
|
|
fprintf(f, "select i1 %%t%d", def);
|
|
} else {
|
|
fprintf(f, "\t%%t%d = icmp ne %s ", def, ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ", 0\n");
|
|
fprintf(f, "select i1 %%t%d", def);
|
|
}
|
|
|
|
fprintf(f, ", %s ", ir_type_llvm_name[insn->type]);
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, ", %s ", ir_type_llvm_name[insn->type]);
|
|
ir_emit_ref(ctx, f, insn->op3);
|
|
fprintf(f, "\n");
|
|
}
|
|
|
|
static void ir_emit_abs(ir_ctx *ctx, FILE *f, int def, ir_insn *insn)
|
|
{
|
|
ir_type type = ctx->ir_base[insn->op1].type;
|
|
|
|
ir_emit_def_ref(ctx, f, def);
|
|
if (IR_IS_TYPE_FP(type)) {
|
|
fprintf(f, "call %s @llvm.fabs.%s(%s ",
|
|
ir_type_llvm_name[type], type == IR_DOUBLE ? "f64" : "f32", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ")\n");
|
|
} else {
|
|
fprintf(f, "call %s @llvm.abs.%s(%s ",
|
|
ir_type_llvm_name[type], ir_type_llvm_name[type], ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, ", i1 0)\n");
|
|
}
|
|
}
|
|
|
|
static void ir_emit_if(ir_ctx *ctx, FILE *f, uint32_t b, ir_ref def, ir_insn *insn)
|
|
{
|
|
ir_type type = ctx->ir_base[insn->op2].type;
|
|
uint32_t true_block = 0, false_block = 0, next_block;
|
|
|
|
ir_get_true_false_blocks(ctx, b, &true_block, &false_block, &next_block);
|
|
|
|
// TODO: i1 @llvm.expect.i1(i1 <val>, i1 <expected_val>) ???
|
|
|
|
if (type == IR_BOOL) {
|
|
fprintf(f, "\tbr i1 ");
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
} else if (IR_IS_TYPE_FP(type)) {
|
|
fprintf(f, "\t%%t%d = fcmp une %s ", def, ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, ", 0.0\n");
|
|
fprintf(f, "\tbr i1 %%t%d", def);
|
|
} else {
|
|
fprintf(f, "\t%%t%d = icmp ne %s ", def, ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, ", 0\n");
|
|
fprintf(f, "\tbr i1 %%t%d", def);
|
|
}
|
|
fprintf(f, ", label %%l%d, label %%l%d\n", true_block, false_block);
|
|
}
|
|
|
|
static void ir_emit_switch(ir_ctx *ctx, FILE *f, uint32_t b, ir_ref def, ir_insn *insn)
|
|
{
|
|
ir_type type = ctx->ir_base[insn->op2].type;
|
|
ir_block *bb;
|
|
uint32_t n, *p, use_block;
|
|
ir_insn *use_insn;
|
|
|
|
// TODO: i1 @llvm.expect.i1(i1 <val>, i1 <expected_val>) ???
|
|
|
|
fprintf(f, "\tswitch %s ", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
|
|
bb = &ctx->cfg_blocks[b];
|
|
|
|
p = &ctx->cfg_edges[bb->successors];
|
|
for (n = bb->successors_count; n != 0; p++, n--) {
|
|
use_block = *p;
|
|
use_insn = &ctx->ir_base[ctx->cfg_blocks[use_block].start];
|
|
if (use_insn->op == IR_CASE_DEFAULT) {
|
|
fprintf(f, ", label %%l%d", ir_skip_empty_target_blocks(ctx, use_block));
|
|
break;
|
|
} else {
|
|
IR_ASSERT(use_insn->op == IR_CASE_VAL);
|
|
}
|
|
}
|
|
fprintf(f, " [\n");
|
|
p = &ctx->cfg_edges[bb->successors];
|
|
for (n = bb->successors_count; n != 0; p++, n--) {
|
|
use_block = *p;
|
|
use_insn = &ctx->ir_base[ctx->cfg_blocks[use_block].start];
|
|
if (use_insn->op == IR_CASE_VAL) {
|
|
fprintf(f, "\t\t%s ", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, use_insn->op2);
|
|
fprintf(f, ", label %%l%d\n", ir_skip_empty_target_blocks(ctx, use_block));
|
|
} else {
|
|
IR_ASSERT(use_insn->op == IR_CASE_DEFAULT);
|
|
}
|
|
}
|
|
fprintf(f, "\t]\n");
|
|
}
|
|
|
|
static void ir_emit_call(ir_ctx *ctx, FILE *f, ir_ref def, ir_insn *insn)
|
|
{
|
|
int j, k, n;
|
|
|
|
if (insn->type != IR_VOID) {
|
|
ir_emit_def_ref(ctx, f, def);
|
|
} else {
|
|
fprintf(f, "\t");
|
|
}
|
|
if (insn->op == IR_TAILCALL) {
|
|
fprintf(f, "tail ");
|
|
}
|
|
fprintf(f, "call %s ", ir_type_llvm_name[insn->type]);
|
|
|
|
// TODO: function prototype ???
|
|
|
|
if (IR_IS_CONST_REF(insn->op2)) {
|
|
fprintf(f, "@%s", ir_get_str(ctx, ctx->ir_base[insn->op2].val.i32));
|
|
} else {
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
}
|
|
fprintf(f, "(");
|
|
n = insn->inputs_count;
|
|
for (j = 3; j <= n; j++) {
|
|
if (j != 3) {
|
|
fprintf(f, ", ");
|
|
}
|
|
k = ir_insn_op(insn, j);
|
|
fprintf(f, "%s ", ir_type_llvm_name[ctx->ir_base[k].type]);
|
|
ir_emit_ref(ctx, f, k);
|
|
}
|
|
fprintf(f, ")\n");
|
|
if (insn->op == IR_TAILCALL) {
|
|
fprintf(f, "\tret %s", ir_type_llvm_name[insn->type]);
|
|
if (insn->type != IR_VOID) {
|
|
fprintf(f, " ");
|
|
ir_emit_ref(ctx, f, def);
|
|
}
|
|
fprintf(f, "\n");
|
|
}
|
|
}
|
|
|
|
static void ir_emit_ijmp(ir_ctx *ctx, FILE *f, ir_insn *insn)
|
|
{
|
|
fprintf(f, "\tindirectbr ptr ");
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, ", []\n");
|
|
}
|
|
|
|
static void ir_emit_alloca(ir_ctx *ctx, FILE *f, ir_ref def, ir_insn *insn)
|
|
{
|
|
ir_emit_def_ref(ctx, f, def);
|
|
fprintf(f, "alloca i8, %s ", ir_type_llvm_name[ctx->ir_base[insn->op2].type]);
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, ", align 16\n");
|
|
}
|
|
|
|
static void ir_emit_vaddr(ir_ctx *ctx, FILE *f, ir_ref def, ir_insn *insn)
|
|
{
|
|
ir_emit_def_ref(ctx, f, def);
|
|
fprintf(f, "bitcast ptr ");
|
|
ir_emit_ref(ctx, f, insn->op1);
|
|
fprintf(f, " to ptr\n");
|
|
}
|
|
|
|
static void ir_emit_load(ir_ctx *ctx, FILE *f, ir_ref def, ir_insn *insn)
|
|
{
|
|
ir_emit_def_ref(ctx, f, def);
|
|
fprintf(f, "load %s, ptr ", ir_type_llvm_name[insn->type]);
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, "\n");
|
|
}
|
|
|
|
static void ir_emit_store(ir_ctx *ctx, FILE *f, ir_insn *insn)
|
|
{
|
|
ir_type type = ctx->ir_base[insn->op3].type;
|
|
|
|
fprintf(f, "\tstore %s ", ir_type_llvm_name[type]);
|
|
ir_emit_ref(ctx, f, insn->op3);
|
|
fprintf(f, ", ptr ");
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, "\n");
|
|
}
|
|
|
|
static bool may_be_used_by_phi(ir_ctx *ctx, ir_block *bb)
|
|
{
|
|
if (bb->successors_count == 1) {
|
|
bb = &ctx->cfg_blocks[ctx->cfg_edges[bb->successors]];
|
|
if (bb->predecessors_count > 1) {
|
|
ir_use_list *use_list = &ctx->use_lists[bb->start];
|
|
ir_ref i, n, *p;
|
|
|
|
n = use_list->count;
|
|
if (n > 1) {
|
|
for (i = 0, p = &ctx->use_edges[use_list->refs]; i < n; i++, p++) {
|
|
if (ctx->ir_base[*p].op == IR_PHI) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ir_emit_func(ir_ctx *ctx, const char *name, FILE *f)
|
|
{
|
|
ir_ref i, n, *p, use;
|
|
ir_insn *insn;
|
|
ir_use_list *use_list;
|
|
uint8_t ret_type;
|
|
bool has_params = 0;
|
|
uint32_t b, target;
|
|
ir_block *bb;
|
|
|
|
ret_type = ir_get_return_type(ctx);
|
|
|
|
if (!ctx->prev_ref) {
|
|
ir_build_prev_refs(ctx);
|
|
}
|
|
|
|
use_list = &ctx->use_lists[1];
|
|
n = use_list->count;
|
|
for (i = 0, p = &ctx->use_edges[use_list->refs]; i < n; i++, p++) {
|
|
insn = &ctx->ir_base[*p];
|
|
if (insn->op == IR_PARAM) {
|
|
has_params = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Emit function prototype */
|
|
fprintf(f, "define %s", ir_type_llvm_name[ret_type]);
|
|
fprintf(f, " @%s(", name);
|
|
if (has_params) {
|
|
use_list = &ctx->use_lists[1];
|
|
n = use_list->count;
|
|
for (i = 0, p = &ctx->use_edges[use_list->refs]; i < n; i++, p++) {
|
|
use = *p;
|
|
insn = &ctx->ir_base[use];
|
|
if (insn->op == IR_PARAM) {
|
|
if (has_params) {
|
|
has_params = 0;
|
|
} else {
|
|
fprintf(f, ", ");
|
|
}
|
|
fprintf(f, "%s %%d%d", ir_type_llvm_name[insn->type], use);
|
|
}
|
|
}
|
|
}
|
|
fprintf(f, ")\n{\n");
|
|
|
|
for (b = 1, bb = ctx->cfg_blocks + b; b <= ctx->cfg_blocks_count; b++, bb++) {
|
|
IR_ASSERT(!(bb->flags & IR_BB_UNREACHABLE));
|
|
if ((bb->flags & (IR_BB_START|IR_BB_ENTRY|IR_BB_EMPTY)) == IR_BB_EMPTY) {
|
|
continue;
|
|
}
|
|
if (bb->predecessors_count > 0 || may_be_used_by_phi(ctx, bb)) {
|
|
fprintf(f, "l%d:\n", b);
|
|
}
|
|
for (i = bb->start, insn = ctx->ir_base + i; i <= bb->end;) {
|
|
switch (insn->op) {
|
|
case IR_START:
|
|
case IR_BEGIN:
|
|
case IR_IF_TRUE:
|
|
case IR_IF_FALSE:
|
|
case IR_CASE_VAL:
|
|
case IR_CASE_DEFAULT:
|
|
case IR_MERGE:
|
|
case IR_LOOP_BEGIN:
|
|
case IR_UNREACHABLE:
|
|
case IR_PARAM:
|
|
/* skip */
|
|
break;
|
|
case IR_PHI:
|
|
ir_emit_phi(ctx, f, i, insn, bb);
|
|
break;
|
|
case IR_VAR:
|
|
ir_use_list *use_list = &ctx->use_lists[i];
|
|
if (use_list->count > 0) {
|
|
fprintf(f, "\t%%d%d = alloca %s\n", i, ir_type_llvm_name[insn->type]);
|
|
}
|
|
break;
|
|
case IR_EQ:
|
|
ir_emit_binary_op(ctx, f, i, insn, "icmp eq", NULL, "fcmp oeq");
|
|
break;
|
|
case IR_NE:
|
|
ir_emit_binary_op(ctx, f, i, insn, "icmp ne", NULL, "fcmp une");
|
|
break;
|
|
case IR_LT:
|
|
ir_emit_binary_op(ctx, f, i, insn, "icmp slt", "icmp ult", "fcmp olt");
|
|
break;
|
|
case IR_GE:
|
|
ir_emit_binary_op(ctx, f, i, insn, "icmp sge", "icmp uge", "fcmp oge");
|
|
break;
|
|
case IR_LE:
|
|
ir_emit_binary_op(ctx, f, i, insn, "icmp sle", "icmp ule", "fcmp ole");
|
|
break;
|
|
case IR_GT:
|
|
ir_emit_binary_op(ctx, f, i, insn, "icmp sgt", "icmp ugt", "fcmp ogt");
|
|
break;
|
|
case IR_ULT:
|
|
ir_emit_binary_op(ctx, f, i, insn, "icmp ult", NULL, "fcmp ult");
|
|
break;
|
|
case IR_UGE:
|
|
ir_emit_binary_op(ctx, f, i, insn, "icmp uge", NULL, "fcmp uge");
|
|
break;
|
|
case IR_ULE:
|
|
ir_emit_binary_op(ctx, f, i, insn, "icmp ule", NULL, "fcmp ule");
|
|
break;
|
|
case IR_UGT:
|
|
ir_emit_binary_op(ctx, f, i, insn, "icmp ugt", NULL, "fcmp ugt");
|
|
break;
|
|
case IR_ADD:
|
|
ir_emit_binary_op(ctx, f, i, insn, "add", NULL, "fadd");
|
|
break;
|
|
case IR_SUB:
|
|
ir_emit_binary_op(ctx, f, i, insn, "sub", NULL, "fsub");
|
|
break;
|
|
case IR_MUL:
|
|
ir_emit_binary_op(ctx, f, i, insn, "mul", NULL, "fmul");
|
|
break;
|
|
case IR_DIV:
|
|
ir_emit_binary_op(ctx, f, i, insn, "sdiv", "udiv", "fdiv");
|
|
break;
|
|
case IR_MOD:
|
|
ir_emit_binary_op(ctx, f, i, insn, "srem", "urem", NULL);
|
|
break;
|
|
case IR_NEG:
|
|
ir_emit_unary_neg(ctx, f, i, insn);
|
|
break;
|
|
case IR_NOT:
|
|
ir_emit_unary_not(ctx, f, i, insn);
|
|
break;
|
|
case IR_OR:
|
|
ir_emit_binary_op(ctx, f, i, insn, "or", NULL, NULL);
|
|
break;
|
|
case IR_AND:
|
|
ir_emit_binary_op(ctx, f, i, insn, "and", NULL, NULL);
|
|
break;
|
|
case IR_XOR:
|
|
ir_emit_binary_op(ctx, f, i, insn, "xor", NULL, NULL);
|
|
break;
|
|
case IR_MIN:
|
|
case IR_MAX:
|
|
ir_emit_minmax_op(ctx, f, i, insn);
|
|
break;
|
|
case IR_COND:
|
|
ir_emit_conditional_op(ctx, f, i, insn);
|
|
break;
|
|
case IR_ABS:
|
|
ir_emit_abs(ctx, f, i, insn);
|
|
break;
|
|
case IR_SHL:
|
|
ir_emit_binary_op(ctx, f, i, insn, "shl", NULL, NULL);
|
|
break;
|
|
case IR_SHR:
|
|
ir_emit_binary_op(ctx, f, i, insn, "lshr", NULL, NULL);
|
|
break;
|
|
case IR_SAR:
|
|
ir_emit_binary_op(ctx, f, i, insn, "ashr", NULL, NULL);
|
|
break;
|
|
case IR_ROL:
|
|
ir_emit_rol_ror(ctx, f, i, insn, "<<", ">>");
|
|
break;
|
|
case IR_ROR:
|
|
ir_emit_rol_ror(ctx, f, i, insn, ">>", "<<");
|
|
break;
|
|
case IR_BSWAP:
|
|
ir_emit_bswap(ctx, f, i, insn);
|
|
break;
|
|
case IR_SEXT:
|
|
IR_ASSERT(IR_IS_TYPE_INT(insn->type));
|
|
IR_ASSERT(IR_IS_TYPE_INT(ctx->ir_base[insn->op1].type));
|
|
IR_ASSERT(ir_type_size[insn->type] > ir_type_size[ctx->ir_base[insn->op1].type]);
|
|
ir_emit_conv(ctx, f, i, insn, "sext");
|
|
break;
|
|
case IR_ZEXT:
|
|
IR_ASSERT(IR_IS_TYPE_INT(insn->type));
|
|
IR_ASSERT(IR_IS_TYPE_INT(ctx->ir_base[insn->op1].type));
|
|
IR_ASSERT(ir_type_size[insn->type] > ir_type_size[ctx->ir_base[insn->op1].type]);
|
|
ir_emit_conv(ctx, f, i, insn, "zext");
|
|
break;
|
|
case IR_TRUNC:
|
|
IR_ASSERT(IR_IS_TYPE_INT(insn->type));
|
|
IR_ASSERT(IR_IS_TYPE_INT(ctx->ir_base[insn->op1].type));
|
|
IR_ASSERT(ir_type_size[insn->type] < ir_type_size[ctx->ir_base[insn->op1].type]);
|
|
ir_emit_conv(ctx, f, i, insn, "trunc");
|
|
break;
|
|
case IR_BITCAST:
|
|
if (insn->type == IR_ADDR
|
|
&& IR_IS_TYPE_INT(ctx->ir_base[insn->op1].type)
|
|
&& ctx->ir_base[insn->op1].type != IR_ADDR) {
|
|
ir_emit_conv(ctx, f, i, insn, "inttoptr");
|
|
} else if (ctx->ir_base[insn->op1].type == IR_ADDR
|
|
&& IR_IS_TYPE_INT(insn->type)
|
|
&& insn->type != IR_ADDR) {
|
|
ir_emit_conv(ctx, f, i, insn, "ptrtoint");
|
|
} else {
|
|
ir_emit_conv(ctx, f, i, insn, "bitcast");
|
|
}
|
|
break;
|
|
case IR_INT2FP:
|
|
IR_ASSERT(IR_IS_TYPE_FP(insn->type));
|
|
IR_ASSERT(IR_IS_TYPE_INT(ctx->ir_base[insn->op1].type));
|
|
ir_emit_conv(ctx, f, i, insn, IR_IS_TYPE_UNSIGNED(ctx->ir_base[insn->op1].type) ? "uitofp" : "sitofp");
|
|
break;
|
|
case IR_FP2INT:
|
|
IR_ASSERT(IR_IS_TYPE_INT(insn->type));
|
|
IR_ASSERT(IR_IS_TYPE_FP(ctx->ir_base[insn->op1].type));
|
|
ir_emit_conv(ctx, f, i, insn, IR_IS_TYPE_UNSIGNED(insn->type) ? "fptoui" : "fptosi");
|
|
break;
|
|
case IR_FP2FP:
|
|
IR_ASSERT(IR_IS_TYPE_FP(insn->type));
|
|
IR_ASSERT(IR_IS_TYPE_FP(ctx->ir_base[insn->op1].type));
|
|
ir_emit_conv(ctx, f, i, insn, insn->type == IR_DOUBLE ? "fpext" : "fptrunc");
|
|
break;
|
|
case IR_COPY:
|
|
ir_emit_conv(ctx, f, i, insn, "bitcast");
|
|
break;
|
|
case IR_ADD_OV:
|
|
ir_emit_binary_overflow_op(ctx, f, i, insn, "sadd", "uadd");
|
|
break;
|
|
case IR_SUB_OV:
|
|
ir_emit_binary_overflow_op(ctx, f, i, insn, "ssub", "usub");
|
|
break;
|
|
case IR_MUL_OV:
|
|
ir_emit_binary_overflow_op(ctx, f, i, insn, "smul", "umul");
|
|
break;
|
|
case IR_OVERFLOW:
|
|
ir_emit_def_ref(ctx, f, i);
|
|
fprintf(f, "extractvalue {%s, i1} %%t%d, 1\n", ir_type_llvm_name[ctx->ir_base[insn->op1].type], insn->op1);
|
|
break;
|
|
case IR_RETURN:
|
|
IR_ASSERT(bb->successors_count == 0);
|
|
if (!insn->op2) {
|
|
fprintf(f, "\tret void\n");
|
|
} else {
|
|
fprintf(f, "\tret %s ", ir_type_llvm_name[ctx->ir_base[insn->op2].type]);
|
|
ir_emit_ref(ctx, f, insn->op2);
|
|
fprintf(f, "\n");
|
|
}
|
|
break;
|
|
case IR_END:
|
|
case IR_LOOP_END:
|
|
IR_ASSERT(bb->successors_count == 1);
|
|
target = ir_skip_empty_target_blocks(ctx, ctx->cfg_edges[bb->successors]);
|
|
fprintf(f, "\tbr label %%l%d\n", target);
|
|
break;
|
|
case IR_IF:
|
|
ir_emit_if(ctx, f, b, i, insn);
|
|
break;
|
|
case IR_SWITCH:
|
|
ir_emit_switch(ctx, f, b, i, insn);
|
|
break;
|
|
case IR_CALL:
|
|
case IR_TAILCALL:
|
|
ir_emit_call(ctx, f, i, insn);
|
|
break;
|
|
case IR_IJMP:
|
|
ir_emit_ijmp(ctx, f, insn);
|
|
break;
|
|
case IR_ALLOCA:
|
|
ir_emit_alloca(ctx, f, i, insn);
|
|
break;
|
|
case IR_VADDR:
|
|
ir_emit_vaddr(ctx, f, i, insn);
|
|
break;
|
|
case IR_LOAD:
|
|
case IR_VLOAD:
|
|
ir_emit_load(ctx, f, i, insn);
|
|
break;
|
|
case IR_STORE:
|
|
case IR_VSTORE:
|
|
ir_emit_store(ctx, f, insn);
|
|
break;
|
|
case IR_TRAP:
|
|
fprintf(f, "\tcall void @llvm.debugtrap()\n");
|
|
break;
|
|
case IR_RLOAD:
|
|
case IR_RSTORE:
|
|
case IR_TLS:
|
|
case IR_GUARD:
|
|
case IR_GUARD_NOT:
|
|
default:
|
|
IR_ASSERT(0 && "NIY instruction");
|
|
ctx->status = IR_ERROR_UNSUPPORTED_CODE_RULE;
|
|
return 0;
|
|
}
|
|
n = ir_insn_len(insn);
|
|
i += n;
|
|
insn += n;
|
|
}
|
|
}
|
|
|
|
fprintf(f, "}\n");
|
|
|
|
for (i = IR_UNUSED + 1, insn = ctx->ir_base - i; i < ctx->consts_count; i++, insn--) {
|
|
if (insn->op == IR_FUNC) {
|
|
// TODO: function prototype ???
|
|
fprintf(f, "declare void @%s()\n", ir_get_str(ctx, insn->val.i32));
|
|
} else if (insn->op == IR_STR) {
|
|
const char *str = ir_get_str(ctx, insn->val.i32);
|
|
// TODO: strlen != size ???
|
|
int len = strlen(str);
|
|
int j;
|
|
|
|
fprintf(f, "@.str%d = private unnamed_addr constant [%d x i8] c\"", i, len + 1);
|
|
for (j = 0; j < len; j++) {
|
|
char c = str[j];
|
|
|
|
if (c == '\\') {
|
|
if (str[j+1] == '\\') {
|
|
j++;
|
|
c = '\\';
|
|
} else if (str[j+1] == '\'') {
|
|
j++;
|
|
c = '\'';
|
|
} else if (str[j+1] == '"') {
|
|
j++;
|
|
c = '"';
|
|
} else if (str[j+1] == 'a') {
|
|
j++;
|
|
c = '\a';
|
|
} else if (str[j+1] == 'b') {
|
|
j++;
|
|
c = '\b';
|
|
} else if (str[j+1] == 'e') {
|
|
j++;
|
|
c = 27; /* '\e'; */
|
|
} else if (str[j+1] == 'f') {
|
|
j++;
|
|
c = '\f';
|
|
} else if (str[j+1] == 'n') {
|
|
j++;
|
|
c = '\n';
|
|
} else if (str[j+1] == 'r') {
|
|
j++;
|
|
c = '\r';
|
|
} else if (str[j+1] == 't') {
|
|
j++;
|
|
c = '\t';
|
|
} else if (str[j+1] == 'v') {
|
|
j++;
|
|
c = '\v';
|
|
} else if (str[j+1] == '?') {
|
|
j++;
|
|
c = 0x3f;
|
|
}
|
|
}
|
|
if (c < ' ' || c >= 127) {
|
|
char c1 = c >> 8;
|
|
char c2 = c & 15;
|
|
c1 = (c1 < 10) ? (c1 + '0') : (c1 - 10 + 'A');
|
|
c2 = (c2 < 10) ? (c2 + '0') : (c2 - 10 + 'A');
|
|
fprintf(f, "\\%c%c", c1, c2);
|
|
} else {
|
|
fprintf(f, "%c", c);
|
|
}
|
|
}
|
|
fprintf(f, "\\00\"\n");
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int ir_emit_llvm(ir_ctx *ctx, const char *name, FILE *f)
|
|
{
|
|
return ir_emit_func(ctx, name, f);
|
|
}
|