Skip to content
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

Merged
merged 14 commits into from
Mar 23, 2022

Conversation

mohammadfawaz
Copy link
Contributor

@mohammadfawaz mohammadfawaz commented Mar 20, 2022

This change basically enables contract calls for IR. Main changes

  • Introduce a new IR instruction contract_call to call a contract. E.g.:
    v12 = contract_call get_b256<42123b96>{addr: v8, coins: v9, asset_id: v10, gas:v11}(v7)
    • note the selector ID, contract address, coins, asset_id, gas, and then the user arguments.
  • Handle asm generation for contract method arguments on both the caller side and the callee side.

I tested the contract tests manually with --use-ir and they seemed okay.

@adlerjohn adlerjohn added enhancement New feature or request compiler General compiler. Should eventually become more specific as the issue is triaged compiler: ir IRgen and sway-ir including optimization passes labels Mar 20, 2022
Copy link
Contributor

@adlerjohn adlerjohn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge conflicts 😬

@mohammadfawaz
Copy link
Contributor Author

Question: the default value for gas is $cgas but it's unclear to me how to represent that in IR. For now, I'm setting the value to zero and interpreting that as $cgas in codegen. That implies that if the user sets gas to 0 in Sway, then $cgas will be forwarded instead of 0. @otrho @adlerjohn thoughts?

@@ -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
Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

@adlerjohn
Copy link
Contributor

For now, I'm setting the value to zero and interpreting that as $cgas in codegen. That implies that if the user sets gas to 0 in Sway, then $cgas will be forwarded instead of 0.

You could instead set it to some max value (e.g. u64 max), then in the codegen set it to $cgas?

Copy link
Contributor

@otrho otrho left a 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.

@@ -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
Copy link
Contributor

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.

@otrho
Copy link
Contributor

otrho commented Mar 22, 2022

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 CALL opcode, with just 4 regular parameters.

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 from_ir.rs: AsmBuilder::compile_contract_call(), which is a 250 line function here, would be relieved.

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.

@mohammadfawaz
Copy link
Contributor Author

The above has been redesigned. The contract call instruction is now much simpler:
v1 = contract_call v2, v3, v4, v5 where the values match the registers used by the CALL VM instruction.
IRGen will basically create a pointer to a list of parameters (address, selector, and user parameters) and use that in the
contract_call instruction.

An instruction called read_register was also added that allows reading a special register from the VM. For now, we only need $cgas to set the default value for gas in the contract_call instruction. In the future, there may be other uses it read_register.

v23 = const u64 0
v24 = const b256 0x0000000000000000000000000000000000000000000000000000000000000000
v25 = const u64 20000
v26 = contract_call v22, v23, v24, v25
Copy link
Contributor Author

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.

Copy link
Contributor

@otrho otrho Mar 23, 2022

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.

Copy link
Contributor

@otrho otrho left a 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())
Copy link
Contributor

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)
}
Copy link
Contributor

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)),
Copy link
Contributor

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),
)),
Copy link
Contributor

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... 🙂

@mohammadfawaz
Copy link
Contributor Author

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.

Thanks Toby. I will merge this now then and will address your comments in a separate PR tomorrow.

@mohammadfawaz mohammadfawaz merged commit dba70a3 into master Mar 23, 2022
@mohammadfawaz mohammadfawaz deleted the mohammadfawaz/contract_call_with_ir branch March 23, 2022 03:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler: ir IRgen and sway-ir including optimization passes compiler General compiler. Should eventually become more specific as the issue is triaged enhancement New feature or request
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

3 participants