From Stack Machine to Functional Machine: Step 1

Anatomy of a Stack Runtime

object "ContractA" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
let _calldata := 256
let _output_pointer := 0

// Copy calldata at the _calldata pointer
calldatacopy(_calldata, 0, calldatasize())

// Read the first 4 bytes representing
// the signature of the called function
// https://solidity.readthedocs.io/en/v0.6.4/abi-spec.html#function-selector
let fn_sig := mslice(_calldata, 4)

switch fn_sig
case 0xffffffff {
// do SOME COMPUTING
let result := 10
let result_length := 32
mstore(_output_pointer, result)
return(_output_pointer, result_length)
}
// other cases/function signatures
default {
// default case - revert with error code
mslicestore(_output_pointer, 0xeee1, 2)
revert(_output_pointer, 2)
}

function mslice(position, length) -> result {
result := div(mload(position), exp(2, sub(256, mul(length, 8))))
}

function mslicestore(_ptr, val, length) {
let slot := 32
mstore(_ptr, shl(mul(sub(slot, length), 8), val))
}
}
}
}
f(some_bytes) => some_other_bytes
parse(some_bytes) => T1
parse(some_other_bytes) => T2
f(T1) => T2
x1 := new(uint(8))
x2 := new(byte(32))
sig1 := signature(signature(uint), signature(8))
sig2 := signature(signature(byte), signature(32))

Calling a function from another function: apply

Let’s define the apply function:

apply(T1) => T2
T1 = Tuple(signature(f), input)
T2 = f(input)
apply(signature(f), input) = f(input)
object "ContractB" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
let _calldata := 256
let _output_pointer := 0

calldatacopy(_calldata, 0, calldatasize())

let fn_sig := mslice(_calldata, 4)

switch fn_sig
case 0xffffffff {
let internal_fn_sig := mslice(add(_calldata, 4), 4)
let input_pointer := add(_calldata, 8)

let result_length := dtapply(
internal_fn_sig,
input_pointer,
_output_pointer
)
return (_output_pointer, result_length)
}
// other cases/function signatures
default {
mslicestore(_output_pointer, 0xeee1, 2)
revert(_output_pointer, 2)
}

function dtapply(
fsig,
input,
output_pointer
) -> result_length
{
switch fsig
case 0xeeeeeeee {
let a := mload(input)
mstore(output_pointer, add(a, 2))
result_length := 32
}
case 0xdddddddd {
let a := mload(input)
mstore(output_pointer, sub(a, 2))
result_length := 32
}
// other cases/function signatures
default {
// default case - revert with error code
mslicestore(output_pointer, 0xeee2, 2)
revert(output_pointer, 2)
}
}

function mslice(position, length) -> result {
result := div(
mload(position),
exp(2, sub(256, mul(length, 8)))
)
}

function mslicestore(_ptr, val, length) {
let slot := 32
mstore(_ptr, shl(mul(sub(slot, length), 8), val))
}
}
}
}

Functional interaction for apply

external Environment->Input Pointer:calldata
Input Pointer->Type Interpreter:type(calldata[4:end])
Type Interpreter->Parser:new T1 = parse(calldata[8:end], sig(T1))
Parser->Type Interpreter:T1
Type Interpreter->Type Interpreter:isFunction(T1)
note over Type Interpreter:if TRUE
Type Interpreter->Function Call:apply(T1)
Function Call->Type Interpreter:T2
Type Interpreter->Output Pointer:set(T2)
note over Type Interpreter:if FALSE
Type Interpreter->Output Pointer:set(T1)
Output Pointer->external Environment:return(pointer_contents)

Recursive apply

object "ContractB" {
code {
datacopy(0, dataoffset("Runtime"), datasize("Runtime"))
return(0, datasize("Runtime"))
}
object "Runtime" {
code {
let _calldata := 256
let _output_pointer := 0

calldatacopy(_calldata, 0, calldatasize())

let fn_sig := mslice(_calldata, 4)

switch fn_sig
case 0xffffffff {
let internal_fn_sig := mslice(add(_calldata, 4), 4)
let input_pointer := add(_calldata, 8)

let result_length := dtapplyRecursive(
internal_fn_sig,
input_pointer,
_output_pointer
)
return (_output_pointer, result_length)
}
// other cases/function signatures
default {
mslicestore(_output_pointer, 0xeee1, 2)
revert(_output_pointer, 2)
}

function dtapplyRecursive(
fsig,
input,
output_pointer
) -> result_length {
switch fsig
case 0xeeeeeeee {
let a := mload(input)
mstore(output_pointer, add(a, 2))
result_length := 32
}
case 0xdddddddd {
let a := mload(input)
mstore(output_pointer, sub(a, 2))
result_length := 32
}
case 0xcccccccc {
let internal_fsig := mslice(input, 4)
// skip the 4 byte signature & the 32 byte value
let count := mslice(add(input, 36), 4)

let temporary_ptr := add(input, 4)

for { let i := 0 } lt(i, count) { i := add(i, 1) } {
result_length := dtapplyRecursive(
internal_fsig,
temporary_ptr,
output_pointer
)
temporary_ptr := output_pointer
}
}
// other cases/function signatures
default {
// default case - revert with error code
mslicestore(output_pointer, 0xeee2, 2)
revert(output_pointer, 2)
}
}

function mslice(position, length) -> result {
result := div(
mload(position),
exp(2, sub(256, mul(length, 8)))
)
}

function mslicestore(_ptr, val, length) {
let slot := 32
mstore(_ptr, shl(mul(sub(slot, length), 8), val))
}
}
}
}

Function Persistence

We could save any type/function on-chain (for the EVM) or into a JSON file treated by Javascript (for Wasm ).

Implementation

The ideas above and the steps that will be described in future articles are even better implemented in the Taylor project. Taylor is an interpreted functional language with typing described by dType.

Next: Step 2

Recursive application is the “engine” of functional programming. In the next step, we will show applications of this engine.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store