Skip to content

RISC-V code generator (binary-encoding, hooked into ir_match/ir_ra/ir_emit_code)#174

Open
steven-studio wants to merge 15 commits into
dstogov:masterfrom
steven-studio:riscv-binary-backend
Open

RISC-V code generator (binary-encoding, hooked into ir_match/ir_ra/ir_emit_code)#174
steven-studio wants to merge 15 commits into
dstogov:masterfrom
steven-studio:riscv-binary-backend

Conversation

@steven-studio

Copy link
Copy Markdown

Description

Follow-up to #169 — implementing the direction we landed on there: hand-written
binary encoding hooked into ir_match()/ir_ra.c/ir_emit_code(), skipping DynAsm
entirely.

What's here

  • ir_riscv.h — register definitions (x0-x31, f0-f31), ABI aliases, regset
    macros, mirroring ir_x86.h's structure
  • ir_riscv_enc.h — R/I/S/B/U/J-type instruction encoders per the RISC-V base
    ISA spec
  • ir_emit_riscv.cir_match(), ir_get_target_constraints(),
    ir_emit_code(), calling convention (lp64d: a0-a7 int args, a0/a1 int
    return, s0-s11 callee-saved), plus the debug/dump helpers (ir_reg_name,
    ir_dump_reg, ir_rule_name)
  • Target-detection fixes in ir.h, ir_ra.c, ir_dump.c, ir_gdb.c — these
    files were silently falling through to x86_64 handling when
    IR_TARGET_RISCV64 was defined, which took a while to track down (register
    allocator was picking x0 as a scratch register before this was fixed)
  • ir_disasm.c-S shells out to riscv64-linux-gnu-objdump for RISC-V;
    this system's libcapstone (4.0.2) has no CS_ARCH_RISCV, so the existing
    capstone path was segfaulting on an uninitialized handle. Open to
    reverting this once capstone has real RISC-V support — wasn't sure if this
    belongs upstream or as a local workaround, your call.

Scope

IR_START, IR_PARAM, IR_ADD, IR_RETURN — enough for a leaf function
with two int params and one addition. Everything in the pipeline is real
(register allocation, constraints, encoding) — nothing stubbed in this path.
The handful of functions not exercised yet (thunks, ir_call_proto) are
stubbed and marked TODO, since they're only needed once CALL/LLVM-emit
paths are in scope.

Verified

{
	l_1 = START(l_5);
	int32_t d_2 = PARAM(l_1, "a", 1);
	int32_t d_3 = PARAM(l_1, "b", 2);
	int32_t d_4 = ADD(d_2, d_3);
	l_5 = RETURN(l_1, d_4);
}

./ir add.ir -O2 -S:

test:
   0:	00a582b3          	add	t0,a1,a0
   4:	00028513          	mv	a0,t0
   8:	00008067          	ret

Cross-checked independently against riscv64-linux-gnu-objdump — matches.

Next

Planning SUB/MUL next (same shape as ADD, different funct7/funct3), then
comparisons and branches (B-type encoder is already in ir_riscv_enc.h,
just not wired to any rule yet). Let me know if you'd rather look at the
current shape before I keep going.

…_emit_code for START/PARAM/ADD/RETURN

Hand-written encoder (no DynAsm), verified end-to-end against
riscv64-linux-gnu-objdump. Also fixes target-detection fallthrough
bugs in ir.h/ir_ra.c/ir_dump.c/ir_gdb.c that were silently treating
IR_TARGET_RISCV64 as x86_64, plus an ir_disasm.c objdump fallback
since this system's libcapstone lacks CS_ARCH_RISCV.

@dstogov dstogov left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, remove binary libir.a.

This looks like a good start. Go forward!
I made only a quick look over the sources.

Comment thread ir_emit_riscv.c
Comment on lines +216 to +220
int32_t rd = riscv_reg_of(ctx, ref, 0);
int32_t rs1 = riscv_reg_of(ctx, ref, 1);
int32_t rs2 = riscv_reg_of(ctx, ref, 2);
emit32(ctx, rv_add((uint32_t)rd, (uint32_t)rs1, (uint32_t)rs2));
break;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Live ranges might be spilled, and then register might need to be loaded and stored.

	ir_reg def_reg = IR_REG_NUM(ctx->regs[def][0]);
	ir_ref op1 = insn->op1;
    
	if (op1_reg != IR_REG_NONE) {
		if (IR_REG_SPILLED(op1_reg)) {
			op1_reg = IR_REG_NUM(op1_reg);
			ir_emit_load(ctx, insn->type, insn->op1_reg, op1);
		}
 	}
    ....
   	if (IR_REG_SPILLED(ctx->regs[def][0])) {
		ir_emit_store(ctx, type, def, def_reg);
	}

Also operands might be constants (IR_IS_CONST_REF(insn->op`)).
I guess, RISC should load them into registers first. This may be done allocating temporary register in ir_get_target_constraints().

		case IR_RISCV_RULE_ADD
			flags = IR_USE_MUST_BE_IN_REG | IR_OP1_MUST_BE_IN_REG | IR_OP2_MUST_BE_IN_REG;
			insn = &ctx->ir_base[ref];
			if (IR_IS_CONST_REF(insn->op1)) {
				constraints->tmp_regs[n] = IR_TMP_REG(1, insn->type, IR_LOAD_SUB_REF, IR_DEF_SUB_REF);
				n = 1;
			}
			if (IR_IS_CONST_REF(insn->op2)) {
				constraints->tmp_regs[n] = IR_TMP_REG(2, insn->type, IR_LOAD_SUB_REF, IR_DEF_SUB_REF);
				n = 1;
			}
			break;

This should lead to generation of necessary spill-load(s).

Comment thread tests/riscv/run_riscv_tests.sh Outdated
Comment on lines +184 to +185
echo ""
echo "Results: $PASS passed, $FAIL failed"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this file? Why not to use make test?

@steven-studio

Copy link
Copy Markdown
Author

Please, remove binary libir.a.

This looks like a good start. Go forward! I made only a quick look over the sources.

Removed libir.a.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants