Skip to content

Commit

Permalink
Use intrinsics for base integer operations (FuelLabs#4553)
Browse files Browse the repository at this point in the history
## Description
This change introduces `__mod`, `__shl` and `__shr` intrinsics for use
with integer operations to remove the asm block based implementations in
core and enable us to support const evaluation of integer expressions in
most cases, notably when specifying values like `1 << 32`.

## Checklist

- [x] I have linked to any relevant issues.
- [x] I have commented my code, particularly in hard-to-understand
areas.
- [x] I have updated the documentation where relevant (API docs, the
reference, and the Sway book).
- [x] I have added tests that prove my fix is effective or that my
feature works.
- [x] I have added (or requested a maintainer to add) the necessary
`Breaking*` or `New Feature` labels where relevant.
- [x] I have done my best to ensure that my PR adheres to [the Fuel Labs
Code Review
Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md).
- [x] I have requested a review from the relevant team or maintainers.
  • Loading branch information
IGI-111 authored May 16, 2023
1 parent f44bfd8 commit 29b1d0c
Show file tree
Hide file tree
Showing 19 changed files with 205 additions and 71 deletions.
29 changes: 28 additions & 1 deletion docs/book/src/reference/compiler_intrinsics.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ __and<T>(lhs: T, rhs: T) -> T
___

```sway
or<T>(lhs: T, rhs: T) -> T
__or<T>(lhs: T, rhs: T) -> T
```

**Description:** Bitwise OR `lhs` and `rhs`.
Expand All @@ -201,6 +201,33 @@ __xor<T>(lhs: T, rhs: T) -> T
**Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`.
___

```sway
__mod<T>(lhs: T, rhs: T) -> T
```

**Description:** Modulo of `lhs` by `rhs`.

**Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`.
___

```sway
__rsh<T>(lhs: T, rhs: u64) -> T
```

**Description:** Logical right shift of `lhs` by `rhs`.

**Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`.
___

```sway
__lsh<T>(lhs: T, rhs: u64) -> T
```

**Description:** Logical left shift of `lhs` by `rhs`.

**Constraints:** `T` is an integer type, i.e. `u8`, `u16`, `u32`, `u64`.
___

```sway
__revert(code: u64)
```
Expand Down
9 changes: 9 additions & 0 deletions sway-ast/src/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ pub enum Intrinsic {
And,
Or,
Xor,
Lsh,
Rsh,
Mod,
Revert,
PtrAdd,
PtrSub,
Expand Down Expand Up @@ -53,6 +56,9 @@ impl fmt::Display for Intrinsic {
Intrinsic::And => "and",
Intrinsic::Or => "or",
Intrinsic::Xor => "xor",
Intrinsic::Lsh => "lsh",
Intrinsic::Rsh => "rsh",
Intrinsic::Mod => "mod",
Intrinsic::Revert => "revert",
Intrinsic::PtrAdd => "ptr_add",
Intrinsic::PtrSub => "ptr_sub",
Expand Down Expand Up @@ -87,6 +93,9 @@ impl Intrinsic {
"__and" => And,
"__or" => Or,
"__xor" => Xor,
"__lsh" => Lsh,
"__rsh" => Rsh,
"__mod" => Mod,
"__revert" => Revert,
"__ptr_add" => PtrAdd,
"__ptr_sub" => PtrSub,
Expand Down
3 changes: 3 additions & 0 deletions sway-core/src/asm_generation/fuel/fuel_asm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,9 @@ impl<'ir> FuelAsmBuilder<'ir> {
BinaryOpKind::And => Either::Left(VirtualOp::AND(res_reg.clone(), val1_reg, val2_reg)),
BinaryOpKind::Or => Either::Left(VirtualOp::OR(res_reg.clone(), val1_reg, val2_reg)),
BinaryOpKind::Xor => Either::Left(VirtualOp::XOR(res_reg.clone(), val1_reg, val2_reg)),
BinaryOpKind::Mod => Either::Left(VirtualOp::MOD(res_reg.clone(), val1_reg, val2_reg)),
BinaryOpKind::Rsh => Either::Left(VirtualOp::SRL(res_reg.clone(), val1_reg, val2_reg)),
BinaryOpKind::Lsh => Either::Left(VirtualOp::SLL(res_reg.clone(), val1_reg, val2_reg)),
};
self.cur_bytecode.push(Op {
opcode,
Expand Down
47 changes: 39 additions & 8 deletions sway-core/src/ir_generation/const_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use super::{
types::*,
};

use sway_ast::Intrinsic;
use sway_error::error::CompileError;
use sway_ir::{
constant::{Constant, ConstantValue},
Expand Down Expand Up @@ -534,7 +535,8 @@ fn const_eval_intrinsic(
| sway_ast::Intrinsic::Div
| sway_ast::Intrinsic::And
| sway_ast::Intrinsic::Or
| sway_ast::Intrinsic::Xor => {
| sway_ast::Intrinsic::Xor
| sway_ast::Intrinsic::Mod => {
let ty = args[0].ty;
assert!(
args.len() == 2 && ty.is_uint(lookup.context) && ty.eq(lookup.context, &args[1].ty)
Expand All @@ -545,13 +547,42 @@ fn const_eval_intrinsic(
};
// All arithmetic is done as if it were u64
let result = match intrinsic.kind {
sway_ast::Intrinsic::Add => arg1.checked_add(*arg2),
sway_ast::Intrinsic::Sub => arg1.checked_sub(*arg2),
sway_ast::Intrinsic::Mul => arg1.checked_mul(*arg2),
sway_ast::Intrinsic::Div => arg1.checked_div(*arg2),
sway_ast::Intrinsic::And => Some(arg1.bitand(arg2)),
sway_ast::Intrinsic::Or => Some(arg1.bitor(*arg2)),
sway_ast::Intrinsic::Xor => Some(arg1.bitxor(*arg2)),
Intrinsic::Add => arg1.checked_add(*arg2),
Intrinsic::Sub => arg1.checked_sub(*arg2),
Intrinsic::Mul => arg1.checked_mul(*arg2),
Intrinsic::Div => arg1.checked_div(*arg2),
Intrinsic::And => Some(arg1.bitand(arg2)),
Intrinsic::Or => Some(arg1.bitor(*arg2)),
Intrinsic::Xor => Some(arg1.bitxor(*arg2)),
Intrinsic::Mod => arg1.checked_rem(*arg2),
_ => unreachable!(),
};
match result {
Some(sum) => Ok(Some(Constant {
ty,
value: ConstantValue::Uint(sum),
})),
None => Ok(None),
}
}
sway_ast::Intrinsic::Lsh | sway_ast::Intrinsic::Rsh => {
let ty = args[0].ty;
assert!(
args.len() == 2
&& ty.is_uint(lookup.context)
&& args[1].ty.is_uint64(lookup.context)
);
let (ConstantValue::Uint(arg1), ConstantValue::Uint(ref arg2)) = (&args[0].value, &args[1].value)
else {
panic!("Type checker allowed incorrect args to binary op");
};
let result = match intrinsic.kind {
Intrinsic::Lsh => u32::try_from(*arg2)
.ok()
.and_then(|arg2| arg1.checked_shl(arg2)),
Intrinsic::Rsh => u32::try_from(*arg2)
.ok()
.and_then(|arg2| arg1.checked_shr(arg2)),
_ => unreachable!(),
};
match result {
Expand Down
8 changes: 7 additions & 1 deletion sway-core/src/ir_generation/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,10 @@ impl<'eng> FnCompiler<'eng> {
| Intrinsic::Div
| Intrinsic::And
| Intrinsic::Or
| Intrinsic::Xor => {
| Intrinsic::Xor
| Intrinsic::Mod
| Intrinsic::Rsh
| Intrinsic::Lsh => {
let op = match kind {
Intrinsic::Add => BinaryOpKind::Add,
Intrinsic::Sub => BinaryOpKind::Sub,
Expand All @@ -769,6 +772,9 @@ impl<'eng> FnCompiler<'eng> {
Intrinsic::And => BinaryOpKind::And,
Intrinsic::Or => BinaryOpKind::Or,
Intrinsic::Xor => BinaryOpKind::Xor,
Intrinsic::Mod => BinaryOpKind::Mod,
Intrinsic::Rsh => BinaryOpKind::Rsh,
Intrinsic::Lsh => BinaryOpKind::Lsh,
_ => unreachable!(),
};
let lhs = &arguments[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ impl ty::TyIntrinsicFunctionKind {
| Intrinsic::Div
| Intrinsic::And
| Intrinsic::Or
| Intrinsic::Xor => type_check_binary_op(ctx, kind, arguments, type_arguments, span),
| Intrinsic::Xor
| Intrinsic::Mod
| Intrinsic::Lsh
| Intrinsic::Rsh => type_check_binary_op(ctx, kind, arguments, type_arguments, span),
Intrinsic::Revert => type_check_revert(ctx, kind, arguments, type_arguments, span),
Intrinsic::PtrAdd | Intrinsic::PtrSub => {
type_check_ptr_ops(ctx, kind, arguments, type_arguments, span)
Expand Down
4 changes: 3 additions & 1 deletion sway-core/src/semantic_analysis/cei_pattern_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,9 @@ fn effects_of_intrinsic(intr: &sway_ast::Intrinsic) -> HashSet<Effect> {
StateLoadWord | StateLoadQuad => HashSet::from([Effect::StorageRead]),
Smo => HashSet::from([Effect::OutputMessage]),
Revert | IsReferenceType | SizeOfType | SizeOfVal | Eq | Gt | Lt | Gtf | AddrOf | Log
| Add | Sub | Mul | Div | And | Or | Xor | PtrAdd | PtrSub => HashSet::new(),
| Add | Sub | Mul | Div | And | Or | Xor | Mod | Rsh | Lsh | PtrAdd | PtrSub => {
HashSet::new()
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions sway-ir/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ pub enum BinaryOpKind {
And,
Or,
Xor,
Mod,
Rsh,
Lsh,
}

/// Special registers in the Fuel Virtual Machine.
Expand Down
3 changes: 3 additions & 0 deletions sway-ir/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ mod ir_builder {
/ "and" _ { BinaryOpKind::And }
/ "or" _ { BinaryOpKind::Or }
/ "xor" _ { BinaryOpKind::Xor }
/ "mod" _ { BinaryOpKind::Mod }
/ "rsh" _ { BinaryOpKind::Rsh }
/ "lsh" _ { BinaryOpKind::Lsh }

rule operation() -> IrAstOperation
= op_asm()
Expand Down
3 changes: 3 additions & 0 deletions sway-ir/src/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ fn instruction_to_doc<'a>(
BinaryOpKind::And => "and",
BinaryOpKind::Or => "or",
BinaryOpKind::Xor => "xor",
BinaryOpKind::Mod => "mod",
BinaryOpKind::Rsh => "rsh",
BinaryOpKind::Lsh => "lsh",
};
maybe_constant_to_doc(context, md_namer, namer, arg1)
.append(maybe_constant_to_doc(context, md_namer, namer, arg2))
Expand Down
60 changes: 12 additions & 48 deletions sway-lib-core/src/ops.sw
Original file line number Diff line number Diff line change
Expand Up @@ -120,37 +120,25 @@ pub trait Mod {

impl Mod for u64 {
fn modulo(self, other: Self) -> Self {
asm(r1: self, r2: other, r3) {
r#mod r3 r1 r2;
r3: u64
}
__mod(self, other)
}
}

impl Mod for u32 {
fn modulo(self, other: Self) -> Self {
asm(r1: self, r2: other, r3) {
r#mod r3 r1 r2;
r3: u32
}
__mod(self, other)
}
}

impl Mod for u16 {
fn modulo(self, other: Self) -> Self {
asm(r1: self, r2: other, r3) {
r#mod r3 r1 r2;
r3: u16
}
__mod(self, other)
}
}

impl Mod for u8 {
fn modulo(self, other: Self) -> Self {
asm(r1: self, r2: other, r3) {
r#mod r3 r1 r2;
r3: u8
}
__mod(self, other)
}
}

Expand Down Expand Up @@ -485,61 +473,37 @@ pub trait Shift {

impl Shift for u64 {
fn lsh(self, other: u64) -> Self {
asm(r1: self, r2: other, r3) {
sll r3 r1 r2;
r3: u64
}
__lsh(self, other)
}
fn rsh(self, other: u64) -> Self {
asm(r1: self, r2: other, r3) {
srl r3 r1 r2;
r3: u64
}
__rsh(self, other)
}
}

impl Shift for u32 {
fn lsh(self, other: u64) -> Self {
asm(r1: self, r2: other, r3) {
sll r3 r1 r2;
r3: u32
}
__lsh(self, other)
}
fn rsh(self, other: u64) -> Self {
asm(r1: self, r2: other, r3) {
srl r3 r1 r2;
r3: u32
}
__rsh(self, other)
}
}

impl Shift for u16 {
fn lsh(self, other: u64) -> Self {
asm(r1: self, r2: other, r3) {
sll r3 r1 r2;
r3: u16
}
__lsh(self, other)
}
fn rsh(self, other: u64) -> Self {
asm(r1: self, r2: other, r3) {
srl r3 r1 r2;
r3: u16
}
__rsh(self, other)
}
}

impl Shift for u8 {
fn lsh(self, other: u64) -> Self {
asm(r1: self, r2: other, r3) {
sll r3 r1 r2;
r3: u8
}
__lsh(self, other)
}
fn rsh(self, other: u64) -> Self {
asm(r1: self, r2: other, r3) {
srl r3 r1 r2;
r3: u8
}
__rsh(self, other)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,17 @@ fn main() -> u64 {
assert(__xor(15, (__or(8, __and(5, 11)))) == 6);
assert(__gt(2, 1) && __lt(1, 2));

assert(__mod(0, 3) == 0);
assert(__mod(1, 3) == 1);
assert(__mod(2, 3) == 2);
assert(__mod(3, 3) == 0);
assert(__mod(4, 3) == 1);
assert(__mod(5, 3) == 2);
assert(__mod(6, 3) == 0);

assert(__lsh(2, 3) == 16);
assert(__rsh(16, 3) == 2);
assert(__rsh(1, 1) == 0);

2
}
Loading

0 comments on commit 29b1d0c

Please sign in to comment.