/* -*- Mode: Javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

"use strict";

var calleeGraph = {};
var callerGraph = {};
var gcFunctions = {};
var suppressedFunctions = {};

function addGCFunction(caller, reason)
{
    if (caller in suppressedFunctions)
        return false;

    if (ignoreGCFunction(caller))
        return false;

    if (!(caller in gcFunctions)) {
        gcFunctions[caller] = reason;
        return true;
    }

    return false;
}

function addCallEdge(caller, callee, suppressed)
{
    if (!(caller in calleeGraph))
        calleeGraph[caller] = [];
    calleeGraph[caller].push({callee:callee, suppressed:suppressed});

    if (!(callee in callerGraph))
        callerGraph[callee] = [];
    callerGraph[callee].push({caller:caller, suppressed:suppressed});
}

var functionNames = [""];

function loadCallgraph(file)
{
    var textLines = snarf(file).split('\n');
    for (var line of textLines) {
	    var match;
        if (match = /^\#(\d+) (.*)/.exec(line)) {
            assert(functionNames.length == match[1]);
            functionNames.push(match[2]);
            continue;
        }
	    var suppressed = false;
	    if (/SUPPRESS_GC/.test(line)) {
            match = /^(..)SUPPRESS_GC (.*)/.exec(line);
            line = match[1] + match[2];
            suppressed = true;
	    }
	    if (match = /^I (\d+) VARIABLE ([^\,]*)/.exec(line)) {
            var caller = functionNames[match[1]];
            var name = match[2];
            if (!indirectCallCannotGC(caller, name) && !suppressed)
		        addGCFunction(caller, "IndirectCall: " + name);
	    } else if (match = /^F (\d+) CLASS (.*?) FIELD (.*)/.exec(line)) {
            var caller = functionNames[match[1]];
            var csu = match[2];
            var fullfield = csu + "." + match[3];
            if (!fieldCallCannotGC(csu, fullfield) && !suppressed)
		        addGCFunction(caller, "FieldCall: " + fullfield);
	    } else if (match = /^D (\d+) (\d+)/.exec(line)) {
            var caller = functionNames[match[1]];
            var callee = functionNames[match[2]];
            addCallEdge(caller, callee, suppressed);
	    }
    }

    var worklist = [];
    for (var name in callerGraph)
	    suppressedFunctions[name] = true;
    for (var name in calleeGraph) {
	    if (!(name in callerGraph)) {
            suppressedFunctions[name] = true;
            worklist.push(name);
	    }
    }
    while (worklist.length) {
	    name = worklist.pop();
	    if (shouldSuppressGC(name))
            continue;
	    if (!(name in suppressedFunctions))
            continue;
	    delete suppressedFunctions[name];
	    if (!(name in calleeGraph))
            continue;
	    for (var entry of calleeGraph[name]) {
            if (!entry.suppressed)
		        worklist.push(entry.callee);
	    }
    }

    for (var name in gcFunctions) {
	    if (name in suppressedFunctions)
            delete gcFunctions[name];
    }

    var gcName = 'void js::GC(JSRuntime*, uint32, uint32)';
    assert(gcName in callerGraph);
    addGCFunction(gcName, "GC");

    var worklist = [];
    for (var name in gcFunctions)
	    worklist.push(name);

    while (worklist.length) {
	    name = worklist.pop();
	    assert(name in gcFunctions);
	    if (!(name in callerGraph))
            continue;
	    for (var entry of callerGraph[name]) {
            if (!entry.suppressed && addGCFunction(entry.caller, name))
		        worklist.push(entry.caller);
	    }
    }
}
