Skip to content

Commit

Permalink
Yul codegen (#298)
Browse files Browse the repository at this point in the history
* translate int state & field, and transcation that manipulates int state/field
* refactor helper functions to generate yul string from ast to a separate object
* adding the new yulString object missing from previous commit
* move constructor call to translateConstructor()
* improve comments & rename class & variables to comply with scala convention
* add comments in mustache templates
* assert when unimplemented; add comments
* refactor ObjScope and FuncScope into YulStringGenerator and add documentation
* refactor YulStringGenerator into methods in yulAST according to PR comments
  • Loading branch information
YinglanChen committed Oct 4, 2020
1 parent 697533d commit 198094a
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 35 deletions.
5 changes: 5 additions & 0 deletions Obsidian_Runtime/src/main/yul_templates/function.mustache
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{{! This is a template used by mustache to generate the final string of yul code. The
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}}{
{{#body}}
{{code}}
Expand Down
18 changes: 17 additions & 1 deletion Obsidian_Runtime/src/main/yul_templates/object.mustache
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
{{! This is a template used by mustache to generate the final string of yul code. The
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)
Consulting example yul code is also helpful in understanding the template.
https://solidity.readthedocs.io/en/latest/yul.html#specification-of-yul-object
}}
object "{{creationObject}}" {
code {
{{! init free memory pointer, see chatper "Layout in Memory" of the Solidity doc}}
{{memoryInit}}
{{! protection against sending Ether }}
{{callValueCheck}}
{{! not impletmented by the current stage, cited from IRGenerator.cpp (link in file comment above) }}
{{#notLibrary}}
{{#constructorHasParams}} let {{constructorParams}} := {{copyConstructorArguments}}() {{/constructorHasParams}}
{{implicitConstructor}}({{constructorParams}})
{{/notLibrary}}
{{! call constructor }}
{{#deploy}}
{{call}}
{{/deploy}}
{{! functions related to constructor }}
{{#deployFunctions}}
{{code}}
{{/deployFunctions}}
}
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 BUG: forget to add implementation of shift_right_unsigned,
it simply returns shr(224, value) }}
let selector := shift_right_unsigned(calldataload(0))
switch selector
{{#dispatchCase}}
case {{hash}} {
{{dispatchCall}}}
{{dispatchCall}}
}
{{/dispatchCase}}
{{/dispatch}}
Expand Down
73 changes: 42 additions & 31 deletions src/main/scala/edu/cmu/cs/obsidian/codegen/CodeGenYul.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,19 @@ import edu.cmu.cs.obsidian.parser._
import edu.cmu.cs.obsidian.Main.{findMainContract, findMainContractName}
import edu.cmu.cs.obsidian.codegen.Code
import jdk.nashorn.internal.runtime.FunctionScope
import edu.cmu.cs.obsidian.codegen.yulString

import edu.cmu.cs.obsidian.typecheck.ContractType

import scala.collection.immutable.Map

// need some table remembering field index in storage
object CodeGenYul extends CodeGenerator {

// TODO improve this temporary symbol table
var temp_symbol_table: Map[String, Int] = Map() // map from field identifiers to index in storage
var temp_table_idx = 0
var state_idx = -1
var state_enum_mapping: Map[String, Int] = Map() // map from state name to an enum value
var state_enum_counter = 0
var tempSymbolTable: Map[String, Int] = Map() // map from field identifiers to index in storage
var tempTableIdx = 0 // counter indicating the next available slot in the table
var stateIdx = -1 // whether or not there is a state
var stateEnumMapping: Map[String, Int] = Map() // map from state name to an enum value
var stateEnumCounter = 0 // counter indicating the next value to assign since we don't knon the total num of states

def gen(filename: String, srcDir: Path, outputPath: Path, protoDir: Path,
options: CompilerOptions, checkedTable: SymbolTable, transformedTable: SymbolTable): Boolean = {
Expand All @@ -42,7 +41,7 @@ object CodeGenYul extends CodeGenerator {
// translate from obsidian AST to yul AST
val translated_obj = translateProgram(ast)
// generate yul string from yul AST
val s = yulString.yulString(translated_obj)
val s = translated_obj.yulString()
// write string to output file
// currently it's created in the Obsidian directory; this may need to be changed, based on desired destination
Files.createDirectories(finalOutputPath)
Expand Down Expand Up @@ -71,6 +70,8 @@ object CodeGenYul extends CodeGenerator {
c match {
case obsContract: ObsidianContractImpl =>
if (!c.modifiers.contains(IsMain())) { // if not main contract
// this is a top level contract object
// note: interfaces are not translated;
// TODO detect an extra contract named "Contract", skip that as a temporary fix
if (c.name != ContractType.topContractName) {
new_subObjects = main_contract_ast.subObjects :+ translateContract(obsContract)
Expand All @@ -90,15 +91,18 @@ object CodeGenYul extends CodeGenerator {
var statement_seq_runtime: Seq[YulStatement] = Seq()

// memory init
val freeMemPointer = 64
val firstFreeMem = 128
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(Literal(LiteralKind.number, freeMemPointer.toString(), "int"),Literal(LiteralKind.number, firstFreeMem.toString(), "int")))
statement_seq_deploy = statement_seq_deploy :+ ExpressionStatement(initExpr)
statement_seq_runtime = statement_seq_runtime :+ ExpressionStatement(initExpr)

// callValueCheck: TODO unimplemented
// it checks for the wei sent together with the current call and revert if that's non zero.
// if callvalue() { revert(0, 0) }

// translate declarations
for (d <- contract.declarations) {
Expand All @@ -115,7 +119,7 @@ object CodeGenYul extends CodeGenerator {
YulObject(contract.name, Code(Block(statement_seq_deploy)), subObjects, Seq())
}


// return statements that go to deploy object, and statements that go to runtime object
def translateDeclaration(declaration: Declaration): (Seq[YulStatement], Seq[YulStatement]) = {
declaration match {
case f: Field => (Seq(), translateField(f))
Expand Down Expand Up @@ -144,25 +148,26 @@ object CodeGenYul extends CodeGenerator {
def translateField(field: Field): Seq[YulStatement] = {
// Reserve a slot in the storage by assigning a index in the symbol table
// since field declaration has not yet be assigned, there is no need to do sstore
temp_symbol_table += field.name -> temp_table_idx
temp_table_idx += 1
tempSymbolTable += field.name -> tempTableIdx
tempTableIdx += 1
Seq()
}

def translateState(s: State): Seq[YulStatement] = {
if (state_idx == -1){
state_idx = temp_table_idx
temp_table_idx += 1
if (stateIdx == -1){
stateIdx = tempTableIdx
tempTableIdx += 1
}
// add state name to enum value mapping
state_enum_mapping += s.name -> state_enum_counter
state_enum_counter += 1
stateEnumMapping += s.name -> stateEnumCounter
stateEnumCounter += 1

Seq()
}

def translateConstructor(constructor: Constructor): Seq[YulStatement] = {
val f: (VariableDeclWithSpec) => TypedName = (v => TypedName(v.varName, mapObsTypeToABI(v.typIn.toString())) )
val parameters: Seq[TypedName] = (constructor.args).map[TypedName](f)
val extractTypeName: (VariableDeclWithSpec) => TypedName = (v => TypedName(v.varName, mapObsTypeToABI(v.typIn.toString())) )
val parameters: Seq[TypedName] = (constructor.args).map[TypedName](extractTypeName)
var body: Seq[YulStatement] =Seq()
for (s <- constructor.body){
body = body ++ translateStatement(s)
Expand Down Expand Up @@ -191,7 +196,7 @@ object CodeGenYul extends CodeGenerator {
val ret: Seq[TypedName] =
if (transaction.retType.isEmpty) {
Seq()
} else { // TODO hard code return variable name now
} else { // TODO hard code return variable name now, need a special naming convention to avoid collisions
Seq(TypedName("retValTempName", mapObsTypeToABI(transaction.retType.get.toString())) )
}

Expand All @@ -217,7 +222,7 @@ object CodeGenYul extends CodeGenerator {
assignTo match {
// TODO only support int/int256 now
case ReferenceIdentifier(x) =>
val idx = temp_symbol_table(x)
val idx = tempSymbolTable(x)
val value = e match {
case NumLiteral(v) => v
case _ =>
Expand All @@ -241,14 +246,18 @@ object CodeGenYul extends CodeGenerator {
def translateExpr(e: Expression): Seq[YulStatement] = {
e match {
case ReferenceIdentifier(x) =>
val idx = temp_symbol_table(x)
val idx = tempSymbolTable(x)
val expr = FunctionCall(Identifier("sload"), Seq(Literal(LiteralKind.number, idx.toString(), "int")))
Seq(ExpressionStatement(expr))
case _ => Seq() // TODO unimplemented
case _ =>
assert(false, "TODO")
Seq() // TODO unimplemented
}
}
}

// Yulstring
// document scope class relation to mustache
// temporary function, not designed for a full recursive walk through of the object
class ObjScope(obj: YulObject) {
class Func(val code: String){}
Expand All @@ -260,7 +269,7 @@ class ObjScope(obj: YulObject) {
"uint256"
}

// TODO unimplemented; hardcode for now
// TODO unimplemented; hardcode for now; bouncycastle library may be helpful
def keccak256(s: String): String = {
"0x70a08231"
}
Expand Down Expand Up @@ -302,14 +311,16 @@ class ObjScope(obj: YulObject) {
s match {
case f: FunctionDefinition => {
dispatch = true
val code = yulString.yulFunctionDefString(f)
val code = f.yulFunctionDefString()
runtimeFunctionArray = runtimeFunctionArray :+ new Func(code)
dispatchArray = dispatchArray :+ new Case(hashFunction(f))
}
case e: ExpressionStatement =>
e.expression match {
case f: FunctionCall => memoryInitRuntime = yulString.yulFunctionCallString(f)
case _ => () // TODO unimplemented
case f: FunctionCall => memoryInitRuntime = f.yulFunctionCallString()
case _ =>
assert(false, "TODO")
() // TODO unimplemented
}
case _ => ()
}
Expand All @@ -322,8 +333,8 @@ class ObjScope(obj: YulObject) {

}

// TODO need to fix indentation
class FuncScope(f: FunctionDefinition){
// TODO need to fix indentation of the output
class FuncScope(f: FunctionDefinition) {
class Param(val name: String){}
class Body(val code: String){}

Expand All @@ -348,7 +359,7 @@ class FuncScope(f: FunctionDefinition){
case ExpressionStatement(e) =>
e match {
case func: FunctionCall =>
codeBody = codeBody :+ new Body(yulString.yulFunctionCallString(func))
codeBody = codeBody :+ new Body(func.yulFunctionCallString())
}
case _ => ()
}
Expand Down

0 comments on commit 198094a

Please sign in to comment.