diff --git a/docs/book/src/advanced/assembly.md b/docs/book/src/advanced/assembly.md index 3bad77ecab6..31d323f5a4d 100644 --- a/docs/book/src/advanced/assembly.md +++ b/docs/book/src/advanced/assembly.md @@ -1,6 +1,6 @@ # Inline Assembly in Sway -While many users will never have to touch assembly language while writing sway code, it is a powerful tool that enables many advanced use-cases (e.g., optimizations, building libraries, etc). +While many users will never have to touch assembly language while writing Sway code, it is a powerful tool that enables many advanced use-cases (e.g., optimizations, building libraries, etc). ## ASM Block @@ -11,7 +11,7 @@ asm() {...} ``` Declaring an `asm` block is similar to declaring a function. -We can specify register names to operate on as arguments, we can perform operations within the block, and we can return a value. +We can specify register names to operate on as arguments, we can perform assembly instructions within the block, and we can return a value by specifying a return register. Here's an example showing what this might look like: ```sway @@ -23,7 +23,12 @@ pub fn add_1(num: u32) -> u32 { } ``` -An `asm` block can only return a single register. If you really need to return more than one value, you can modify a tuple. Here's an example showing how you can implement this `(u64, u64)`: +The return register is specified at the end of the `asm` block, after all the assembly instructions. It consists of the register name and an optional return type. In the above example, the return register name is `r2` and the return type is `u32`. +If the return type is omitted, it is `u64` by default. + +The return register itself is optional. If it is not specified, similar to functions, the returned value from the `asm` block will be [unit](../basics/built_in_types.md#unit-type), `()`. + +An `asm` block can only return a single register. If you really need to return more than one value, you can modify a tuple. Here's an example showing how you can implement this for `(u64, u64)`: ```sway {{#include ../../../../examples/asm_return_tuple_pointer/src/main.sw}} @@ -37,7 +42,7 @@ Note that in the above example: - we declared a second register `r2` (you may choose any register names you want). - we use the `add` opcode to add `one` to the value of `r1` and store it in `r2`. - `one` is an example of a "reserved register", of which there are 16 in total. Further reading on this is linked below under "Semantics". -- we return `r2` & specify the return type as being u32 (the return type is u64 by default). +- we return `r2` and specify the return type as being `u32`. An important note is that the `ji` and `jnei` opcodes are not available within an `asm` block. For those looking to introduce control flow to `asm` blocks, it is recommended to surround smaller chunks of `asm` with control flow (`if`, `else`, and `while`). diff --git a/docs/book/src/basics/built_in_types.md b/docs/book/src/basics/built_in_types.md index ddfbb7b233b..e7798a803b0 100644 --- a/docs/book/src/basics/built_in_types.md +++ b/docs/book/src/basics/built_in_types.md @@ -13,6 +13,7 @@ Sway is a statically typed language. At compile time, the types of every value m Sway has the following primitive types: +1. `()` (unit type) 1. `u8` (8-bit unsigned integer) 1. `u16` (16-bit unsigned integer) 1. `u32` (32-bit unsigned integer) @@ -26,6 +27,25 @@ Sway has the following primitive types: All other types in Sway are built up of these primitive types, or references to these primitive types. You may notice that there are no signed integers—this is by design. In the blockchain domain that Sway occupies, floating-point values and negative numbers have smaller utility, so their implementation has been left up to libraries for specific use cases. +## Unit Type + +The unit type, `()`, is a type that allows only one value, and thus, represents a value with no information. It is used to indicate the absence of a meaningful value, or the result of a function that performs an action, but does not return any data. The value of the unit type, called simply unit, has the same symbol as the unit type, `()`. Unit type in Sway serves a similar purpose as `void` in imperative languages like C or Java. + +For example: + +```Sway +fn returns_unit() -> () { // Here, `()` represent the unit type. + () // Here, `()` represents the single unit value of the unit type. +} +``` + +In Sway, if the function return type is not specified, it is `()` by default. Thus, the above example is semantically same as the following: + +```Sway +fn returns_unit() { +} +``` + ## Numeric Types All of the unsigned integer types are numeric types. diff --git a/sway-core/src/asm_generation/fuel/abstract_instruction_set.rs b/sway-core/src/asm_generation/fuel/abstract_instruction_set.rs index 799773ab219..ea11591842b 100644 --- a/sway-core/src/asm_generation/fuel/abstract_instruction_set.rs +++ b/sway-core/src/asm_generation/fuel/abstract_instruction_set.rs @@ -52,7 +52,7 @@ impl AbstractInstructionSet { for idx in dead_jumps { self.ops[idx] = Op { opcode: Either::Left(VirtualOp::NOOP), - comment: "removed redundant JUMP".into(), + comment: "remove redundant jump operation".into(), owning_span: None, }; } @@ -105,7 +105,7 @@ impl AbstractInstructionSet { for idx in dead_moves { self.ops[idx] = Op { opcode: Either::Left(VirtualOp::NOOP), - comment: "removed redundant MOVE".into(), + comment: "remove redundant move operation".into(), owning_span: None, }; } diff --git a/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs b/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs index bf0627de013..fc218425daf 100644 --- a/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs +++ b/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs @@ -120,14 +120,14 @@ impl AllocatedAbstractInstructionSet { if mask_l.value != 0 { new_ops.push(AllocatedAbstractOp { opcode: Either::Left(AllocatedOpcode::PSHL(mask_l)), - comment: "Save registers 16..40".into(), + comment: "save registers 16..40".into(), owning_span: op.owning_span.clone(), }); } if mask_h.value != 0 { new_ops.push(AllocatedAbstractOp { opcode: Either::Left(AllocatedOpcode::PSHH(mask_h)), - comment: "Save registers 40..64".into(), + comment: "save registers 40..64".into(), owning_span: op.owning_span.clone(), }); } @@ -146,14 +146,14 @@ impl AllocatedAbstractInstructionSet { if mask_h.value != 0 { new_ops.push(AllocatedAbstractOp { opcode: Either::Left(AllocatedOpcode::POPH(mask_h)), - comment: "Restore registers 40..64".into(), + comment: "restore registers 40..64".into(), owning_span: op.owning_span.clone(), }); } if mask_l.value != 0 { new_ops.push(AllocatedAbstractOp { opcode: Either::Left(AllocatedOpcode::POPL(mask_l)), - comment: "Restore registers 16..40".into(), + comment: "restore registers 16..40".into(), owning_span: op.owning_span.clone(), }); } @@ -299,7 +299,8 @@ impl AllocatedAbstractInstructionSet { AllocatedRegister::Constant(ConstantRegister::InstructionStart), ), owning_span: owning_span.clone(), - comment: "Get current instruction offset from Instruction start".into(), + comment: "get current instruction offset from instructions start ($is)" + .into(), }); realized_ops.push(RealizedOp { opcode: AllocatedOpcode::SRLI( @@ -308,7 +309,7 @@ impl AllocatedAbstractInstructionSet { VirtualImmediate12 { value: 2 }, ), owning_span: owning_span.clone(), - comment: "Current instruction offset in 32b words".into(), + comment: "get current instruction offset in 32-bit words".into(), }); realized_ops.push(RealizedOp { opcode: AllocatedOpcode::ADDI(r1.clone(), r1, imm), @@ -540,7 +541,7 @@ impl AllocatedAbstractInstructionSet { if rel_offset(lab) == 0 { new_ops.push(AllocatedAbstractOp { opcode: Either::Left(AllocatedOpcode::NOOP), - comment: "NOP for self loop".into(), + comment: "emit noop for self loop".into(), owning_span: None, }); new_ops.push(op); @@ -583,7 +584,7 @@ impl AllocatedAbstractInstructionSet { if rel_offset(lab) == 0 { new_ops.push(AllocatedAbstractOp { opcode: Either::Left(AllocatedOpcode::NOOP), - comment: "NOP for self loop".into(), + comment: "emit noop for self loop".into(), owning_span: None, }); new_ops.push(op); @@ -628,7 +629,7 @@ impl AllocatedAbstractInstructionSet { if rel_offset(lab) <= consts::TWELVE_BITS { new_ops.push(op) } else { - panic!("Return to address must be right after the call for which we saved this addr."); + panic!("Return to address must be right after the call for which we saved this address."); } } diff --git a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs index fec3b2b83b3..a97ff923635 100644 --- a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs +++ b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs @@ -126,7 +126,7 @@ impl<'ir, 'eng> AsmBuilder for FuelAsmBuilder<'ir, 'eng> { VirtualRegister::Constant(ConstantRegister::FuncArg0), dataid, )), - comment: format!("ptr to {} default value", name), + comment: format!("get pointer to configurable {} default value", name), owning_span: None, }); @@ -138,7 +138,7 @@ impl<'ir, 'eng> AsmBuilder for FuelAsmBuilder<'ir, 'eng> { value: encoded_bytes.len() as u16, }, )), - comment: format!("length of {} default value", name), + comment: format!("get length of configurable {} default value", name), owning_span: None, }); @@ -150,7 +150,7 @@ impl<'ir, 'eng> AsmBuilder for FuelAsmBuilder<'ir, 'eng> { value: global.offset_in_bytes as u16, }, )), - comment: format!("ptr to global {} stack address", name), + comment: format!("get pointer to configurable {} stack address", name), owning_span: None, }); @@ -159,14 +159,14 @@ impl<'ir, 'eng> AsmBuilder for FuelAsmBuilder<'ir, 'eng> { self.before_entries.push(Op::save_ret_addr( VirtualRegister::Constant(ConstantRegister::CallReturnAddress), ret_label, - "", + "set new return address", None, )); // call decode self.before_entries.push(Op { opcode: Either::Right(crate::asm_lang::ControlFlowOp::Call(*decode_fn_label)), - comment: format!("decode {}", name), + comment: format!("decode configurable {}", name), owning_span: None, }); @@ -334,7 +334,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.cur_bytecode.push(Op::register_move( arg_reg.clone(), phi_reg.clone(), - "parameter from branch to block argument", + "move parameter from branch to block argument", None, )); } @@ -354,7 +354,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { ) -> Result<(), ErrorEmitted> { let Some(instruction) = instr_val.get_instruction(self.context) else { return Err(handler.emit_err(CompileError::Internal( - "Value not an instruction.", + "Value is not an instruction.", self.md_mgr .val_to_span(self.context, *instr_val) .unwrap_or_else(Span::dummy), @@ -543,7 +543,9 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { const_copy.clone(), init_val_reg, )), - comment: "copy const asm init to GP reg".into(), + comment: + "copy ASM block argument's constant initial value to register" + .into(), owning_span: self.md_mgr.val_to_span(self.context, *instr_val), }); const_copy @@ -609,10 +611,11 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { }); } - // Now, load the designated asm return register into the desired return register, but only - // if it was named. - if let Some(ret_reg_name) = &asm_block.return_name { - // Lookup and replace the return register. + // ASM block always returns a value. The return value is either the one contained in + // the return register specified at the end of the ASM block, or it is unit, `()`, in + // the case of an ASM block without the return register specified. + let (ret_reg, comment) = if let Some(ret_reg_name) = &asm_block.return_name { + // If the return register is specified, lookup it by name. let ret_reg = match realize_register(ret_reg_name.as_str()) { Some(reg) => reg, None => { @@ -626,14 +629,40 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { })); } }; - let instr_reg = self.reg_seqr.next(); - inline_ops.push(Op { - opcode: Either::Left(VirtualOp::MOVE(instr_reg.clone(), ret_reg)), - comment: format!("return value from inline asm ({})", ret_reg_name), - owning_span: self.md_mgr.val_to_span(self.context, *instr_val), - }); - self.reg_map.insert(*instr_val, instr_reg); - } + + ( + ret_reg, + format!( + "return value from ASM block with return register {}", + ret_reg_name + ), + ) + } else { + // If the return register is not specified, the return value is unit, `()`, and we + // move constant register $zero to the final instruction register. + if !asm_block.return_type.is_unit(self.context) { + return Err(handler.emit_err(CompileError::InternalOwned( + format!("Return type of an ASM block without return register must be unit, but it was {}.", asm_block.return_type.as_string(self.context)), + self.md_mgr + .val_to_span(self.context, *instr_val) + .unwrap_or_else(Span::dummy), + ))); + } + + ( + VirtualRegister::Constant(ConstantRegister::Zero), + "return unit value from ASM block without return register".into(), + ) + }; + + // Move the return register to the instruction register. + let instr_reg = self.reg_seqr.next(); + inline_ops.push(Op { + opcode: Either::Left(VirtualOp::MOVE(instr_reg.clone(), ret_reg)), + comment, + owning_span: self.md_mgr.val_to_span(self.context, *instr_val), + }); + self.reg_map.insert(*instr_val, instr_reg); self.cur_bytecode.append(&mut inline_ops); @@ -648,7 +677,8 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { ) -> Result<(), CompileError> { let val_reg = self.value_to_register(bitcast_val)?; let reg = if to_type.is_bool(self.context) { - // This may not be necessary if we just treat a non-zero value as 'true'. + // We treat only one as `true`, and not every non-zero value. + // So, every non-zero value must be converted to one. let res_reg = self.reg_seqr.next(); self.cur_bytecode.push(Op { opcode: Either::Left(VirtualOp::EQ( @@ -656,7 +686,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { val_reg, VirtualRegister::Constant(ConstantRegister::Zero), )), - comment: "convert to inverted boolean".into(), + comment: "[bitcast to bool]: convert value to inverted boolean".into(), owning_span: self.md_mgr.val_to_span(self.context, *instr_val), }); self.cur_bytecode.push(Op { @@ -665,13 +695,15 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { res_reg.clone(), VirtualImmediate12 { value: 1 }, )), - comment: "invert boolean".into(), + comment: "[bitcast to bool]: invert inverted boolean".into(), owning_span: self.md_mgr.val_to_span(self.context, *instr_val), }); res_reg + } else if to_type.is_unit(self.context) { + // Unit is represented as zero. + VirtualRegister::Constant(ConstantRegister::Zero) } else { - // This is a no-op, although strictly speaking Unit should probably be compiled as - // a zero. + // For all other values, bitcast is a no-op. val_reg }; self.reg_map.insert(*instr_val, reg); @@ -1019,7 +1051,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.cur_bytecode.push(Op::register_move( phi_reg.clone(), local_reg, - "parameter from branch to block argument", + "move parameter from branch to block argument", None, )); } @@ -1052,13 +1084,13 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { owning_span: self.md_mgr.val_to_span(self.context, *instr_val), }); - // now, move the return value of the contract call to the return register. + // Now, move the return value of the contract call to the return register. // TODO validate RETL matches the expected type (this is a comment from the old codegen) let instr_reg = self.reg_seqr.next(); self.cur_bytecode.push(Op::register_move( instr_reg.clone(), VirtualRegister::Constant(ConstantRegister::ReturnValue), - "save call result", + "save external contract call result", None, )); self.reg_map.insert(*instr_val, instr_reg); @@ -1153,7 +1185,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { array_elem_size, size_reg.clone(), None, - "get size of element", + "get array element size", owning_span.clone(), ); @@ -1175,7 +1207,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { reg, offset_reg.clone(), )), - comment: "add to array base".into(), + comment: "add array element offset to array base".into(), owning_span: owning_span.clone(), }); @@ -1198,7 +1230,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { const_offs, instr_reg.clone(), Some(&base_reg), - "get offset to element", + "get offset to aggregate element", owning_span.clone(), ); self.reg_map.insert(*instr_val, instr_reg); @@ -1312,7 +1344,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { value: g.offset_in_bytes as u16, }, )), - comment: format!("configurable {} address", name), + comment: format!("get address of configurable {}", name), owning_span: self.md_mgr.val_to_span(self.context, *addr_val), }); self.reg_map.insert(*addr_val, addr_reg); @@ -1324,7 +1356,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { addr_reg.clone(), dataid.clone(), )), - comment: format!("configurable {} address", name), + comment: format!("get address of configurable {}", name), owning_span: self.md_mgr.val_to_span(self.context, *addr_val), }); self.reg_map.insert(*addr_val, addr_reg); @@ -1381,7 +1413,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { src_reg, VirtualImmediate12 { value: 0 }, )), - comment: "load value".into(), + comment: "load byte".into(), owning_span, }); } @@ -1392,7 +1424,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { src_reg, VirtualImmediate12 { value: 0 }, )), - comment: "load value".into(), + comment: "load word".into(), owning_span, }); } @@ -1433,13 +1465,13 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { value: byte_len as u32, }, )), - comment: "get length for mcp".into(), + comment: "get data length for memory copy".into(), owning_span: owning_span.clone(), }); self.cur_bytecode.push(Op { opcode: Either::Left(VirtualOp::MCP(dst_reg, src_reg, len_reg)), - comment: "copy memory with mem_copy".into(), + comment: "copy memory".into(), owning_span, }); @@ -1487,7 +1519,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { VirtualRegister::Constant(ConstantRegister::Zero), VirtualRegister::Constant(ConstantRegister::Zero), )), - comment: "".into(), + comment: "log non-pointer value".into(), }); } else { // If the type is a pointer then we use LOGD to log the data. First put the size into @@ -1507,7 +1539,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { VirtualImmediate12 { value: 0 }, )), owning_span: owning_span.clone(), - comment: "load slice ptr".into(), + comment: "load slice pointer for logging data".into(), }); self.cur_bytecode.push(Op { opcode: Either::Left(VirtualOp::LW( @@ -1516,7 +1548,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { VirtualImmediate12 { value: 1 }, )), owning_span: owning_span.clone(), - comment: "load slice size".into(), + comment: "load slice size for logging data".into(), }); self.cur_bytecode.push(Op { owning_span, @@ -1536,7 +1568,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { size_in_bytes, size_reg.clone(), None, - "loading size for LOGD", + "load data size for logging data", owning_span.clone(), ); @@ -1548,7 +1580,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { log_val_reg.clone(), size_reg, )), - comment: "log ptr".into(), + comment: "log data".into(), }); } } @@ -1578,7 +1610,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { sway_ir::Register::Flag => ConstantRegister::Flags, }), )), - comment: "move register into abi function".to_owned(), + comment: "read special register".to_owned(), owning_span: self.md_mgr.val_to_span(self.context, *instr_val), }); @@ -1601,7 +1633,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { ConstantRegister::Zero, ))), owning_span, - comment: "returning unit as zero".into(), + comment: "return unit as zero".into(), }); } else { let ret_reg = self.value_to_register(ret_val)?; @@ -1636,7 +1668,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { VirtualImmediate12 { value: 0 }, )), owning_span: owning_span.clone(), - comment: "load ptr of returned slice".into(), + comment: "load pointer to returned slice".into(), }); } else { let size_in_bytes = ret_type @@ -1648,7 +1680,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { size_in_bytes, size_reg.clone(), None, - "get size of returned ref", + "get size of type returned by pointer", owning_span.clone(), ); } @@ -1693,7 +1725,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { VirtualRegister::Constant(ConstantRegister::HeapPointer), VirtualImmediate12::new(0, Span::dummy()).unwrap(), )), - comment: "jmp_mem: Load MEM[$hp]".into(), + comment: "[jump]: load word from MEM[$hp]".into(), }); self.cur_bytecode.push(Op { owning_span: owning_span.clone(), @@ -1702,7 +1734,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { target_reg, VirtualRegister::Constant(ConstantRegister::InstructionStart), )), - comment: "jmp_mem: Subtract $is since Jmp adds it back.".into(), + comment: "[jump]: subtract instructions start ($is) since jmp adds it back".into(), }); self.cur_bytecode.push(Op { owning_span: owning_span.clone(), @@ -1711,13 +1743,13 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { is_target_reg.clone(), VirtualImmediate12::new(4, Span::dummy()).unwrap(), )), - comment: "jmp_mem: Divide by 4 since Jmp multiplies by 4.".into(), + comment: "[jump]: divide by 4 since jmp multiplies by 4".into(), }); self.cur_bytecode.push(Op { owning_span, opcode: Either::Left(VirtualOp::JMP(by4_reg)), - comment: "jmp_mem: Jump to computed value".into(), + comment: "[jump]: jump to computed value".into(), }); Ok(()) @@ -1785,7 +1817,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { was_slot_set_reg.clone(), number_of_slots_reg, )), - comment: "clear a sequence of storage slots".into(), + comment: "clear sequence of storage slots".into(), owning_span, }); @@ -1839,7 +1871,13 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { number_of_slots_reg, ), }), - comment: "access a sequence of storage slots".into(), + comment: format!( + "{} sequence of storage slots", + match access_type { + StateAccessType::Read => "read", + StateAccessType::Write => "write", + } + ), owning_span, }); @@ -1872,7 +1910,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.cur_bytecode.push(Op { opcode: Either::Left(VirtualOp::SRW(load_reg.clone(), was_slot_set_reg, key_reg)), - comment: "single word state access".into(), + comment: "read single word from contract state".into(), owning_span, }); @@ -1909,7 +1947,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.cur_bytecode.push(Op { opcode: Either::Left(VirtualOp::SWW(key_reg, was_slot_set_reg.clone(), store_reg)), - comment: "single word state access".into(), + comment: "write single word to contract state".into(), owning_span, }); @@ -1949,7 +1987,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { val_reg, VirtualImmediate12 { value: 0 }, )), - comment: "store value".into(), + comment: "store byte".into(), owning_span, }); } @@ -1960,7 +1998,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { val_reg, VirtualImmediate12 { value: 0 }, )), - comment: "store value".into(), + comment: "store word".into(), owning_span, }); } @@ -2031,7 +2069,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { reg.clone(), data_id.clone(), )), - comment: "literal instantiation".into(), + comment: "load constant from data section".into(), owning_span: span, }); (reg, Some(data_id)) @@ -2055,7 +2093,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { // to determine when it may be initialised and/or reused. } - // Get the reg corresponding to `value`. Returns an ICE if the value is not in reg_map or is + // Get the reg corresponding to `value`. Returns an ICE if the value is not in `reg_map` or is // not a constant. pub(super) fn value_to_register( &mut self, diff --git a/sway-core/src/asm_generation/fuel/functions.rs b/sway-core/src/asm_generation/fuel/functions.rs index 5939535c716..a53860e4204 100644 --- a/sway-core/src/asm_generation/fuel/functions.rs +++ b/sway-core/src/asm_generation/fuel/functions.rs @@ -79,7 +79,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.cur_bytecode.push(Op::register_move( VirtualRegister::Constant(ConstantRegister::ARG_REGS[idx]), arg_reg, - format!("pass arg {idx}"), + format!("[call]: pass argument {idx}"), self.md_mgr.val_to_span(self.context, *arg_val), )); } @@ -92,7 +92,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.cur_bytecode.push(Op::register_move( VirtualRegister::Constant(ConstantRegister::ARG_REGS[idx]), arg_reg, - format!("pass arg {idx}"), + format!("[call]: pass argument {idx}"), self.md_mgr.val_to_span(self.context, *arg_val), )); } else { @@ -120,7 +120,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { ) .expect("Too many arguments, cannot handle."), )), - comment: format!("Pass arg {idx} via its stack slot"), + comment: format!("[call]: pass argument {idx} via its stack slot"), owning_span: self.md_mgr.val_to_span(self.context, *arg_val), }); } @@ -138,7 +138,8 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { VirtualImmediate12::new(self.locals_size_bytes(), Span::dummy()) .expect("Stack size too big for these many arguments, cannot handle."), )), - comment: "Save address of stack arguments in last arg register".to_string(), + comment: "[call]: save address of stack arguments in last argument register" + .to_string(), owning_span: self.md_mgr.val_to_span(self.context, *instr_val), }); } else { @@ -151,7 +152,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { VirtualImmediate18::new(self.locals_size_bytes(), Span::dummy()) .expect("Stack size too big for these many arguments, cannot handle."), )), - comment: "Temporarily save the locals size to add up next".to_string(), + comment: "[call]: temporarily save locals size to add up next".to_string(), owning_span: self.md_mgr.val_to_span(self.context, *instr_val), }); self.cur_bytecode.push(Op { @@ -166,7 +167,8 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { [(compiler_constants::NUM_ARG_REGISTERS - 1) as usize], ), )), - comment: "Save address of stack arguments in last arg register".to_string(), + comment: "[call]: save address of stack arguments in last argument register" + .to_string(), owning_span: self.md_mgr.val_to_span(self.context, *instr_val), }); } @@ -177,7 +179,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.cur_bytecode.push(Op::save_ret_addr( VirtualRegister::Constant(ConstantRegister::CallReturnAddress), ret_label, - "set new return addr", + "[call]: set new return address", None, )); @@ -185,7 +187,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { let (fn_label, _) = self.func_to_labels(function); self.cur_bytecode.push(Op { opcode: Either::Right(OrganizationalOp::Call(fn_label)), - comment: format!("call {}", function.get_name(self.context)), + comment: format!("[call]: call {}", function.get_name(self.context)), owning_span: None, }); self.cur_bytecode.push(Op::unowned_jump_label(ret_label)); @@ -197,7 +199,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { ret_reg.clone(), VirtualRegister::Constant(ConstantRegister::CallReturnValue), )), - comment: "copy the return value".into(), + comment: "[call]: copy the return value".into(), owning_span: None, }); self.reg_map.insert(*instr_val, ret_reg); @@ -284,7 +286,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { // Save any general purpose registers used here on the stack. self.cur_bytecode.push(Op { opcode: Either::Right(OrganizationalOp::PushAll(start_label)), - comment: "save all regs".to_owned(), + comment: "save all registers".to_owned(), owning_span: span.clone(), }); } @@ -305,14 +307,14 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.cur_bytecode.push(Op::register_move( reta.clone(), VirtualRegister::Constant(ConstantRegister::CallReturnAddress), - "save reta", + "save return address", None, )); let retv = self.reg_seqr.next(); self.cur_bytecode.push(Op::register_move( retv.clone(), VirtualRegister::Constant(ConstantRegister::CallReturnValue), - "save retv", + "save return value", None, )); @@ -346,14 +348,14 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.cur_bytecode.push(Op::register_move( VirtualRegister::Constant(ConstantRegister::CallReturnAddress), reta, - "restore reta", + "restore return address", None, )); // Restore GP regs. self.cur_bytecode.push(Op { opcode: Either::Right(OrganizationalOp::PopAll(start_label)), - comment: "restore all regs".to_owned(), + comment: "restore all registers".to_owned(), owning_span: None, }); @@ -387,7 +389,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.cur_bytecode.push(Op::register_move( arg_copy_reg.clone(), VirtualRegister::Constant(ConstantRegister::ARG_REGS[idx]), - format!("save arg {idx} ({arg_name})"), + format!("save argument {idx} ({arg_name})"), self.md_mgr.val_to_span(self.context, *arg_val), )); @@ -396,7 +398,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { } } else { // Get NUM_ARG_REGISTERS - 1 arguments from arg registers and rest from the stack. - for (idx, (_, arg_val)) in function.args_iter(self.context).enumerate() { + for (idx, (arg_name, arg_val)) in function.args_iter(self.context).enumerate() { let arg_copy_reg = self.reg_seqr.next(); // Except for the last arg register, the others hold an argument. if idx < compiler_constants::NUM_ARG_REGISTERS as usize - 1 { @@ -404,7 +406,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { self.cur_bytecode.push(Op::register_move( arg_copy_reg.clone(), VirtualRegister::Constant(ConstantRegister::ARG_REGS[idx]), - format!("save arg {idx}"), + format!("save argument {idx} ({arg_name})"), self.md_mgr.val_to_span(self.context, *arg_val), )); } else { @@ -430,7 +432,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { ) .expect("Too many arguments, cannot handle."), )), - comment: format!("Load arg {idx} from its stack slot"), + comment: format!("load argument {idx} ({arg_name}) from its stack slot"), owning_span: self.md_mgr.val_to_span(self.context, *arg_val), }); } @@ -475,7 +477,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { single_arg_reg.clone(), VirtualImmediate12 { value: 0 }, )), - comment: "load main fn parameter".into(), + comment: "load main function parameter".into(), owning_span: None, }); } @@ -519,7 +521,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { args_base_reg.clone(), offs_reg.clone(), )), - comment: format!("get offset for arg {name}"), + comment: format!("get offset of argument {name}"), owning_span: None, }); @@ -530,7 +532,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { offs_reg, VirtualImmediate12 { value: 0 }, )), - comment: format!("get arg {name}"), + comment: format!("get argument {name}"), owning_span: None, }); } else { @@ -540,7 +542,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { offs_reg, VirtualImmediate12 { value: 0 }, )), - comment: format!("get arg {name}"), + comment: format!("get argument {name}"), owning_span: None, }); } @@ -553,7 +555,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { value: arg_word_offset as u16 * 8, }, )), - comment: format!("get arg {name}"), + comment: format!("get argument {name}"), owning_span: None, }); } else { @@ -565,7 +567,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { value: arg_word_offset as u16, }, )), - comment: format!("get arg {name}"), + comment: format!("get argument {name}"), owning_span: None, }); } @@ -574,7 +576,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { arg_word_offset * 8, current_arg_reg.clone(), Some(&args_base_reg), - format!("get offset or arg {name}"), + format!("get offset of argument {name}"), None, ); } @@ -597,7 +599,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { // see https://github.com/FuelLabs/fuel-specs/pull/193#issuecomment-876496372 VirtualImmediate12 { value: 74 }, )), - comment: "base register for method parameter".into(), + comment: "get base register for method arguments".into(), owning_span: None, }); } @@ -612,7 +614,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { value: GTFArgs::ScriptData as u16, }, )), - comment: "base register for main fn parameter".into(), + comment: "get base register for main function arguments".into(), owning_span: None, }); } @@ -650,7 +652,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { value: GTFArgs::InputType as u16, }, )), - comment: "get input type".into(), + comment: "get predicate input type".into(), owning_span: None, }); @@ -671,7 +673,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { value: GTFArgs::InputCoinPredicateData as u16, }, )), - comment: "get input coin predicate data pointer".into(), + comment: "get predicate input coin data pointer".into(), owning_span: None, }); @@ -692,7 +694,9 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { two.clone(), VirtualImmediate18 { value: 2u32 }, )), - comment: "register containing 2".into(), + comment: + "[predicate input is message]: set register to 2 (Input::Message discriminator)" + .into(), owning_span: None, }); self.cur_bytecode.push(Op { @@ -701,7 +705,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { input_type, two, )), - comment: "input type is message(2)".into(), + comment: "[predicate input is message]: check if input type is message".into(), owning_span: None, }); @@ -713,16 +717,17 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { input_type_is_message, VirtualImmediate12 { value: 1 }, )), - comment: "input type is not message(2)".into(), + comment: "[predicate input is message]: check if input type is not message".into(), owning_span: None, }); // Label to jump to if the input type is *not* 2, i.e. not "message" (and not "coin" since // we checked that earlier). Then do the jump. let input_type_not_message_label = self.reg_seqr.get_label(); - self.cur_bytecode.push(Op::jump_if_not_zero( + self.cur_bytecode.push(Op::jump_if_not_zero_comment( input_type_not_message, input_type_not_message_label, + "[predicate input is message]: jump to return false from predicate", )); // If the input is indeed a "message", then use `GTF` to get the "input message predicate @@ -735,7 +740,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { value: GTFArgs::InputMessagePredicateData as u16, }, )), - comment: "input message predicate data pointer".into(), + comment: "get predicate input message data pointer".into(), owning_span: None, }); self.cur_bytecode.push(Op::jump_to_label(success_label)); @@ -751,7 +756,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { ConstantRegister::Zero, ))), owning_span: None, - comment: "return false".into(), + comment: "return false from predicate".into(), }); // Final success label to continue execution at if we successfully obtained the predicate @@ -873,7 +878,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { locals_base_reg.clone(), VirtualRegister::Constant(ConstantRegister::StackPointer), format!( - "save locals base register for {}", + "save locals base register for function {}", function.get_name(self.context) ) .to_string(), @@ -888,7 +893,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { opcode: Either::Left(VirtualOp::CFEI(VirtualImmediate24 { value: locals_size_bytes as u32 + (max_num_extra_args * 8) as u32, })), - comment: format!("allocate {locals_size_bytes} bytes for locals and {max_num_extra_args} slots for call arguments."), + comment: format!("allocate {locals_size_bytes} bytes for locals and {max_num_extra_args} slots for call arguments"), owning_span: None, }); ( @@ -927,7 +932,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { VirtualRegister::Constant(ConstantRegister::Scratch), data_id, )), - comment: "load initializer from data section".to_owned(), + comment: "load local variable initializer from data section".to_owned(), owning_span: None, }); } @@ -938,7 +943,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { VirtualRegister::Constant(ConstantRegister::Scratch), c.clone(), )), - comment: "load initializer from register".into(), + comment: "load local variable initializer from register".into(), owning_span: None, }); } @@ -958,7 +963,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { value: var_stack_off_bytes as u16, }, )), - comment: "calc local variable address".to_owned(), + comment: "get local variable address".to_owned(), owning_span: None, }); } else { @@ -971,7 +976,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { value: var_stack_off_bytes as u32, }, )), - comment: "stack offset of local variable into register".to_owned(), + comment: "move stack offset of local variable into register".to_owned(), owning_span: None, }); self.cur_bytecode.push(Op { @@ -980,7 +985,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { locals_base_reg.clone(), dst_reg.clone(), )), - comment: "calc local variable address".to_owned(), + comment: "get local variable address".to_owned(), owning_span: None, }); } @@ -994,7 +999,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { VirtualRegister::Constant(ConstantRegister::Scratch), VirtualImmediate12 { value: 0 }, )), - comment: "store initializer to local variable".to_owned(), + comment: "store byte initializer to local variable".to_owned(), owning_span: None, }); } else { @@ -1004,7 +1009,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { VirtualRegister::Constant(ConstantRegister::Scratch), VirtualImmediate12 { value: 0 }, )), - comment: "store initializer to local variable".to_owned(), + comment: "store word initializer to local variable".to_owned(), owning_span: None, }); } @@ -1039,7 +1044,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { opcode: Either::Left(VirtualOp::CFSI(VirtualImmediate24 { value: u32::try_from(locals_size_bytes + (max_num_extra_args * 8)).unwrap(), })), - comment: format!("free {locals_size_bytes} bytes for locals and {max_num_extra_args} slots for extra call arguments."), + comment: format!("free {locals_size_bytes} bytes for locals and {max_num_extra_args} slots for extra call arguments"), owning_span: None, }); } diff --git a/sway-core/src/asm_generation/fuel/programs/abstract.rs b/sway-core/src/asm_generation/fuel/programs/abstract.rs index 9f81c767c42..c30f72621e7 100644 --- a/sway-core/src/asm_generation/fuel/programs/abstract.rs +++ b/sway-core/src/asm_generation/fuel/programs/abstract.rs @@ -225,7 +225,7 @@ impl AbstractProgram { let entry = self.entries.iter().find(|x| x.name == "__entry").unwrap(); asm.ops.push(AllocatedAbstractOp { opcode: Either::Right(ControlFlowOp::Jump(entry.label)), - comment: "jump to abi method selector".into(), + comment: "jump to ABI function selector".into(), owning_span: None, }); } @@ -247,7 +247,7 @@ impl AbstractProgram { // Build the switch statement for selectors. asm.ops.push(AllocatedAbstractOp { opcode: Either::Right(ControlFlowOp::Comment), - comment: "Begin contract ABI selector switch".into(), + comment: "[function selection]: begin contract function selector switch".into(), owning_span: None, }); @@ -261,7 +261,7 @@ impl AbstractProgram { "constant infallible value", ), )), - comment: "load input function selector".into(), + comment: "[function selection]: load input function selector".into(), owning_span: None, }); @@ -283,7 +283,10 @@ impl AbstractProgram { // Load the data into a register for comparison. asm.ops.push(AllocatedAbstractOp { opcode: Either::Left(AllocatedOpcode::LoadDataId(PROG_SELECTOR_REG, data_label)), - comment: format!("load fn selector for comparison {}", entry.name), + comment: format!( + "[function selection]: load function {} selector for comparison", + entry.name + ), owning_span: None, }); @@ -294,7 +297,10 @@ impl AbstractProgram { INPUT_SELECTOR_REG, PROG_SELECTOR_REG, )), - comment: "function selector comparison".into(), + comment: format!( + "[function selection]: compare function {} selector with input selector", + entry.name + ), owning_span: None, }); @@ -302,7 +308,7 @@ impl AbstractProgram { asm.ops.push(AllocatedAbstractOp { // If the comparison result is _not_ equal to 0, then it was indeed equal. opcode: Either::Right(ControlFlowOp::JumpIfNotZero(CMP_RESULT_REG, entry.label)), - comment: "jump to selected function".into(), + comment: "[function selection]: jump to selected contract function".into(), owning_span: None, }); } @@ -310,7 +316,7 @@ impl AbstractProgram { if let Some(fallback_fn) = fallback_fn { asm.ops.push(AllocatedAbstractOp { opcode: Either::Right(ControlFlowOp::Call(fallback_fn)), - comment: "call fallback function".into(), + comment: "[function selection]: call contract fallback function".into(), owning_span: None, }); } @@ -322,14 +328,15 @@ impl AbstractProgram { value: compiler_constants::MISMATCHED_SELECTOR_REVERT_CODE, }, )), - comment: "special code for mismatched selector".into(), + comment: "[function selection]: load revert code for mismatched function selector" + .into(), owning_span: None, }); asm.ops.push(AllocatedAbstractOp { opcode: Either::Left(AllocatedOpcode::RVRT(AllocatedRegister::Constant( ConstantRegister::Scratch, ))), - comment: "revert if no selectors matched".into(), + comment: "[function selection]: revert if no selectors have matched".into(), owning_span: None, }); } @@ -340,7 +347,7 @@ impl AbstractProgram { opcode: Either::Left(AllocatedOpcode::CFEI(VirtualImmediate24 { value: len_in_bytes as u32, })), - comment: "stack space for globals".into(), + comment: "allocate stack space for globals".into(), owning_span: None, }); } diff --git a/sway-core/src/asm_generation/fuel/register_allocator.rs b/sway-core/src/asm_generation/fuel/register_allocator.rs index 9cae883fe26..8e02d2d29fa 100644 --- a/sway-core/src/asm_generation/fuel/register_allocator.rs +++ b/sway-core/src/asm_generation/fuel/register_allocator.rs @@ -791,7 +791,7 @@ fn spill(ops: &[Op], spills: &FxHashSet) -> Vec { value: offset_bytes, }, )), - comment: "Spill/Refill: Set offset".to_string(), + comment: "[spill/refill]: set offset".to_string(), owning_span: None, }; inst_list.push(offset_mov_instr); @@ -801,7 +801,7 @@ fn spill(ops: &[Op], spills: &FxHashSet) -> Vec { VirtualRegister::Constant(ConstantRegister::Scratch), VirtualRegister::Constant(ConstantRegister::LocalsBase), )), - comment: "Spill/Refill: Add offset to stack base".to_string(), + comment: "[spill/refill]: add offset to stack base".to_string(), owning_span: None, }; inst_list.push(offset_add_instr); @@ -827,7 +827,7 @@ fn spill(ops: &[Op], spills: &FxHashSet) -> Vec { value: offset_upper_12, }, )), - comment: "Spill/Refill: Offset computation".to_string(), + comment: "[spill/refill]: compute offset".to_string(), owning_span: None, }; inst_list.push(offset_upper_mov_instr); @@ -837,7 +837,7 @@ fn spill(ops: &[Op], spills: &FxHashSet) -> Vec { VirtualRegister::Constant(ConstantRegister::Scratch), VirtualImmediate12 { value: 12 }, )), - comment: "Spill/Refill: Offset computation".to_string(), + comment: "[spill/refill]: compute offset".to_string(), owning_span: None, }; inst_list.push(offset_upper_shift_instr); @@ -847,7 +847,7 @@ fn spill(ops: &[Op], spills: &FxHashSet) -> Vec { VirtualRegister::Constant(ConstantRegister::Scratch), VirtualRegister::Constant(ConstantRegister::LocalsBase), )), - comment: "Spill/Refill: Offset computation".to_string(), + comment: "[spill/refill]: compute offset".to_string(), owning_span: None, }; inst_list.push(offset_add_instr); @@ -876,7 +876,7 @@ fn spill(ops: &[Op], spills: &FxHashSet) -> Vec { value: (offset_bytes / 8) as u16, }, )), - comment: "Refilling from spill".to_string(), + comment: "[spill/refill]: refill from spill".to_string(), owning_span: None, }); } else { @@ -889,7 +889,7 @@ fn spill(ops: &[Op], spills: &FxHashSet) -> Vec { // This will be multiplied by 8 by the VM offset_imm_word, )), - comment: "Refilling from spill".to_string(), + comment: "[spill/refill]: refill from spill".to_string(), owning_span: None, }; spilled.push(lw); @@ -914,7 +914,7 @@ fn spill(ops: &[Op], spills: &FxHashSet) -> Vec { value: (offset_bytes / 8) as u16, }, )), - comment: "Spill".to_string(), + comment: "[spill/refill]: spill".to_string(), owning_span: None, }); } else { @@ -927,7 +927,7 @@ fn spill(ops: &[Op], spills: &FxHashSet) -> Vec { // This will be multiplied by 8 by the VM offset_imm_word, )), - comment: "Spill".to_string(), + comment: "[spill/refill]: spill".to_string(), owning_span: None, }; spilled.push(sw); diff --git a/sway-core/src/asm_lang/mod.rs b/sway-core/src/asm_lang/mod.rs index afe223f68c3..ba9b66090a4 100644 --- a/sway-core/src/asm_lang/mod.rs +++ b/sway-core/src/asm_lang/mod.rs @@ -37,6 +37,31 @@ use std::{ /// The column where the ; for comments starts const COMMENT_START_COLUMN: usize = 40; +fn fmt_opcode_and_comment( + opcode: String, + comment: &str, + fmtr: &mut fmt::Formatter<'_>, +) -> fmt::Result { + // We want the comment to be at the `COMMENT_START_COLUMN` offset to the right, + // to not interfere with the ASM but to be aligned. + // Some operations like, e.g., data section offset, can span multiple lines. + // In that case, we put the comment at the end of the last line, aligned. + let mut op_and_comment = opcode; + if !comment.is_empty() { + let mut op_length = match op_and_comment.rfind('\n') { + Some(new_line_index) => op_and_comment.len() - new_line_index - 1, + None => op_and_comment.len(), + }; + while op_length < COMMENT_START_COLUMN { + op_and_comment.push(' '); + op_length += 1; + } + write!(op_and_comment, "; {}", comment)?; + } + + write!(fmtr, "{op_and_comment}") +} + impl From<&AsmRegister> for VirtualRegister { fn from(o: &AsmRegister) -> Self { VirtualRegister::Virtual(o.name.clone()) @@ -46,7 +71,25 @@ impl From<&AsmRegister> for VirtualRegister { #[derive(Debug, Clone)] pub(crate) struct Op { pub(crate) opcode: Either, - /// A descriptive comment for ASM readability + /// A descriptive comment for ASM readability. + /// + /// Comments are a part of the compiler output and meant to + /// help both Sway developers interested in the generated ASM + /// and the Sway compiler developers. + /// + /// Comments follow these guidelines: + /// - they start with an imperative verb. E.g.: "allocate" and not "allocating". + /// - they start with a lowercase letter. E.g.: "allocate" and not "Allocate". + /// - they do not end in punctuation. E.g.: "store value" and not "store value.". + /// - they use full words. E.g.: "load return address" and not "load reta" or "load return addr". + /// - abbreviations are written in upper-case. E.g.: "ABI" and not "abi". + /// - names (e.g., function, argument, etc.) are written without quotes. E.g. "main" and not "'main'". + /// - assembly operations are written in lowercase. E.g.: "move" and not "MOVE". + /// - they are short and concise. + /// - if an operation is a part of a logical group of operations, start the comment + /// by a descriptive group name enclosed in square brackets and followed by colon. + /// The remaining part of the comment follows the above guidelines. E.g.: + /// "[bitcast to bool]: convert value to inverted boolean". pub(crate) comment: String, pub(crate) owning_span: Option, } @@ -54,7 +97,9 @@ pub(crate) struct Op { #[derive(Clone, Debug)] pub(crate) struct AllocatedAbstractOp { pub(crate) opcode: Either>, - /// A descriptive comment for ASM readability + /// A descriptive comment for ASM readability. + /// + /// For writing guidelines, see [Op::comment]. pub(crate) comment: String, pub(crate) owning_span: Option, } @@ -62,7 +107,9 @@ pub(crate) struct AllocatedAbstractOp { #[derive(Clone, Debug)] pub(crate) struct RealizedOp { pub(crate) opcode: AllocatedOpcode, - /// A descriptive comment for ASM readability + /// A descriptive comment for ASM readability. + /// + /// For writing guidelines, see [Op::comment]. pub(crate) comment: String, pub(crate) owning_span: Option, } @@ -211,7 +258,7 @@ impl Op { } } - /// Jumps to [Label] `label` if the given [VirtualRegister] `reg0` is not equal to zero. + /// Jumps to [Label] `label` if the given [VirtualRegister] `reg0` is not equal to zero. pub(crate) fn jump_if_not_zero(reg0: VirtualRegister, label: Label) -> Self { Op { opcode: Either::Right(OrganizationalOp::JumpIfNotZero(reg0, label)), @@ -220,6 +267,19 @@ impl Op { } } + /// Jumps to [Label] `label` if the given [VirtualRegister] `reg0` is not equal to zero. + pub(crate) fn jump_if_not_zero_comment( + reg0: VirtualRegister, + label: Label, + comment: impl Into, + ) -> Self { + Op { + opcode: Either::Right(OrganizationalOp::JumpIfNotZero(reg0, label)), + comment: comment.into(), + owning_span: None, + } + } + /// Dynamically jumps to a register value. pub(crate) fn jump_to_register( reg: VirtualRegister, @@ -983,17 +1043,7 @@ fn two_regs_imm_12( impl fmt::Display for Op { fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { - // We want the comment to always be 40 characters offset to the right to not interfere with - // the ASM but to be aligned. - let mut op_and_comment = self.opcode.to_string(); - if !self.comment.is_empty() { - while op_and_comment.len() < COMMENT_START_COLUMN { - op_and_comment.push(' '); - } - write!(op_and_comment, "; {}", self.comment)?; - } - - write!(fmtr, "{op_and_comment}") + fmt_opcode_and_comment(self.opcode.to_string(), &self.comment, fmtr) } } @@ -1113,17 +1163,7 @@ impl fmt::Display for VirtualOp { impl fmt::Display for AllocatedAbstractOp { fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { - // We want the comment to always be 40 characters offset to the right to not interfere with - // the ASM but to be aligned. - let mut op_and_comment = self.opcode.to_string(); - if !self.comment.is_empty() { - while op_and_comment.len() < COMMENT_START_COLUMN { - op_and_comment.push(' '); - } - write!(op_and_comment, "; {}", self.comment)?; - } - - write!(fmtr, "{op_and_comment}") + fmt_opcode_and_comment(self.opcode.to_string(), &self.comment, fmtr) } } diff --git a/sway-core/src/language/parsed/expression/asm.rs b/sway-core/src/language/parsed/expression/asm.rs index bc5f01b20e3..98bebcab667 100644 --- a/sway-core/src/language/parsed/expression/asm.rs +++ b/sway-core/src/language/parsed/expression/asm.rs @@ -15,6 +15,14 @@ pub struct AsmExpression { pub(crate) whole_block_span: Span, } +impl AsmExpression { + /// True if the [AsmExpression] has neither assembly operations nor + /// the return register specified. + pub fn is_empty(&self) -> bool { + self.body.is_empty() && self.returns.is_none() + } +} + impl EqWithEngines for AsmExpression {} impl PartialEqWithEngines for AsmExpression { fn eq(&self, other: &Self, ctx: &PartialEqWithEnginesContext) -> bool { diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index d8cf218d053..47478b59e7b 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -907,6 +907,13 @@ impl ty::TyExpression { let type_engine = ctx.engines.te(); let engines = ctx.engines(); + if asm.is_empty() { + handler.emit_warn(CompileWarning { + span: span.clone(), + warning_content: Warning::AsmBlockIsEmpty, + }); + } + // Various checks that we can catch early to check that the assembly is valid. For now, // this includes two checks: // 1. Check that no control flow opcodes are used. @@ -2617,7 +2624,7 @@ fn check_asm_block_validity( if reg.initializer.is_none() { let span = reg.name.span(); - // Emit warning if this register shadows a variable + // Emit warning if this register shadows a constant, or a configurable, or a variable. let temp_handler = Handler::default(); let decl = ctx.namespace().resolve_call_path_typed( &temp_handler, @@ -2630,11 +2637,25 @@ fn check_asm_block_validity( None, ); - if let Ok(ty::TyDecl::VariableDecl(decl)) = decl { + let shadowing_item = match decl { + Ok(ty::TyDecl::ConstantDecl(decl)) => { + let decl = ctx.engines.de().get_constant(&decl.decl_id); + Some((decl.name().into(), "Constant")) + } + Ok(ty::TyDecl::ConfigurableDecl(decl)) => { + let decl = ctx.engines.de().get_configurable(&decl.decl_id); + Some((decl.name().into(), "Configurable")) + } + Ok(ty::TyDecl::VariableDecl(decl)) => Some((decl.name.into(), "Variable")), + _ => None, + }; + + if let Some((item, item_kind)) = shadowing_item { handler.emit_warn(CompileWarning { span: span.clone(), - warning_content: Warning::UninitializedAsmRegShadowsVariable { - name: decl.name.clone(), + warning_content: Warning::UninitializedAsmRegShadowsItem { + constant_or_configurable_or_variable: item_kind, + item, }, }); } diff --git a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs index e96a20a1156..4ed2e8799ab 100644 --- a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs +++ b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs @@ -3691,6 +3691,7 @@ fn asm_block_to_asm_expression( let whole_block_span = asm_block.span(); let asm_block_contents = asm_block.contents.into_inner(); let (returns, return_type) = match asm_block_contents.final_expr_opt { + // If the return register is specified. Some(asm_final_expr) => { let asm_register = AsmRegister { name: asm_final_expr.register.as_str().to_owned(), @@ -3698,10 +3699,12 @@ fn asm_block_to_asm_expression( let returns = Some((asm_register, asm_final_expr.register.span())); let return_type = match asm_final_expr.ty_opt { Some((_colon_token, ty)) => ty_to_type_info(context, handler, engines, ty)?, + // If the return type is not specified, the ASM block returns `u64` as the default. None => TypeInfo::UnsignedInteger(IntegerBits::SixtyFour), }; (returns, return_type) } + // If the return register is not specified, the return type is unit, `()`. None => (None, TypeInfo::Tuple(Vec::new())), }; let registers = { diff --git a/sway-error/src/warning.rs b/sway-error/src/warning.rs index cef06a740cf..78a4ac8953b 100644 --- a/sway-error/src/warning.rs +++ b/sway-error/src/warning.rs @@ -4,7 +4,7 @@ use core::fmt; use either::Either; -use sway_types::{Ident, SourceId, Span, Spanned}; +use sway_types::{Ident, IdentUnique, SourceId, Span, Spanned}; // TODO: since moving to using Idents instead of strings, // the warning_content will usually contain a duplicate of the span. @@ -67,8 +67,14 @@ pub enum Warning { ShadowsOtherSymbol { name: Ident, }, - UninitializedAsmRegShadowsVariable { - name: Ident, + AsmBlockIsEmpty, + UninitializedAsmRegShadowsItem { + /// Text "Constant" or "Configurable" or "Variable". + /// Denotes the type of the `item` that shadows the uninitialized ASM register. + constant_or_configurable_or_variable: &'static str, + /// The name of the item that shadows the register, that points to the name in + /// the item declaration. + item: IdentUnique, }, OverridingTraitImplementation, DeadDeclaration, @@ -101,7 +107,6 @@ pub enum Warning { UnrecognizedAttribute { attrib_name: Ident, }, - NamespaceAttributeDeprecated, AttributeExpectedNumberOfArguments { attrib_name: Ident, received_args: usize, @@ -191,9 +196,8 @@ impl fmt::Display for Warning { NonScreamingSnakeCaseConstName { name } => { write!( f, - "Constant name \"{}\" is not idiomatic. Constant names should be SCREAMING_SNAKE_CASE, like \ + "Constant name \"{name}\" is not idiomatic. Constant names should be SCREAMING_SNAKE_CASE, like \ \"{}\".", - name, to_screaming_snake_case(name.as_str()), ) }, @@ -211,9 +215,14 @@ impl fmt::Display for Warning { f, "This shadows another symbol in this scope with the same name \"{name}\"." ), - UninitializedAsmRegShadowsVariable { name } => write!( + AsmBlockIsEmpty => write!( + f, + "This ASM block is empty." + ), + UninitializedAsmRegShadowsItem { constant_or_configurable_or_variable, item } => write!( f, - "This uninitialized register is shadowing a variable, you probably meant to also initialize it like \"{name}: {name}\"." + "This uninitialized register is shadowing a {}. You probably meant to also initialize it, like \"{item}: {item}\".", + constant_or_configurable_or_variable.to_ascii_lowercase(), ), OverridingTraitImplementation => write!( f, @@ -245,13 +254,6 @@ impl fmt::Display for Warning { ), MatchExpressionUnreachableArm { .. } => write!(f, "This match arm is unreachable."), UnrecognizedAttribute {attrib_name} => write!(f, "Unknown attribute: \"{attrib_name}\"."), - NamespaceAttributeDeprecated => write!(f, "Attribute namespace is deprecated.\n\ - You can use namespaces inside storage:\n\ - storage {{\n\ - \tmy_storage_namespace {{\n\ - \t\tfield: u64 = 1, \n\ - \t}}\n\ - }}"), AttributeExpectedNumberOfArguments {attrib_name, received_args, expected_min_len, expected_max_len } => write!( f, "Attribute: \"{attrib_name}\" expected {} argument(s) received {received_args}.", @@ -368,6 +370,48 @@ impl ToDiagnostic for CompileWarning { ] } }, + UninitializedAsmRegShadowsItem { constant_or_configurable_or_variable, item } => Diagnostic { + reason: Some(Reason::new(code(1), format!("Uninitialized ASM register is shadowing a {}", constant_or_configurable_or_variable.to_ascii_lowercase()))), + issue: Issue::warning( + source_engine, + self.span(), + format!("Uninitialized register \"{item}\" is shadowing a {} of the same name.", constant_or_configurable_or_variable.to_ascii_lowercase()), + ), + hints: { + let mut hints = vec![ + Hint::info( + source_engine, + item.span(), + format!("{constant_or_configurable_or_variable} \"{item}\" is declared here.") + ), + ]; + + hints.append(&mut Hint::multi_help( + source_engine, + &self.span(), + vec![ + format!("Are you trying to initialize the register to the value of the {}?", constant_or_configurable_or_variable.to_ascii_lowercase()), + format!("In that case, you must do it explicitly: `{item}: {item}`."), + format!("Otherwise, to avoid the confusion with the shadowed {}, consider renaming the register \"{item}\".", constant_or_configurable_or_variable.to_ascii_lowercase()), + ] + )); + + hints + }, + help: vec![], + }, + AsmBlockIsEmpty => Diagnostic { + reason: Some(Reason::new(code(1), "ASM block is empty".to_string())), + issue: Issue::warning( + source_engine, + self.span(), + "This ASM block is empty.".to_string(), + ), + hints: vec![], + help: vec![ + "Consider adding assembly instructions or a return register to the ASM block, or removing the block altogether.".to_string(), + ], + }, _ => Diagnostic { // TODO: Temporary we use self here to achieve backward compatibility. // In general, self must not be used and will not be used once we diff --git a/sway-ir/src/block.rs b/sway-ir/src/block.rs index b16e7212ad7..deea1096d16 100644 --- a/sway-ir/src/block.rs +++ b/sway-ir/src/block.rs @@ -120,7 +120,7 @@ impl Block { context.blocks[self.0].label = unique_label; } - /// Get the number of instructions in this block + /// Get the number of instructions in this block. pub fn num_instructions(&self, context: &Context) -> usize { context.blocks[self.0].instructions.len() } diff --git a/sway-ir/src/function.rs b/sway-ir/src/function.rs index f49665c146a..14bf40b615a 100644 --- a/sway-ir/src/function.rs +++ b/sway-ir/src/function.rs @@ -11,6 +11,7 @@ use std::fmt::Write; use rustc_hash::{FxHashMap, FxHashSet}; +use crate::InstOp; use crate::{ block::{Block, BlockIterator, Label}, constant::Constant, @@ -248,12 +249,44 @@ impl Function { } /// Return the number of instructions in this function. + /// + /// The [crate::InstOp::AsmBlock] is counted as a single instruction, + /// regardless of the number of [crate::asm::AsmInstruction]s in the ASM block. + /// E.g., even if the ASM block is empty and contains no instructions, it + /// will still be counted as a single instruction. + /// + /// If you want to count every ASM instruction as an instruction, use + /// `num_instructions_incl_asm_instructions` instead. pub fn num_instructions(&self, context: &Context) -> usize { self.block_iter(context) .map(|block| block.num_instructions(context)) .sum() } + /// Return the number of instructions in this function, including + /// the [crate::asm::AsmInstruction]s found in [crate::InstOp::AsmBlock]s. + /// + /// Every [crate::asm::AsmInstruction] encountered in any of the ASM blocks + /// will be counted as an instruction. The [crate::InstOp::AsmBlock] itself + /// is not counted but rather replaced with the number of ASM instructions + /// found in the block. In other words, empty ASM blocks do not count as + /// instructions. + /// + /// If you want to count [crate::InstOp::AsmBlock]s as single instructions, use + /// `num_instructions` instead. + pub fn num_instructions_incl_asm_instructions(&self, context: &Context) -> usize { + self.instruction_iter(context).fold(0, |num, (_, value)| { + match &value + .get_instruction(context) + .expect("We are iterating through the instructions.") + .op + { + InstOp::AsmBlock(asm, _) => num + asm.body.len(), + _ => num + 1, + } + }) + } + /// Return the function name. pub fn get_name<'a>(&self, context: &'a Context) -> &'a str { &context.functions[self.0].name diff --git a/sway-ir/src/instruction.rs b/sway-ir/src/instruction.rs index 4932f4359ff..325938cf8af 100644 --- a/sway-ir/src/instruction.rs +++ b/sway-ir/src/instruction.rs @@ -644,8 +644,8 @@ impl InstOp { pub fn may_have_side_effect(&self) -> bool { match self { - InstOp::AsmBlock(_, _) - | InstOp::Call(..) + InstOp::AsmBlock(asm, _) => !asm.body.is_empty(), + InstOp::Call(..) | InstOp::ContractCall { .. } | InstOp::FuelVm(FuelVmInstruction::Log { .. }) | InstOp::FuelVm(FuelVmInstruction::Smo { .. }) @@ -831,7 +831,7 @@ impl<'a, 'eng> InstructionInserter<'a, 'eng> { // XXX Maybe these should return result, in case they get bad args? // - /// Append a new [`Instruction::AsmBlock`] from `args` and a `body`. + /// Append a new [InstOp::AsmBlock] from `args` and a `body`. pub fn asm_block( self, args: Vec, diff --git a/sway-ir/src/optimize/inline.rs b/sway-ir/src/optimize/inline.rs index 4a3e97302f6..0c0fbf39485 100644 --- a/sway-ir/src/optimize/inline.rs +++ b/sway-ir/src/optimize/inline.rs @@ -125,7 +125,7 @@ pub fn fn_inline( // If the function is (still) small then also inline it. const MAX_INLINE_INSTRS_COUNT: usize = 4; - if func.num_instructions(ctx) <= MAX_INLINE_INSTRS_COUNT { + if func.num_instructions_incl_asm_instructions(ctx) <= MAX_INLINE_INSTRS_COUNT { return true; } @@ -245,7 +245,7 @@ pub fn is_small_fn( max_blocks.map_or(true, |max_block_count| { function.num_blocks(context) <= max_block_count }) && max_instrs.map_or(true, |max_instrs_count| { - function.num_instructions(context) <= max_instrs_count + function.num_instructions_incl_asm_instructions(context) <= max_instrs_count }) && max_stack_size.map_or(true, |max_stack_size_count| { function .locals_iter(context) diff --git a/sway-ir/src/parser.rs b/sway-ir/src/parser.rs index bb9de2e52ec..caa439c3fb7 100644 --- a/sway-ir/src/parser.rs +++ b/sway-ir/src/parser.rs @@ -235,13 +235,13 @@ mod ir_builder { / op_store() rule op_asm() -> IrAstOperation - = "asm" _ "(" _ args:(asm_arg() ** comma()) ")" _ ret:asm_ret()? meta_idx:comma_metadata_idx()? "{" _ + = "asm" _ "(" _ args:(asm_arg() ** comma()) ")" _ ret:asm_ret() meta_idx:comma_metadata_idx()? "{" _ ops:asm_op()* "}" _ { IrAstOperation::Asm( args, - ret.clone().map(|(ty, _)| ty).unwrap_or(IrAstTy::Unit), - ret.map(|(_, nm)| nm), + ret.0, + ret.1, ops, meta_idx ) @@ -455,8 +455,8 @@ mod ir_builder { IrAstAsmArgInit::Var(var) } - rule asm_ret() -> (IrAstTy, Ident) - = "->" _ ty:ast_ty() ret:id_id() { + rule asm_ret() -> (IrAstTy, Option) + = "->" _ ty:ast_ty() ret:id_id()? { (ty, ret) } diff --git a/sway-ir/src/printer.rs b/sway-ir/src/printer.rs index bcb957b2999..2478b3ef4f9 100644 --- a/sway-ir/src/printer.rs +++ b/sway-ir/src/printer.rs @@ -1094,13 +1094,14 @@ fn asm_block_to_doc( .collect(), )) .append( - return_name - .as_ref() - .map(|rn| { - Doc::text(format!(" -> {} {rn}", return_type.as_string(context))) - .append(md_namer.md_idx_to_doc(context, metadata)) - }) - .unwrap_or(Doc::Empty), + Doc::text(format!( + " -> {}{}", + return_type.as_string(context), + return_name + .as_ref() + .map_or("".to_string(), |rn| format!(" {rn}")) + )) + .append(md_namer.md_idx_to_doc(context, metadata)), ) .append(Doc::text(" {")), )) diff --git a/sway-ir/src/verify.rs b/sway-ir/src/verify.rs index be2fcc77fc2..f5bbb39cea9 100644 --- a/sway-ir/src/verify.rs +++ b/sway-ir/src/verify.rs @@ -227,131 +227,131 @@ impl<'a, 'eng> InstructionVerifier<'a, 'eng> { fn verify_instructions(&self) -> Result<(), IrError> { for ins in self.cur_block.instruction_iter(self.context) { let value_content = &self.context.values[ins.0]; - if let ValueDatum::Instruction(instruction) = &value_content.value { - if instruction.parent != self.cur_block { - return Err(IrError::InconsistentParent( - format!("Instr_{:?}", ins.0), - self.cur_block.get_label(self.context), - instruction.parent.get_label(self.context), - )); + let ValueDatum::Instruction(instruction) = &value_content.value else { + unreachable!("The value must be an instruction, because it is retrieved via block instruction iterator.") + }; + + if instruction.parent != self.cur_block { + return Err(IrError::InconsistentParent( + format!("Instr_{:?}", ins.0), + self.cur_block.get_label(self.context), + instruction.parent.get_label(self.context), + )); + } + + match &instruction.op { + InstOp::AsmBlock(..) => (), + InstOp::BitCast(value, ty) => self.verify_bitcast(value, ty)?, + InstOp::UnaryOp { op, arg } => self.verify_unary_op(op, arg)?, + InstOp::BinaryOp { op, arg1, arg2 } => self.verify_binary_op(op, arg1, arg2)?, + InstOp::Branch(block) => self.verify_br(block)?, + InstOp::Call(func, args) => self.verify_call(func, args)?, + InstOp::CastPtr(val, ty) => self.verify_cast_ptr(val, ty)?, + InstOp::Cmp(pred, lhs_value, rhs_value) => { + self.verify_cmp(pred, lhs_value, rhs_value)? } - match &instruction.op { - InstOp::AsmBlock(..) => (), - InstOp::BitCast(value, ty) => self.verify_bitcast(value, ty)?, - InstOp::UnaryOp { op, arg } => self.verify_unary_op(op, arg)?, - InstOp::BinaryOp { op, arg1, arg2 } => self.verify_binary_op(op, arg1, arg2)?, - InstOp::Branch(block) => self.verify_br(block)?, - InstOp::Call(func, args) => self.verify_call(func, args)?, - InstOp::CastPtr(val, ty) => self.verify_cast_ptr(val, ty)?, - InstOp::Cmp(pred, lhs_value, rhs_value) => { - self.verify_cmp(pred, lhs_value, rhs_value)? + InstOp::ConditionalBranch { + cond_value, + true_block, + false_block, + } => self.verify_cbr(cond_value, true_block, false_block)?, + InstOp::ContractCall { + params, + coins, + asset_id, + gas, + .. + } => self.verify_contract_call(params, coins, asset_id, gas)?, + // XXX move the fuelvm verification into a module + InstOp::FuelVm(fuel_vm_instr) => match fuel_vm_instr { + FuelVmInstruction::Gtf { index, tx_field_id } => { + self.verify_gtf(index, tx_field_id)? } - InstOp::ConditionalBranch { - cond_value, - true_block, - false_block, - } => self.verify_cbr(cond_value, true_block, false_block)?, - InstOp::ContractCall { - params, + FuelVmInstruction::Log { + log_val, + log_ty, + log_id, + } => self.verify_log(log_val, log_ty, log_id)?, + FuelVmInstruction::ReadRegister(_) => (), + FuelVmInstruction::JmpMem => (), + FuelVmInstruction::Revert(val) => self.verify_revert(val)?, + FuelVmInstruction::Smo { + recipient, + message, + message_size, coins, - asset_id, - gas, - .. - } => self.verify_contract_call(params, coins, asset_id, gas)?, - // XXX move the fuelvm verification into a module - InstOp::FuelVm(fuel_vm_instr) => match fuel_vm_instr { - FuelVmInstruction::Gtf { index, tx_field_id } => { - self.verify_gtf(index, tx_field_id)? - } - FuelVmInstruction::Log { - log_val, - log_ty, - log_id, - } => self.verify_log(log_val, log_ty, log_id)?, - FuelVmInstruction::ReadRegister(_) => (), - FuelVmInstruction::JmpMem => (), - FuelVmInstruction::Revert(val) => self.verify_revert(val)?, - FuelVmInstruction::Smo { - recipient, - message, - message_size, - coins, - } => self.verify_smo(recipient, message, message_size, coins)?, - FuelVmInstruction::StateClear { - key, - number_of_slots, - } => self.verify_state_clear(key, number_of_slots)?, - FuelVmInstruction::StateLoadWord(key) => { - self.verify_state_load_word(key)? - } - FuelVmInstruction::StateLoadQuadWord { - load_val: dst_val, - key, - number_of_slots, - } - | FuelVmInstruction::StateStoreQuadWord { - stored_val: dst_val, - key, - number_of_slots, - } => self.verify_state_access_quad(dst_val, key, number_of_slots)?, - FuelVmInstruction::StateStoreWord { - stored_val: dst_val, - key, - } => self.verify_state_store_word(dst_val, key)?, - FuelVmInstruction::WideUnaryOp { op, result, arg } => { - self.verify_wide_unary_op(op, result, arg)? - } - FuelVmInstruction::WideBinaryOp { - op, - result, - arg1, - arg2, - } => self.verify_wide_binary_op(op, result, arg1, arg2)?, - FuelVmInstruction::WideModularOp { - op, - result, - arg1, - arg2, - arg3, - } => self.verify_wide_modular_op(op, result, arg1, arg2, arg3)?, - FuelVmInstruction::WideCmpOp { op, arg1, arg2 } => { - self.verify_wide_cmp(op, arg1, arg2)? - } - FuelVmInstruction::Retd { .. } => (), - }, - InstOp::GetElemPtr { - base, - elem_ptr_ty, - indices, - } => self.verify_get_elem_ptr(&ins, base, elem_ptr_ty, indices)?, - InstOp::GetLocal(local_var) => self.verify_get_local(local_var)?, - InstOp::GetConfig(_, name) => self.verify_get_config(self.cur_module, name)?, - InstOp::IntToPtr(value, ty) => self.verify_int_to_ptr(value, ty)?, - InstOp::Load(ptr) => self.verify_load(ptr)?, - InstOp::MemCopyBytes { - dst_val_ptr, - src_val_ptr, - byte_len, - } => self.verify_mem_copy_bytes(dst_val_ptr, src_val_ptr, byte_len)?, - InstOp::MemCopyVal { - dst_val_ptr, - src_val_ptr, - } => self.verify_mem_copy_val(dst_val_ptr, src_val_ptr)?, - InstOp::Nop => (), - InstOp::PtrToInt(val, ty) => self.verify_ptr_to_int(val, ty)?, - InstOp::Ret(val, ty) => self.verify_ret(val, ty)?, - InstOp::Store { - dst_val_ptr, - stored_val, - } => self.verify_store(&ins, dst_val_ptr, stored_val)?, - }; - - // Verify the instruction metadata too. - self.context.verify_metadata(value_content.metadata)?; - } else { - unreachable!("Verify instruction is not an instruction."); - } + } => self.verify_smo(recipient, message, message_size, coins)?, + FuelVmInstruction::StateClear { + key, + number_of_slots, + } => self.verify_state_clear(key, number_of_slots)?, + FuelVmInstruction::StateLoadWord(key) => self.verify_state_load_word(key)?, + FuelVmInstruction::StateLoadQuadWord { + load_val: dst_val, + key, + number_of_slots, + } + | FuelVmInstruction::StateStoreQuadWord { + stored_val: dst_val, + key, + number_of_slots, + } => self.verify_state_access_quad(dst_val, key, number_of_slots)?, + FuelVmInstruction::StateStoreWord { + stored_val: dst_val, + key, + } => self.verify_state_store_word(dst_val, key)?, + FuelVmInstruction::WideUnaryOp { op, result, arg } => { + self.verify_wide_unary_op(op, result, arg)? + } + FuelVmInstruction::WideBinaryOp { + op, + result, + arg1, + arg2, + } => self.verify_wide_binary_op(op, result, arg1, arg2)?, + FuelVmInstruction::WideModularOp { + op, + result, + arg1, + arg2, + arg3, + } => self.verify_wide_modular_op(op, result, arg1, arg2, arg3)?, + FuelVmInstruction::WideCmpOp { op, arg1, arg2 } => { + self.verify_wide_cmp(op, arg1, arg2)? + } + FuelVmInstruction::Retd { .. } => (), + }, + InstOp::GetElemPtr { + base, + elem_ptr_ty, + indices, + } => self.verify_get_elem_ptr(&ins, base, elem_ptr_ty, indices)?, + InstOp::GetLocal(local_var) => self.verify_get_local(local_var)?, + InstOp::GetConfig(_, name) => self.verify_get_config(self.cur_module, name)?, + InstOp::IntToPtr(value, ty) => self.verify_int_to_ptr(value, ty)?, + InstOp::Load(ptr) => self.verify_load(ptr)?, + InstOp::MemCopyBytes { + dst_val_ptr, + src_val_ptr, + byte_len, + } => self.verify_mem_copy_bytes(dst_val_ptr, src_val_ptr, byte_len)?, + InstOp::MemCopyVal { + dst_val_ptr, + src_val_ptr, + } => self.verify_mem_copy_val(dst_val_ptr, src_val_ptr)?, + InstOp::Nop => (), + InstOp::PtrToInt(val, ty) => self.verify_ptr_to_int(val, ty)?, + InstOp::Ret(val, ty) => self.verify_ret(val, ty)?, + InstOp::Store { + dst_val_ptr, + stored_val, + } => self.verify_store(&ins, dst_val_ptr, stored_val)?, + }; + + // Verify the instruction metadata too. + self.context.verify_metadata(value_content.metadata)?; } + Ok(()) } diff --git a/sway-ir/tests/dce/copy_prop_1.ir b/sway-ir/tests/dce/copy_prop_1.ir index b2d31c695a6..ab69f049d8f 100644 --- a/sway-ir/tests/dce/copy_prop_1.ir +++ b/sway-ir/tests/dce/copy_prop_1.ir @@ -18,4 +18,4 @@ script { // regex: VAL=v\d+ -// not: mem_copy_val $VAL, VAL +// not: mem_copy_val $VAL, $VAL diff --git a/sway-ir/tests/dce/dce_dead_asm_block.ir b/sway-ir/tests/dce/dce_dead_asm_block.ir new file mode 100644 index 00000000000..3fb2f6e1403 --- /dev/null +++ b/sway-ir/tests/dce/dce_dead_asm_block.ir @@ -0,0 +1,184 @@ +// The IR code represents the below function. +// +// ASM blocks above the separation line must survive DCE because +// their either have side-effects (or better to say, could have), +// or their unit result is used. +// +// ASM blocks below the line must be optimized away. +// The call to `function` must remain, though. +// +// fn main() { +// poke(asm() { }); +// poke(asm() { zero }); +// poke(asm() { zero: () }); +// +// let arg = 11u64; +// asm(a: arg, b: arg, res) { +// add res a b; +// }; +// +// asm(a: arg, b: arg, res) { +// add res a b; +// res +// }; +// +// // ----------- +// +// asm () { } +// asm () { zero } +// asm() { zero: () } +// +// asm(arg: arg) { }; +// asm(arg: arg) { arg }; +// +// let a = asm() { }; +// let b = asm() { zero }; +// let c = asm(arg: arg) { }; +// let d = asm(arg: arg) { arg }; +// let e = asm(arg: arg) { arg: u32 }; +// +// let f = asm(arg: function()) { arg }; +// } + +// regex: VAL=v\d+ + +script { + entry fn main() -> () { + // check: local u64 arg + local u64 arg + // not: local () a + local () a + // not: local u64 b + local u64 b + // not: local () c + local () c + // not: local u64 d + local u64 d + // not: local u64 e + local u64 e + // not: local u64 f + local u64 f + + // check: entry() + entry(): + + // check: = asm() -> () + v0 = asm() -> () { + } + v1 = call poke_1(v0) + + // check: = asm() -> u64 zero + v2 = asm() -> u64 zero { + } + v3 = call poke_2(v2) + + // check: = asm() -> () zero + v4 = asm() -> () zero { + } + v5 = call poke_1(v4) + + v6 = get_local ptr u64, arg + v7 = const u64 11 + store v7 to v6 + v8 = get_local ptr u64, arg + v9 = load v8 + v10 = get_local ptr u64, arg + v11 = load v10 + + // check: = asm(a: $VAL, b: $VAL, res) -> () + v12 = asm(a: v9, b: v11, res) -> () { + add res a b + } + + v13 = get_local ptr u64, arg + v14 = load v13 + v15 = get_local ptr u64, arg + v16 = load v15 + + // check: = asm(a: $VAL, b: $VAL, res) -> u64 res + v17 = asm(a: v14, b: v16, res) -> u64 res { + add res a b + } + + // ----------- + + // not: asm + + // check: = asm() -> u64 zero + v2 = asm() -> u64 zero { + } + v3 = call poke_2(v2) + + v18 = asm() -> () { + } + v19 = asm() -> u64 zero { + } + v20 = asm() -> () zero { + } + v21 = get_local ptr u64, arg + v22 = load v21 + v23 = asm(arg: v22) -> () { + } + v24 = get_local ptr u64, arg + v25 = load v24 + v26 = asm(arg: v25) -> u64 arg { + } + v27 = asm() -> () { + } + v28 = get_local ptr (), a + store v27 to v28 + v29 = asm() -> u64 zero { + } + v30 = get_local ptr u64, b + store v29 to v30 + v31 = get_local ptr u64, arg + v32 = load v31 + v33 = asm(arg: v32) -> () { + } + v34 = get_local ptr (), c + store v33 to v34 + v35 = get_local ptr u64, arg + v36 = load v35 + v37 = asm(arg: v36) -> u64 arg { + } + v38 = get_local ptr u64, d + store v37 to v38 + v39 = get_local ptr u64, arg + v40 = load v39 + v41 = asm(arg: v40) -> u64 arg { + } + v42 = get_local ptr u64, e + store v41 to v42 + + // check: call function() + v43 = call function() + v44 = asm(arg: v43) -> u64 arg { + } + v45 = get_local ptr u64, f + store v44 to v45 + v46 = const unit () + + // not: asm + + // check: ret () + ret () v46 + } + + fn poke_1(_x: ()) -> () { + entry(_x: ()): + v0 = const unit () + ret () v0 + } + + fn poke_2(_x: u64) -> () { + entry(_x: u64): + v0 = const unit () + ret () v0 + } + + fn function() -> u64 { + entry(): + v0 = const u64 0 + ret u64 v0 + } +} diff --git a/sway-ir/tests/demote_misc/demote_wide_binary_ops_constants.ir b/sway-ir/tests/demote_misc/demote_wide_binary_ops_constants.ir index d326d543eb3..7a7981b0194 100644 --- a/sway-ir/tests/demote_misc/demote_wide_binary_ops_constants.ir +++ b/sway-ir/tests/demote_misc/demote_wide_binary_ops_constants.ir @@ -10,7 +10,6 @@ script { } } -// regex: VAL=v\d+ // regex: ID=[[:alpha:]0-9_]+ // check: v0 = get_local ptr u256, __wide_lhs diff --git a/sway-ir/tests/demote_misc/demote_wide_binary_ops_loads.ir b/sway-ir/tests/demote_misc/demote_wide_binary_ops_loads.ir index a2ca7aaf3b5..0e159fcd627 100644 --- a/sway-ir/tests/demote_misc/demote_wide_binary_ops_loads.ir +++ b/sway-ir/tests/demote_misc/demote_wide_binary_ops_loads.ir @@ -12,7 +12,6 @@ script { } } -// regex: VAL=v\d+ // regex: ID=[[:alpha:]0-9_]+ // check: local u256 __wide_lhs diff --git a/sway-ir/tests/demote_misc/demote_wide_cmp_constants.ir b/sway-ir/tests/demote_misc/demote_wide_cmp_constants.ir index c3fcd598b00..f0d43db2f21 100644 --- a/sway-ir/tests/demote_misc/demote_wide_cmp_constants.ir +++ b/sway-ir/tests/demote_misc/demote_wide_cmp_constants.ir @@ -8,7 +8,6 @@ script { } } -// regex: VAL=v\d+ // regex: ID=[[:alpha:]0-9_]+ // check: v0 = get_local ptr u256, __wide_lhs diff --git a/sway-ir/tests/demote_misc/demote_wide_mod_constants.ir b/sway-ir/tests/demote_misc/demote_wide_mod_constants.ir index 5375bdf7080..d78440ef647 100644 --- a/sway-ir/tests/demote_misc/demote_wide_mod_constants.ir +++ b/sway-ir/tests/demote_misc/demote_wide_mod_constants.ir @@ -12,7 +12,6 @@ script { } } -// regex: VAL=v\d+ // regex: ID=[[:alpha:]0-9_]+ // check: local u256 __wide_lhs diff --git a/sway-ir/tests/demote_misc/demote_wide_not_constants.ir b/sway-ir/tests/demote_misc/demote_wide_not_constants.ir index 46727c2dbe2..c8f08674e68 100644 --- a/sway-ir/tests/demote_misc/demote_wide_not_constants.ir +++ b/sway-ir/tests/demote_misc/demote_wide_not_constants.ir @@ -8,7 +8,6 @@ script { } } -// regex: VAL=v\d+ // regex: ID=[[:alpha:]0-9_]+ // check: v0 = get_local ptr u256, __wide_lhs diff --git a/sway-ir/tests/inline/big_asm_blocks.ir b/sway-ir/tests/inline/big_asm_blocks.ir new file mode 100644 index 00000000000..4bde773b71d --- /dev/null +++ b/sway-ir/tests/inline/big_asm_blocks.ir @@ -0,0 +1,104 @@ +// instrs 4 +// +// This test proves that https://github.com/FuelLabs/sway/issues/6332 is fixed. +// +// `test_function`s all must be inlined because the contain only one instruction and `ret`. +// `testf` must not be inlined. Although it has only a single `asm` instruction, that +// one has a large number of instructions. + +// regex: VAR=v\d+ +// regex: LABEL=[[:alpha:]0-9_]+ + +script { + // check: entry fn main() -> () + entry fn main() -> () { + entry(): + + // check: call testf() + // not: call test_function1() + v0 = call test_function1() + + // check: call testf() + // not: call test_function2() + v1 = call test_function2() + + // check: call testf() + // not: call test_function3() + v2 = call test_function3() + v3 = const unit () + ret () v3 + } + + // check: fn test_function1() -> () + fn test_function1() -> () { + entry(): + + // check: call testf() + v0 = call testf() + v1 = const unit () + ret () v1 + } + + // check: fn test_function2() -> bool + fn test_function2() -> bool { + entry(): + + // check: call testf() + v0 = call testf() + v1 = const bool true + ret bool v1 + } + + // check: fn test_function3() -> u64 + fn test_function3() -> u64 { + entry(): + + // check: call testf() + v0 = call testf() + v1 = const u64 0 + ret u64 v1 + } + + // check: fn testf() -> () + fn testf() -> () { + entry(): + v0 = asm(r1, r2) -> () { + movi r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + addi r1 r2 i1 + } + v1 = const unit () + ret () v1 + } +} diff --git a/sway-ir/tests/serialize/asm_block.ir b/sway-ir/tests/serialize/asm_block.ir new file mode 100644 index 00000000000..ef37a8da783 --- /dev/null +++ b/sway-ir/tests/serialize/asm_block.ir @@ -0,0 +1,23 @@ +script { + // check: main() -> u64 + entry fn main() -> u64 { + entry(): + + c0 = const u64 0 + + // check: = asm() -> () { + v0 = asm() -> () { + } + + // check: = asm() -> () zero { + v1 = asm() -> () zero { + } + + // check: = asm() -> u64 zero { + v2 = asm() -> u64 zero { + } + + rv = const u64 0 + ret u64 rv + } +} diff --git a/test/src/e2e_vm_tests/mod.rs b/test/src/e2e_vm_tests/mod.rs index b4f3930e81b..f52bba955c6 100644 --- a/test/src/e2e_vm_tests/mod.rs +++ b/test/src/e2e_vm_tests/mod.rs @@ -990,7 +990,12 @@ fn parse_test_toml(path: &Path, run_config: &RunConfig) -> Result"] +entry = "main.sw" +license = "Apache-2.0" +name = "asm_empty_block" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_empty_block/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_empty_block/src/main.sw new file mode 100644 index 00000000000..dc0662d5895 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_empty_block/src/main.sw @@ -0,0 +1,18 @@ +library; + +pub fn test() { + let _ = asm() { + }; + + let _ = asm() { }; + + let _ = asm(r1: 0) { }; + + let _ = asm() { zero }; + + let _ = asm() { zero: u32 }; + + let _ = asm(r1: 0, res) { + addi res r1 i0; + }; +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_empty_block/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_empty_block/test.toml new file mode 100644 index 00000000000..458c8abb30c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_empty_block/test.toml @@ -0,0 +1,17 @@ +category = "compile" +expected_warnings = 3 + +#check: $()warning +#sameln: $()ASM block is empty +#check: $()let _ = asm() { +#check: $()This ASM block is empty. + +#check: $()warning +#sameln: $()ASM block is empty +#check: $()let _ = asm() { }; +#nextln: $()This ASM block is empty. + +#check: $()warning +#sameln: $()ASM block is empty +#check: $()let _ = asm(r1: 0) { }; +#nextln: $()This ASM block is empty. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/Forc.lock new file mode 100644 index 00000000000..895eeb65a4f --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/Forc.lock @@ -0,0 +1,8 @@ +[[package]] +name = "asm_uninitialized_register_shadows_item" +source = "member" +dependencies = ["core"] + +[[package]] +name = "core" +source = "path+from-root-B978682C941EBFB4" diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/Forc.toml similarity index 79% rename from test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/Forc.toml rename to test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/Forc.toml index 69bfc30b4af..e99e6f10031 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/Forc.toml @@ -2,7 +2,7 @@ authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" -name = "deprecated_attribute" +name = "asm_uninitialized_register_shadows_item" implicit-std = false [dependencies] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/src/main.sw new file mode 100644 index 00000000000..e4200fb6d8e --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/src/main.sw @@ -0,0 +1,102 @@ +contract; + +configurable { + CONFIG: u64 = 0, +} + +pub fn test() { + let x = 0; + let y = 0; + + // Shadowing a variable. + + let _ = asm(x) { // Not used. + zero + }; + + let _ = asm(x) { // Used. + movi x i0; + }; + + let _ = asm(x: 0) { + zero + }; + + let _ = asm(x: x) { + zero + }; + + let _ = asm(x: y) { + zero + }; + + // Shadowing a configurable. + + let _ = asm(CONFIG) { // Not used. + zero + }; + + let _ = asm(CONFIG) { // Used. + movi CONFIG i0; + }; + + let _ = asm(CONFIG: 0) { + zero + }; + + let _ = asm(CONFIG: CONFIG) { + zero + }; + + let _ = asm(CONFIG: y) { + zero + }; + + // Shadowing a non-local constant. + + let _ = asm(G_CONST) { // Not used. + zero + }; + + let _ = asm(G_CONST) { // Used. + movi G_CONST i0; + }; + + let _ = asm(G_CONST: 0) { + zero + }; + + let _ = asm(G_CONST: G_CONST) { + zero + }; + + let _ = asm(G_CONST: y) { + zero + }; + + const L_CONST: u64 = 0; + + // Shadowing a local constant. + + let _ = asm(L_CONST) { // Not used. + zero + }; + + let _ = asm(L_CONST) { // Used. + movi L_CONST i0; + }; + + let _ = asm(L_CONST: 0) { + zero + }; + + let _ = asm(L_CONST: L_CONST) { + zero + }; + + let _ = asm(L_CONST: y) { + zero + }; +} + +const G_CONST: u64 = 0; \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/test.toml new file mode 100644 index 00000000000..a26b3fe2b10 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_uninitialized_register_shadows_item/test.toml @@ -0,0 +1,98 @@ +category = "compile" +expected_warnings = 8 + +#check: $()warning +#sameln: $()Uninitialized ASM register is shadowing a variable +#check: $()let x = 0; +#nextln: $()Variable "x" is declared here. +#check: $()let _ = asm(x) { // Not used. +#nextln: $()Uninitialized register "x" is shadowing a variable of the same name. +#nextln: $()Are you trying to initialize the register to the value of the variable? +#nextln: $()In that case, you must do it explicitly: `x: x`. +#nextln: $()Otherwise, to avoid the confusion with the shadowed variable, consider renaming the register "x". + +#check: $()warning +#sameln: $()Uninitialized ASM register is shadowing a variable +#check: $()let x = 0; +#nextln: $()Variable "x" is declared here. +#check: $()let _ = asm(x) { // Used. +#nextln: $()Uninitialized register "x" is shadowing a variable of the same name. +#nextln: $()Are you trying to initialize the register to the value of the variable? +#nextln: $()In that case, you must do it explicitly: `x: x`. +#nextln: $()Otherwise, to avoid the confusion with the shadowed variable, consider renaming the register "x". + +#not: let _ = asm(x: 0) +#not: let _ = asm(x: x) +#not: let _ = asm(x: y) + +#check: $()warning +#sameln: $()Uninitialized ASM register is shadowing a configurable +#check: $()CONFIG: u64 = 0, +#nextln: $()Configurable "CONFIG" is declared here. +#check: $()let _ = asm(CONFIG) { // Not used. +#nextln: $()Uninitialized register "CONFIG" is shadowing a configurable of the same name. +#nextln: $()Are you trying to initialize the register to the value of the configurable? +#nextln: $()In that case, you must do it explicitly: `CONFIG: CONFIG`. +#nextln: $()Otherwise, to avoid the confusion with the shadowed configurable, consider renaming the register "CONFIG". + +#check: $()warning +#sameln: $()Uninitialized ASM register is shadowing a configurable +#check: $()CONFIG: u64 = 0, +#nextln: $()Configurable "CONFIG" is declared here. +#check: $()let _ = asm(CONFIG) { // Used. +#nextln: $()Uninitialized register "CONFIG" is shadowing a configurable of the same name. +#nextln: $()Are you trying to initialize the register to the value of the configurable? +#nextln: $()In that case, you must do it explicitly: `CONFIG: CONFIG`. +#nextln: $()Otherwise, to avoid the confusion with the shadowed configurable, consider renaming the register "CONFIG". + +#not: let _ = asm(CONFIG: 0) +#not: let _ = asm(CONFIG: CONFIG) +#not: let _ = asm(CONFIG: y) + +#check: $()warning +#sameln: $()Uninitialized ASM register is shadowing a constant +#check: $()let _ = asm(G_CONST) { // Not used. +#nextln: $()Uninitialized register "G_CONST" is shadowing a constant of the same name. +#nextln: $()Are you trying to initialize the register to the value of the constant? +#nextln: $()In that case, you must do it explicitly: `G_CONST: G_CONST`. +#nextln: $()Otherwise, to avoid the confusion with the shadowed constant, consider renaming the register "G_CONST". +#check: $()const G_CONST: u64 = 0; +#nextln: $()Constant "G_CONST" is declared here. + +#check: $()warning +#sameln: $()Uninitialized ASM register is shadowing a constant +#check: $()let _ = asm(G_CONST) { // Used. +#nextln: $()Uninitialized register "G_CONST" is shadowing a constant of the same name. +#nextln: $()Are you trying to initialize the register to the value of the constant? +#nextln: $()In that case, you must do it explicitly: `G_CONST: G_CONST`. +#nextln: $()Otherwise, to avoid the confusion with the shadowed constant, consider renaming the register "G_CONST". +#check: $()const G_CONST: u64 = 0; +#nextln: $()Constant "G_CONST" is declared here. + +#not: let _ = asm(G_CONST: 0) +#not: let _ = asm(G_CONST: G_CONST) +#not: let _ = asm(G_CONST: y) + +#check: $()warning +#sameln: $()Uninitialized ASM register is shadowing a constant +#check: $()const L_CONST: u64 = 0; +#nextln: $()Constant "L_CONST" is declared here. +#check: $()let _ = asm(L_CONST) { // Not used. +#nextln: $()Uninitialized register "L_CONST" is shadowing a constant of the same name. +#nextln: $()Are you trying to initialize the register to the value of the constant? +#nextln: $()In that case, you must do it explicitly: `L_CONST: L_CONST`. +#nextln: $()Otherwise, to avoid the confusion with the shadowed constant, consider renaming the register "L_CONST". + +#check: $()warning +#sameln: $()Uninitialized ASM register is shadowing a constant +#check: $()const L_CONST: u64 = 0; +#nextln: $()Constant "L_CONST" is declared here. +#check: $()let _ = asm(L_CONST) { // Used. +#nextln: $()Uninitialized register "L_CONST" is shadowing a constant of the same name. +#nextln: $()Are you trying to initialize the register to the value of the constant? +#nextln: $()In that case, you must do it explicitly: `L_CONST: L_CONST`. +#nextln: $()Otherwise, to avoid the confusion with the shadowed constant, consider renaming the register "L_CONST". + +#not: let _ = asm(L_CONST: 0) +#not: let _ = asm(L_CONST: L_CONST) +#not: let _ = asm(L_CONST: y) diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/Forc.lock index 99cddc51e15..ff9e2c5ddbc 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/Forc.lock +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/Forc.lock @@ -1,8 +1,3 @@ [[package]] name = "asm_without_return" source = "member" -dependencies = ["core"] - -[[package]] -name = "core" -source = "path+from-root-8F74D3135E693ACB" diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/Forc.toml index a61e721b2cf..75dca95945d 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/Forc.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/Forc.toml @@ -4,6 +4,3 @@ entry = "main.sw" license = "Apache-2.0" name = "asm_without_return" implicit-std = false - -[dependencies] -core = { path = "../../../../../../../sway-lib-core" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/json_abi_oracle.json deleted file mode 100644 index fad72a08f17..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/json_abi_oracle.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "configurables": [], - "functions": [ - { - "attributes": null, - "inputs": [], - "name": "main", - "output": { - "name": "", - "type": 0, - "typeArguments": null - } - } - ], - "loggedTypes": [], - "messagesTypes": [], - "types": [ - { - "components": [], - "type": "()", - "typeId": 0, - "typeParameters": null - } - ] -} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/json_abi_oracle_new_encoding.json deleted file mode 100644 index e60dda965d4..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/json_abi_oracle_new_encoding.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "configurables": [], - "encoding": "1", - "functions": [ - { - "attributes": null, - "inputs": [], - "name": "main", - "output": { - "name": "", - "type": 0, - "typeArguments": null - } - } - ], - "loggedTypes": [], - "messagesTypes": [], - "types": [ - { - "components": [], - "type": "()", - "typeId": 0, - "typeParameters": null - } - ] -} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/src/main.sw index 5293a2ac763..7823d9009f5 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/src/main.sw @@ -1,6 +1,6 @@ -script; +library; -fn main() { +pub fn test() { asm() { }; @@ -8,4 +8,27 @@ fn main() { add r3 r1 r2; add r4 r2 r2; }; + + // These cases prove that https://github.com/FuelLabs/sway/issues/6354 is fixed. + poke(asm() { }); + + let arg_u8 = 11u8; + + poke(asm(a: arg_u8, b: arg_u8, res) { + add res a b; + }); + + let x = asm(a: arg_u8, b: arg_u8, res) { + add res a b; + }; + + poke(x); + + // Return the unit result of ASM block as function result. + asm(r1: 5, r2: 5, r3) { + add r3 r1 r2; + } } + +#[inline(never)] +fn poke(_x: T) { } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/test.toml index 8fb3859ce4c..f067d2a5f9e 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/asm_without_return/test.toml @@ -1,4 +1,2 @@ -category = "run" -expected_result = { action = "return", value = 0 } -expected_result_new_encoding = { action = "return_data", value = "" } -validate_abi = true +category = "compile" +expected_warnings = 2 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/Forc.lock deleted file mode 100644 index 1e81cf8a690..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/Forc.lock +++ /dev/null @@ -1,8 +0,0 @@ -[[package]] -name = "core" -source = "path+from-root-F66B05B51B812E20" - -[[package]] -name = "deprecated_attribute" -source = "member" -dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/src/main.sw deleted file mode 100644 index 73a67d733cd..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/src/main.sw +++ /dev/null @@ -1,7 +0,0 @@ -contract; - -#[namespace(my_namespace)] -storage { - v:u64 = 1, -} - diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/test.toml deleted file mode 100644 index c0685025b07..00000000000 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/deprecated_attribute/test.toml +++ /dev/null @@ -1,3 +0,0 @@ -category = "compile" - -expected_warnings = 2 diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw index 42605631ca1..d037c6dfb25 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw @@ -4,7 +4,7 @@ use basic_storage_abi::{BasicStorage, Quad}; #[cfg(experimental_new_encoding = false)] const CONTRACT_ID = 0x94db39f409a31b9f2ebcadeea44378e419208c20de90f5d8e1e33dc1523754cb; #[cfg(experimental_new_encoding = true)] -const CONTRACT_ID = 0xe3e3a00062ed46de91902cda85348b27b371158bb1d5b9a6ccebe7307be59ff4; // AUTO-CONTRACT-ID ../../test_contracts/basic_storage --release +const CONTRACT_ID = 0x2dbff1a516d8e0427cac759bbe80ce4dc5e55dc365a96441f80b6a6da8802a86; // AUTO-CONTRACT-ID ../../test_contracts/basic_storage --release fn main() -> u64 { let addr = abi(BasicStorage, CONTRACT_ID); diff --git a/test/src/ir_generation/tests/jmp_mem.sw b/test/src/ir_generation/tests/jmp_mem.sw index f4e998bea94..fd5f10a2a65 100644 --- a/test/src/ir_generation/tests/jmp_mem.sw +++ b/test/src/ir_generation/tests/jmp_mem.sw @@ -49,7 +49,7 @@ impl MyContract for Contract { // check: csiz $(len=$REG) $REG // check: ldc $REG $$zero $len -// check: lw $(target=$REG) $$hp i0 ; jmp_mem: Load MEM[$$hp] -// check: sub $(jmp_target_4=$REG) $target $$is ; jmp_mem: Subtract $$is since Jmp adds it back -// check: divi $(jmp_target=$REG) $jmp_target_4 i4 ; jmp_mem: Divide by 4 since Jmp multiplies by 4 -// check: jmp $jmp_target ; jmp_mem: Jump to computed value +// check: lw $(target=$REG) $$hp i0 +// check: sub $(jmp_target_4=$REG) $target $$is +// check: divi $(jmp_target=$REG) $jmp_target_4 i4 +// check: jmp $jmp_target