Skip to content

Commit

Permalink
improve ability to output yul from internal AST representation (#316)
Browse files Browse the repository at this point in the history
* removing unused imports

* this will make the indentation a little better, but its not a real solution

* hacking on the mustache file and AST translation for #310

* end of day scraps and comments to myself for where to start Monday

* scraps and notes to myself

* adding assert false and first cut at outputting a default return; removing dead comments and latenight code that didnt compile

* default return, now not in AST form and at the bottom

* adding asserts before more unit values

* adding cases

* hoisting to a function

* type, adding a fully qualified name to satisfy the IDE

* scraps, comments, code hygine

* scraps, comments, code hygine

* scraps, comments, code hygine

* massaging whitespace

* fussing with syntax

* committing to overriding toString

* overriding some toStrings

* code hygine

* less manual toString for function calls

* new implementation of function strings seems good

* combinator for integer literals

* removing the code i pushed into the object structure

* noting a bug to fix in the future

* moving code around a little, working on comments

* finessing output for switch

* whitespace

* i may have deprecated this code; check tomorrow

* scraps, clean up before merging #315

* cleaning up todos and code quality for #315

* yulString object was dead code

* lifting repeated code, adjusting comments

* cleaning up assignment case

* removing repeated code

* removing repeated code

* removing needless tostring

* adding type annotations, removing needless parens, replacing iteration that misses cases with a map that works

* braces and parens

* a little more code clean up for #315

* string interpolation
  • Loading branch information
ivoysey committed Apr 9, 2021
1 parent 2fc00da commit 646dfe8
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 277 deletions.
6 changes: 3 additions & 3 deletions Obsidian_Runtime/src/main/yul_templates/function.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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}}{
{{#body}}
{{code}}
{{/body}}
{{#body}}
{{code}}
{{/body}}
}
2 changes: 2 additions & 0 deletions Obsidian_Runtime/src/main/yul_templates/object.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ object "{{creationObject}}" {
{{#deployFunctions}}
{{code}}
{{/deployFunctions}}
{{defaultReturn}}
}
object "{{runtimeObject}}" {
code {
Expand All @@ -43,6 +44,7 @@ object "{{creationObject}}" {
{{#runtimeFunctions}}
{{code}}
{{/runtimeFunctions}}
{{defaultReturn}}
}
{{runtimeSubObjects}}
}
Expand Down
209 changes: 71 additions & 138 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/CodeGenYul.scala

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/Util.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package edu.cmu.cs.obsidian.codegen

/* utility functions shared between yulAST and CodeGenYul */
object Util {
def brace(str: String): String = s"{$str}"
def paren(str: String): String = s"($str)"
def ilit(i: Int): Literal = Literal(LiteralKind.number,i.toString,"int")
def blit(b : Boolean): Literal = Literal(LiteralKind.boolean,b.toString,"bool")
val true_lit: Literal = blit(true)
val false_lit: Literal = blit(false)

// TODO unimplemented; hardcode to uint256 for now
def mapObsTypeToABI(ntype: String): String = {
"uint256"
}

// TODO unimplemented; hardcode for now; bouncycastle library may be helpful
def keccak256(s: String): String = {
"0x70a08231"
}

def hashFunction(f: FunctionDefinition): String = {
// TODO/iev until the above todo gets resolved, the outer call makes this function
// basically \_ => "0x70a08231". the original implementation didn't have a
// seperator so i don't know what it should be but probably not " "
keccak256(f.name + paren(f.parameters.map(p=>mapObsTypeToABI(p.ntype)).mkString(" ")))
// TODO truncate and keep the first 4 bytes
}
}
190 changes: 103 additions & 87 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/yulAST.scala
Original file line number Diff line number Diff line change
@@ -1,71 +1,120 @@
package edu.cmu.cs.obsidian.codegen

import java.io.{FileReader, StringWriter}
import com.github.mustachejava.Mustache
import com.github.mustachejava.MustacheFactory
import com.github.mustachejava.DefaultMustacheFactory
import edu.cmu.cs.obsidian.codegen
import edu.cmu.cs.obsidian.codegen.Util._

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

object LiteralKind extends Enumeration {
type LiteralKind = Value
val number, boolean, string = Value
val number: codegen.LiteralKind.Value = Value("int")
val boolean: codegen.LiteralKind.Value = Value("bool")
val string: codegen.LiteralKind.Value = Value("string")
}
trait Expression extends YulAST
trait YulStatement extends YulAST


// for each asm struct, create a case class
case class TypedName (name: String, ntype: String) extends YulAST
case class Case (value: Literal, body: Block) extends YulAST
case class Case (value: Literal, body: Block) extends YulAST {
override def toString: String = {
s"case ${value.toString} ${brace(body.toString)}"
}
}

case class Literal (kind: LiteralKind.LiteralKind, value: String, vtype: String) extends Expression
case class Identifier (name: String) extends Expression
case class FunctionCall (functionName: Identifier, arguments: Seq[Expression]) extends Expression {
def yulFunctionCallString(): String = {
var code = this.functionName.name+"("
var isFirst = true
for (arg <- this.arguments){
val argStr =
arg match {
case Literal(_,value, _)=> value
case _ => ""
}
if (isFirst){
code = code + argStr
isFirst = false
}
else {
code = code + "," + argStr
}
case class Literal (kind: LiteralKind.LiteralKind, value: String, vtype: String) extends Expression {
override def toString: String = {
/* todo/iev: i'm not positive if these assertions are the best idea, but they might be
a nice seatbelt. the constants i'm checking against may be wrong or incomplete right now,
this needs to be tested. add more complex assertions?
*/
val msg: String = "internal error: literal with inconsistent type string"
kind match {
case edu.cmu.cs.obsidian.codegen.LiteralKind.number => assert(vtype=="int",msg)
case edu.cmu.cs.obsidian.codegen.LiteralKind.boolean => assert(vtype=="bool",msg)
case edu.cmu.cs.obsidian.codegen.LiteralKind.string => assert(vtype=="string",msg)
}
code + ")" + "\n"
value
}
}
case class Identifier (name: String) extends Expression {
override def toString: String = {
name
}
}

case class FunctionCall (functionName: Identifier, arguments: Seq[Expression]) extends Expression {
override def toString: String = {
//iev: this assert replicates previous behaviour, but i'm not sure if that was right
assert(arguments.exists(arg => arg match { case Literal(_,_,_) => true case _ => false }),
"internal error: function call with non-literal argument")
s"${functionName.toString} ${paren(arguments.map(id=>id.toString).mkString(", "))}"
}
}

case class Assignment (variableNames: Seq[Identifier], value: Expression) extends YulStatement
case class VariableDeclaration (variables: Seq[TypedName]) extends YulStatement
case class Assignment (variableNames: Seq[Identifier], value: Expression) extends YulStatement {
override def toString: String = {
s"let ${variableNames.map(id => id.name).mkString(", ")} := ${value.toString}"
}
}
case class VariableDeclaration (variables: Seq[TypedName]) extends YulStatement {
override def toString: String = {
s"let ${variables.map(id => id.name+":"+id.ntype).mkString(", ")}"
}
}
case class FunctionDefinition (
name: String,
parameters: Seq[TypedName],
returnVariables: Seq[TypedName],
body: Block) extends YulStatement {
def yulFunctionDefString(): String = {
override def toString: String = {
val mf = new DefaultMustacheFactory()
val mustache = mf.compile(new FileReader("Obsidian_Runtime/src/main/yul_templates/function.mustache"),"function")
val scope = new FuncScope(this)
mustache.execute(new StringWriter(), scope).toString()
mustache.execute(new StringWriter(), scope).toString
}
}

case class If (condition: Expression, body: Block) extends YulStatement{
override def toString: String = {
s"if ${condition.toString} ${brace(body.toString)}"
}
}

case class Switch (expression: Expression, cases: Seq[Case]) extends YulStatement{
override def toString: String = {
s"switch ${expression.toString}" + "\n" + cases.map(c => c.toString).mkString("\n") + "\n"
}
}

case class ForLoop (pre: Block, condition: Expression, post: Block, body: Block) extends YulStatement {
override def toString: String = {
s"for ${brace(pre.toString)} ${condition.toString} ${brace(post.toString)}" + "\n" +
brace(body.toString)
}
}
case class Break () extends YulStatement {
override def toString: String = "break"
}
case class Continue () extends YulStatement {
override def toString: String = "continue"
}
case class Leave () extends YulStatement {
override def toString: String = "leave"
}
case class ExpressionStatement (expression: Expression) extends YulStatement{
override def toString: String = {
expression.toString
}
}
case class Block (statements: Seq[YulStatement]) extends YulStatement {
override def toString: String = {
statements.map(s => s.toString).mkString(" ")
}
}
case class If (condition: Expression, body: Block) extends YulStatement
case class Switch (expression: Expression, cases: Seq[Case]) extends YulStatement
case class ForLoop (pre: Block, condition: Expression, post: Block, body: Block) extends YulStatement
case class Break () extends YulStatement
case class Continue () extends YulStatement
case class Leave () extends YulStatement
case class ExpressionStatement (expression: Expression) extends YulStatement
case class Block (statements: Seq[YulStatement]) extends YulStatement


/*
Expand All @@ -84,7 +133,7 @@ case class YulObject (name: String, code: Code, subObjects: Seq[YulObject], data
val mf = new DefaultMustacheFactory()
val mustache = mf.compile(new FileReader("Obsidian_Runtime/src/main/yul_templates/object.mustache"),"example")
val scope = new ObjScope(this)
val raw: String = mustache.execute(new StringWriter(), scope).toString()
val raw: String = mustache.execute(new StringWriter(), scope).toString
raw.replaceAll("&amp;","&").replaceAll("&gt;",">").replaceAll("&#10;", "\n")
}

Expand All @@ -100,26 +149,6 @@ case class YulObject (name: String, code: Code, subObjects: Seq[YulObject], data
class Case(val hash: String){}
class Call(val call: String){}

// TODO unimplemented; hardcode to uint256 for now
def mapObsTypeToABI(ntype: String): String = {
"uint256"
}

// TODO unimplemented; hardcode for now; bouncycastle library maybe helpful
def keccak256(s: String): String = {
"0x70a08231"
}

def hashFunction(f: FunctionDefinition): String = {
var strRep: String = f.name + "("
for (p <- f.parameters){
strRep = strRep + mapObsTypeToABI(p.ntype)
}
strRep = strRep + ")"
keccak256(strRep)
// TODO truncate and keep the first 4 bytes
}

val mainContractName: String = obj.name
val creationObject: String = mainContractName
val runtimeObject: String = mainContractName + "_deployed"
Expand All @@ -132,48 +161,48 @@ case class YulObject (name: String, code: Code, subObjects: Seq[YulObject], data

for (s <- obj.code.block.statements) {
s match {
case f: FunctionDefinition => deployFunctionArray = deployFunctionArray :+ new Func(f.yulFunctionDefString())
case f: FunctionDefinition =>
deployFunctionArray = deployFunctionArray :+ new Func(f.toString)
case e: ExpressionStatement =>
e.expression match {
case f: FunctionCall => deployCall = deployCall :+ new Call(f.yulFunctionCallString())
case f: FunctionCall => deployCall = deployCall :+ new Call(f.toString)
case _ =>
assert(false, "TODO")
assert(false, "TODO: objscope not implemented for expression statement " + e.toString)
() // TODO unimplemented
}
case _ =>
assert(false, "TODO")
assert(false, "TODO: objscope not implemented for block statement " + s.toString)
() // TODO unimplemented
}
}

for (sub <- obj.subObjects) { // TODO separate runtime object out as a module (make it verbose)
for (s <- sub.code.block.statements) { // temporary fix due to issue above
s match {
case f: FunctionDefinition => {
case f: FunctionDefinition =>
dispatch = true
val code = f.yulFunctionDefString()
runtimeFunctionArray = runtimeFunctionArray :+ new Func(code)
runtimeFunctionArray = runtimeFunctionArray :+ new Func(f.toString)
dispatchArray = dispatchArray :+ new Case(hashFunction(f))
}
case e: ExpressionStatement =>
e.expression match {
case f: FunctionCall => memoryInitRuntime = f.yulFunctionCallString()
case f: FunctionCall => memoryInitRuntime = f.toString
case _ =>
assert(false, "TODO")
assert(false, "TODO: " + e.toString())
() // TODO unimplemented
}
case _ => ()
case _ =>
assert(false)
()
}
}
}
def deploy(): Array[Call] = deployCall
def deployFunctions(): Array[Func] = deployFunctionArray
def runtimeFunctions(): Array[Func] = runtimeFunctionArray
def dispatchCase(): Array[Case] = dispatchArray

def defaultReturn(): FunctionCall = FunctionCall(Identifier("return"),Seq(ilit(0),ilit(0)))
}

// TODO need to fix indentation of the output
class FuncScope(f: FunctionDefinition) {
class Param(val name: String){}
class Body(val code: String){}
Expand All @@ -193,29 +222,16 @@ case class YulObject (name: String, code: Code, subObjects: Seq[YulObject], data
}
}
// construct body
var codeBody: Array[Body] = Array[Body]()
for (s <- f.body.statements){
s match {
case ExpressionStatement(e) =>
e match {
case func: FunctionCall =>
codeBody = codeBody :+ new Body(func.yulFunctionCallString())
}
case _ => ()
}
}
var codeBody: Array[Body] = f.body.statements.map(s => new Body(s.toString)).toArray

// TODO assume only one return variable for now
var hasRetVal = false
var retParams = ""
if (f.returnVariables.nonEmpty){
hasRetVal = true
retParams = f.returnVariables.apply(0).name
retParams = f.returnVariables.head.name
}
def params(): Array[Param] = argRest
def body(): Array[Body] = codeBody
}
}




45 changes: 0 additions & 45 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/yulString.scala

This file was deleted.

0 comments on commit 646dfe8

Please sign in to comment.