UNPKG

pxt-core

Version:

Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors

1,142 lines • 934 kB
var pxt; (function (pxt) { let callbacks; let ghSetup = false; class CachedGithubDb { constructor(db) { this.db = db; } loadAsync(repopath, tag, suffix, loader) { // only cache releases if (!/^v\d+\.\d+\.\d+$/.test(tag)) return loader(repopath, tag); const key = `gh-${suffix}-${repopath}#${tag}`; return callbacks.cacheGet(key) .then(json => { if (json) { const p = pxt.Util.jsonTryParse(json); if (p) { pxt.debug(`cache hit ${key}`); return Promise.resolve(p); } } // download and cache return loader(repopath, tag) .then(p => { if (p) { pxt.debug(`cached ${key}`); return callbacks.cacheSet(key, JSON.stringify(p)) .then(() => p); } return p; }); }); } latestVersionAsync(repopath, config) { return this.db.latestVersionAsync(repopath, config); } loadConfigAsync(repopath, tag) { return this.loadAsync(repopath, tag, "pxt", (r, t) => this.db.loadConfigAsync(r, t)); } loadPackageAsync(repopath, tag) { return this.loadAsync(repopath, tag, "pkg", (r, t) => this.db.loadPackageAsync(r, t)); } loadTutorialMarkdown(repopath, tag) { return this.loadAsync(repopath, tag, "tutorial", (r, t) => this.db.loadTutorialMarkdown(r, t)); } cacheReposAsync(resp) { return this.db.cacheReposAsync(resp); } } function pkgOverrideAsync(pkg) { const f = callbacks === null || callbacks === void 0 ? void 0 : callbacks.pkgOverrideAsync; const v = f ? f(pkg.id) : Promise.resolve(undefined); return v.then(r => r ? r : pkg.commonDownloadAsync()); } class SimpleHost { constructor(packageFiles) { this.packageFiles = packageFiles; } resolve(module, filename) { return ""; } readFile(module, filename) { const fid = module.id == "this" ? filename : "pxt_modules/" + module.id + "/" + filename; if (this.packageFiles[fid] !== undefined) { return this.packageFiles[fid]; } else if (pxt.appTarget.bundledpkgs[module.id]) { return pxt.appTarget.bundledpkgs[module.id][filename]; } else { return null; } } writeFile(module, filename, contents) { const pref = module.id == "this" ? "" : "pxt_modules/" + module.id + "/"; pxt.debug(`write file ${pref + filename}`); this.packageFiles[pref + filename] = contents; } getHexInfoAsync(extInfo) { //pxt.log(`getHexInfoAsync(${extInfo})`); return Promise.resolve({ hex: ["SKIP"] }); } cacheStoreAsync(id, val) { //pxt.log(`cacheStoreAsync(${id}, ${val})`) if (callbacks === null || callbacks === void 0 ? void 0 : callbacks.cacheSet) return callbacks.cacheSet(id, val); return Promise.resolve(); } cacheGetAsync(id) { //pxt.log(`cacheGetAsync(${id})`) if (callbacks === null || callbacks === void 0 ? void 0 : callbacks.cacheGet) return callbacks.cacheGet(id); return Promise.resolve(""); } downloadPackageAsync(pkg) { if (ghSetup) return pkgOverrideAsync(pkg) .then(resp => { if (resp) { pxt.U.iterMap(resp, (fn, cont) => { this.writeFile(pkg, fn, cont); }); } }); //pxt.log(`downloadPackageAsync(${pkg.id})`) return Promise.resolve(); } resolveVersionAsync(pkg) { //pxt.log(`resolveVersionAsync(${pkg.id})`) return Promise.resolve("*"); } } pxt.SimpleHost = SimpleHost; function prepPythonOptions(opts) { // this is suboptimal, but we need apisInfo for the python converter if (opts.target.preferredEditor == pxt.PYTHON_PROJECT_NAME) { const opts2 = pxt.U.clone(opts); opts2.ast = true; opts2.target.preferredEditor = pxt.JAVASCRIPT_PROJECT_NAME; //opts2.noEmit = true // remove previously converted .ts files, so they don't end up in apisinfo for (let f of opts2.sourceFiles) { if (pxt.U.endsWith(f, ".py")) opts2.fileSystem[f.slice(0, -3) + ".ts"] = " "; } const res = pxtc.compile(opts2); opts.apisInfo = pxtc.getApiInfo(res.ast, opts2.jres); } } pxt.prepPythonOptions = prepPythonOptions; function simpleInstallPackagesAsync(files) { const host = new SimpleHost(files); const mainPkg = new pxt.MainPackage(host); return mainPkg.loadAsync(true); } pxt.simpleInstallPackagesAsync = simpleInstallPackagesAsync; function simpleGetCompileOptionsAsync(files, simpleOptions) { const host = new SimpleHost(files); const mainPkg = new pxt.MainPackage(host); return mainPkg.loadAsync() .then(() => { let target = mainPkg.getTargetOptions(); if (target.hasHex) target.isNative = simpleOptions.native; return mainPkg.getCompileOptionsAsync(target); }).then(opts => { patchTS(mainPkg.targetVersion(), opts); if (mainPkg.getPreferredEditor() === pxt.PYTHON_PROJECT_NAME) { patchPY(mainPkg.targetVersion(), opts); } prepPythonOptions(opts); return opts; }); } pxt.simpleGetCompileOptionsAsync = simpleGetCompileOptionsAsync; function simpleCompileAsync(files, optionsOrNative) { const options = typeof optionsOrNative == "boolean" ? { native: optionsOrNative } : optionsOrNative || {}; return simpleGetCompileOptionsAsync(files, options) .then(opts => pxtc.compile(opts)) .then((r) => { if (!r.success) r.errors = r.diagnostics.map(ts.pxtc.getDiagnosticString).join("") || "Unknown error."; return r; }); } pxt.simpleCompileAsync = simpleCompileAsync; function patchTS(version, opts) { if (!version) return; pxt.debug(`applying TS patches relative to ${version}`); for (let fn of Object.keys(opts.fileSystem)) { if (fn.indexOf("/") == -1 && pxt.U.endsWith(fn, ".ts")) { const ts = opts.fileSystem[fn]; const ts2 = pxt.patching.patchJavaScript(version, ts); if (ts != ts2) { pxt.debug(`applying TS patch to ${fn}`); opts.fileSystem[fn] = ts2; } } } } pxt.patchTS = patchTS; function patchPY(version, opts) { if (!version) return; pxt.debug(`applying PY patches relative to ${version}`); for (let fn of Object.keys(opts.fileSystem)) { if (fn.indexOf("/") == -1 && pxt.U.endsWith(fn, ".py")) { const initial = opts.fileSystem[fn]; const patched = pxt.patching.patchPython(version, initial); if (initial != patched) { pxt.debug(`applying PY patch to ${fn}`); opts.fileSystem[fn] = patched; } } } } pxt.patchPY = patchPY; function setupSimpleCompile(cfg) { if (typeof global != "undefined" && !global.btoa) { global.btoa = function (str) { return Buffer.from(str, "binary").toString("base64"); }; global.atob = function (str) { return Buffer.from(str, "base64").toString("binary"); }; } if (typeof pxtTargetBundle != "undefined") { pxt.debug("setup app bundle"); pxt.setAppTarget(pxtTargetBundle); } if (cfg) { callbacks = cfg; ghSetup = true; if (cfg.httpRequestAsync) pxt.Util.httpRequestCoreAsync = cfg.httpRequestAsync; pxt.github.forceProxy = true; pxt.github.db = new CachedGithubDb(new pxt.github.MemoryGithubDb()); } pxt.debug("simple setup done"); } pxt.setupSimpleCompile = setupSimpleCompile; })(pxt || (pxt = {})); /// <reference path='../built/pxtlib.d.ts' /> var pxt; (function (pxt) { function simshim(prog, pathParse) { let SK = ts.SyntaxKind; let checker = prog.getTypeChecker(); let mainWr = pxt.cpp.nsWriter("declare namespace"); let currNs = ""; let currMod; for (let src of prog.getSourceFiles()) { if (pathParse) { let pp = pathParse(src.fileName); pxt.debug("SimShim[1]: " + pp.dir); if (!pxt.U.endsWith(pp.dir, "/sim") && !pxt.U.startsWith(src.fileName, "sim/")) continue; } else if (!pxt.U.startsWith(src.fileName, "sim/")) continue; pxt.debug("SimShim[2]: " + src.fileName); for (let stmt of src.statements) { let mod = stmt; if (stmt.kind == SK.ModuleDeclaration && mod.name.text == "pxsim") { currMod = mod; doStmt(mod.body); } } } let res = {}; res[pxt.appTarget.corepkg] = mainWr.finish(); return res; function typeOf(node) { let r; if (ts.isExpression(node)) r = checker.getContextualType(node); if (!r) r = checker.getTypeAtLocation(node); return r; } /* let doSymbol = (sym: ts.Symbol) => { if (sym.getFlags() & ts.SymbolFlags.HasExports) { typechecker.getExportsOfModule(sym).forEach(doSymbol) } decls[pxtc.getFullName(typechecker, sym)] = sym } */ function emitModuleDeclaration(mod) { let prevNs = currNs; if (currNs) currNs += "."; currNs += mod.name.text; doStmt(mod.body); currNs = prevNs; } function mapType(tp) { let fn = checker.typeToString(tp, currMod, ts.TypeFormatFlags.UseFullyQualifiedType); fn = fn.replace(/^pxsim\./, ""); switch (fn) { case "RefAction": return "() => void"; case "RefBuffer": return "Buffer"; default: return fn; } } function promiseElementType(tp) { if (pxtc.isObjectType(tp) && (tp.objectFlags & ts.ObjectFlags.Reference) && tp.symbol.name == "Promise") { return tp.typeArguments[0]; } return null; } function emitClassDeclaration(cl) { let cmts = getExportComments(cl); if (!cmts) return; mainWr.setNs(currNs); mainWr.write(cmts); let prevNs = currNs; if (currNs) currNs += "."; currNs += cl.name.text; let decl = prevNs ? "" : "declare"; let ext = ""; if (cl.heritageClauses) for (let h of cl.heritageClauses) { if (h.token == SK.ExtendsKeyword) { ext = " extends " + mapType(typeOf(h.types[0])); } } mainWr.write(`${decl} class ${cl.name.text}${ext} {`); mainWr.incrIndent(); for (let mem of cl.members) { switch (mem.kind) { case SK.MethodDeclaration: emitFunctionDeclaration(mem); break; case SK.PropertyDeclaration: emitPropertyDeclaration(mem); break; case SK.Constructor: emitConstructorDeclaration(mem); break; case SK.GetAccessor: let hasSetter = cl.members.some(m => m.kind == SK.SetAccessor && m.name.getText() == mem.name.getText()); emitFunctionDeclaration(mem, hasSetter); break; default: break; } } currNs = prevNs; mainWr.decrIndent(); mainWr.write(`}`); } function getExportComments(n) { let cmts = pxtc.getComments(n); if (!/^\s*\/\/%/m.test(cmts)) return null; return cmts; } function emitPropertyDeclaration(fn) { let cmts = getExportComments(fn); if (!cmts) return; let nm = fn.name.getText(); let attrs = "//% shim=." + nm; let tp = checker.getTypeAtLocation(fn); mainWr.write(cmts); mainWr.write(attrs); mainWr.write(`public ${nm}: ${mapType(tp)};`); mainWr.write(""); } function emitConstructorDeclaration(fn) { let cmts = getExportComments(fn); if (!cmts) return; let tp = checker.getTypeAtLocation(fn); let args = fn.parameters.map(p => p.name.getText() + ": " + mapType(typeOf(p))); mainWr.write(cmts); mainWr.write(`//% shim="new ${currNs}"`); mainWr.write(`constructor(${args.join(", ")});`); mainWr.write(""); } function emitFunctionDeclaration(fn, hasSetter = false) { let cmts = getExportComments(fn); if (!cmts) return; let fnname = fn.name.getText(); let isMethod = fn.kind == SK.MethodDeclaration || fn.kind == SK.GetAccessor || fn.kind == SK.SetAccessor; let attrs = "//% shim=" + (isMethod ? "." + fnname : currNs + "::" + fnname); let sig = checker.getSignatureFromDeclaration(fn); let rettp = checker.getReturnTypeOfSignature(sig); let asyncName = /Async$/.test(fnname); let prom = promiseElementType(rettp); if (prom) { attrs += " promise"; rettp = prom; if (!asyncName) pxt.U.userError(`${currNs}::${fnname} should be called ${fnname}Async`); } else if (asyncName) { pxt.U.userError(`${currNs}::${fnname} doesn't return a promise`); } pxt.debug("emitFun: " + fnname); let args = fn.parameters.map(p => { return `${p.name.getText()}${p.questionToken ? "?" : ""}: ${mapType(typeOf(p))}`; }); let localname = fnname.replace(/Async$/, ""); let defkw = isMethod ? "public" : "function"; let allArgs = `(${args.join(", ")})`; if (fn.kind == SK.GetAccessor) { defkw = hasSetter ? "public" : "readonly"; allArgs = ""; attrs += " property"; } if (!isMethod) mainWr.setNs(currNs); mainWr.write(cmts); mainWr.write(attrs); mainWr.write(`${defkw} ${localname}${allArgs}: ${mapType(rettp)};`); mainWr.write(""); } function doStmt(stmt) { switch (stmt.kind) { case SK.ModuleDeclaration: return emitModuleDeclaration(stmt); case SK.ModuleBlock: return stmt.statements.forEach(doStmt); case SK.FunctionDeclaration: return emitFunctionDeclaration(stmt); case SK.ClassDeclaration: return emitClassDeclaration(stmt); } //pxt.log("SKIP", pxtc.stringKind(stmt)) //let mod = stmt as ts.ModuleDeclaration //if (mod.name) pxt.log(mod.name.text) /* if (mod.name) { let sym = typechecker.getSymbolAtLocation(mod.name) if (sym) doSymbol(sym) } */ } } pxt.simshim = simshim; })(pxt || (pxt = {})); var ts; (function (ts) { var pxtc; (function (pxtc) { /** * Traverses the AST and injects information about function calls into the expression * nodes. The decompiler consumes this information later * * @param program The TypeScript Program representing the code to compile * @param entryPoint The name of the source file to annotate the AST of * @param compileTarget The compilation of the target */ function annotate(program, entryPoint, compileTarget) { const oldTarget = pxtc.target; pxtc.target = compileTarget; let src = program.getSourceFiles().filter(f => f.fileName === entryPoint)[0]; let checker = program.getTypeChecker(); recurse(src); pxtc.target = oldTarget; function recurse(parent) { ts.forEachChild(parent, child => { switch (child.kind) { case ts.SyntaxKind.CallExpression: mkCallInfo(child, child.arguments.slice(0), null, getDecl(child.expression)); break; case ts.SyntaxKind.PropertyAccessExpression: mkCallInfo(child, []); break; case ts.SyntaxKind.TaggedTemplateExpression: mkCallInfo(child, [child.template], true, getDecl(child.tag)); break; case ts.SyntaxKind.BinaryExpression: annotateBinaryExpression(child); break; case ts.SyntaxKind.Identifier: const decl = getDecl(child); if (decl && decl.getSourceFile().fileName !== pxt.MAIN_TS && decl.kind == ts.SyntaxKind.VariableDeclaration) { const info = pxtc.pxtInfo(child); info.flags |= 4 /* PxtNodeFlags.IsGlobalIdentifier */; if (!info.commentAttrs) { info.commentAttrs = pxtc.parseComments(decl); } } break; } recurse(child); }); } function annotateBinaryExpression(node) { const trg = node.left; const expr = node.right; let lt = typeOf(node.left); let rt = typeOf(node.right); if (node.operatorToken.kind == pxtc.SK.PlusToken || node.operatorToken.kind == pxtc.SK.PlusEqualsToken) { if (pxtc.isStringType(lt) || (pxtc.isStringType(rt) && node.operatorToken.kind == pxtc.SK.PlusToken)) { pxtc.pxtInfo(node).exprInfo = { leftType: checker.typeToString(lt), rightType: checker.typeToString(rt) }; } } switch (node.operatorToken.kind) { case ts.SyntaxKind.EqualsToken: case ts.SyntaxKind.PlusEqualsToken: case ts.SyntaxKind.MinusEqualsToken: if (trg.kind == pxtc.SK.PropertyAccessExpression) { // Getter/Setter let decl = getDecl(trg); if (decl && decl.kind == pxtc.SK.GetAccessor) { decl = ts.getDeclarationOfKind(decl.symbol, pxtc.SK.SetAccessor); mkCallInfo(trg, [expr], false, decl); } else if (decl && (decl.kind == pxtc.SK.PropertySignature || decl.kind == pxtc.SK.PropertyAssignment || (pxtc.target && pxtc.target.switches.slowFields))) { mkCallInfo(trg, [expr]); } } break; } } function mkCallInfo(node, args, isExpression = false, d = null) { let hasRet = isExpression || !(typeOf(node).flags & ts.TypeFlags.Void); let decl = d || getDecl(node); if (node.expression && decl) { let isMethod = false; switch (decl.kind) { // we treat properties via calls // so we say they are "methods" case pxtc.SK.PropertySignature: case pxtc.SK.PropertyAssignment: case pxtc.SK.PropertyDeclaration: if (!pxtc.isStatic(decl)) { isMethod = true; } break; case pxtc.SK.Parameter: if (pxtc.isCtorField(decl)) { isMethod = true; } break; // TOTO case: case SK.ShorthandPropertyAssignment // these are the real methods case pxtc.SK.GetAccessor: case pxtc.SK.SetAccessor: case pxtc.SK.MethodDeclaration: case pxtc.SK.MethodSignature: if (!pxtc.isStatic(decl)) { isMethod = true; } break; default: break; } if (isMethod) { const expr = node.expression; // Add the "this" parameter to the call info if (expr.kind === pxtc.SK.PropertyAccessExpression) { // If the node is a property access, the right hand side is just // the function name so grab the left instead args.unshift(expr.expression); } else { args.unshift(node.expression); } } } let callInfo = { decl, qName: decl ? pxtc.getNodeFullName(checker, decl) : "?", args: args, isExpression: hasRet }; pxtc.pxtInfo(node).callInfo = callInfo; } function getDecl(node) { if (!node) return null; let sym = checker.getSymbolAtLocation(node); let decl; if (sym) { decl = sym.valueDeclaration; if (!decl && sym.declarations) { let decl0 = sym.declarations[0]; if (decl0 && decl0.kind == ts.SyntaxKind.ImportEqualsDeclaration) { sym = checker.getSymbolAtLocation(decl0.moduleReference); if (sym) decl = sym.valueDeclaration; } } } if (!decl && node.kind == pxtc.SK.PropertyAccessExpression) { const namedNode = node; decl = { kind: pxtc.SK.PropertySignature, symbol: { isBogusSymbol: true, name: namedNode.name.getText() }, name: namedNode.name, }; pxtc.pxtInfo(decl).flags |= 2 /* PxtNodeFlags.IsBogusFunction */; } return decl; } function typeOf(node) { let r; const info = pxtc.pxtInfo(node); if (info.typeCache) return info.typeCache; if (ts.isExpression(node)) r = checker.getContextualType(node); if (!r) { r = checker.getTypeAtLocation(node); } if (!r) return r; if (ts.isStringLiteral(node)) return r; // skip checkType() - type is any for literal fragments return pxtc.checkType(r); } } pxtc.annotate = annotate; })(pxtc = ts.pxtc || (ts.pxtc = {})); })(ts || (ts = {})); var ts; (function (ts) { var pxtc; (function (pxtc) { function asmStringLiteral(s) { let r = "\""; for (let i = 0; i < s.length; ++i) { // TODO generate warning when seeing high character ? let c = s.charCodeAt(i) & 0xff; let cc = String.fromCharCode(c); if (cc == "\\" || cc == "\"") r += "\\" + cc; else if (cc == "\n") r += "\\n"; else if (c <= 0xf) r += "\\x0" + c.toString(16); else if (c < 32 || c > 127) r += "\\x" + c.toString(16); else r += cc; } return r + "\""; } pxtc.asmStringLiteral = asmStringLiteral; // this class defines the interface between the IR // and a particular assembler (Thumb, AVR). Thus, // the registers mentioned below are VIRTUAL registers // required by the IR-machine, rather than PHYSICAL registers // at the assembly level. // that said, the assumptions below about registers are based on // ARM, so a mapping will be needed for other processors // Assumptions: // - registers can hold a pointer (data or code) // - special registers include: sp // - fixed registers are r0, r1, r2, r3, r5, r6 // - r0 is the current value (from expression evaluation) // - registers for runtime calls (r0, r1,r2,r3) // - r5 is for captured locals in lambda // - r6 for global{} // - r4 and r7 are used as temporary // - C code assumes the following are calee saved: r4-r7, r8-r11 // - r12 is intra-procedure scratch and can be treated like r0-r3 // - r13 is sp, r14 is lr, r15 is pc // - for calls to user functions, all arguments passed on stack class AssemblerSnippets { nop() { return "TBD(nop)"; } reg_gets_imm(reg, imm) { return "TBD(reg_gets_imm)"; } // Registers are stored on the stack in numerical order proc_setup(numlocals, main) { return "TBD(proc_setup)"; } push_fixed(reg) { return "TBD(push_fixed)"; } push_local(reg) { return "TBD(push_local)"; } push_locals(n) { return "TBD(push_locals)"; } pop_fixed(reg) { return "TBD(pop_fixed)"; } pop_locals(n) { return "TBD(pop_locals)"; } proc_return() { return "TBD(proc_return)"; } debugger_stmt(lbl) { return ""; } debugger_bkpt(lbl) { return ""; } debugger_proc(lbl) { return ""; } unconditional_branch(lbl) { return "TBD(unconditional_branch)"; } beq(lbl) { return "TBD(beq)"; } bne(lbl) { return "TBD(bne)"; } cmp(reg1, reg) { return "TBD(cmp)"; } cmp_zero(reg1) { return "TBD(cmp_zero)"; } arithmetic() { return ""; } // load_reg_src_off is load/store indirect // word? - does offset represent an index that must be multiplied by word size? // inf? - control over size of referenced data // str? - true=Store/false=Load // src - can range over load_reg_src_off(reg, src, off, word, store, inf) { return "TBD(load_reg_src_off)"; } rt_call(name, r0, r1) { return "TBD(rt_call)"; } call_lbl(lbl, saveStack) { return "TBD(call_lbl)"; } call_reg(reg) { return "TBD(call_reg)"; } helper_prologue() { return "TBD(lambda_prologue)"; } helper_epilogue() { return "TBD(lambda_epilogue)"; } pop_clean(pops) { return "TBD"; } load_ptr_full(lbl, reg) { return "TBD(load_ptr_full)"; } emit_int(v, reg) { return "TBD(emit_int)"; } obj_header(vt) { return `.word ${vt}`; } string_literal(lbl, strLit) { const info = utf8AsmStringLiteral(strLit); return ` .balign 4 .object ${lbl} ${lbl}: ${this.obj_header(info.vt)} ${info.asm} `; } hex_literal(lbl, data) { // if buffer looks as if it was prepared for in-app reprogramming (at least 8 bytes of 0xff) // align it to 8 bytes, to make sure it can be rewritten also on SAMD51 const align = /f{16}/i.test(data) ? 8 : 4; return ` .balign ${align} .object ${lbl} ${lbl}: ${this.obj_header("pxt::buffer_vt")} ${hexLiteralAsm(data)} `; } } pxtc.AssemblerSnippets = AssemblerSnippets; function utf8AsmStringLiteral(strLit) { const PXT_STRING_SKIP_INCR = 16; let vt = "pxt::string_inline_ascii_vt"; let utfLit = pxtc.target.utf8 ? pxtc.U.toUTF8(strLit, true) : strLit; let asm = ""; if (utfLit !== strLit) { if (strLit.length > PXT_STRING_SKIP_INCR) { vt = "pxt::string_skiplist16_packed_vt"; let skipList = []; let off = 0; for (let i = 0; i + PXT_STRING_SKIP_INCR <= strLit.length; i += PXT_STRING_SKIP_INCR) { off += pxtc.U.toUTF8(strLit.slice(i, i + PXT_STRING_SKIP_INCR), true).length; skipList.push(off); } asm = ` .short ${utfLit.length}, ${strLit.length} .short ${skipList.map(s => s.toString()).join(", ")} .string ${asmStringLiteral(utfLit)} `; } else { vt = "pxt::string_inline_utf8_vt"; } } if (!asm) asm = ` .short ${utfLit.length} .string ${asmStringLiteral(utfLit)} `; return { vt, asm }; } pxtc.utf8AsmStringLiteral = utf8AsmStringLiteral; function hexLiteralAsm(data, suff = "") { return ` .word ${data.length >> 1}${suff} .hex ${data}${data.length % 4 == 0 ? "" : "00"} `; } pxtc.hexLiteralAsm = hexLiteralAsm; // helper for emit_int function numBytes(n) { let v = 0; for (let q = n; q > 0; q >>>= 8) { v++; } return v || 1; } pxtc.numBytes = numBytes; class ProctoAssembler { constructor(t, bin, proc) { this.resText = ""; this.exprStack = []; this.calls = []; this.proc = null; this.baseStackSize = 0; // real stack size is this + exprStack.length this.labelledHelpers = {}; this.write = (s) => { this.resText += pxtc.asmline(s); }; this.t = t; // TODO in future, figure out if we follow the "Snippets" architecture this.bin = bin; this.proc = proc; if (this.proc) this.work(); } emitHelpers() { this.emitLambdaTrampoline(); this.emitArrayMethods(); this.emitFieldMethods(); this.emitBindHelper(); } redirectOutput(f) { let prevWrite = this.write; let res = ""; this.write = s => res += pxtc.asmline(s); try { f(); } finally { this.write = prevWrite; } return res; } stackSize() { return this.baseStackSize + this.exprStack.length; } stackAlignmentNeeded(offset = 0) { if (!pxtc.target.stackAlign) return 0; let npush = pxtc.target.stackAlign - ((this.stackSize() + offset) & (pxtc.target.stackAlign - 1)); if (npush == pxtc.target.stackAlign) return 0; else return npush; } getAssembly() { return this.resText; } work() { this.write(` ; ; Function ${this.proc.getFullName()} ; `); let baseLabel = this.proc.label(); this.write(`.object ${baseLabel} ${JSON.stringify(this.proc.getFullName())}`); let preLabel = baseLabel + "_pre"; let bkptLabel = baseLabel + "_bkpt"; let locLabel = baseLabel + "_locals"; let endLabel = baseLabel + "_end"; this.write(`${preLabel}:`); this.emitLambdaWrapper(this.proc.isRoot); this.write(`.section code`); this.write(`${baseLabel}:`); if (this.proc.classInfo && this.proc.info.thisParameter && !pxtc.target.switches.skipClassCheck && !pxtc.target.switches.noThisCheckOpt) { this.write(`mov r7, lr`); this.write(`ldr r0, [sp, #0]`); this.emitInstanceOf(this.proc.classInfo, "validate"); this.write("mov lr, r7"); } this.write(` ${baseLabel}_nochk: @stackmark func @stackmark args `); // create a new function for later use by hex file generation this.proc.fillDebugInfo = th => { let labels = th.getLabels(); this.proc.debugInfo = { locals: (this.proc.seqNo == 1 ? this.bin.globals : this.proc.locals).map(l => l.getDebugInfo()), args: this.proc.args.map(l => l.getDebugInfo()), name: this.proc.getName(), codeStartLoc: pxtc.U.lookup(labels, locLabel), codeEndLoc: pxtc.U.lookup(labels, endLabel), bkptLoc: pxtc.U.lookup(labels, bkptLabel), localsMark: pxtc.U.lookup(th.stackAtLabel, locLabel), idx: this.proc.seqNo, calls: this.calls, size: pxtc.U.lookup(labels, endLabel) + 2 - pxtc.U.lookup(labels, preLabel) }; for (let ci of this.calls) { ci.addr = pxtc.U.lookup(labels, ci.callLabel); ci.stack = pxtc.U.lookup(th.stackAtLabel, ci.callLabel); ci.callLabel = undefined; // don't waste space } for (let i = 0; i < this.proc.body.length; ++i) { let bi = this.proc.body[i].breakpointInfo; if (bi) { let off = pxtc.U.lookup(th.stackAtLabel, `__brkp_${bi.id}`); if (off !== this.proc.debugInfo.localsMark) { pxt.log(bi); pxt.log(th.stackAtLabel); pxtc.U.oops(`offset doesn't match: ${off} != ${this.proc.debugInfo.localsMark}`); } } } }; if (this.bin.breakpoints) { this.write(this.t.debugger_proc(bkptLabel)); } this.baseStackSize = 1; // push {lr} let numlocals = this.proc.locals.length; this.write("push {lr}"); this.write(".locals:\n"); if (this.proc.perfCounterNo) { this.write(this.t.emit_int(this.proc.perfCounterNo, "r0")); this.write("bl pxt::startPerfCounter"); } this.write(this.t.proc_setup(numlocals)); this.baseStackSize += numlocals; this.write("@stackmark locals"); this.write(`${locLabel}:`); for (let i = 0; i < this.proc.body.length; ++i) { let s = this.proc.body[i]; // pxt.log("STMT", s.toString()) switch (s.stmtKind) { case pxtc.ir.SK.Expr: this.emitExpr(s.expr); break; case pxtc.ir.SK.StackEmpty: if (this.exprStack.length > 0) { for (let stmt of this.proc.body.slice(i - 4, i + 1)) pxt.log(`PREVSTMT ${stmt.toString().trim()}`); for (let e of this.exprStack) pxt.log(`EXPRSTACK ${e.currUses}/${e.totalUses} E: ${e.toString()}`); pxtc.oops("stack should be empty"); } this.write("@stackempty locals"); break; case pxtc.ir.SK.Jmp: this.emitJmp(s); break; case pxtc.ir.SK.Label: this.write(s.lblName + ":"); this.validateJmpStack(s); break; case pxtc.ir.SK.Comment: this.write(`; ${s.expr.data}`); break; case pxtc.ir.SK.Breakpoint: if (this.bin.breakpoints) { let lbl = `__brkp_${s.breakpointInfo.id}`; if (s.breakpointInfo.isDebuggerStmt) { this.write(this.t.debugger_stmt(lbl)); } else { this.write(this.t.debugger_bkpt(lbl)); } } break; default: pxtc.oops(); } } pxtc.assert(0 <= numlocals && numlocals < 127); if (numlocals > 0) this.write(this.t.pop_locals(numlocals)); if (this.proc.perfCounterNo) { this.write("mov r4, r0"); this.write(this.t.emit_int(this.proc.perfCounterNo, "r0")); this.write("bl pxt::stopPerfCounter"); this.write("mov r0, r4"); } this.write(`${endLabel}:`); this.write(this.t.proc_return()); this.write("@stackempty func"); this.write("@stackempty args"); this.write("; endfun"); } mkLbl(root) { let l = root + this.bin.lblNo++; if (l[0] != "_") l = "." + l; return l; } dumpStack() { let r = "["; for (let s of this.exprStack) { r += s.sharingInfo() + ": " + s.toString() + "; "; } r += "]"; return r; } terminate(expr) { pxtc.assert(expr.exprKind == pxtc.ir.EK.SharedRef); let arg = expr.args[0]; // pxt.log("TERM", arg.sharingInfo(), arg.toString(), this.dumpStack()) pxtc.U.assert(arg.currUses != arg.totalUses); // we should have the terminated expression on top pxtc.U.assert(this.exprStack[0] === arg, "term at top"); // we pretend it's popped and simulate what clearStack would do let numEntries = 1; while (numEntries < this.exprStack.length) { let ee = this.exprStack[numEntries]; if (ee.currUses != ee.totalUses) break; numEntries++; } // in this branch we just remove all that stuff off the stack this.write(`@dummystack ${numEntries}`); this.write(this.t.pop_locals(numEntries)); return numEntries; } validateJmpStack(lbl, off = 0) { // pxt.log("Validate:", off, lbl.lblName, this.dumpStack()) let currSize = this.exprStack.length - off; if (lbl.lblStackSize == null) { lbl.lblStackSize = currSize; } else { if (lbl.lblStackSize != currSize) { pxt.log(lbl.lblStackSize, currSize); pxt.log(this.dumpStack()); pxtc.U.oops("stack misaligned at: " + lbl.lblName); } } } emitJmp(jmp) { let termOff = 0; if (jmp.jmpMode == pxtc.ir.JmpMode.Always) { if (jmp.expr) this.emitExpr(jmp.expr); if (jmp.terminateExpr) termOff = this.terminate(jmp.terminateExpr); this.write(this.t.unconditional_branch(jmp.lblName) + " ; with expression"); } else { let lbl = this.mkLbl("jmpz"); this.emitExpr(jmp.expr); // TODO: remove ARM-specific code if (jmp.expr.exprKind == pxtc.ir.EK.RuntimeCall && (jmp.expr.data === "thumb::subs" || pxtc.U.startsWith(jmp.expr.data, "_cmp_"))) { // no cmp required } else { this.write(this.t.cmp_zero("r0")); } if (jmp.jmpMode == pxtc.ir.JmpMode.IfNotZero) { this.write(this.t.beq(lbl)); // this is to *skip* the following 'b' instruction; beq itself has a very short range } else { // IfZero this.write(this.t.bne(lbl)); } if (jmp.terminateExpr) termOff = this.terminate(jmp.terminateExpr); this.write(this.t.unconditional_branch(jmp.lblName)); this.write(lbl + ":"); } this.validateJmpStack(jmp.lbl, termOff); } clearStack(fast = false) { let numEntries = 0; while (this.exprStack.length > 0 && this.exprStack[0].currUses == this.exprStack[0].totalUses) { numEntries++; this.exprStack.shift(); } if (numEntries) this.write(this.t.pop_locals(numEntries)); if (!fast) { let toClear = this.exprStack.filter(e => e.currUses == e.totalUses && e.irCurrUses != -1); if (toClear.length > 0) { // use r7 as temp; r0-r3 might be used as arguments to functions this.write(this.t.reg_gets_imm("r7", 0)); for (let a of toClear) { a.irCurrUses = -1; this.write(this.loadFromExprStack("r7", a, 0, true)); } } } } emitExprInto(e, reg) { switch (e.exprKind) { case pxtc.ir.EK.NumberLiteral: if (typeof e.data == "number") this.write(this.t.emit_int(e.data, reg)); else pxtc.oops(); break; case pxtc.ir.EK.PointerLiteral: this.write(this.t.load_ptr_full(e.data, reg)); break; case pxtc.ir.EK.SharedRef: let arg = e.args[0]; pxtc.U.assert(!!arg.currUses); // not first use pxtc.U.assert(arg.currUses < arg.totalUses); arg.currUses++; let idx = this.exprStack.indexOf(arg); pxtc.U.assert(idx >= 0); if (idx == 0 && arg.totalUses == arg.currUses) { this.write(this.t.pop_fixed([reg]) + ` ; tmpref @${this.exprStack.length}`); this.exprStack.shift(); this.clearStack(); } else { let idx0 = idx.toString() + ":" + this.exprStack.length; this.write(this.t.load_reg_src_off(reg, "sp", idx0, true) + ` ; tmpref @${this.exprStack.length - idx}`); } break; case pxtc.ir.EK.CellRef: let cell = e.data; if (cell.isGlobal()) { let inf = this.bitSizeInfo(cell.bitSize); let off = "#" + cell.index; if (inf.needsSignExt || cell.index >= inf.immLimit) { this.write(this.t.emit_int(cell.index, reg)); off = reg; } this.write(this.t.load_reg_src_off("r7", "r6", "#0")); this.write(this.t.load_reg_src_off(reg, "r7", off, false, false, inf)); } else { let [src, imm, idx] = this.cellref(cell); this.write(this.t.load_reg_src_off(reg, src, imm, idx)); } break; default: pxtc.oops(); } } bitSizeInfo(b) { let inf = { size: pxtc.sizeOfBitSize(b), immLimit: 128 }; if (inf.size == 1) { inf.immLimit = 32; } else if (inf.size == 2) { inf.immLimit = 64; } if (b == 1 /* BitSize.Int8 */ || b == 3 /* BitSize.Int16 */) { inf.needsSignExt = true; } return inf; } // result in R0 emitExpr(e) { //pxt.log(`EMITEXPR ${e.sharingInfo()} E: ${e.toString()}`) switch (e.exprKind) { case pxtc.ir.EK.JmpValue: this.write("; jmp value (already in r0)"); break; case pxtc.ir.EK.Nop: // this is there because we need different addresses for breakpoints this.write(this.t.nop()); break; case pxtc.ir.EK.FieldAccess: this.emitExpr(e.args[0]); return this.emitFieldAccess(e); case pxtc.ir.EK.Store: return this.emitStore(e.args[0], e.args[1]); case pxtc.ir.EK.RuntimeCall: return this.emitRtCall(e); case pxtc.ir.EK.ProcCall: return this.emitProcCall(e); case pxtc.ir.EK.SharedDef: return this.emitSharedDef(e); case pxtc.ir.EK.Sequence: e.args.forEach(e => this.emitExpr(e)); return this.clearStack(); case pxtc.ir.EK.InstanceOf: this.emitExpr(e.args[0]); return this.emitInstanceOf(e.data, e.jsInfo); default: return this.emitExprInto(e, "r0"); } } emitFieldAccess(e, store = false) { let info = e.data; let pref = store ? "st" : "ld"; let lbl = pref +