1172 строки
47 KiB
JavaScript
1172 строки
47 KiB
JavaScript
//"use strict";
|
|
|
|
// Convert analyzed data to javascript. Everything has already been calculated
|
|
// before this stage, which just does the final conversion to JavaScript.
|
|
|
|
// Handy sets
|
|
|
|
var STRUCT_LIST = set('struct', 'list');
|
|
var UNDERSCORE_OPENPARENS = set('_', '(');
|
|
|
|
// JSifier
|
|
function JSify(data, functionsOnly, givenFunctions) {
|
|
var mainPass = !functionsOnly;
|
|
|
|
if (mainPass) {
|
|
// We will start to print out the data, but must do so carefully - we are
|
|
// dealing with potentially *huge* strings. Convenient replacements and
|
|
// manipulations may create in-memory copies, and we may OOM.
|
|
//
|
|
// Final shape that will be created:
|
|
// shell
|
|
// (body)
|
|
// preamble
|
|
// runtime
|
|
// generated code
|
|
// postamble
|
|
// global_vars
|
|
//
|
|
// First, we print out everything until the generated code. Then the
|
|
// functions will print themselves out as they are parsed. Finally, we
|
|
// will call finalCombiner in the main pass, to print out everything
|
|
// else. This lets us not hold any strings in memory, we simply print
|
|
// things out as they are ready.
|
|
|
|
var shellFile = BUILD_AS_SHARED_LIB ? 'shell_sharedlib.js' : 'shell.js';
|
|
var shellParts = read(shellFile).split('{{BODY}}');
|
|
print(shellParts[0]);
|
|
var preFile = BUILD_AS_SHARED_LIB ? 'preamble_sharedlib.js' : 'preamble.js';
|
|
var pre = processMacros(preprocess(read(preFile).replace('{{RUNTIME}}', getRuntime())));
|
|
print(pre);
|
|
|
|
Functions.implementedFunctions = set(data.unparsedFunctions.map(function(func) { return func.ident }));
|
|
}
|
|
|
|
// Does simple 'macro' substitution, using Django-like syntax,
|
|
// {{{ code }}} will be replaced with |eval(code)|.
|
|
function processMacros(text) {
|
|
return text.replace(/{{{[^}]+}}}/g, function(str) {
|
|
str = str.substr(3, str.length-6);
|
|
return eval(str).toString();
|
|
});
|
|
}
|
|
|
|
var substrate = new Substrate('JSifyer');
|
|
|
|
if (mainPass) {
|
|
// Handle unparsed types TODO: Batch them
|
|
analyzer(intertyper(data.unparsedTypess[0].lines, true));
|
|
data.unparsedTypess = null;
|
|
|
|
// Add additional necessary items for the main pass. We can now do this since types are parsed (types can be used through
|
|
// generateStructInfo in library.js)
|
|
LibraryManager.load();
|
|
var libFuncsToInclude;
|
|
if (INCLUDE_FULL_LIBRARY) {
|
|
assert(!BUILD_AS_SHARED_LIB, 'Cannot have both INCLUDE_FULL_LIBRARY and BUILD_AS_SHARED_LIB set.')
|
|
libFuncsToInclude = [];
|
|
for (var key in LibraryManager.library) {
|
|
if (!key.match(/__(deps|postset)$/)) {
|
|
libFuncsToInclude.push(key);
|
|
}
|
|
}
|
|
} else {
|
|
libFuncsToInclude = ['memset', 'malloc', 'free'];
|
|
}
|
|
libFuncsToInclude.forEach(function(ident) {
|
|
data.functionStubs.push({
|
|
intertype: 'functionStub',
|
|
ident: '_' + ident
|
|
});
|
|
});
|
|
}
|
|
|
|
// Functions
|
|
|
|
Functions.currFunctions = !mainPass ? givenFunctions.currFunctions : {};
|
|
Functions.currExternalFunctions = !mainPass ? givenFunctions.currExternalFunctions : {};
|
|
|
|
// Now that first-pass analysis has completed (so we have basic types, etc.), we can get around to handling unparsedFunctions
|
|
(!mainPass ? data.functions : data.unparsedFunctions.concat(data.functions)).forEach(function(func) {
|
|
// Save just what we need, to save memory
|
|
Functions.currFunctions[func.ident] = {
|
|
hasVarArgs: func.hasVarArgs,
|
|
numParams: func.params.length,
|
|
labelIds: func.labelIds // TODO: We need this for globals, but perhaps we can calculate them early and free this
|
|
};
|
|
});
|
|
|
|
data.functionStubs.forEach(function(func) {
|
|
// Don't overwrite stubs that have more info.
|
|
if (!Functions.currExternalFunctions.hasOwnProperty(func.ident) ||
|
|
!Functions.currExternalFunctions[func.ident].numParams === undefined) {
|
|
Functions.currExternalFunctions[func.ident] = {
|
|
hasVarArgs: func.hasVarArgs,
|
|
numParams: func.params && func.params.length
|
|
};
|
|
}
|
|
});
|
|
|
|
var MAX_BATCH_FUNC_LINES = 1000;
|
|
while (data.unparsedFunctions.length > 0) {
|
|
var currFuncLines = [];
|
|
var currBaseLineNums = [];
|
|
while (currFuncLines.length == 0 ||
|
|
(data.unparsedFunctions.length > 0 && currFuncLines.length + data.unparsedFunctions[0].lines.length <= MAX_BATCH_FUNC_LINES)) {
|
|
currBaseLineNums.push([currFuncLines.length, data.unparsedFunctions[0].lineNum-1]);
|
|
currFuncLines = currFuncLines.concat(data.unparsedFunctions[0].lines); // for first one, assign, do not concat?
|
|
data.unparsedFunctions.shift();
|
|
}
|
|
dprint('unparsedFunctions','====================\n// Processing function batch of ' + currBaseLineNums.length +
|
|
' functions, ' + currFuncLines.length + ' lines, functions left: ' + data.unparsedFunctions.length);
|
|
if (DEBUG_MEMORY) MemoryDebugger.tick('pre-func');
|
|
JSify(analyzer(intertyper(currFuncLines, true, currBaseLineNums)), true, Functions);
|
|
if (DEBUG_MEMORY) MemoryDebugger.tick('post-func');
|
|
}
|
|
currFuncLines = currBaseLineNums = null; // Do not hold on to anything from inside that loop (JS function scoping..)
|
|
data.unparsedFunctions = null;
|
|
|
|
// Actors
|
|
|
|
// type
|
|
// FIXME: This is no longer used, we do not actually need to JSify on types. TODO: Remove this and related code
|
|
substrate.addActor('Type', {
|
|
processItem: function(item) {
|
|
var type = Types.types[item.name_];
|
|
var niceName = toNiceIdent(item.name_);
|
|
// We might export all of Types.types, cleaner that way, but do not want slowdowns in accessing flatteners
|
|
item.JS = 'var ' + niceName + '___SIZE = ' + Types.types[item.name_].flatSize + '; // ' + item.name_ + '\n';
|
|
if (type.needsFlattening && !type.flatFactor) {
|
|
item.JS += 'var ' + niceName + '___FLATTENER = ' + JSON.stringify(Types.types[item.name_].flatIndexes) + ';';
|
|
}
|
|
return [item];
|
|
}
|
|
});
|
|
|
|
function makeEmptyStruct(type) {
|
|
var ret = [];
|
|
var typeData = Types.types[type];
|
|
assertTrue(typeData);
|
|
for (var i = 0; i < typeData.flatSize; i++) {
|
|
ret.push(0);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
function alignStruct(values, type) {
|
|
var typeData = Types.types[type];
|
|
assertTrue(typeData);
|
|
var ret = new Array(typeData.flatSize);
|
|
var index = 0;
|
|
var i = 0;
|
|
while (i < values.length) {
|
|
// Pad until the right place
|
|
var padded = typeData.flatFactor ? typeData.flatFactor*i : typeData.flatIndexes[i];
|
|
while (index < padded) {
|
|
ret[index++] = 0;
|
|
}
|
|
// Add current value(s)
|
|
var currValue = flatten(values[i]);
|
|
if (I64_MODE == 1 && typeData.fields[i] == 'i64') {
|
|
// 'flatten' out the 64-bit value into two 32-bit halves
|
|
ret[index++] = currValue>>>0;
|
|
ret[index++] = 0;
|
|
ret[index++] = 0;
|
|
ret[index++] = 0;
|
|
ret[index++] = Math.floor(currValue/4294967296);
|
|
ret[index++] = 0;
|
|
ret[index++] = 0;
|
|
ret[index++] = 0;
|
|
} else if (typeof currValue == 'object') {
|
|
for (var j = 0; j < currValue.length; j++) {
|
|
ret[index++] = currValue[j];
|
|
}
|
|
} else {
|
|
ret[index++] = currValue;
|
|
}
|
|
i += 1;
|
|
}
|
|
while (index < typeData.flatSize) {
|
|
ret[index++] = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Gets an entire constant expression
|
|
function makeConst(value, type, ident) {
|
|
// Gets an array of constant items, separated by ',' tokens
|
|
function handleSegments(tokens) {
|
|
// Handle a single segment (after comma separation)
|
|
function handleSegment(segment) {
|
|
var ret;
|
|
if (segment.intertype === 'value') {
|
|
ret = segment.ident;
|
|
} else if (segment.intertype === 'emptystruct') {
|
|
ret = makeEmptyStruct(segment.type);
|
|
} else if (segment.intertype in PARSABLE_LLVM_FUNCTIONS) {
|
|
ret = finalizeLLVMFunctionCall(segment);
|
|
} else if (segment.intertype in STRUCT_LIST) {
|
|
ret = alignStruct(handleSegments(segment.contents), segment.type);
|
|
} else if (segment.intertype === 'string') {
|
|
ret = parseLLVMString(segment.text); // + ' /* ' + text + '*/';
|
|
} else if (segment.intertype === 'blockaddress') {
|
|
ret = finalizeBlockAddress(segment);
|
|
} else {
|
|
throw 'Invalid segment: ' + dump(segment);
|
|
}
|
|
assert(segment.type, 'Missing type for constant segment!');
|
|
return indexizeFunctions(ret, segment.type);
|
|
};
|
|
return tokens.map(handleSegment)
|
|
}
|
|
|
|
//dprint('jsifier const: ' + JSON.stringify(value) + ',' + type + '\n');
|
|
if (value.intertype in PARSABLE_LLVM_FUNCTIONS) {
|
|
return [finalizeLLVMFunctionCall(value)];
|
|
} else if (Runtime.isNumberType(type) || pointingLevels(type) >= 1) {
|
|
return indexizeFunctions(parseNumerical(value.value), type);
|
|
} else if (value.intertype === 'emptystruct') {
|
|
return makeEmptyStruct(type);
|
|
} else if (value.intertype === 'string') {
|
|
return JSON.stringify(parseLLVMString(value.text)) +
|
|
' /* ' + value.text.substr(0, 20).replace(/\*/g, '_') + ' */'; // make string safe for inclusion in comment
|
|
} else {
|
|
return alignStruct(handleSegments(value.contents), type);
|
|
}
|
|
}
|
|
|
|
function parseConst(value, type, ident) {
|
|
var constant = makeConst(value, type);
|
|
if (typeof constant === 'object') {
|
|
constant = flatten(constant).map(function(x) { return parseNumerical(x) })
|
|
}
|
|
return constant;
|
|
}
|
|
|
|
// globalVariable
|
|
substrate.addActor('GlobalVariable', {
|
|
processItem: function(item) {
|
|
function needsPostSet(value) {
|
|
return value[0] in UNDERSCORE_OPENPARENS || value.substr(0, 14) === 'CHECK_OVERFLOW';
|
|
}
|
|
|
|
item.intertype = 'GlobalVariableStub';
|
|
assert(!item.lines); // FIXME remove this, after we are sure it isn't needed
|
|
var ret = [item];
|
|
if (item.ident == '_llvm_global_ctors') {
|
|
item.JS = '\n__globalConstructor__ = function() {\n' +
|
|
item.ctors.map(function(ctor) { return ' ' + toNiceIdent(ctor) + '();' }).join('\n') +
|
|
'\n}\n';
|
|
return ret;
|
|
} else {
|
|
if (item.external && BUILD_AS_SHARED_LIB) {
|
|
// External variables in shared libraries should not be declared as
|
|
// they would shadow similarly-named globals in the parent.
|
|
item.JS = '';
|
|
} else {
|
|
item.JS = 'var ' + item.ident + ';';
|
|
}
|
|
var constant = null;
|
|
if (item.external) {
|
|
// Import external global variables from the library if available.
|
|
var shortident = item.ident.slice(1);
|
|
if (LibraryManager.library[shortident] &&
|
|
LibraryManager.library[shortident].length &&
|
|
!BUILD_AS_SHARED_LIB) {
|
|
var val = LibraryManager.library[shortident];
|
|
var padding;
|
|
if (Runtime.isNumberType(item.type) || isPointerType(item.type)) {
|
|
padding = [item.type].concat(zeros(Runtime.getNativeFieldSize(item.type)));
|
|
} else {
|
|
padding = makeEmptyStruct(item.type);
|
|
}
|
|
var padded = val.concat(padding.slice(val.length));
|
|
var js = item.ident + '=' + makePointer(JSON.stringify(padded), null, 'ALLOC_STATIC', item.type) + ';'
|
|
if (LibraryManager.library[shortident + '__postset']) {
|
|
js += '\n' + LibraryManager.library[shortident + '__postset'];
|
|
}
|
|
ret.push({
|
|
intertype: 'GlobalVariablePostSet',
|
|
JS: js
|
|
});
|
|
}
|
|
return ret;
|
|
} else {
|
|
constant = parseConst(item.value, item.type, item.ident);
|
|
if (typeof constant === 'string' && constant[0] != '[') {
|
|
constant = [constant]; // A single item. We may need a postset for it.
|
|
}
|
|
if (typeof constant === 'object') {
|
|
// This is a flattened object. We need to find its idents, so they can be assigned to later
|
|
constant.forEach(function(value, i) {
|
|
if (needsPostSet(value)) { // ident, or expression containing an ident
|
|
ret.push({
|
|
intertype: 'GlobalVariablePostSet',
|
|
JS: makeSetValue(item.ident, i, value, 'i32', false, true) // ignore=true, since e.g. rtti and statics cause lots of safe_heap errors
|
|
});
|
|
constant[i] = '0';
|
|
}
|
|
});
|
|
constant = '[' + constant.join(', ') + ']';
|
|
}
|
|
// NOTE: This is the only place that could potentially create static
|
|
// allocations in a shared library.
|
|
constant = makePointer(constant, null, BUILD_AS_SHARED_LIB ? 'ALLOC_NORMAL' : 'ALLOC_STATIC', item.type);
|
|
|
|
var js = item.ident + '=' + constant + ';';
|
|
// Special case: class vtables. We make sure they are null-terminated, to allow easy runtime operations
|
|
if (item.ident.substr(0, 5) == '__ZTV') {
|
|
js += '\n' + makePointer('[0]', null, BUILD_AS_SHARED_LIB ? 'ALLOC_NORMAL' : 'ALLOC_STATIC', ['void*']) + ';';
|
|
}
|
|
if (item.ident in EXPORTED_GLOBALS) {
|
|
js += '\nModule["' + item.ident + '"] = ' + item.ident + ';';
|
|
}
|
|
if (BUILD_AS_SHARED_LIB == 2 && !item.private_) {
|
|
// TODO: make the assert conditional on ASSERTIONS
|
|
js += 'if (globalScope) { assert(!globalScope["' + item.ident + '"]); globalScope["' + item.ident + '"] = ' + item.ident + ' }';
|
|
}
|
|
return ret.concat({
|
|
intertype: 'GlobalVariable',
|
|
JS: js,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// alias
|
|
substrate.addActor('Alias', {
|
|
processItem: function(item) {
|
|
item.intertype = 'GlobalVariableStub';
|
|
var ret = [item];
|
|
item.JS = 'var ' + item.ident + ';';
|
|
// Set the actual value in a postset, since it may be a global variable. We also order by dependencies there
|
|
var value = Variables.globals[item.ident].resolvedAlias = finalizeLLVMParameter(item.value);
|
|
ret.push({
|
|
intertype: 'GlobalVariablePostSet',
|
|
ident: item.ident,
|
|
dependencies: set([value]),
|
|
JS: item.ident + ' = ' + value + ';'
|
|
});
|
|
return ret;
|
|
}
|
|
});
|
|
|
|
var addedLibraryItems = {};
|
|
|
|
// functionStub
|
|
substrate.addActor('FunctionStub', {
|
|
processItem: function(item) {
|
|
function addFromLibrary(ident) {
|
|
if (ident in addedLibraryItems) return '';
|
|
// Don't replace implemented functions with library ones (which can happen when we add dependencies).
|
|
// Note: We don't return the dependencies here. Be careful not to end up where this matters
|
|
if (('_' + ident) in Functions.implementedFunctions) return '';
|
|
|
|
addedLibraryItems[ident] = true;
|
|
var snippet = LibraryManager.library[ident];
|
|
var redirectedIdent = null;
|
|
var deps = LibraryManager.library[ident + '__deps'] || [];
|
|
var isFunction = false;
|
|
|
|
if (typeof snippet === 'string') {
|
|
if (LibraryManager.library[snippet]) {
|
|
// Redirection for aliases. We include the parent, and at runtime make ourselves equal to it.
|
|
// This avoid having duplicate functions with identical content.
|
|
redirectedIdent = snippet;
|
|
deps.push(snippet);
|
|
snippet = '_' + snippet;
|
|
}
|
|
} else if (typeof snippet === 'object') {
|
|
snippet = stringifyWithFunctions(snippet);
|
|
} else if (typeof snippet === 'function') {
|
|
isFunction = true;
|
|
snippet = snippet.toString();
|
|
assert(snippet.indexOf('XXX missing C define') == -1,
|
|
'Trying to include a library function with missing C defines: ' + ident + ' | ' + snippet);
|
|
|
|
// name the function; overwrite if it's already named
|
|
snippet = snippet.replace(/function(?:\s+([^(]+))?\s*\(/, 'function _' + ident + '(');
|
|
if (LIBRARY_DEBUG) {
|
|
snippet = snippet.replace('{', '{ print("[library call:' + ident + ']"); ');
|
|
}
|
|
}
|
|
|
|
var postsetId = ident + '__postset';
|
|
var postset = LibraryManager.library[postsetId];
|
|
if (postset && !addedLibraryItems[postsetId]) {
|
|
addedLibraryItems[postsetId] = true;
|
|
ret.push({
|
|
intertype: 'GlobalVariablePostSet',
|
|
JS: postset
|
|
});
|
|
}
|
|
|
|
if (redirectedIdent) {
|
|
deps = deps.concat(LibraryManager.library[redirectedIdent + '__deps'] || []);
|
|
}
|
|
// $ident's are special, we do not prefix them with a '_'.
|
|
if (ident[0] === '$') {
|
|
ident = ident.substr(1);
|
|
} else {
|
|
ident = '_' + ident;
|
|
}
|
|
var text = (deps ? '\n' + deps.map(addFromLibrary).join('\n') : '');
|
|
text += isFunction ? snippet : 'var ' + ident + '=' + snippet + ';';
|
|
if (ident in EXPORTED_FUNCTIONS) {
|
|
text += '\nModule["' + ident + '"] = ' + ident + ';';
|
|
}
|
|
return text;
|
|
}
|
|
|
|
var ret = [item];
|
|
if (IGNORED_FUNCTIONS.indexOf(item.ident) >= 0) return null;
|
|
var shortident = item.ident.substr(1);
|
|
if (BUILD_AS_SHARED_LIB) {
|
|
// Shared libraries reuse the runtime of their parents.
|
|
item.JS = '';
|
|
} else if (LibraryManager.library.hasOwnProperty(shortident)) {
|
|
item.JS = addFromLibrary(shortident);
|
|
} else {
|
|
item.JS = 'var ' + item.ident + '; // stub for ' + item.ident;
|
|
}
|
|
return ret;
|
|
}
|
|
});
|
|
|
|
// function splitter
|
|
substrate.addActor('FunctionSplitter', {
|
|
processItem: function(item) {
|
|
var ret = [item];
|
|
item.splitItems = 0;
|
|
item.labels.forEach(function(label) {
|
|
label.lines.forEach(function(line) {
|
|
line.func = item.ident;
|
|
line.funcData = item; // TODO: remove all these, access it globally
|
|
line.parentLabel = label.ident;
|
|
ret.push(line);
|
|
item.splitItems ++;
|
|
});
|
|
});
|
|
|
|
this.forwardItems(ret, 'FuncLineTriager');
|
|
}
|
|
});
|
|
|
|
// function reconstructor & post-JS optimizer
|
|
substrate.addActor('FunctionReconstructor', {
|
|
funcs: {},
|
|
seen: {},
|
|
processItem: function(item) {
|
|
if (this.seen[item.__uid__]) return null;
|
|
if (item.intertype == 'function') {
|
|
this.funcs[item.ident] = item;
|
|
item.relines = {};
|
|
this.seen[item.__uid__] = true;
|
|
return null;
|
|
}
|
|
var line = item;
|
|
var func = this.funcs[line.func];
|
|
if (!func) return null;
|
|
|
|
// Re-insert our line
|
|
this.seen[item.__uid__] = true;
|
|
var label = func.labels.filter(function(label) { return label.ident == line.parentLabel })[0];
|
|
label.lines = label.lines.map(function(line2) {
|
|
return (line2.lineNum !== line.lineNum) ? line2 : line;
|
|
});
|
|
func.splitItems --;
|
|
// OLD delete line.funcData; // clean up
|
|
if (func.splitItems > 0) return null;
|
|
|
|
// We have this function all reconstructed, go and finalize it's JS!
|
|
|
|
if (IGNORED_FUNCTIONS.indexOf(func.ident) >= 0) return null;
|
|
|
|
func.JS = '\n';
|
|
|
|
if (CLOSURE_ANNOTATIONS) {
|
|
func.JS += '/**\n';
|
|
func.paramIdents.forEach(function(param) {
|
|
func.JS += ' * @param {number} ' + param + '\n';
|
|
});
|
|
func.JS += ' * @return {number}\n'
|
|
func.JS += ' */\n';
|
|
}
|
|
|
|
func.JS += 'function ' + func.ident + '(' + func.paramIdents.join(', ') + ') {\n';
|
|
|
|
if (PROFILE) {
|
|
func.JS += ' if (PROFILING) { '
|
|
+ 'var __parentProfilingNode__ = PROFILING_NODE; PROFILING_NODE = PROFILING_NODE.children["' + func.ident + '"]; '
|
|
+ 'if (!PROFILING_NODE) __parentProfilingNode__.children["' + func.ident + '"] = PROFILING_NODE = { time: 0, children: {}, calls: 0 };'
|
|
+ 'PROFILING_NODE.calls++; '
|
|
+ 'var __profilingStartTime__ = Date.now() '
|
|
+ '}\n';
|
|
}
|
|
|
|
func.JS += ' ' + RuntimeGenerator.stackEnter(func.initialStack) + ';\n';
|
|
|
|
// Make copies of by-value params
|
|
// XXX It is not clear we actually need this. While without this we fail, it does look like
|
|
// Clang normally does the copy itself, in the calling function. We only need this code
|
|
// when Clang optimizes the code and passes the original, not the copy, to the other
|
|
// function. But Clang still copies, the copy is just unused! Need to figure out if that
|
|
// is caused by our running just some optimizations (the safe ones), or if its a bug
|
|
// in Clang, or a bug in our understanding of the IR.
|
|
func.params.forEach(function(param) {
|
|
if (param.byVal) {
|
|
var type = removePointing(param.type);
|
|
var typeInfo = Types.types[type];
|
|
func.JS += ' var tempParam = ' + param.ident + '; ' + param.ident + ' = ' + RuntimeGenerator.stackAlloc(typeInfo.flatSize) + ';' +
|
|
makeCopyValues(param.ident, 'tempParam', typeInfo.flatSize, 'null') + ';\n';
|
|
}
|
|
});
|
|
|
|
if (LABEL_DEBUG) func.JS += " print(INDENT + ' Entering: " + func.ident + "'); INDENT += ' ';\n";
|
|
|
|
if (true) { // TODO: optimize away when not needed
|
|
if (CLOSURE_ANNOTATIONS) func.JS += '/** @type {number} */';
|
|
func.JS += ' var __label__;\n';
|
|
}
|
|
if (func.needsLastLabel) {
|
|
func.JS += ' var __lastLabel__ = null;\n';
|
|
}
|
|
|
|
// Walk function blocks and generate JS
|
|
function walkBlock(block, indent) {
|
|
if (!block) return '';
|
|
dprint('relooping', 'walking block: ' + block.type + ',' + block.entries + ' : ' + block.labels.length);
|
|
function getLabelLines(label, indent) {
|
|
if (!label) return '';
|
|
var ret = '';
|
|
if (LABEL_DEBUG) {
|
|
ret += indent + "print(INDENT + '" + func.ident + ":" + label.ident + "');\n";
|
|
}
|
|
if (EXECUTION_TIMEOUT > 0) {
|
|
ret += indent + 'if (Date.now() - START_TIME >= ' + (EXECUTION_TIMEOUT*1000) + ') throw "Timed out!" + (new Error().stack);\n';
|
|
}
|
|
// for special labels we care about (for phi), mark that we visited them
|
|
return ret + label.lines.map(function(line) { return line.JS + (Debugging.on ? Debugging.getComment(line.lineNum) : '') })
|
|
.join('\n')
|
|
.split('\n') // some lines include line breaks
|
|
.map(function(line) { return indent + line })
|
|
.join('\n');
|
|
}
|
|
var ret = '';
|
|
if (block.type == 'emulated') {
|
|
if (block.labels.length > 1) {
|
|
if (block.entries.length == 1) {
|
|
ret += indent + '__label__ = ' + getLabelId(block.entries[0]) + '; ' + (SHOW_LABELS ? '/* ' + block.entries[0] + ' */' : '') + '\n';
|
|
} // otherwise, should have been set before!
|
|
ret += indent + 'while(1) switch(__label__) {\n';
|
|
ret += block.labels.map(function(label) {
|
|
return indent + ' case ' + getLabelId(label.ident) + ': // ' + label.ident + '\n'
|
|
+ getLabelLines(label, indent + ' ');
|
|
}).join('\n');
|
|
ret += '\n' + indent + ' default: assert(0, "bad label: " + __label__);\n' + indent + '}';
|
|
} else {
|
|
ret += (SHOW_LABELS ? indent + '/* ' + block.entries[0] + ' */' : '') + '\n' + getLabelLines(block.labels[0], indent);
|
|
}
|
|
ret += '\n';
|
|
} else if (block.type == 'reloop') {
|
|
ret += indent + block.id + ': while(1) { ' + (SHOW_LABELS ? ' /* ' + block.entries + + ' */' : '') + '\n';
|
|
ret += walkBlock(block.inner, indent + ' ');
|
|
ret += indent + '}\n';
|
|
} else if (block.type == 'multiple') {
|
|
var first = true;
|
|
var multipleIdent = '';
|
|
ret += indent + block.id + ': do { \n';
|
|
multipleIdent = ' ';
|
|
// TODO: Find out cases where the final if/case is not needed - where we know we must be in a specific label at that point
|
|
var SWITCH_IN_MULTIPLE = 0; // This appears to never be worth it, for no amount of labels
|
|
if (SWITCH_IN_MULTIPLE && block.entryLabels.length >= 2) {
|
|
ret += indent + multipleIdent + 'switch(__label__) {\n';
|
|
block.entryLabels.forEach(function(entryLabel) {
|
|
ret += indent + multipleIdent + ' case ' + getLabelId(entryLabel.ident) + ': {\n';
|
|
ret += walkBlock(entryLabel.block, indent + ' ' + multipleIdent);
|
|
ret += indent + multipleIdent + ' } break;\n';
|
|
});
|
|
ret += indent + multipleIdent + '}\n';
|
|
} else {
|
|
block.entryLabels.forEach(function(entryLabel) {
|
|
ret += indent + multipleIdent + (first ? '' : 'else ') + 'if (__label__ == ' + getLabelId(entryLabel.ident) + ') {\n';
|
|
ret += walkBlock(entryLabel.block, indent + ' ' + multipleIdent);
|
|
ret += indent + multipleIdent + '}\n';
|
|
first = false;
|
|
});
|
|
}
|
|
ret += indent + '} while(0);\n';
|
|
} else {
|
|
throw "Walked into an invalid block type: " + block.type;
|
|
}
|
|
return ret + walkBlock(block.next, indent);
|
|
}
|
|
func.JS += walkBlock(func.block, ' ');
|
|
// Finalize function
|
|
if (LABEL_DEBUG) func.JS += " INDENT = INDENT.substr(0, INDENT.length-2);\n";
|
|
// Add an unneeded return, needed for strict mode to not throw warnings in some cases.
|
|
// If we are not relooping, then switches make it unimportant to have this (and, we lack hasReturn anyhow)
|
|
if (RELOOP && func.lines.length > 0 && func.labels.filter(function(label) { return label.hasReturn }).length > 0) {
|
|
func.JS += ' return' + (func.returnType !== 'void' ? ' null' : '') + ';\n';
|
|
}
|
|
func.JS += '}\n';
|
|
if (func.ident in EXPORTED_FUNCTIONS) {
|
|
func.JS += 'Module["' + func.ident + '"] = ' + func.ident + ';';
|
|
}
|
|
|
|
if (func.lines.length >= CLOSURE_INLINE_PREVENTION_LINES) {
|
|
func.JS += func.ident + '["X"]=1;';
|
|
}
|
|
|
|
if (BUILD_AS_SHARED_LIB == 2) {
|
|
// TODO: make the assert conditional on ASSERTIONS
|
|
func.JS += 'if (globalScope) { assert(!globalScope["' + func.ident + '"]); globalScope["' + func.ident + '"] = ' + func.ident + ' }';
|
|
}
|
|
|
|
return func;
|
|
}
|
|
});
|
|
|
|
function getVarData(funcData, ident) {
|
|
var local = funcData.variables[ident];
|
|
if (local) return local;
|
|
var global = Variables.globals[ident];
|
|
return global || null;
|
|
}
|
|
|
|
function getVarImpl(funcData, ident) {
|
|
if (ident === 'null' || isNumber(ident)) return VAR_NATIVIZED; // like nativized, in that we have the actual value right here
|
|
var data = getVarData(funcData, ident);
|
|
assert(data, 'What variable is this? |' + ident + '|');
|
|
return data.impl;
|
|
}
|
|
|
|
substrate.addActor('FuncLineTriager', {
|
|
processItem: function(item) {
|
|
if (item.intertype == 'function') {
|
|
this.forwardItem(item, 'FunctionReconstructor');
|
|
} else if (item.JS) {
|
|
if (item.parentLineNum) {
|
|
this.forwardItem(item, 'AssignReintegrator');
|
|
} else {
|
|
this.forwardItem(item, 'FunctionReconstructor');
|
|
}
|
|
} else {
|
|
this.forwardItem(item, 'Intertype:' + item.intertype);
|
|
}
|
|
}
|
|
});
|
|
|
|
// assignment
|
|
substrate.addActor('Intertype:assign', {
|
|
processItem: function(item) {
|
|
var pair = splitItem(item, 'value', ['funcData']);
|
|
this.forwardItem(pair.parent, 'AssignReintegrator');
|
|
this.forwardItem(pair.child, 'FuncLineTriager');
|
|
}
|
|
});
|
|
substrate.addActor('AssignReintegrator', makeReintegrator(function(item, child) {
|
|
// 'var', since this is SSA - first assignment is the only assignment, and where it is defined
|
|
item.JS = '';
|
|
if (CLOSURE_ANNOTATIONS) item.JS += '/** @type {number} */ ';
|
|
item.JS += (item.overrideSSA ? '' : 'var ') + toNiceIdent(item.ident);
|
|
|
|
var type = item.value.type;
|
|
var value = parseNumerical(item.value.JS);
|
|
var impl = getVarImpl(item.funcData, item.ident);
|
|
switch (impl) {
|
|
case VAR_NATIVE: {
|
|
break;
|
|
}
|
|
case VAR_NATIVIZED: {
|
|
// SSA, so this must be the alloca. No need for a value
|
|
if (!item.overrideSSA) value = '';
|
|
break;
|
|
}
|
|
case VAR_EMULATED: {
|
|
break;
|
|
}
|
|
default: throw 'zz unknown impl: ' + impl;
|
|
}
|
|
if (value)
|
|
item.JS += '=' + value;
|
|
item.JS += ';';
|
|
|
|
this.forwardItem(item, 'FunctionReconstructor');
|
|
}));
|
|
|
|
// Function lines
|
|
function makeFuncLineActor(intertype, func) {
|
|
return substrate.addActor('Intertype:' + intertype, {
|
|
processItem: function(item) {
|
|
item.JS = func(item);
|
|
if (!item.JS) throw "No JS generated for " + dump(item);
|
|
this.forwardItem(item, 'FuncLineTriager');
|
|
}
|
|
});
|
|
}
|
|
makeFuncLineActor('noop', function(item) {
|
|
return ';';
|
|
});
|
|
makeFuncLineActor('var', function(item) { // assigns into phis become simple vars when MICRO_OPTS
|
|
return 'var ' + item.ident + ';';
|
|
});
|
|
makeFuncLineActor('store', function(item) {
|
|
var value = finalizeLLVMParameter(item.value);
|
|
if (pointingLevels(item.pointerType) == 1) {
|
|
value = parseNumerical(value, item.valueType);
|
|
}
|
|
var impl = VAR_EMULATED;
|
|
if (item.pointer.intertype == 'value') {
|
|
impl = getVarImpl(item.funcData, item.ident);
|
|
}
|
|
switch (impl) {
|
|
case VAR_NATIVIZED:
|
|
return item.ident + '=' + value + ';'; // We have the actual value here
|
|
break;
|
|
case VAR_EMULATED:
|
|
if (item.pointer.intertype == 'value') {
|
|
return makeSetValue(item.ident, 0, value, item.valueType, 0, 0, item.align) + ';';
|
|
} else {
|
|
return makeSetValue(0, finalizeLLVMParameter(item.pointer), value, item.valueType, 0, 0, item.align) + ';';
|
|
}
|
|
break;
|
|
default:
|
|
throw 'unknown [store] impl: ' + impl;
|
|
}
|
|
return null;
|
|
});
|
|
|
|
makeFuncLineActor('deleted', function(item) { return ';' });
|
|
|
|
function getLabelId(label) {
|
|
var funcData = Framework.currItem.funcData;
|
|
var labelIds = funcData.labelIds;
|
|
if (labelIds[label] !== undefined) return labelIds[label];
|
|
return labelIds[label] = funcData.labelIdCounter++;
|
|
}
|
|
|
|
function makeBranch(label, lastLabel, labelIsVariable) {
|
|
var pre = '';
|
|
if (!MICRO_OPTS && lastLabel) {
|
|
pre = '__lastLabel__ = ' + getLabelId(lastLabel) + '; ';
|
|
}
|
|
if (label[0] == 'B') {
|
|
assert(!labelIsVariable, 'Cannot handle branches to variables with special branching options');
|
|
var parts = label.split('|');
|
|
var trueLabel = parts[1] || '';
|
|
var oldLabel = parts[2] || '';
|
|
var labelSetting = oldLabel ? '__label__ = ' + getLabelId(oldLabel) + ';' +
|
|
(SHOW_LABELS ? ' /* to: ' + cleanLabel(oldLabel) + ' */' : '') : ''; // TODO: optimize away the setting
|
|
if (label[1] == 'R') {
|
|
return pre + labelSetting + 'break ' + trueLabel + ';';
|
|
} else if (label[1] == 'C') { // CONT
|
|
return pre + labelSetting + 'continue ' + trueLabel + ';';
|
|
} else if (label[1] == 'N') { // NOPP
|
|
return pre + ';'; // Returning no text might confuse this parser
|
|
} else if (label[1] == 'J') { // JSET
|
|
return pre + labelSetting + ';';
|
|
} else {
|
|
throw 'Invalid B-op in branch: ' + trueLabel + ',' + oldLabel;
|
|
}
|
|
} else {
|
|
if (!labelIsVariable) label = getLabelId(label);
|
|
return pre + '__label__ = ' + label + ';' + (SHOW_LABELS ? ' /* to: ' + cleanLabel(label) + ' */' : '') + ' break;';
|
|
}
|
|
}
|
|
|
|
function calcPhiSets(item) {
|
|
if (!item.phi) return null;
|
|
var phiSets = {};
|
|
item.dependent.params.forEach(function(param) {
|
|
if (!phiSets[param.targetLabel]) phiSets[param.targetLabel] = [];
|
|
phiSets[param.targetLabel].push(param);
|
|
param.valueJS = finalizeLLVMParameter(param.value);
|
|
});
|
|
return phiSets;
|
|
}
|
|
|
|
function getPhiSetsForLabel(phiSets, label) {
|
|
if (!phiSets) return '';
|
|
label = getOldLabel(label);
|
|
if (!phiSets[label]) return '';
|
|
var labelSets = phiSets[label];
|
|
// FIXME: Many of the |var |s here are not needed, but without them we get slowdowns with closure compiler. TODO: remove this workaround.
|
|
if (labelSets.length == 1) {
|
|
return 'var ' + labelSets[0].ident + ' = ' + labelSets[0].valueJS + ';';
|
|
}
|
|
// TODO: eliminate unneeded sets (to undefined etc.)
|
|
var deps = {}; // for each ident we will set, which others it depends on
|
|
var valueJSes = {};
|
|
labelSets.forEach(function(labelSet) {
|
|
deps[labelSet.ident] = {};
|
|
valueJSes[labelSet.ident] = labelSet.valueJS;
|
|
});
|
|
labelSets.forEach(function(labelSet) {
|
|
walkInterdata(labelSet.value, function mark(item) {
|
|
if (item.intertype == 'value' && item.ident in deps) {
|
|
deps[labelSet.ident][item.ident] = true;
|
|
}
|
|
});
|
|
});
|
|
var pre = '', post = '', idents;
|
|
mainLoop: while ((idents = keys(deps)).length > 0) {
|
|
var remove = function(ident) {
|
|
for (var i = 0; i < idents.length; i++) {
|
|
delete deps[idents[i]][ident];
|
|
}
|
|
delete deps[ident];
|
|
}
|
|
for (var i = 0; i < idents.length; i++) {
|
|
if (keys(deps[idents[i]]).length == 0) {
|
|
pre = 'var ' + idents[i] + ' = ' + valueJSes[idents[i]] + ';' + pre;
|
|
remove(idents[i]);
|
|
continue mainLoop;
|
|
}
|
|
}
|
|
// If we got here, we have circular dependencies, and must break at least one.
|
|
pre = 'var ' + idents[0] + '$phi = ' + valueJSes[idents[i]] + ';' + pre;
|
|
post += 'var ' + idents[0] + ' = ' + idents[0] + '$phi;';
|
|
remove(idents[0]);
|
|
}
|
|
return pre + post;
|
|
|
|
/* // Safe, unoptimized copying
|
|
var ret = '';
|
|
for (var i = 0; i < labelSets.length-1; i++) {
|
|
ret += 'var ' + labelSets[i].ident + '$phi = ' + labelSets[i].valueJS + ';';
|
|
}
|
|
ret += labelSets[labelSets.length-1].ident + ' = ' + labelSets[labelSets.length-1].valueJS + ';';
|
|
for (var i = 0; i < labelSets.length-1; i++) {
|
|
ret += labelSets[i].ident + ' = ' + labelSets[i].ident + '$phi;';
|
|
}
|
|
return ret;
|
|
*/
|
|
}
|
|
|
|
makeFuncLineActor('branch', function(item) {
|
|
var phiSets = calcPhiSets(item);
|
|
if (!item.value) {
|
|
return getPhiSetsForLabel(phiSets, item.label) + makeBranch(item.label, item.currLabelId);
|
|
} else {
|
|
var condition = finalizeLLVMParameter(item.value);
|
|
var labelTrue = getPhiSetsForLabel(phiSets, item.labelTrue) + makeBranch(item.labelTrue, item.currLabelId);
|
|
var labelFalse = getPhiSetsForLabel(phiSets, item.labelFalse) + makeBranch(item.labelFalse, item.currLabelId);
|
|
if (labelTrue == ';' && labelFalse == ';') return ';';
|
|
var head = 'if (' + condition + ') { ';
|
|
var head2 = 'if (!(' + condition + ')) { ';
|
|
var else_ = ' } else { ';
|
|
var tail = ' }';
|
|
if (labelTrue == ';') {
|
|
return head2 + labelFalse + tail;
|
|
} else if (labelFalse == ';') {
|
|
return head + labelTrue + tail;
|
|
} else {
|
|
return head + labelTrue + else_ + labelFalse + tail;
|
|
}
|
|
}
|
|
});
|
|
makeFuncLineActor('switch', function(item) {
|
|
// TODO: Find a case where switch is important, and benchmark that. var SWITCH_IN_SWITCH = 1;
|
|
var phiSets = calcPhiSets(item);
|
|
var ret = '';
|
|
var first = true;
|
|
item.switchLabels.forEach(function(switchLabel) {
|
|
if (!first) {
|
|
ret += 'else ';
|
|
} else {
|
|
first = false;
|
|
}
|
|
ret += 'if (' + item.ident + ' == ' + switchLabel.value + ') {\n';
|
|
ret += ' ' + getPhiSetsForLabel(phiSets, switchLabel.label) + makeBranch(switchLabel.label, item.currLabelId || null) + '\n';
|
|
ret += '}\n';
|
|
});
|
|
if (item.switchLabels.length > 0) ret += 'else {\n';
|
|
ret += getPhiSetsForLabel(phiSets, item.defaultLabel) + makeBranch(item.defaultLabel, item.currLabelId) + '\n';
|
|
if (item.switchLabels.length > 0) ret += '}\n';
|
|
if (item.value) {
|
|
ret += ' ' + toNiceIdent(item.value);
|
|
}
|
|
return ret;
|
|
});
|
|
makeFuncLineActor('return', function(item) {
|
|
var ret = RuntimeGenerator.stackExit(item.funcData.initialStack) + ';\n';
|
|
if (PROFILE) {
|
|
ret += 'if (PROFILING) { '
|
|
+ 'PROFILING_NODE.time += Date.now() - __profilingStartTime__; '
|
|
+ 'PROFILING_NODE = __parentProfilingNode__ '
|
|
+ '}\n';
|
|
}
|
|
if (LABEL_DEBUG) {
|
|
ret += "print(INDENT + 'Exiting: " + item.funcData.ident + "');\n"
|
|
+ "INDENT = INDENT.substr(0, INDENT.length-2);\n";
|
|
}
|
|
ret += 'return';
|
|
if (item.value) {
|
|
ret += ' ' + finalizeLLVMParameter(item.value);
|
|
}
|
|
return ret + ';';
|
|
});
|
|
makeFuncLineActor('resume', function(item) {
|
|
return (EXCEPTION_DEBUG ? 'print("Resuming exception");' : '') + 'throw [0,0];';
|
|
});
|
|
makeFuncLineActor('invoke', function(item) {
|
|
// Wrapping in a function lets us easily return values if we are
|
|
// in an assignment
|
|
var phiSets = calcPhiSets(item);
|
|
var call_ = makeFunctionCall(item.ident, item.params, item.funcData, item.type);
|
|
var ret = '(function() { try { __THREW__ = false; return '
|
|
+ call_ + ' '
|
|
+ '} catch(e) { '
|
|
+ 'if (typeof e != "number") throw e; '
|
|
+ 'if (ABORT) throw e; __THREW__ = true; '
|
|
+ (EXCEPTION_DEBUG ? 'print("Exception: " + e + ", currently at: " + (new Error().stack)); ' : '')
|
|
+ 'return null } })(); if (!__THREW__) { ' + getPhiSetsForLabel(phiSets, item.toLabel) + makeBranch(item.toLabel, item.currLabelId)
|
|
+ ' } else { ' + getPhiSetsForLabel(phiSets, item.unwindLabel) + makeBranch(item.unwindLabel, item.currLabelId) + ' }';
|
|
return ret;
|
|
});
|
|
makeFuncLineActor('landingpad', function(item) {
|
|
// Just a stub
|
|
return '{ f0: 0, f1: 0 }';
|
|
});
|
|
makeFuncLineActor('load', function(item) {
|
|
var value = finalizeLLVMParameter(item.pointer);
|
|
var impl = item.ident ? getVarImpl(item.funcData, item.ident) : VAR_EMULATED;
|
|
switch (impl) {
|
|
case VAR_NATIVIZED: {
|
|
return value; // We have the actual value here
|
|
}
|
|
case VAR_EMULATED: return makeGetValue(value, 0, item.type, 0, item.unsigned, 0, item.align);
|
|
default: throw "unknown [load] impl: " + impl;
|
|
}
|
|
});
|
|
makeFuncLineActor('extractvalue', function(item) {
|
|
assert(item.indexes.length == 1); // TODO: use getelementptr parsing stuff, for depth. For now, we assume that LLVM aggregates are flat,
|
|
// and we emulate them using simple JS objects { f1: , f2: , } etc., for speed
|
|
return item.ident + '.f' + item.indexes[0][0].text;
|
|
});
|
|
makeFuncLineActor('insertvalue', function(item) {
|
|
assert(item.indexes.length == 1); // TODO: see extractvalue
|
|
var ret = '(', ident;
|
|
if (item.ident === 'undef') {
|
|
item.ident = 'tempValue';
|
|
ret += item.ident + ' = [' + makeEmptyStruct(item.type) + '], ';
|
|
}
|
|
return ret + item.ident + '.f' + item.indexes[0][0].text + ' = ' + finalizeLLVMParameter(item.value) + ', ' + item.ident + ')';
|
|
});
|
|
makeFuncLineActor('indirectbr', function(item) {
|
|
return makeBranch(finalizeLLVMParameter(item.value), item.currLabelId, true);
|
|
});
|
|
makeFuncLineActor('alloca', function(item) {
|
|
if (typeof item.allocatedIndex === 'number') {
|
|
if (item.allocatedSize === 0) return ''; // This will not actually be shown - it's nativized
|
|
return getFastValue('__stackBase__', '+', item.allocatedIndex.toString());
|
|
} else {
|
|
return RuntimeGenerator.stackAlloc(getFastValue(calcAllocatedSize(item.allocatedType), '*', item.allocatedNum));
|
|
}
|
|
});
|
|
makeFuncLineActor('phi', function(item) {
|
|
var params = item.params;
|
|
assert(!MICRO_OPTS);
|
|
function makeOne(i) {
|
|
if (i === params.length-1) {
|
|
return finalizeLLVMParameter(params[i].value);
|
|
}
|
|
return '__lastLabel__ == ' + getLabelId(params[i].label) + ' ? ' +
|
|
finalizeLLVMParameter(params[i].value) + ' : (' + makeOne(i+1) + ')';
|
|
}
|
|
var ret = makeOne(0);
|
|
if (item.postSet) ret += item.postSet;
|
|
return ret;
|
|
});
|
|
|
|
makeFuncLineActor('mathop', processMathop);
|
|
|
|
makeFuncLineActor('bitcast', function(item) {
|
|
return finalizeLLVMParameter(item.params[0]);
|
|
});
|
|
|
|
function makeFunctionCall(ident, params, funcData, type) {
|
|
// We cannot compile assembly. See comment in intertyper.js:'Call'
|
|
assert(ident != 'asm', 'Inline assembly cannot be compiled to JavaScript!');
|
|
|
|
var shortident = LibraryManager.getRootIdent(ident.slice(1)) || ident.slice(1); // ident may not be in library, if all there is is ident__inline
|
|
var args = [];
|
|
var argsTypes = [];
|
|
var varargs = [];
|
|
var varargsTypes = [];
|
|
var ignoreFunctionIndexizing = [];
|
|
var useJSArgs = (shortident + '__jsargs') in LibraryManager.library;
|
|
var hasVarArgs = isVarArgsFunctionType(type);
|
|
var normalArgs = (hasVarArgs && !useJSArgs) ? countNormalArgs(type) : -1;
|
|
|
|
if (I64_MODE == 1 && ident in LLVM.INTRINSICS_32) {
|
|
// Some LLVM intrinsics use i64 where it is not needed, and would cause much overhead
|
|
params.forEach(function(param) { if (param.type == 'i64') param.type = 'i32' });
|
|
}
|
|
|
|
params.forEach(function(param, i) {
|
|
var val = finalizeParam(param);
|
|
if (!hasVarArgs || useJSArgs || i < normalArgs) {
|
|
if (param.type == 'i64' && I64_MODE == 1) {
|
|
val = makeCopyI64(val); // Must copy [low, high] i64s, so they don't end up modified in the caller
|
|
}
|
|
args.push(val);
|
|
argsTypes.push(param.type);
|
|
} else {
|
|
if (!(param.type == 'i64' && I64_MODE == 1)) {
|
|
varargs.push(val);
|
|
varargs = varargs.concat(zeros(Runtime.getNativeFieldSize(param.type)-1));
|
|
varargsTypes.push(param.type);
|
|
varargsTypes = varargsTypes.concat(zeros(Runtime.getNativeFieldSize(param.type)-1));
|
|
} else {
|
|
// i64 mode 1. Write one i32 with type i64, and one i32 with type i32
|
|
varargs.push(val + '[0]');
|
|
varargs = varargs.concat(zeros(Runtime.getNativeFieldSize('i32')-1));
|
|
ignoreFunctionIndexizing.push(varargs.length); // We will have a value there, but no type (the type is i64, but we write two i32s)
|
|
varargs.push(val + '[1]');
|
|
varargs = varargs.concat(zeros(Runtime.getNativeFieldSize('i32')-1));
|
|
varargsTypes.push('i64');
|
|
varargsTypes = varargsTypes.concat(zeros(Runtime.getNativeFieldSize('i32')-1));
|
|
varargsTypes.push('i32');
|
|
varargsTypes = varargsTypes.concat(zeros(Runtime.getNativeFieldSize('i32')-1));
|
|
}
|
|
}
|
|
});
|
|
|
|
args = args.map(function(arg, i) { return indexizeFunctions(arg, argsTypes[i]) });
|
|
varargs = varargs.map(function(vararg, i) {
|
|
if (ignoreFunctionIndexizing.indexOf(i) >= 0) return vararg;
|
|
return vararg === 0 ? 0 : indexizeFunctions(vararg, varargsTypes[i])
|
|
});
|
|
|
|
if (hasVarArgs && !useJSArgs) {
|
|
if (varargs.length === 0) {
|
|
varargs = [0];
|
|
varargsTypes = ['i32'];
|
|
}
|
|
varargs = makePointer('[' + varargs + ']', 0, 'ALLOC_STACK', varargsTypes);
|
|
}
|
|
|
|
args = args.concat(varargs);
|
|
var argsText = args.join(', ');
|
|
|
|
// Inline if either we inline whenever we can (and we can), or if there is no noninlined version
|
|
var inline = LibraryManager.library[shortident + '__inline'];
|
|
var nonInlined = shortident in LibraryManager.library;
|
|
if (inline && (INLINE_LIBRARY_FUNCS || !nonInlined)) {
|
|
return inline.apply(null, args); // Warning: inlining does not prevent recalculation of the arguments. They should be simple identifiers
|
|
}
|
|
|
|
if (getVarData(funcData, ident)) {
|
|
ident = 'FUNCTION_TABLE[' + ident + ']';
|
|
}
|
|
|
|
return ident + '(' + args.join(', ') + ')';
|
|
}
|
|
makeFuncLineActor('getelementptr', function(item) { return finalizeLLVMFunctionCall(item) });
|
|
makeFuncLineActor('call', function(item) {
|
|
if (item.standalone && LibraryManager.isStubFunction(item.ident)) return ';';
|
|
return makeFunctionCall(item.ident, item.params, item.funcData, item.type) + (item.standalone ? ';' : '');
|
|
});
|
|
|
|
makeFuncLineActor('unreachable', function(item) { return 'throw "Reached an unreachable!"' }); // Original .ll line: ' + item.lineNum + '";' });
|
|
|
|
// Final combiner
|
|
|
|
function finalCombiner(items) {
|
|
dprint('unparsedFunctions', 'Starting finalCombiner');
|
|
var itemsDict = { type: [], GlobalVariableStub: [], functionStub: [], function: [], GlobalVariable: [], GlobalVariablePostSet: [] };
|
|
items.forEach(function(item) {
|
|
item.lines = null;
|
|
var small = { intertype: item.intertype, JS: item.JS, ident: item.ident, dependencies: item.dependencies }; // Release memory
|
|
itemsDict[small.intertype].push(small);
|
|
});
|
|
items = null;
|
|
|
|
var splitPostSets = splitter(itemsDict.GlobalVariablePostSet, function(x) { return x.ident && x.dependencies });
|
|
itemsDict.GlobalVariablePostSet = splitPostSets.leftIn;
|
|
var orderedPostSets = splitPostSets.splitOut;
|
|
|
|
var limit = orderedPostSets.length * orderedPostSets.length;
|
|
for (var i = 0; i < orderedPostSets.length; i++) {
|
|
for (var j = i+1; j < orderedPostSets.length; j++) {
|
|
if (orderedPostSets[j].ident in orderedPostSets[i].dependencies) {
|
|
var temp = orderedPostSets[i];
|
|
orderedPostSets[i] = orderedPostSets[j];
|
|
orderedPostSets[j] = temp;
|
|
i--;
|
|
limit--;
|
|
assert(limit > 0, 'Could not sort postsets!');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
itemsDict.GlobalVariablePostSet = itemsDict.GlobalVariablePostSet.concat(orderedPostSets);
|
|
|
|
//
|
|
|
|
if (!mainPass) {
|
|
var generated = itemsDict.function.concat(itemsDict.type).concat(itemsDict.GlobalVariableStub).concat(itemsDict.GlobalVariable).concat(itemsDict.GlobalVariablePostSet);
|
|
Functions.allIdents = Functions.allIdents.concat(itemsDict.function.map(function(func) {
|
|
return func.ident;
|
|
}).filter(function(func) {
|
|
return IGNORED_FUNCTIONS.indexOf(func.ident) < 0;
|
|
}));
|
|
if (!DEBUG_MEMORY) print(generated.map(function(item) { return item.JS }).join('\n'));
|
|
return;
|
|
}
|
|
|
|
// This is the main pass. Print out the generated code that we have here, together with the
|
|
// rest of the output that we started to print out earlier (see comment on the
|
|
// "Final shape that will be created").
|
|
var generated = itemsDict.functionStub.concat(itemsDict.GlobalVariablePostSet);
|
|
generated.forEach(function(item) { print(indentify(item.JS || '', 2)); });
|
|
if (RUNTIME_TYPE_INFO) {
|
|
Types.cleanForRuntime();
|
|
print('Runtime.typeInfo = ' + JSON.stringify(Types.types));
|
|
print('Runtime.structMetadata = ' + JSON.stringify(Types.structMetadata));
|
|
}
|
|
var postFile = BUILD_AS_SHARED_LIB ? 'postamble_sharedlib.js' : 'postamble.js';
|
|
var postParts = processMacros(preprocess(read(postFile))).split('{{GLOBAL_VARS}}');
|
|
print(postParts[0]);
|
|
|
|
// Print out global variables and postsets TODO: batching
|
|
JSify(analyzer(intertyper(data.unparsedGlobalss[0].lines, true)), true, Functions);
|
|
data.unparsedGlobalss = null;
|
|
|
|
print(Functions.generateIndexing()); // done last, as it may rely on aliases set in postsets
|
|
|
|
// Load runtime-linked libraries
|
|
RUNTIME_LINKED_LIBS.forEach(function(lib) {
|
|
print('eval(read("' + lib + '"))(FUNCTION_TABLE.length, this);');
|
|
});
|
|
|
|
print(postParts[1]);
|
|
|
|
print(shellParts[1]);
|
|
// Print out some useful metadata (for additional optimizations later, like the eliminator)
|
|
print('// EMSCRIPTEN_GENERATED_FUNCTIONS: ' + JSON.stringify(Functions.allIdents) + '\n');
|
|
|
|
return null;
|
|
}
|
|
|
|
// Data
|
|
|
|
if (mainPass) {
|
|
substrate.addItems(data.functionStubs, 'FunctionStub');
|
|
assert(data.functions.length == 0);
|
|
} else {
|
|
substrate.addItems(values(data.globalVariables), 'GlobalVariable');
|
|
substrate.addItems(data.aliass, 'Alias');
|
|
substrate.addItems(data.functions, 'FunctionSplitter');
|
|
}
|
|
|
|
finalCombiner(substrate.solve());
|
|
|
|
dprint('framework', 'Big picture: Finishing JSifier, main pass=' + mainPass);
|
|
}
|
|
|