mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	EWasm binary transform.
This commit is contained in:
		
							parent
							
								
									9bec533453
								
							
						
					
					
						commit
						081845d775
					
				| @ -200,7 +200,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const | ||||
| 		Dialect const& dialect = languageToDialect(m_language, EVMVersion{}); | ||||
| 
 | ||||
| 		MachineAssemblyObject object; | ||||
| 		object.assembly = EWasmObjectCompiler::compile(*m_parserResult, dialect); | ||||
| 		object.assembly = EWasmObjectCompiler::compile(*m_parserResult, dialect).first; | ||||
| 		return object; | ||||
| 	} | ||||
| 	} | ||||
|  | ||||
| @ -50,6 +50,8 @@ add_library(yul | ||||
| 	backends/wasm/EWasmObjectCompiler.h | ||||
| 	backends/wasm/EWasmToText.cpp | ||||
| 	backends/wasm/EWasmToText.h | ||||
| 	backends/wasm/BinaryTransform.cpp | ||||
| 	backends/wasm/BinaryTransform.h | ||||
| 	backends/wasm/WasmDialect.cpp | ||||
| 	backends/wasm/WasmDialect.h | ||||
| 	backends/wasm/WordSizeTransform.cpp | ||||
|  | ||||
							
								
								
									
										582
									
								
								libyul/backends/wasm/BinaryTransform.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										582
									
								
								libyul/backends/wasm/BinaryTransform.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,582 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /**
 | ||||
|  * EWasm to binary encoder. | ||||
|  */ | ||||
| 
 | ||||
| #include <libyul/backends/wasm/BinaryTransform.h> | ||||
| 
 | ||||
| #include <libyul/Exceptions.h> | ||||
| #include <libdevcore/CommonData.h> | ||||
| 
 | ||||
| #include <boost/range/adaptor/reversed.hpp> | ||||
| 
 | ||||
| using namespace std; | ||||
| using namespace yul; | ||||
| using namespace dev; | ||||
| using namespace yul::wasm; | ||||
| 
 | ||||
| namespace | ||||
| { | ||||
| 
 | ||||
| bytes toBytes(uint8_t _b) | ||||
| { | ||||
| 	return bytes(1, _b); | ||||
| } | ||||
| 
 | ||||
| enum class Section: uint8_t | ||||
| { | ||||
| 	CUSTOM = 0x00, | ||||
| 	TYPE = 0x01, | ||||
| 	IMPORT = 0x02, | ||||
| 	FUNCTION = 0x03, | ||||
| 	MEMORY = 0x05, | ||||
| 	GLOBAL = 0x06, | ||||
| 	EXPORT = 0x07, | ||||
| 	CODE = 0x0a | ||||
| }; | ||||
| 
 | ||||
| bytes toBytes(Section _s) | ||||
| { | ||||
| 	return toBytes(uint8_t(_s)); | ||||
| } | ||||
| 
 | ||||
| enum class ValueType: uint8_t | ||||
| { | ||||
| 	Void = 0x40, | ||||
| 	Function = 0x60, | ||||
| 	I64 = 0x7e, | ||||
| 	I32 = 0x7f | ||||
| }; | ||||
| 
 | ||||
| bytes toBytes(ValueType _vt) | ||||
| { | ||||
| 	return toBytes(uint8_t(_vt)); | ||||
| } | ||||
| 
 | ||||
| enum class Export: uint8_t | ||||
| { | ||||
| 	Function = 0x0, | ||||
| 	Memory = 0x2 | ||||
| }; | ||||
| 
 | ||||
| bytes toBytes(Export _export) | ||||
| { | ||||
| 	return toBytes(uint8_t(_export)); | ||||
| } | ||||
| 
 | ||||
| enum class Opcode: uint8_t | ||||
| { | ||||
| 	Unreachable = 0x00, | ||||
| 	Nop = 0x01, | ||||
| 	Block = 0x02, | ||||
| 	Loop = 0x03, | ||||
| 	If = 0x04, | ||||
| 	Else = 0x05, | ||||
| 	Try = 0x06, | ||||
| 	Catch = 0x07, | ||||
| 	Throw = 0x08, | ||||
| 	Rethrow = 0x09, | ||||
| 	BrOnExn = 0x0a, | ||||
| 	End = 0x0b, | ||||
| 	Br = 0x0c, | ||||
| 	BrIf = 0x0d, | ||||
| 	BrTable = 0x0e, | ||||
| 	Return = 0x0f, | ||||
| 	Call = 0x10, | ||||
| 	CallIndirect = 0x11, | ||||
| 	ReturnCall = 0x12, | ||||
| 	ReturnCallIndirect = 0x13, | ||||
| 	Drop = 0x1a, | ||||
| 	Select = 0x1b, | ||||
| 	LocalGet = 0x20, | ||||
| 	LocalSet = 0x21, | ||||
| 	LocalTee = 0x22, | ||||
| 	GlobalGet = 0x23, | ||||
| 	GlobalSet = 0x24, | ||||
| 	I32Const = 0x41, | ||||
| 	I64Const = 0x42, | ||||
| }; | ||||
| 
 | ||||
| bytes toBytes(Opcode _o) | ||||
| { | ||||
| 	return toBytes(uint8_t(_o)); | ||||
| } | ||||
| 
 | ||||
| static std::map<string, uint8_t> const builtins = { | ||||
| 	{"i32.load", 0x28}, | ||||
| 	{"i64.load", 0x29}, | ||||
| 	{"i32.load8_s", 0x2c}, | ||||
| 	{"i32.load8_u", 0x2d}, | ||||
| 	{"i32.load16_s", 0x2e}, | ||||
| 	{"i32.load16_u", 0x2f}, | ||||
| 	{"i64.load8_s", 0x30}, | ||||
| 	{"i64.load8_u", 0x31}, | ||||
| 	{"i64.load16_s", 0x32}, | ||||
| 	{"i64.load16_u", 0x33}, | ||||
| 	{"i64.load32_s", 0x34}, | ||||
| 	{"i64.load32_u", 0x35}, | ||||
| 	{"i32.store", 0x36}, | ||||
| 	{"i64.store", 0x37}, | ||||
| 	{"i32.store8", 0x3a}, | ||||
| 	{"i32.store16", 0x3b}, | ||||
| 	{"i64.store8", 0x3c}, | ||||
| 	{"i64.store16", 0x3d}, | ||||
| 	{"i64.store32", 0x3e}, | ||||
| 	{"memory.size", 0x3f}, | ||||
| 	{"memory.grow", 0x40}, | ||||
| 	{"i32.eqz", 0x45}, | ||||
| 	{"i32.eq", 0x46}, | ||||
| 	{"i32.ne", 0x47}, | ||||
| 	{"i32.lt_s", 0x48}, | ||||
| 	{"i32.lt_u", 0x49}, | ||||
| 	{"i32.gt_s", 0x4a}, | ||||
| 	{"i32.gt_u", 0x4b}, | ||||
| 	{"i32.le_s", 0x4c}, | ||||
| 	{"i32.le_u", 0x4d}, | ||||
| 	{"i32.ge_s", 0x4e}, | ||||
| 	{"i32.ge_u", 0x4f}, | ||||
| 	{"i64.eqz", 0x50}, | ||||
| 	{"i64.eq", 0x51}, | ||||
| 	{"i64.ne", 0x52}, | ||||
| 	{"i64.lt_s", 0x53}, | ||||
| 	{"i64.lt_u", 0x54}, | ||||
| 	{"i64.gt_s", 0x55}, | ||||
| 	{"i64.gt_u", 0x56}, | ||||
| 	{"i64.le_s", 0x57}, | ||||
| 	{"i64.le_u", 0x58}, | ||||
| 	{"i64.ge_s", 0x59}, | ||||
| 	{"i64.ge_u", 0x5a}, | ||||
| 	{"i32.clz", 0x67}, | ||||
| 	{"i32.ctz", 0x68}, | ||||
| 	{"i32.popcnt", 0x69}, | ||||
| 	{"i32.add", 0x6a}, | ||||
| 	{"i32.sub", 0x6b}, | ||||
| 	{"i32.mul", 0x6c}, | ||||
| 	{"i32.div_s", 0x6d}, | ||||
| 	{"i32.div_u", 0x6e}, | ||||
| 	{"i32.rem_s", 0x6f}, | ||||
| 	{"i32.rem_u", 0x70}, | ||||
| 	{"i32.and", 0x71}, | ||||
| 	{"i32.or", 0x72}, | ||||
| 	{"i32.xor", 0x73}, | ||||
| 	{"i32.shl", 0x74}, | ||||
| 	{"i32.shr_s", 0x75}, | ||||
| 	{"i32.shr_u", 0x76}, | ||||
| 	{"i32.rotl", 0x77}, | ||||
| 	{"i32.rotr", 0x78}, | ||||
| 	{"i64.clz", 0x79}, | ||||
| 	{"i64.ctz", 0x7a}, | ||||
| 	{"i64.popcnt", 0x7b}, | ||||
| 	{"i64.add", 0x7c}, | ||||
| 	{"i64.sub", 0x7d}, | ||||
| 	{"i64.mul", 0x7e}, | ||||
| 	{"i64.div_s", 0x7f}, | ||||
| 	{"i64.div_u", 0x80}, | ||||
| 	{"i64.rem_s", 0x81}, | ||||
| 	{"i64.rem_u", 0x82}, | ||||
| 	{"i64.and", 0x83}, | ||||
| 	{"i64.or", 0x84}, | ||||
| 	{"i64.xor", 0x85}, | ||||
| 	{"i64.shl", 0x86}, | ||||
| 	{"i64.shr_s", 0x87}, | ||||
| 	{"i64.shr_u", 0x88}, | ||||
| 	{"i64.rotl", 0x89}, | ||||
| 	{"i64.rotr", 0x8a}, | ||||
| 	{"i32.wrap_i64", 0xa7}, | ||||
| 	{"i64.extend_i32_s", 0xac}, | ||||
| 	{"i64.extend_i32_u", 0xad}, | ||||
| }; | ||||
| 
 | ||||
| bytes lebEncode(uint64_t _n) | ||||
| { | ||||
| 	bytes encoded; | ||||
| 	while (_n > 0x7f) | ||||
| 	{ | ||||
| 		encoded.emplace_back(uint8_t(0x80 | (_n & 0x7f))); | ||||
| 		_n >>= 7; | ||||
| 	} | ||||
| 	encoded.emplace_back(_n); | ||||
| 	return encoded; | ||||
| } | ||||
| 
 | ||||
| bytes lebEncodeSigned(int64_t _n) | ||||
| { | ||||
| 	if (_n >= 0 && _n < 0x40) | ||||
| 		return toBytes(uint8_t(uint64_t(_n) & 0xff)); | ||||
| 	else if (-_n > 0 && -_n < 0x40) | ||||
| 		return toBytes(uint8_t(uint64_t(_n + 0x80) & 0xff)); | ||||
| 	else | ||||
| 		return toBytes(uint8_t(0x80 | uint8_t(_n & 0x7f))) + lebEncodeSigned(_n / 0x80); | ||||
| } | ||||
| 
 | ||||
| bytes prefixSize(bytes _data) | ||||
| { | ||||
| 	size_t size = _data.size(); | ||||
| 	return lebEncode(size) + std::move(_data); | ||||
| } | ||||
| 
 | ||||
| bytes makeSection(Section _section, bytes _data) | ||||
| { | ||||
| 	return toBytes(_section) + prefixSize(std::move(_data)); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::run(Module const& _module) | ||||
| { | ||||
| 	BinaryTransform bt; | ||||
| 
 | ||||
| 	for (size_t i = 0; i < _module.globals.size(); ++i) | ||||
| 		bt.m_globals[_module.globals[i].variableName] = i; | ||||
| 
 | ||||
| 	size_t funID = 0; | ||||
| 	for (FunctionImport const& fun: _module.imports) | ||||
| 		bt.m_functions[fun.internalName] = funID++; | ||||
| 	for (FunctionDefinition const& fun: _module.functions) | ||||
| 		bt.m_functions[fun.name] = funID++; | ||||
| 
 | ||||
| 	bytes ret{0, 'a', 's', 'm'}; | ||||
| 	// version
 | ||||
| 	ret += bytes{1, 0, 0, 0}; | ||||
| 	ret += bt.typeSection(_module.imports, _module.functions); | ||||
| 	ret += bt.importSection(_module.imports); | ||||
| 	ret += bt.functionSection(_module.functions); | ||||
| 	ret += bt.memorySection(); | ||||
| 	ret += bt.globalSection(); | ||||
| 	ret += bt.exportSection(); | ||||
| 	for (auto const& sub: _module.subModules) | ||||
| 	{ | ||||
| 		// TODO should we prefix and / or shorten the name?
 | ||||
| 		bytes data = BinaryTransform::run(sub.second); | ||||
| 		size_t length = data.size(); | ||||
| 		ret += bt.customSection(sub.first, std::move(data)); | ||||
| 		bt.m_subModulePosAndSize[sub.first] = {ret.size() - length, length}; | ||||
| 	} | ||||
| 	ret += bt.codeSection(_module.functions); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(Literal const& _literal) | ||||
| { | ||||
| 	return toBytes(Opcode::I64Const) + lebEncodeSigned(_literal.value); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(StringLiteral const&) | ||||
| { | ||||
| 	// TODO is this used?
 | ||||
| 	yulAssert(false, "String literals not yet implemented"); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(LocalVariable const& _variable) | ||||
| { | ||||
| 	return toBytes(Opcode::LocalGet) + lebEncode(m_locals.at(_variable.name)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(GlobalVariable const& _variable) | ||||
| { | ||||
| 	return toBytes(Opcode::GlobalGet) + lebEncode(m_globals.at(_variable.name)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(BuiltinCall const& _call) | ||||
| { | ||||
| 	// We need to avoid visiting the arguments of `dataoffset` and `datasize` because
 | ||||
| 	// they are references to object names that should not end up in the code.
 | ||||
| 	if (_call.functionName == "dataoffset") | ||||
| 	{ | ||||
| 		string name = boost::get<StringLiteral>(_call.arguments.at(0)).value; | ||||
| 		return toBytes(Opcode::I64Const) + lebEncodeSigned(m_subModulePosAndSize.at(name).first); | ||||
| 	} | ||||
| 	else if (_call.functionName == "datasize") | ||||
| 	{ | ||||
| 		string name = boost::get<StringLiteral>(_call.arguments.at(0)).value; | ||||
| 		return toBytes(Opcode::I64Const) + lebEncodeSigned(m_subModulePosAndSize.at(name).second); | ||||
| 	} | ||||
| 
 | ||||
| 	bytes args = visit(_call.arguments); | ||||
| 
 | ||||
| 	if (_call.functionName == "unreachable") | ||||
| 			return toBytes(Opcode::Unreachable); | ||||
| 	else | ||||
| 	{ | ||||
| 		yulAssert(builtins.count(_call.functionName), "Builtin " + _call.functionName + " not found"); | ||||
| 		bytes ret = std::move(args) + toBytes(builtins.at(_call.functionName)); | ||||
| 		if ( | ||||
| 			_call.functionName.find(".load") != string::npos || | ||||
| 			_call.functionName.find(".store") != string::npos | ||||
| 		) | ||||
| 			// alignment and offset
 | ||||
| 			ret += bytes{{3, 0}}; | ||||
| 		return ret; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(FunctionCall const& _call) | ||||
| { | ||||
| 	return visit(_call.arguments) + toBytes(Opcode::Call) + lebEncode(m_functions.at(_call.functionName)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(LocalAssignment const& _assignment) | ||||
| { | ||||
| 	return | ||||
| 		boost::apply_visitor(*this, *_assignment.value) + | ||||
| 		toBytes(Opcode::LocalSet) + | ||||
| 		lebEncode(m_locals.at(_assignment.variableName)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(GlobalAssignment const& _assignment) | ||||
| { | ||||
| 	return | ||||
| 		boost::apply_visitor(*this, *_assignment.value) + | ||||
| 		toBytes(Opcode::GlobalSet) + | ||||
| 		lebEncode(m_globals.at(_assignment.variableName)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(If const& _if) | ||||
| { | ||||
| 	bytes result = | ||||
| 		boost::apply_visitor(*this, *_if.condition) + | ||||
| 		toBytes(Opcode::If) + | ||||
| 		toBytes(ValueType::Void); | ||||
| 
 | ||||
| 	m_labels.push({}); | ||||
| 
 | ||||
| 	result += visit(_if.statements); | ||||
| 	if (_if.elseStatements) | ||||
| 		result += toBytes(Opcode::Else) + visit(*_if.elseStatements); | ||||
| 
 | ||||
| 	m_labels.pop(); | ||||
| 
 | ||||
| 	result += toBytes(Opcode::End); | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(Loop const& _loop) | ||||
| { | ||||
| 	bytes result = toBytes(Opcode::Loop) + toBytes(ValueType::Void); | ||||
| 
 | ||||
| 	m_labels.push(_loop.labelName); | ||||
| 	result += visit(_loop.statements); | ||||
| 	m_labels.pop(); | ||||
| 
 | ||||
| 	result += toBytes(Opcode::End); | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(Break const&) | ||||
| { | ||||
| 	yulAssert(false, "br not yet implemented."); | ||||
| 	// TODO the index is just the nesting depth.
 | ||||
| 	return {}; | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(BreakIf const&) | ||||
| { | ||||
| 	yulAssert(false, "br_if not yet implemented."); | ||||
| 	// TODO the index is just the nesting depth.
 | ||||
| 	return {}; | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(Block const& _block) | ||||
| { | ||||
| 	return | ||||
| 		toBytes(Opcode::Block) + | ||||
| 		toBytes(ValueType::Void) + | ||||
| 		visit(_block.statements) + | ||||
| 		toBytes(Opcode::End); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::operator()(FunctionDefinition const& _function) | ||||
| { | ||||
| 	bytes ret; | ||||
| 
 | ||||
| 	// This is a kind of run-length-encoding of local types. Has to be adapted once
 | ||||
| 	// we have locals of different types.
 | ||||
| 	ret += lebEncode(1); // number of locals groups
 | ||||
| 	ret += lebEncode(_function.locals.size()); | ||||
| 	ret += toBytes(ValueType::I64); | ||||
| 
 | ||||
| 	m_locals.clear(); | ||||
| 	size_t varIdx = 0; | ||||
| 	for (size_t i = 0; i < _function.parameterNames.size(); ++i) | ||||
| 		m_locals[_function.parameterNames[i]] = varIdx++; | ||||
| 	for (size_t i = 0; i < _function.locals.size(); ++i) | ||||
| 		m_locals[_function.locals[i].variableName] = varIdx++; | ||||
| 
 | ||||
| 	ret += visit(_function.body); | ||||
| 	ret += toBytes(Opcode::End); | ||||
| 
 | ||||
| 	return prefixSize(std::move(ret)); | ||||
| } | ||||
| 
 | ||||
| BinaryTransform::Type BinaryTransform::typeOf(FunctionImport const& _import) | ||||
| { | ||||
| 	return { | ||||
| 		encodeTypes(_import.paramTypes), | ||||
| 		encodeTypes(_import.returnType ? vector<string>(1, *_import.returnType) : vector<string>()) | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| BinaryTransform::Type BinaryTransform::typeOf(FunctionDefinition const& _funDef) | ||||
| { | ||||
| 
 | ||||
| 	return { | ||||
| 		encodeTypes(vector<string>(_funDef.parameterNames.size(), "i64")), | ||||
| 		encodeTypes(vector<string>(_funDef.returns ? 1 : 0, "i64")) | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| uint8_t BinaryTransform::encodeType(string const& _typeName) | ||||
| { | ||||
| 	if (_typeName == "i32") | ||||
| 		return uint8_t(ValueType::I32); | ||||
| 	else if (_typeName == "i64") | ||||
| 		return uint8_t(ValueType::I64); | ||||
| 	else | ||||
| 		yulAssert(false, ""); | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| vector<uint8_t> BinaryTransform::encodeTypes(vector<string> const& _typeNames) | ||||
| { | ||||
| 	vector<uint8_t> result; | ||||
| 	for (auto const& t: _typeNames) | ||||
| 		result.emplace_back(encodeType(t)); | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::typeSection( | ||||
| 	vector<FunctionImport> const& _imports, | ||||
| 	vector<FunctionDefinition> const& _functions | ||||
| ) | ||||
| { | ||||
| 	map<Type, vector<string>> types; | ||||
| 	for (auto const& import: _imports) | ||||
| 		types[typeOf(import)].emplace_back(import.internalName); | ||||
| 	for (auto const& fun: _functions) | ||||
| 		types[typeOf(fun)].emplace_back(fun.name); | ||||
| 
 | ||||
| 	bytes result; | ||||
| 	size_t index = 0; | ||||
| 	for (auto const& [type, funNames]: types) | ||||
| 	{ | ||||
| 		for (string const& name: funNames) | ||||
| 			m_functionTypes[name] = index; | ||||
| 		result += toBytes(ValueType::Function); | ||||
| 		result += lebEncode(type.first.size()) + type.first; | ||||
| 		result += lebEncode(type.second.size()) + type.second; | ||||
| 
 | ||||
| 		index++; | ||||
| 	} | ||||
| 
 | ||||
| 	return makeSection(Section::TYPE, lebEncode(index) + std::move(result)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::importSection( | ||||
| 	vector<FunctionImport> const& _imports | ||||
| ) | ||||
| { | ||||
| 	bytes result = lebEncode(_imports.size()); | ||||
| 	for (FunctionImport const& import: _imports) | ||||
| 	{ | ||||
| 		uint8_t importKind = 0; // function
 | ||||
| 		result += | ||||
| 			encodeName(import.module) + | ||||
| 			encodeName(import.externalName) + | ||||
| 			toBytes(importKind) + | ||||
| 			lebEncode(m_functionTypes[import.internalName]); | ||||
| 	} | ||||
| 	return makeSection(Section::IMPORT, std::move(result)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::functionSection(vector<FunctionDefinition> const& _functions) | ||||
| { | ||||
| 	bytes result = lebEncode(_functions.size()); | ||||
| 	for (auto const& fun: _functions) | ||||
| 		result += lebEncode(m_functionTypes.at(fun.name)); | ||||
| 	return makeSection(Section::FUNCTION, std::move(result)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::memorySection() | ||||
| { | ||||
| 	bytes result = lebEncode(1); | ||||
| 	result.push_back(0); // flags
 | ||||
| 	result.push_back(1); // initial
 | ||||
| 	return makeSection(Section::MEMORY, std::move(result)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::globalSection() | ||||
| { | ||||
| 	bytes result = lebEncode(m_globals.size()); | ||||
| 	for (size_t i = 0; i < m_globals.size(); ++i) | ||||
| 		result += | ||||
| 			// mutable i64
 | ||||
| 			bytes{uint8_t(ValueType::I64), 1} + | ||||
| 			toBytes(Opcode::I64Const) + | ||||
| 			lebEncodeSigned(0) + | ||||
| 			toBytes(Opcode::End); | ||||
| 
 | ||||
| 	return makeSection(Section::GLOBAL, std::move(result)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::exportSection() | ||||
| { | ||||
| 	bytes result = lebEncode(2); | ||||
| 	result += encodeName("memory") + toBytes(Export::Memory) + lebEncode(0); | ||||
| 	result += encodeName("main") + toBytes(Export::Function) + lebEncode(m_functions.at("main")); | ||||
| 	return makeSection(Section::EXPORT, std::move(result)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::customSection(string const& _name, bytes _data) | ||||
| { | ||||
| 	bytes result = encodeName(_name) + std::move(_data); | ||||
| 	return makeSection(Section::CUSTOM, std::move(result)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::codeSection(vector<wasm::FunctionDefinition> const& _functions) | ||||
| { | ||||
| 	bytes result = lebEncode(_functions.size()); | ||||
| 	for (FunctionDefinition const& fun: _functions) | ||||
| 		result += (*this)(fun); | ||||
| 	return makeSection(Section::CODE, std::move(result)); | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::visit(vector<Expression> const& _expressions) | ||||
| { | ||||
| 	bytes result; | ||||
| 	for (auto const& expr: _expressions) | ||||
| 		result += boost::apply_visitor(*this, expr); | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::visitReversed(vector<Expression> const& _expressions) | ||||
| { | ||||
| 	bytes result; | ||||
| 	for (auto const& expr: _expressions | boost::adaptors::reversed) | ||||
| 		result += boost::apply_visitor(*this, expr); | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| bytes BinaryTransform::encodeName(std::string const& _name) | ||||
| { | ||||
| 	// UTF-8 is allowed here by the Wasm spec, but since all names here should stem from
 | ||||
| 	// Solidity or Yul identifiers or similar, non-ascii characters ending up here
 | ||||
| 	// is a very bad sign.
 | ||||
| 	for (char c: _name) | ||||
| 		yulAssert(uint8_t(c) <= 0x7f, "Non-ascii character found."); | ||||
| 	return lebEncode(_name.size()) + asBytes(_name); | ||||
| } | ||||
							
								
								
									
										94
									
								
								libyul/backends/wasm/BinaryTransform.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								libyul/backends/wasm/BinaryTransform.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | ||||
| /*
 | ||||
| 	This file is part of solidity. | ||||
| 
 | ||||
| 	solidity is free software: you can redistribute it and/or modify | ||||
| 	it under the terms of the GNU General Public License as published by | ||||
| 	the Free Software Foundation, either version 3 of the License, or | ||||
| 	(at your option) any later version. | ||||
| 
 | ||||
| 	solidity is distributed in the hope that it will be useful, | ||||
| 	but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| 	GNU General Public License for more details. | ||||
| 
 | ||||
| 	You should have received a copy of the GNU General Public License | ||||
| 	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| */ | ||||
| /**
 | ||||
|  * EWasm to binary encoder. | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <libyul/backends/wasm/EWasmAST.h> | ||||
| 
 | ||||
| #include <libdevcore/Common.h> | ||||
| 
 | ||||
| #include <vector> | ||||
| #include <stack> | ||||
| 
 | ||||
| namespace yul | ||||
| { | ||||
| namespace wasm | ||||
| { | ||||
| 
 | ||||
| /**
 | ||||
|  * Web assembly to binary transform. | ||||
|  */ | ||||
| class BinaryTransform: public boost::static_visitor<dev::bytes> | ||||
| { | ||||
| public: | ||||
| 	static dev::bytes run(Module const& _module); | ||||
| 
 | ||||
| 	dev::bytes operator()(wasm::Literal const& _literal); | ||||
| 	dev::bytes operator()(wasm::StringLiteral const& _literal); | ||||
| 	dev::bytes operator()(wasm::LocalVariable const& _identifier); | ||||
| 	dev::bytes operator()(wasm::GlobalVariable const& _identifier); | ||||
| 	dev::bytes operator()(wasm::BuiltinCall const& _builinCall); | ||||
| 	dev::bytes operator()(wasm::FunctionCall const& _functionCall); | ||||
| 	dev::bytes operator()(wasm::LocalAssignment const& _assignment); | ||||
| 	dev::bytes operator()(wasm::GlobalAssignment const& _assignment); | ||||
| 	dev::bytes operator()(wasm::If const& _if); | ||||
| 	dev::bytes operator()(wasm::Loop const& _loop); | ||||
| 	dev::bytes operator()(wasm::Break const& _break); | ||||
| 	dev::bytes operator()(wasm::BreakIf const& _break); | ||||
| 	dev::bytes operator()(wasm::Block const& _block); | ||||
| 	dev::bytes operator()(wasm::FunctionDefinition const& _function); | ||||
| 
 | ||||
| private: | ||||
| 	using Type = std::pair<std::vector<std::uint8_t>, std::vector<std::uint8_t>>; | ||||
| 	static Type typeOf(wasm::FunctionImport const& _import); | ||||
| 	static Type typeOf(wasm::FunctionDefinition const& _funDef); | ||||
| 
 | ||||
| 	static uint8_t encodeType(std::string const& _typeName); | ||||
| 	static std::vector<uint8_t> encodeTypes(std::vector<std::string> const& _typeNames); | ||||
| 	dev::bytes typeSection( | ||||
| 		std::vector<wasm::FunctionImport> const& _imports, | ||||
| 		std::vector<wasm::FunctionDefinition> const& _functions | ||||
| 	); | ||||
| 
 | ||||
| 	dev::bytes importSection(std::vector<wasm::FunctionImport> const& _imports); | ||||
| 	dev::bytes functionSection(std::vector<wasm::FunctionDefinition> const& _functions); | ||||
| 	dev::bytes memorySection(); | ||||
| 	dev::bytes globalSection(); | ||||
| 	dev::bytes exportSection(); | ||||
| 	dev::bytes customSection(std::string const& _name, dev::bytes _data); | ||||
| 	dev::bytes codeSection(std::vector<wasm::FunctionDefinition> const& _functions); | ||||
| 
 | ||||
| 	dev::bytes visit(std::vector<wasm::Expression> const& _expressions); | ||||
| 	dev::bytes visitReversed(std::vector<wasm::Expression> const& _expressions); | ||||
| 
 | ||||
| 	static dev::bytes encodeName(std::string const& _name); | ||||
| 
 | ||||
| 	std::map<std::string, size_t> m_locals; | ||||
| 	std::map<std::string, size_t> m_globals; | ||||
| 	std::map<std::string, size_t> m_functions; | ||||
| 	std::map<std::string, size_t> m_functionTypes; | ||||
| 	std::stack<std::string> m_labels; | ||||
| 	std::map<std::string, std::pair<size_t, size_t>> m_subModulePosAndSize; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| } | ||||
| 
 | ||||
| @ -23,6 +23,8 @@ | ||||
| #include <boost/variant.hpp> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <map> | ||||
| #include <memory> | ||||
| 
 | ||||
| namespace yul | ||||
| { | ||||
| @ -86,6 +88,16 @@ struct FunctionDefinition | ||||
| 	std::vector<Expression> body; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  * Abstract representation of a wasm module. | ||||
|  */ | ||||
| struct Module | ||||
| { | ||||
| 	std::vector<GlobalVariableDeclaration> globals; | ||||
| 	std::vector<FunctionImport> imports; | ||||
| 	std::vector<FunctionDefinition> functions; | ||||
| 	std::map<std::string, Module> subModules; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
| } | ||||
|  | ||||
| @ -20,7 +20,6 @@ | ||||
| 
 | ||||
| #include <libyul/backends/wasm/EWasmCodeTransform.h> | ||||
| 
 | ||||
| #include <libyul/backends/wasm/EWasmToText.h> | ||||
| #include <libyul/optimiser/NameCollector.h> | ||||
| 
 | ||||
| #include <libyul/AsmData.h> | ||||
| @ -37,10 +36,11 @@ using namespace std; | ||||
| using namespace dev; | ||||
| using namespace yul; | ||||
| 
 | ||||
| string EWasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _ast) | ||||
| wasm::Module EWasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _ast) | ||||
| { | ||||
| 	wasm::Module module; | ||||
| 
 | ||||
| 	EWasmCodeTransform transform(_dialect, _ast); | ||||
| 	vector<wasm::FunctionDefinition> functions; | ||||
| 
 | ||||
| 	for (auto const& statement: _ast.statements) | ||||
| 	{ | ||||
| @ -49,17 +49,14 @@ string EWasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _ast) | ||||
| 			"Expected only function definitions at the highest level." | ||||
| 		); | ||||
| 		if (statement.type() == typeid(yul::FunctionDefinition)) | ||||
| 			functions.emplace_back(transform.translateFunction(boost::get<yul::FunctionDefinition>(statement))); | ||||
| 			module.functions.emplace_back(transform.translateFunction(boost::get<yul::FunctionDefinition>(statement))); | ||||
| 	} | ||||
| 
 | ||||
| 	std::vector<wasm::FunctionImport> imports; | ||||
| 	for (auto& imp: transform.m_functionsToImport) | ||||
| 		imports.emplace_back(std::move(imp.second)); | ||||
| 	return EWasmToText().run( | ||||
| 		transform.m_globalVariables, | ||||
| 		imports, | ||||
| 		functions | ||||
| 	); | ||||
| 		module.imports.emplace_back(std::move(imp.second)); | ||||
| 	module.globals = transform.m_globalVariables; | ||||
| 
 | ||||
| 	return module; | ||||
| } | ||||
| 
 | ||||
| wasm::Expression EWasmCodeTransform::generateMultiAssignment( | ||||
|  | ||||
| @ -35,7 +35,7 @@ struct AsmAnalysisInfo; | ||||
| class EWasmCodeTransform: public boost::static_visitor<wasm::Expression> | ||||
| { | ||||
| public: | ||||
| 	static std::string run(Dialect const& _dialect, yul::Block const& _ast); | ||||
| 	static wasm::Module run(Dialect const& _dialect, yul::Block const& _ast); | ||||
| 
 | ||||
| public: | ||||
| 	wasm::Expression operator()(yul::Instruction const& _instruction); | ||||
|  | ||||
| @ -21,32 +21,36 @@ | ||||
| #include <libyul/backends/wasm/EWasmObjectCompiler.h> | ||||
| 
 | ||||
| #include <libyul/backends/wasm/EWasmCodeTransform.h> | ||||
| #include <libyul/backends/wasm/BinaryTransform.h> | ||||
| #include <libyul/backends/wasm/EWasmToText.h> | ||||
| 
 | ||||
| #include <libyul/Object.h> | ||||
| #include <libyul/Exceptions.h> | ||||
| 
 | ||||
| #include <libdevcore/CommonData.h> | ||||
| 
 | ||||
| using namespace yul; | ||||
| using namespace std; | ||||
| 
 | ||||
| string EWasmObjectCompiler::compile(Object& _object, Dialect const& _dialect) | ||||
| pair<string, dev::bytes> EWasmObjectCompiler::compile(Object& _object, Dialect const& _dialect) | ||||
| { | ||||
| 	EWasmObjectCompiler compiler(_dialect); | ||||
| 	return compiler.run(_object); | ||||
| 	wasm::Module module = compiler.run(_object); | ||||
| 	return {EWasmToText().run(module), wasm::BinaryTransform::run(module)}; | ||||
| } | ||||
| 
 | ||||
| string EWasmObjectCompiler::run(Object& _object) | ||||
| wasm::Module EWasmObjectCompiler::run(Object& _object) | ||||
| { | ||||
| 	string ret; | ||||
| 	yulAssert(_object.analysisInfo, "No analysis info."); | ||||
| 	yulAssert(_object.code, "No code."); | ||||
| 
 | ||||
| 	wasm::Module module = EWasmCodeTransform::run(m_dialect, *_object.code); | ||||
| 
 | ||||
| 	for (auto& subNode: _object.subObjects) | ||||
| 		if (Object* subObject = dynamic_cast<Object*>(subNode.get())) | ||||
| 			ret += compile(*subObject, m_dialect); | ||||
| 			module.subModules[subObject->name.str()] = run(*subObject); | ||||
| 		else | ||||
| 			yulAssert(false, "Data is not yet supported for EWasm."); | ||||
| 
 | ||||
| 	yulAssert(_object.analysisInfo, "No analysis info."); | ||||
| 	yulAssert(_object.code, "No code."); | ||||
| 	ret += EWasmCodeTransform::run(m_dialect, *_object.code); | ||||
| 
 | ||||
| 	return ret; | ||||
| 	return module; | ||||
| } | ||||
|  | ||||
| @ -21,22 +21,33 @@ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <string> | ||||
| #include <vector> | ||||
| #include <tuple> | ||||
| 
 | ||||
| namespace dev | ||||
| { | ||||
| using bytes = std::vector<uint8_t>; | ||||
| } | ||||
| namespace yul | ||||
| { | ||||
| struct Object; | ||||
| struct Dialect; | ||||
| namespace wasm | ||||
| { | ||||
| struct Module; | ||||
| } | ||||
| 
 | ||||
| class EWasmObjectCompiler | ||||
| { | ||||
| public: | ||||
| 	static std::string compile(Object& _object, Dialect const& _dialect); | ||||
| 	/// Compiles the given object and returns the WAST and the binary representation.
 | ||||
| 	static std::pair<std::string, dev::bytes> compile(Object& _object, Dialect const& _dialect); | ||||
| private: | ||||
| 	EWasmObjectCompiler(Dialect const& _dialect): | ||||
| 		m_dialect(_dialect) | ||||
| 	{} | ||||
| 
 | ||||
| 	std::string run(Object& _object); | ||||
| 	wasm::Module run(Object& _object); | ||||
| 
 | ||||
| 	Dialect const& m_dialect; | ||||
| }; | ||||
|  | ||||
| @ -30,14 +30,15 @@ using namespace std; | ||||
| using namespace yul; | ||||
| using namespace dev; | ||||
| 
 | ||||
| string EWasmToText::run( | ||||
| 	vector<wasm::GlobalVariableDeclaration> const& _globals, | ||||
| 	vector<wasm::FunctionImport> const& _imports, | ||||
| 	vector<wasm::FunctionDefinition> const& _functions | ||||
| ) | ||||
| string EWasmToText::run(wasm::Module const& _module) | ||||
| { | ||||
| 	string ret = "(module\n"; | ||||
| 	for (wasm::FunctionImport const& imp: _imports) | ||||
| 	for (auto const& sub: _module.subModules) | ||||
| 		ret += | ||||
| 			"    ;; sub-module \"" + | ||||
| 			sub.first + | ||||
| 			"\" will be encoded as custom section in binary here, but is skipped in text mode.\n"; | ||||
| 	for (wasm::FunctionImport const& imp: _module.imports) | ||||
| 	{ | ||||
| 		ret += "    (import \"" + imp.module + "\" \"" + imp.externalName + "\" (func $" + imp.internalName; | ||||
| 		if (!imp.paramTypes.empty()) | ||||
| @ -52,10 +53,10 @@ string EWasmToText::run( | ||||
| 	// export the main function
 | ||||
| 	ret += "    (export \"main\" (func $main))\n"; | ||||
| 
 | ||||
| 	for (auto const& g: _globals) | ||||
| 	for (auto const& g: _module.globals) | ||||
| 		ret += "    (global $" + g.variableName + " (mut i64) (i64.const 0))\n"; | ||||
| 	ret += "\n"; | ||||
| 	for (auto const& f: _functions) | ||||
| 	for (auto const& f: _module.functions) | ||||
| 		ret += transform(f) + "\n"; | ||||
| 	return move(ret) + ")\n"; | ||||
| } | ||||
|  | ||||
| @ -31,11 +31,7 @@ struct AsmAnalysisInfo; | ||||
| class EWasmToText: public boost::static_visitor<std::string> | ||||
| { | ||||
| public: | ||||
| 	std::string run( | ||||
| 		std::vector<wasm::GlobalVariableDeclaration> const& _globals, | ||||
| 		std::vector<wasm::FunctionImport> const& _imports, | ||||
| 		std::vector<wasm::FunctionDefinition> const& _functions | ||||
| 	); | ||||
| 	std::string run(wasm::Module const& _module); | ||||
| 
 | ||||
| public: | ||||
| 	std::string operator()(wasm::Literal const& _literal); | ||||
|  | ||||
| @ -1,63 +1,5 @@ | ||||
| {"contracts":{"A":{"C":{"ewasm":{"wast":"(module | ||||
|     (import \"ethereum\" \"revert\" (func $eth.revert (param i32 i32))) | ||||
|     (memory $memory (export \"memory\") 1) | ||||
|     (export \"main\" (func $main)) | ||||
| 
 | ||||
| (func $main | ||||
|     (local $_1 i64) | ||||
|     (local $_2 i64) | ||||
|     (local $_3 i64) | ||||
|     (local $hi i64) | ||||
|     (local $y i64) | ||||
|     (local $hi_1 i64) | ||||
|     (local.set $_1 (i64.const 0)) | ||||
|     (local.set $_2 (i64.add (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (i64.const 64)) (i64.const 64))) | ||||
|     (local.set $_3 (i64.const 65280)) | ||||
|     (local.set $hi (i64.shl (i64.or (i64.shl (i64.or (i64.and (i64.shl (local.get $_1) (i64.const 8)) (local.get $_3)) (i64.and (i64.shr_u (local.get $_1) (i64.const 8)) (i64.const 255))) (i64.const 16)) (call $endian_swap_16 (i64.shr_u (local.get $_1) (i64.const 16)))) (i64.const 32))) | ||||
|     (local.set $y (i64.or (local.get $hi) (call $endian_swap_32 (i64.shr_u (local.get $_1) (i64.const 32))))) | ||||
|     (i64.store (i32.wrap_i64 (local.get $_2)) (local.get $y)) | ||||
|     (i64.store (i32.wrap_i64 (i64.add (local.get $_2) (i64.const 8))) (local.get $y)) | ||||
|     (i64.store (i32.wrap_i64 (i64.add (local.get $_2) (i64.const 16))) (local.get $y)) | ||||
|     (local.set $hi_1 (i64.shl (i64.or (i64.shl (i64.or (i64.and (i64.shl (i64.const 128) (i64.const 8)) (local.get $_3)) (i64.and (i64.shr_u (i64.const 128) (i64.const 8)) (i64.const 255))) (i64.const 16)) (call $endian_swap_16 (i64.shr_u (i64.const 128) (i64.const 16)))) (i64.const 32))) | ||||
|     (i64.store (i32.wrap_i64 (i64.add (local.get $_2) (i64.const 24))) (i64.or (local.get $hi_1) (call $endian_swap_32 (i64.shr_u (i64.const 128) (i64.const 32))))) | ||||
|     (call $eth.revert (i32.wrap_i64 (i64.add (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) (i64.const 64))) (i32.wrap_i64 (call $u256_to_i32 (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)))) | ||||
| ) | ||||
| 
 | ||||
| (func $u256_to_i32 | ||||
|     (param $x1 i64) | ||||
|     (param $x2 i64) | ||||
|     (param $x3 i64) | ||||
|     (param $x4 i64) | ||||
|     (result i64) | ||||
|     (local $v i64) | ||||
|     (if (i64.ne (i64.extend_i32_u (i64.ne (local.get $v) (i64.or (i64.or (local.get $x1) (local.get $x2)) (local.get $x3)))) (i64.const 0)) (then | ||||
|         (unreachable))) | ||||
|     (if (i64.ne (i64.extend_i32_u (i64.ne (local.get $v) (i64.shr_u (local.get $x4) (i64.const 32)))) (i64.const 0)) (then | ||||
|         (unreachable))) | ||||
|     (local.set $v (local.get $x4)) | ||||
|     (local.get $v) | ||||
| ) | ||||
| 
 | ||||
| (func $endian_swap_16 | ||||
|     (param $x i64) | ||||
|     (result i64) | ||||
|     (local $y i64) | ||||
|     (local.set $y (i64.or (i64.and (i64.shl (local.get $x) (i64.const 8)) (i64.const 65280)) (i64.and (i64.shr_u (local.get $x) (i64.const 8)) (i64.const 255)))) | ||||
|     (local.get $y) | ||||
| ) | ||||
| 
 | ||||
| (func $endian_swap_32 | ||||
|     (param $x i64) | ||||
|     (result i64) | ||||
|     (local $y i64) | ||||
|     (local $hi i64) | ||||
|     (local.set $hi (i64.shl (call $endian_swap_16 (local.get $x)) (i64.const 16))) | ||||
|     (local.set $y (i64.or (local.get $hi) (call $endian_swap_16 (i64.shr_u (local.get $x) (i64.const 16))))) | ||||
|     (local.get $y) | ||||
| ) | ||||
| 
 | ||||
| ) | ||||
| (module | ||||
|     ;; sub-module \"C_2_deployed\" will be encoded as custom section in binary here, but is skipped in text mode. | ||||
|     (import \"ethereum\" \"codeCopy\" (func $eth.codeCopy (param i32 i32 i32))) | ||||
|     (import \"ethereum\" \"finish\" (func $eth.finish (param i32 i32))) | ||||
|     (memory $memory (export \"memory\") 1) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user