-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implementing returning expressions (#331)
* 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
Showing
8 changed files
with
438 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"gas" : 30000000, | ||
"gasprice" : "0x9184e72a000", | ||
"startingeth" : 5000000, | ||
"numaccts" : 1 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
182
src/main/scala/edu/cmu/cs/obsidian/codegen/CodeGenYul.scala
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(","))) | ||
} | ||
} |
Oops, something went wrong.