Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 95 additions & 1 deletion pxtcompiler/emitter/backthumb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ namespace ts.pxtc {
"numops::lsrs": "_numops_lsrs",
"pxt::toInt": "_numops_toInt",
"pxt::fromInt": "_numops_fromInt",
"pxt::fromBool": "_pxt_fromBool",
"numops::toBool": "_numops_toBool",
"numops::toBoolDecr": "_numops_toBoolDecr",
"Boolean_::bang": "_pxt_boolean_bang",
}

// snippets for ARM Thumb assembly
Expand Down Expand Up @@ -323,6 +327,96 @@ _numops_fromInt:
blx lr
.over2:
${this.callCPPPush("pxt::fromInt")}

; Tag a raw 0/1 truth value into a boolean object (taggedTrue/taggedFalse).
; Inverse of the toBool fast paths below; matches C++ pxt::fromBool.
_pxt_fromBool:
@scope _pxt_fromBool
cmp r0, #0
beq .false
.true:
movs r0, #${taggedTrue}
blx lr
.false:
movs r0, #${taggedFalse}
blx lr

; Logical NOT of a raw 0/1 truth value -> raw 0/1 (matches C++ bool bang(bool)).
; Callers pass an already-tested condition, so this is raw-in / raw-out; value
; context (`x = !y`) wraps the result with fromBool to re-tag it.
_pxt_boolean_bang:
@scope _pxt_boolean_bang
cmp r0, #0
beq .true
.false:
movs r0, #0
blx lr
.true:
movs r0, #1
blx lr

; Fast-path truthiness test: tagged value in r0 -> raw 0/1 in r0.
; Value encodings (see taggedSpecialValue in emitter.ts): tagged int = (n<<1)|1
; (low bit set); special tags = (n<<2)|2 with undefined=0, null, false, NaN,
; true; heap objects have low 2 bits 00.
; Only non-pointer values are decided inline here; any heap object is handed to
; the C++ helper at .boxed (which also handles boxed numbers/strings like 0.0
; and ""). INVARIANT: the falsy set below must match C++ numops::toBool exactly
; -- if a new falsy tagged value is ever added, it must be added here too, or it
; will fall through to .true.
_numops_toBool:
@scope _numops_toBool
cmp r0, #0 ; undefined (0) -> false
beq .false
cmp r0, #1 ; integer 0 ((0<<1)|1) -> false
beq .false
lsls r1, r0, #31 ; odd => nonzero tagged int -> true
bne .true
cmp r0, #${taggedNull} ; null -> false
beq .false
cmp r0, #${taggedFalse} ; false -> false
beq .false
cmp r0, #${taggedNaN} ; NaN -> false
beq .false
lsls r1, r0, #30 ; low 2 bits 00 => heap object -> defer to C++
beq .boxed
.true: ; remaining specials (e.g. true) are truthy
movs r0, #1
blx lr
.false:
movs r0, #0
blx lr
.boxed:
${this.callCPPPush("numops::toBool")}

; Same truthiness classification as _numops_toBool (see comments above), but
; this variant also consumes (ref-decrements) its argument. Only heap objects
; are ref-counted, and those go through the C++ fallback which does the decr;
; the inline non-pointer paths carry no refcount, so they need none.
_numops_toBoolDecr:
@scope _numops_toBoolDecr
cmp r0, #0 ; undefined (0) -> false
beq .false
cmp r0, #1 ; integer 0 ((0<<1)|1) -> false
beq .false
lsls r1, r0, #31 ; odd => nonzero tagged int -> true
bne .true
cmp r0, #${taggedNull} ; null -> false
beq .false
cmp r0, #${taggedFalse} ; false -> false
beq .false
cmp r0, #${taggedNaN} ; NaN -> false
beq .false
lsls r1, r0, #30 ; low 2 bits 00 => heap object -> defer to C++
beq .boxed
.true:
movs r0, #1
blx lr
.false:
movs r0, #0
blx lr
.boxed:
${this.callCPPPush("numops::toBoolDecr")}
`


Expand Down Expand Up @@ -351,7 +445,7 @@ _cmp_${op}:
// Also, cmp isn't needed when ref-counting (it ends with movs r0, r4)
r += boxedOp(`
bl numops::${op}
bl numops::toBoolDecr
bl _numops_toBoolDecr
cmp r0, #0`)
}

Expand Down
36 changes: 35 additions & 1 deletion pxtcompiler/emitter/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4157,7 +4157,41 @@ ${lbl}: .short 0xffff
function emitExpressionStatement(node: ExpressionStatement) {
emitExprAsStmt(node.expression)
}
function emitCondition(expr: Expression, inner: ir.Expr = null) {
function emitCondition(expr: Expression, inner: ir.Expr = null): ir.Expr {
// Lower `&&` / `||` used as a condition into short-circuiting jumps
// that yield 0/1 directly, instead of boxing the intermediate
// booleans and converting them back with a runtime helper.
if (!inner && !isStackMachine() && expr.kind == SK.BinaryExpression) {
const binary = expr as BinaryExpression
const op = binary.operatorToken.kind
if (op == SK.AmpersandAmpersandToken || op == SK.BarBarToken) {
const shortCircuitLabel = proc.mkLabel("lazycond")
const doneLabel = proc.mkLabel("lazycondfin")
if (op == SK.AmpersandAmpersandToken) {
proc.emitJmp(shortCircuitLabel, emitCondition(binary.left), ir.JmpMode.IfZero)
proc.emitJmp(shortCircuitLabel, emitCondition(binary.right), ir.JmpMode.IfZero)
proc.emitJmp(doneLabel, ir.numlit(1), ir.JmpMode.Always)
proc.emitLbl(shortCircuitLabel)
proc.emitJmp(doneLabel, ir.numlit(0), ir.JmpMode.Always)
} else {
proc.emitJmp(shortCircuitLabel, emitCondition(binary.left), ir.JmpMode.IfNotZero)
proc.emitJmp(shortCircuitLabel, emitCondition(binary.right), ir.JmpMode.IfNotZero)
proc.emitJmp(doneLabel, ir.numlit(0), ir.JmpMode.Always)
proc.emitLbl(shortCircuitLabel)
proc.emitJmp(doneLabel, ir.numlit(1), ir.JmpMode.Always)
}
proc.emitLbl(doneLabel)
return captureJmpValue()
}
}
// Lower `!expr` used as a condition by negating the operand's
// condition directly, avoiding a boxed boolean just to invert it.
if (!inner && !isStackMachine() && expr.kind == SK.PrefixUnaryExpression) {
const unary = expr as PrefixUnaryExpression
if (unary.operator == SK.ExclamationToken) {
return ir.rtcall("Boolean_::bang", [emitCondition(unary.operand)])
}
}
if (!inner && isThumb() && expr.kind == SK.BinaryExpression) {
let be = expr as BinaryExpression
let mapped = U.lookup(thumbCmpMap, simpleInstruction(be, be.operatorToken.kind))
Expand Down