bytecodealliance / bytecodealliance/wasmtime

Cranelift: unknown memory problem for F64X2 type expression eval but F64 works

Open
#7,976 6 comments 0 reactions 0 assignees View on GitHub
bug cranelift
Dominant language
Rust
Stars
18.6k
Forks
1.8k
Avg merge
1d 18h
Merged PRs (30d)
126

Description

### Summary
I am try to compile and execute a function which execute expression `(a + b) / c < d` on each element in the arrays. firstly, I generate the IR of this function with F64 type, that works fine. then I convert the function to F64X2 variant to see if there are any performance improvements, but unexpected memory problem occurs. My first intuition is that the problem lies in the function call section, but nothing i can found. so the biggest problem is that I don't know how to troubleshoot this type of issue.

### failure `.clif` Test Case for F64X2
```
pushq %rbp
unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
movq %rsp, %rbp
unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
block0:
movq %rdx, %rax
vpshufd $68, %xmm0, %xmm3
vpshufd $68, %xmm1, %xmm2
jmp label1
block1:
vmovupd 0(%rdi), %xmm4
vaddpd %xmm4, 0(%rsi), %xmm4
vdivpd %xmm4, %xmm3, %xmm4
vcmppd $1, %xmm4, %xmm2, %xmm4
vmovdqu %xmm4, 0(%rdx)
lea 1(%rcx), %rcx
lea 16(%rdi), %rdi
lea 16(%rsi), %rsi
lea 2(%rdx), %rdx
cmpq %r8, %rcx
jl label2; j label3
block2:
jmp label1
block3:
movq %rbp, %rsp
popq %rbp
ret
```
### Steps to Reproduce

1. the test cases.

``` rust
#[test]
fn test_jit_expr_on_array_v3_64() {
let BATCH_SIZE = 64;
let a = create_primitive_array::(BATCH_SIZE, 0.);
let b = create_primitive_array::(BATCH_SIZE, 0.);
let c = 3.0_f64;
let d = 3.0_f64;
// jit compiled function.
let op = jit_expr_v3();
for _ in 0..100000 {
let res = jit_expr_on_array_v3(&a, &b, c, d, op).unwrap();
let (values, _) = res.into_parts();
}
}
```

