wasm-metering
Version:
injects metering into webassembly binaries
1,146 lines (1,045 loc) • 35.7 kB
JavaScript
/*
* Copyright 2016 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var wasm = {};
wasm.ready = new Promise(function(resolve, reject) {
wasm.$resolve = resolve;
wasm.$reject = reject;
});
var Module = {};
Module.onRuntimeInitialized = function() {
// Helpers /////////////////////////////////////////////////////////////////////
function loadi8(addr) { return HEAP8[addr]; }
function loadi16(addr) { return HEAP16[addr>>1]; }
function loadi32(addr) { return HEAP32[addr>>2]; }
function loadu8(addr) { return HEAPU8[addr]; }
function loadu16(addr) { return HEAPU16[addr>>1]; }
function loadu32(addr) { return HEAPU32[addr>>2]; }
function loadf32(addr) { return HEAPF32[addr>>2]; }
function loadf64(addr) { return HEAPF64[addr>>4]; }
function storei8(addr, value) { HEAP8[addr] = value; }
function storei16(addr, value) { HEAP16[addr>>1] = value; }
function storei32(addr, value) { HEAP32[addr>>2] = value; }
function storeu8(addr, value) { HEAPU8[addr] = value; }
function storeu16(addr, value) { HEAPU16[addr>>1] = value; }
function storeu32(addr, value) { HEAPU32[addr>>2] = value; }
function storef32(addr, value) { HEAPF32[addr>>2] = value; }
function storef64(addr, value) { HEAPF64[addr>>4] = value; }
function loadcstr(addr) { return Module.AsciiToString(addr); }
function loadstrslice(addr, len) { return Module.Pointer_stringify(addr, len); }
function loadbuffer(addr, len) {
return new Uint8Array(HEAPU8.buffer, addr, len);
}
function sizeof(structName) { return Module['_wasm_sizeof_' + structName](); }
function offsetof(structName, fieldName) {
return Module['_wasm_offsetof_' + structName + '_' + fieldName]();
}
function malloc(size) {
var addr = Module._malloc(size);
if (addr == 0) {
throw new Error('out of memory');
}
return addr;
}
function mallocz(size) {
var addr = malloc(size);
HEAPU8.fill(0, addr, addr + size);
return addr;
}
function free(addr) { Module._free(addr); }
// Types ///////////////////////////////////////////////////////////////////////
function Type(name, id) {
this.name = name;
if (id) {
// alias
this.id = id;
} else {
this.id = Type.id++;
Type.map[this.id] = this;
}
}
Type.id = 0;
Type.map = {};
Type.check = function(expected, actual) {
if (actual.id != expected.id) {
throw new Error('type mismatch; expected ' + expected.toString() +
', got ' + actual.toString());
}
};
Type.prototype = Object.create(Object.prototype);
Type.prototype.toString = function(name, lastPrec) {
var result = ''
if (this.name) {
result = this.name;
if (name) {
result += ' ' + name;
}
return result;
}
name = name || '';
var prec = this.prec;
if (prec && lastPrec && prec > lastPrec) {
name = '(' + name + ')';
}
if (this.kind == 'ptr') {
return this.pointee.toString('*' + result + name, prec);
} else if (this.kind == 'array') {
return this.element.toString(result + '[]', prec);
} else if (this.kind == 'fn') {
name += '(';
if (this.params.length > 0) {
var paramNames = [];
for (var i = 0; i < this.params.length; ++i) {
paramNames.push(this.params[i].toString());
}
name += paramNames.join(', ');
}
name += ')';
return this.result.toString(name, prec);
} else {
throw new Error('unknown type kind');
}
};
Type.prototype.load = function() {
throw new Error(this.toString() + " cannot be loaded");
};
Type.prototype.store = function() {
throw new Error(this.toString() + " cannot be stored");
};
Type.prototype.call = function() {
throw new Error(this.toString() + " cannot be called");
};
function PrimitiveType(name, size, sig, min, max) {
Type.call(this, name);
this.size = size;
this.sig = sig;
this.min = min;
this.max = max;
}
PrimitiveType.prototype = Object.create(Type.prototype);
PrimitiveType.prototype.kind = 'primitive'
PrimitiveType.prototype.primitive = true;
PrimitiveType.prototype.checkRange = function(value) {
if (value < this.min || value > this.max) {
throw new Error('value out of range: ' + value + ' not in [' + this.min + ',' +
this.max + ']');
}
};
PrimitiveType.prototype.toJS = function(value) {
return value;
};
PrimitiveType.prototype.initAccessors = function(value, addr) {
var type = this;
value.load = function() { return type.toJS(type.load(addr)); };
value.store = function(value) { type.store(addr, type.fromJS(value)); };
};
// TODO(binji): figure out what the signature chars actually are
var Void = new PrimitiveType('Void', 0, 'v');
var Bool = new PrimitiveType('Bool', 1, 'i');
var I8 = new PrimitiveType('I8', 1, 'i', -128, 127);
var I16 = new PrimitiveType('I16', 2, 'i', -32768, 32767);
var I32 = new PrimitiveType('I32', 4, 'i', -0x80000000, 0x7fffffff);
var U8 = new PrimitiveType('U8', 1, 'i', 0, 255);
var U16 = new PrimitiveType('U16', 2, 'i', 0, 65535);
var U32 = new PrimitiveType('U32', 4, 'i', 0, 0xffffffff);
var U64 = new PrimitiveType('U64', 8, 'i');
var F32 = new PrimitiveType('F32', 4, 'f');
var F64 = new PrimitiveType('F64', 8, 'd');
Void.fromJS = function(value) {}
Bool.fromJS = function(value) { return !!value; };
I8.fromJS = function(value) { this.checkRange(value); return value|0; };
I16.fromJS = function(value) { this.checkRange(value); return value|0; };
I32.fromJS = function(value) { this.checkRange(value); return value|0; };
U8.fromJS = function(value) { this.checkRange(value); return value>>>0; };
U16.fromJS = function(value) { this.checkRange(value); return value>>>0; };
U32.fromJS = function(value) { this.checkRange(value); return value>>>0; };
U64.fromJS = function(value) { throw new Error('u64 not yet supported'); };
F32.fromJS = function(value) { return Math.fround(value); };
F64.fromJS = function(value) { return +value; };
Bool.toJS = function(value) { return value ? true : false; };
Bool.load = function(addr) { return loadu8(addr); };
I8.load = function(addr) { return loadi8(addr); };
I16.load = function(addr) { return loadi16(addr); };
I32.load = function(addr) { return loadi32(addr); };
U8.load = function(addr) { return loadu8(addr); };
U16.load = function(addr) { return loadu16(addr); };
U32.load = function(addr) { return loadu32(addr); };
U64.load = function(addr) { throw new Error('u64 not yet supported'); };
F32.load = function(addr) { return loadf32(addr); };
F64.load = function(addr) { return loadf64(addr); };
Bool.store = function(addr, value) { return storeu8(addr, value); };
I8.store = function(addr, value) { return storei8(addr, value); };
I16.store = function(addr, value) { return storei16(addr, value); };
I32.store = function(addr, value) { return storei32(addr, value); };
U8.store = function(addr, value) { return storeu8(addr, value); };
U16.store = function(addr, value) { return storeu16(addr, value); };
U32.store = function(addr, value) { return storeu32(addr, value); };
U64.store = function(addr, value) { throw new Error('u64 not yet supported'); };
F32.store = function(addr, value) { return storef32(addr, value); };
F64.store = function(addr, value) { return storef64(addr, value); };
function Alias(name, type) {
var alias = Object.create(type);
alias.name = name;
return alias;
}
function PtrType(type) {
Type.call(this);
this.pointee = type;
}
PtrType.map = {};
PtrType.prototype = Object.create(Type.prototype);
PtrType.prototype.kind = 'ptr';
PtrType.prototype.prec = 1;
PtrType.prototype.primitive = true;
PtrType.prototype.sig = 'i'
PtrType.prototype.size = 4;
PtrType.prototype.fromJS = function(value) {
if (value === null) {
return 0;
}
Value.$check(value);
Type.check(this, value.$ptrType);
return value.$addr;
};
PtrType.prototype.toJS = function(value) {
if (value == 0) {
return null;
}
return new Value(this.pointee, value);
};
PtrType.prototype.initAccessors = function(value, addr) {
var type = this;
value.load = function() { return type.toJS(loadu32(addr)); };
value.store = function(value) { storeu32(addr, type.fromJS(value)); };
};
function Ptr(type) {
if (type.id in PtrType.map) {
return PtrType.map[type.id];
}
var result = new PtrType(type);
PtrType.map[type.id] = result;
return result;
}
var VoidPtr = Ptr(Void);
var ArrayLen = Alias('ArrayLen', U32);
var BufLen = Alias('BufLen', U32);
var BufPtr = Alias('BufPtr', VoidPtr);
var Str = Alias('Str', Ptr(U8));
var StrLen = Alias('StrLen', U32);
var StrPtr = Alias('StrPtr', Ptr(U8));
var UserData = Alias('UserData', VoidPtr);
Str.toJS = function(addr) { return loadcstr(addr); };
Str.initAccessors = function(value, addr) {
value.load = function() { return loadcstr(loadu32(addr)); }
};
function ArrayType(type) {
Type.call(this);
this.element = type;
}
ArrayType.map = {};
ArrayType.prototype = Object.create(Type.prototype);
ArrayType.prototype.kind = 'ptr';
ArrayType.prototype.prec = 2;
ArrayType.prototype.primitive = true;
ArrayType.prototype.sig = 'i'
ArrayType.prototype.size = 4;
ArrayType.prototype.initAccessors = function(value, arrayAddr) {
var type = this;
value.index = function(index) {
return new Value(type.element, arrayAddr + index * type.element.size);
};
};
function ArrayPtr(type) {
if (type.id in ArrayType.map) {
return ArrayType.map[type.id];
}
var result = new ArrayType(type);
ArrayType.map[type.id] = result;
return result;
}
function FnType(result, params) {
if (!result.primitive) {
throw new Error(result.toString() + " is not a primitive type");
}
for (var i = 0; i < params.length; ++i) {
var param = params[i];
if (!param.primitive) {
throw new Error(param.toString() + " is not a primitive type");
}
}
Type.call(this);
this.result = result;
this.params = params;
this.funcSig = this.generateSig();
}
FnType.generateKey = function(result, params) {
var key = result.id + ',';
for (var i = 0; i < params.length; ++i) {
key += params[i].id + ',';
}
return key;
};
FnType.map = {};
FnType.prototype = Object.create(Type.prototype);
FnType.prototype.kind = 'fn';
FnType.prototype.prec = 3;
FnType.prototype.primitive = true;
FnType.prototype.sig = 'i'
FnType.prototype.size = 4;
FnType.prototype.generateSig = function() {
var result = this.result.sig;
for (var i = 0; i < this.params.length; ++i) {
result += this.params[i].sig;
}
return result;
};
FnType.prototype.argsFromJS = function(inArgs) {
if (inArgs.length != this.params.length) {
throw new Error("argument count mismatch: expecting " + this.params.length +
", got " + inArgs.length);
}
var outArgs = [];
for (var i = 0; i < inArgs.length; ++i) {
outArgs.push(this.params[i].fromJS(inArgs[i]));
}
return outArgs;
};
FnType.prototype.argsToJS = function(inArgs) {
var outArgs = [];
for (var i = 0; i < inArgs.length; ++i) {
outArgs.push(this.params[i].toJS(inArgs[i]));
}
return outArgs;
};
FnType.prototype.define = function(name) {
var type = this;
return function() {
var result = Module[name].apply(Module, type.argsFromJS(arguments));
return type.result.toJS(result);
};
};
FnType.prototype.initAccessors = function(value, addr) {
var type = this;
value.load = function() { return new FnValue(type, loadu32(addr)); };
value.store = function(value) {
if (!(value instanceof FnValue)) {
throw new Error('fn value ' + value + ' not instanceof FnValue');
}
Type.check(type, value.$type);
return storeu32(addr, value.$index);
};
value.call = function() {
var result = Runtime.dynCall(type.funcSig, loadu32(addr),
type.argsFromJS(arguments));
return type.result.toJS(result);
};
};
function Fn(result, params) {
var key = FnType.generateKey(result, params);
if (key in FnType.map) {
return FnType.map[key];
}
var result = new FnType(result, params);
FnType.map[key] = result;
return result;
}
function Field(name, type, offset) {
this.name = name;
this.type = type;
this.offset = offset;
}
Field.prototype = Object.create(Object.prototype);
Field.prototype.initAccessors = function(value, structAddr) {
value[this.name] = new Value(this.type, structAddr + this.offset);
};
function StructType(name) {
Type.call(this, name);
this.fields = {};
}
StructType.prototype = Object.create(Type.prototype);
StructType.prototype.kind = 'struct';
StructType.prototype.primitive = false;
StructType.prototype.define = function(cName, fieldTypes) {
this.cName = cName;
this.size = sizeof(cName);
for (fieldName in fieldTypes) {
var type = fieldTypes[fieldName];
var offset = offsetof(cName, fieldName);
this.fields[fieldName] = new Field(fieldName, type, offset);
}
};
StructType.prototype.describe = function() {
var result = 'struct ' + this.name + ' {\n';
var lines = [];
for (fieldName in this.fields) {
var field = this.fields[fieldName];
lines.push(' ' + field.type.toString(fieldName) + ';');
}
result += lines.join('\n');
result += '\n};';
return result;
};
StructType.prototype.initAccessors = function(value, addr) {
var type = this;
for (fieldName in this.fields) {
var field = this.fields[fieldName];
field.initAccessors(value, addr);
}
};
function Struct(name) {
return new StructType(name);
}
// Values //////////////////////////////////////////////////////////////////////
function Value(type, addr) {
this.$type = type;
this.$ptrType = Ptr(type);
this.$addr = addr;
type.initAccessors(this, this.$addr);
}
Value.$at = function(type, addr) { return new Value(type, addr); };
Value.$malloc = function(type) { return new Value(type, mallocz(type.size)); };
Value.$check = function(value) {
if (!(value instanceof Value)) {
throw new Error('value ' + value + ' not instanceof Value');
}
};
Value.prototype = Object.create(Object.prototype);
Value.prototype.toString = function() {
return '<' + this.$type + '>@' + this.$addr;
};
Value.prototype.$free = function() { free(this.$addr); };
function FnValue(type, f) {
if (!(type instanceof FnType)) {
throw new Error('type ' + type + ' not instanceof FnType');
}
this.$type = type;
if (typeof f == 'function') {
if (f.length != type.params.length) {
throw new Error("argument count mismatch: expecting " + type.params.length +
", got " + f.length);
}
var wrapped = function() {
return type.result.fromJS(f.apply(null, type.argsToJS(arguments)));
};
this.$index = Runtime.addFunction(wrapped);
this.$owned = true;
} else {
// assume that if is the table index.
this.$index = f | 0;
this.$owned = false;
}
}
FnValue.prototype = Object.create(Object.prototype);
FnValue.prototype.toString = function() {
return '<' + this.type + '>@' + this.$index;
};
FnValue.prototype.$destroy = function() {
if (this.$owned) {
Runtime.removeFunction(this.$index);
}
};
function StrValue(addr) {
Value.call(this, Str.pointee, addr);
}
StrValue.$at = function(addr) { return new StrValue(addr); }
StrValue.$mallocCStr = function(s) {
var addr = malloc(s.length + 1);
Module.writeAsciiToMemory(s, addr);
return new StrValue(addr);
}
StrValue.prototype = Object.create(Value.prototype);
StrValue.prototype.toString = function() {
return '<Str>@' + this.$addr;
};
function BufferValue(addr, size) {
Value.call(this, VoidPtr.pointee, addr);
this.size = size;
}
BufferValue.$malloc = function(buf) {
var addr;
var size;
if (buf instanceof ArrayBuffer) {
size = buf.byteLength;
addr = malloc(size);
HEAPU8.set(new Uint8Array(buf), addr);
} else if (ArrayBuffer.isView(buf)) {
size = buf.byteLength;
addr = malloc(size);
HEAPU8.set(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength),
addr);
} else if (typeof buf == 'string') {
size = buf.length;
addr = malloc(size);
Module.writeAsciiToMemory(buf, addr, true); // don't null-terminate
} else {
throw new Error('unknown buffer type: ' + buf);
}
return new BufferValue(addr, size);
};
BufferValue.prototype = Object.create(Value.prototype);
BufferValue.prototype.toString = function() {
return '<Buffer>@' + this.$addr + ', ' + this.size + ' bytes';
};
// Wasm enums //////////////////////////////////////////////////////////////////
// WasmResult
var OK = 0;
var ERROR = 1;
// Wasm low-level types ////////////////////////////////////////////////////////
var I = (function() {
var Allocator = Struct('Allocator');
var AstLexer = Struct('AstLexer');
var BinaryErrorHandler = Struct('BinaryErrorHandler');
var Location = Struct('Location');
var MemoryWriter = Struct('MemoryWriter');
var OutputBuffer = Struct('OutputBuffer');
var ReadBinaryOptions = Struct('ReadBinaryOptions');
var Script = Struct('Script');
var SourceErrorHandler = Struct('SourceErrorHandler');
var StackAllocator = Struct('StackAllocator');
var Stream = Struct('Stream');
var StrSlice = Struct('StrSlice');
var WriteBinaryOptions = Struct('WriteBinaryOptions');
var Writer = Struct('Writer');
var BinaryErrorHandlerCallback = Fn(Void, [U32, Str, UserData]);
var SourceErrorHandlerCallback =
Fn(Void, [Ptr(Location), Str, StrPtr, StrLen, U32, UserData]);
Allocator.define('allocator', {
alloc: Fn(VoidPtr, [Ptr(Allocator), U32, U32, Str, I32]),
realloc: Fn(VoidPtr, [Ptr(Allocator), VoidPtr, U32, U32, Str, I32]),
free: Fn(VoidPtr, [Ptr(Allocator), VoidPtr, Str, I32]),
destroy: Fn(Void, [Ptr(Allocator)]),
mark: Fn(I32, [Ptr(Allocator)]),
reset_to_mark: Fn(Void, [Ptr(Allocator), I32]),
print_stats: Fn(Void, [Ptr(Allocator)]),
});
BinaryErrorHandler.define('binary_error_handler', {
on_error: BinaryErrorHandlerCallback,
user_data: UserData,
});
Location.define('location', {
filename: Str,
line: U32,
first_column: U32,
last_column: U32,
});
MemoryWriter.define('memory_writer', {
base: Writer,
buf: OutputBuffer,
});
OutputBuffer.define('output_buffer', {
allocator: Allocator,
start: BufPtr,
size: BufLen,
capacity: U32,
});
ReadBinaryOptions.define('read_binary_options', {
read_debug_names: Bool,
});
Script.define('script');
SourceErrorHandler.define('source_error_handler', {
on_error: SourceErrorHandlerCallback,
source_line_max_length: U32,
user_data: UserData,
});
StackAllocator.define('stack_allocator', {
allocator: Allocator,
});
Stream.define('stream', {
writer: Ptr(Writer),
result: U32,
offset: U32,
log_stream: Ptr(Stream),
});
StrSlice.define('string_slice', {
start: StrPtr,
length: StrLen,
});
WriteBinaryOptions.define('write_binary_options', {
log_stream: Ptr(Stream),
canonicalize_lebs: Bool,
write_debug_names: Bool,
});
Writer.define('writer', {
write_data: Fn(I32, [U32, BufPtr, BufLen, UserData]),
move_data: Fn(I32, [U32, U32, U32, UserData]),
});
// Wasm low-level functions ////////////////////////////////////////////////////
var checkAst = Fn(I32, [Ptr(Allocator), Ptr(AstLexer), Ptr(Script), Ptr(SourceErrorHandler)]).define('_wasm_check_ast');
var closeMemWriter = Fn(Void, [Ptr(MemoryWriter)]).define('_wasm_close_mem_writer');
var defaultBinaryErrorCallback = BinaryErrorHandlerCallback.define('_wasm_default_binary_error_callback');
var defaultSourceErrorCallback = SourceErrorHandlerCallback.define('_wasm_default_source_error_callback');
var destroyAstLexer = Fn(Void, [Ptr(AstLexer)]).define('_wasm_destroy_ast_lexer');
var destroyOutputBuffer = Fn(Void, [Ptr(OutputBuffer)]).define('_wasm_destroy_output_buffer');
var destroyScript = Fn(Void, [Ptr(Script)]).define('_wasm_destroy_script');
var destroyStackAllocator = Fn(Void, [Ptr(StackAllocator)]).define('_wasm_destroy_stack_allocator');
var getLibcAllocator = Fn(Ptr(Allocator), []).define('_wasm_get_libc_allocator');
var initMemWriter = Fn(I32, [Ptr(Allocator), Ptr(MemoryWriter)]).define('_wasm_init_mem_writer');
var initStackAllocator = Fn(Void, [Ptr(StackAllocator), Ptr(Allocator)]).define('_wasm_init_stack_allocator');
var initStream = Fn(Void, [Ptr(Stream), Ptr(Writer), Ptr(Stream)]).define('_wasm_init_stream');
var newAstBufferLexer = Fn(Ptr(AstLexer), [Ptr(Allocator), Str, BufPtr, BufLen]).define('_wasm_new_ast_buffer_lexer');
var parseAst = Fn(I32, [Ptr(AstLexer), Ptr(Script), Ptr(SourceErrorHandler)]).define('_wasm_parse_ast');
var writeBinaryScript = Fn(I32, [Ptr(Allocator), Ptr(Writer), Ptr(Script), Ptr(WriteBinaryOptions)]).define('_wasm_write_binary_script');
return {
// Types
Allocator: Allocator,
AstLexer: AstLexer,
BinaryErrorHandler: BinaryErrorHandler,
Location: Location,
MemoryWriter: MemoryWriter,
OutputBuffer: OutputBuffer,
ReadBinaryOptions: ReadBinaryOptions,
Script: Script,
SourceErrorHandler: SourceErrorHandler,
StackAllocator: StackAllocator,
Stream: Stream,
StrSlice: StrSlice,
WriteBinaryOptions: WriteBinaryOptions,
Writer: Writer,
// Functions
checkAst: checkAst,
closeMemWriter: closeMemWriter,
defaultBinaryErrorCallback: defaultBinaryErrorCallback,
defaultSourceErrorCallback: defaultSourceErrorCallback,
destroyAstLexer: destroyAstLexer,
destroyOutputBuffer: destroyOutputBuffer,
destroyScript: destroyScript,
destroyStackAllocator: destroyStackAllocator,
getLibcAllocator: getLibcAllocator,
initMemWriter: initMemWriter,
initStackAllocator: initStackAllocator,
initStream: initStream,
newAstBufferLexer: newAstBufferLexer,
parseAst: parseAst,
writeBinaryScript: writeBinaryScript,
};
})();
// Helpers for friendly objects ////////////////////////////////////////////////
function define$From(obj) {
obj.$from = function($) {
var o = Object.create(obj.prototype);
o.$ = $;
return o;
};
}
function definePrimitiveGetter(proto, name, cName) {
cName = cName || name;
Object.defineProperty(proto, name, {
get: function() { return this.$[cName].load(); }
});
}
function definePrimitiveGetterSetter(proto, name, cName) {
cName = cName || name;
Object.defineProperty(proto, name, {
get: function() { return this.$[cName].load(); },
set: function(value) { this.$[cName].store(value); },
});
}
function defineStrGetter(proto, name, cName) {
cName = cName || name;
Object.defineProperty(proto, name, {
get: function() {
return this.$[cName].load();
}
});
}
// Get a StrSlice, given a pointer and length
function defineStrSliceGetter(proto, name, ptrName, lenName) {
Object.defineProperty(proto, name, {
get: function() {
var offset = this.$[ptrName].load();
var length = this.$[lenName].load();
return loadstrslice(offset.$addr, length);
}
});
}
// Get a StrSlice, given a StrSlice object
function defineStrSliceObjGetter(proto, name, cName) {
cName = cName || name;
Object.defineProperty(proto, name, {
get: function() {
var offset = this.$[cName].start.load();
var length = this.$[cName].length.load();
return loadstrslice(offset.$addr, length);
}
});
}
function defineBufferGetter(proto, name, ptrName, lenName) {
Object.defineProperty(proto, name, {
get: function() {
var offset = this.$[ptrName].load();
var length = this.$[lenName].load();
return loadbuffer(offset.$addr, length);
}
});
}
// Friendly objects ////////////////////////////////////////////////////////////
// Allocator
function Allocator() {
this.$ = null;
}
define$From(Allocator);
Allocator.prototype = Object.create(Object.prototype);
// StackAllocator
function StackAllocator(fallbackAllocator) {
this.$ = Value.$malloc(I.StackAllocator);
I.initStackAllocator(this.$, fallbackAllocator.$);
this.allocator = Allocator.$from(this.$.allocator);
}
StackAllocator.prototype = Object.create(Object.prototype);
StackAllocator.prototype.$destroy = function() {
I.destroyStackAllocator(this.$);
this.$.$free();
};
// LibcAllocator
LibcAllocator = Allocator.$from(I.getLibcAllocator());
// Writer
function Writer(writeData, moveData) {
this.$ = Value.$malloc(I.Writer);
this.$writeData = new FnValue(I.Writer.fields.write_data.type, writeData);
this.$moveData = new FnValue(I.Writer.fields.move_data.type, moveData);
this.$.write_data.store(this.$writeData);
this.$.move_data.store(this.$moveData);
}
define$From(Writer);
Writer.prototype = Object.create(Object.prototype);
Writer.prototype.$destroy = function() {
this.$moveData.$destroy();
this.$writeData.$destroy();
this.$.$free();
};
// MemoryWriter
function MemoryWriter(allocator) {
this.$ = Value.$malloc(I.MemoryWriter);
var result = I.initMemWriter(allocator.$, this.$);
if (result != OK) {
throw new Error('unable to initialize MemoryWriter');
}
this.writer = Writer.$from(this.$.base);
this.buf = OutputBuffer.$from(this.$.buf);
}
MemoryWriter.prototype = Object.create(MemoryWriter.prototype);
MemoryWriter.prototype.$destroy = function() {
I.closeMemWriter(this.$);
this.$.$free();
};
// OutputBuffer
function OutputBuffer() {
this.$ = null;
}
define$From(OutputBuffer);
OutputBuffer.prototype = Object.create(Object.prototype);
defineBufferGetter(OutputBuffer.prototype, 'buf', 'start', 'size');
defineStrSliceGetter(OutputBuffer.prototype, 'bufStr', 'start', 'size');
definePrimitiveGetter(OutputBuffer.prototype, 'size');
definePrimitiveGetter(OutputBuffer.prototype, 'capacity');
OutputBuffer.prototype.$destroy = function() {
I.destroyOutputBuffer(this.$);
this.$.$free();
};
// Stream
function Stream(writer, logStream) {
this.$ = Value.$malloc(I.Stream);
I.initStream(this.$, writer.$, logStream ? logStream.$ : null);
}
Stream.prototype = Object.create(Object.prototype);
definePrimitiveGetter(Stream.prototype, 'offset');
definePrimitiveGetter(Stream.prototype, 'result');
Stream.prototype.$destroy = function() {
this.$.$free();
};
// StringStream
function StringStream() {
this.$writer = new MemoryWriter(LibcAllocator);
Stream.call(this, this.$writer.writer, null);
}
StringStream.prototype = Object.create(Object.prototype);
Object.defineProperty(StringStream.prototype, 'string', {
get: function() {
return this.$writer.buf.bufStr;
}
});
StringStream.prototype.$destroy = function() {
Stream.prototype.$destroy.call(this);
this.$writer.$destroy();
};
// parseAst
function parseAst(allocator, filename, buffer) {
var sourceLineMaxLength = 80;
var astLexer = new AstLexer(allocator, filename, buffer);
var errorHandler = new SourceErrorHandler(sourceLineMaxLength);
var script = new Script();
var result = I.parseAst(astLexer.$, script.$, errorHandler.$);
script.$allocator = allocator;
script.$astLexer = astLexer;
script.$errorHandler = errorHandler;
if (result != OK) {
script.$destroy();
throw new Error('parseAst failed:\n' + errorHandler.errorMessage);
}
return script;
}
// AstLexer
function AstLexer(allocator, filename, buffer) {
this.$filename = StrValue.$mallocCStr(filename);
this.$buffer = BufferValue.$malloc(buffer);
this.$ = I.newAstBufferLexer(allocator.$, this.$filename, this.$buffer,
this.$buffer.size);
if (this.$ === null) {
this.$buffer.$free();
this.$filename.$free();
throw new Error('unable to create AstLexer');
}
}
AstLexer.prototype = Object.create(Object.prototype);
AstLexer.prototype.$destroy = function() {
I.destroyAstLexer(this.$);
this.$.$free();
this.$buffer.$free();
this.$filename.$free();
}
// SourceErrorHandler
function SourceErrorHandler(sourceLineMaxLength) {
this.$ = Value.$malloc(I.SourceErrorHandler);
var wrapper = function(loc, error, sourceLine, sourceLineLength,
sourceLineColumnOffset, userData){
loc = Location.$from(loc);
sourceLine = loadstrslice(sourceLine.$addr, sourceLineLength);
var lines = [
loc.filename + ':' + loc.line + ':' + loc.firstColumn,
error
];
if (sourceLine.length > 0) {
var numSpaces = loc.firstColumn - 1 - sourceLineColumnOffset;
var numCarets =
Math.min(loc.lastColumn - loc.firstColumn, sourceLine.length);
lines.push(sourceLine);
lines.push(' '.repeat(numSpaces) + '^'.repeat(numCarets));
}
this.errorMessage += lines.join('\n') + '\n';
}.bind(this);
this.$callback =
new FnValue(I.SourceErrorHandler.fields.on_error.type, wrapper);
this.$.on_error.store(this.$callback);
this.$.source_line_max_length.store(sourceLineMaxLength);
this.errorMessage = '';
}
SourceErrorHandler.prototype = Object.create(Object.prototype);
SourceErrorHandler.prototype.$destroy = function() {
this.$callback.$destroy();
this.$.$free();
};
// Location
function Location() {
this.$ = null;
}
define$From(Location);
Location.prototype = Object.create(Object.prototype);
defineStrGetter(Location.prototype, 'filename');
definePrimitiveGetter(Location.prototype, 'line');
definePrimitiveGetter(Location.prototype, 'firstColumn', 'first_column');
definePrimitiveGetter(Location.prototype, 'lastColumn', 'last_column');
Location.prototype.$destroy = function() {
this.$.$free();
};
// Script
function Script() {
this.$ = Value.$malloc(I.Script);
this.$allocator = null;
this.$astLexer = null;
this.$errorHandler = null;
}
Script.prototype = Object.create(Object.prototype);
Script.prototype.check = function() {
var result = I.checkAst(
this.$allocator.$, this.$astLexer.$, this.$, this.$errorHandler.$);
if (result != OK) {
throw new Error('check failed:\n' + this.$errorHandler.errorMessage);
}
};
Script.prototype.toBinary = function(options) {
var mw = new MemoryWriter(this.$allocator);
options = new WriteBinaryOptions(this.$allocator, options || {});
try {
var result =
I.writeBinaryScript(this.$allocator.$, mw.writer.$, this.$, options.$);
if (result != OK) {
throw new Error('writeBinaryScript failed');
}
return {buffer: mw.buf.buf, log: options.log}
} finally {
options.$destroy();
mw.$destroy();
}
};
Script.prototype.$destroy = function() {
I.destroyScript(this.$);
if (this.$errorHandler) this.$errorHandler.$destroy();
if (this.$astLexer) this.$astLexer.$destroy();
this.$.$free();
};
// WriteBinaryOptions
function WriteBinaryOptions(allocator, options) {
this.$ = Value.$malloc(I.WriteBinaryOptions);
if (options.log) {
this.$logStream = new StringStream();
this.$.log_stream.store(this.$logStream.$);
} else {
this.$logStream = null;
this.$.log_stream.store(null);
}
var optBool = function(v, def) { return v === undefined ? def : v; };
this.$.canonicalize_lebs.store(optBool(options.canonicalizeLebs, true));
this.$.write_debug_names.store(optBool(options.writeDebugNames, false));
}
WriteBinaryOptions.prototype = Object.create(Object.prototype);
Object.defineProperty(WriteBinaryOptions.prototype, 'log', {
get: function() {
return this.$logStream ? this.$logStream.string : '';
}
});
WriteBinaryOptions.prototype.$destroy = function() {
if (this.$logStream) {
this.$logStream.$destroy();
}
this.$.$free();
};
// BinaryErrorHandler
function BinaryErrorHandler() {
this.$ = Value.$malloc(I.BinaryErrorHandler);
this.$callback = new FnValue(
I.BinaryErrorHandler.fields.on_error.type,
function(offset, error, userData) {
this.errorMessage += '@0x' + offset.toString(16) + ': ' + error + '\n';
}.bind(this));
this.$.on_error.store(this.$callback);
this.$.user_data.store(null);
this.errorMessage = '';
}
BinaryErrorHandler.prototype = Object.create(Object.prototype);
BinaryErrorHandler.prototype.$destroy = function() {
this.$callback.$destroy();
this.$.$free();
};
// ReadBinaryOptions
function ReadBinaryOptions(options) {
this.$ = Value.$malloc(I.ReadBinaryOptions);
this.$.read_debug_names.store(options.readDebugNames || false);
}
ReadBinaryOptions.prototype = Object.create(Object.prototype);
ReadBinaryOptions.prototype.$destroy = function() {
this.$.$free();
};
////////////////////////////////////////////////////////////////////////////////
var resolve = wasm.$resolve;
wasm = {
ready: wasm.ready,
// Types
LibcAllocator: LibcAllocator,
StackAllocator: StackAllocator,
// Functions
parseAst: parseAst,
};
resolve();
};
/*
wasm.ready.then(function() {
if (false) {
try {
var sa = new wasm.StackAllocator(wasm.LibcAllocator);
var a = sa.allocator;
var data = '(module\n (func (result i32)\n (i32.const 1)))';
var s = wasm.parseAst(a, 'foo.wast', data);
s.check();
var output = s.toBinary({log: true});
print('log output:\n' + output.log);
print('output:\n' + output.buffer);
} catch(e) {
print('ERROR' + (e.stack ? e.stack : e));
}
if (s) s.$destroy();
if (sa) sa.$destroy();
}
if (false) {
try {
var a = new wasm.StackAllocator(wasm.LibcAllocator);
var ma = wasm.LibcAllocator;
var buf = new Uint8Array([
0, 97, 115, 109, 11, 0, 0, 0, 4, 116, 121, 112, 101, 8, 2, 64,
1, 1, 0, 64, 0, 0, 6, 105, 109, 112, 111, 114, 116, 12, 1, 0,
3, 115, 116, 100, 5, 112, 114, 105, 110, 116, 8, 102, 117, 110, 99, 116,
105, 111, 110, 2, 1, 1, 6, 101, 120, 112, 111, 114, 116, 4, 1, 0,
1, 102, 4, 99, 111, 100, 101, 8, 1, 6, 0, 16, 42, 24, 1, 0,
]);
var im = wasm.readInterpreterModule(a.allocator, ma, buf, {});
var it = new wasm.InterpreterThread(a.allocator, im);
im.run(it, 1000, 0);
} catch(e) {
print('ERROR:' + (e.stack ? e.stack : e));
}
if (it) it.$destroy();
if (im) im.$destroy();
if (a) a.$destroy();
}
if (true) {
try {
var sa = new wasm.StackAllocator(wasm.LibcAllocator);
var a = sa.allocator;
var source = `
(module
(func $test (result i32)
(call $fib (i32.const 4)))
(func $fib (param $p i32) (result i32)
(local $a i32)
(local $b i32)
(local $t i32)
(set_local $a (i32.const 1))
(set_local $b (i32.const 1))
(loop $exit $cont
(set_local $p (i32.sub (get_local $p) (i32.const 1)))
(br_if $exit (i32.le_s (get_local $p) (i32.const 0)))
(set_local $t (get_local $b))
(set_local $b (i32.add (get_local $a) (get_local $b)))
(set_local $a (get_local $t))
(br $cont))
(return (get_local $b)))
(export "test" $test))
`;
var s = wasm.parseAst(a, 'foo.wast', source);
s.check();
var output = s.toBinary();
var im = wasm.readInterpreterModule(a, wasm.LibcAllocator, output.buffer);
if (true) {
print('disassemble:\n' + im.disassemble(0, 1000));
}
if (true) {
var it = new wasm.InterpreterThread(a, im);
print('running');
do {
print(im.tracePC(it).trimRight());
} while (im.run(it, 1, 0));
print('done');
}
} catch(e) {
print('ERROR:' + (e.stack ? e.stack : e));
}
if (it) it.$destroy();
if (im) im.$destroy();
if (s) s.$destroy();
if (sa) sa.$destroy();
}
}).catch(function(error) {
print('ERROR:' + (error.stack ? error.stack : error));
});
*/