wasm-utils/runner/index.html
2017-04-25 14:35:49 +03:00

330 lines
11 KiB
HTML

<html>
<head>
<meta charset="UTF-8">
<script>
// Check for wasm support.
if (!('WebAssembly' in window)) {
alert('you need a browser with wasm support enabled :(');
}
if (!ArrayBuffer.transfer) {
ArrayBuffer.transfer = function(source, length) {
source = Object(source);
var dest = new ArrayBuffer(length);
if (!(source instanceof ArrayBuffer) || !(dest instanceof ArrayBuffer)) {
throw new TypeError('Source and destination must be ArrayBuffer instances');
}
if (dest.byteLength >= source.byteLength) {
var nextOffset = 0;
var leftBytes = source.byteLength;
var wordSizes = [8, 4, 2, 1];
wordSizes.forEach(function(_wordSize_) {
if (leftBytes >= _wordSize_) {
var done = transferWith(_wordSize_, source, dest, nextOffset, leftBytes);
nextOffset = done.nextOffset;
leftBytes = done.leftBytes;
}
});
}
return dest;
function transferWith(wordSize, source, dest, nextOffset, leftBytes) {
var ViewClass = Uint8Array;
switch (wordSize) {
case 8:
ViewClass = Float64Array;
break;
case 4:
ViewClass = Float32Array;
break;
case 2:
ViewClass = Uint16Array;
break;
case 1:
ViewClass = Uint8Array;
break;
default:
ViewClass = Uint8Array;
break;
}
var view_source = new ViewClass(source, nextOffset, Math.trunc(leftBytes / wordSize));
var view_dest = new ViewClass(dest, nextOffset, Math.trunc(leftBytes / wordSize));
for (var i = 0; i < view_dest.length; i++) {
view_dest[i] = view_source[i];
}
return {
nextOffset : view_source.byteOffset + view_source.byteLength,
leftBytes : source.byteLength - (view_source.byteOffset + view_source.byteLength)
}
}
};
}
function Storage(memoryBuf) {
var self = this;
self.size = 16 * 1024;
self.buffer = new ArrayBuffer(self.size);
self.memory = memoryBuf;
self.total = 0;
self.write = function(offset, len, ptr) {
var oldSize = false;
while (offset + len > self.size) {
oldSize || (oldSize = self.size);
self.size = self.size * 2;
}
if (oldSize) {
self.buffer = ArrayBuffer.transfer(self.buffer, self.size);
}
if (offset + len > self.total) {
self.total = offset + len;
}
let memView = new DataView(self.memory);
let storageView = new DataView(self.buffer);
for (i = 0; i < len; i++) {
storageView.setInt8(offset+i, memView.getInt8(ptr+i));
}
return len;
}
self.read = function(offset, len, ptr) {
if (offset + len > self.total) {
return -1;
}
let memView = new DataView(self.memory);
let storageView = new DataView(self.buffer);
for (var i = 0; i < len; i++) {
memView.setInt8(ptr+i, storageView.getInt8(offset+i));
}
return len;
}
self.size = function() {
return self.total;
}
self.toArr = function() {
let result = [];
let dataView = new DataView(self.buffer);
for (var i = 0; i < self.total; i++) {
result.push(dataView.getInt8(i));
}
return result;
}
}
function Runtime() {
var self = this;
self.memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
self.storage = new Storage(self.memory.buffer);
// todo: figure out how to do counter with multiple executables
self.gasCounter = 0;
self.resolveAlloc = function(instance) {
return instance.exports._malloc;
}
self.resolveFree = function(instance) {
return instance.exports._free;
}
self.gas = function(val) {
self.gasCounter += val;
}
self.call = function(instance, args) {
let alloc = self.resolveAlloc(instance);
// call descriptor size
let ptr = alloc(16);
let dataView = new DataView(self.memory.buffer);
var arg_ptr = false;
if (args.length > 0) {
arg_ptr = alloc(args.length);
dataView.setInt32(ptr, arg_ptr, true);
dataView.setInt32(ptr+4, args.length, true);
for (var i = 0; i < args.length; i++) {
dataView.setInt8(arg_ptr+i, args[i], false);
}
} else {
dataView.setInt32(ptr, 0, true);
dataView.setInt32(ptr+4, 0, true);
}
// zero result
dataView.setInt32(ptr+8, 0, true);
dataView.setInt32(ptr+12, 0, true);
self.gasCounter = 0;
instance.exports._call(ptr);
let result_ptr = dataView.getInt32(ptr+8, true);
let result_length = dataView.getInt32(ptr+12, true);
let free = self.resolveFree(instance);
let result = [];
if (result_ptr != 0) {
for (var i = 0; i < result_length; i++) {
result.push(dataView.getInt8(result_ptr + i));
}
}
arg_ptr && (free(arg_ptr));
result_ptr && (free(result_ptr));
free(ptr);
return result;
}
}
function loadWebAssembly(filename, imports) {
return fetch(filename)
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.compile(buffer))
.then(module => {
imports = imports || {};
imports.env = imports.env || {};
var env = imports.env;
imports.env.memoryBase = imports.env.memoryBase || 1024;
imports.env.tableBase = imports.env.tableBase || 0;
window.runtime = new Runtime();
env.STACKTOP = env.STACKTOP || 0;
env.STACK_MAX = env.STACK_MAX || 5*1024*1024;
env.DYNAMICTOP_PTR = env.STACK_MAX;
env.enlargeMemory = env.enlargeMemory || function() {
return 1;
};
env.getTotalMemory = env.getTotalMemory || function() {
return 16 * 1024 * 1024;
};
env.abortOnCannotGrowMemory = env.abortOnCannotGrowMemory || function() { throw "abort growing memory"; };
env._abort = env._abort || function() { throw "_abort"; };
env.abort = env.abort || function() { throw "abort"; };
env.___setErrNo = env.___setErrNo || function() { throw "setting error no"; };
// dead symbols in rust wasm32-unknown-emscripten target
// todo: strip/raise issue in rust compiler
env.invoke_vi = function() { throw "invoke_vi: unreachable!"; }
env.invoke_v = function() { throw "invoke_v: unreachable!"; }
// todo: also test unwind about those two
env._rust_begin_unwind = function() { throw "_rust_begin_unwind: unreachable!"; }
env._llvm_trap = function() { throw "_llvm_trap: unreachable!"; }
env._emscripten_memcpy_big = function() { throw "_emscripten_memcpy_big: unreachable!"; }
env.___gxx_personality_v0 = function() { throw "___gxx_personality_v0: unreachable!"; }
env.___resumeException = function() { throw "___resumeException: unreachable!"; }
env.___cxa_find_matching_catch_2 = function() { throw "___cxa_find_matching_catch_2: unreachable!"; }
env.___gxx_personality_v0 = function() { throw "___gxx_personality_v0: unreachable!"; }
env.memoryBase = env.memoryBase || 0;
env.tableBase = env.tableBase || 0;
env._storage_read = runtime.storage.read;
env._storage_write = runtime.storage.write;
env._storage_size = runtime.storage.size;
env.gas = runtime.gas;
if (!imports.env.memory) {
imports.env.memory = runtime.memory;
}
if (!imports.env.table) {
imports.env.table = new WebAssembly.Table({ initial: 6, maximum: 6, element: 'anyfunc' });
}
return new WebAssembly.Instance(module, imports);
});
}
function strToArray(str) {
var src = str.trim().substr(1, str.length-2);
if (src.length == 0) {
return [];
} else {
return src.split(",").map(p => Number(p.trim()));
}
}
function arrayToStr(arr) {
return "[" + arr.join(", ") + "]";
}
loadWebAssembly('out/contract.wasm')
.then(instance => {
var button = document.getElementById('do-call');
button.value = 'Execute call';
button.addEventListener('click', function() {
button.setAttribute("disabled", "1");
let args = strToArray(document.getElementById("context").value);
let result = runtime.call(instance, args);
document.getElementById("result").value = arrayToStr(result);
document.getElementById("storage").value = arrayToStr(runtime.storage.toArr());
document.getElementById("gas").innerHTML = "Gas used: <b>" + runtime.gasCounter + "</b>";
button.removeAttribute("disabled");
}, false);
}
);
</script>
</head>
<body>
<div style="position: absolute; margin-top: 24px; top: 0px; left: 600px; height: 300px; width: 600px">
<textarea readonly style="width: 100%; height: 100%; padding: 8px; resize: none">
pub fn call(descriptor: *mut u8) {
// This initializes safe wrapper for contract input and output
let mut ctx = CallArgs::from_raw(descriptor);
// Copies all contract input data to the separate buffer
let data = ctx.context().to_vec();
// Appends all input to the storage (as it is a logger contract)
let _ = storage::append(&data);
// Returns all that passed to this contract as an output
*ctx.result_mut() = data;
// Saves the wrapper state to commit return stream
ctx.save(descriptor);
}
</textarea>
<a href="https://github.com/NikVolf/wasm-tools/blob/master/samples/logger_contract.rs">Full source with preamble</a>
</div>
<div>
<label for="context" style="display: block">Input</label>
<textarea style="width: 480px; height: 96px; margin-bottom: 24px; resize: none" id="context">[10, 12, 16]</textarea>
</div>
<div>
<label for="storage" style="display: block">Storage</label>
<div style="padding: 5px; background-color: lightgray; display: inline-block">
<textarea readonly style="width: 480px; height: 120px; margin-bottom: 24px; resize: none" id="storage">[]</textarea>
</div>
</div>
<div>
<label for="result" style="display: block">Result</label>
<div style="padding: 5px; background-color: lightgray; display: inline-block">
<textarea readonly style="width: 480px; height: 96px; margin-bottom: 24px; resize: none" id="result">[]</textarea>
</div>
</div>
<div style="height: 5px; margin-top: 5px; border-top: 1px solid black"></div>
<input type="button" id="do-call" value="(waiting for WebAssembly)" style="background-color: rgb(240, 64, 64); color: white; font-size: 120%" ></input>
<span id="gas" style="margin-left: 32px">Gas cost: <b>0</b></span>
</body>
</html>