The problem lies in the third iteration,blew is the debug screenshot
![image](https://github.com/bytecodealliance/wasmtime/assets/13784260/3a2d97c6-b616-4902-8fca-7b6730d9e26d)

2. jit compiled function.

``` rust
pub fn jit_expr_v3() -> fn(*const u8, *const u8, *const bool, f64, f64, i64, i64) {
let mut ctx = CodegenContext::builder().debug().finish();
let data_type = types::F64X2;
let result_type = types::I8X2;

let mut func_ctx = ctx.create_func_gen_ctx(
"op_v3",
vec![
AbiParam::new(ctx.ptype()),
AbiParam::new(ctx.ptype()),
AbiParam::special(ctx.ptype(), ArgumentPurpose::StructReturn),
AbiParam::new(types::F64),
AbiParam::new(types::F64),
AbiParam::new(types::I64),
AbiParam::new(types::I64),
],
vec![],
);
let entry_block = func_ctx.builder.create_block();
let body_block = func_ctx.builder.create_block();
let exit_block = func_ctx.builder.create_block();

func_ctx.builder.switch_to_block(entry_block);
func_ctx
.builder
.append_block_params_for_function_params(entry_block);
let p1 = func_ctx.builder.block_params(entry_block)[0];
let p2 = func_ctx.builder.block_params(entry_block)[1];
let p3 = func_ctx.builder.block_params(entry_block)[2];
let p4 = func_ctx.builder.block_params(entry_block)[3];
let p5 = func_ctx.builder.block_params(entry_block)[4];
let p6 = func_ctx.builder.block_params(entry_block)[5];
let p7 = func_ctx.builder.block_params(entry_block)[6];

let p4 = func_ctx.builder.ins().splat(data_type, p4);
let p5 = func_ctx.builder.ins().splat(data_type, p5);

func_ctx
.builder
.append_block_param(body_block, func_ctx.ptype);
func_ctx
.builder
.append_block_param(body_block, func_ctx.ptype);
func_ctx
.builder
.append_block_param(body_block, func_ctx.ptype);
func_ctx.builder.append_block_param(body_block, data_type);
func_ctx.builder.append_block_param(body_block, data_type);
func_ctx.builder.append_block_param(body_block, types::I64);
func_ctx.builder.append_block_param(body_block, types::I64);
func_ctx
.builder
.ins()
.jump(body_block, &[p1, p2, p3, p4, p5, p6, p7]);
func_ctx.builder.seal_block(entry_block);

func_ctx.builder.switch_to_block(body_block);
let lhs_ref = func_ctx.builder.block_params(body_block)[0];
let rhs_ref = func_ctx.builder.block_params(body_block)[1];
let result_ref = func_ctx.builder.block_params(body_block)[2];
let to_div = func_ctx.builder.block_params(body_block)[3];
let to_lt = func_ctx.builder.block_params(body_block)[4];
let start = func_ctx.builder.block_params(body_block)[5];
let end = func_ctx.builder.block_params(body_block)[6];

let lhs = func_ctx
.builder
.ins()
.load(data_type, MemFlags::new(), lhs_ref, 0);
let rhs = func_ctx
.builder
.ins()
.load(data_type, MemFlags::new(), rhs_ref, 0);
let sum = func_ctx.builder.ins().fadd(lhs, rhs);
let div_result = func_ctx.builder.ins().fdiv(sum, to_div);
let result = func_ctx
.builder
.ins()
.fcmp(FloatCC::LessThan, div_result, to_lt);

func_ctx
.builder
.ins()
.store(MemFlags::new(), result, result_ref, 0);

let offset = func_ctx
.builder
.ins()
.iconst(types::I64, data_type.bytes() as i64);

let result_offset = func_ctx
.builder
.ins()
.iconst(types::I64, result_type.bytes() as i64);

let next_lhs_ref = func_ctx.builder.ins().iadd(offset, lhs_ref);
let next_rhs_ref = func_ctx.builder.ins().iadd(offset, rhs_ref);
let next_result_ref = func_ctx.builder.ins().iadd(result_offset, result_ref);

let next_start = func_ctx.builder.ins().iadd_imm(start, 1);
let cond = func_ctx
.builder
.ins()
.icmp(IntCC::SignedLessThan, next_start, end);
func_ctx.builder.ins().brif(
cond,
body_block,
&[
next_lhs_ref,
next_rhs_ref,
next_result_ref,
to_div,
to_lt,
next_start,
end,
],
exit_block,
&[],
);
func_ctx.builder.switch_to_block(exit_block);
let func_id = func_ctx.finalize(&[]);
let code = ctx.finalize(func_id);
unsafe { mem::transmute::<_, fn(*const u8, *const u8, *const bool, f64, f64, i64, i64)>(code) }
}
```
2. call jit comiled function

``` rust
pub fn jit_expr_on_array_v3(
a: &Float64Array,
b: &Float64Array,
c: f64,
d: f64,
op: fn(*const u8, *const u8, *const bool, f64, f64, i64, i64),
) -> Result {
if a.len() != b.len() {
return Err(ArrowError::ComputeError(
"Cannot perform binary operation on arrays of different length".to_string(),
));
}

if a.is_empty() {
return Err(ArrowError::ComputeError(
"Cannot perform binary operation on arrays of different length".to_string(),
));
}
let nulls = NullBuffer::union(a.logical_nulls().as_ref(), b.logical_nulls().as_ref());
let a_ptr = a.values().inner().as_ptr();
let b_ptr = b.values().inner().as_ptr();
let mut res: Vec = Vec::with_capacity((a.len() / 2) * 2);
let res_ptr = res.as_ptr();
op(a_ptr, b_ptr, res_ptr, c, d, 0, (a.len() / 2) as i64);
unsafe {
res.set_len((a.len() / 2) * 2);
}
let buffer = BooleanBuffer::from_iter(res);
Ok(BooleanArray::new(buffer, nulls))
}
```

> also attch workable version.

### sucessful `.clif` Test Case for F64
```
pushq %rbp
unwind PushFrameRegs { offset_upward_to_caller_sp: 16 }
movq %rsp, %rbp
unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 }
block0:
movq %rdx, %r9
movq %r9, %rax
jmp label1
block1:
vmovsd 0(%rdi), %xmm2
vaddsd %xmm2, 0(%rsi), %xmm2
vdivsd %xmm2, %xmm0, %xmm2
ucomisd %xmm2, %xmm1
setnbe %r10b
movb %r10b, 0(%rax)
lea 1(%rcx), %rcx
lea 8(%rdi), %rdi
lea 8(%rsi), %rsi
lea 1(%rax), %rax
cmpq %r8, %rcx
jl label2; j label3
block2:
jmp label1
block3:
movq %r9, %rax
movq %rbp, %rsp
popq %rbp
ret
```

### Steps to Reproduce

1) the test cases.

