Skip to content

Commit

Permalink
feat(avm-simulator): add to_radix_le instruction (#6308)
Browse files Browse the repository at this point in the history
  • Loading branch information
IlyasRidhuan committed May 10, 2024
1 parent 8cf9168 commit 6374a32
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 69 deletions.
4 changes: 4 additions & 0 deletions avm-transpiler/src/opcodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ pub enum AvmOpcode {
POSEIDON2,
SHA256, // temp - may be removed, but alot of contracts rely on it
PEDERSEN, // temp - may be removed, but alot of contracts rely on it
// Conversions
TORADIXLE,
}

impl AvmOpcode {
Expand Down Expand Up @@ -155,6 +157,8 @@ impl AvmOpcode {
AvmOpcode::POSEIDON2 => "POSEIDON2",
AvmOpcode::SHA256 => "SHA256 ",
AvmOpcode::PEDERSEN => "PEDERSEN",
// Conversions
AvmOpcode::TORADIXLE => "TORADIXLE",
}
}
}
3 changes: 3 additions & 0 deletions barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ enum class OpCode : uint8_t {
KECCAK,
POSEIDON2,

// Conversions
TORADIXLE,

// Sentinel
LAST_OPCODE_SENTINEL,
};
Expand Down
185 changes: 116 additions & 69 deletions docs/docs/protocol-specs/public-vm/gen/_instruction-set.mdx

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions docs/src/preprocess/InstructionSet/InstructionSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,39 @@ halt
"Tag checks": "",
"Tag updates": "",
},
{
id: "to_radix_le",
Name: "`TORADIXLE`",
Category: "Conversions",
Flags: [{ name: "indirect", description: INDIRECT_FLAG_DESCRIPTION }],
Args: [
{
name: "srcOffset",
description: "memory offset of word to convert.",
},
{
name: "dstOffset",
description: "memory offset specifying where the first limb of the radix-conversion result is stored.",
},
{
name: "radix",
description: "the maximum bit-size of each limb.",
mode: "immediate",
type: "u32",
},
{
name: "numLimbs",
description: "the number of limbs the word will be converted into.",
type: "u32",
mode: "immediate",
}
],

Expression: `TBD: Storage of limbs and if T[dstOffset] is constrained to U8`,
Summary: "Convert a word to an array of limbs in little-endian radix form",
Details: "The limbs will be stored in a contiguous memory block starting at `dstOffset`.",
"Tag checks": "`T[srcOffset] == field`",
}
];
const INSTRUCTION_SET = INSTRUCTION_SET_RAW.map((instr) => {
instr["Bit-size"] = instructionSize(instr);
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/simulator/src/avm/avm_gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ export const GasCosts: Record<Opcode, Gas | typeof DynamicGasCost> = {
[Opcode.POSEIDON2]: TemporaryDefaultGasCost,
[Opcode.SHA256]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,
[Opcode.PEDERSEN]: TemporaryDefaultGasCost, // temp - may be removed, but alot of contracts rely on i: TemporaryDefaultGasCost,t
// Conversions
[Opcode.TORADIXLE]: TemporaryDefaultGasCost,
};

/** Returns the fixed base gas cost for a given opcode, or throws if set to dynamic. */
Expand Down
90 changes: 90 additions & 0 deletions yarn-project/simulator/src/avm/opcodes/conversion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { type AvmContext } from '../avm_context.js';
import { Field, type Uint8, Uint32 } from '../avm_memory_types.js';
import { initContext } from '../fixtures/index.js';
import { Addressing, AddressingMode } from './addressing_mode.js';
import { ToRadixLE } from './conversion.js';

describe('Conversion Opcodes', () => {
let context: AvmContext;

beforeEach(async () => {
context = initContext();
});

describe('To Radix LE', () => {
it('Should (de)serialize correctly', () => {
const buf = Buffer.from([
ToRadixLE.opcode, // opcode
1, // indirect
...Buffer.from('12345678', 'hex'), // inputStateOffset
...Buffer.from('23456789', 'hex'), // outputStateOffset
...Buffer.from('00000002', 'hex'), // radix
...Buffer.from('00000100', 'hex'), // numLimbs
]);
const inst = new ToRadixLE(
/*indirect=*/ 1,
/*srcOffset=*/ 0x12345678,
/*dstOffset=*/ 0x23456789,
/*radix=*/ 2,
/*numLimbs=*/ 256,
);

expect(ToRadixLE.deserialize(buf)).toEqual(inst);
expect(inst.serialize()).toEqual(buf);
});

it('Should decompose correctly - direct', async () => {
const arg = new Field(0b1011101010100n);
const indirect = 0;
const srcOffset = 0;
const radix = 2; // Bit decomposition
const numLimbs = 10; // only the first 10 bits
const dstOffset = 20;
context.machineState.memory.set(srcOffset, arg);

await new ToRadixLE(indirect, srcOffset, dstOffset, radix, numLimbs).execute(context);

const resultBuffer: Buffer = Buffer.concat(
context.machineState.memory.getSliceAs<Uint8>(dstOffset, numLimbs).map(byte => byte.toBuffer()),
);
// The expected result is the first 10 bits of the input, reversed
const expectedResults = '1011101010100'.split('').reverse().slice(0, numLimbs).map(Number);
for (let i = 0; i < numLimbs; i++) {
expect(resultBuffer.readUInt8(i)).toEqual(expectedResults[i]);
}
});

it('Should decompose correctly - indirect', async () => {
const arg = new Field(Buffer.from('1234567890abcdef', 'hex'));
const indirect = new Addressing([
/*srcOffset=*/ AddressingMode.INDIRECT,
/*dstOffset*/ AddressingMode.INDIRECT,
]).toWire();
const srcOffset = 0;
const srcOffsetReal = 10;
const dstOffset = 2;
const dstOffsetReal = 30;
context.machineState.memory.set(srcOffset, new Uint32(srcOffsetReal));
context.machineState.memory.set(dstOffset, new Uint32(dstOffsetReal));
context.machineState.memory.set(srcOffsetReal, arg);

const radix = 1 << 8; // Byte decomposition
const numLimbs = 32; // 256-bit decomposition
await new ToRadixLE(indirect, srcOffset, dstOffset, radix, numLimbs).execute(context);

const resultBuffer: Buffer = Buffer.concat(
context.machineState.memory.getSliceAs<Uint8>(dstOffsetReal, numLimbs).map(byte => byte.toBuffer()),
);
// The expected result is the input (padded to 256 bits),and reversed
const expectedResults = '1234567890abcdef'
.padStart(64, '0')
.split('')
.reverse()
.map(a => parseInt(a, 16));
// Checking the value in each byte of the buffer is correct
for (let i = 0; i < numLimbs; i++) {
expect(resultBuffer.readUInt8(i)).toEqual(expectedResults[2 * i] + expectedResults[2 * i + 1] * 16);
}
});
});
});
58 changes: 58 additions & 0 deletions yarn-project/simulator/src/avm/opcodes/conversion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { assert } from '../../../../foundation/src/json-rpc/js_utils.js';
import { type AvmContext } from '../avm_context.js';
import { TypeTag, Uint8 } from '../avm_memory_types.js';
import { Opcode, OperandType } from '../serialization/instruction_serialization.js';
import { Addressing } from './addressing_mode.js';
import { Instruction } from './instruction.js';

export class ToRadixLE extends Instruction {
static type: string = 'TORADIXLE';
static readonly opcode: Opcode = Opcode.TORADIXLE;

// Informs (de)serialization. See Instruction.deserialize.
static readonly wireFormat: OperandType[] = [
OperandType.UINT8, // Opcode
OperandType.UINT8, // Indirect
OperandType.UINT32, // src memory address
OperandType.UINT32, // dst memory address
OperandType.UINT32, // radix (immediate)
OperandType.UINT32, // number of limbs (Immediate)
];

constructor(
private indirect: number,
private srcOffset: number,
private dstOffset: number,
private radix: number,
private numLimbs: number,
) {
assert(radix <= 256, 'Radix cannot be greater than 256');
super();
}

public async execute(context: AvmContext): Promise<void> {
const memory = context.machineState.memory.track(this.type);
const [srcOffset, dstOffset] = Addressing.fromWire(this.indirect).resolve([this.srcOffset, this.dstOffset], memory);
const memoryOperations = { reads: 1, writes: this.numLimbs, indirect: this.indirect };
context.machineState.consumeGas(this.gasCost(memoryOperations));

// The radix gadget only takes in a Field
memory.checkTag(TypeTag.FIELD, srcOffset);

let value: bigint = memory.get(srcOffset).toBigInt();
const radixBN: bigint = BigInt(this.radix);
const limbArray = [];

for (let i = 0; i < this.numLimbs; i++) {
const limb = value % radixBN;
limbArray.push(limb);
value /= radixBN;
}

const res = [...limbArray].map(byte => new Uint8(byte));
memory.setSlice(dstOffset, res);

memory.assert(memoryOperations);
context.machineState.incrementPc();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ export enum Opcode {
POSEIDON2,
SHA256, // temp - may be removed, but alot of contracts rely on it
PEDERSEN, // temp - may be removed, but alot of contracts rely on it
// Conversion
TORADIXLE,
}

// Possible types for an instruction's operand in its wire format. (Keep in sync with CPP code.
Expand Down

0 comments on commit 6374a32

Please sign in to comment.