"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var util_1 = require("./util");
var metadata_1 = __importDefault(require("./metadata"));
var stub_1 = __importDefault(require("./stub"));
var debug_1 = __importDefault(require("debug"));
var log = (0, debug_1.default)("dylink");
var STACK_ALIGN = 16; // copied from emscripten
function importWebAssemblyDlopen(_a) {
    var path = _a.path, importObject = _a.importObject, importWebAssembly = _a.importWebAssembly, importWebAssemblySync = _a.importWebAssemblySync, readFileSync = _a.readFileSync, stub = _a.stub;
    return __awaiter(this, void 0, void 0, function () {
        function symbolViaPointer(key) {
            log("functionViaPointer", key);
            var f = mainInstance.exports["__WASM_EXPORT__".concat(key)];
            if (f == null)
                return;
            return f();
        }
        function functionViaPointer(key) {
            if (mainInstance == null)
                return; // not yet available
            log("functionViaPointer", key);
            var f = mainInstance.exports["__WASM_EXPORT__".concat(key)];
            if (f == null)
                return;
            var ptr = f();
            if (__indirect_function_table == null) {
                throw Error("__indirect_function_table must be defined");
            }
            return __indirect_function_table.get(ptr);
        }
        function setTable(index, f) {
            if (__indirect_function_table == null) {
                throw Error("__indirect_function_table must be defined");
            }
            if (__indirect_function_table.get(index)) {
                throw Error("setTable: attempt to overwrite existing function! ".concat(index));
            }
            if (__indirect_function_table.length <= index + 50) {
                __indirect_function_table.grow(50);
            }
            // log("setTable ", index, typeof index, f, typeof f);
            __indirect_function_table.set(index, f);
        }
        // See if the function we want is defined in some
        // already imported dynamic library:
        function functionFromOtherLibrary(name) {
            var _a, _b, _c;
            for (var handle in handleToLibrary) {
                var _d = handleToLibrary[handle], symToPtr = _d.symToPtr, instance = _d.instance;
                // two places that could have the pointer:
                var ptr = (_a = symToPtr[name]) !== null && _a !== void 0 ? _a : (_c = (_b = instance.exports)["__WASM_EXPORT__".concat(name)]) === null || _c === void 0 ? void 0 : _c.call(_b);
                if (ptr != null) {
                    if (__indirect_function_table == null) {
                        throw Error("__indirect_function_table must be defined");
                    }
                    log("functionFromOtherLibrary - got ", name, " from ", path);
                    return __indirect_function_table.get(ptr);
                }
            }
            return undefined;
        }
        function getFunction(name, path) {
            var _a, _b, _c, _d;
            if (path === void 0) { path = ""; }
            var f = (_d = (_c = (_b = (_a = importObject === null || importObject === void 0 ? void 0 : importObject.env) === null || _a === void 0 ? void 0 : _a[name]) !== null && _b !== void 0 ? _b : functionViaPointer(name)) !== null && _c !== void 0 ? _c : mainInstance.exports[name]) !== null && _d !== void 0 ? _d : functionFromOtherLibrary(name);
            if (f != null)
                return f;
            if (path) {
                (0, debug_1.default)("stub")(name, "undefined importing", path);
            }
            return importObjectWithStub.env[name];
        }
        function dlopenEnvHandler(path) {
            return function (env, key) {
                if (key in env) {
                    return Reflect.get(env, key);
                }
                log("dlopenEnvHandler", key);
                // important to check importObject.env LAST since it could be a proxy
                // that generates stub functions:
                var f = getFunction(key, path);
                if (f == null) {
                    log("dlopenEnvHandler got null");
                    return;
                }
                return f;
                // FOR LOW LEVEL DEBUGGING ONLY!
                //     return (...args) => {
                //       console.log("env call ", key);
                //       // @ts-ignore
                //       return f(...args);
                //     };
            };
        }
        function GOTMemHandler(GOT, key) {
            if (key in GOT) {
                return Reflect.get(GOT, key);
            }
            /*
            The spec has the following (garbled?) statement about what this is:
            "However since exports are static, modules connect [sic -- cannot?]
            export the final relocated addresses (i.e. they cannot add
            __memory_base before exporting). Thus, the exported address is
            before relocation; the loader, which knows __memory_base, can
            then calculate the final relocated address."
        
            In any case, what we need to do here is return the *memory address*
            of the variable with name key.  For example, if key='stdin', we
            are returning the address of the stdin file descriptor (that integer).
            */
            var rtn = GOT[key];
            if (!rtn) {
                var x = new WebAssembly.Global({
                    value: "i32",
                    mutable: true,
                }, 0);
                memMap[key] = x;
                rtn = GOT[key] = x;
            }
            return rtn;
        }
        function GOTFuncHandler(GOT, key) {
            if (key in GOT) {
                return Reflect.get(GOT, key);
            }
            var rtn = GOT[key];
            if (!rtn) {
                log("GOTFuncHandler ", key, "-->", nextTablePos);
                // place in the table -- we make a note of where to put it,
                // and actually place it later below after the import is done.
                var ptr = new WebAssembly.Global({
                    value: "i32",
                    mutable: true,
                }, nextTablePos);
                rtn = GOT[key] = funcMap[key] = ptr;
                nextTablePos += 1;
            }
            return rtn;
        }
        var env, memory, __indirect_function_table, GOT, memMap, funcMap, GOTmem, GOTfunc, pathToLibrary, handleToLibrary, importObjectWithStub, mainInstance, _b, nextTablePos;
        return __generator(this, function (_c) {
            switch (_c.label) {
                case 0:
                    if (importObject == null) {
                        importObject = {};
                    }
                    env = importObject.env;
                    if (env == null) {
                        env = importObject.env = {};
                    }
                    memory = env.memory;
                    if (memory == null) {
                        memory = env.memory = new WebAssembly.Memory({ initial: 10 });
                    }
                    __indirect_function_table = env.__indirect_function_table;
                    if (__indirect_function_table == null) {
                        // TODO: Make the 1000 bigger if your main module has a large number of function pointers
                        // Maybe we need to parse the wasm bundle in general (that's what emscripten does).
                        __indirect_function_table = env.__indirect_function_table =
                            new WebAssembly.Table({ initial: 1000, element: "anyfunc" });
                    }
                    GOT = {};
                    memMap = {};
                    funcMap = {};
                    GOTmem = new Proxy(GOT, { get: GOTMemHandler });
                    GOTfunc = new Proxy(GOT, { get: GOTFuncHandler });
                    pathToLibrary = {};
                    handleToLibrary = {};
                    env.dlopen = function (pathnamePtr, _flags) {
                        var _a, _b, _c, _d;
                        // TODO: _flags are ignored for now.
                        if (memory == null)
                            throw Error("bug"); // mainly for typescript
                        var path = (0, util_1.recvString)(pathnamePtr, memory);
                        log("dlopen: path='%s'", path);
                        if (pathToLibrary[path] != null) {
                            return pathToLibrary[path].handle;
                        }
                        var binary = new Uint8Array(readFileSync(path));
                        var metadata = (0, metadata_1.default)(binary);
                        log("metadata", metadata);
                        // alignments are powers of 2
                        var memAlign = Math.pow(2, (_a = metadata.memoryAlign) !== null && _a !== void 0 ? _a : 0);
                        // finalize alignments and verify them
                        memAlign = Math.max(memAlign, STACK_ALIGN); // we at least need stack alignment
                        var malloc = getFunction("malloc");
                        if (malloc == null) {
                            throw Error("malloc from libc must be available in the  main instance");
                        }
                        if (metadata.memorySize == null) {
                            throw Error("memorySize must be defined in the shared library");
                        }
                        var alloc = malloc(metadata.memorySize + memAlign);
                        if (alloc == 0) {
                            throw Error("malloc failed (you cannot use a stub for malloc)");
                        }
                        // TODO: I read that the stack is 64KB by default, typically, so
                        // that's what I'm allocating.  I don't know how to confirm or
                        // ensure this with options to clang yet with 100% certainty, though
                        // obviously that's important to do.
                        var stack_alloc = malloc(65536);
                        if (stack_alloc == 0) {
                            throw Error("malloc failed for stack");
                        }
                        log("allocating %s bytes for shared library -- at ", metadata.memorySize + memAlign, alloc);
                        var __memory_base = metadata.memorySize
                            ? (0, util_1.alignMemory)(alloc, memAlign)
                            : 0;
                        var __table_base = metadata.tableSize ? nextTablePos : 0;
                        var env = {
                            memory: memory,
                            __indirect_function_table: __indirect_function_table,
                            __memory_base: __memory_base,
                            __table_base: __table_base,
                            __stack_pointer: new WebAssembly.Global({
                                value: "i32",
                                mutable: true,
                            }, stack_alloc),
                        };
                        log("env =", env);
                        var libImportObject = __assign(__assign({}, importObject), { env: new Proxy(env, { get: dlopenEnvHandler(path) }), "GOT.mem": GOTmem, "GOT.func": GOTfunc });
                        // account for the entries that got inserted during the import.
                        // This must happen BEFORE the import, since that will create some
                        // new entries to get put in the table below, and the import itself
                        // will put entries from the current position up to metadata.tableSize
                        // positions forward.
                        nextTablePos += (_b = metadata.tableSize) !== null && _b !== void 0 ? _b : 0;
                        var instance = importWebAssemblySync(path, libImportObject);
                        //log("got exports=", instance.exports);
                        if (__indirect_function_table == null) {
                            throw Error("bug");
                        }
                        var symToPtr = {};
                        for (var name_1 in instance.exports) {
                            // TODO: I'm worried that these might be VERY slow.
                            // It probably doesn't matter for Python, since I think it only
                            // uses this for the module init...
                            if (funcMap[name_1] != null)
                                continue;
                            var val = instance.exports[name_1];
                            if (symToPtr[name_1] != null || typeof val != "function")
                                continue;
                            setTable(nextTablePos, val);
                            symToPtr[name_1] = nextTablePos;
                            nextTablePos += 1;
                        }
                        for (var symName in funcMap) {
                            var f = (_c = instance.exports[symName]) !== null && _c !== void 0 ? _c : mainInstance.exports[symName];
                            if (f == null)
                                continue;
                            //log("table[%s] = %s", funcMap[symName], symName, f);
                            setTable(funcMap[symName].value, f);
                            symToPtr[symName] = funcMap[symName];
                            delete funcMap[symName];
                        }
                        for (var symName in memMap) {
                            var x = memMap[symName];
                            delete memMap[symName];
                            var ptrBeforeOffset = (_d = instance.exports[symName]) === null || _d === void 0 ? void 0 : _d.value;
                            if (ptrBeforeOffset == null) {
                                var ptr = symbolViaPointer(symName);
                                if (ptr == null) {
                                    throw Error("dlopen -- UNRESOLVED SYMBOL: ".concat(symName));
                                }
                                else {
                                    //console.log("found ", symName, " in global");
                                    x.value = ptr;
                                }
                            }
                            else {
                                x.value = ptrBeforeOffset + __memory_base;
                                //console.log("putting ", symName, " in offset");
                            }
                        }
                        if (instance.exports.__wasm_call_ctors != null) {
                            // This **MUST** be after updating all the values above!!
                            log("calling __wasm_call_ctors for dynamic library");
                            instance.exports.__wasm_call_ctors();
                        }
                        if (instance.exports.__wasm_apply_data_relocs != null) {
                            // This **MUST** be after updating all the values above!!
                            log("calling __wasm_apply_data_relocs for dynamic library");
                            instance.exports.__wasm_apply_data_relocs();
                        }
                        // Get an available handle by maxing all the int versions of the
                        // keys of the handleToLibrary map.
                        var handle = Math.max.apply(Math, __spreadArray([0], __read(Object.keys(handleToLibrary).map(parseInt)), false)) + 1;
                        var library = {
                            path: path,
                            handle: handle,
                            instance: instance,
                            symToPtr: symToPtr,
                        };
                        pathToLibrary[path] = library;
                        handleToLibrary[handle] = library;
                        //     log(
                        //       "after dlopen table looks like:",
                        //       nonzeroPositions(__indirect_function_table)
                        //     );
                        return handle;
                    };
                    env.dlsym = function (handle, symbolPtr) {
                        var _a, _b;
                        if (memory == null)
                            throw Error("bug"); // mainly for typescript
                        var symName = (0, util_1.recvString)(symbolPtr, memory);
                        log("dlsym: handle=%s, symName='%s'", handle, symName);
                        var lib = handleToLibrary[handle];
                        if (lib == null) {
                            throw Error("dlsym: invalid handle ".concat(handle));
                        }
                        var ptr = lib.symToPtr[symName];
                        log("ptr = ", ptr);
                        if (ptr != null) {
                            // symbol is a known function pointer
                            return ptr;
                        }
                        // sometimes its an alias:
                        ptr = (_b = (_a = lib.instance.exports)["__WASM_EXPORT__".concat(symName)]) === null || _b === void 0 ? void 0 : _b.call(_a);
                        if (ptr != null) {
                            // symbol is a known function pointer
                            return ptr;
                        }
                        // NOT sure if this is at all correct or meaningful or what to even
                        // do with non functions!
                        // I think Python only uses function pointers?
                        // return lib.instance.exports[symName]
                        throw Error("dlsym: handle=".concat(handle, " - unknown symbol '").concat(symName, "'"));
                    };
                    /*
                    "The function dlerror() returns a human readable string describing the most
                    recent error that occurred from dlopen(), dlsym() or dlclose() since the last
                    call to dlerror(). It returns NULL if no errors have occurred since
                    initialization or since it was last called."
                    */
                    env.dlerror = function () {
                        // TODO: need to allocate a string to implement this, and also keep track
                        // of errors.
                        return 0;
                    };
                    /*
                    "The function dlclose() decrements the reference count on the dynamic library
                    handle handle. If the reference count drops to zero and no other loaded
                    libraries use symbols in it, then the dynamic library is unloaded. The function
                    dlclose() returns 0 on success, and nonzero on error."
                    */
                    env.dlclose = function (_handle) {
                        // TODO: we never clean up... yet. But we could.
                        return 0;
                    };
                    importObjectWithStub = stub
                        ? __assign(__assign({}, importObject), { env: (0, stub_1.default)(importObject.env, functionViaPointer) }) : importObject;
                    if (!(importWebAssembly != null)) return [3 /*break*/, 2];
                    return [4 /*yield*/, importWebAssembly(path, importObjectWithStub)];
                case 1:
                    _b = _c.sent();
                    return [3 /*break*/, 3];
                case 2:
                    _b = importWebAssemblySync(path, importObjectWithStub);
                    _c.label = 3;
                case 3:
                    mainInstance = _b;
                    if (mainInstance.exports.__wasm_call_ctors != null) {
                        // We also **MUST** explicitly call the WASM constructors. This is
                        // a library function that is part of the zig libc code.  We have
                        // to call this because the wasm file is built using build-lib, so
                        // there is no main that does this.  This call does things like
                        // setup the filesystem mapping.    Yes, it took me **days**
                        // to figure this out, including reading a lot of assembly code. :shrug:
                        mainInstance.exports.__wasm_call_ctors();
                    }
                    nextTablePos = Math.max.apply(Math, __spreadArray([0], __read((0, util_1.nonzeroPositions)(__indirect_function_table)), false)) + 1;
                    return [2 /*return*/, mainInstance];
            }
        });
    });
}
exports.default = importWebAssemblyDlopen;
//# sourceMappingURL=import.js.map