Skip to content

Commit db4a336

Browse files
authored
Support for the same geth binary as WOMIR (#113)
* Managing the stack from the JAAF family instructions. * Code reuse. * Frame allocator that can track sparse allocated ranges. * Persisting WomController across segments. * Carrying across segments only the values in active frames. * Fixing memory reset. * Sharing FP. * Also saving newly allocated frames. * Adapting the tests to use AllocateFrame. * Commented out stack print. * 32-bit offset in mem ops * Emitting trap on float instructions. Instead of panicking. * Added test program. * Small things.
1 parent d529fc0 commit db4a336

File tree

5 files changed

+103
-108
lines changed

5 files changed

+103
-108
lines changed

extensions/circuit/src/adapters/loadstore.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ pub struct Rv32LoadStoreReadRecord<F: Field> {
116116
/// read from mememory for loads
117117
pub mem_read: Option<RecordId>,
118118
pub rs1_ptr: F,
119-
pub imm: F,
120-
pub imm_sign: F,
119+
pub imm_hi: F,
120+
pub imm_lo: F,
121121
pub mem_as: F,
122122
pub mem_ptr_limbs: [u32; 2],
123123
pub shift_amount: u32,
@@ -384,9 +384,9 @@ impl<F: PrimeField32> VmAdapterChipWom<F> for Rv32LoadStoreAdapterChip<F> {
384384
let rs1_record = wom.read::<RV32_REGISTER_NUM_LIMBS>(b + fp_f);
385385

386386
let rs1_val = compose(rs1_record.1);
387-
let imm = c.as_canonical_u32();
388-
let imm_sign = g.as_canonical_u32();
389-
let imm_extended = imm + imm_sign * 0xff000000;
387+
let imm_lo = c.as_canonical_u32();
388+
let imm_hi = g.as_canonical_u32();
389+
let imm_extended = imm_lo | imm_hi << 16;
390390

391391
let ptr_val = rs1_val.wrapping_add(imm_extended);
392392
let shift_amount = ptr_val % 4;
@@ -429,8 +429,8 @@ impl<F: PrimeField32> VmAdapterChipWom<F> for Rv32LoadStoreAdapterChip<F> {
429429
rs1_ptr: b,
430430
wom_read,
431431
mem_read,
432-
imm: c,
433-
imm_sign: g,
432+
imm_lo: c,
433+
imm_hi: g,
434434
shift_amount,
435435
mem_ptr_limbs,
436436
mem_as: e,

integration/src/instruction_builder.rs

Lines changed: 33 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -503,146 +503,122 @@ pub fn jump_if_zero<F: PrimeField32>(condition_reg: usize, to_pc_imm: usize) ->
503503

504504
/// LOADW instruction: Load word from memory
505505
/// rd = MEM[rs1 + imm]
506-
pub fn loadw<F: PrimeField32>(rd: usize, rs1: usize, imm: i32) -> Instruction<F> {
507-
let imm_unsigned = (imm & 0xFFFFFF) as usize;
508-
let imm_sign = if imm < 0 { 1 } else { 0 };
509-
506+
pub fn loadw<F: PrimeField32>(rd: usize, rs1: usize, imm: u32) -> Instruction<F> {
510507
Instruction::new(
511508
LoadStoreOpcode::LOADW.global_opcode(),
512509
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rd), // a: rd
513510
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rs1), // b: rs1
514-
F::from_canonical_usize(imm_unsigned), // c: imm (lower 24 bits)
511+
F::from_canonical_u32(imm & 0xFFFF), // c: imm (lower 16 bits)
515512
F::ONE, // d: register address space
516513
F::from_canonical_usize(2), // e: memory address space (2 for word)
517514
F::ONE, // f: enabled
518-
F::from_canonical_usize(imm_sign), // g: imm sign
515+
F::from_canonical_u32(imm >> 16), // g: imm (higher 16 bits)
519516
)
520517
}
521518

522519
/// STOREW instruction: Store word to memory
523520
/// MEM[rs1 + imm] = rs2
524-
pub fn storew<F: PrimeField32>(value: usize, base_address: usize, imm: i32) -> Instruction<F> {
525-
let imm_unsigned = (imm & 0xFFFFFF) as usize;
526-
let imm_sign = if imm < 0 { 1 } else { 0 };
527-
521+
pub fn storew<F: PrimeField32>(value: usize, base_address: usize, imm: u32) -> Instruction<F> {
528522
Instruction::new(
529523
LoadStoreOpcode::STOREW.global_opcode(),
530524
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * value), // a: rs2 (data to store)
531525
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * base_address), // b: rs1 (base address)
532-
F::from_canonical_usize(imm_unsigned), // c: imm (offset)
533-
F::ONE, // d: register address space
534-
F::from_canonical_usize(2), // e: memory address space (2 for word, same as LOADW)
535-
F::ONE, // f: enabled
536-
F::from_canonical_usize(imm_sign), // g: imm sign
526+
F::from_canonical_u32(imm & 0xFFFF), // c: imm (lower 16 bits)
527+
F::ONE, // d: register address space
528+
F::from_canonical_usize(2), // e: memory address space (2 for word)
529+
F::ONE, // f: enabled
530+
F::from_canonical_u32(imm >> 16), // g: imm (higher 16 bits)
537531
)
538532
}
539533

540534
/// LOADB: load byte from memory
541535
/// rd = MEM[rs1 + imm] (sign-extended)
542-
pub fn loadb<F: PrimeField32>(rd: usize, rs1: usize, imm: i32) -> Instruction<F> {
543-
let imm_unsigned = (imm & 0xFFFFFF) as usize;
544-
let imm_sign = if imm < 0 { 1 } else { 0 };
545-
536+
pub fn loadb<F: PrimeField32>(rd: usize, rs1: usize, imm: u32) -> Instruction<F> {
546537
Instruction::new(
547538
LoadStoreOpcode::LOADB.global_opcode(),
548539
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rd), // a: rd
549540
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rs1), // b: rs1
550-
F::from_canonical_usize(imm_unsigned), // c: imm (lower 24 bits)
541+
F::from_canonical_u32(imm & 0xFFFF), // c: imm (lower 16 bits)
551542
F::ONE, // d: register address space
552-
F::from_canonical_usize(2), // e: memory address space (2 for byte, same as word)
543+
F::from_canonical_usize(2), // e: memory address space (2 for byte)
553544
F::ONE, // f: enabled
554-
F::from_canonical_usize(imm_sign), // g: imm sign
545+
F::from_canonical_u32(imm >> 16), // g: imm (higher 16 bits)
555546
)
556547
}
557548

558549
/// LOADBU instruction: Load byte unsigned from memory
559550
/// rd = MEM[rs1 + imm] (zero-extended)
560-
pub fn loadbu<F: PrimeField32>(rd: usize, rs1: usize, imm: i32) -> Instruction<F> {
561-
let imm_unsigned = (imm & 0xFFFFFF) as usize;
562-
let imm_sign = if imm < 0 { 1 } else { 0 };
563-
551+
pub fn loadbu<F: PrimeField32>(rd: usize, rs1: usize, imm: u32) -> Instruction<F> {
564552
Instruction::new(
565553
LoadStoreOpcode::LOADBU.global_opcode(),
566554
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rd), // a: rd
567555
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rs1), // b: rs1
568-
F::from_canonical_usize(imm_unsigned), // c: imm (lower 24 bits)
556+
F::from_canonical_u32(imm & 0xFFFF), // c: imm (lower 16 bits)
569557
F::ONE, // d: register address space
570-
F::from_canonical_usize(2), // e: memory address space (2 for byte, same as word)
558+
F::from_canonical_usize(2), // e: memory address space (2 for byte)
571559
F::ONE, // f: enabled
572-
F::from_canonical_usize(imm_sign), // g: imm sign
560+
F::from_canonical_u32(imm >> 16), // g: imm (higher 16 bits)
573561
)
574562
}
575563

