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

chore(avm): operands reordering #10182

Merged
merged 4 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
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
16 changes: 0 additions & 16 deletions avm-transpiler/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion avm-transpiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ license = "MIT OR Apache-2.0"
# local
acvm = { path = "../noir/noir-repo/acvm-repo/acvm", features = ["bn254"] }
noirc_errors = { path = "../noir/noir-repo/compiler/noirc_errors" }
fxhash = "0.2.1"

# external
base64 = "0.21"
Expand Down
41 changes: 40 additions & 1 deletion avm-transpiler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,50 @@ This component transpiles Aztec public contracts code from Noir's Brillig byteco
## Build

```
./boostrap.sh
./bootstrap.sh
```

## Run

```
cargo run <aztec-contract-artifact-json> <transpiled-output-json>
```

## Testing Transpiler Changes

After bootstrap in `avm-transpiler`, go to `noir-contracts` and only compile avm_test_contract with:

```
nargo compile --package avm_test_contract --inliner-aggressiveness=0 --silence-warnings
```

Important: use the right nargo binary located in
`aztec-packages/noir/noir-repo/target/release/nargo`
If required, build nargo by going in `noir/noir-repo` and run
`cargo build --release`.

Then, transpile it:

```
scripts/transpile.sh
```

Go to yarn-project/simulator and run:

```
yarn build:fast
```

This takes in the TS generated by the compilation and transpilation.

Finally, run

```
yarn test src/avm/avm_simulator.test.ts
```

To test against some .cpp changes, compile the bb binary and run bb prover test:

```
yarn test src/avm_proving.test.ts
```
35 changes: 25 additions & 10 deletions avm-transpiler/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ pub struct AvmInstruction {
/// Its usage will depend on the instruction.
pub tag: Option<AvmTypeTag>,

/// Different instructions have different numbers of operands
/// Different instructions have different numbers of operands. These operands contain
/// memory addresses.
pub operands: Vec<AvmOperand>,

// Operands which are immediate, i.e., contain hardcoded constants.
pub immediates: Vec<AvmOperand>,
}

impl Display for AvmInstruction {
Expand All @@ -32,35 +36,46 @@ impl Display for AvmInstruction {
if let Some(indirect) = &self.indirect {
write!(f, ", indirect: {}", indirect)?;
}
// This will be either inTag or dstTag depending on the operation
if let Some(dst_tag) = self.tag {
write!(f, ", tag: {}", dst_tag as u8)?;
}
if !self.operands.is_empty() {
write!(f, ", operands: [")?;
for operand in &self.operands {
write!(f, "{operand}, ")?;
}
write!(f, "]")?;
};
// This will be either inTag or dstTag depending on the operation
if let Some(dst_tag) = self.tag {
write!(f, ", tag: {}", dst_tag as u8)?;
}
if !self.immediates.is_empty() {
write!(f, ", immediates: [")?;
for immediate in &self.immediates {
write!(f, "{immediate}, ")?;
}
write!(f, "]")?;
};
Ok(())
}
}

impl AvmInstruction {
/// Bytes representation for generating AVM bytecode
/// Order: INDIRECT, OPERANDS, TAG, IMMEDIATES
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, in principle I saw no reason to introduce the immediates separately, since you could've just modified the particular opcodes and put the immediate at the end. However then I saw you have the tag in between them. Is this needed? if you do: Indirect, addresses, immediates, tag then I think you wouldn't need to separate the immediates and that seems conceptually right?

Or do you need the tag to be in the middle for some reason?

Copy link
Contributor Author

@jeanmon jeanmon Nov 25, 2024

Choose a reason for hiding this comment

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

@fcarreiro I had the same thoughts and the reason I nevertheless introduced the immediates is for the SET opcode. If we put the tag before the immediates then all set opcode flavors start the same which would help to have less complex relations in bytecode decomposition.

I thought that conceptually to have "immediates" is not too bad neither.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Basically, the offset for TAG in set opcode would be at the same position (except SET_8) for all flavors. If we put TAG at the end, then we need to offset by the different immediate sizes.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see. Ok, not too bad.

pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.push(self.opcode as u8);
if let Some(indirect) = &self.indirect {
bytes.extend_from_slice(&indirect.to_be_bytes());
}
for operand in &self.operands {
bytes.extend_from_slice(&operand.to_be_bytes());
}
// This will be either inTag or dstTag depending on the operation
if let Some(tag) = self.tag {
bytes.extend_from_slice(&(tag as u8).to_be_bytes());
}
for operand in &self.operands {
bytes.extend_from_slice(&operand.to_be_bytes());
for immediate in &self.immediates {
bytes.extend_from_slice(&immediate.to_be_bytes());
}
bytes
}
Expand All @@ -84,6 +99,7 @@ impl Default for AvmInstruction {
indirect: None,
tag: None,
operands: vec![],
immediates: vec![],
}
}
}
Expand All @@ -102,9 +118,8 @@ pub enum AvmTypeTag {
INVALID,
}

