Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIMD-0174: SBPF arithmetics improvements #174

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions proposals/0174-sbpf-arithmetics-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
simd: '0174'
title: SBPF arithmetics improvements
authors:
- Alexander Meißner
category: Standard
type: Core
status: Draft
created: 2024-09-06
feature: TBD
extends: SIMD-0161
---

## Summary

This proposal introduces wide multiplication, signed division and explicit sign
extension to SBPF.
Comment on lines +16 to +17

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's specify which SBPF version this will be released in.


## Motivation

All major hardware ISAs support 64 x 64 = 128 bit multiplication; BPF does not.
This hurts performance of big integer arithmetics. Similarly, signed division
and signed remainder / modulo instructions must be emulated in software as
well.

Another issue related to arithmetics is that some 32 bit instructions perform
sign extension of their results (output, **not** input, there is a difference)
implicitly. This is first of all useless because source languages do not read
the 32 MSBs of a 32 bit result stored in a 64 bit register. And second, it
requires interpreters and compilers to perform extra work to add sign extension
at the end of these instructions. Instead we should go the same route as the
underlying hardware ISAs and require sign extension to be made explicit in a
dedicated instruction.

Furthermore, the instruction `sub dst, imm` is redundant as it can also be
encoded as `add dst, -imm`. If we were to swap the operands meaning of minuend
and subtrahend, then the instruction would become useful and would even render
`neg dst` redundant. Negation would then be encoded as `reg = 0 - reg`. This
would also make it clear how sign extension rules work for negation.

## Alternatives Considered

None.

## New Terminology

None.

## Detailed Design

The following must go into effect if and only if a program indicates the
SBPF-version (TBD) or higher in its program header (see SIMD-0161).

### Changes to the Bytecode Verifier

A program containing one of the following instructions must throw
`VerifierError::UnknownOpCode` during verification:

- the `MUL` instruction (opcodes `0x24`, `0x2C`, `0x27` and `0x2F`)
- the `DIV` instruction (opcodes `0x34`, `0x3C`, `0x37` and `0x3F`)
- the `MOD` instruction (opcodes `0x94`, `0x9C`, `0x97` and `0x9F`)
- the `NEG` instruction (opcodes `0x84` and `0x87`)

A program containing one of the following instructions must **not** throw
`VerifierError::UnknownOpCode` during verification anymore (opcodes are for
immediate and register variant each, in that order):

- the `UHMUL64` instruction (opcode `0x36` and `0x3E`)
- the `UDIV32` instruction (opcode `0x46` and `0x4E`)
- the `UDIV64` instruction (opcode `0x56` and `0x5E`)
- the `UREM32` instruction (opcode `0x66` and `0x6E`)
- the `UREM64` instruction (opcode `0x76` and `0x7E`)
- the `LMUL32` instruction (opcode `0x86` and `0x8E`)
- the `LMUL64` instruction (opcode `0x96` and `0x9E`)
- the `SHMUL64` instruction (opcode `0xB6` and `0xBE`)
- the `SDIV32` instruction (opcode `0xC6` and `0xCE`)
- the `SDIV64` instruction (opcode `0xD6` and `0xDE`)
- the `SREM32` instruction (opcode `0xE6` and `0xEE`)
- the `SREM64` instruction (opcode `0xF6` and `0xFE`)
Lichtso marked this conversation as resolved.
Show resolved Hide resolved

The verification rule, that an immediate divisor must not be zero or else
`VerifierError::DivisionByZero` is thrown, is moved to the new division like
instructions: `UDIV32_IMM`, `UDIV64_IMM`, `UREM32_IMM`, `UREM64_IMM`,
`SDIV32_IMM`, `SDIV64_IMM`, `SREM32_IMM`, `SREM64_IMM`.

### Changes to Execution

#### PQR Instruction Class

A new instruction class product, quotient and remainder (PQR) is introduced:

- the `UHMUL64` instruction (opcode `0x36` and `0x3E`) produces the 64 MSBs of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an impl note for UHMUL64_IMM: imm is sign extended to 64-bits.

Practically speaking, this means that to do 64x64=128 bit umul one should use UHMUL64_REG.

the product of an unsigned 64 x 64 bit multiplication (`dst * imm` and
`dst * src`).
- the `UDIV32` instruction (opcode `0x46` and `0x4E`) produces the quotient of
an unsigned 32 bit division (`dst / imm` and `dst / src`).
- the `UDIV64` instruction (opcode `0x56` and `0x5E`) produces the quotient of
an unsigned 64 bit division (`dst / imm` and `dst / src`).
- the `UREM32` instruction (opcode `0x66` and `0x6E`) produces the remainder of
an unsigned 64 bit division (`dst % imm` and `dst % src`).
- the `UREM64` instruction (opcode `0x76` and `0x7E`) produces the remainder of
an unsigned 64 bit division (`dst % imm` and `dst % src`).
- the `LMUL32` instruction (opcode `0x86` and `0x8E`) produces the 32 LSBs of
the product of any 32 x 32 bit multiplication (`dst * imm` and `dst * src`).
- the `LMUL64` instruction (opcode `0x96` and `0x9E`) produces the 64 LSBs of
the product of any 64 x 64 bit multiplication (`dst * imm` and `dst * src`).
- the `SHMUL64` instruction (opcode `0xB6` and `0xBE`) produces the 64 MSBs of
the product of a signed 64 x 64 bit multiplication (`dst * imm` and
`dst * src`).
- the `SDIV32` instruction (opcode `0xC6` and `0xCE`) produces the quotient of
a signed 32 bit division (`dst / imm` and `dst / src`).
- the `SDIV64` instruction (opcode `0xD6` and `0xDE`) produces the quotient of
a signed 64 bit division (`dst / imm` and `dst / src`).
- the `SREM32` instruction (opcode `0xE6` and `0xEE`) produces the remainder of
a signed 32 bit division (`dst / imm` and `dst / src`).
- the `SREM64` instruction (opcode `0xF6` and `0xFE`) produces the remainder of
a signed 64 bit division (`dst / imm` and `dst / src`).

Runtime exceptions are:

- If the divisor (`imm` or `src`) of any division is zero,
`EbpfError::DivideByZero` must be thrown.
- If the dividend (`dst`) of a signed division has the minimal value for its
bit-width and the divisor (`imm` or `src`) is `-1`, then
`EbpfError::DivideOverflow` must be thrown.
Lichtso marked this conversation as resolved.
Show resolved Hide resolved

#### Explicit Sign Extension

The following instructions must stop performing implicit sign extension of
their results, instead filling the 32 MSBs of `dst` with zeros:

- the `ADD32` instruction (opcode `0x04` and `0x0C`)
- the `SUB32` instruction (opcode `0x14` and `0x1C`)
- all of the new 32 bit PQR instructions: `UDIV32`, `UREM32`, `SDIV32`,
`SREM32` and `LMUL32`

Instead the `MOV32_REG` instruction (opcode `0xBC`) which until now did zero
out the 32 MSBs, must now perform sign extension in the 32 MSBs. Meaning this
instruction becomes the explicit sign extension operation.

#### Register Immediate Subtraction

The operands roles of `SUB32_IMM` (opcode `0x14`) and `SUB64_IMM` (opcode
`0x17`) must be swapped: Until now the resulting difference was `src - imm` and
it must be changed to `imm - src`.

## Impact

The toolchain will emit machine-code according to the selected SBPF version.
Big integer arithmetics and signed divisions will become cheaper CU wise.

## Security Considerations

None.
Loading