Skip to content

Commit

Permalink
implementing returning expressions (#331)
Browse files Browse the repository at this point in the history
* updating the test for #305

* adding cases for an exhaustive switch

* adding cases for an exhaustive switch

* end of day scraps, working on #305

* actual implementation of the hash for function names

* code style tweaks

* rename

* less hardecoded mapping from Obsidian to Yul types but it still needs work

* comment about outputting type annotations

* end of day scraps

* removing two big blocks of redundant code, neither of which was getting run

* removing possibly silly assertion

* pulling funcscope into its own file

* fixing selector case syntax

* removing an assertion about what functions can be called on that i think does not make sense

* comments and imports clean up

* adding some hard coded library functions to the mustache file, code to dispatch table

* adding json file so that travis will also run simple call as a test

* comment about boilerplate origin

* tightening up funcscope implementation

* callvalue check to util

* end of day scraps

* whitespace

* helper functions for binary and unary expressions; first crack at translating add, which led to #322

* function call sites, first real shot

* helper functions for binary and unary translations and the obvious translations with a couple of todos. stub of test case.

* scala style

* small changes to assignment

* adding other operations even though this does not work yet

* Adding back in a default case and some comments in the test file

* style and whitespace

* adding a todo noting repeated work so i dont forget. its a small optimization but an easy one

* further thoughts in a todo

* fixing a bug in less or equal and great or equal. its not the best fix but it will work for now

* removing some code that didn\'t pan out

* whitespace and scala style

* working on runtime object, missing ingredients in the deployed object

* moving memory initialization into the place it already had in the mustache file

* whitespace, scala style, changing syntax level for memory init to make it cleaner

* adding a todo

* adding code copy

* removing the json file for prim ops; that file parses but causes travis to fail because of #327 and #328 but i want to get this code merged before fixng those issues

* renaming helpers for consistency

* variable arguments! neat!

* cleaning up some redundant helper functions

* adding tiny test case to work on returning expressions

* whitespace and scala style

* adding a type annotation, tidying up some old code to use new helpers

* comment

* possible fix for keccak256 use on functions with args

* comments

* fixing todo about multiple return variables

* slightly more whitespace in function output

* more whitespace in the output

* adding an argument to translateStatement so that i can thread through information about the return variables that are in scope

* fixing a bug in Assignment, starting on a bug in VariableDeclaration. there was a copy and paste bug that included let in assignment, making it a declaration, so everywhere that we use assignment might be either one of the two.

* reworking the definition of VariableDeclaration

* quashing a warning, scala style and whitespace

* adding declarations

* adding a declaration, using helpers that ive already written

* first bit of updating the dispatch table entry creation

* scraps

* scraps

* fixing a typo and a warning

* updating mustache file to remove the boiler plate we now generate

* adding a tostring for typed names; updating the tostring for blocks; adding comments to the dispatchEntry function; adding code to generate the abi encode functions needed and to keep track of which ones to generate as we build the dispatch table

* helper functions for decls, renaming the previous now that there are two

* typo and whitespace

* adding json so the return example gets run in Travis, too

* removing dead code

* Scraps

* Adding a mapping of function names to the number of things they return so that the local invocations can declare temporary variables appropriately

* missing declaration that travis caught; missing edge case for if there are no returns

* scraps

* addressing all the comments in the PR review except getting rid of the mapping i'm building for functions in favor of using the AST

* threading a checked table through down to translateExpression

* threading the contract name through, removing the mapping
  • Loading branch information
ivoysey committed May 3, 2021
1 parent b4e8fd8 commit 979d4c1
Show file tree
Hide file tree
Showing 8 changed files with 438 additions and 130 deletions.
2 changes: 1 addition & 1 deletion Obsidian_Runtime/src/main/yul_templates/function.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ structures of these templates are referenced from github repo
ethereum/solidity/libsolidity/codegen/ir/IRGenerator.cpp
(https://github.com/ethereum/solidity/blob/develop/libsolidity/codegen/ir/IRGenerator.cpp) }}

function {{functionName}}({{arg0}}{{#params}},{{name}}{{/params}}){{#hasRetVal}}->{{retParams}}{{/hasRetVal}}{
function {{functionName}}({{arg0}}{{#params}}, {{name}}{{/params}}){{#hasRetVal}} -> {{retParams}}{{/hasRetVal}} {
{{#body}}
{{code}}
{{/body}}
Expand Down
14 changes: 10 additions & 4 deletions Obsidian_Runtime/src/main/yul_templates/object.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,7 @@ object "{{creationObject}}" {
function abi_decode_tuple(headStart, dataEnd) {
if slt(sub(dataEnd, headStart), 0) { revert(0, 0) }
}
function abi_encode_tuple_to_fromStack(headStart ) -> tail {
tail := add(headStart, 0)
}
{{abiEncodeTupleFuncs}}
function allocate_memory(size) -> memPtr {
memPtr := allocate_unbounded()
Expand Down Expand Up @@ -82,6 +79,15 @@ object "{{creationObject}}" {
result := and(add(value, 31), not(31))
}
function abi_encode_t_uint256_to_t_uint256_fromStack(value, pos) {
mstore(pos, cleanup_t_uint256(value))
}
{{! the "cleanup" functions enforce the invariants on the types from the abi wrt how they're encoded;
with uint256, there are none, so it just returns. }}
function cleanup_t_uint256(value) -> cleaned {
cleaned := value
}
{{#runtimeFunctions}}
{{code}}
{{/runtimeFunctions}}
Expand Down
6 changes: 6 additions & 0 deletions resources/tests/GanacheTests/Return.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"gas" : 30000000,
"gasprice" : "0x9184e72a000",
"startingeth" : 5000000,
"numaccts" : 1
}
9 changes: 9 additions & 0 deletions resources/tests/GanacheTests/Return.obs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
main contract Return {
transaction f() returns int{
return (4+4);
}
transaction g(){
f();
return;
}
}
182 changes: 117 additions & 65 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/CodeGenYul.scala

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/FuncScope.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package edu.cmu.cs.obsidian.codegen

class FuncScope(f: FunctionDefinition) {
class Param(val name: String){}
class Body(val code: String){}
class Param(val name: String) {}

class Body(val code: String) {}

val functionName: String = f.name
val (arg0, argRest) = f.parameters match {
case hd +: tl => (hd, tl.map(p => new Param(p.name)).toArray)
case _ => ("", Array[Param]())
}

// TODO assume only one return variable for now
var retParams: String = if (f.returnVariables.nonEmpty) { f.returnVariables.head.name } else { "" }
var hasRetVal: Boolean = f.returnVariables.nonEmpty
var retParams: String = f.returnVariables.map(v => v.name).mkString(", ")

def params(): Array[Param] = argRest

def body(): Array[Body] = f.body.statements.map(s => new Body(s.toString)).toArray
}
193 changes: 156 additions & 37 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/Util.scala
Original file line number Diff line number Diff line change
@@ -1,49 +1,168 @@
package edu.cmu.cs.obsidian.codegen

import edu.cmu.cs.obsidian.codegen
import edu.cmu.cs.obsidian.typecheck.ObsidianType
import org.bouncycastle.jcajce.provider.digest.Keccak
import org.bouncycastle.util.encoders.Hex

/* utility functions shared between yulAST and CodeGenYul */
object Util {
def brace(str: String): String = s"{$str}"
def paren(str: String): String = s"($str)"
def quote(str: String): String = "\"" + str + "\"" // escape characters are known to not work with string interpolation
def ilit(i: Int): Literal = Literal(LiteralKind.number, i.toString, "int")
def blit(b : Boolean): Literal = Literal(LiteralKind.boolean, b.toString, "bool")
def hexlit(s: String): Literal = Literal(LiteralKind.number, s, "int")
def stringlit(s: String): Literal = Literal(LiteralKind.string, s, "string")
def callvaluecheck: YulStatement = codegen.If(FunctionCall(Identifier("callvalue"), Seq()), Block(Seq(ExpressionStatement(FunctionCall(Identifier("revert"), Seq(ilit(0), ilit(0)))))))
def unary_ap(n: String, e: Expression): Expression = FunctionCall(Identifier(n),Seq(e))
def binary_ap(n : String, e1: Expression, e2: Expression): Expression = FunctionCall(Identifier(n), Seq(e1, e2))
def triple(n : String, e1: Expression, e2: Expression, e3: Expression): Expression = FunctionCall(Identifier(n), Seq(e1, e2, e3))
def store_then_ret(retvar:Identifier, e:Expression) = Seq(edu.cmu.cs.obsidian.codegen.Assignment(Seq(retvar),e)) //todo this may not be needed once i work out more cases

val true_lit: Literal = blit(true)
val false_lit: Literal = blit(false)

def mapObsTypeToABI(ntype: String): String = {
// todo: this covers the primitive types from ObsidianType.scala but is hard to maintain because
// it's basically hard coded, and doesn't traverse the structure of more complicated types.
ntype match {
case "bool" => "boolean"
case "int" => "u256"
case "string" => "string"
case "Int256" => "int256"
case "unit" => assert(assertion = false, "unimplemented: unit type not encoded in Yul"); ""
case _ => assert(assertion = false, "yul codegen encountered an obsidian type without a mapping to the ABI"); ""
/**
* wrap a string in balanced braces
*
* @param str the string to wrap
* @return the string inside balanced braces
*/
def brace(str: String): String = s"{$str}"

/**
* wrap a string in balanced parentheses
*
* @param str the string to wrap
* @return the string inside balanced parentheses
*/
def paren(str: String): String = s"($str)"

/**
* wrap a string in balanced quotes
*
* @param str the string to wrap
* @return the string inside balanced quotes
*/
def quote(str: String): String = "\"" + str + "\"" // escape characters are known to not work with string interpolation

/**
* shorthand for building Yul integer literals
*
* @param i the scala integer
* @return the corresponding Yul integer literal
*/
def intlit(i: Int): Literal = Literal(LiteralKind.number, i.toString, "int")

/**
* shorthand for building Yul boolean literals
*
* @param b the scala integer
* @return the corresponding Yul boolean literal
*/
def boollit(b: Boolean): Literal = Literal(LiteralKind.boolean, b.toString, "bool")

/**
* shorthand for building Yul hex literals
*
* @param s the scala hex
* @return the corresponding Yul hex literal
*/
def hexlit(s: String): Literal = Literal(LiteralKind.number, s, "int")

/**
* shorthand for building Yul string literals
*
* @param s the scala string
* @return the corresponding Yul string literal
*/
def stringlit(s: String): Literal = Literal(LiteralKind.string, s, "string")

/**
* shorthand for building Yul function applications
*
* @param n the name of the Yul function to apply
* @param es the possibly empty sequence of arguments for the function
* @return the expression that applies the function to the arguments
*/
def apply(n: String, es: Expression*): Expression = FunctionCall(Identifier(n), es)

/**
* @return the yul call value check statement, which makes sure that funds are not spent inappropriately
*/
def callvaluecheck: YulStatement = codegen.If(apply("callvalue"), Block(Seq(ExpressionStatement(apply("revert", intlit(0), intlit(0))))))

/**
* shorthand for bulding yul assignment statements, here assigning one expression to just one
* identifier
*
* @param id the identifier to be assigned
* @param e the expression to assign to it
* @return the Yul assignment expression
*/
def assign1(id: Identifier, e: Expression): Assignment = codegen.Assignment(Seq(id), e)

/**
* shorthand for building the yul expression that declares one variable without giving it a type
* or an initial value
*
* @param id the name of the variable to be declared
* @return the expression declaring the variable
*/
def decl_0exp(id: Identifier): VariableDeclaration = VariableDeclaration(Seq((id, None)), None)


/**
* shorthand for building the yul expression that declares a sequence (non-empty) of identifiers
* with an initial value but no typing information
*
* @param id the identifiers to be declared, which cannot be the empty sequence
* @param e the expression to assign the identifiers to as an initial value
* @return the Yul expression for the declaration
*/
def decl_nexp(id: Seq[Identifier], e: Expression): VariableDeclaration = {
assert(id.nonEmpty, "internal error: tried to build a declaration with no identifiers")
VariableDeclaration(id.map(i => (i, None)), Some(e))
}

/**
* shorthand for building the yul expression that declares just identifier
* with an initial value but no typing information
*
* @param id the identifier to be declared
* @param e the expression to assign the identifier to as an initial value
* @return the Yul expression for the declaration
*/
def decl_1exp(id: Identifier, e: Expression): VariableDeclaration = decl_nexp(Seq(id), e)

def mapObsTypeToABI(ntype: String): String = {
// todo: this covers the primitive types from ObsidianType.scala but is hard to maintain because
// it's basically hard coded, and doesn't traverse the structure of more complicated types.
//
// see https://docs.soliditylang.org/en/latest/abi-spec.html#types
ntype match {
case "bool" => "bool"
case "int" => "u256"
case "string" => "string"
case "Int256" => "int256"
case "unit" => assert(assertion = false, "unimplemented: unit type not encoded in Yul"); ""
case _ => assert(assertion = false, "yul codegen encountered an obsidian type without a mapping to the ABI"); ""
}
}
}

def functionRename(name: String): String = {
name //todo some sort of alpha variation here combined with consulting a mapping
}

def keccak256(s: String): String = {
val digestK: Keccak.Digest256 = new Keccak.Digest256()
s"0x${Hex.toHexString(digestK.digest(s.getBytes).slice(0, 4))}" //todo: i'm not sure if it should be the first or last 4.
}
/**
* returns the width of an obsidian type. right now this is always 1 because obsidian
* does not currently implement tuples. when it does, updating this function will cause yul
* codegen to correctly emit code that uses tuples in yul.
*
* @param t the obsidian type of interest
* @return the width of the type
*/
def obsTypeToWidth(t: ObsidianType): Int = { 1 }

def hashOfFunctionDef(f: FunctionDefinition): String = {
keccak256(f.name + paren(f.parameters.map(p=>mapObsTypeToABI(p.ntype)).mkString(" ")))
}
def functionRename(name: String): String = {
name //todo some sort of alpha variation here combined with consulting a mapping; consult the ABI
}

def keccak256(s: String): String = {
val digestK: Keccak.Digest256 = new Keccak.Digest256()
s"0x${Hex.toHexString(digestK.digest(s.getBytes).slice(0, 4))}"
}

def hashOfFunctionDef(f: FunctionDefinition): String = {
/* The first four bytes of the call data for a function call specifies the function to be
called. It is the first (left, high-order in big-endian) four bytes of the Keccak-256 hash
of the signature of the function. The signature is defined as the canonical expression of
the basic prototype without data location specifier, i.e. the function name with the
parenthesised list of parameter types. Parameter types are split by a single comma -
no spaces are used. */
// todo: the keccak256 implementation seems to agree with solc, but it's never been run on functions with arguments.
keccak256(f.name + paren(f.parameters.map(p => mapObsTypeToABI(p.ntype)).mkString(",")))
}
}

0 comments on commit 979d4c1

Please sign in to comment.