-
Notifications
You must be signed in to change notification settings - Fork 5.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Contracts and contract calls in IR #976
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge conflicts 😬
Question: the default value for |
sway-ir/src/instruction.rs
Outdated
@@ -99,6 +109,7 @@ impl Instruction { | |||
match self { | |||
Instruction::AsmBlock(asm_block, _) => asm_block.get_type(context), | |||
Instruction::Call(function, _) => Some(context.functions[function.0].return_type), | |||
Instruction::ContractCall { .. } => None, // TODO fixed this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@otrho thoughts on this? I don't have the type of this here so I probably need to carry it over in the AST. For function call, you've done this by looking at the body of the function definition. For contract calls, we can't really do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the Sway side the call returns a typed value though, which can be determined by the ABI I guess. So we might need to either bundle up the return type into the instruction since we have the selector there, it can be fetched from the ABI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that not all contract calls will have access to the ABI at compile time. E.g. a smart contract wallet that simply calls a provided contract after authentication is not going to know the ABI of all possible contract it will call when it's compiled. ABI casting implies knowing the ABI, but a use of the CALL
instruction does not.
You could instead set it to some max value (e.g. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great, thought my initial impression is that there's too much going on in from_ir.rs
and the ContractCall
instruction in the IR is too big. I'll discuss this offline with you some more though.
sway-ir/src/instruction.rs
Outdated
@@ -99,6 +109,7 @@ impl Instruction { | |||
match self { | |||
Instruction::AsmBlock(asm_block, _) => asm_block.get_type(context), | |||
Instruction::Call(function, _) => Some(context.functions[function.0].return_type), | |||
Instruction::ContractCall { .. } => None, // TODO fixed this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the Sway side the call returns a typed value though, which can be determined by the ABI I guess. So we might need to either bundle up the return type into the instruction since we have the selector there, it can be fetched from the ABI.
I don't want seem authoritarian because 'this isn't how I would do it' so push back if this is unreasonable... but I'd prefer the contract call instruction to resemble the The coins, asset and gas would just be the 2nd-4th args. The first arg would be a struct pointer which IRgen sets up with the address, selector and user args. Much of the complexity in Having the named special args in the curly braces is a bit out of place and pretty much unnecessary IMO. And the issue around a default for gas would be handled by the compiler -- if it requires the value from the register then it injects an ASM block to fetch it, or even better, we introduce a simple instruction which fetches the register value for us. |
The above has been redesigned. The contract call instruction is now much simpler: An instruction called |
v23 = const u64 0 | ||
v24 = const b256 0x0000000000000000000000000000000000000000000000000000000000000000 | ||
v25 = const u64 20000 | ||
v26 = contract_call v22, v23, v24, v25 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@otrho should we go with or with something like:
v26 = contract_call method_name(v22, v23, v24, v25)
?
We can easily do both. The only downside of the above is that it looks like a regular method call instead of an instruction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to add the method name for readability then it could just be an ignored identifier parameter, like
_ = contract_call method_name, v22, v23, v24, v25
.
Or if we want to add it to be useful for tooling then it could be added as metadata.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do much prefer this. Even though the 250 line func in from_ir.rs
became a 180 line func in optimize.rs
I think it's better to have the complexity there.
A few comments but nothing urgent. Either this or my IR verification PR is going to break the other, though they might still not conflict... might be better to merge this first since I know how to fix the aggregate stuff and can add the verification for these new ops.
|
||
self.reg_map.insert(*instr_val, instr_reg); | ||
|
||
ok((), Vec::new(), Vec::new()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need for this to return a CompileResult
when it can't fail.
rule op_read_register() -> IrAstOperation | ||
= "read_register" _ reg_name:id() { | ||
IrAstOperation::ReadRegister(reg_name) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be tempted to validate the register names here, rather than just letting them be any id
.
)) | ||
.append(match span_md_idx { | ||
None => Doc::Empty, | ||
Some(_) => Doc::text(md_namer.meta_as_string(context, span_md_idx, true)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way I've done the metadata elsewhere is to let md_namer.meta_as_string()
return an empty string on None
, also letting it add the comma on Some
. The match
isn't needed at all then.
namer.name(context, ins_value), | ||
reg_name, | ||
md_namer.meta_as_string(context, span_md_idx, true), | ||
)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which is exactly what you've done here... 🙂
Thanks Toby. I will merge this now then and will address your comments in a separate PR tomorrow. |
This change basically enables contract calls for IR. Main changes
contract_call
to call a contract. E.g.:v12 = contract_call get_b256<42123b96>{addr: v8, coins: v9, asset_id: v10, gas:v11}(v7)
I tested the contract tests manually with
--use-ir
and they seemed okay.