576564
/// LOADH: load halfword from memory
577565
/// rd = MEM[rs1 + imm] (sign-extended)
578566
#[allow(unused)]
579-
pub fn loadh<F: PrimeField32>(rd: usize, rs1: usize, imm: i32) -> Instruction<F> {
580-
let imm_unsigned = (imm & 0xFFFFFF) as usize;
581-
let imm_sign = if imm < 0 { 1 } else { 0 };
582-
567+
pub fn loadh<F: PrimeField32>(rd: usize, rs1: usize, imm: u32) -> Instruction<F> {
583568
Instruction::new(
584569
LoadStoreOpcode::LOADH.global_opcode(),
585570
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rd), // a: rd
586571
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rs1), // b: rs1
587-
F::from_canonical_usize(imm_unsigned), // c: imm (lower 24 bits)
572+
F::from_canonical_u32(imm & 0xFFFF), // c: imm (lower 16 bits)
588573
F::ONE, // d: register address space
589-
F::from_canonical_usize(2), // e: memory address space (2 for byte, same as word)
574+
F::from_canonical_usize(2), // e: memory address space (2 for halfword)
590575
F::ONE, // f: enabled
591-
F::from_canonical_usize(imm_sign), // g: imm sign
576+
F::from_canonical_u32(imm >> 16), // g: imm (higher 16 bits)
592577
)
593578
}
594579

