Skip to content

Commit

Permalink
Implement OP_DUP and OP_SWAP (ruby#122)
Browse files Browse the repository at this point in the history
* Make it possible to implement a gen_ function that writes to a codeblock, and implement OP_DUP as an example. Import CRuby opcodes and VM instruction size by hand.

* Remove commented-out C impl of gen_dup

* Huh. Mov isn't implemented. I thought it was.

* Remove pub from mov() since I'm no longer using it

* Add implementation of gen_swap, stack_swap and test_gen_swap

* Use Rust-ier return syntax
  • Loading branch information
noahgibbs committed Jan 30, 2022
1 parent 70fee18 commit b9524e8
Show file tree
Hide file tree
Showing 4 changed files with 299 additions and 68 deletions.
4 changes: 4 additions & 0 deletions yjit/src/asm/x86_64/mod.rs
Expand Up @@ -413,6 +413,10 @@ impl CodeBlock
};
}

pub fn get_write_pos(self) -> usize {
return self.write_pos;
}

/*
// Initialize a code block object
void cb_init(codeblock_t *cb, uint8_t *mem_block, uint32_t mem_size)
Expand Down
129 changes: 80 additions & 49 deletions yjit/src/codegen.rs
@@ -1,6 +1,7 @@
use crate::cruby::*;
use crate::asm::x86_64::*;
use crate::core::*;
use InsnOpnd::*;
use CodegenStatus::*;

// Callee-saved registers
Expand Down Expand Up @@ -53,14 +54,14 @@ pub struct JITState

impl JITState {
pub fn new() -> Self {
return JITState {
JITState {
block: Block::new(BLOCKID_NULL),
iseq: IseqPtr(0),
insn_idx: 0,
opcode: 0,
side_exit_for_pc: CodePtr::null(),
record_boundary_patch_point: false
};
}
}
}

Expand All @@ -71,7 +72,7 @@ enum CodegenStatus {
}

// Code generation function signature
type CodeGenFn = fn(jit: &JITState, ctx: &mut Context, cb: &CodeBlock) -> CodegenStatus;
type CodeGenFn = fn(jit: &JITState, ctx: &mut Context, cb: &mut CodeBlock) -> CodegenStatus;



Expand Down Expand Up @@ -816,56 +817,112 @@ gen_single_block(blockid_t blockid, const ctx_t *start_ctx, rb_execution_context



fn gen_nop(jit: &JITState, ctx: &mut Context, cb: &CodeBlock) -> CodegenStatus
fn gen_nop(jit: &JITState, ctx: &mut Context, cb: &mut CodeBlock) -> CodegenStatus
{
// Do nothing
return KeepCompiling;
KeepCompiling
}

fn gen_pop(jit: &JITState, ctx: &mut Context, cb: &CodeBlock) -> CodegenStatus
fn gen_pop(jit: &JITState, ctx: &mut Context, cb: &mut CodeBlock) -> CodegenStatus
{
// Decrement SP
ctx.stack_pop(1);
return KeepCompiling;
KeepCompiling
}

fn gen_dup(jit: &JITState, ctx: &mut Context, cb: &mut CodeBlock) -> CodegenStatus
{
let dup_val = ctx.stack_pop(1);
let (mapping, tmp_type) = ctx.get_opnd_mapping(StackOpnd(0));

let loc0 = ctx.stack_push_mapping((mapping, tmp_type));
//mov(cb, REG0, dup_val); // Huh. Mov() isn't implemented. Why did I think it was?
//mov(cb, loc0, REG0);

KeepCompiling
}

// Swap top 2 stack entries
fn gen_swap(jit: &JITState, ctx: &mut Context, cb: &mut CodeBlock) -> CodegenStatus
{
stack_swap(ctx, cb, 0, 1, REG0, REG1);
KeepCompiling
}

fn stack_swap(ctx: &mut Context, cb: &mut CodeBlock, offset0: u16, offset1: u16, reg0: X86Opnd, reg1: X86Opnd) -> ()
{
let opnd0 = ctx.stack_opnd(offset0 as i32);
let opnd1 = ctx.stack_opnd(offset1 as i32);

let mapping0 = ctx.get_opnd_mapping(InsnOpnd::StackOpnd(offset0));
let mapping1 = ctx.get_opnd_mapping(InsnOpnd::StackOpnd(offset1));

//mov(cb, REG0, opnd0);
//mov(cb, REG1, opnd1);
//mov(cb, opnd0, REG1);
//mov(cb, opnd1, REG0);

ctx.set_opnd_mapping(InsnOpnd::StackOpnd(offset0), mapping1);
ctx.set_opnd_mapping(InsnOpnd::StackOpnd(offset1), mapping0);
}

#[cfg(test)]
mod tests {
use super::*;

//use crate::codegen::*;
use crate::asm::x86_64::*;

#[test]
fn test_gen_nop() {
let status = gen_nop(&JITState::new(), &mut Context::new(), &CodeBlock::new());
let mut context = Context::new();
let mut cb = CodeBlock::new();
let status = gen_nop(&JITState::new(), &mut context, &mut cb);

assert!(matches!(KeepCompiling, status));
assert_eq!(context.diff(&Context::new()), 0);
assert_eq!(cb.get_write_pos(), 0);
}

#[test]
fn test_gen_pop() {
let mut context = Context::new_with_stack_size(1);
let status = gen_pop(&JITState::new(), &mut context, &CodeBlock::new());
let status = gen_pop(&JITState::new(), &mut context, &mut CodeBlock::new());

assert!(matches!(KeepCompiling, status));
assert_eq!(context.diff(&Context::new()), 0);
}
}

/*
static codegen_status_t
gen_dup(jitstate_t *jit, ctx_t *ctx, codeblock_t *cb)
{
// Get the top value and its type
x86opnd_t dup_val = ctx_stack_pop(ctx, 0);
temp_type_mapping_t mapping = ctx_get_opnd_mapping(ctx, OPND_STACK(0));
#[test]
fn test_gen_dup() {
let mut context = Context::new();
context.stack_push(Type::Fixnum);
context.stack_push(Type::Fixnum); // I feel like this shouldn't be needed - asking on Slack.
let mut cb = CodeBlock::new();
let status = gen_dup(&JITState::new(), &mut context, &mut cb);

// Push the same value on top
x86opnd_t loc0 = ctx_stack_push_mapping(ctx, mapping);
mov(cb, REG0, dup_val);
mov(cb, loc0, REG0);
assert!(matches!(KeepCompiling, status));
//assert_eq!(cb.get_write_pos(), 2); // Can check the write_pos once the mov statements can be uncommented
}

return YJIT_KEEP_COMPILING;
#[test]
fn test_gen_swap() {
let mut context = Context::new();
context.stack_push(Type::Fixnum);
context.stack_push(Type::Flonum);
let mut cb = CodeBlock::new();
let status = gen_swap(&JITState::new(), &mut context, &mut cb);

let (_, tmp_type_top) = context.get_opnd_mapping(StackOpnd(0));
let (_, tmp_type_next) = context.get_opnd_mapping(StackOpnd(1));

assert!(matches!(KeepCompiling, status));
assert_eq!(tmp_type_top, Type::Fixnum);
assert_eq!(tmp_type_next, Type::Flonum);
}
}

/*
// duplicate stack top n elements
static codegen_status_t
gen_dupn(jitstate_t *jit, ctx_t *ctx, codeblock_t *cb)
Expand Down Expand Up @@ -893,32 +950,6 @@ gen_dupn(jitstate_t *jit, ctx_t *ctx, codeblock_t *cb)
return YJIT_KEEP_COMPILING;
}
static void
stack_swap(ctx_t *ctx, codeblock_t *cb, int offset0, int offset1, x86opnd_t reg0, x86opnd_t reg1)
{
x86opnd_t opnd0 = ctx_stack_opnd(ctx, offset0);
x86opnd_t opnd1 = ctx_stack_opnd(ctx, offset1);
temp_type_mapping_t mapping0 = ctx_get_opnd_mapping(ctx, OPND_STACK(offset0));
temp_type_mapping_t mapping1 = ctx_get_opnd_mapping(ctx, OPND_STACK(offset1));
mov(cb, reg0, opnd0);
mov(cb, reg1, opnd1);
mov(cb, opnd0, reg1);
mov(cb, opnd1, reg0);
ctx_set_opnd_mapping(ctx, OPND_STACK(offset0), mapping1);
ctx_set_opnd_mapping(ctx, OPND_STACK(offset1), mapping0);
}
// Swap top 2 stack entries
static codegen_status_t
gen_swap(jitstate_t *jit, ctx_t *ctx, codeblock_t *cb)
{
stack_swap(ctx , cb, 0, 1, REG0, REG1);
return YJIT_KEEP_COMPILING;
}
// set Nth stack entry to stack top
static codegen_status_t
gen_setn(jitstate_t *jit, ctx_t *ctx, codeblock_t *cb)
Expand Down Expand Up @@ -4981,9 +5012,9 @@ fn get_gen_fn(opcode: VALUE) -> Option<CodeGenFn>
match opcode {
OP_NOP => Some(gen_nop),
OP_POP => Some(gen_pop),
OP_DUP => Some(gen_dup),

/*
yjit_reg_op(BIN(dup), gen_dup);
yjit_reg_op(BIN(dupn), gen_dupn);
yjit_reg_op(BIN(swap), gen_swap);
yjit_reg_op(BIN(setn), gen_setn);
Expand Down
26 changes: 13 additions & 13 deletions yjit/src/core.rs
Expand Up @@ -477,7 +477,7 @@ impl Context {
}

/// Get an operand for the adjusted stack pointer address
fn sp_opnd(&self, offset_bytes: usize) -> X86Opnd
pub fn sp_opnd(&self, offset_bytes: usize) -> X86Opnd
{
let offset = ((self.sp_offset as usize) * SIZEOF_VALUE) + offset_bytes;
let offset = offset as i32;
Expand All @@ -486,7 +486,7 @@ impl Context {

/// Push one new value on the temp stack with an explicit mapping
/// Return a pointer to the new stack top
fn stack_push_mapping(&mut self, (mapping, temp_type): (TempMapping, Type)) -> X86Opnd
pub fn stack_push_mapping(&mut self, (mapping, temp_type): (TempMapping, Type)) -> X86Opnd
{
// If type propagation is disabled, store no types
if get_option!(no_type_prop) {
Expand Down Expand Up @@ -515,19 +515,19 @@ impl Context {

/// Push one new value on the temp stack
/// Return a pointer to the new stack top
fn stack_push(&mut self, val_type: Type) -> X86Opnd
pub fn stack_push(&mut self, val_type: Type) -> X86Opnd
{
return self.stack_push_mapping((MapToStack, val_type));
}

/// Push the self value on the stack
fn stack_push_self(&mut self) -> X86Opnd
pub fn stack_push_self(&mut self) -> X86Opnd
{
return self.stack_push_mapping((MapToSelf, Type::Unknown));
}

/// Push a local variable on the stack
fn stack_push_local(&mut self, local_idx: usize) -> X86Opnd
pub fn stack_push_local(&mut self, local_idx: usize) -> X86Opnd
{
if local_idx >= MAX_LOCAL_TYPES {
return self.stack_push(Type::Unknown);
Expand Down Expand Up @@ -565,7 +565,7 @@ impl Context {
}

/// Get an operand pointing to a slot on the temp stack
fn stack_opnd(&self, idx: i32) -> X86Opnd
pub fn stack_opnd(&self, idx: i32) -> X86Opnd
{
// SP points just above the topmost value
let offset = ((self.sp_offset as i32) - 1 - idx) * (SIZEOF_VALUE as i32);
Expand All @@ -574,7 +574,7 @@ impl Context {
}

/// Get the type of an instruction operand
fn get_opnd_type(&self, opnd: InsnOpnd) -> Type
pub fn get_opnd_type(&self, opnd: InsnOpnd) -> Type
{
match opnd {
SelfOpnd => {
Expand Down Expand Up @@ -612,7 +612,7 @@ impl Context {
/// This value must be compatible and at least as specific as the previously known type.
/// If this value originated from self, or an lvar, the learned type will be
/// propagated back to its source.
fn upgrade_opnd_type(&mut self, opnd: InsnOpnd, opnd_type: Type)
pub fn upgrade_opnd_type(&mut self, opnd: InsnOpnd, opnd_type: Type)
{
// If type propagation is disabled, store no types
if get_option!(no_type_prop) {
Expand Down Expand Up @@ -657,7 +657,7 @@ impl Context {
This is can be used with stack_push_mapping or set_opnd_mapping to copy
a stack value's type while maintaining the mapping.
*/
fn get_opnd_mapping(&self, opnd: InsnOpnd) -> (TempMapping, Type)
pub fn get_opnd_mapping(&self, opnd: InsnOpnd) -> (TempMapping, Type)
{
let opnd_type = self.get_opnd_type(opnd);

Expand All @@ -684,7 +684,7 @@ impl Context {
}

/// Overwrite both the type and mapping of a stack operand.
fn set_opnd_mapping(&mut self, opnd: InsnOpnd, (mapping, opnd_type): (TempMapping, Type))
pub fn set_opnd_mapping(&mut self, opnd: InsnOpnd, (mapping, opnd_type): (TempMapping, Type))
{
match opnd {
SelfOpnd => unreachable!("self always maps to self"),
Expand All @@ -711,7 +711,7 @@ impl Context {
}

/// Set the type of a local variable
fn set_local_type(&mut self, local_idx: usize, local_type: Type) {
pub fn set_local_type(&mut self, local_idx: usize, local_type: Type) {
let ctx = self;

// If type propagation is disabled, store no types
Expand Down Expand Up @@ -744,7 +744,7 @@ impl Context {

/// Erase local variable type information
/// eg: because of a call we can't track
fn clear_local_types(ctx: &mut Self) {
pub fn clear_local_types(ctx: &mut Self) {
// When clearing local types we must detach any stack mappings to those
// locals. Even if local values may have changed, stack values will not.
for (i, mapping) in ctx.temp_mapping.iter_mut().enumerate() {
Expand Down Expand Up @@ -846,7 +846,7 @@ impl Context {
}

/// Keep track of a block version. Block should be fully constructed.
fn add_block_version(block: &mut Block)
pub fn add_block_version(block: &mut Block)
{
todo!();

Expand Down

0 comments on commit b9524e8

Please sign in to comment.