blob: 88c6d28fb62139b7ad0eabfde209bf149b6c8629 [file] [log] [blame] [edit]
//"use strict";
// Various namespace-like modules
var STACK_ALIGN = TARGET_X86 ? 4 : 8;
var LLVM = {
LINKAGES: set('private', 'linker_private', 'linker_private_weak', 'linker_private_weak_def_auto', 'internal',
'available_externally', 'linkonce', 'common', 'weak', 'appending', 'extern_weak', 'linkonce_odr',
'weak_odr', 'externally_visible', 'dllimport', 'dllexport', 'unnamed_addr', 'thread_local'),
VISIBILITIES: set('default', 'hidden', 'protected'),
PARAM_ATTR: set('noalias', 'signext', 'zeroext', 'inreg', 'sret', 'nocapture', 'nest'),
FUNC_ATTR: set('hidden', 'nounwind', 'define', 'inlinehint', '{'),
CALLING_CONVENTIONS: set('ccc', 'fastcc', 'coldcc', 'cc10', 'x86_fastcallcc', 'x86_stdcallcc', 'cc11'),
ACCESS_OPTIONS: set('volatile', 'atomic'),
INVOKE_MODIFIERS: set('alignstack', 'alwaysinline', 'inlinehint', 'naked', 'noimplicitfloat', 'noinline', 'alwaysinline attribute.', 'noredzone', 'noreturn', 'nounwind', 'optsize', 'readnone', 'readonly', 'ssp', 'sspreq'),
SHIFTS: set('ashr', 'lshr', 'shl'),
PHI_REACHERS: set('branch', 'switch', 'invoke', 'indirectbr'),
EXTENDS: set('sext', 'zext'),
COMPS: set('icmp', 'fcmp'),
CONVERSIONS: set('inttoptr', 'ptrtoint', 'uitofp', 'sitofp', 'fptosi', 'fptoui'),
INTRINSICS_32: set('_llvm_memcpy_p0i8_p0i8_i64', '_llvm_memmove_p0i8_p0i8_i64', '_llvm_memset_p0i8_i64'), // intrinsics that need args converted to i32 in USE_TYPED_ARRAYS == 2
};
LLVM.GLOBAL_MODIFIERS = set(keys(LLVM.LINKAGES).concat(['constant', 'global', 'hidden']));
var Debugging = {
handleMetadata: function(lines) {
for (var i = lines.length-1; i >= 0; i--) {
if (/^!\d+ = metadata .*/.exec(lines[i])) {
Debugging.processMetadata(lines);
break;
}
}
},
processMetadata: function(lines) {
var llvmLineToMetadata = {};
var metadataToSourceLine = {};
var metadataToParentMetadata = {};
var metadataToFilename = {};
var form1 = new RegExp(/^ .*, !dbg !(\d+) *$/);
var form2 = new RegExp(/^ .*, !dbg !(\d+) *; .*$/);
var form3 = new RegExp(/^!(\d+) = metadata !{i32 (\d+), (?:i32 \d+|null), metadata !(\d+), .*}$/);
var form3a = new RegExp(/^!(\d+) = metadata !{i32 \d+, (?:i32 \d+|metadata !\d+), (?:i32 \d+|null), (?:i32 \d+|null), metadata !(\d+), (?:i32 \d+|null)}.*/);
var form3ab = new RegExp(/^!(\d+) = metadata !{i32 \d+, (?:metadata !\d+|i32 \d+|null), metadata !(\d+).*$/);
var form3ac = new RegExp(/^!(\d+) = metadata !{i32 \d+, (?:metadata !\d+|null), metadata !"[^"]*", metadata !(\d+)[^\[]*.*$/);
var form3ad = new RegExp(/^!(\d+) = metadata !{i32 \d+, (?:i32 \d+|null), (?:i32 \d+|null), metadata !"[^"]*", metadata !"[^"]*", metadata !"[^"]*", metadata !(\d+),.*$/);
var form3b = new RegExp(/^!(\d+) = metadata !{i32 \d+, metadata !"([^"]+)", metadata !"([^"]*)", (metadata !\d+|null)}.*$/);
var form3c = new RegExp(/^!(\d+) = metadata !{\w+\d* !?(\d+)[^\d].*$/);
var form4 = new RegExp(/^!llvm.dbg.[\w\.]+ = .*$/);
var form5 = new RegExp(/^!(\d+) = metadata !{.*$/);
var form6 = new RegExp(/^ (tail )?call void \@llvm.dbg.\w+\(metadata .*$/);
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
line = line.replace(/; +\[debug line = \d+:\d+\]/, '');
var skipLine = false;
if (form6.exec(line)) {
lines[i] = ';';
continue;
}
var calc = form1.exec(line) || form2.exec(line);
if (calc) {
llvmLineToMetadata[i+1] = calc[1];
lines[i] = line.replace(/, !dbg !\d+/, '');
continue;
}
calc = form3.exec(line);
if (calc) {
metadataToSourceLine[calc[1]] = calc[2];
metadataToParentMetadata[calc[1]] = calc[3];
lines[i] = ';'; // return an empty line, to keep line numbers of subsequent lines the same
continue;
}
calc = form3a.exec(line) || form3ab.exec(line) || form3ac.exec(line) || form3ad.exec(line);
if (calc) {
metadataToParentMetadata[calc[1]] = calc[2];
lines[i] = ';';
continue;
}
calc = form3b.exec(line);
if (calc) {
metadataToFilename[calc[1]] = /* LLVM 2.8<= : calc[3] + '/' + */ calc[2];
lines[i] = ';';
continue;
}
calc = form3c.exec(line) || form4.exec(line) || form5.exec(line);
if (calc) {
lines[i] = ';';
continue;
}
if (line[0] == '!') skipLine = true;
lines[i] = skipLine ? ';' : line;
}
/*
dprint("ll ==> meta: " + JSON.stringify(llvmLineToMetadata));
dprint("meta ==> sline: " + JSON.stringify(metadataToSourceLine));
dprint("meta ==> pmeta: " + JSON.stringify(metadataToParentMetadata));
dprint("meta ==> fname: " + JSON.stringify(metadataToFilename));
*/
this.llvmLineToSourceLine = {};
this.llvmLineToSourceFile = {};
for (var l in llvmLineToMetadata) {
var m = llvmLineToMetadata[l];
this.llvmLineToSourceLine[l] = metadataToSourceLine[m];
dprint('metadata', 'starting to recurse metadata for: ' + m);
while (!metadataToFilename[m]) {
dprint('metadata', 'recursing metadata, at: ' + m);
m = metadataToParentMetadata[m];
assert(m, 'Confused as to parent metadata for llvm #' + l + ', metadata !' + m);
}
// Normalize Windows path slashes coming from LLVM metadata, so that forward slashes can be assumed as path delimiters.
this.llvmLineToSourceFile[l] = metadataToFilename[m].replace(/\\5C/g, '/');
}
this.on = true;
},
clear: function() {
this.llvmLineToSourceLine = {};
this.llvmLineToSourceFile = {};
this.on = false;
},
getComment: function(lineNum) {
if (!this.on) return null;
return lineNum in this.llvmLineToSourceLine ? ' //@line ' + this.llvmLineToSourceLine[lineNum] + ' "' +
this.llvmLineToSourceFile[lineNum] + '"' : '';
},
getAssociatedSourceFile: function(lineNum) {
if (!this.on) return null;
return lineNum in this.llvmLineToSourceLine ? this.llvmLineToSourceFile[lineNum] : null;
},
getIdentifier: function(lineNum) {
if (!this.on) return null;
if (lineNum === undefined) {
lineNum = Framework.currItem.lineNum;
assert(lineNum !== undefined);
}
var approx = false;
var sourceFile;
while (lineNum >= 0) {
var sourceFile = this.llvmLineToSourceFile[lineNum];
if (sourceFile) break;
lineNum--;
approx = true;
}
if (!sourceFile) return 'UNKNOWN';
return sourceFile.split('/').slice(-1)[0] + ':' + (approx ? '~' : '') + this.llvmLineToSourceLine[lineNum];
}
};
var PreProcessor = {
eliminateUnneededIntrinsics: function(lines) {
// LLVM sometimes aggresively adds lifetime annotations, for example
//
// %0 = bitcast %"class.std::__1::__tree"** %this.addr.i to i8* ; [#uses=1 type=i8*]
// call void @llvm.lifetime.start(i64 -1, i8* %0) nounwind
// [..]
// %6 = bitcast float* %__x.addr.i to i8* ; [#uses=1 type=i8*]
// call void @llvm.lifetime.end(i64 -1, i8* %6) nounwind
//
// This greatly hurts us if we do not eliminate it ahead of time, because while we
// will correctly do nothing for the lifetime intrinsic itself, the bitcast of the
// parameter to it will prevent nativization of the variable being cast (!)
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (/call void @llvm.lifetime.(start|end)\(i\d+ -1,.*/.exec(line)) {
lines[i] = ';';
}
}
}
};
var Variables = {
globals: {},
indexedGlobals: {}, // for indexed globals, ident ==> index
// Used in calculation of indexed globals
nextIndexedOffset: 0,
resolveAliasToIdent: function(ident) {
while (1) {
var varData = Variables.globals[ident];
if (!(varData && varData.targetIdent)) break;
ident = varData.targetIdent; // might need to eval to turn (6) into 6
}
return ident;
},
};
var Types = {
types: {},
fatTypes: {}, // With QUANTUM_SIZE=1, we store the full-size type data here
flipTypes: function() {
var temp = this.fatTypes;
this.fatTypes = this.types;
this.types = temp;
},
structMetadata: {},
// Remove all data not needed during runtime (like line numbers, JS, etc.)
cleanForRuntime: function() {
values(this.types).forEach(function(type) {
delete type.intertype;
delete type.name_;
delete type.lineNum;
delete type.lines;
delete type.needsFlattening;
delete type.JS;
});
keys(this.types).forEach(function(longer) {
var shorter = longer.replace('%struct.', '').replace('%class.');
if (shorter === longer) return;
if (shorter in this.types) return;
this.types[shorter] = this.types[longer];
}, this);
},
needAnalysis: {}, // Types noticed during parsing, that need analysis
// Set to true if we actually use precise i64 math: If PRECISE_I64_MATH is set, and also such math is actually
// needed (+,-,*,/,% - we do not need it for bitops), or PRECISE_I64_MATH is 2 (forced)
preciseI64MathUsed: (PRECISE_I64_MATH == 2)
};
var firstTableIndex = (ASM_JS ? 2*RESERVED_FUNCTION_POINTERS : 0) + 2;
var Functions = {
// All functions that will be implemented in this file. Maps id to signature
implementedFunctions: {},
libraryFunctions: {}, // functions added from the library. value 2 means asmLibraryFunction
unimplementedFunctions: {}, // library etc. functions that we need to index, maps id to signature
indexedFunctions: {},
nextIndex: firstTableIndex, // Start at a non-0 (even, see below) value
neededTables: set('v', 'vi', 'ii', 'iii'), // signatures that appeared (initialized with library stuff
// we always use), and we will need a function table for
blockAddresses: {}, // maps functions to a map of block labels to label ids
aliases: {}, // in shared modules (MAIN_MODULE or SHARED_MODULE), a list of aliases for functions that have them
getSignature: function(returnType, argTypes, hasVarArgs) {
var sig = returnType == 'void' ? 'v' : (isIntImplemented(returnType) ? 'i' : 'f');
for (var i = 0; i < argTypes.length; i++) {
var type = argTypes[i];
if (!type) break; // varargs
sig += isIntImplemented(type) ? (getBits(type) == 64 ? 'ii' : 'i') : 'f'; // legalized i64s will be i32s
}
if (hasVarArgs) sig += 'i';
return sig;
},
// Mark a function as needing indexing. Python will coordinate them all
getIndex: function(ident, glTentative, sig) {
if (glTentative) { // see library_gl.js
if (!(ident in this.indexedFunctions)) {
if (!Functions.getIndex.tentative) Functions.getIndex.tentative = {}; // only used by GL emulation; TODO: generalize when needed
Functions.getIndex.tentative[ident] = 0;
}
if (ident in Functions.implementedFunctions) {
ident = '_glRelocate' + ident;
}
}
var ret;
if (phase != 'post' && singlePhase) {
if (!glTentative) this.indexedFunctions[ident] = 0; // tell python we need this indexized
ret = "'{{ FI_" + toNiceIdent(ident) + " }}'"; // something python will replace later
} else {
if (!singlePhase) return 'NO_INDEX'; // Should not index functions in post
ret = this.indexedFunctions[ident];
if (!ret) {
if (glTentative) return '0';
ret = this.nextIndex;
this.nextIndex += 2; // Need to have indexes be even numbers, see |polymorph| test
this.indexedFunctions[ident] = ret;
}
ret = ret.toString();
}
if (SIDE_MODULE && sig) { // sig can be undefined for the GL library functions
ret = '((F_BASE_' + sig + ' + ' + ret + ')|0)';
} else if (BUILD_AS_SHARED_LIB) {
ret = '(FUNCTION_TABLE_OFFSET + ' + ret + ')';
}
return ret;
},
getTable: function(sig) {
return ASM_JS ? 'FUNCTION_TABLE_' + sig : 'FUNCTION_TABLE';
},
// Generate code for function indexing
generateIndexing: function() {
var tables = { pre: '' };
if (ASM_JS) {
keys(Functions.neededTables).forEach(function(sig) { // add some default signatures that are used in the library
tables[sig] = zeros(firstTableIndex);
});
}
for (var ident in this.indexedFunctions) {
var sig = ASM_JS ? Functions.implementedFunctions[ident] || Functions.unimplementedFunctions[ident] || LibraryManager.library[ident.substr(1) + '__sig'] : 'x';
assert(sig, ident);
if (!tables[sig]) tables[sig] = zeros(firstTableIndex);
var index = this.indexedFunctions[ident];
for (var i = tables[sig].length; i < index; i++) {
tables[sig][i] = 0; // keep flat
}
tables[sig][index] = ident;
}
var generated = false;
var wrapped = {};
var maxTable = 0;
for (var t in tables) {
if (t == 'pre') continue;
generated = true;
var table = tables[t];
for (var i = 0; i < table.length; i++) {
// Resolve multi-level aliases all the way down
while (1) {
var varData = Variables.globals[table[i]];
if (!(varData && varData.resolvedAlias && varData.resolvedAlias.indexOf('FUNCTION_TABLE_OFFSET') < 0)) break;
table[i] = table[+varData.resolvedAlias || eval(varData.resolvedAlias)]; // might need to eval to turn (6) into 6
}
// Resolve library aliases
if (table[i]) {
var libName = LibraryManager.getRootIdent(table[i].substr(1));
if (libName && typeof libName == 'string') {
table[i] = (libName.indexOf('.') < 0 ? '_' : '') + libName;
}
}
if (ASM_JS) {
var curr = table[i];
if (curr && curr != '0' && !Functions.implementedFunctions[curr]) {
curr = toNiceIdent(curr); // fix Math.* to Math_*
// This is a library function, we can't just put it in the function table, need a wrapper
if (!wrapped[curr]) {
var args = '', arg_coercions = '', call = curr + '(', retPre = '', retPost = '';
if (t[0] != 'v') {
if (t[0] == 'i') {
retPre = 'return ';
retPost = '|0';
} else {
retPre = 'return +';
}
}
for (var j = 1; j < t.length; j++) {
args += (j > 1 ? ',' : '') + 'a' + j;
arg_coercions += 'a' + j + '=' + asmCoercion('a' + j, t[j] != 'i' ? 'float' : 'i32') + ';';
call += (j > 1 ? ',' : '') + asmCoercion('a' + j, t[j] != 'i' ? 'float' : 'i32');
}
call += ')';
if (curr == '_setjmp') printErr('WARNING: setjmp used via a function pointer. If this is for libc setjmp (not something of your own with the same name), it will break things');
tables.pre += 'function ' + curr + '__wrapper(' + args + ') { ' + arg_coercions + ' ; ' + retPre + call + retPost + ' }\n';
wrapped[curr] = 1;
}
table[i] = curr + '__wrapper';
}
}
}
if (table.length > 20) {
// add some newlines in the table, for readability
var j = 10;
while (j+10 < table.length) {
table[j] += '\n';
j += 10;
}
}
maxTable = Math.max(maxTable, table.length);
}
if (ASM_JS) maxTable = ceilPowerOfTwo(maxTable);
for (var t in tables) {
if (t == 'pre') continue;
var table = tables[t];
if (ASM_JS) {
// asm function table mask must be power of two
// if nonaliasing, then standardize function table size, to avoid aliasing pointers through the &M mask (in a small table using a big index)
var fullSize = ALIASING_FUNCTION_POINTERS ? ceilPowerOfTwo(table.length) : maxTable;
for (var i = table.length; i < fullSize; i++) {
table[i] = 0;
}
}
// finalize table
var indices = table.toString().replace('"', '');
if (BUILD_AS_SHARED_LIB) {
// Shared libraries reuse the parent's function table.
tables[t] = Functions.getTable(t) + '.push.apply(' + Functions.getTable(t) + ', [' + indices + ']);\n';
} else {
tables[t] = 'var ' + Functions.getTable(t) + ' = [' + indices + '];\n';
if (SAFE_DYNCALLS) {
tables[t] += 'var FUNCTION_TABLE_NAMES = ' + JSON.stringify(table).replace(/\n/g, '').replace(/,0/g, ',0\n') + ';\n';
}
}
}
if (!generated && !ASM_JS) {
tables['x'] = 'var FUNCTION_TABLE = [0, 0];\n'; // default empty table
}
Functions.tables = tables;
}
};
var LibraryManager = {
library: null,
structs: {},
loaded: false,
load: function() {
if (this.library) return;
var libraries = ['library.js', 'library_browser.js', 'library_sdl.js', 'library_gl.js', 'library_glut.js', 'library_xlib.js', 'library_egl.js', 'library_gc.js', 'library_jansson.js', 'library_openal.js', 'library_glfw.js'].concat(additionalLibraries);
for (var i = 0; i < libraries.length; i++) {
eval(processMacros(preprocess(read(libraries[i]))));
}
this.loaded = true;
},
// Given an ident, see if it is an alias for something, and so forth, returning
// the earliest ancestor (the root)
getRootIdent: function(ident) {
if (!this.library) return null;
var ret = LibraryManager.library[ident];
if (!ret) return null;
var last = ident;
while (typeof ret === 'string') {
last = ret;
ret = LibraryManager.library[ret];
}
return last;
},
isStubFunction: function(ident) {
var libCall = LibraryManager.library[ident.substr(1)];
return typeof libCall === 'function' && libCall.toString().replace(/\s/g, '') === 'function(){}'
&& !(ident in Functions.implementedFunctions);
}
};
// Safe way to access a C define. We check that we don't add library functions with missing defines.
function cDefine(key) {
return key in C_DEFINES ? C_DEFINES[key] : ('0 /* XXX missing C define ' + key + ' */');
}
var PassManager = {
serialize: function() {
if (phase == 'pre') {
print('\n//FORWARDED_DATA:' + JSON.stringify({
Types: Types,
Variables: Variables,
Functions: Functions,
EXPORTED_FUNCTIONS: EXPORTED_FUNCTIONS // needed for asm.js global constructors (ctors)
}));
} else if (phase == 'funcs') {
print('\n//FORWARDED_DATA:' + JSON.stringify({
Types: { preciseI64MathUsed: Types.preciseI64MathUsed },
Functions: {
blockAddresses: Functions.blockAddresses,
indexedFunctions: Functions.indexedFunctions,
implementedFunctions: ASM_JS ? Functions.implementedFunctions : [],
unimplementedFunctions: Functions.unimplementedFunctions,
neededTables: Functions.neededTables
}
}));
} else if (phase == 'post') {
print('\n//FORWARDED_DATA:' + JSON.stringify({
Functions: { tables: Functions.tables }
}));
}
},
load: function(json) {
var data = JSON.parse(json);
for (var i in data.Types) {
Types[i] = data.Types[i];
}
for (var i in data.Variables) {
Variables[i] = data.Variables[i];
}
for (var i in data.Functions) {
Functions[i] = data.Functions[i];
}
/*
print('\n//LOADED_DATA:' + phase + ':' + JSON.stringify({
Types: Types,
Variables: Variables,
Functions: Functions
}));
*/
}
};