595580
/// LOADHU instruction: Load halfword unsigned from memory
596581
/// rd = MEM[rs1 + imm] (zero-extended)
597-
pub fn loadhu<F: PrimeField32>(rd: usize, rs1: usize, imm: i32) -> Instruction<F> {
598-
let imm_unsigned = (imm & 0xFFFFFF) as usize;
599-
let imm_sign = if imm < 0 { 1 } else { 0 };
600-
582+
pub fn loadhu<F: PrimeField32>(rd: usize, rs1: usize, imm: u32) -> Instruction<F> {
601583
Instruction::new(
602584
LoadStoreOpcode::LOADHU.global_opcode(),
603585
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rd), // a: rd
604586
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rs1), // b: rs1
605-
F::from_canonical_usize(imm_unsigned), // c: imm (lower 24 bits)
587+
F::from_canonical_u32(imm & 0xFFFF), // c: imm (lower 16 bits)
606588
F::ONE, // d: register address space
607-
F::from_canonical_usize(2), // e: memory address space (2 for halfword, same as word)
589+
F::from_canonical_usize(2), // e: memory address space (2 for halfword)
608590
F::ONE, // f: enabled
609-
F::from_canonical_usize(imm_sign), // g: imm sign
591+
F::from_canonical_u32(imm >> 16), // g: imm (higher 16 bits)
610592
)
611593
}
612594

613595
/// STOREB instruction: Store byte to memory
614596
/// MEM[rs1 + imm] = rs2 (lowest byte)
615-
pub fn storeb<F: PrimeField32>(rs2: usize, rs1: usize, imm: i32) -> Instruction<F> {
616-
let imm_unsigned = (imm & 0xFFFFFF) as usize;
617-
let imm_sign = if imm < 0 { 1 } else { 0 };
618-
597+
pub fn storeb<F: PrimeField32>(rs2: usize, rs1: usize, imm: u32) -> Instruction<F> {
619598
Instruction::new(
620599
LoadStoreOpcode::STOREB.global_opcode(),
621600
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rs2), // a: rs2 (data to store)
622601
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rs1), // b: rs1 (base address)
623-
F::from_canonical_usize(imm_unsigned), // c: imm (offset)
602+
F::from_canonical_u32(imm & 0xFFFF), // c: imm (lower 16 bits)
624603
F::ONE, // d: register address space
625-
F::from_canonical_usize(2), // e: memory address space (2 for byte, same as word)
604+
F::from_canonical_usize(2), // e: memory address space (2 for byte)
626605
F::ONE, // f: enabled
627-
F::from_canonical_usize(imm_sign), // g: imm sign
606+
F::from_canonical_u32(imm >> 16), // g: imm (higher 16 bits)
628607
)
629608
}
630609

631610
/// STOREH instruction: Store halfword to memory
632611
/// MEM[rs1 + imm] = rs2 (lowest halfword)
633-
pub fn storeh<F: PrimeField32>(rs2: usize, rs1: usize, imm: i32) -> Instruction<F> {
634-
let imm_unsigned = (imm & 0xFFFFFF) as usize;
635-
let imm_sign = if imm < 0 { 1 } else { 0 };
636-
612+
pub fn storeh<F: PrimeField32>(rs2: usize, rs1: usize, imm: u32) -> Instruction<F> {
637613
Instruction::new(
638614
LoadStoreOpcode::STOREH.global_opcode(),
639615
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rs2), // a: rs2 (data to store)
640616
F::from_canonical_usize(riscv::RV32_REGISTER_NUM_LIMBS * rs1), // b: rs1 (base address)
641-
F::from_canonical_usize(imm_unsigned), // c: imm (offset)
617+
F::from_canonical_u32(imm & 0xFFFF), // c: imm (lower 16 bits)
642618
F::ONE, // d: register address space
643-
F::from_canonical_usize(2), // e: memory address space (2 for halfword, same as word)
619+
F::from_canonical_usize(2), // e: memory address space (2 for halfword)
644620
F::ONE, // f: enabled
645-
F::from_canonical_usize(imm_sign), // g: imm sign
621+
F::from_canonical_u32(imm >> 16), // g: imm (higher 16 bits)
646622
)
647623
}
648624

