1use crate::mir::IMMUTABLE_WORD_SIZE;
9use alloy_primitives::U256;
10use solar_config::{EvmVersion, OptimizationMode};
11use solar_data_structures::map::FxHashMap;
12
13const EVM_WORD_BYTES: usize = 32;
14const EVM_WORD_BITS: usize = EVM_WORD_BYTES * 8;
15const MIN_COMPACT_MASK_WIDTH: u8 = EVM_WORD_BYTES as u8 / 2;
16
17mod id_counter;
18use id_counter::IdCounter;
19
20mod inst;
21pub(super) use inst::{AsmInst, AsmInstKind, PushValueId};
22pub use inst::{DeferredConst, Label};
23
24mod local_interner;
25use local_interner::LocalInterner;
26
27mod program;
28pub(in crate::backend::evm) use program::{
29 EvmAsmProgram, StructuredAsmContext, StructuredAsmProgram,
30};
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub struct ImmutableRef {
38 pub id: u32,
40 pub code_offset: usize,
43}
44
45#[derive(Debug)]
47pub struct AssembledCode {
48 pub bytecode: Vec<u8>,
50 pub label_offsets: FxHashMap<Label, usize>,
52 pub immutable_refs: Vec<ImmutableRef>,
54}
55
56#[derive(Clone, Copy, Debug, Default)]
58pub struct AssemblerConfig {
59 pub evm_version: EvmVersion,
61 pub optimization: OptimizationMode,
63 pub evm_ir_stack_schedule: bool,
68 pub evm_ir_layout_passes: bool,
73}
74
75#[derive(Debug)]
77pub struct Assembler {
78 config: AssemblerConfig,
80 pub(in crate::backend::evm) program: StructuredAsmProgram,
82 push_values: LocalInterner<U256, PushValueId>,
84 next_label: IdCounter<Label>,
86 next_deferred: IdCounter<DeferredConst>,
88 deferred_values: FxHashMap<DeferredConst, U256>,
90}
91
92impl Assembler {
93 #[must_use]
95 pub fn new() -> Self {
96 Self::with_config(AssemblerConfig::default())
97 }
98
99 #[must_use]
101 pub fn with_config(config: AssemblerConfig) -> Self {
102 Self {
103 config,
104 program: StructuredAsmProgram::default(),
105 push_values: LocalInterner::new(),
106 next_label: IdCounter::new(),
107 next_deferred: IdCounter::new(),
108 deferred_values: FxHashMap::default(),
109 }
110 }
111
112 pub fn clear(&mut self) {
114 self.program.clear();
115 self.push_values.clear();
116 self.next_label.clear();
117 self.next_deferred.clear();
118 self.deferred_values.clear();
119 }
120
121 pub fn new_label(&mut self) -> Label {
123 self.next_label.next()
124 }
125
126 pub fn new_deferred_const(&mut self) -> DeferredConst {
128 self.next_deferred.next()
129 }
130
131 pub fn emit_op(&mut self, opcode: u8) {
133 self.program.push(AsmInst::op(opcode));
134 }
135
136 pub fn emit_push(&mut self, value: U256) {
138 let inst = self.push_inst(value);
139 self.program.push(inst);
140 }
141
142 pub fn emit_push_label(&mut self, label: Label) {
144 self.program.push(AsmInst::push_label(label));
145 }
146
147 pub fn emit_push_deferred(&mut self, id: DeferredConst) {
149 self.program.push(AsmInst::push_deferred(id));
150 }
151
152 pub fn set_deferred_const(&mut self, id: DeferredConst, value: U256) {
154 self.deferred_values.insert(id, value);
155 }
156
157 pub fn emit_push_immutable(&mut self, id: u32) {
159 self.program.push(AsmInst::push_immutable(id));
160 }
161
162 pub fn define_label(&mut self, label: Label) {
164 self.program.define_label(label);
165 }
166
167 pub(in crate::backend::evm) fn mark_label_cold(&mut self, label: Label) {
169 self.program.mark_cold(label);
170 }
171
172 fn push_inst(&mut self, value: U256) -> AsmInst {
173 if let Ok(value) = u32::try_from(value)
174 && let Some(inst) = AsmInst::push_inline(value)
175 {
176 return inst;
177 }
178
179 AsmInst::push(self.push_values.intern(value))
180 }
181
182 pub(super) fn push_value(&self, index: PushValueId) -> U256 {
183 *self.push_values.get(index)
184 }
185
186 pub(super) fn inst_push_value(&self, inst: AsmInst) -> Option<U256> {
187 match inst.kind() {
188 AsmInstKind::PushInline(value) => Some(U256::from(value)),
189 AsmInstKind::Push(index) => Some(self.push_value(index)),
190 _ => None,
191 }
192 }
193
194 #[must_use]
197 pub fn assemble(&mut self) -> AssembledCode {
198 let mut ir_program = std::mem::take(&mut self.program);
199 let deferred_values = &self.deferred_values;
200 let push_values = &mut self.push_values;
201 ir_program.resolve_deferred_consts(|id| {
202 let value = deferred_values
203 .get(&id)
204 .copied()
205 .unwrap_or_else(|| panic!("deferred constant {id:?} was never resolved"));
206 if let Ok(value) = u32::try_from(value)
207 && let Some(inst) = AsmInst::push_inline(value)
208 {
209 return inst;
210 }
211 AsmInst::push(push_values.intern(value))
212 });
213 if self.config.evm_ir_stack_schedule || self.config.evm_ir_layout_passes {
214 ir_program.optimize_with_evm_ir(self);
215 }
216 let mut program = ir_program.to_asm_program();
217 self.run_assembler_passes(&mut program);
218
219 let mut push_widths: FxHashMap<usize, u8> = FxHashMap::default();
221
222 for (idx, inst) in program.instructions.iter().enumerate() {
224 if matches!(inst.kind(), AsmInstKind::PushLabel(_)) {
225 push_widths.insert(idx, 2);
226 }
227 }
228
229 let max_iterations = 10;
231 for _ in 0..max_iterations {
232 let (label_offsets, new_widths) = self.compute_offsets(&program, &push_widths);
233
234 let mut changed = false;
235 for (idx, &width) in &new_widths {
236 if push_widths.get(idx) != Some(&width) {
237 changed = true;
238 }
239 }
240
241 if !changed {
242 let result = self.emit_bytecode(&program, label_offsets, &push_widths);
244 self.clear();
245 return result;
246 }
247
248 for (idx, width) in new_widths {
249 push_widths.insert(idx, width);
250 }
251 }
252
253 let (label_offsets, _) = self.compute_offsets(&program, &push_widths);
255 let result = self.emit_bytecode(&program, label_offsets, &push_widths);
256 self.clear();
257 result
258 }
259
260 fn compute_offsets(
262 &self,
263 program: &EvmAsmProgram,
264 push_widths: &FxHashMap<usize, u8>,
265 ) -> (FxHashMap<Label, usize>, FxHashMap<usize, u8>) {
266 let mut offset = 0usize;
267 let mut label_offsets = FxHashMap::default();
268 let mut new_widths = FxHashMap::default();
269 let out = BytecodeAssembler::new(self.config);
270
271 for (idx, inst) in program.instructions.iter().enumerate() {
272 match inst.kind() {
273 AsmInstKind::Op(_) => {
274 offset += 1;
275 }
276 AsmInstKind::PushInline(value) => {
277 offset += out.encoded_push_len(U256::from(value));
278 }
279 AsmInstKind::Push(index) => {
280 offset += out.encoded_push_len(self.push_value(index));
281 }
282 AsmInstKind::PushLabel(_) => {
283 let width = push_widths.get(&idx).copied().unwrap_or(2);
285 offset += out.fixed_push_len(width);
286 }
287 AsmInstKind::PushDeferred(_) => {
288 unreachable!("deferred constants must be resolved before assembly");
289 }
290 AsmInstKind::PushImmutable(_) => {
291 offset += 33;
293 }
294 AsmInstKind::Label(label) => {
295 label_offsets.insert(label, offset);
296 offset += 1;
297 }
298 }
299 }
300
301 for (idx, inst) in program.instructions.iter().enumerate() {
303 if let AsmInstKind::PushLabel(label) = inst.kind()
304 && let Some(&target_offset) = label_offsets.get(&label)
305 {
306 let width = out.push_width(U256::from(target_offset));
307 new_widths.insert(idx, width);
308 }
309 }
310
311 (label_offsets, new_widths)
312 }
313
314 fn emit_bytecode(
316 &self,
317 program: &EvmAsmProgram,
318 label_offsets: FxHashMap<Label, usize>,
319 push_widths: &FxHashMap<usize, u8>,
320 ) -> AssembledCode {
321 let mut out = BytecodeAssembler::new(self.config);
322
323 for (idx, inst) in program.instructions.iter().enumerate() {
324 match inst.kind() {
325 AsmInstKind::Op(opcode) => {
326 out.emit_op(opcode);
327 }
328 AsmInstKind::PushInline(value) => {
329 out.emit_push_value(U256::from(value));
330 }
331 AsmInstKind::Push(index) => {
332 out.emit_push_value(self.push_value(index));
333 }
334 AsmInstKind::PushLabel(label) => {
335 let target_offset = label_offsets
336 .get(&label)
337 .copied()
338 .unwrap_or_else(|| panic!("label {label:?} was never defined"));
339 let width = push_widths.get(&idx).copied().unwrap_or(2);
340 out.emit_push_fixed_width(U256::from(target_offset), width);
341 }
342 AsmInstKind::PushDeferred(_) => {
343 unreachable!("deferred constants must be resolved before assembly");
344 }
345 AsmInstKind::PushImmutable(id) => {
346 out.emit_push_immutable(id);
347 }
348 AsmInstKind::Label(_) => {
349 out.emit_op(op::JUMPDEST);
350 }
351 }
352 }
353
354 out.finish(label_offsets)
355 }
356
357 #[cfg(test)]
359 fn push_width(value: U256) -> u8 {
360 value.byte_len() as u8
361 }
362}
363
364impl Default for Assembler {
365 fn default() -> Self {
366 Self::new()
367 }
368}
369
370impl StructuredAsmContext for Assembler {
371 fn push_value(&self, index: PushValueId) -> U256 {
372 self.push_value(index)
373 }
374
375 fn push_inst(&mut self, value: U256) -> AsmInst {
376 self.push_inst(value)
377 }
378
379 fn new_label(&mut self) -> Label {
380 self.new_label()
381 }
382
383 fn run_evm_ir_stack_schedule(&self) -> bool {
384 self.config.evm_ir_stack_schedule
385 }
386
387 fn run_evm_ir_layout_passes(&self) -> bool {
388 self.config.evm_ir_layout_passes
389 }
390}
391
392#[derive(Debug)]
393struct BytecodeAssembler {
394 config: AssemblerConfig,
395 bytecode: Vec<u8>,
396 immutable_refs: Vec<ImmutableRef>,
397}
398
399impl BytecodeAssembler {
400 fn new(config: AssemblerConfig) -> Self {
401 Self { config, bytecode: Vec::new(), immutable_refs: Vec::new() }
402 }
403
404 fn emit_op(&mut self, opcode: u8) {
405 self.bytecode.push(opcode);
406 }
407
408 fn emit_push_immutable(&mut self, id: u32) {
409 self.immutable_refs.push(ImmutableRef { id, code_offset: self.bytecode.len() });
410 self.bytecode.push(op::PUSH32);
411 self.bytecode.extend(std::iter::repeat_n(0, IMMUTABLE_WORD_SIZE));
412 }
413
414 fn encoded_push_len(&self, value: U256) -> usize {
415 match self.compact_push(value) {
416 CompactPush::Literal { width } => self.fixed_push_len(width),
417 CompactPush::FullWord => self.zero_push_len() + 1,
418 CompactPush::LowerAllOnesMask { .. } => self.zero_push_len() + 4,
419 CompactPush::Not => self.fixed_push_len(self.push_width(!value)) + 1,
420 CompactPush::Shl { shift } => {
421 self.fixed_push_len(self.push_width(value >> usize::from(shift))) + 3
422 }
423 }
424 }
425
426 fn compact_push(&self, value: U256) -> CompactPush {
427 let width = self.push_width(value);
428 let normal_len = self.fixed_push_len(width);
429 let mut best = (normal_len, CompactPush::Literal { width });
430
431 if self.config.optimization == OptimizationMode::None {
432 return best.1;
433 }
434
435 let mut consider = |len: usize, compact: CompactPush| {
436 if len < best.0 {
437 best = (len, compact);
438 }
439 };
440
441 if value == U256::MAX {
442 consider(self.zero_push_len() + 1, CompactPush::FullWord);
443 }
444
445 if width >= MIN_COMPACT_MASK_WIDTH {
451 let bytes = value.to_be_bytes::<EVM_WORD_BYTES>();
452 let start = EVM_WORD_BYTES - width as usize;
453 if bytes[start..].iter().all(|&byte| byte == 0xff) {
454 let shift = EVM_WORD_BITS - usize::from(width) * 8;
455 consider(
456 self.zero_push_len() + 4,
457 CompactPush::LowerAllOnesMask { shift: shift as u8 },
458 );
459 }
460 }
461
462 if width as usize == EVM_WORD_BYTES {
467 let inverted = !value;
468 let inverted_width = self.push_width(inverted);
469 let inverted_len = self.fixed_push_len(inverted_width) + 1;
470 consider(inverted_len, CompactPush::Not);
471 }
472
473 let trailing_zero_bytes = (0..EVM_WORD_BYTES).take_while(|&i| value.byte(i) == 0).count();
478 if trailing_zero_bytes > 0 && trailing_zero_bytes < EVM_WORD_BYTES {
479 let shift = trailing_zero_bytes * 8;
480 let shifted = value >> shift;
481 let shifted_width = self.push_width(shifted);
482 let shifted_len = self.fixed_push_len(shifted_width) + 3;
483 consider(shifted_len, CompactPush::Shl { shift: shift as u8 });
484 }
485
486 best.1
487 }
488
489 fn emit_push_value(&mut self, value: U256) {
491 match self.compact_push(value) {
492 CompactPush::Literal { width } => {
493 self.emit_push_fixed_width(value, width);
494 }
495 CompactPush::FullWord => {
496 self.emit_push_zero();
497 self.bytecode.push(op::NOT);
498 }
499 CompactPush::LowerAllOnesMask { shift } => {
500 self.emit_push_zero();
501 self.bytecode.push(op::NOT);
502 self.bytecode.push(op::PUSH1);
503 self.bytecode.push(shift);
504 self.bytecode.push(op::SHR);
505 }
506 CompactPush::Not => {
507 let inverted = !value;
508 self.emit_push_fixed_width(inverted, self.push_width(inverted));
509 self.bytecode.push(op::NOT);
510 }
511 CompactPush::Shl { shift } => {
512 let shifted = value >> usize::from(shift);
513 self.emit_push_fixed_width(shifted, self.push_width(shifted));
514 self.bytecode.push(op::PUSH1);
515 self.bytecode.push(shift);
516 self.bytecode.push(op::SHL);
517 }
518 }
519 }
520
521 fn emit_push_fixed_width(&mut self, value: U256, width: u8) {
523 if width == 0 {
524 self.emit_push_zero();
525 return;
526 }
527
528 self.bytecode.push(op::push(width));
529
530 let bytes = value.to_be_bytes::<EVM_WORD_BYTES>();
531 let start = EVM_WORD_BYTES - width as usize;
532 self.bytecode.extend_from_slice(&bytes[start..]);
533 }
534
535 fn emit_push_zero(&mut self) {
536 if self.config.evm_version.has_push0() {
537 self.bytecode.push(op::PUSH0);
538 } else {
539 self.bytecode.push(op::PUSH1);
540 self.bytecode.push(0);
541 }
542 }
543
544 fn fixed_push_len(&self, width: u8) -> usize {
545 if width == 0 { self.zero_push_len() } else { 1 + width as usize }
546 }
547
548 fn zero_push_len(&self) -> usize {
549 if self.config.evm_version.has_push0() { 1 } else { 2 }
550 }
551
552 fn push_width(&self, value: U256) -> u8 {
554 if value.is_zero() && !self.config.evm_version.has_push0() {
555 1
556 } else {
557 value.byte_len() as u8
558 }
559 }
560
561 fn finish(self, label_offsets: FxHashMap<Label, usize>) -> AssembledCode {
562 AssembledCode {
563 bytecode: self.bytecode,
564 label_offsets,
565 immutable_refs: self.immutable_refs,
566 }
567 }
568}
569
570#[derive(Clone, Copy, Debug, PartialEq, Eq)]
571enum CompactPush {
572 Literal { width: u8 },
574 FullWord,
576 LowerAllOnesMask { shift: u8 },
578 Not,
580 Shl { shift: u8 },
582}
583
584pub mod op {
586 pub const STOP: u8 = 0x00;
587 pub const ADD: u8 = 0x01;
588 pub const MUL: u8 = 0x02;
589 pub const SUB: u8 = 0x03;
590 pub const DIV: u8 = 0x04;
591 pub const SDIV: u8 = 0x05;
592 pub const MOD: u8 = 0x06;
593 pub const SMOD: u8 = 0x07;
594 pub const ADDMOD: u8 = 0x08;
595 pub const MULMOD: u8 = 0x09;
596 pub const EXP: u8 = 0x0a;
597 pub const SIGNEXTEND: u8 = 0x0b;
598
599 pub const LT: u8 = 0x10;
600 pub const GT: u8 = 0x11;
601 pub const SLT: u8 = 0x12;
602 pub const SGT: u8 = 0x13;
603 pub const EQ: u8 = 0x14;
604 pub const ISZERO: u8 = 0x15;
605 pub const AND: u8 = 0x16;
606 pub const OR: u8 = 0x17;
607 pub const XOR: u8 = 0x18;
608 pub const NOT: u8 = 0x19;
609 pub const BYTE: u8 = 0x1a;
610 pub const SHL: u8 = 0x1b;
611 pub const SHR: u8 = 0x1c;
612 pub const SAR: u8 = 0x1d;
613 pub const CLZ: u8 = 0x1e;
614
615 pub const KECCAK256: u8 = 0x20;
616
617 pub const ADDRESS: u8 = 0x30;
618 pub const BALANCE: u8 = 0x31;
619 pub const ORIGIN: u8 = 0x32;
620 pub const CALLER: u8 = 0x33;
621 pub const CALLVALUE: u8 = 0x34;
622 pub const CALLDATALOAD: u8 = 0x35;
623 pub const CALLDATASIZE: u8 = 0x36;
624 pub const CALLDATACOPY: u8 = 0x37;
625 pub const CODESIZE: u8 = 0x38;
626 pub const CODECOPY: u8 = 0x39;
627 pub const GASPRICE: u8 = 0x3a;
628 pub const EXTCODESIZE: u8 = 0x3b;
629 pub const EXTCODECOPY: u8 = 0x3c;
630 pub const RETURNDATASIZE: u8 = 0x3d;
631 pub const RETURNDATACOPY: u8 = 0x3e;
632 pub const EXTCODEHASH: u8 = 0x3f;
633
634 pub const BLOCKHASH: u8 = 0x40;
635 pub const COINBASE: u8 = 0x41;
636 pub const TIMESTAMP: u8 = 0x42;
637 pub const NUMBER: u8 = 0x43;
638 pub const PREVRANDAO: u8 = 0x44;
639 pub const GASLIMIT: u8 = 0x45;
640 pub const CHAINID: u8 = 0x46;
641 pub const SELFBALANCE: u8 = 0x47;
642 pub const BASEFEE: u8 = 0x48;
643 pub const BLOBHASH: u8 = 0x49;
644 pub const BLOBBASEFEE: u8 = 0x4a;
645
646 pub const POP: u8 = 0x50;
647 pub const MLOAD: u8 = 0x51;
648 pub const MSTORE: u8 = 0x52;
649 pub const MSTORE8: u8 = 0x53;
650 pub const SLOAD: u8 = 0x54;
651 pub const SSTORE: u8 = 0x55;
652 pub const JUMP: u8 = 0x56;
653 pub const JUMPI: u8 = 0x57;
654 pub const PC: u8 = 0x58;
655 pub const MSIZE: u8 = 0x59;
656 pub const GAS: u8 = 0x5a;
657 pub const JUMPDEST: u8 = 0x5b;
658 pub const TLOAD: u8 = 0x5c;
659 pub const TSTORE: u8 = 0x5d;
660 pub const MCOPY: u8 = 0x5e;
661 pub const PUSH0: u8 = 0x5f;
662 pub const PUSH1: u8 = 0x60;
663 pub const PUSH2: u8 = 0x61;
664 pub const PUSH3: u8 = 0x62;
665 pub const PUSH4: u8 = 0x63;
666 pub const PUSH5: u8 = 0x64;
667 pub const PUSH6: u8 = 0x65;
668 pub const PUSH7: u8 = 0x66;
669 pub const PUSH8: u8 = 0x67;
670 pub const PUSH9: u8 = 0x68;
671 pub const PUSH10: u8 = 0x69;
672 pub const PUSH11: u8 = 0x6a;
673 pub const PUSH12: u8 = 0x6b;
674 pub const PUSH13: u8 = 0x6c;
675 pub const PUSH14: u8 = 0x6d;
676 pub const PUSH15: u8 = 0x6e;
677 pub const PUSH16: u8 = 0x6f;
678 pub const PUSH17: u8 = 0x70;
679 pub const PUSH18: u8 = 0x71;
680 pub const PUSH19: u8 = 0x72;
681 pub const PUSH20: u8 = 0x73;
682 pub const PUSH21: u8 = 0x74;
683 pub const PUSH22: u8 = 0x75;
684 pub const PUSH23: u8 = 0x76;
685 pub const PUSH24: u8 = 0x77;
686 pub const PUSH25: u8 = 0x78;
687 pub const PUSH26: u8 = 0x79;
688 pub const PUSH27: u8 = 0x7a;
689 pub const PUSH28: u8 = 0x7b;
690 pub const PUSH29: u8 = 0x7c;
691 pub const PUSH30: u8 = 0x7d;
692 pub const PUSH31: u8 = 0x7e;
693 pub const PUSH32: u8 = 0x7f;
694
695 pub const DUP1: u8 = 0x80;
696 pub const DUP2: u8 = 0x81;
697 pub const DUP3: u8 = 0x82;
698 pub const DUP4: u8 = 0x83;
699 pub const DUP5: u8 = 0x84;
700 pub const DUP6: u8 = 0x85;
701 pub const DUP7: u8 = 0x86;
702 pub const DUP8: u8 = 0x87;
703 pub const DUP9: u8 = 0x88;
704 pub const DUP10: u8 = 0x89;
705 pub const DUP11: u8 = 0x8a;
706 pub const DUP12: u8 = 0x8b;
707 pub const DUP13: u8 = 0x8c;
708 pub const DUP14: u8 = 0x8d;
709 pub const DUP15: u8 = 0x8e;
710 pub const DUP16: u8 = 0x8f;
711
712 pub const SWAP1: u8 = 0x90;
713 pub const SWAP2: u8 = 0x91;
714 pub const SWAP3: u8 = 0x92;
715 pub const SWAP4: u8 = 0x93;
716 pub const SWAP5: u8 = 0x94;
717 pub const SWAP6: u8 = 0x95;
718 pub const SWAP7: u8 = 0x96;
719 pub const SWAP8: u8 = 0x97;
720 pub const SWAP9: u8 = 0x98;
721 pub const SWAP10: u8 = 0x99;
722 pub const SWAP11: u8 = 0x9a;
723 pub const SWAP12: u8 = 0x9b;
724 pub const SWAP13: u8 = 0x9c;
725 pub const SWAP14: u8 = 0x9d;
726 pub const SWAP15: u8 = 0x9e;
727 pub const SWAP16: u8 = 0x9f;
728
729 pub const LOG0: u8 = 0xa0;
730 pub const LOG1: u8 = 0xa1;
731 pub const LOG2: u8 = 0xa2;
732 pub const LOG3: u8 = 0xa3;
733 pub const LOG4: u8 = 0xa4;
734
735 pub const DATALOAD: u8 = 0xd0;
736 pub const DATALOADN: u8 = 0xd1;
737 pub const DATASIZE: u8 = 0xd2;
738 pub const DATACOPY: u8 = 0xd3;
739
740 pub const RJUMP: u8 = 0xe0;
741 pub const RJUMPI: u8 = 0xe1;
742 pub const RJUMPV: u8 = 0xe2;
743 pub const CALLF: u8 = 0xe3;
744 pub const RETF: u8 = 0xe4;
745 pub const JUMPF: u8 = 0xe5;
746 pub const DUPN: u8 = 0xe6;
747 pub const SWAPN: u8 = 0xe7;
748 pub const EXCHANGE: u8 = 0xe8;
749 pub const EOFCREATE: u8 = 0xec;
750 pub const RETURNCONTRACT: u8 = 0xee;
751
752 pub const CREATE: u8 = 0xf0;
753 pub const CALL: u8 = 0xf1;
754 pub const CALLCODE: u8 = 0xf2;
755 pub const RETURN: u8 = 0xf3;
756 pub const DELEGATECALL: u8 = 0xf4;
757 pub const CREATE2: u8 = 0xf5;
758 pub const RETURNDATALOAD: u8 = 0xf7;
759 pub const EXTCALL: u8 = 0xf8;
760 pub const EXTDELEGATECALL: u8 = 0xf9;
761 pub const STATICCALL: u8 = 0xfa;
762 pub const EXTSTATICCALL: u8 = 0xfb;
763 pub const REVERT: u8 = 0xfd;
764 pub const INVALID: u8 = 0xfe;
765 pub const SELFDESTRUCT: u8 = 0xff;
766
767 #[must_use]
769 pub const fn push(width: u8) -> u8 {
770 debug_assert!(width >= 1 && width <= 32);
771 PUSH1 + width - 1
772 }
773
774 #[must_use]
776 pub const fn dup(n: u8) -> u8 {
777 debug_assert!(n >= 1 && n <= 16);
778 DUP1 + n - 1
779 }
780
781 #[must_use]
783 pub const fn swap(n: u8) -> u8 {
784 debug_assert!(n >= 1 && n <= 16);
785 SWAP1 + n - 1
786 }
787
788 #[must_use]
790 pub const fn is_terminal(op: u8) -> bool {
791 matches!(op, STOP | JUMP | RETURN | REVERT | INVALID | SELFDESTRUCT)
792 }
793}
794
795#[cfg(test)]
796mod tests {
797 use super::*;
798
799 fn size_optimized_assembler() -> Assembler {
800 Assembler::with_config(AssemblerConfig {
801 evm_version: EvmVersion::Shanghai,
802 optimization: OptimizationMode::Size,
803 ..AssemblerConfig::default()
804 })
805 }
806
807 #[test]
808 fn test_push_width() {
809 assert_eq!(Assembler::push_width(U256::ZERO), 0);
810 assert_eq!(Assembler::push_width(U256::from(1)), 1);
811 assert_eq!(Assembler::push_width(U256::from(255)), 1);
812 assert_eq!(Assembler::push_width(U256::from(256)), 2);
813 assert_eq!(Assembler::push_width(U256::from(0xFFFF)), 2);
814 assert_eq!(Assembler::push_width(U256::from(0x10000)), 3);
815 }
816
817 #[test]
818 fn assembler_inst_is_compact() {
819 assert_eq!(std::mem::size_of::<AsmInst>(), 4);
820 }
821
822 #[test]
823 fn push_values_are_inline_or_interned() {
824 let mut asm = Assembler::new();
825 let inline = u32::MAX >> 1;
826 let large = U256::from(1u64 << 31);
827
828 assert!(AsmInst::push_inline(inline).is_some());
829 assert!(AsmInst::push_inline(1u32 << 31).is_none());
830
831 asm.emit_push(U256::from(inline));
832 asm.emit_push(large);
833 asm.emit_push(large);
834
835 let instructions = asm.program.instructions();
836 assert_eq!(instructions[0].kind(), AsmInstKind::PushInline(inline));
837 assert_eq!(instructions[1].kind(), AsmInstKind::Push(PushValueId::from_usize(0)));
838 assert_eq!(instructions[1], instructions[2]);
839 assert_eq!(asm.push_values.len(), 1);
840 assert_eq!(*asm.push_values.get(PushValueId::from_usize(0)), large);
841 }
842
843 #[test]
844 fn assembler_can_be_reused_after_assembly() {
845 let mut asm = Assembler::new();
846 let large = U256::from(1u64 << 31);
847
848 asm.emit_push(large);
849 let first = asm.assemble();
850
851 assert_eq!(first.bytecode, vec![0x63, 0x80, 0, 0, 0]);
852 assert!(asm.program.instructions().is_empty());
853 assert_eq!(asm.push_values.len(), 0);
854
855 asm.emit_push(U256::from(2));
856 let second = asm.assemble();
857
858 assert_eq!(second.bytecode, vec![0x60, 2]);
859 }
860
861 #[test]
862 fn push_zero_uses_push0_when_available() {
863 let mut asm = Assembler::with_config(AssemblerConfig {
864 evm_version: EvmVersion::Shanghai,
865 optimization: OptimizationMode::None,
866 ..AssemblerConfig::default()
867 });
868
869 asm.emit_push(U256::ZERO);
870 let result = asm.assemble();
871
872 assert_eq!(result.bytecode, vec![op::PUSH0]);
873 }
874
875 #[test]
876 fn push_zero_uses_push1_before_shanghai() {
877 let mut asm = Assembler::with_config(AssemblerConfig {
878 evm_version: EvmVersion::Berlin,
879 optimization: OptimizationMode::Gas,
880 ..AssemblerConfig::default()
881 });
882
883 asm.emit_push(U256::ZERO);
884 let result = asm.assemble();
885
886 assert_eq!(result.bytecode, vec![op::PUSH1, 0]);
887 }
888
889 #[test]
890 fn compact_push_respects_optimization_mode() {
891 let mut size_optimized = Assembler::with_config(AssemblerConfig {
892 evm_version: EvmVersion::Shanghai,
893 optimization: OptimizationMode::Size,
894 ..AssemblerConfig::default()
895 });
896 size_optimized.emit_push(U256::MAX);
897
898 let mut gas_optimized = Assembler::with_config(AssemblerConfig {
899 evm_version: EvmVersion::Shanghai,
900 optimization: OptimizationMode::Gas,
901 ..AssemblerConfig::default()
902 });
903 gas_optimized.emit_push(U256::MAX);
904
905 let mut unoptimized = Assembler::with_config(AssemblerConfig {
906 evm_version: EvmVersion::Shanghai,
907 optimization: OptimizationMode::None,
908 ..AssemblerConfig::default()
909 });
910 unoptimized.emit_push(U256::MAX);
911
912 let compact = vec![op::PUSH0, op::NOT];
913 assert_eq!(size_optimized.assemble().bytecode, compact);
914 assert_eq!(gas_optimized.assemble().bytecode, compact);
915
916 let mut expected = vec![op::PUSH32];
917 expected.extend(std::iter::repeat_n(0xff, 32));
918 assert_eq!(unoptimized.assemble().bytecode, expected);
919 }
920
921 #[test]
922 fn compact_push_uses_push1_zero_before_shanghai() {
923 let mut asm = Assembler::with_config(AssemblerConfig {
924 evm_version: EvmVersion::Berlin,
925 optimization: OptimizationMode::Size,
926 ..AssemblerConfig::default()
927 });
928
929 asm.emit_push(U256::MAX);
930 let result = asm.assemble();
931
932 assert_eq!(result.bytecode, vec![op::PUSH1, 0, op::NOT]);
933 }
934
935 #[test]
936 fn test_simple_assembly() {
937 let mut asm = Assembler::new();
938
939 asm.emit_push(U256::from(42));
940 asm.emit_push(U256::from(10));
941 asm.emit_op(op::ADD);
942 asm.emit_op(op::STOP);
943
944 let result = asm.assemble();
945
946 assert_eq!(result.bytecode, vec![0x60, 42, 0x60, 10, 0x01, 0x00]);
948 }
949
950 #[test]
951 fn test_label_resolution() {
952 let mut asm = Assembler::new();
953
954 let loop_label = asm.new_label();
955 let end_label = asm.new_label();
956
957 asm.define_label(loop_label);
958 asm.emit_push(U256::from(1));
959 asm.emit_push_label(end_label);
960 asm.emit_op(op::JUMPI);
961 asm.emit_push_label(loop_label);
962 asm.emit_op(op::JUMP);
963
964 asm.define_label(end_label);
965 asm.emit_op(op::STOP);
966
967 let result = asm.assemble();
968
969 assert!(result.label_offsets.contains_key(&loop_label));
971 assert!(result.label_offsets.contains_key(&end_label));
972 assert_eq!(result.label_offsets[&loop_label], 0);
973 }
974
975 #[test]
976 fn cold_terminal_block_moves_after_hot_block() {
977 let mut asm = Assembler::with_config(AssemblerConfig {
978 evm_ir_layout_passes: true,
979 ..AssemblerConfig::default()
980 });
981 let cold = asm.new_label();
982 let hot = asm.new_label();
983
984 asm.emit_push_label(hot);
985 asm.emit_op(op::JUMP);
986 asm.mark_label_cold(cold);
987 asm.define_label(cold);
988 asm.emit_push(U256::ZERO);
989 asm.emit_push(U256::ZERO);
990 asm.emit_op(op::REVERT);
991 asm.define_label(hot);
992 asm.emit_op(op::STOP);
993
994 let result = asm.assemble();
995
996 assert_eq!(
997 result.bytecode,
998 vec![
999 op::PUSH1,
1000 3,
1001 op::JUMP,
1002 op::JUMPDEST,
1003 op::STOP,
1004 op::JUMPDEST,
1005 op::PUSH0,
1006 op::PUSH0,
1007 op::REVERT,
1008 ]
1009 );
1010 }
1011
1012 #[test]
1013 fn cold_terminal_block_keeps_fallthrough_position() {
1014 let mut asm = Assembler::with_config(AssemblerConfig {
1015 evm_ir_layout_passes: true,
1016 ..AssemblerConfig::default()
1017 });
1018 let cold = asm.new_label();
1019
1020 asm.emit_push(U256::ONE);
1021 asm.mark_label_cold(cold);
1022 asm.define_label(cold);
1023 asm.emit_push(U256::ZERO);
1024 asm.emit_push(U256::ZERO);
1025 asm.emit_op(op::REVERT);
1026
1027 let result = asm.assemble();
1028
1029 assert_eq!(
1030 result.bytecode,
1031 vec![op::PUSH1, 1, op::JUMPDEST, op::PUSH0, op::PUSH0, op::REVERT]
1032 );
1033 }
1034
1035 #[test]
1036 fn compact_full_word_all_ones_push() {
1037 let mut asm = size_optimized_assembler();
1038
1039 asm.emit_push(U256::MAX);
1040 asm.emit_op(op::STOP);
1041
1042 let result = asm.assemble();
1043
1044 assert_eq!(result.bytecode, vec![op::PUSH0, op::NOT, op::STOP]);
1045 }
1046
1047 #[test]
1048 fn compact_lower_all_ones_mask_push() {
1049 let mut asm = size_optimized_assembler();
1050 let mask = (U256::from(1) << 160) - U256::from(1);
1051
1052 asm.emit_push(mask);
1053 asm.emit_op(op::STOP);
1054
1055 let result = asm.assemble();
1056
1057 assert_eq!(result.bytecode, vec![op::PUSH0, op::NOT, 0x60, 96, op::SHR, op::STOP]);
1058 }
1059
1060 #[test]
1061 fn compact_not_small_push() {
1062 let mut asm = size_optimized_assembler();
1063
1064 asm.emit_push(!U256::from(31));
1065 asm.emit_op(op::STOP);
1066
1067 let result = asm.assemble();
1068
1069 assert_eq!(result.bytecode, vec![0x60, 31, op::NOT, op::STOP]);
1070 }
1071
1072 #[test]
1073 fn compact_not_byte_push() {
1074 let mut asm = size_optimized_assembler();
1075
1076 asm.emit_push(!U256::from(255));
1077 asm.emit_op(op::STOP);
1078
1079 let result = asm.assemble();
1080
1081 assert_eq!(result.bytecode, vec![0x60, 255, op::NOT, op::STOP]);
1082 }
1083
1084 #[test]
1085 fn compact_left_aligned_selector_push() {
1086 let mut asm = size_optimized_assembler();
1087 let selector = U256::from(0x35ea6a75u64) << 224;
1088
1089 asm.emit_push(selector);
1090 asm.emit_op(op::STOP);
1091
1092 let result = asm.assemble();
1093
1094 assert_eq!(
1095 result.bytecode,
1096 vec![0x63, 0x35, 0xea, 0x6a, 0x75, 0x60, 224, op::SHL, op::STOP]
1097 );
1098 }
1099
1100 #[test]
1101 fn compact_right_padded_text_push() {
1102 let mut asm = size_optimized_assembler();
1103 let text = U256::from_be_slice(b"Machine finished:");
1104 let value = text << ((32 - "Machine finished:".len()) * 8);
1105
1106 asm.emit_push(value);
1107 asm.emit_op(op::STOP);
1108
1109 let result = asm.assemble();
1110
1111 let mut expected = vec![0x70];
1112 expected.extend_from_slice(b"Machine finished:");
1113 expected.extend_from_slice(&[0x60, 120, op::SHL, op::STOP]);
1114 assert_eq!(result.bytecode, expected);
1115 }
1116}