/// Operands are usually 32 bits (offsets or jump destinations)
/// Constants (as used by the SET instruction) can have size
/// different from 32 bits
/// Operands are usually 8, 16 and 32 bits (offsets)
/// Immediates (as used by the SET instruction) can have different sizes
#[allow(non_camel_case_types)]
pub enum AvmOperand {
U8 { value: u8 },
Expand Down
44 changes: 20 additions & 24 deletions avm-transpiler/src/transpile.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use acvm::acir::brillig::{BitSize, IntegerBitSize, Opcode as BrilligOpcode};
use fxhash::FxHashMap as HashMap;
use std::collections::BTreeMap;

use acvm::acir::circuit::BrilligOpcodeLocation;
Expand Down Expand Up @@ -90,12 +89,12 @@ pub fn brillig_to_avm(brillig_bytecode: &[BrilligOpcode<FieldElement>]) -> (Vec<
.direct_operand(destination)
.build(),
),
tag: None,
operands: vec![
make_operand(bits_needed, &lhs.to_usize()),
make_operand(bits_needed, &rhs.to_usize()),
make_operand(bits_needed, &destination.to_usize()),
],
..Default::default()
});
}
BrilligOpcode::BinaryIntOp { destination, op, lhs, rhs, .. } => {
Expand Down Expand Up @@ -178,12 +177,12 @@ pub fn brillig_to_avm(brillig_bytecode: &[BrilligOpcode<FieldElement>]) -> (Vec<
.direct_operand(destination)
.build(),
),
tag: None,
operands: vec![
make_operand(bits_needed, &lhs.to_usize()),
make_operand(bits_needed, &rhs.to_usize()),
make_operand(bits_needed, &destination.to_usize()),
],
..Default::default()
});
}
BrilligOpcode::Not { destination, source, .. } => {
Expand All @@ -207,7 +206,7 @@ pub fn brillig_to_avm(brillig_bytecode: &[BrilligOpcode<FieldElement>]) -> (Vec<
make_operand(bits_needed, &source.to_usize()),
make_operand(bits_needed, &destination.to_usize()),
],
tag: None,
..Default::default()
});
}
BrilligOpcode::CalldataCopy { destination_address, size_address, offset_address } => {
Expand Down Expand Up @@ -236,7 +235,7 @@ pub fn brillig_to_avm(brillig_bytecode: &[BrilligOpcode<FieldElement>]) -> (Vec<
assert!(location.num_bits() <= 32);
avm_instrs.push(AvmInstruction {
opcode: AvmOpcode::JUMP_32,
operands: vec![AvmOperand::BRILLIG_LOCATION { brillig_pc: *location as u32 }],
immediates: vec![AvmOperand::BRILLIG_LOCATION { brillig_pc: *location as u32 }],
..Default::default()
});
}
Expand All @@ -247,10 +246,8 @@ pub fn brillig_to_avm(brillig_bytecode: &[BrilligOpcode<FieldElement>]) -> (Vec<
indirect: Some(
AddressingModeBuilder::default().direct_operand(condition).build(),
),
operands: vec![
AvmOperand::BRILLIG_LOCATION { brillig_pc: *location as u32 },
make_operand(16, &condition.to_usize()),
],
operands: vec![make_operand(16, &condition.to_usize())],
immediates: vec![AvmOperand::BRILLIG_LOCATION { brillig_pc: *location as u32 }],
..Default::default()
});
}
Expand Down Expand Up @@ -300,7 +297,7 @@ pub fn brillig_to_avm(brillig_bytecode: &[BrilligOpcode<FieldElement>]) -> (Vec<
assert!(location.num_bits() <= 32);
avm_instrs.push(AvmInstruction {
opcode: AvmOpcode::INTERNALCALL,
operands: vec![AvmOperand::BRILLIG_LOCATION { brillig_pc: *location as u32 }],
immediates: vec![AvmOperand::BRILLIG_LOCATION { brillig_pc: *location as u32 }],
..Default::default()
});
}
Expand Down Expand Up @@ -353,8 +350,8 @@ pub fn brillig_to_avm(brillig_bytecode: &[BrilligOpcode<FieldElement>]) -> (Vec<
.into_iter()
.map(|i| match i.opcode {
AvmOpcode::JUMP_32 | AvmOpcode::JUMPI_32 | AvmOpcode::INTERNALCALL => {
let new_operands = i
.operands
let new_immediates = i
.immediates
.into_iter()
.map(|o| match o {
AvmOperand::BRILLIG_LOCATION { brillig_pc } => {
Expand All @@ -365,7 +362,7 @@ pub fn brillig_to_avm(brillig_bytecode: &[BrilligOpcode<FieldElement>]) -> (Vec<
_ => o,
})
.collect::<Vec<AvmOperand>>();
AvmInstruction { operands: new_operands, ..i }
AvmInstruction { immediates: new_immediates, ..i }
}
_ => i,
})
Expand Down Expand Up @@ -832,10 +829,8 @@ fn handle_getter_instruction(
avm_instrs.push(AvmInstruction {
opcode: AvmOpcode::GETENVVAR_16,
indirect: Some(AddressingModeBuilder::default().direct_operand(&dest_offset).build()),
operands: vec![
AvmOperand::U8 { value: var_idx as u8 },
AvmOperand::U16 { value: dest_offset.to_usize() as u16 },
],
operands: vec![AvmOperand::U16 { value: dest_offset.to_usize() as u16 }],
immediates: vec![AvmOperand::U8 { value: var_idx as u8 }],
..Default::default()
});
}
Expand Down Expand Up @@ -882,10 +877,8 @@ fn generate_set_instruction(
Some(AddressingModeBuilder::default().direct_operand(dest).build())
},
tag: Some(tag),
operands: vec![
make_operand(bits_needed_opcode, value),
make_operand(bits_needed_mem, &(dest.to_usize())),
],
operands: vec![make_operand(bits_needed_mem, &(dest.to_usize()))],
immediates: vec![make_operand(bits_needed_opcode, value)],
}
}

Expand Down Expand Up @@ -924,6 +917,7 @@ fn generate_cast_instruction(
make_operand(bits_needed, &(source.to_usize())),
make_operand(bits_needed, &(destination.to_usize())),
],
..Default::default()
}
}