integration/src/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2142,6 +2142,15 @@ mod wast_tests {
21422142
run_single_wasm_test("../sample-programs/keccak.wasm", "main", &[0, 0], &[]).unwrap()
21432143
}
21442144

2145+
#[test]
2146+
fn test_keeper_js() {
2147+
// This is program is a stripped down version of geth, compiled for Go's js target.
2148+
// Source: https://github.com/ethereum/go-ethereum/tree/master/cmd/keeper
2149+
// Compile command:
2150+
// GOOS=js GOARCH=wasm go -gcflags=all=-d=softfloat build -tags "example" -o keeper.wasm
2151+
run_single_wasm_test("../sample-programs/keeper_js.wasm", "run", &[0, 0], &[]).unwrap();
2152+
}
2153+
21452154
#[test]
21462155
fn test_keccak_rust_womir() {
21472156
run_womir_guest(

integration/src/womir_translation.rs

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ pub const ERROR_CODE_OFFSET: u32 = 100;
5454

5555
pub const ERROR_ABORT_CODE: u32 = 200;
5656

57+
/// Error code emitted in place of unimplemented instructions.
58+
/// This is to allow programs that has such instruction to compile,
59+
/// but the runtime error only happens if they are actually executed.
60+
pub const ERROR_UNIMPLEMENTED_CODE: u32 = 201;
61+
5762
pub struct LinkedProgram<'a, F: PrimeField32> {
5863
module: Module<'a>,
5964
label_map: HashMap<String, LabelValue>,
@@ -644,6 +649,10 @@ impl<'a, F: PrimeField32> Settings<'a> for OpenVMSettings<F> {
644649
("env", "abort") => {
645650
vec![Directive::Instruction(ib::abort())]
646651
}
652+
("gojs", _) => {
653+
// Just NOP for GoJS intrinsics
654+
vec![]
655+
}
647656
_ => unimplemented!(
648657
"Imported function `{}` from module `{}` is not supported",
649658
function,
@@ -1174,6 +1183,8 @@ fn translate_complex_ins<F: PrimeField32>(
11741183
})
11751184
.collect_vec();
11761185
match op {
1186+
Op::Nop => Tree::Empty,
1187+
11771188
Op::I32Const { value } => {
11781189
let output = output.unwrap().start as usize;
11791190
let value_u = value as u32;
@@ -1874,10 +1885,6 @@ fn translate_complex_ins<F: PrimeField32>(
18741885
}
18751886

18761887
// Float instructions
1877-
Op::F32Load { memarg: _ } => todo!(),
1878-
Op::F64Load { memarg: _ } => todo!(),
1879-
Op::F32Store { memarg: _ } => todo!(),
1880-
Op::F64Store { memarg: _ } => todo!(),
18811888
Op::F32Const { value } => {
18821889
let output = output.unwrap().start as usize;
18831890
let value_u = value.bits();
@@ -1900,38 +1907,44 @@ fn translate_complex_ins<F: PrimeField32>(
19001907
]
19011908
.into()
19021909
}
1903-
Op::F32Abs => todo!(),
1904-
Op::F32Neg => todo!(),
1905-
Op::F32Ceil => todo!(),
1906-
Op::F32Floor => todo!(),
1907-
Op::F32Trunc => todo!(),
1908-
Op::F32Nearest => todo!(),
1909-
Op::F32Sqrt => todo!(),
1910-
Op::F64Abs => todo!(),
1911-
Op::F64Neg => todo!(),
1912-
Op::F64Ceil => todo!(),
1913-
Op::F64Floor => todo!(),
1914-
Op::F64Trunc => todo!(),
1915-
Op::F64Nearest => todo!(),
1916-
Op::F64Sqrt => todo!(),
1917-
Op::I32TruncF32S => todo!(),
1918-
Op::I32TruncF32U => todo!(),
1919-
Op::I32TruncF64S => todo!(),
1920-
Op::I32TruncF64U => todo!(),
1921-
Op::I64TruncF32S => todo!(),
1922-
Op::I64TruncF32U => todo!(),
1923-
Op::I64TruncF64S => todo!(),
1924-
Op::I64TruncF64U => todo!(),
1925-
Op::F32ConvertI32S => todo!(),
1926-
Op::F32ConvertI32U => todo!(),
1927-
Op::F32ConvertI64S => todo!(),
1928-
Op::F32ConvertI64U => todo!(),
1929-
Op::F32DemoteF64 => todo!(),
1930-
Op::F64ConvertI32S => todo!(),
1931-
Op::F64ConvertI32U => todo!(),
1932-
Op::F64ConvertI64S => todo!(),
1933-
Op::F64ConvertI64U => todo!(),
1934-
Op::F64PromoteF32 => todo!(),
1910+
Op::F32Load { .. }
1911+
| Op::F64Load { .. }
1912+
| Op::F32Store { .. }
1913+
| Op::F64Store { .. }
1914+
| Op::F32Abs
1915+
| Op::F32Neg
1916+
| Op::F32Ceil
1917+
| Op::F32Floor
1918+
| Op::F32Trunc
1919+
| Op::F32Nearest
1920+
| Op::F32Sqrt
1921+
| Op::F64Abs
1922+
| Op::F64Neg
1923+
| Op::F64Ceil
1924+
| Op::F64Floor
1925+
| Op::F64Trunc
1926+
| Op::F64Nearest
1927+
| Op::F64Sqrt
1928+
| Op::I32TruncF32S
1929+
| Op::I32TruncF32U
1930+
| Op::I32TruncF64S
1931+
| Op::I32TruncF64U
1932+
| Op::I64TruncF32S
1933+
| Op::I64TruncF32U
1934+
| Op::I64TruncF64S
1935+
| Op::I64TruncF64U
1936+
| Op::F32ConvertI32S
1937+
| Op::F32ConvertI32U
1938+
| Op::F32ConvertI64S
1939+
| Op::F32ConvertI64U
1940+
| Op::F32DemoteF64
1941+
| Op::F64ConvertI32S
1942+
| Op::F64ConvertI32U
1943+
| Op::F64ConvertI64S
1944+
| Op::F64ConvertI64U
1945+
| Op::F64PromoteF32 => {
1946+
Directive::Instruction(ib::trap(ERROR_UNIMPLEMENTED_CODE as usize)).into()
1947+
}
19351948

19361949
Op::I32ReinterpretF32
19371950
| Op::F32ReinterpretI32
@@ -1998,7 +2011,7 @@ fn emit_table_get<F: PrimeField32>(
19982011
Directive::Instruction(ib::loadw(
19992012
dest_reg as usize,
20002013
mul_result,
2001-
base_addr as i32 + (i as i32) * 4,
2014+
base_addr + (i as u32) * 4,
20022015
))
20032016
}));
20042017

@@ -2197,16 +2210,13 @@ fn const_i16_as_field(value: &WasmValue) -> AluImm {
21972210
c.into()
21982211
}
21992212

2200-
fn mem_offset<F: PrimeField32>(memarg: MemArg, c: &Ctx<F>) -> i32 {
2213+
fn mem_offset<F: PrimeField32>(memarg: MemArg, c: &Ctx<F>) -> u32 {
22012214
assert_eq!(memarg.memory, 0, "no multiple memories supported");
22022215
let mem_start = c
22032216
.program
22042217
.linear_memory_start()
22052218
.expect("no memory allocated");
2206-
let offset = mem_start + u32::try_from(memarg.offset).expect("offset too large");
2207-
// RISC-V requires offset immediates to have 16 bits, but for WASM we changed it to 24 bits.
2208-
assert!(offset < (1 << 24));
2209-
offset as i32
2219+
mem_start.wrapping_add(u32::try_from(memarg.offset).expect("offset too large"))
22102220
}
22112221

22122222
fn load_from_const_addr<F: PrimeField32>(
@@ -2225,7 +2235,7 @@ fn load_from_const_addr<F: PrimeField32>(
22252235
Directive::Instruction(ib::loadw(
22262236
dest_reg as usize,
22272237
base_addr_reg.start as usize,
2228-
(i as i32) * 4,
2238+
(i as u32) * 4,
22292239
))
22302240
}));
22312241

@@ -2248,7 +2258,7 @@ fn store_to_const_addr<F: PrimeField32>(
22482258
Directive::Instruction(ib::storew(
22492259
input_reg as usize,
22502260
base_addr_reg.start as usize,
2251-
i as i32 * 4,
2261+
i as u32 * 4,
22522262
))
22532263
}));
22542264

sample-programs/keeper_js.wasm

14.8 MB
Binary file not shown.

0 commit comments

Comments
 (0)