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
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
@@ -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`.
@@ -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)
```
9 changes: 9 additions & 0 deletions sway-ast/src/intrinsics.rs
Original file line number Diff line number Diff line change
@@ -23,6 +23,9 @@ pub enum Intrinsic {
And,
Or,
Xor,
Lsh,
Rsh,
Mod,
Revert,
PtrAdd,
PtrSub,
@@ -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",
@@ -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,
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
@@ -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,
47 changes: 39 additions & 8 deletions sway-core/src/ir_generation/const_eval.rs
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ use super::{
types::*,
};

use sway_ast::Intrinsic;
use sway_error::error::CompileError;
use sway_ir::{
constant::{Constant, ConstantValue},
@@ -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)
@@ -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 {
8 changes: 7 additions & 1 deletion sway-core/src/ir_generation/function.rs
Original file line number Diff line number Diff line change
@@ -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,
@@ -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];
Original file line number Diff line number Diff line change
@@ -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)
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
@@ -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()
}
}
}

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

/// Special registers in the Fuel Virtual Machine.
3 changes: 3 additions & 0 deletions sway-ir/src/parser.rs
Original file line number Diff line number Diff line change
@@ -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()
3 changes: 3 additions & 0 deletions sway-ir/src/printer.rs
Original file line number Diff line number Diff line change
@@ -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))
60 changes: 12 additions & 48 deletions sway-lib-core/src/ops.sw
Original file line number Diff line number Diff line change
@@ -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)
}
}

@@ -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)
}
}

Original file line number Diff line number Diff line change
@@ -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.