diff --git a/src/Language/Yul.hs b/src/Language/Yul.hs index d3163a18..3ed17f0a 100644 --- a/src/Language/Yul.hs +++ b/src/Language/Yul.hs @@ -98,7 +98,7 @@ hlist, vlist, nvlist, pprBlock :: Pretty a => [a] -> Doc hlist = hsep . map ppr vlist = vcat . map ppr nvlist = nest 2 . vlist -pprBlock stmts = lbrace $$ nvlist stmts $$ rbrace +pprBlock stmts = braces(nvlist stmts) instance Pretty YulObject where @@ -142,9 +142,8 @@ instance Pretty YulStmt where $$ maybe empty (\stmts -> text "default" <+> pprBlock stmts) def where pprCase (lit, stmts) = text "case" <+> ppr lit <+> pprBlock stmts ppr (YFor pre cond post stmts) = - text "for" <+> braces (hlist pre) - <+> ppr cond - <+> hlist post <+> pprBlock stmts + text "for" <+> braces (hlist pre) <+> ppr cond <+> braces (hlist post) + $$ pprBlock stmts ppr YBreak = text "break" ppr YContinue = text "continue" ppr YLeave = text "leave" diff --git a/src/Language/Yul/Parser.hs b/src/Language/Yul/Parser.hs index e0d390d2..5533211f 100644 --- a/src/Language/Yul/Parser.hs +++ b/src/Language/Yul/Parser.hs @@ -71,6 +71,7 @@ yulStmt = sc *> choice , yulFun , YLet <$> (pKeyword "let" *> commaSep pName) <*> optional (symbol ":=" *> yulExp) , YIf <$> (pKeyword "if" *> yulExp) <*> yulBlock + , YFor <$> (pKeyword "for" *> yulBlock) <*> yulExp <*> yulBlock <*> yulBlock , YSwitch <$> (pKeyword "switch" *> yulExp) <*> many yulCase <*> diff --git a/src/Solcore/Primitives/Primitives.hs b/src/Solcore/Primitives/Primitives.hs index d72d6759..c571d57f 100644 --- a/src/Solcore/Primitives/Primitives.hs +++ b/src/Solcore/Primitives/Primitives.hs @@ -214,7 +214,7 @@ yulPrimOps = [ (Name "stop", monotype unit) , (Name "callvalue", monotype word) , (Name "calldataload", monotype (word :-> word)) , (Name "calldatasize", monotype word) - , (Name "calldatacopy", monotype (word :-> word :-> word :-> word)) + , (Name "calldatacopy", monotype (word :-> word :-> word :-> unit)) , (Name "codesize", monotype word) , (Name "codecopy", monotype (word :-> word :-> word :-> unit)) , (Name "datasize", monotype (string :-> word)) diff --git a/std/dispatch.solc b/std/dispatch.solc index b1454fec..2d9e480e 100644 --- a/std/dispatch.solc +++ b/std/dispatch.solc @@ -38,6 +38,17 @@ instance ():ABIString { } } +instance memory(string):ABIString { + function append(head : word, tail : word, prx : Proxy(memory(string))) -> word { + let size : word = 6; + assembly { + mstore(head, add(mload(head), size)) + mstore(tail, 0x737472696e670000000000000000000000000000000000000000000000000000) + } + return Add.add(tail, size); + } +} + function append_left_bracket(head : word, tail : word) -> word { let size = 1; assembly { @@ -183,12 +194,15 @@ forall payability args rets fn // abi encode rets to memory let ptr = abi_encode(rets); - // TODO: this is broken for dynamically sized types... - let retSz : word = ABIAttribs.headSize(prets); + // let retSz : word = ABIAttribs.headSize(prets); + // the approach above does not work for dynamically sized types... + // ...instead we take the size of memory allocated by the encoding let start : word = Typedef.rep(ptr); - + let encStart : word = Typedef.rep(ptr); // cannot be called start - see #252 + let end : word = get_free_memory(); + let retSz : word = Sub.sub(end, encStart); assembly { - return(start,retSz) + return(encStart, retSz) } } diff --git a/std/std.solc b/std/std.solc index cd489871..704f8cfb 100644 --- a/std/std.solc +++ b/std/std.solc @@ -36,12 +36,23 @@ pragma no-coverage-condition ABIDecode, MemoryType; - memory vectors */ + +forall t.t:Typedef(word) => +function log1(v:t, topic:word) -> () { + let w : word = Typedef.rep(v); + assembly { + mstore(0,w) + log1(0,32,topic) + } +} + function unimplemented() { assembly { mstore(0, 0xaddc0de1) revert(0, 4) } } + // --- booleans --- // TODO: this should short circuit. probably needs some compiler magic to do so. @@ -107,6 +118,12 @@ forall abs rep . class abs:Typedef(rep) { function rep(x:abs) -> rep; } +forall t. +default instance t:Typedef(t) { + function abs(x:t) -> t { return x; } + function rep(x:t) -> t { return x; } +} + // --- Equality --- forall a. class a:Eq { @@ -155,6 +172,7 @@ function lt(x:a, y:a) -> bool { return gt(y,x); } + // --- Arithmetic --- forall t . class t:Add { @@ -432,6 +450,23 @@ forall t . instance returndata(t) : Typedef(word) { data mapping(member, index) = mapping(word) ; +// --- Low-level memory ops + +function mload(a:word) -> word { + let res: word; + assembly { res := mload(a) } + return res; +} + +function mstore(a:word, v:word) -> () { + assembly { mstore(a,v) } +} + +function strlen(s:memory(string)) -> word { + match s { | memory(a) => return mload(a); } +} + + // --- Free Memory Pointer --- // Memory in solidity is bump allocated in a single arena @@ -543,6 +578,8 @@ forall ty . class ty:WordReader { function read(reader:ty) -> word; // returns a new WordReader that points to a location `offset` bytes further into the array function advance(reader:ty, offset:word) -> ty; + // copies a block from the underlying source to memory + function copyToMem(reader:ty, dst: word, cnt: word) -> (); } // WordReader for memory @@ -560,6 +597,11 @@ instance MemoryWordReader:WordReader { | MemoryWordReader(ptr) => return MemoryWordReader(Add.add(ptr, offset)); } } + function copyToMem(reader:MemoryWordReader, dst:word, cnt: word) -> () { + match reader { + | MemoryWordReader(ptr) => assembly { mcopy(dst, ptr, cnt) } + } + } } // WordReader for calldata @@ -590,6 +632,11 @@ instance CalldataWordReader:WordReader { | CalldataWordReader(ptr) => return CalldataWordReader(Add.add(ptr, offset)); } } + function copyToMem(reader:CalldataWordReader, dst:word, cnt: word) -> () { + match reader { + | CalldataWordReader(ptr) => assembly { calldatacopy(dst, ptr, cnt) } + } + } } // --- HasWordReader --- @@ -632,6 +679,7 @@ instance uint256:MemoryType(uint256) { } // We load a DynArray into a sized pointer to the first element +/* forall ty ret . ty:MemoryType(ret) => instance DynArray(ty):MemoryType(slice(memory(ret))) { function loadFromMemory(p : Proxy (DynArray(ty)), loc:word) -> slice(memory(ret)) { let length; @@ -642,19 +690,20 @@ forall ty ret . ty:MemoryType(ret) => instance DynArray(ty):MemoryType(slice(mem return slice(Typedef.abs(loc) : memory(ret), length); } } - +*/ // FAIL: patterson // FAIL: bound variable // if we ty is a MemoryType that returns deref and deref is ABIEncode, then we can encode a memory(ty) // by loading it and then running the ABI encoding for the loaded value +/* forall ty deref . ty:MemoryType(deref), deref:ABIEncode => instance memory(ty):ABIEncode { function encodeInto(x:memory(ty), basePtr:word, offset:word, tail:word) -> word { let prx : Proxy(ty); // FIXED: before was Proxy(deref) return ABIEncode.encodeInto(MemoryType.loadFromMemory(prx, Typedef.rep(x)) : deref, basePtr, offset, tail); } } - +*/ // --- ABI Tuples --- // Tuples in Solidity are always desugared to nested pairs (to allow for @@ -700,6 +749,10 @@ forall t . instance DynArray(t):ABIAttribs { function headSize(ty : Proxy(DynArray(t))) -> word { return 32; } function isStatic(ty : Proxy(DynArray(t))) -> bool { return false; } } +instance string:ABIAttribs { + function headSize(ty: Proxy(string)) -> word { return 32; } + function isStatic(ty : Proxy(string)) -> bool { return false; } +} // computes the attribs for a pair of two types that implement attribs forall a b . a:ABIAttribs, b:ABIAttribs => instance (a,b):ABIAttribs { @@ -720,14 +773,14 @@ forall a b . a:ABIAttribs, b:ABIAttribs => instance (a,b):ABIAttribs { // if an abi tuple contains dynamic elems we store it in the tail, otherwise we // treat it the same as a series of nested pairs forall tuple . tuple:ABIAttribs => instance ABITuple(tuple):ABIAttribs { - function headSize(ty : Proxy(tuple)) -> word { + function headSize(ty : Proxy(ABITuple(tuple))) -> word { let px : Proxy(tuple); match ABIAttribs.isStatic(px) { | true => return ABIAttribs.headSize(px); | false => return 32; } } - function isStatic(ty : Proxy(tuple)) -> bool { + function isStatic(ty : Proxy(ABITuple(tuple))) -> bool { let px : Proxy(tuple); return ABIAttribs.isStatic(px); } @@ -735,21 +788,21 @@ forall tuple . tuple:ABIAttribs => instance ABITuple(tuple):ABIAttribs { // for pointer types we fetch the attribs of the pointed to type, not the pointer itself forall ty . ty:ABIAttribs => instance memory(ty):ABIAttribs { - function headSize(ty : Proxy(ty)) -> word { + function headSize(p : Proxy(memory(ty))) -> word { let px : Proxy(ty); return ABIAttribs.headSize(px); } - function isStatic(ty : Proxy(ty)) -> bool { + function isStatic(p : Proxy(memory(ty))) -> bool { let px : Proxy(ty); return ABIAttribs.isStatic(px); } } forall ty . ty:ABIAttribs => instance calldata(ty):ABIAttribs { - function headSize(ty : Proxy(ty)) -> word { + function headSize(p : Proxy(calldata(ty))) -> word { let px : Proxy(ty); return ABIAttribs.headSize(px); } - function isStatic(ty : Proxy(ty)) -> bool { + function isStatic(ty : Proxy(calldata(ty))) -> bool { let px : Proxy(ty); return ABIAttribs.isStatic(px); } @@ -784,6 +837,31 @@ instance uint256:ABIEncode { } } +function round_up_to_mul_of_32(value:word) -> word { + let result : word; + assembly { result := and(add(value, 31), not(31)) } + return result; +} + +instance memory(string):ABIEncode { + function encodeInto(x:memory(string), basePtr:word, offset:word, tail:word) -> word { + let tailOffset = Sub.sub(tail,basePtr); + let srcPtr = Typedef.rep(x); + + assembly { + let length := mload(srcPtr) + let total := add(length, 32) + + mstore(add(basePtr, offset), sub(tail, basePtr)) + mcopy(tail, srcPtr, total) + let rounded := and(add(total, 31), not(31)) + tail := add(tail, rounded) + } + + return tail; + } +} + instance ():ABIEncode { // a unit256 is written directly into the head function encodeInto(x:(), basePtr:word, offset:word, tail:word) -> word { @@ -859,6 +937,11 @@ forall ty reader . reader:WordReader => instance ABIDecoder(ty, reader):WordRead | ABIDecoder(ptr) => return ABIDecoder(WordReader.advance(ptr, offset)); } } + function copyToMem(decoder:ABIDecoder(ty, reader), dst:word, cnt: word) -> () { + match decoder { + | ABIDecoder(ptr) => WordReader.copyToMem(ptr, dst, cnt); + } + } } // ABI Decoding for uint @@ -874,7 +957,28 @@ forall reader . reader:WordReader => instance ABIDecoder((), reader):ABIDecode(( } } -// ABI decoding for a pait of decodable values +// ABI decoding for strings (only in memory) +forall reader. reader : WordReader => +instance ABIDecoder(memory(string), reader):ABIDecode(memory(string)) +{ + function decode(ptr:ABIDecoder(memory(string), reader), currentHeadOffset:word) -> memory(string) { + let tmp:word; + let headRdr = WordReader.advance(ptr, currentHeadOffset); + let tailPtr : word = WordReader.read(headRdr); + + let src = WordReader.advance(ptr, tailPtr); + let srcRdr = getReader(src); + let length = WordReader.read(src); + let total = Add.add(length, 32); + let rounded = round_up_to_mul_of_32(total); + let resultPtr : word = allocate_memory(rounded) ; + WordReader.copyToMem(srcRdr, resultPtr, total); + return memory(resultPtr); + } // decode ends +} + + +// ABI decoding for a pair of decodable values // FAIL: Coverage forall a b a_decoded b_decoded reader . reader:WordReader, ABIDecoder(b,reader):ABIDecode(b_decoded), ABIDecoder(a,reader):ABIDecode(a_decoded), a:ABIAttribs => instance ABIDecoder((a,b), reader):ABIDecode((a_decoded,b_decoded)) { @@ -905,6 +1009,7 @@ forall reader tuple tuple_decoded . reader:WordReader, tuple:ABIDecode(tuple_dec } } + forall reader tuple tuple_decoded . reader:WordReader, tuple:ABIDecode(tuple_decoded), tuple:ABIAttribs => instance ABIDecoder(memory(ABITuple(tuple)), reader):ABIDecode(memory(tuple_decoded)) { @@ -964,46 +1069,6 @@ forall baseType baseType_decoded reader . ABIDecoder(baseType, CalldataWordReade } } -// forall nm args rets f g . nm:Selector, args:ABIDecode, rets:ABIEncode, f:invokable(args, rets) => instance Dispatch(nm,args,rets,f):GenerateDispatch { -// function dispatch_if_selector_match(d:Dispatch(nm,args,rets,f)) -> g { -// return lam() { -// match d { -// | Dispatch(name, args, rets, fn) => match selector_matches(name) { -// | false => return (); -// | true => return (); -// }} -// }; -// } -// } - -//// /// Translation of the above contract -//// struct StorageContext { -//// x:uint; -//// y:bool; -//// } -//// -//// function C_f(ctxt:StorageContext) public { -//// ctxt.x = 42; -//// } -//// -//// -//// function entry_C() { -//// GenerateDispatch.dispatch_if_selector_match(DispatchFunction("f()", C_f)); // could also be (nested) pairs of dispatch functions, if the contract had more functions -//// revert("unknown selector"); -//// } -//// -//// // init code for contract creation -//// function init_C() { -//// // constructor code -//// let code_start := allocate_unbounded() // fetch some free memory -//// let code_length := __builtin_fetch_code(entry_C, code_start) // sounds weirder than it is - this will just add the code for entry_C to a Yul subobject and use some Yul builtins for fetching the code to be deployed -//// assembly { -//// return(code_start, code_length) -//// } -//// } -//// -//// - // --- Assignment --- @@ -1291,7 +1356,7 @@ instance memory(string):Storable(string) { function sload(src:storage(string)) -> memory(string) { let srcPtr : word = Typedef.rep(src); let dstPtr : word = get_free_memory(); - let endPtr = loadBytesFromStorage(srcPtr, srcPtr); + let endPtr = loadBytesFromStorage(srcPtr, dstPtr); set_free_memory(endPtr); return memory(dstPtr); } diff --git a/test/Cases.hs b/test/Cases.hs index 775f0a2a..6d9038ca 100644 --- a/test/Cases.hs +++ b/test/Cases.hs @@ -61,6 +61,7 @@ dispatches = testGroup "Files for dispatch cases" [ runDispatchTest "basic.solc" + , runDispatchTest "stringid.solc" ] where runDispatchTest file = runTestForFileWith (emptyOption mempty) file "./test/examples/dispatch" @@ -187,6 +188,7 @@ cases = , runTestExpectingFailure "overlapping-heads.solc" caseFolder , runTestExpectingFailure "instance-wrong-sig.solc" caseFolder , runTestForFile "match-yul.solc" caseFolder + , runTestForFile "yul-for.solc" caseFolder ] where caseFolder = "./test/examples/cases" diff --git a/test/examples/cases/yul-for.solc b/test/examples/cases/yul-for.solc new file mode 100644 index 00000000..f978b196 --- /dev/null +++ b/test/examples/cases/yul-for.solc @@ -0,0 +1,14 @@ +contract YulFor { + function main() { + let loopStart = 128; + let loopEnd = 256; + let res : word; + assembly { + let i := loopStart + for {} lt(i, loopEnd) { i := add(i, 32) } + { mstore(i, 42) } + res := mload(192) + } + return res; + } +}