pxt-core
Version:
Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors
1,142 lines • 934 kB
JavaScript
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 +