Skip to content

Commit

Permalink
implementing function calls (#330)
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

* function call sites, first real shot

* style and whitespace

* 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
  • Loading branch information
ivoysey committed Apr 23, 2021
1 parent b542e8a commit b4e8fd8
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 35 deletions.
22 changes: 15 additions & 7 deletions Obsidian_Runtime/src/main/yul_templates/object.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,34 @@ object "{{creationObject}}" {
{{#constructorHasParams}} let {{constructorParams}} := {{copyConstructorArguments}}() {{/constructorHasParams}}
{{implicitConstructor}}({{constructorParams}})
{{/notLibrary}}
{{! call constructor }}
{{! todo: write and call constructor }}
{{#deploy}}
{{call}}
{{/deploy}}
{{! functions related to constructor }}
{{#deployFunctions}}
{{code}}
{{/deployFunctions}}
{{codeCopy}}
{{defaultReturn}}
}
object "{{runtimeObject}}" {
code {
{{! init free memory pointer, see chatper "Layout in Memory" of the Solidity doc}}
{{memoryInitRuntime}}
{{! obtain which runtime function is called, https://solidity.readthedocs.io/en/latest/abi-spec.html#function-selector}}
{{#dispatch}}
{{! TODO 224 is a magic number offset to shift to follow the spec above; check that it's right }}
let selector := shr(224, calldataload(0))
{{dispatchCase}}
{{/dispatch}}

{{! todo: is 4 a magic number or should it be generated based on the object in question? check the ABI }}
if iszero(lt(calldatasize(), 4)) {
{
{{#dispatch}}
{{! TODO 224 is a magic number offset to shift to follow the spec above; check that it's right }}
let selector := shr(224, calldataload(0))
{{dispatchCase}}
{{/dispatch}}
}
if iszero(calldatasize()) { }
revert(0, 0)
{{! todo: this is deadcode for at least the empty contract but gets emitted anyway; trim it down somehow? }}
{{! these functions for memory management are produced by the solc IR backend for
Solidity programs analagous to our test Obsidian programs.
Expand Down Expand Up @@ -77,9 +85,9 @@ object "{{creationObject}}" {
{{#runtimeFunctions}}
{{code}}
{{/runtimeFunctions}}
{{defaultReturn}}
}
{{runtimeSubObjects}}
}
{{subObjects}}
}
}
20 changes: 7 additions & 13 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/CodeGenYul.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import edu.cmu.cs.obsidian.codegen.LiteralKind.LiteralKind

import java.io.{File, FileWriter}
import java.nio.file.{Files, Path, Paths}
import scala.annotation.tailrec
// note: some constructor names collide with edu.cmu.cs.obsidian.codegen.
// in those places we use the fully qualified name
import edu.cmu.cs.obsidian.Main.{findMainContract, findMainContractName}
Expand Down Expand Up @@ -102,14 +101,6 @@ object CodeGenYul extends CodeGenerator {
var statement_seq_deploy: Seq[YulStatement] = Seq()
var statement_seq_runtime: Seq[YulStatement] = Seq()

// memory init
val freeMemPointer = 64 // 0x40: currently allocated memory size (aka. free memory pointer)
val firstFreeMem = 128 // 0x80: first byte in memory not reserved for special usages
// the free memory pointer points to 0x80 initially
val initExpr = FunctionCall(Identifier("mstore"), Seq(ilit(freeMemPointer), ilit(firstFreeMem)))
statement_seq_deploy = statement_seq_deploy :+ ExpressionStatement(initExpr)
statement_seq_runtime = statement_seq_runtime :+ ExpressionStatement(initExpr) //todo add `:+ ExpressionStatement(callvaluecheck)` here, fix what breaks

// translate declarations
for (d <- contract.declarations) {
val (deploy_seq, runtime_seq) = translateDeclaration(d)
Expand Down Expand Up @@ -295,7 +286,6 @@ object CodeGenYul extends CodeGenerator {
Seq(edu.cmu.cs.obsidian.codegen.Assignment(Seq(retvar), binary_ap("or", binary_ap(s, e1id, e2id), binary_ap("eq", e1id, e2id))))
}

@tailrec
def translateExpr(retvar: Identifier, e: Expression): Seq[YulStatement] = {
e match {
case e: AtomicExpression =>
Expand Down Expand Up @@ -348,9 +338,13 @@ object CodeGenYul extends CodeGenerator {
case LessThanOrEquals(e1, e2) => geq_leq("slt", retvar, e1, e2)
case NotEquals(e1, e2) => translateExpr(retvar, LogicalNegation(Equals(e1, e2)))
}
case e@LocalInvocation(name, genericParams, params, args) =>
//val expr = FunctionCall(Identifier(name),args.map(x => translateExpr(e) match)) // todo iev working here
Seq()
case e@LocalInvocation(name, genericParams, params, args) => // todo: why are the middle two args not used?
val (seqs: Seq[Seq[YulStatement]], ids: Seq[edu.cmu.cs.obsidian.codegen.Expression]) =
args.map(p => {
val id = nextTemp()
(translateExpr(id, p), ExpressionStatement(id))
}).unzip
seqs.flatten :+ ExpressionStatement(FunctionCall(Identifier(name), ids))
case Invocation(recipient, genericParams, params, name, args, isFFIInvocation) =>
assert(assertion = false, "TODO: translation of " + e.toString + " is not implemented")
Seq()
Expand Down
14 changes: 5 additions & 9 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/FuncScope.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@ class FuncScope(f: FunctionDefinition) {
class Body(val code: String){}

val functionName: String = f.name
val arg0: String = if (f.parameters.nonEmpty) { f.parameters.head.name } else { "" }
var argRest: Array[Param] =
if (f.parameters.nonEmpty) {
f.parameters.drop(1).map(p => new Param(p.name)).toArray
} else {
Array[Param]()
}
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 hasRetVal: Boolean = f.returnVariables.nonEmpty
var retParams: String = if (hasRetVal) { f.returnVariables.head.name } else { "" }
var retParams: String = if (f.returnVariables.nonEmpty) { f.returnVariables.head.name } else { "" }
def params(): Array[Param] = argRest
def body(): Array[Body] = f.body.statements.map(s => new Body(s.toString)).toArray
}
3 changes: 3 additions & 0 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/Util.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import org.bouncycastle.util.encoders.Hex
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)
Expand Down
32 changes: 26 additions & 6 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/yulAST.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import edu.cmu.cs.obsidian.codegen.Util._

import java.io.{FileReader, StringWriter}


// reminder: use abstract class if want to create a base class that requires constructor arguments
sealed trait YulAST

Expand Down Expand Up @@ -49,7 +48,11 @@ case class Literal(kind: LiteralKind.LiteralKind, value: String, vtype: String)
//
// however, as of 12 April 2021, this produces a ton of warnings from solc about "user
// defined types are not yet supported"
value
kind match {
case edu.cmu.cs.obsidian.codegen.LiteralKind.number => value
case edu.cmu.cs.obsidian.codegen.LiteralKind.boolean => value
case edu.cmu.cs.obsidian.codegen.LiteralKind.string => quote(value)
}
}
}

Expand Down Expand Up @@ -161,7 +164,8 @@ case class YulObject(name: String, code: Code, subobjects: Seq[YulObject], data:
raw.replaceAll("&amp;", "&").
replaceAll("&gt;", ">").
replaceAll("&#10;", "\n").
replaceAll("&#61;", "=")
replaceAll("&#61;", "=").
replaceAll("&quot;", "\"")
}

// ObjScope and FuncScope are designed to facilitate mustache templates, with the following rules
Expand All @@ -181,7 +185,14 @@ case class YulObject(name: String, code: Code, subobjects: Seq[YulObject], data:
var dispatch = false
var dispatchArray: Array[codegen.Case] = Array[codegen.Case]()
var deployCall: Array[Call] = Array[Call]()
var memoryInitRuntime: String = ""

val freeMemPointer = 64 // 0x40: currently allocated memory size (aka. free memory pointer)
val firstFreeMem = 128 // 0x80: first byte in memory not reserved for special usages
// the free memory pointer points to 0x80 initially
var memoryInitRuntime: Expression = binary_ap("mstore", ilit(freeMemPointer), ilit(firstFreeMem))
var memoryInit: Expression = memoryInitRuntime

def callValueCheck(): YulStatement = callvaluecheck

def dispatchEntry(f: FunctionDefinition): Seq[YulStatement] = {
Seq(
Expand Down Expand Up @@ -232,7 +243,10 @@ case class YulObject(name: String, code: Code, subobjects: Seq[YulObject], data:
dispatchArray = dispatchArray :+ codegen.Case(hexlit(hashOfFunctionDef(f)), Block(dispatchEntry(f)))
case e: ExpressionStatement =>
e.expression match {
case f: FunctionCall => memoryInitRuntime = f.toString
case f: FunctionCall =>
//TODO what was this line doing?
//memoryInitRuntime = f.toString
()
case _ =>
assert(assertion = false, "TODO: " + e.toString())
() // TODO unimplemented
Expand All @@ -244,9 +258,15 @@ case class YulObject(name: String, code: Code, subobjects: Seq[YulObject], data:
}
}



def dispatchCase(): codegen.Switch = codegen.Switch(Identifier("selector"), dispatchArray.toSeq)

def defaultReturn(): FunctionCall = FunctionCall(Identifier("return"), Seq(ilit(0), ilit(0)))
val datasize: Expression = unary_ap("datasize", stringlit(runtimeObject))

def codeCopy() : Expression = triple("codecopy",ilit(0), unary_ap("dataoffset",stringlit(runtimeObject)), datasize)

def defaultReturn(): Expression = binary_ap("return", ilit(0), datasize)

class Func(val code: String) {}

Expand Down

0 comments on commit b4e8fd8

Please sign in to comment.