Compare commits

..

7 Commits

Author SHA1 Message Date
David Mak 742b264911 standalone: Add regression test for numeric primitive operations 2023-11-03 15:57:39 +08:00
David Mak 53e02ace27 core: Fix int32-to-uint64 conversion
This conversion should be sign-extended.
2023-11-03 15:57:39 +08:00
David Mak ad92eb9015 core: Fix conversion from float to unsigned types
These conversions also need to wraparound.
2023-11-03 15:57:39 +08:00
David Mak ce9969ceb3 core: Fix handling of float-to-int32 casts
Out-of-bound conversions should be wrapped around.
2023-11-03 15:57:39 +08:00
David Mak dd66551c2d standalone: Fix output format string for output_uint* 2023-11-03 15:57:39 +08:00
David Mak 50828f5a82 core: Implement bitwise not for unsigned ints and fix implementation 2023-11-03 15:57:39 +08:00
David Mak 23f0805f20 core: Remove trunc
The behavior of trunc is already implemented by casts and is therefore
redundant.
2023-11-03 15:57:39 +08:00
6 changed files with 317 additions and 138 deletions

View File

@ -1352,25 +1352,16 @@ pub fn gen_expr<'ctx, 'a, G: CodeGenerator>(
}
_ => val.into(),
}
} else if [ctx.primitives.int32, ctx.primitives.int64].contains(&ty) {
} else if [ctx.primitives.int32, ctx.primitives.int64, ctx.primitives.uint32, ctx.primitives.uint64].contains(&ty) {
let val = val.into_int_value();
match op {
ast::Unaryop::USub => ctx.builder.build_int_neg(val, "neg").into(),
ast::Unaryop::Invert => ctx.builder.build_not(val, "not").into(),
ast::Unaryop::Not => ctx
.builder
.build_int_compare(
IntPredicate::EQ,
val,
val.get_type().const_zero(),
"not",
)
.into(),
ast::Unaryop::Not => ctx.builder.build_xor(val, val.get_type().const_all_ones(), "not").into(),
_ => val.into(),
}
} else if ty == ctx.primitives.float {
let val =
if let BasicValueEnum::FloatValue(val) = val { val } else { unreachable!() };
let val = val.into_float_value();
match op {
ast::Unaryop::USub => ctx.builder.build_float_neg(val, "neg").into(),
ast::Unaryop::Not => ctx

View File

@ -477,12 +477,16 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
resolver: None,
codegen_callback: Some(Arc::new(GenCall::new(Box::new(
|ctx, _, fun, args, generator| {
let int32 = ctx.primitives.int32;
let int64 = ctx.primitives.int64;
let uint32 = ctx.primitives.uint32;
let uint64 = ctx.primitives.uint64;
let float = ctx.primitives.float;
let boolean = ctx.primitives.bool;
let PrimitiveStore {
int32,
int64,
uint32,
uint64,
float,
bool: boolean,
..
} = ctx.primitives;
let arg_ty = fun.0.args[0].ty;
let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty)?;
Ok(if ctx.unifier.unioned(arg_ty, boolean) {
@ -512,15 +516,21 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
.into(),
)
} else if ctx.unifier.unioned(arg_ty, float) {
let val = ctx
let to_int64 = ctx
.builder
.build_float_to_signed_int(
arg.into_float_value(),
ctx.ctx.i64_type(),
"",
);
let val = ctx.builder
.build_int_truncate(
to_int64,
ctx.ctx.i32_type(),
"fptosi",
)
.into();
Some(val)
"conv",
);
Some(val.into())
} else {
unreachable!()
})
@ -542,12 +552,16 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
resolver: None,
codegen_callback: Some(Arc::new(GenCall::new(Box::new(
|ctx, _, fun, args, generator| {
let int32 = ctx.primitives.int32;
let int64 = ctx.primitives.int64;
let uint32 = ctx.primitives.uint32;
let uint64 = ctx.primitives.uint64;
let float = ctx.primitives.float;
let boolean = ctx.primitives.bool;
let PrimitiveStore {
int32,
int64,
uint32,
uint64,
float,
bool: boolean,
..
} = ctx.primitives;
let arg_ty = fun.0.args[0].ty;
let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty)?;
Ok(
@ -609,12 +623,16 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
resolver: None,
codegen_callback: Some(Arc::new(GenCall::new(Box::new(
|ctx, _, fun, args, generator| {
let int32 = ctx.primitives.int32;
let int64 = ctx.primitives.int64;
let uint32 = ctx.primitives.uint32;
let uint64 = ctx.primitives.uint64;
let float = ctx.primitives.float;
let boolean = ctx.primitives.bool;
let PrimitiveStore {
int32,
int64,
uint32,
uint64,
float,
bool: boolean,
..
} = ctx.primitives;
let arg_ty = fun.0.args[0].ty;
let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty)?;
let res = if ctx.unifier.unioned(arg_ty, boolean) {
@ -632,13 +650,34 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
.build_int_truncate(arg.into_int_value(), ctx.ctx.i32_type(), "trunc")
.into()
} else if ctx.unifier.unioned(arg_ty, float) {
ctx.builder
let llvm_i32 = ctx.ctx.i32_type();
let llvm_i64 = ctx.ctx.i64_type();
let arg = arg.into_float_value();
let arg_gez = ctx.builder
.build_float_compare(FloatPredicate::OGE, arg, arg.get_type().const_zero(), "");
let to_int32 = ctx.builder
.build_float_to_signed_int(
arg,
llvm_i32,
""
);
let to_uint64 = ctx.builder
.build_float_to_unsigned_int(
arg.into_float_value(),
ctx.ctx.i32_type(),
"ftoi",
)
.into()
arg,
llvm_i64,
""
);
let val = ctx.builder.build_select(
arg_gez,
ctx.builder.build_int_truncate(to_uint64, llvm_i32, ""),
to_int32,
"conv"
);
val.into()
} else {
unreachable!();
};
@ -661,33 +700,60 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
resolver: None,
codegen_callback: Some(Arc::new(GenCall::new(Box::new(
|ctx, _, fun, args, generator| {
let int32 = ctx.primitives.int32;
let int64 = ctx.primitives.int64;
let uint32 = ctx.primitives.uint32;
let uint64 = ctx.primitives.uint64;
let float = ctx.primitives.float;
let boolean = ctx.primitives.bool;
let PrimitiveStore {
int32,
int64,
uint32,
uint64,
float,
bool: boolean,
..
} = ctx.primitives;
let arg_ty = fun.0.args[0].ty;
let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty)?;
let res = if ctx.unifier.unioned(arg_ty, int32)
|| ctx.unifier.unioned(arg_ty, uint32)
let res = if ctx.unifier.unioned(arg_ty, uint32)
|| ctx.unifier.unioned(arg_ty, boolean)
{
ctx.builder
.build_int_z_extend(arg.into_int_value(), ctx.ctx.i64_type(), "zext")
.into()
} else if ctx.unifier.unioned(arg_ty, int32) {
ctx.builder
.build_int_s_extend(arg.into_int_value(), ctx.ctx.i64_type(), "sext")
.into()
} else if ctx.unifier.unioned(arg_ty, int64)
|| ctx.unifier.unioned(arg_ty, uint64)
{
arg
} else if ctx.unifier.unioned(arg_ty, float) {
ctx.builder
let llvm_i64 = ctx.ctx.i64_type();
let arg = arg.into_float_value();
let arg_gez = ctx.builder
.build_float_compare(FloatPredicate::OGE, arg, arg.get_type().const_zero(), "");
let to_int64 = ctx.builder
.build_float_to_signed_int(
arg,
llvm_i64,
""
);
let to_uint64 = ctx.builder
.build_float_to_unsigned_int(
arg.into_float_value(),
ctx.ctx.i64_type(),
"ftoi",
)
.into()
arg,
llvm_i64,
""
);
let val = ctx.builder.build_select(
arg_gez,
to_uint64,
to_int64,
"conv"
);
val.into()
} else {
unreachable!();
};
@ -710,16 +776,20 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
resolver: None,
codegen_callback: Some(Arc::new(GenCall::new(Box::new(
|ctx, _, fun, args, generator| {
let int32 = ctx.primitives.int32;
let int64 = ctx.primitives.int64;
let boolean = ctx.primitives.bool;
let float = ctx.primitives.float;
let PrimitiveStore {
int32,
int64,
uint32,
uint64,
float,
bool: boolean,
..
} = ctx.primitives;
let arg_ty = fun.0.args[0].ty;
let arg = args[0].1.clone().to_basic_value_enum(ctx, generator, arg_ty)?;
Ok(
if ctx.unifier.unioned(arg_ty, boolean)
|| ctx.unifier.unioned(arg_ty, int32)
|| ctx.unifier.unioned(arg_ty, int64)
if [boolean, int32, int64].iter().any(|ty| ctx.unifier.unioned(arg_ty, *ty))
{
let arg = arg.into_int_value();
let val = ctx
@ -727,6 +797,13 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
.build_signed_int_to_float(arg, ctx.ctx.f64_type(), "sitofp")
.into();
Some(val)
} else if [uint32, uint64].iter().any(|ty| ctx.unifier.unioned(arg_ty, *ty)) {
let arg = arg.into_int_value();
let val = ctx
.builder
.build_unsigned_int_to_float(arg, ctx.ctx.f64_type(), "uitofp")
.into();
Some(val)
} else if ctx.unifier.unioned(arg_ty, float) {
Some(arg)
} else {
@ -1448,66 +1525,6 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
&[(float, "x")],
"llvm.fabs.f64",
),
create_fn_by_codegen(
primitives,
&var_map,
"trunc",
int32,
&[(float, "x")],
Box::new(|ctx, _, _, args, generator| {
let llvm_f64 = ctx.ctx.f64_type();
let llvm_i32 = ctx.ctx.i32_type();
let arg = args[0].1.clone()
.to_basic_value_enum(ctx, generator, ctx.primitives.float)?;
let intrinsic_fn = ctx.module.get_function("llvm.trunc.f64").unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("llvm.trunc.f64", fn_type, None)
});
let val = ctx
.builder
.build_call(intrinsic_fn, &[arg.into()], "")
.try_as_basic_value()
.left()
.unwrap();
let val_toint = ctx.builder
.build_float_to_signed_int(val.into_float_value(), llvm_i32, "trunc");
Ok(Some(val_toint.into()))
}),
),
create_fn_by_codegen(
primitives,
&var_map,
"trunc64",
int64,
&[(float, "x")],
Box::new(|ctx, _, _, args, generator| {
let llvm_f64 = ctx.ctx.f64_type();
let llvm_i64 = ctx.ctx.i64_type();
let arg = args[0].1.clone()
.to_basic_value_enum(ctx, generator, ctx.primitives.float)?;
let intrinsic_fn = ctx.module.get_function("llvm.trunc.f64").unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
ctx.module.add_function("llvm.trunc.f64", fn_type, None)
});
let val = ctx
.builder
.build_call(intrinsic_fn, &[arg.into()], "")
.try_as_basic_value()
.left()
.unwrap();
let val_toint = ctx.builder
.build_float_to_signed_int(val.into_float_value(), llvm_i64, "trunc");
Ok(Some(val_toint.into()))
}),
),
create_fn_by_intrinsic(
primitives,
&var_map,
@ -1977,8 +1994,6 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
"log10",
"log2",
"fabs",
"trunc",
"trunc64",
"sqrt",
"rint",
"tan",

View File

@ -34,11 +34,11 @@ void output_int64(int64_t x) {
}
void output_uint32(uint32_t x) {
printf("%d\n", x);
printf("%u\n", x);
}
void output_uint64(uint64_t x) {
printf("%ld\n", x);
printf("%lu\n", x);
}
void output_float64(double x) {

View File

@ -112,8 +112,6 @@ def patch(module):
module.floor64 = math.floor
module.ceil = math.ceil
module.ceil64 = math.ceil
module.trunc = math.trunc
module.trunc64 = math.trunc
# NumPy Math functions
module.isnan = np.isnan
@ -126,6 +124,7 @@ def patch(module):
module.log10 = np.log10
module.log2 = np.log2
module.fabs = np.fabs
module.trunc = np.trunc
module.sqrt = np.sqrt
module.rint = np.rint
module.tan = np.tan

View File

@ -95,14 +95,6 @@ def test_ceil64():
for x in [-1.5, -0.5, 0.5, 1.5]:
output_int64(ceil64(x))
def test_trunc():
for x in [-1.5, -0.5, 0.5, 1.5]:
output_int32(trunc(x))
def test_trunc64():
for x in [-1.5, -0.5, 0.5, 1.5]:
output_int64(trunc64(x))
def test_sqrt():
for x in [1.0, 2.0, 4.0, dbl_inf(), -dbl_inf(), dbl_nan()]:
output_float64(sqrt(x))
@ -236,8 +228,6 @@ def run() -> int32:
test_floor64()
test_ceil()
test_ceil64()
test_trunc()
test_trunc64()
test_sqrt()
test_rint()
test_tan()

View File

@ -0,0 +1,184 @@
@extern
def output_bool(x: bool):
...
@extern
def output_int32(x: int32):
...
@extern
def output_int64(x: int64):
...
@extern
def output_uint32(x: uint32):
...
@extern
def output_uint64(x: uint64):
...
@extern
def output_float64(x: float):
...
def u32_min() -> uint32:
return uint32(0)
def u32_max() -> uint32:
return ~uint32(0)
def i32_min() -> int32:
return int32(1 << 31)
def i32_max() -> int32:
return int32(~(1 << 31))
def u64_min() -> uint64:
return uint64(0)
def u64_max() -> uint64:
return ~uint64(0)
def i64_min() -> int64:
return int64(1) << int64(63)
def i64_max() -> int64:
return ~(int64(1) << int64(63))
def test_u32_bnot():
output_uint32(~uint32(0))
def test_u64_bnot():
output_uint64(~uint64(0))
def test_conv_from_i32():
for x in [
i32_min(),
i32_min() + 1,
-1,
0,
1,
i32_max() - 1,
i32_max()
]:
output_int64(int64(x))
output_uint32(uint32(x))
output_uint64(uint64(x))
output_float64(float(x))
def test_conv_from_u32():
for x in [
u32_min(),
u32_min() + uint32(1),
u32_max() - uint32(1),
u32_max()
]:
output_uint64(uint64(x))
output_int32(int32(x))
output_int64(int64(x))
output_float64(float(x))
def test_conv_from_i64():
for x in [
i64_min(),
i64_min() + int64(1),
int64(-1),
int64(0),
int64(1),
i64_max() - int64(1),
i64_max()
]:
output_int32(int32(x))
output_uint64(uint64(x))
output_uint32(uint32(x))
output_float64(float(x))
def test_conv_from_u64():
for x in [
u64_min(),
u64_min() + uint64(1),
u64_max() - uint64(1),
u64_max()
]:
output_uint32(uint32(x))
output_int64(int64(x))
output_int32(int32(x))
output_float64(float(x))
def test_f64toi32():
for x in [
float(i32_min()) - 1.0,
float(i32_min()),
float(i32_min()) + 1.0,
-1.5,
-0.5,
0.5,
1.5,
float(i32_max()) - 1.0,
float(i32_max()),
float(i32_max()) + 1.0
]:
output_int32(int32(x))
def test_f64toi64():
for x in [
float(i64_min()),
float(i64_min()) + 1.0,
-1.5,
-0.5,
0.5,
1.5,
# 2^53 is the highest integral power-of-two of which uint64 and float have a one-to-one correspondence
float(uint64(2) ** uint64(52)) - 1.0,
float(uint64(2) ** uint64(52)),
float(uint64(2) ** uint64(52)) + 1.0,
]:
output_int64(int64(x))
def test_f64tou32():
for x in [
-1.5,
float(u32_min()) - 1.0,
-0.5,
float(u32_min()),
0.5,
float(u32_min()) + 1.0,
1.5,
float(u32_max()) - 1.0,
float(u32_max()),
float(u32_max()) + 1.0
]:
output_uint32(uint32(x))
def test_f64tou64():
for x in [
-1.5,
float(u64_min()) - 1.0,
-0.5,
float(u64_min()),
0.5,
float(u64_min()) + 1.0,
1.5,
# 2^53 is the highest integral power-of-two of which uint64 and float have a one-to-one correspondence
float(uint64(2) ** uint64(52)) - 1.0,
float(uint64(2) ** uint64(52)),
float(uint64(2) ** uint64(52)) + 1.0,
]:
output_uint64(uint64(x))
def run() -> int32:
test_u32_bnot()
test_u64_bnot()
test_conv_from_i32()
test_conv_from_u32()
test_conv_from_i64()
test_conv_from_u64()
test_f64toi32()
test_f64toi64()
test_f64tou32()
test_f64tou64()
return 0