``` rust
fn test_jit_expr_on_array_v3_64() {
let BATCH_SIZE = 64;
let a = create_primitive_array::(BATCH_SIZE, 0.);
let b = create_primitive_array::(BATCH_SIZE, 0.);
let c = 3.0_f64;
let d = 3.0_f64;
let op = jit_expr_v3();
for _ in 0..100000 {
let res = jit_expr_on_array_v3(&a, &b, c, d, op).unwrap();
let (values, _) = res.into_parts();
}
}
```
``` rust
pub fn jit_expr_on_array_v3(
a: &Float64Array,
b: &Float64Array,
c: f64,
d: f64,
op: fn(*const u8, *const u8, *const bool, f64, f64, i64, i64),
) -> Result {
if a.len() != b.len() {
return Err(ArrowError::ComputeError(
"Cannot perform binary operation on arrays of different length".to_string(),
));
}

if a.is_empty() {
return Err(ArrowError::ComputeError(
"Cannot perform binary operation on arrays of different length".to_string(),
));
}
let nulls = NullBuffer::union(a.logical_nulls().as_ref(), b.logical_nulls().as_ref());
let a_ptr = a.values().inner().as_ptr();
let b_ptr = b.values().inner().as_ptr();
let mut res: Vec = Vec::with_capacity(a.len());
let res_ptr = res.as_ptr();
op(a_ptr, b_ptr, res_ptr, c, d, 0, (a.len()) as i64);
unsafe {
res.set_len(a.len());
}
let buffer = BooleanBuffer::from_iter(res);
Ok(BooleanArray::new(buffer, nulls))
}
```
``` rust
pub fn jit_expr_v3() -> fn(*const u8, *const u8, *const bool, f64, f64, i64, i64) {
let mut ctx = CodegenContext::builder().debug().finish();
let data_type = types::F64;
let result_type = types::I8;

let mut func_ctx = ctx.create_func_gen_ctx(
"op_v3",
vec![
AbiParam::new(ctx.ptype()),
AbiParam::new(ctx.ptype()),
AbiParam::special(ctx.ptype(), ArgumentPurpose::StructReturn),
AbiParam::new(types::F64),
AbiParam::new(types::F64),
AbiParam::new(types::I64),
AbiParam::new(types::I64),
],
vec![],
);
let entry_block = func_ctx.builder.create_block();
let body_block = func_ctx.builder.create_block();
let exit_block = func_ctx.builder.create_block();

func_ctx.builder.switch_to_block(entry_block);
func_ctx
.builder
.append_block_params_for_function_params(entry_block);
let p1 = func_ctx.builder.block_params(entry_block)[0];
let p2 = func_ctx.builder.block_params(entry_block)[1];
let p3 = func_ctx.builder.block_params(entry_block)[2];
let p4 = func_ctx.builder.block_params(entry_block)[3];
let p5 = func_ctx.builder.block_params(entry_block)[4];
let p6 = func_ctx.builder.block_params(entry_block)[5];
let p7 = func_ctx.builder.block_params(entry_block)[6];

// let p4 = func_ctx.builder.ins().splat(data_type, p4);
// let p5 = func_ctx.builder.ins().splat(data_type, p5);

func_ctx
.builder
.append_block_param(body_block, func_ctx.ptype);
func_ctx
.builder
.append_block_param(body_block, func_ctx.ptype);
func_ctx
.builder
.append_block_param(body_block, func_ctx.ptype);
func_ctx.builder.append_block_param(body_block, data_type);
func_ctx.builder.append_block_param(body_block, data_type);
func_ctx.builder.append_block_param(body_block, types::I64);
func_ctx.builder.append_block_param(body_block, types::I64);
func_ctx
.builder
.ins()
.jump(body_block, &[p1, p2, p3, p4, p5, p6, p7]);
func_ctx.builder.seal_block(entry_block);

func_ctx.builder.switch_to_block(body_block);
let lhs_ref = func_ctx.builder.block_params(body_block)[0];
let rhs_ref = func_ctx.builder.block_params(body_block)[1];
let result_ref = func_ctx.builder.block_params(body_block)[2];
let to_div = func_ctx.builder.block_params(body_block)[3];
let to_lt = func_ctx.builder.block_params(body_block)[4];
let start = func_ctx.builder.block_params(body_block)[5];
let end = func_ctx.builder.block_params(body_block)[6];

let lhs = func_ctx
.builder
.ins()
.load(data_type, MemFlags::new(), lhs_ref, 0);
let rhs = func_ctx
.builder
.ins()
.load(data_type, MemFlags::new(), rhs_ref, 0);
let sum = func_ctx.builder.ins().fadd(lhs, rhs);
let div_result = func_ctx.builder.ins().fdiv(sum, to_div);
let result = func_ctx
.builder
.ins()
.fcmp(FloatCC::LessThan, div_result, to_lt);

func_ctx
.builder
.ins()
.store(MemFlags::new(), result, result_ref, 0);

let offset = func_ctx
.builder
.ins()
.iconst(types::I64, data_type.bytes() as i64);

let result_offset = func_ctx
.builder
.ins()
.iconst(types::I64, result_type.bytes() as i64);

let next_lhs_ref = func_ctx.builder.ins().iadd(offset, lhs_ref);
let next_rhs_ref = func_ctx.builder.ins().iadd(offset, rhs_ref);
let next_result_ref = func_ctx.builder.ins().iadd(result_offset, result_ref);

let next_start = func_ctx.builder.ins().iadd_imm(start, 1);
let cond = func_ctx
.builder
.ins()
.icmp(IntCC::SignedLessThan, next_start, end);
func_ctx.builder.ins().brif(
cond,
body_block,
&[
next_lhs_ref,
next_rhs_ref,
next_result_ref,
to_div,
to_lt,
next_start,
end,
],
exit_block,
&[],
);
func_ctx.builder.switch_to_block(exit_block);
let func_id = func_ctx.finalize(&[]);
let code = ctx.finalize(func_id);
unsafe { mem::transmute::<_, fn(*const u8, *const u8, *const bool, f64, f64, i64, i64)>(code) }
}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.