Expand Down Expand Up @@ -1087,14 +1081,16 @@ fn handle_black_box_function(avm_instrs: &mut Vec<AvmInstruction>, operation: &B
.direct_operand(radix)
.build(),
),
tag: None,
operands: vec![
AvmOperand::U16 { value: input_offset as u16 },
AvmOperand::U16 { value: output_offset as u16 },
AvmOperand::U16 { value: radix_offset as u16 },
],
immediates: vec![
AvmOperand::U16 { value: num_limbs as u16 },
AvmOperand::U8 { value: *output_bits as u8 },
],
..Default::default()
});
}
// This will be changed to utilise relative memory offsets
Expand Down Expand Up @@ -1202,10 +1198,10 @@ fn handle_debug_log(
),
operands: vec![
AvmOperand::U16 { value: message_offset.to_usize() as u16 },
AvmOperand::U16 { value: message_size as u16 },
AvmOperand::U16 { value: fields_offset_ptr.to_usize() as u16 },
AvmOperand::U16 { value: fields_size_ptr.to_usize() as u16 },
],
immediates: vec![AvmOperand::U16 { value: message_size as u16 }],
..Default::default()
});
}
Expand Down Expand Up @@ -1462,11 +1458,11 @@ fn handle_get_contract_instance(
.build(),
),
operands: vec![
AvmOperand::U8 { value: member_idx as u8 },
AvmOperand::U16 { value: address_offset.to_usize() as u16 },
AvmOperand::U16 { value: dest_offset.to_usize() as u16 },
AvmOperand::U16 { value: exists_offset.to_usize() as u16 },
],
immediates: vec![AvmOperand::U8 { value: member_idx as u8 }],
..Default::default()
});
}
Expand Down
Loading
Loading