From 038cf8b90f2f03604332ee2a825577a0a469745a Mon Sep 17 00:00:00 2001 From: Jairus Tanaka Date: Thu, 23 Apr 2026 22:30:38 -0400 Subject: [PATCH 1/4] feat: add initial tail-call support behind feature flag --- src/compiler.ts | 58 +- src/module.ts | 23 +- tests/compiler/features/tail-calls.debug.wat | 2433 +++++++++++++++++ tests/compiler/features/tail-calls.json | 6 + .../compiler/features/tail-calls.release.wat | 1513 ++++++++++ tests/compiler/features/tail-calls.ts | 27 + tests/features.json | 7 + 7 files changed, 4042 insertions(+), 25 deletions(-) create mode 100644 tests/compiler/features/tail-calls.debug.wat create mode 100644 tests/compiler/features/tail-calls.json create mode 100644 tests/compiler/features/tail-calls.release.wat create mode 100644 tests/compiler/features/tail-calls.ts diff --git a/src/compiler.ts b/src/compiler.ts index 7e4c3c9c40..11e789caa0 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -223,6 +223,8 @@ import { lowerRequiresExportRuntime } from "./bindings/js"; +import * as binaryen from "./glue/binaryen"; + /** Features enabled by default. */ export const defaultFeatures = Feature.MutableGlobals | Feature.SignExtension @@ -375,7 +377,9 @@ export const enum Constraints { /** Indicates that static data is preferred. */ PreferStatic = 1 << 4, /** Indicates that the value will become `this` of a property access or instance call. */ - IsThis = 1 << 5 + IsThis = 1 << 5, + /** Indicates that the expression is compiled for an immediate return position. */ + WillReturn = 1 << 6 } /** Runtime features to be activated by the compiler. */ @@ -2825,6 +2829,7 @@ export class Compiler extends DiagnosticEmitter { if (valueExpression) { let constraints = Constraints.ConvImplicit; if (flow.sourceFunction.is(CommonFlags.ModuleExport)) constraints |= Constraints.MustWrap; + if (!flow.isInline && this.options.hasFeature(Feature.TailCalls)) constraints |= Constraints.WillReturn; expr = this.compileExpression(valueExpression, returnType, constraints); if (!flow.canOverflow(expr, returnType)) flow.set(FlowFlags.ReturnsWrapped); @@ -2854,6 +2859,13 @@ export class Compiler extends DiagnosticEmitter { : module.br(inlineReturnLabel); } + if ( + expr && + this.options.hasFeature(Feature.TailCalls) && + getExpressionId(expr) == ExpressionId.Call && + binaryen._BinaryenCallIsReturn(expr) + ) return expr; + // Otherwise emit a normal return return expr ? this.currentType == Type.void @@ -6145,6 +6157,8 @@ export class Compiler extends DiagnosticEmitter { Constraints.ConvImplicit | Constraints.IsThis ); } + if (!this.canTailReturn(constraints, contextualType, functionInstance.signature.returnType)) + constraints &= ~Constraints.WillReturn; return this.compileCallDirect( functionInstance, expression.args, @@ -6184,6 +6198,27 @@ export class Compiler extends DiagnosticEmitter { return module.unreachable(); } + private canTailReturn(constraints: Constraints, contextType: Type, returnType: Type): bool { + if ((constraints & Constraints.WillReturn) == 0) return false; + // Tail calls are only valid as long as no result conversions are needed. + // `compileExpression` skips conversion for: + // 1. `currentType == nonnull`, and + // 2. nullable reference identity (`T | null` -> `T | null`) where conversion is a no-op. + let sameWithoutNullable = returnType.equals(contextType.nonNullableType); + let sameNullableRef = returnType.isReference && returnType.equals(contextType); + if (!sameWithoutNullable && !sameNullableRef) return false; + if ((constraints & Constraints.MustWrap) == 0) return true; + switch (returnType.kind) { + case TypeKind.I8: + case TypeKind.I16: + case TypeKind.U8: + case TypeKind.U16: + return false; + default: + return true; + } + } + /** Compiles the given arguments like a call expression according to the specified context. */ private compileCallExpressionLike( /** Called expression. */ @@ -6439,7 +6474,13 @@ export class Compiler extends DiagnosticEmitter { operands[index] = paramExpr; } assert(index == numArgumentsInclThis); - return this.makeCallDirect(instance, operands, reportNode, (constraints & Constraints.WillDrop) != 0); + return this.makeCallDirect( + instance, + operands, + reportNode, + (constraints & Constraints.WillDrop) != 0, + (constraints & Constraints.WillReturn) != 0 + ); } makeCallInline( @@ -6883,7 +6924,8 @@ export class Compiler extends DiagnosticEmitter { instance: Function, operands: ExpressionRef[] | null, reportNode: Node, - immediatelyDropped: bool = false + immediatelyDropped: bool = false, + isReturn: bool = false ): ExpressionRef { if (instance.hasDecorator(DecoratorFlags.Inline)) { if (!instance.is(CommonFlags.Overridden)) { @@ -6982,8 +7024,10 @@ export class Compiler extends DiagnosticEmitter { lastOperand ], lastOperandType.toRef()); this.operandsTostack(instance.signature, operands); - let expr = module.call(instance.internalName, operands, returnTypeRef); - if (returnType != Type.void && immediatelyDropped) { + let expr = isReturn + ? module.return_call(instance.internalName, operands, returnTypeRef) + : module.call(instance.internalName, operands, returnTypeRef); + if (!isReturn && returnType != Type.void && immediatelyDropped) { expr = module.drop(expr); this.currentType = Type.void; } else { @@ -6999,7 +7043,9 @@ export class Compiler extends DiagnosticEmitter { } if (operands) this.operandsTostack(instance.signature, operands); - let expr = module.call(instance.internalName, operands, returnType.toRef()); + let expr = isReturn + ? module.return_call(instance.internalName, operands, returnType.toRef()) + : module.call(instance.internalName, operands, returnType.toRef()); this.currentType = returnType; return expr; } diff --git a/src/module.ts b/src/module.ts index 2719f60c50..030d0e323b 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1842,35 +1842,20 @@ export class Module { index: ExpressionRef, operands: ExpressionRef[] | null, params: TypeRef, - results: TypeRef, - isReturn: bool = false + results: TypeRef ): ExpressionRef { let cStr = this.allocStringCached(tableName != null ? tableName : CommonNames.DefaultTable ); let cArr = allocPtrArray(operands); - let ret = isReturn - ? binaryen._BinaryenReturnCallIndirect( - this.ref, cStr, index, cArr, operands ? operands.length : 0, params, results - ) - : binaryen._BinaryenCallIndirect( - this.ref, cStr, index, cArr, operands ? operands.length : 0, params, results - ); + let ret = binaryen._BinaryenCallIndirect( + this.ref, cStr, index, cArr, operands ? operands.length : 0, params, results + ); binaryen._free(cArr); return ret; } - return_call_indirect( - tableName: string | null, - index: ExpressionRef, - operands: ExpressionRef[] | null, - params: TypeRef, - results: TypeRef - ): ExpressionRef { - return this.call_indirect(tableName, index, operands, params, results, true); - } - unreachable(): ExpressionRef { return binaryen._BinaryenUnreachable(this.ref); } diff --git a/tests/compiler/features/tail-calls.debug.wat b/tests/compiler/features/tail-calls.debug.wat new file mode 100644 index 0000000000..2aa8cd92c9 --- /dev/null +++ b/tests/compiler/features/tail-calls.debug.wat @@ -0,0 +1,2433 @@ +(module + (type $0 (func (param i32) (result i32))) + (type $1 (func (param i32 i32))) + (type $2 (func (param i32))) + (type $3 (func)) + (type $4 (func (param i32 i32) (result i32))) + (type $5 (func (param i32 i32 i32))) + (type $6 (func (param i32) (result i64))) + (type $7 (func (param i32 i32 i32 i32))) + (type $8 (func (param i32 i32 i64) (result i32))) + (type $9 (func (result i32))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (global $~lib/rt/itcms/total (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/threshold (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/state (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/visitCount (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/pinSpace (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/iter (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/toSpace (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/white (mut i32) (i32.const 0)) + (global $~lib/shared/runtime/Runtime.Stub i32 (i32.const 0)) + (global $~lib/shared/runtime/Runtime.Minimal i32 (i32.const 1)) + (global $~lib/shared/runtime/Runtime.Incremental i32 (i32.const 2)) + (global $~lib/rt/itcms/fromSpace (mut i32) (i32.const 0)) + (global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0)) + (global $~lib/native/ASC_LOW_MEMORY_LIMIT i32 (i32.const 0)) + (global $~lib/rt/__rtti_base i32 (i32.const 416)) + (global $~lib/memory/__data_end i32 (i32.const 440)) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 33208)) + (global $~lib/memory/__heap_base i32 (i32.const 33208)) + (memory $0 1) + (data $0 (i32.const 12) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00(\00\00\00A\00l\00l\00o\00c\00a\00t\00i\00o\00n\00 \00t\00o\00o\00 \00l\00a\00r\00g\00e\00\00\00\00\00") + (data $1 (i32.const 76) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00 \00\00\00~\00l\00i\00b\00/\00r\00t\00/\00i\00t\00c\00m\00s\00.\00t\00s\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data $2 (i32.const 144) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data $3 (i32.const 176) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data $4 (i32.const 204) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00$\00\00\00I\00n\00d\00e\00x\00 \00o\00u\00t\00 \00o\00f\00 \00r\00a\00n\00g\00e\00\00\00\00\00\00\00\00\00") + (data $5 (i32.const 268) ",\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\14\00\00\00~\00l\00i\00b\00/\00r\00t\00.\00t\00s\00\00\00\00\00\00\00\00\00") + (data $6 (i32.const 320) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data $7 (i32.const 348) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\1e\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data $8 (i32.const 416) "\05\00\00\00 \00\00\00 \00\00\00 \00\00\00\00\00\00\00 \00\00\00") + (table $0 1 1 funcref) + (elem $0 (i32.const 1)) + (export "direct" (func $features/tail-calls/direct)) + (export "directVoid" (func $features/tail-calls/directVoid)) + (export "converted" (func $features/tail-calls/converted)) + (export "nullable" (func $features/tail-calls/nullable)) + (export "memory" (memory $0)) + (start $~start) + (func $features/tail-calls/inc (param $x i32) (result i32) + local.get $x + i32.const 1 + i32.add + return + ) + (func $features/tail-calls/direct (param $x i32) (result i32) + local.get $x + return_call $features/tail-calls/inc + ) + (func $features/tail-calls/noop + ) + (func $features/tail-calls/directVoid + return_call $features/tail-calls/noop + ) + (func $features/tail-calls/converted (param $x i32) (result i64) + local.get $x + call $features/tail-calls/inc + i64.extend_i32_s + return + ) + (func $~lib/rt/itcms/Object#set:nextWithColor (param $this i32) (param $nextWithColor i32) + local.get $this + local.get $nextWithColor + i32.store offset=4 + ) + (func $~lib/rt/itcms/Object#set:prev (param $this i32) (param $prev i32) + local.get $this + local.get $prev + i32.store offset=8 + ) + (func $~lib/rt/itcms/initLazy (param $space i32) (result i32) + local.get $space + local.get $space + call $~lib/rt/itcms/Object#set:nextWithColor + local.get $space + local.get $space + call $~lib/rt/itcms/Object#set:prev + local.get $space + return + ) + (func $~lib/rt/itcms/Object#get:nextWithColor (param $this i32) (result i32) + local.get $this + i32.load offset=4 + ) + (func $~lib/rt/itcms/Object#get:next (param $this i32) (result i32) + local.get $this + call $~lib/rt/itcms/Object#get:nextWithColor + i32.const 3 + i32.const -1 + i32.xor + i32.and + return + ) + (func $~lib/rt/itcms/Object#get:color (param $this i32) (result i32) + local.get $this + call $~lib/rt/itcms/Object#get:nextWithColor + i32.const 3 + i32.and + return + ) + (func $~lib/rt/itcms/visitRoots (param $cookie i32) + (local $pn i32) + (local $iter i32) + local.get $cookie + call $~lib/rt/__visit_globals + global.get $~lib/rt/itcms/pinSpace + local.set $pn + local.get $pn + call $~lib/rt/itcms/Object#get:next + local.set $iter + loop $while-continue|0 + local.get $iter + local.get $pn + i32.ne + if + i32.const 1 + drop + local.get $iter + call $~lib/rt/itcms/Object#get:color + i32.const 3 + i32.eq + i32.eqz + if + i32.const 0 + i32.const 96 + i32.const 160 + i32.const 16 + call $~lib/builtins/abort + unreachable + end + local.get $iter + i32.const 20 + i32.add + local.get $cookie + call $~lib/rt/__visit_members + local.get $iter + call $~lib/rt/itcms/Object#get:next + local.set $iter + br $while-continue|0 + end + end + ) + (func $~lib/rt/itcms/Object#set:color (param $this i32) (param $color i32) + local.get $this + local.get $this + call $~lib/rt/itcms/Object#get:nextWithColor + i32.const 3 + i32.const -1 + i32.xor + i32.and + local.get $color + i32.or + call $~lib/rt/itcms/Object#set:nextWithColor + ) + (func $~lib/rt/itcms/Object#get:prev (param $this i32) (result i32) + local.get $this + i32.load offset=8 + ) + (func $~lib/rt/itcms/Object#set:next (param $this i32) (param $obj i32) + local.get $this + local.get $obj + local.get $this + call $~lib/rt/itcms/Object#get:nextWithColor + i32.const 3 + i32.and + i32.or + call $~lib/rt/itcms/Object#set:nextWithColor + ) + (func $~lib/rt/itcms/Object#unlink (param $this i32) + (local $next i32) + (local $prev i32) + local.get $this + call $~lib/rt/itcms/Object#get:next + local.set $next + local.get $next + i32.const 0 + i32.eq + if + i32.const 1 + drop + local.get $this + call $~lib/rt/itcms/Object#get:prev + i32.const 0 + i32.eq + if (result i32) + local.get $this + global.get $~lib/memory/__heap_base + i32.lt_u + else + i32.const 0 + end + i32.eqz + if + i32.const 0 + i32.const 96 + i32.const 128 + i32.const 18 + call $~lib/builtins/abort + unreachable + end + return + end + local.get $this + call $~lib/rt/itcms/Object#get:prev + local.set $prev + i32.const 1 + drop + local.get $prev + i32.eqz + if + i32.const 0 + i32.const 96 + i32.const 132 + i32.const 16 + call $~lib/builtins/abort + unreachable + end + local.get $next + local.get $prev + call $~lib/rt/itcms/Object#set:prev + local.get $prev + local.get $next + call $~lib/rt/itcms/Object#set:next + ) + (func $~lib/rt/itcms/Object#get:rtId (param $this i32) (result i32) + local.get $this + i32.load offset=12 + ) + (func $~lib/shared/typeinfo/Typeinfo#get:flags (param $this i32) (result i32) + local.get $this + i32.load + ) + (func $~lib/rt/__typeinfo (param $id i32) (result i32) + (local $ptr i32) + global.get $~lib/rt/__rtti_base + local.set $ptr + local.get $id + local.get $ptr + i32.load + i32.gt_u + if + i32.const 224 + i32.const 288 + i32.const 21 + i32.const 28 + call $~lib/builtins/abort + unreachable + end + local.get $ptr + i32.const 4 + i32.add + local.get $id + i32.const 4 + i32.mul + i32.add + call $~lib/shared/typeinfo/Typeinfo#get:flags + return + ) + (func $~lib/rt/itcms/Object#get:isPointerfree (param $this i32) (result i32) + (local $rtId i32) + local.get $this + call $~lib/rt/itcms/Object#get:rtId + local.set $rtId + local.get $rtId + i32.const 2 + i32.le_u + if (result i32) + i32.const 1 + else + local.get $rtId + call $~lib/rt/__typeinfo + i32.const 32 + i32.and + i32.const 0 + i32.ne + end + return + ) + (func $~lib/rt/itcms/Object#linkTo (param $this i32) (param $list i32) (param $withColor i32) + (local $prev i32) + local.get $list + call $~lib/rt/itcms/Object#get:prev + local.set $prev + local.get $this + local.get $list + local.get $withColor + i32.or + call $~lib/rt/itcms/Object#set:nextWithColor + local.get $this + local.get $prev + call $~lib/rt/itcms/Object#set:prev + local.get $prev + local.get $this + call $~lib/rt/itcms/Object#set:next + local.get $list + local.get $this + call $~lib/rt/itcms/Object#set:prev + ) + (func $~lib/rt/itcms/Object#makeGray (param $this i32) + (local $1 i32) + local.get $this + global.get $~lib/rt/itcms/iter + i32.eq + if + local.get $this + call $~lib/rt/itcms/Object#get:prev + local.tee $1 + i32.eqz + if (result i32) + i32.const 0 + i32.const 96 + i32.const 148 + i32.const 30 + call $~lib/builtins/abort + unreachable + else + local.get $1 + end + global.set $~lib/rt/itcms/iter + end + local.get $this + call $~lib/rt/itcms/Object#unlink + local.get $this + global.get $~lib/rt/itcms/toSpace + local.get $this + call $~lib/rt/itcms/Object#get:isPointerfree + if (result i32) + global.get $~lib/rt/itcms/white + i32.eqz + else + i32.const 2 + end + call $~lib/rt/itcms/Object#linkTo + ) + (func $~lib/rt/itcms/__visit (param $ptr i32) (param $cookie i32) + (local $obj i32) + local.get $ptr + i32.eqz + if + return + end + local.get $ptr + i32.const 20 + i32.sub + local.set $obj + i32.const 0 + drop + local.get $obj + call $~lib/rt/itcms/Object#get:color + global.get $~lib/rt/itcms/white + i32.eq + if + local.get $obj + call $~lib/rt/itcms/Object#makeGray + global.get $~lib/rt/itcms/visitCount + i32.const 1 + i32.add + global.set $~lib/rt/itcms/visitCount + end + ) + (func $~lib/rt/itcms/visitStack (param $cookie i32) + (local $ptr i32) + global.get $~lib/memory/__stack_pointer + local.set $ptr + loop $while-continue|0 + local.get $ptr + global.get $~lib/memory/__heap_base + i32.lt_u + if + local.get $ptr + i32.load + local.get $cookie + call $~lib/rt/itcms/__visit + local.get $ptr + i32.const 4 + i32.add + local.set $ptr + br $while-continue|0 + end + end + ) + (func $~lib/rt/common/BLOCK#get:mmInfo (param $this i32) (result i32) + local.get $this + i32.load + ) + (func $~lib/rt/itcms/Object#get:size (param $this i32) (result i32) + i32.const 4 + local.get $this + call $~lib/rt/common/BLOCK#get:mmInfo + i32.const 3 + i32.const -1 + i32.xor + i32.and + i32.add + return + ) + (func $~lib/rt/tlsf/Root#set:flMap (param $this i32) (param $flMap i32) + local.get $this + local.get $flMap + i32.store + ) + (func $~lib/rt/common/BLOCK#set:mmInfo (param $this i32) (param $mmInfo i32) + local.get $this + local.get $mmInfo + i32.store + ) + (func $~lib/rt/tlsf/Block#set:prev (param $this i32) (param $prev i32) + local.get $this + local.get $prev + i32.store offset=4 + ) + (func $~lib/rt/tlsf/Block#set:next (param $this i32) (param $next i32) + local.get $this + local.get $next + i32.store offset=8 + ) + (func $~lib/rt/tlsf/Block#get:prev (param $this i32) (result i32) + local.get $this + i32.load offset=4 + ) + (func $~lib/rt/tlsf/Block#get:next (param $this i32) (result i32) + local.get $this + i32.load offset=8 + ) + (func $~lib/rt/tlsf/Root#get:flMap (param $this i32) (result i32) + local.get $this + i32.load + ) + (func $~lib/rt/tlsf/removeBlock (param $root i32) (param $block i32) + (local $blockInfo i32) + (local $size i32) + (local $fl i32) + (local $sl i32) + (local $6 i32) + (local $7 i32) + (local $boundedSize i32) + (local $prev i32) + (local $next i32) + (local $root|11 i32) + (local $fl|12 i32) + (local $sl|13 i32) + (local $root|14 i32) + (local $fl|15 i32) + (local $sl|16 i32) + (local $head i32) + (local $root|18 i32) + (local $fl|19 i32) + (local $slMap i32) + (local $root|21 i32) + (local $fl|22 i32) + (local $slMap|23 i32) + local.get $block + call $~lib/rt/common/BLOCK#get:mmInfo + local.set $blockInfo + i32.const 1 + drop + local.get $blockInfo + i32.const 1 + i32.and + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 268 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $blockInfo + i32.const 3 + i32.const -1 + i32.xor + i32.and + local.set $size + i32.const 1 + drop + local.get $size + i32.const 12 + i32.ge_u + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 270 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $size + i32.const 256 + i32.lt_u + if + i32.const 0 + local.set $fl + local.get $size + i32.const 4 + i32.shr_u + local.set $sl + else + local.get $size + local.tee $6 + i32.const 1073741820 + local.tee $7 + local.get $6 + local.get $7 + i32.lt_u + select + local.set $boundedSize + i32.const 31 + local.get $boundedSize + i32.clz + i32.sub + local.set $fl + local.get $boundedSize + local.get $fl + i32.const 4 + i32.sub + i32.shr_u + i32.const 1 + i32.const 4 + i32.shl + i32.xor + local.set $sl + local.get $fl + i32.const 8 + i32.const 1 + i32.sub + i32.sub + local.set $fl + end + i32.const 1 + drop + local.get $fl + i32.const 23 + i32.lt_u + if (result i32) + local.get $sl + i32.const 16 + i32.lt_u + else + i32.const 0 + end + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 284 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $block + call $~lib/rt/tlsf/Block#get:prev + local.set $prev + local.get $block + call $~lib/rt/tlsf/Block#get:next + local.set $next + local.get $prev + if + local.get $prev + local.get $next + call $~lib/rt/tlsf/Block#set:next + end + local.get $next + if + local.get $next + local.get $prev + call $~lib/rt/tlsf/Block#set:prev + end + local.get $block + block $~lib/rt/tlsf/GETHEAD|inlined.0 (result i32) + local.get $root + local.set $root|11 + local.get $fl + local.set $fl|12 + local.get $sl + local.set $sl|13 + local.get $root|11 + local.get $fl|12 + i32.const 4 + i32.shl + local.get $sl|13 + i32.add + i32.const 2 + i32.shl + i32.add + i32.load offset=96 + br $~lib/rt/tlsf/GETHEAD|inlined.0 + end + i32.eq + if + local.get $root + local.set $root|14 + local.get $fl + local.set $fl|15 + local.get $sl + local.set $sl|16 + local.get $next + local.set $head + local.get $root|14 + local.get $fl|15 + i32.const 4 + i32.shl + local.get $sl|16 + i32.add + i32.const 2 + i32.shl + i32.add + local.get $head + i32.store offset=96 + local.get $next + i32.eqz + if + block $~lib/rt/tlsf/GETSL|inlined.0 (result i32) + local.get $root + local.set $root|18 + local.get $fl + local.set $fl|19 + local.get $root|18 + local.get $fl|19 + i32.const 2 + i32.shl + i32.add + i32.load offset=4 + br $~lib/rt/tlsf/GETSL|inlined.0 + end + local.set $slMap + local.get $root + local.set $root|21 + local.get $fl + local.set $fl|22 + local.get $slMap + i32.const 1 + local.get $sl + i32.shl + i32.const -1 + i32.xor + i32.and + local.tee $slMap + local.set $slMap|23 + local.get $root|21 + local.get $fl|22 + i32.const 2 + i32.shl + i32.add + local.get $slMap|23 + i32.store offset=4 + local.get $slMap + i32.eqz + if + local.get $root + local.get $root + call $~lib/rt/tlsf/Root#get:flMap + i32.const 1 + local.get $fl + i32.shl + i32.const -1 + i32.xor + i32.and + call $~lib/rt/tlsf/Root#set:flMap + end + end + end + ) + (func $~lib/rt/tlsf/insertBlock (param $root i32) (param $block i32) + (local $blockInfo i32) + (local $block|3 i32) + (local $right i32) + (local $rightInfo i32) + (local $block|6 i32) + (local $block|7 i32) + (local $left i32) + (local $leftInfo i32) + (local $size i32) + (local $fl i32) + (local $sl i32) + (local $13 i32) + (local $14 i32) + (local $boundedSize i32) + (local $root|16 i32) + (local $fl|17 i32) + (local $sl|18 i32) + (local $head i32) + (local $root|20 i32) + (local $fl|21 i32) + (local $sl|22 i32) + (local $head|23 i32) + (local $root|24 i32) + (local $fl|25 i32) + (local $root|26 i32) + (local $fl|27 i32) + (local $slMap i32) + i32.const 1 + drop + local.get $block + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 201 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $block + call $~lib/rt/common/BLOCK#get:mmInfo + local.set $blockInfo + i32.const 1 + drop + local.get $blockInfo + i32.const 1 + i32.and + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 203 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + block $~lib/rt/tlsf/GETRIGHT|inlined.0 (result i32) + local.get $block + local.set $block|3 + local.get $block|3 + i32.const 4 + i32.add + local.get $block|3 + call $~lib/rt/common/BLOCK#get:mmInfo + i32.const 3 + i32.const -1 + i32.xor + i32.and + i32.add + br $~lib/rt/tlsf/GETRIGHT|inlined.0 + end + local.set $right + local.get $right + call $~lib/rt/common/BLOCK#get:mmInfo + local.set $rightInfo + local.get $rightInfo + i32.const 1 + i32.and + if + local.get $root + local.get $right + call $~lib/rt/tlsf/removeBlock + local.get $block + local.get $blockInfo + i32.const 4 + i32.add + local.get $rightInfo + i32.const 3 + i32.const -1 + i32.xor + i32.and + i32.add + local.tee $blockInfo + call $~lib/rt/common/BLOCK#set:mmInfo + block $~lib/rt/tlsf/GETRIGHT|inlined.1 (result i32) + local.get $block + local.set $block|6 + local.get $block|6 + i32.const 4 + i32.add + local.get $block|6 + call $~lib/rt/common/BLOCK#get:mmInfo + i32.const 3 + i32.const -1 + i32.xor + i32.and + i32.add + br $~lib/rt/tlsf/GETRIGHT|inlined.1 + end + local.set $right + local.get $right + call $~lib/rt/common/BLOCK#get:mmInfo + local.set $rightInfo + end + local.get $blockInfo + i32.const 2 + i32.and + if + block $~lib/rt/tlsf/GETFREELEFT|inlined.0 (result i32) + local.get $block + local.set $block|7 + local.get $block|7 + i32.const 4 + i32.sub + i32.load + br $~lib/rt/tlsf/GETFREELEFT|inlined.0 + end + local.set $left + local.get $left + call $~lib/rt/common/BLOCK#get:mmInfo + local.set $leftInfo + i32.const 1 + drop + local.get $leftInfo + i32.const 1 + i32.and + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 221 + i32.const 16 + call $~lib/builtins/abort + unreachable + end + local.get $root + local.get $left + call $~lib/rt/tlsf/removeBlock + local.get $left + local.set $block + local.get $block + local.get $leftInfo + i32.const 4 + i32.add + local.get $blockInfo + i32.const 3 + i32.const -1 + i32.xor + i32.and + i32.add + local.tee $blockInfo + call $~lib/rt/common/BLOCK#set:mmInfo + end + local.get $right + local.get $rightInfo + i32.const 2 + i32.or + call $~lib/rt/common/BLOCK#set:mmInfo + local.get $blockInfo + i32.const 3 + i32.const -1 + i32.xor + i32.and + local.set $size + i32.const 1 + drop + local.get $size + i32.const 12 + i32.ge_u + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 233 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + i32.const 1 + drop + local.get $block + i32.const 4 + i32.add + local.get $size + i32.add + local.get $right + i32.eq + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 234 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $right + i32.const 4 + i32.sub + local.get $block + i32.store + local.get $size + i32.const 256 + i32.lt_u + if + i32.const 0 + local.set $fl + local.get $size + i32.const 4 + i32.shr_u + local.set $sl + else + local.get $size + local.tee $13 + i32.const 1073741820 + local.tee $14 + local.get $13 + local.get $14 + i32.lt_u + select + local.set $boundedSize + i32.const 31 + local.get $boundedSize + i32.clz + i32.sub + local.set $fl + local.get $boundedSize + local.get $fl + i32.const 4 + i32.sub + i32.shr_u + i32.const 1 + i32.const 4 + i32.shl + i32.xor + local.set $sl + local.get $fl + i32.const 8 + i32.const 1 + i32.sub + i32.sub + local.set $fl + end + i32.const 1 + drop + local.get $fl + i32.const 23 + i32.lt_u + if (result i32) + local.get $sl + i32.const 16 + i32.lt_u + else + i32.const 0 + end + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 251 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + block $~lib/rt/tlsf/GETHEAD|inlined.1 (result i32) + local.get $root + local.set $root|16 + local.get $fl + local.set $fl|17 + local.get $sl + local.set $sl|18 + local.get $root|16 + local.get $fl|17 + i32.const 4 + i32.shl + local.get $sl|18 + i32.add + i32.const 2 + i32.shl + i32.add + i32.load offset=96 + br $~lib/rt/tlsf/GETHEAD|inlined.1 + end + local.set $head + local.get $block + i32.const 0 + call $~lib/rt/tlsf/Block#set:prev + local.get $block + local.get $head + call $~lib/rt/tlsf/Block#set:next + local.get $head + if + local.get $head + local.get $block + call $~lib/rt/tlsf/Block#set:prev + end + local.get $root + local.set $root|20 + local.get $fl + local.set $fl|21 + local.get $sl + local.set $sl|22 + local.get $block + local.set $head|23 + local.get $root|20 + local.get $fl|21 + i32.const 4 + i32.shl + local.get $sl|22 + i32.add + i32.const 2 + i32.shl + i32.add + local.get $head|23 + i32.store offset=96 + local.get $root + local.get $root + call $~lib/rt/tlsf/Root#get:flMap + i32.const 1 + local.get $fl + i32.shl + i32.or + call $~lib/rt/tlsf/Root#set:flMap + local.get $root + local.set $root|26 + local.get $fl + local.set $fl|27 + block $~lib/rt/tlsf/GETSL|inlined.1 (result i32) + local.get $root + local.set $root|24 + local.get $fl + local.set $fl|25 + local.get $root|24 + local.get $fl|25 + i32.const 2 + i32.shl + i32.add + i32.load offset=4 + br $~lib/rt/tlsf/GETSL|inlined.1 + end + i32.const 1 + local.get $sl + i32.shl + i32.or + local.set $slMap + local.get $root|26 + local.get $fl|27 + i32.const 2 + i32.shl + i32.add + local.get $slMap + i32.store offset=4 + ) + (func $~lib/rt/tlsf/addMemory (param $root i32) (param $start i32) (param $endU64 i64) (result i32) + (local $end i32) + (local $root|4 i32) + (local $tail i32) + (local $tailInfo i32) + (local $size i32) + (local $leftSize i32) + (local $left i32) + (local $root|10 i32) + (local $tail|11 i32) + local.get $endU64 + i32.wrap_i64 + local.set $end + i32.const 1 + drop + local.get $start + i64.extend_i32_u + local.get $endU64 + i64.le_u + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 382 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $start + i32.const 4 + i32.add + i32.const 15 + i32.add + i32.const 15 + i32.const -1 + i32.xor + i32.and + i32.const 4 + i32.sub + local.set $start + local.get $end + i32.const 15 + i32.const -1 + i32.xor + i32.and + local.set $end + block $~lib/rt/tlsf/GETTAIL|inlined.0 (result i32) + local.get $root + local.set $root|4 + local.get $root|4 + i32.load offset=1568 + br $~lib/rt/tlsf/GETTAIL|inlined.0 + end + local.set $tail + i32.const 0 + local.set $tailInfo + local.get $tail + if + i32.const 1 + drop + local.get $start + local.get $tail + i32.const 4 + i32.add + i32.ge_u + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 389 + i32.const 16 + call $~lib/builtins/abort + unreachable + end + local.get $start + i32.const 16 + i32.sub + local.get $tail + i32.eq + if + local.get $start + i32.const 16 + i32.sub + local.set $start + local.get $tail + call $~lib/rt/common/BLOCK#get:mmInfo + local.set $tailInfo + else + end + else + i32.const 1 + drop + local.get $start + local.get $root + i32.const 1572 + i32.add + i32.ge_u + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 402 + i32.const 5 + call $~lib/builtins/abort + unreachable + end + end + local.get $end + local.get $start + i32.sub + local.set $size + local.get $size + i32.const 4 + i32.const 12 + i32.add + i32.const 4 + i32.add + i32.lt_u + if + i32.const 0 + return + end + local.get $size + i32.const 2 + i32.const 4 + i32.mul + i32.sub + local.set $leftSize + local.get $start + local.set $left + local.get $left + local.get $leftSize + i32.const 1 + i32.or + local.get $tailInfo + i32.const 2 + i32.and + i32.or + call $~lib/rt/common/BLOCK#set:mmInfo + local.get $left + i32.const 0 + call $~lib/rt/tlsf/Block#set:prev + local.get $left + i32.const 0 + call $~lib/rt/tlsf/Block#set:next + local.get $start + i32.const 4 + i32.add + local.get $leftSize + i32.add + local.set $tail + local.get $tail + i32.const 0 + i32.const 2 + i32.or + call $~lib/rt/common/BLOCK#set:mmInfo + local.get $root + local.set $root|10 + local.get $tail + local.set $tail|11 + local.get $root|10 + local.get $tail|11 + i32.store offset=1568 + local.get $root + local.get $left + call $~lib/rt/tlsf/insertBlock + i32.const 1 + return + ) + (func $~lib/rt/tlsf/initialize + (local $rootOffset i32) + (local $pagesBefore i32) + (local $pagesNeeded i32) + (local $root i32) + (local $root|4 i32) + (local $tail i32) + (local $fl i32) + (local $root|7 i32) + (local $fl|8 i32) + (local $slMap i32) + (local $sl i32) + (local $root|11 i32) + (local $fl|12 i32) + (local $sl|13 i32) + (local $head i32) + (local $memStart i32) + i32.const 0 + drop + global.get $~lib/memory/__heap_base + i32.const 15 + i32.add + i32.const 15 + i32.const -1 + i32.xor + i32.and + local.set $rootOffset + memory.size + local.set $pagesBefore + local.get $rootOffset + i32.const 1572 + i32.add + i32.const 65535 + i32.add + i32.const 65535 + i32.const -1 + i32.xor + i32.and + i32.const 16 + i32.shr_u + local.set $pagesNeeded + local.get $pagesNeeded + local.get $pagesBefore + i32.gt_s + if (result i32) + local.get $pagesNeeded + local.get $pagesBefore + i32.sub + memory.grow + i32.const 0 + i32.lt_s + else + i32.const 0 + end + if + unreachable + end + local.get $rootOffset + local.set $root + local.get $root + i32.const 0 + call $~lib/rt/tlsf/Root#set:flMap + local.get $root + local.set $root|4 + i32.const 0 + local.set $tail + local.get $root|4 + local.get $tail + i32.store offset=1568 + i32.const 0 + local.set $fl + loop $for-loop|0 + local.get $fl + i32.const 23 + i32.lt_u + if + local.get $root + local.set $root|7 + local.get $fl + local.set $fl|8 + i32.const 0 + local.set $slMap + local.get $root|7 + local.get $fl|8 + i32.const 2 + i32.shl + i32.add + local.get $slMap + i32.store offset=4 + i32.const 0 + local.set $sl + loop $for-loop|1 + local.get $sl + i32.const 16 + i32.lt_u + if + local.get $root + local.set $root|11 + local.get $fl + local.set $fl|12 + local.get $sl + local.set $sl|13 + i32.const 0 + local.set $head + local.get $root|11 + local.get $fl|12 + i32.const 4 + i32.shl + local.get $sl|13 + i32.add + i32.const 2 + i32.shl + i32.add + local.get $head + i32.store offset=96 + local.get $sl + i32.const 1 + i32.add + local.set $sl + br $for-loop|1 + end + end + local.get $fl + i32.const 1 + i32.add + local.set $fl + br $for-loop|0 + end + end + local.get $rootOffset + i32.const 1572 + i32.add + local.set $memStart + i32.const 0 + drop + local.get $root + local.get $memStart + memory.size + i64.extend_i32_s + i64.const 16 + i64.shl + call $~lib/rt/tlsf/addMemory + drop + local.get $root + global.set $~lib/rt/tlsf/ROOT + ) + (func $~lib/rt/tlsf/checkUsedBlock (param $ptr i32) (result i32) + (local $block i32) + local.get $ptr + i32.const 4 + i32.sub + local.set $block + local.get $ptr + i32.const 0 + i32.ne + if (result i32) + local.get $ptr + i32.const 15 + i32.and + i32.eqz + else + i32.const 0 + end + if (result i32) + local.get $block + call $~lib/rt/common/BLOCK#get:mmInfo + i32.const 1 + i32.and + i32.eqz + else + i32.const 0 + end + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 562 + i32.const 3 + call $~lib/builtins/abort + unreachable + end + local.get $block + return + ) + (func $~lib/rt/tlsf/freeBlock (param $root i32) (param $block i32) + i32.const 0 + drop + local.get $block + local.get $block + call $~lib/rt/common/BLOCK#get:mmInfo + i32.const 1 + i32.or + call $~lib/rt/common/BLOCK#set:mmInfo + local.get $root + local.get $block + call $~lib/rt/tlsf/insertBlock + ) + (func $~lib/rt/tlsf/__free (param $ptr i32) + local.get $ptr + global.get $~lib/memory/__heap_base + i32.lt_u + if + return + end + global.get $~lib/rt/tlsf/ROOT + i32.eqz + if + call $~lib/rt/tlsf/initialize + end + global.get $~lib/rt/tlsf/ROOT + local.get $ptr + call $~lib/rt/tlsf/checkUsedBlock + call $~lib/rt/tlsf/freeBlock + ) + (func $~lib/rt/itcms/free (param $obj i32) + local.get $obj + global.get $~lib/memory/__heap_base + i32.lt_u + if + local.get $obj + i32.const 0 + call $~lib/rt/itcms/Object#set:nextWithColor + local.get $obj + i32.const 0 + call $~lib/rt/itcms/Object#set:prev + else + global.get $~lib/rt/itcms/total + local.get $obj + call $~lib/rt/itcms/Object#get:size + i32.sub + global.set $~lib/rt/itcms/total + i32.const 0 + drop + local.get $obj + i32.const 4 + i32.add + call $~lib/rt/tlsf/__free + end + ) + (func $~lib/rt/itcms/step (result i32) + (local $obj i32) + (local $1 i32) + (local $black i32) + (local $from i32) + block $break|0 + block $case2|0 + block $case1|0 + block $case0|0 + global.get $~lib/rt/itcms/state + local.set $1 + local.get $1 + i32.const 0 + i32.eq + br_if $case0|0 + local.get $1 + i32.const 1 + i32.eq + br_if $case1|0 + local.get $1 + i32.const 2 + i32.eq + br_if $case2|0 + br $break|0 + end + i32.const 1 + global.set $~lib/rt/itcms/state + i32.const 0 + global.set $~lib/rt/itcms/visitCount + i32.const 0 + call $~lib/rt/itcms/visitRoots + global.get $~lib/rt/itcms/toSpace + global.set $~lib/rt/itcms/iter + global.get $~lib/rt/itcms/visitCount + i32.const 1 + i32.mul + return + end + global.get $~lib/rt/itcms/white + i32.eqz + local.set $black + global.get $~lib/rt/itcms/iter + call $~lib/rt/itcms/Object#get:next + local.set $obj + loop $while-continue|1 + local.get $obj + global.get $~lib/rt/itcms/toSpace + i32.ne + if + local.get $obj + global.set $~lib/rt/itcms/iter + local.get $obj + call $~lib/rt/itcms/Object#get:color + local.get $black + i32.ne + if + local.get $obj + local.get $black + call $~lib/rt/itcms/Object#set:color + i32.const 0 + global.set $~lib/rt/itcms/visitCount + local.get $obj + i32.const 20 + i32.add + i32.const 0 + call $~lib/rt/__visit_members + global.get $~lib/rt/itcms/visitCount + i32.const 1 + i32.mul + return + end + local.get $obj + call $~lib/rt/itcms/Object#get:next + local.set $obj + br $while-continue|1 + end + end + i32.const 0 + global.set $~lib/rt/itcms/visitCount + i32.const 0 + call $~lib/rt/itcms/visitRoots + global.get $~lib/rt/itcms/iter + call $~lib/rt/itcms/Object#get:next + local.set $obj + local.get $obj + global.get $~lib/rt/itcms/toSpace + i32.eq + if + i32.const 0 + call $~lib/rt/itcms/visitStack + global.get $~lib/rt/itcms/iter + call $~lib/rt/itcms/Object#get:next + local.set $obj + loop $while-continue|2 + local.get $obj + global.get $~lib/rt/itcms/toSpace + i32.ne + if + local.get $obj + call $~lib/rt/itcms/Object#get:color + local.get $black + i32.ne + if + local.get $obj + local.get $black + call $~lib/rt/itcms/Object#set:color + local.get $obj + i32.const 20 + i32.add + i32.const 0 + call $~lib/rt/__visit_members + end + local.get $obj + call $~lib/rt/itcms/Object#get:next + local.set $obj + br $while-continue|2 + end + end + global.get $~lib/rt/itcms/fromSpace + local.set $from + global.get $~lib/rt/itcms/toSpace + global.set $~lib/rt/itcms/fromSpace + local.get $from + global.set $~lib/rt/itcms/toSpace + local.get $black + global.set $~lib/rt/itcms/white + local.get $from + call $~lib/rt/itcms/Object#get:next + global.set $~lib/rt/itcms/iter + i32.const 2 + global.set $~lib/rt/itcms/state + end + global.get $~lib/rt/itcms/visitCount + i32.const 1 + i32.mul + return + end + global.get $~lib/rt/itcms/iter + local.set $obj + local.get $obj + global.get $~lib/rt/itcms/toSpace + i32.ne + if + local.get $obj + call $~lib/rt/itcms/Object#get:next + global.set $~lib/rt/itcms/iter + i32.const 1 + drop + local.get $obj + call $~lib/rt/itcms/Object#get:color + global.get $~lib/rt/itcms/white + i32.eqz + i32.eq + i32.eqz + if + i32.const 0 + i32.const 96 + i32.const 229 + i32.const 20 + call $~lib/builtins/abort + unreachable + end + local.get $obj + call $~lib/rt/itcms/free + i32.const 10 + return + end + global.get $~lib/rt/itcms/toSpace + global.get $~lib/rt/itcms/toSpace + call $~lib/rt/itcms/Object#set:nextWithColor + global.get $~lib/rt/itcms/toSpace + global.get $~lib/rt/itcms/toSpace + call $~lib/rt/itcms/Object#set:prev + i32.const 0 + global.set $~lib/rt/itcms/state + br $break|0 + end + i32.const 0 + return + ) + (func $~lib/rt/itcms/interrupt + (local $budget i32) + i32.const 0 + drop + i32.const 0 + drop + i32.const 1024 + i32.const 200 + i32.mul + i32.const 100 + i32.div_u + local.set $budget + loop $do-loop|0 + local.get $budget + call $~lib/rt/itcms/step + i32.sub + local.set $budget + global.get $~lib/rt/itcms/state + i32.const 0 + i32.eq + if + i32.const 0 + drop + i32.const 200 + i32.const 100 + i32.rem_u + i32.const 0 + i32.eq + drop + global.get $~lib/rt/itcms/total + i32.const 200 + i32.const 100 + i32.div_u + i32.mul + i32.const 1024 + i32.add + global.set $~lib/rt/itcms/threshold + i32.const 0 + drop + return + end + local.get $budget + i32.const 0 + i32.gt_s + br_if $do-loop|0 + end + i32.const 0 + drop + global.get $~lib/rt/itcms/total + i32.const 1024 + global.get $~lib/rt/itcms/total + global.get $~lib/rt/itcms/threshold + i32.sub + i32.const 1024 + i32.lt_u + i32.mul + i32.add + global.set $~lib/rt/itcms/threshold + i32.const 0 + drop + ) + (func $~lib/rt/tlsf/computeSize (param $size i32) (result i32) + local.get $size + i32.const 12 + i32.le_u + if (result i32) + i32.const 12 + else + local.get $size + i32.const 4 + i32.add + i32.const 15 + i32.add + i32.const 15 + i32.const -1 + i32.xor + i32.and + i32.const 4 + i32.sub + end + return + ) + (func $~lib/rt/tlsf/prepareSize (param $size i32) (result i32) + local.get $size + i32.const 1073741820 + i32.gt_u + if + i32.const 32 + i32.const 368 + i32.const 461 + i32.const 29 + call $~lib/builtins/abort + unreachable + end + local.get $size + return_call $~lib/rt/tlsf/computeSize + ) + (func $~lib/rt/tlsf/roundSize (param $size i32) (result i32) + local.get $size + i32.const 536870910 + i32.lt_u + if (result i32) + local.get $size + i32.const 1 + i32.const 27 + local.get $size + i32.clz + i32.sub + i32.shl + i32.add + i32.const 1 + i32.sub + else + local.get $size + end + return + ) + (func $~lib/rt/tlsf/searchBlock (param $root i32) (param $size i32) (result i32) + (local $fl i32) + (local $sl i32) + (local $requestSize i32) + (local $root|5 i32) + (local $fl|6 i32) + (local $slMap i32) + (local $head i32) + (local $flMap i32) + (local $root|10 i32) + (local $fl|11 i32) + (local $root|12 i32) + (local $fl|13 i32) + (local $sl|14 i32) + (local $root|15 i32) + (local $fl|16 i32) + (local $sl|17 i32) + local.get $size + i32.const 256 + i32.lt_u + if + i32.const 0 + local.set $fl + local.get $size + i32.const 4 + i32.shr_u + local.set $sl + else + local.get $size + call $~lib/rt/tlsf/roundSize + local.set $requestSize + i32.const 4 + i32.const 8 + i32.mul + i32.const 1 + i32.sub + local.get $requestSize + i32.clz + i32.sub + local.set $fl + local.get $requestSize + local.get $fl + i32.const 4 + i32.sub + i32.shr_u + i32.const 1 + i32.const 4 + i32.shl + i32.xor + local.set $sl + local.get $fl + i32.const 8 + i32.const 1 + i32.sub + i32.sub + local.set $fl + end + i32.const 1 + drop + local.get $fl + i32.const 23 + i32.lt_u + if (result i32) + local.get $sl + i32.const 16 + i32.lt_u + else + i32.const 0 + end + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 334 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + block $~lib/rt/tlsf/GETSL|inlined.2 (result i32) + local.get $root + local.set $root|5 + local.get $fl + local.set $fl|6 + local.get $root|5 + local.get $fl|6 + i32.const 2 + i32.shl + i32.add + i32.load offset=4 + br $~lib/rt/tlsf/GETSL|inlined.2 + end + i32.const 0 + i32.const -1 + i32.xor + local.get $sl + i32.shl + i32.and + local.set $slMap + i32.const 0 + local.set $head + local.get $slMap + i32.eqz + if + local.get $root + call $~lib/rt/tlsf/Root#get:flMap + i32.const 0 + i32.const -1 + i32.xor + local.get $fl + i32.const 1 + i32.add + i32.shl + i32.and + local.set $flMap + local.get $flMap + i32.eqz + if + i32.const 0 + local.set $head + else + local.get $flMap + i32.ctz + local.set $fl + block $~lib/rt/tlsf/GETSL|inlined.3 (result i32) + local.get $root + local.set $root|10 + local.get $fl + local.set $fl|11 + local.get $root|10 + local.get $fl|11 + i32.const 2 + i32.shl + i32.add + i32.load offset=4 + br $~lib/rt/tlsf/GETSL|inlined.3 + end + local.set $slMap + i32.const 1 + drop + local.get $slMap + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 347 + i32.const 18 + call $~lib/builtins/abort + unreachable + end + block $~lib/rt/tlsf/GETHEAD|inlined.2 (result i32) + local.get $root + local.set $root|12 + local.get $fl + local.set $fl|13 + local.get $slMap + i32.ctz + local.set $sl|14 + local.get $root|12 + local.get $fl|13 + i32.const 4 + i32.shl + local.get $sl|14 + i32.add + i32.const 2 + i32.shl + i32.add + i32.load offset=96 + br $~lib/rt/tlsf/GETHEAD|inlined.2 + end + local.set $head + end + else + block $~lib/rt/tlsf/GETHEAD|inlined.3 (result i32) + local.get $root + local.set $root|15 + local.get $fl + local.set $fl|16 + local.get $slMap + i32.ctz + local.set $sl|17 + local.get $root|15 + local.get $fl|16 + i32.const 4 + i32.shl + local.get $sl|17 + i32.add + i32.const 2 + i32.shl + i32.add + i32.load offset=96 + br $~lib/rt/tlsf/GETHEAD|inlined.3 + end + local.set $head + end + local.get $head + return + ) + (func $~lib/rt/tlsf/growMemory (param $root i32) (param $size i32) + (local $pagesBefore i32) + (local $root|3 i32) + (local $pagesNeeded i32) + (local $5 i32) + (local $6 i32) + (local $pagesWanted i32) + (local $pagesAfter i32) + i32.const 0 + drop + local.get $size + i32.const 256 + i32.ge_u + if + local.get $size + call $~lib/rt/tlsf/roundSize + local.set $size + end + memory.size + local.set $pagesBefore + local.get $size + i32.const 4 + local.get $pagesBefore + i32.const 16 + i32.shl + i32.const 4 + i32.sub + block $~lib/rt/tlsf/GETTAIL|inlined.1 (result i32) + local.get $root + local.set $root|3 + local.get $root|3 + i32.load offset=1568 + br $~lib/rt/tlsf/GETTAIL|inlined.1 + end + i32.ne + i32.shl + i32.add + local.set $size + local.get $size + i32.const 65535 + i32.add + i32.const 65535 + i32.const -1 + i32.xor + i32.and + i32.const 16 + i32.shr_u + local.set $pagesNeeded + local.get $pagesBefore + local.tee $5 + local.get $pagesNeeded + local.tee $6 + local.get $5 + local.get $6 + i32.gt_s + select + local.set $pagesWanted + local.get $pagesWanted + memory.grow + i32.const 0 + i32.lt_s + if + local.get $pagesNeeded + memory.grow + i32.const 0 + i32.lt_s + if + unreachable + end + end + memory.size + local.set $pagesAfter + local.get $root + local.get $pagesBefore + i32.const 16 + i32.shl + local.get $pagesAfter + i64.extend_i32_s + i64.const 16 + i64.shl + call $~lib/rt/tlsf/addMemory + drop + ) + (func $~lib/rt/tlsf/prepareBlock (param $root i32) (param $block i32) (param $size i32) + (local $blockInfo i32) + (local $remaining i32) + (local $spare i32) + (local $block|6 i32) + (local $block|7 i32) + local.get $block + call $~lib/rt/common/BLOCK#get:mmInfo + local.set $blockInfo + i32.const 1 + drop + local.get $size + i32.const 4 + i32.add + i32.const 15 + i32.and + i32.eqz + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 361 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $blockInfo + i32.const 3 + i32.const -1 + i32.xor + i32.and + local.get $size + i32.sub + local.set $remaining + local.get $remaining + i32.const 4 + i32.const 12 + i32.add + i32.ge_u + if + local.get $block + local.get $size + local.get $blockInfo + i32.const 2 + i32.and + i32.or + call $~lib/rt/common/BLOCK#set:mmInfo + local.get $block + i32.const 4 + i32.add + local.get $size + i32.add + local.set $spare + local.get $spare + local.get $remaining + i32.const 4 + i32.sub + i32.const 1 + i32.or + call $~lib/rt/common/BLOCK#set:mmInfo + local.get $root + local.get $spare + call $~lib/rt/tlsf/insertBlock + else + local.get $block + local.get $blockInfo + i32.const 1 + i32.const -1 + i32.xor + i32.and + call $~lib/rt/common/BLOCK#set:mmInfo + block $~lib/rt/tlsf/GETRIGHT|inlined.3 (result i32) + local.get $block + local.set $block|7 + local.get $block|7 + i32.const 4 + i32.add + local.get $block|7 + call $~lib/rt/common/BLOCK#get:mmInfo + i32.const 3 + i32.const -1 + i32.xor + i32.and + i32.add + br $~lib/rt/tlsf/GETRIGHT|inlined.3 + end + block $~lib/rt/tlsf/GETRIGHT|inlined.2 (result i32) + local.get $block + local.set $block|6 + local.get $block|6 + i32.const 4 + i32.add + local.get $block|6 + call $~lib/rt/common/BLOCK#get:mmInfo + i32.const 3 + i32.const -1 + i32.xor + i32.and + i32.add + br $~lib/rt/tlsf/GETRIGHT|inlined.2 + end + call $~lib/rt/common/BLOCK#get:mmInfo + i32.const 2 + i32.const -1 + i32.xor + i32.and + call $~lib/rt/common/BLOCK#set:mmInfo + end + ) + (func $~lib/rt/tlsf/allocateBlock (param $root i32) (param $size i32) (result i32) + (local $payloadSize i32) + (local $block i32) + local.get $size + call $~lib/rt/tlsf/prepareSize + local.set $payloadSize + local.get $root + local.get $payloadSize + call $~lib/rt/tlsf/searchBlock + local.set $block + local.get $block + i32.eqz + if + local.get $root + local.get $payloadSize + call $~lib/rt/tlsf/growMemory + local.get $root + local.get $payloadSize + call $~lib/rt/tlsf/searchBlock + local.set $block + i32.const 1 + drop + local.get $block + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 499 + i32.const 16 + call $~lib/builtins/abort + unreachable + end + end + i32.const 1 + drop + local.get $block + call $~lib/rt/common/BLOCK#get:mmInfo + i32.const 3 + i32.const -1 + i32.xor + i32.and + local.get $payloadSize + i32.ge_u + i32.eqz + if + i32.const 0 + i32.const 368 + i32.const 501 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $root + local.get $block + call $~lib/rt/tlsf/removeBlock + local.get $root + local.get $block + local.get $payloadSize + call $~lib/rt/tlsf/prepareBlock + i32.const 0 + drop + local.get $block + return + ) + (func $~lib/rt/tlsf/__alloc (param $size i32) (result i32) + global.get $~lib/rt/tlsf/ROOT + i32.eqz + if + call $~lib/rt/tlsf/initialize + end + global.get $~lib/rt/tlsf/ROOT + local.get $size + call $~lib/rt/tlsf/allocateBlock + i32.const 4 + i32.add + return + ) + (func $~lib/rt/itcms/Object#set:rtId (param $this i32) (param $rtId i32) + local.get $this + local.get $rtId + i32.store offset=12 + ) + (func $~lib/rt/itcms/Object#set:rtSize (param $this i32) (param $rtSize i32) + local.get $this + local.get $rtSize + i32.store offset=16 + ) + (func $~lib/rt/itcms/__new (param $size i32) (param $id i32) (result i32) + (local $obj i32) + (local $ptr i32) + local.get $size + i32.const 1073741804 + i32.ge_u + if + i32.const 32 + i32.const 96 + i32.const 261 + i32.const 31 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/rt/itcms/total + global.get $~lib/rt/itcms/threshold + i32.ge_u + if + call $~lib/rt/itcms/interrupt + end + i32.const 16 + local.get $size + i32.add + call $~lib/rt/tlsf/__alloc + i32.const 4 + i32.sub + local.set $obj + local.get $obj + local.get $id + call $~lib/rt/itcms/Object#set:rtId + local.get $obj + local.get $size + call $~lib/rt/itcms/Object#set:rtSize + local.get $obj + global.get $~lib/rt/itcms/fromSpace + global.get $~lib/rt/itcms/white + call $~lib/rt/itcms/Object#linkTo + global.get $~lib/rt/itcms/total + local.get $obj + call $~lib/rt/itcms/Object#get:size + i32.add + global.set $~lib/rt/itcms/total + local.get $obj + i32.const 20 + i32.add + local.set $ptr + local.get $ptr + i32.const 0 + local.get $size + memory.fill + local.get $ptr + return + ) + (func $features/tail-calls/maybeBox (param $x i32) (result i32) + local.get $x + if (result i32) + i32.const 0 + call $features/tail-calls/Box#constructor + else + i32.const 0 + end + return + ) + (func $features/tail-calls/nullable (param $x i32) (result i32) + local.get $x + return_call $features/tail-calls/maybeBox + ) + (func $~lib/rt/__visit_globals (param $0 i32) + (local $1 i32) + i32.const 224 + local.get $0 + call $~lib/rt/itcms/__visit + i32.const 32 + local.get $0 + call $~lib/rt/itcms/__visit + ) + (func $~lib/arraybuffer/ArrayBufferView~visit (param $0 i32) (param $1 i32) + (local $2 i32) + local.get $0 + local.get $1 + call $~lib/object/Object~visit + local.get $0 + i32.load + local.get $1 + call $~lib/rt/itcms/__visit + ) + (func $~lib/object/Object~visit (param $0 i32) (param $1 i32) + ) + (func $~lib/rt/__visit_members (param $0 i32) (param $1 i32) + block $invalid + block $features/tail-calls/Box + block $~lib/arraybuffer/ArrayBufferView + block $~lib/string/String + block $~lib/arraybuffer/ArrayBuffer + block $~lib/object/Object + local.get $0 + i32.const 8 + i32.sub + i32.load + br_table $~lib/object/Object $~lib/arraybuffer/ArrayBuffer $~lib/string/String $~lib/arraybuffer/ArrayBufferView $features/tail-calls/Box $invalid + end + return + end + return + end + return + end + local.get $0 + local.get $1 + call $~lib/arraybuffer/ArrayBufferView~visit + return + end + return + end + unreachable + ) + (func $~start + memory.size + i32.const 16 + i32.shl + global.get $~lib/memory/__heap_base + i32.sub + i32.const 1 + i32.shr_u + global.set $~lib/rt/itcms/threshold + i32.const 144 + call $~lib/rt/itcms/initLazy + global.set $~lib/rt/itcms/pinSpace + i32.const 176 + call $~lib/rt/itcms/initLazy + global.set $~lib/rt/itcms/toSpace + i32.const 320 + call $~lib/rt/itcms/initLazy + global.set $~lib/rt/itcms/fromSpace + ) + (func $~stack_check + global.get $~lib/memory/__stack_pointer + global.get $~lib/memory/__data_end + i32.lt_s + if + i32.const 33232 + i32.const 33280 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + ) + (func $features/tail-calls/Box#constructor (param $this i32) (result i32) + (local $1 i32) + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i64.const 0 + i64.store + local.get $this + i32.eqz + if + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.const 4 + call $~lib/rt/itcms/__new + local.tee $this + i32.store + end + global.get $~lib/memory/__stack_pointer + local.get $this + local.set $1 + global.get $~lib/memory/__stack_pointer + local.get $1 + i32.store offset=4 + local.get $1 + call $~lib/object/Object#constructor + local.tee $this + i32.store + local.get $this + local.set $1 + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $1 + ) + (func $~lib/object/Object#constructor (param $this i32) (result i32) + (local $1 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + local.get $this + i32.eqz + if + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.const 0 + call $~lib/rt/itcms/__new + local.tee $this + i32.store + end + local.get $this + local.set $1 + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $1 + ) +) diff --git a/tests/compiler/features/tail-calls.json b/tests/compiler/features/tail-calls.json new file mode 100644 index 0000000000..ba47225711 --- /dev/null +++ b/tests/compiler/features/tail-calls.json @@ -0,0 +1,6 @@ +{ + "features": [ + "tail-calls" + ], + "skipInstantiate": true +} diff --git a/tests/compiler/features/tail-calls.release.wat b/tests/compiler/features/tail-calls.release.wat new file mode 100644 index 0000000000..9312da098d --- /dev/null +++ b/tests/compiler/features/tail-calls.release.wat @@ -0,0 +1,1513 @@ +(module + (type $0 (func (param i32) (result i32))) + (type $1 (func)) + (type $2 (func (param i32))) + (type $3 (func (param i32 i32))) + (type $4 (func (param i32) (result i64))) + (type $5 (func (param i32 i32 i32 i32))) + (type $6 (func (param i32 i32 i64))) + (type $7 (func (result i32))) + (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (global $~lib/rt/itcms/total (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/threshold (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/state (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/visitCount (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/pinSpace (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/iter (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/toSpace (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/white (mut i32) (i32.const 0)) + (global $~lib/rt/itcms/fromSpace (mut i32) (i32.const 0)) + (global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0)) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 34232)) + (memory $0 1) + (data $0 (i32.const 1036) "<") + (data $0.1 (i32.const 1048) "\02\00\00\00(\00\00\00A\00l\00l\00o\00c\00a\00t\00i\00o\00n\00 \00t\00o\00o\00 \00l\00a\00r\00g\00e") + (data $1 (i32.const 1100) "<") + (data $1.1 (i32.const 1112) "\02\00\00\00 \00\00\00~\00l\00i\00b\00/\00r\00t\00/\00i\00t\00c\00m\00s\00.\00t\00s") + (data $4 (i32.const 1228) "<") + (data $4.1 (i32.const 1240) "\02\00\00\00$\00\00\00I\00n\00d\00e\00x\00 \00o\00u\00t\00 \00o\00f\00 \00r\00a\00n\00g\00e") + (data $5 (i32.const 1292) ",") + (data $5.1 (i32.const 1304) "\02\00\00\00\14\00\00\00~\00l\00i\00b\00/\00r\00t\00.\00t\00s") + (data $7 (i32.const 1372) "<") + (data $7.1 (i32.const 1384) "\02\00\00\00\1e\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s") + (data $8 (i32.const 1440) "\05\00\00\00 \00\00\00 \00\00\00 \00\00\00\00\00\00\00 ") + (export "direct" (func $features/tail-calls/direct)) + (export "directVoid" (func $features/tail-calls/directVoid)) + (export "converted" (func $features/tail-calls/converted)) + (export "nullable" (func $features/tail-calls/nullable)) + (export "memory" (memory $0)) + (start $~start) + (func $features/tail-calls/direct (param $0 i32) (result i32) + local.get $0 + i32.const 1 + i32.add + ) + (func $features/tail-calls/directVoid + ) + (func $features/tail-calls/converted (param $0 i32) (result i64) + local.get $0 + i32.const 1 + i32.add + i64.extend_i32_s + ) + (func $~lib/rt/itcms/visitRoots + (local $0 i32) + (local $1 i32) + i32.const 1248 + call $~lib/rt/itcms/__visit + i32.const 1056 + call $~lib/rt/itcms/__visit + global.get $~lib/rt/itcms/pinSpace + local.tee $1 + i32.load offset=4 + i32.const -4 + i32.and + local.set $0 + loop $while-continue|0 + local.get $0 + local.get $1 + i32.ne + if + local.get $0 + i32.load offset=4 + i32.const 3 + i32.and + i32.const 3 + i32.ne + if + i32.const 0 + i32.const 1120 + i32.const 160 + i32.const 16 + call $~lib/builtins/abort + unreachable + end + local.get $0 + i32.const 20 + i32.add + call $~lib/rt/__visit_members + local.get $0 + i32.load offset=4 + i32.const -4 + i32.and + local.set $0 + br $while-continue|0 + end + end + ) + (func $~lib/rt/itcms/__visit (param $0 i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + local.get $0 + i32.eqz + if + return + end + global.get $~lib/rt/itcms/white + local.get $0 + i32.const 20 + i32.sub + local.tee $1 + i32.load offset=4 + i32.const 3 + i32.and + i32.eq + if + local.get $1 + global.get $~lib/rt/itcms/iter + i32.eq + if + local.get $1 + i32.load offset=8 + local.tee $0 + i32.eqz + if + i32.const 0 + i32.const 1120 + i32.const 148 + i32.const 30 + call $~lib/builtins/abort + unreachable + end + local.get $0 + global.set $~lib/rt/itcms/iter + end + block $__inlined_func$~lib/rt/itcms/Object#unlink$113 + local.get $1 + i32.load offset=4 + i32.const -4 + i32.and + local.tee $0 + i32.eqz + if + local.get $1 + i32.load offset=8 + i32.eqz + local.get $1 + i32.const 34232 + i32.lt_u + i32.and + i32.eqz + if + i32.const 0 + i32.const 1120 + i32.const 128 + i32.const 18 + call $~lib/builtins/abort + unreachable + end + br $__inlined_func$~lib/rt/itcms/Object#unlink$113 + end + local.get $1 + i32.load offset=8 + local.tee $2 + i32.eqz + if + i32.const 0 + i32.const 1120 + i32.const 132 + i32.const 16 + call $~lib/builtins/abort + unreachable + end + local.get $0 + local.get $2 + i32.store offset=8 + local.get $2 + local.get $0 + local.get $2 + i32.load offset=4 + i32.const 3 + i32.and + i32.or + i32.store offset=4 + end + global.get $~lib/rt/itcms/toSpace + local.set $2 + local.get $1 + i32.load offset=12 + local.tee $0 + i32.const 2 + i32.le_u + if (result i32) + i32.const 1 + else + local.get $0 + i32.const 1440 + i32.load + i32.gt_u + if + i32.const 1248 + i32.const 1312 + i32.const 21 + i32.const 28 + call $~lib/builtins/abort + unreachable + end + local.get $0 + i32.const 2 + i32.shl + i32.const 1444 + i32.add + i32.load + i32.const 32 + i32.and + end + local.set $3 + local.get $2 + i32.load offset=8 + local.set $0 + local.get $1 + global.get $~lib/rt/itcms/white + i32.eqz + i32.const 2 + local.get $3 + select + local.get $2 + i32.or + i32.store offset=4 + local.get $1 + local.get $0 + i32.store offset=8 + local.get $0 + local.get $1 + local.get $0 + i32.load offset=4 + i32.const 3 + i32.and + i32.or + i32.store offset=4 + local.get $2 + local.get $1 + i32.store offset=8 + global.get $~lib/rt/itcms/visitCount + i32.const 1 + i32.add + global.set $~lib/rt/itcms/visitCount + end + ) + (func $~lib/rt/tlsf/removeBlock (param $0 i32) (param $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + local.get $1 + i32.load + local.tee $3 + i32.const 1 + i32.and + i32.eqz + if + i32.const 0 + i32.const 1392 + i32.const 268 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $3 + i32.const -4 + i32.and + local.tee $3 + i32.const 12 + i32.lt_u + if + i32.const 0 + i32.const 1392 + i32.const 270 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $3 + i32.const 256 + i32.lt_u + if (result i32) + local.get $3 + i32.const 4 + i32.shr_u + else + i32.const 31 + i32.const 1073741820 + local.get $3 + local.get $3 + i32.const 1073741820 + i32.ge_u + select + local.tee $3 + i32.clz + i32.sub + local.tee $4 + i32.const 7 + i32.sub + local.set $2 + local.get $3 + local.get $4 + i32.const 4 + i32.sub + i32.shr_u + i32.const 16 + i32.xor + end + local.tee $3 + i32.const 16 + i32.lt_u + local.get $2 + i32.const 23 + i32.lt_u + i32.and + i32.eqz + if + i32.const 0 + i32.const 1392 + i32.const 284 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $1 + i32.load offset=8 + local.set $5 + local.get $1 + i32.load offset=4 + local.tee $4 + if + local.get $4 + local.get $5 + i32.store offset=8 + end + local.get $5 + if + local.get $5 + local.get $4 + i32.store offset=4 + end + local.get $1 + local.get $0 + local.get $2 + i32.const 4 + i32.shl + local.get $3 + i32.add + i32.const 2 + i32.shl + i32.add + local.tee $1 + i32.load offset=96 + i32.eq + if + local.get $1 + local.get $5 + i32.store offset=96 + local.get $5 + i32.eqz + if + local.get $0 + local.get $2 + i32.const 2 + i32.shl + i32.add + local.tee $1 + i32.load offset=4 + i32.const -2 + local.get $3 + i32.rotl + i32.and + local.set $3 + local.get $1 + local.get $3 + i32.store offset=4 + local.get $3 + i32.eqz + if + local.get $0 + local.get $0 + i32.load + i32.const -2 + local.get $2 + i32.rotl + i32.and + i32.store + end + end + end + ) + (func $~lib/rt/tlsf/insertBlock (param $0 i32) (param $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + local.get $1 + i32.eqz + if + i32.const 0 + i32.const 1392 + i32.const 201 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $1 + i32.load + local.tee $3 + i32.const 1 + i32.and + i32.eqz + if + i32.const 0 + i32.const 1392 + i32.const 203 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $1 + i32.const 4 + i32.add + local.get $1 + i32.load + i32.const -4 + i32.and + i32.add + local.tee $4 + i32.load + local.tee $2 + i32.const 1 + i32.and + if + local.get $0 + local.get $4 + call $~lib/rt/tlsf/removeBlock + local.get $1 + local.get $3 + i32.const 4 + i32.add + local.get $2 + i32.const -4 + i32.and + i32.add + local.tee $3 + i32.store + local.get $1 + i32.const 4 + i32.add + local.get $1 + i32.load + i32.const -4 + i32.and + i32.add + local.tee $4 + i32.load + local.set $2 + end + local.get $3 + i32.const 2 + i32.and + if + local.get $1 + i32.const 4 + i32.sub + i32.load + local.tee $1 + i32.load + local.tee $6 + i32.const 1 + i32.and + i32.eqz + if + i32.const 0 + i32.const 1392 + i32.const 221 + i32.const 16 + call $~lib/builtins/abort + unreachable + end + local.get $0 + local.get $1 + call $~lib/rt/tlsf/removeBlock + local.get $1 + local.get $6 + i32.const 4 + i32.add + local.get $3 + i32.const -4 + i32.and + i32.add + local.tee $3 + i32.store + end + local.get $4 + local.get $2 + i32.const 2 + i32.or + i32.store + local.get $3 + i32.const -4 + i32.and + local.tee $2 + i32.const 12 + i32.lt_u + if + i32.const 0 + i32.const 1392 + i32.const 233 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $4 + local.get $1 + i32.const 4 + i32.add + local.get $2 + i32.add + i32.ne + if + i32.const 0 + i32.const 1392 + i32.const 234 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $4 + i32.const 4 + i32.sub + local.get $1 + i32.store + local.get $2 + i32.const 256 + i32.lt_u + if (result i32) + local.get $2 + i32.const 4 + i32.shr_u + else + i32.const 31 + i32.const 1073741820 + local.get $2 + local.get $2 + i32.const 1073741820 + i32.ge_u + select + local.tee $2 + i32.clz + i32.sub + local.tee $3 + i32.const 7 + i32.sub + local.set $5 + local.get $2 + local.get $3 + i32.const 4 + i32.sub + i32.shr_u + i32.const 16 + i32.xor + end + local.tee $2 + i32.const 16 + i32.lt_u + local.get $5 + i32.const 23 + i32.lt_u + i32.and + i32.eqz + if + i32.const 0 + i32.const 1392 + i32.const 251 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $0 + local.get $5 + i32.const 4 + i32.shl + local.get $2 + i32.add + i32.const 2 + i32.shl + i32.add + i32.load offset=96 + local.set $3 + local.get $1 + i32.const 0 + i32.store offset=4 + local.get $1 + local.get $3 + i32.store offset=8 + local.get $3 + if + local.get $3 + local.get $1 + i32.store offset=4 + end + local.get $0 + local.get $5 + i32.const 4 + i32.shl + local.get $2 + i32.add + i32.const 2 + i32.shl + i32.add + local.get $1 + i32.store offset=96 + local.get $0 + local.get $0 + i32.load + i32.const 1 + local.get $5 + i32.shl + i32.or + i32.store + local.get $0 + local.get $5 + i32.const 2 + i32.shl + i32.add + local.tee $0 + local.get $0 + i32.load offset=4 + i32.const 1 + local.get $2 + i32.shl + i32.or + i32.store offset=4 + ) + (func $~lib/rt/tlsf/addMemory (param $0 i32) (param $1 i32) (param $2 i64) + (local $3 i32) + (local $4 i32) + (local $5 i32) + local.get $2 + local.get $1 + i64.extend_i32_u + i64.lt_u + if + i32.const 0 + i32.const 1392 + i32.const 382 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $1 + i32.const 19 + i32.add + i32.const -16 + i32.and + i32.const 4 + i32.sub + local.set $1 + local.get $0 + i32.load offset=1568 + local.tee $3 + if + local.get $3 + i32.const 4 + i32.add + local.get $1 + i32.gt_u + if + i32.const 0 + i32.const 1392 + i32.const 389 + i32.const 16 + call $~lib/builtins/abort + unreachable + end + local.get $3 + local.get $1 + i32.const 16 + i32.sub + local.tee $5 + i32.eq + if + local.get $3 + i32.load + local.set $4 + local.get $5 + local.set $1 + end + else + local.get $0 + i32.const 1572 + i32.add + local.get $1 + i32.gt_u + if + i32.const 0 + i32.const 1392 + i32.const 402 + i32.const 5 + call $~lib/builtins/abort + unreachable + end + end + local.get $2 + i32.wrap_i64 + i32.const -16 + i32.and + local.get $1 + i32.sub + local.tee $3 + i32.const 20 + i32.lt_u + if + return + end + local.get $1 + local.get $4 + i32.const 2 + i32.and + local.get $3 + i32.const 8 + i32.sub + local.tee $3 + i32.const 1 + i32.or + i32.or + i32.store + local.get $1 + i32.const 0 + i32.store offset=4 + local.get $1 + i32.const 0 + i32.store offset=8 + local.get $1 + i32.const 4 + i32.add + local.get $3 + i32.add + local.tee $3 + i32.const 2 + i32.store + local.get $0 + local.get $3 + i32.store offset=1568 + local.get $0 + local.get $1 + call $~lib/rt/tlsf/insertBlock + ) + (func $~lib/rt/tlsf/initialize + (local $0 i32) + (local $1 i32) + memory.size + local.tee $1 + i32.const 0 + i32.le_s + if (result i32) + i32.const 1 + local.get $1 + i32.sub + memory.grow + i32.const 0 + i32.lt_s + else + i32.const 0 + end + if + unreachable + end + i32.const 34240 + i32.const 0 + i32.store + i32.const 35808 + i32.const 0 + i32.store + loop $for-loop|0 + local.get $0 + i32.const 23 + i32.lt_u + if + local.get $0 + i32.const 2 + i32.shl + i32.const 34240 + i32.add + i32.const 0 + i32.store offset=4 + i32.const 0 + local.set $1 + loop $for-loop|1 + local.get $1 + i32.const 16 + i32.lt_u + if + local.get $0 + i32.const 4 + i32.shl + local.get $1 + i32.add + i32.const 2 + i32.shl + i32.const 34240 + i32.add + i32.const 0 + i32.store offset=96 + local.get $1 + i32.const 1 + i32.add + local.set $1 + br $for-loop|1 + end + end + local.get $0 + i32.const 1 + i32.add + local.set $0 + br $for-loop|0 + end + end + i32.const 34240 + i32.const 35812 + memory.size + i64.extend_i32_s + i64.const 16 + i64.shl + call $~lib/rt/tlsf/addMemory + i32.const 34240 + global.set $~lib/rt/tlsf/ROOT + ) + (func $~lib/rt/itcms/step (result i32) + (local $0 i32) + (local $1 i32) + (local $2 i32) + block $break|0 + block $case2|0 + block $case1|0 + block $case0|0 + global.get $~lib/rt/itcms/state + br_table $case0|0 $case1|0 $case2|0 $break|0 + end + i32.const 1 + global.set $~lib/rt/itcms/state + i32.const 0 + global.set $~lib/rt/itcms/visitCount + call $~lib/rt/itcms/visitRoots + global.get $~lib/rt/itcms/toSpace + global.set $~lib/rt/itcms/iter + global.get $~lib/rt/itcms/visitCount + return + end + global.get $~lib/rt/itcms/white + i32.eqz + local.set $1 + global.get $~lib/rt/itcms/iter + i32.load offset=4 + i32.const -4 + i32.and + local.set $0 + loop $while-continue|1 + local.get $0 + global.get $~lib/rt/itcms/toSpace + i32.ne + if + local.get $0 + global.set $~lib/rt/itcms/iter + local.get $1 + local.get $0 + i32.load offset=4 + local.tee $2 + i32.const 3 + i32.and + i32.ne + if + local.get $0 + local.get $2 + i32.const -4 + i32.and + local.get $1 + i32.or + i32.store offset=4 + i32.const 0 + global.set $~lib/rt/itcms/visitCount + local.get $0 + i32.const 20 + i32.add + call $~lib/rt/__visit_members + global.get $~lib/rt/itcms/visitCount + return + end + local.get $0 + i32.load offset=4 + i32.const -4 + i32.and + local.set $0 + br $while-continue|1 + end + end + i32.const 0 + global.set $~lib/rt/itcms/visitCount + call $~lib/rt/itcms/visitRoots + global.get $~lib/rt/itcms/toSpace + global.get $~lib/rt/itcms/iter + i32.load offset=4 + i32.const -4 + i32.and + i32.eq + if + global.get $~lib/memory/__stack_pointer + local.set $0 + loop $while-continue|0 + local.get $0 + i32.const 34232 + i32.lt_u + if + local.get $0 + i32.load + call $~lib/rt/itcms/__visit + local.get $0 + i32.const 4 + i32.add + local.set $0 + br $while-continue|0 + end + end + global.get $~lib/rt/itcms/iter + i32.load offset=4 + i32.const -4 + i32.and + local.set $0 + loop $while-continue|2 + local.get $0 + global.get $~lib/rt/itcms/toSpace + i32.ne + if + local.get $1 + local.get $0 + i32.load offset=4 + local.tee $2 + i32.const 3 + i32.and + i32.ne + if + local.get $0 + local.get $2 + i32.const -4 + i32.and + local.get $1 + i32.or + i32.store offset=4 + local.get $0 + i32.const 20 + i32.add + call $~lib/rt/__visit_members + end + local.get $0 + i32.load offset=4 + i32.const -4 + i32.and + local.set $0 + br $while-continue|2 + end + end + global.get $~lib/rt/itcms/fromSpace + local.set $0 + global.get $~lib/rt/itcms/toSpace + global.set $~lib/rt/itcms/fromSpace + local.get $0 + global.set $~lib/rt/itcms/toSpace + local.get $1 + global.set $~lib/rt/itcms/white + local.get $0 + i32.load offset=4 + i32.const -4 + i32.and + global.set $~lib/rt/itcms/iter + i32.const 2 + global.set $~lib/rt/itcms/state + end + global.get $~lib/rt/itcms/visitCount + return + end + global.get $~lib/rt/itcms/iter + local.tee $0 + global.get $~lib/rt/itcms/toSpace + i32.ne + if + local.get $0 + i32.load offset=4 + local.tee $1 + i32.const -4 + i32.and + global.set $~lib/rt/itcms/iter + global.get $~lib/rt/itcms/white + i32.eqz + local.get $1 + i32.const 3 + i32.and + i32.ne + if + i32.const 0 + i32.const 1120 + i32.const 229 + i32.const 20 + call $~lib/builtins/abort + unreachable + end + local.get $0 + i32.const 34232 + i32.lt_u + if + local.get $0 + i32.const 0 + i32.store offset=4 + local.get $0 + i32.const 0 + i32.store offset=8 + else + global.get $~lib/rt/itcms/total + local.get $0 + i32.load + i32.const -4 + i32.and + i32.const 4 + i32.add + i32.sub + global.set $~lib/rt/itcms/total + local.get $0 + i32.const 4 + i32.add + local.tee $0 + i32.const 34232 + i32.ge_u + if + global.get $~lib/rt/tlsf/ROOT + i32.eqz + if + call $~lib/rt/tlsf/initialize + end + global.get $~lib/rt/tlsf/ROOT + local.get $0 + i32.const 4 + i32.sub + local.set $2 + local.get $0 + i32.const 15 + i32.and + i32.const 1 + local.get $0 + select + if (result i32) + i32.const 1 + else + local.get $2 + i32.load + i32.const 1 + i32.and + end + if + i32.const 0 + i32.const 1392 + i32.const 562 + i32.const 3 + call $~lib/builtins/abort + unreachable + end + local.get $2 + local.get $2 + i32.load + i32.const 1 + i32.or + i32.store + local.get $2 + call $~lib/rt/tlsf/insertBlock + end + end + i32.const 10 + return + end + global.get $~lib/rt/itcms/toSpace + global.get $~lib/rt/itcms/toSpace + i32.store offset=4 + global.get $~lib/rt/itcms/toSpace + global.get $~lib/rt/itcms/toSpace + i32.store offset=8 + i32.const 0 + global.set $~lib/rt/itcms/state + end + i32.const 0 + ) + (func $~lib/rt/tlsf/searchBlock (param $0 i32) (result i32) + (local $1 i32) + (local $2 i32) + local.get $0 + i32.load offset=4 + i32.const -2 + i32.and + local.tee $1 + if (result i32) + local.get $0 + local.get $1 + i32.ctz + i32.const 2 + i32.shl + i32.add + i32.load offset=96 + else + local.get $0 + i32.load + i32.const -2 + i32.and + local.tee $1 + if (result i32) + local.get $0 + local.get $1 + i32.ctz + local.tee $2 + i32.const 2 + i32.shl + i32.add + i32.load offset=4 + local.tee $1 + i32.eqz + if + i32.const 0 + i32.const 1392 + i32.const 347 + i32.const 18 + call $~lib/builtins/abort + unreachable + end + local.get $0 + local.get $1 + i32.ctz + local.get $2 + i32.const 4 + i32.shl + i32.add + i32.const 2 + i32.shl + i32.add + i32.load offset=96 + else + i32.const 0 + end + end + ) + (func $~lib/rt/itcms/__new (param $0 i32) (result i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + global.get $~lib/rt/itcms/total + global.get $~lib/rt/itcms/threshold + i32.ge_u + if + block $__inlined_func$~lib/rt/itcms/interrupt$72 + i32.const 2048 + local.set $1 + loop $do-loop|0 + local.get $1 + call $~lib/rt/itcms/step + i32.sub + local.set $1 + global.get $~lib/rt/itcms/state + i32.eqz + if + global.get $~lib/rt/itcms/total + i32.const 1 + i32.shl + i32.const 1024 + i32.add + global.set $~lib/rt/itcms/threshold + br $__inlined_func$~lib/rt/itcms/interrupt$72 + end + local.get $1 + i32.const 0 + i32.gt_s + br_if $do-loop|0 + end + global.get $~lib/rt/itcms/total + global.get $~lib/rt/itcms/total + global.get $~lib/rt/itcms/threshold + i32.sub + i32.const 1024 + i32.lt_u + i32.const 10 + i32.shl + i32.add + global.set $~lib/rt/itcms/threshold + end + end + global.get $~lib/rt/tlsf/ROOT + i32.eqz + if + call $~lib/rt/tlsf/initialize + end + global.get $~lib/rt/tlsf/ROOT + local.tee $2 + call $~lib/rt/tlsf/searchBlock + local.tee $1 + i32.eqz + if + memory.size + local.tee $1 + i32.const 4 + local.get $2 + i32.load offset=1568 + local.get $1 + i32.const 16 + i32.shl + i32.const 4 + i32.sub + i32.ne + i32.shl + i32.const 65563 + i32.add + i32.const -65536 + i32.and + i32.const 16 + i32.shr_u + local.tee $3 + local.get $1 + local.get $3 + i32.gt_s + select + memory.grow + i32.const 0 + i32.lt_s + if + local.get $3 + memory.grow + i32.const 0 + i32.lt_s + if + unreachable + end + end + local.get $2 + local.get $1 + i32.const 16 + i32.shl + memory.size + i64.extend_i32_s + i64.const 16 + i64.shl + call $~lib/rt/tlsf/addMemory + local.get $2 + call $~lib/rt/tlsf/searchBlock + local.tee $1 + i32.eqz + if + i32.const 0 + i32.const 1392 + i32.const 499 + i32.const 16 + call $~lib/builtins/abort + unreachable + end + end + local.get $1 + i32.load + i32.const -4 + i32.and + i32.const 28 + i32.lt_u + if + i32.const 0 + i32.const 1392 + i32.const 501 + i32.const 14 + call $~lib/builtins/abort + unreachable + end + local.get $2 + local.get $1 + call $~lib/rt/tlsf/removeBlock + local.get $1 + i32.load + local.tee $3 + i32.const -4 + i32.and + i32.const 28 + i32.sub + local.tee $4 + i32.const 16 + i32.ge_u + if + local.get $1 + local.get $3 + i32.const 2 + i32.and + i32.const 28 + i32.or + i32.store + local.get $1 + i32.const 32 + i32.add + local.tee $3 + local.get $4 + i32.const 4 + i32.sub + i32.const 1 + i32.or + i32.store + local.get $2 + local.get $3 + call $~lib/rt/tlsf/insertBlock + else + local.get $1 + local.get $3 + i32.const -2 + i32.and + i32.store + local.get $1 + i32.const 4 + i32.add + local.get $1 + i32.load + i32.const -4 + i32.and + i32.add + local.tee $2 + local.get $2 + i32.load + i32.const -3 + i32.and + i32.store + end + local.get $1 + local.get $0 + i32.store offset=12 + local.get $1 + i32.const 0 + i32.store offset=16 + global.get $~lib/rt/itcms/fromSpace + local.tee $0 + i32.load offset=8 + local.set $2 + local.get $1 + local.get $0 + global.get $~lib/rt/itcms/white + i32.or + i32.store offset=4 + local.get $1 + local.get $2 + i32.store offset=8 + local.get $2 + local.get $1 + local.get $2 + i32.load offset=4 + i32.const 3 + i32.and + i32.or + i32.store offset=4 + local.get $0 + local.get $1 + i32.store offset=8 + global.get $~lib/rt/itcms/total + local.get $1 + i32.load + i32.const -4 + i32.and + i32.const 4 + i32.add + i32.add + global.set $~lib/rt/itcms/total + local.get $1 + i32.const 20 + i32.add + local.tee $0 + i32.const 0 + i32.const 0 + memory.fill + local.get $0 + ) + (func $features/tail-calls/nullable (param $0 i32) (result i32) + (local $1 i32) + block $folding-inner0 + local.get $0 + if + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1464 + i32.lt_s + br_if $folding-inner0 + global.get $~lib/memory/__stack_pointer + i64.const 0 + i64.store + global.get $~lib/memory/__stack_pointer + i32.const 4 + call $~lib/rt/itcms/__new + local.tee $0 + i32.store + global.get $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store offset=4 + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1464 + i32.lt_s + br_if $folding-inner0 + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + local.get $0 + i32.eqz + if + global.get $~lib/memory/__stack_pointer + i32.const 0 + call $~lib/rt/itcms/__new + local.tee $0 + i32.store + end + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + local.get $0 + i32.store + global.get $~lib/memory/__stack_pointer + i32.const 8 + i32.add + global.set $~lib/memory/__stack_pointer + else + i32.const 0 + local.set $0 + end + local.get $0 + return + end + i32.const 34256 + i32.const 34304 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + ) + (func $~lib/rt/__visit_members (param $0 i32) + block $invalid + block $features/tail-calls/Box + block $~lib/arraybuffer/ArrayBufferView + block $~lib/string/String + block $~lib/arraybuffer/ArrayBuffer + block $~lib/object/Object + local.get $0 + i32.const 8 + i32.sub + i32.load + br_table $~lib/object/Object $~lib/arraybuffer/ArrayBuffer $~lib/string/String $~lib/arraybuffer/ArrayBufferView $features/tail-calls/Box $invalid + end + return + end + return + end + return + end + local.get $0 + i32.load + call $~lib/rt/itcms/__visit + return + end + return + end + unreachable + ) + (func $~start + memory.size + i32.const 16 + i32.shl + i32.const 34232 + i32.sub + i32.const 1 + i32.shr_u + global.set $~lib/rt/itcms/threshold + i32.const 1172 + i32.const 1168 + i32.store + i32.const 1176 + i32.const 1168 + i32.store + i32.const 1168 + global.set $~lib/rt/itcms/pinSpace + i32.const 1204 + i32.const 1200 + i32.store + i32.const 1208 + i32.const 1200 + i32.store + i32.const 1200 + global.set $~lib/rt/itcms/toSpace + i32.const 1348 + i32.const 1344 + i32.store + i32.const 1352 + i32.const 1344 + i32.store + i32.const 1344 + global.set $~lib/rt/itcms/fromSpace + ) +) diff --git a/tests/compiler/features/tail-calls.ts b/tests/compiler/features/tail-calls.ts new file mode 100644 index 0000000000..1a09579ef4 --- /dev/null +++ b/tests/compiler/features/tail-calls.ts @@ -0,0 +1,27 @@ +function inc(x: i32): i32 { + return x + 1; +} + +function noop(): void {} + +export function direct(x: i32): i32 { + return inc(x); +} + +export function directVoid(): void { + return noop(); +} + +export function converted(x: i32): i64 { + return inc(x); +} + +class Box {} + +function maybeBox(x: i32): Box | null { + return x ? new Box() : null; +} + +export function nullable(x: i32): Box | null { + return maybeBox(x); +} diff --git a/tests/features.json b/tests/features.json index 5c47772500..f92c07759b 100644 --- a/tests/features.json +++ b/tests/features.json @@ -29,6 +29,13 @@ "v8_flags": [ ] }, + "tail-calls": { + "asc_flags": [ + "--enable tail-calls" + ], + "v8_flags": [ + ] + }, "simd": { "asc_flags": [ "--enable simd" From 9a03329f6137ebf12aeeeeca464165557bd7504a Mon Sep 17 00:00:00 2001 From: Jairus Tanaka Date: Thu, 23 Apr 2026 22:48:09 -0400 Subject: [PATCH 2/4] feat: add support for tail-calls on `call_indirect` --- src/compiler.ts | 58 +++++++++++++++++++++++++++++++++++-------------- src/module.ts | 16 ++++++++++++++ 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/compiler.ts b/src/compiler.ts index 11e789caa0..7db8e57923 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -2859,12 +2859,7 @@ export class Compiler extends DiagnosticEmitter { : module.br(inlineReturnLabel); } - if ( - expr && - this.options.hasFeature(Feature.TailCalls) && - getExpressionId(expr) == ExpressionId.Call && - binaryen._BinaryenCallIsReturn(expr) - ) return expr; + if (expr && this.options.hasFeature(Feature.TailCalls) && this.isTailReturnCallExpression(expr)) return expr; // Otherwise emit a normal return return expr @@ -6173,13 +6168,16 @@ export class Compiler extends DiagnosticEmitter { let functionArg = this.compileExpression(expression.expression, Type.auto); let signature = this.currentType.getSignature(); if (signature) { + if (!this.canTailReturn(constraints, contextualType, signature.returnType)) + constraints &= ~Constraints.WillReturn; return this.compileCallIndirect( signature, functionArg, expression.args, expression, 0, - contextualType == Type.void + contextualType == Type.void, + constraints ); } this.error( @@ -6219,6 +6217,17 @@ export class Compiler extends DiagnosticEmitter { } } + private isTailReturnCallExpression(expr: ExpressionRef): bool { + switch (getExpressionId(expr)) { + case ExpressionId.Call: + return binaryen._BinaryenCallIsReturn(expr); + case ExpressionId.CallIndirect: + return binaryen._BinaryenCallIndirectIsReturn(expr); + default: + return false; + } + } + /** Compiles the given arguments like a call expression according to the specified context. */ private compileCallExpressionLike( /** Called expression. */ @@ -7057,7 +7066,8 @@ export class Compiler extends DiagnosticEmitter { argumentExpressions: Expression[], reportNode: Node, thisArg: ExpressionRef = 0, - immediatelyDropped: bool = false + immediatelyDropped: bool = false, + constraints: Constraints = Constraints.None ): ExpressionRef { let numArguments = argumentExpressions.length; @@ -7087,7 +7097,14 @@ export class Compiler extends DiagnosticEmitter { ); } assert(index == numArgumentsInclThis); - return this.makeCallIndirect(signature, functionArg, reportNode, operands, immediatelyDropped); + return this.makeCallIndirect( + signature, + functionArg, + reportNode, + operands, + immediatelyDropped, + (constraints & Constraints.WillReturn) != 0 + ); } /** Creates an indirect call to a first-class function. */ @@ -7097,6 +7114,7 @@ export class Compiler extends DiagnosticEmitter { reportNode: Node, operands: ExpressionRef[] | null = null, immediatelyDropped: bool = false, + isReturn: bool = false, ): ExpressionRef { let module = this.module; let numOperands = operands ? operands.length : 0; @@ -7147,13 +7165,21 @@ export class Compiler extends DiagnosticEmitter { ], sizeTypeRef); } if (operands) this.operandsTostack(signature, operands); - let expr = module.call_indirect( - null, // TODO: handle multiple tables - module.load(4, false, functionArg, TypeRef.I32), // ._index - operands, - signature.paramRefs, - signature.resultRefs - ); + let expr = isReturn // TODO: handle multiple tables here + ? module.return_call_indirect( + null, + module.load(4, false, functionArg, TypeRef.I32), + operands, + signature.paramRefs, + signature.resultRefs + ) + : module.call_indirect( + null, + module.load(4, false, functionArg, TypeRef.I32), + operands, + signature.paramRefs, + signature.resultRefs + ); this.currentType = returnType; return expr; } diff --git a/src/module.ts b/src/module.ts index 030d0e323b..67256396ea 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1856,6 +1856,22 @@ export class Module { return ret; } + return_call_indirect( + tableName: string | null, + index: ExpressionRef, + operands: ExpressionRef[] | null, + params: TypeRef, + results: TypeRef + ): ExpressionRef { + let cStr = this.allocStringCached(tableName != null ? tableName : CommonNames.DefaultTable); + let cArr = allocPtrArray(operands); + let ret = binaryen._BinaryenReturnCallIndirect( + this.ref, cStr, index, cArr, operands ? operands.length : 0, params, results + ); + binaryen._free(cArr); + return ret; + } + unreachable(): ExpressionRef { return binaryen._BinaryenUnreachable(this.ref); } From 5e0b2fe13cfbfe1eb1cf9c856161ce919fac2f19 Mon Sep 17 00:00:00 2001 From: Jairus Tanaka Date: Thu, 23 Apr 2026 22:54:57 -0400 Subject: [PATCH 3/4] tests: add coverage for `call_indirect` --- tests/compiler/features/tail-calls.debug.wat | 312 +++++++++++++---- .../compiler/features/tail-calls.release.wat | 314 +++++++++++++----- tests/compiler/features/tail-calls.ts | 24 ++ 3 files changed, 492 insertions(+), 158 deletions(-) diff --git a/tests/compiler/features/tail-calls.debug.wat b/tests/compiler/features/tail-calls.debug.wat index 2aa8cd92c9..76dc282b99 100644 --- a/tests/compiler/features/tail-calls.debug.wat +++ b/tests/compiler/features/tail-calls.debug.wat @@ -4,12 +4,14 @@ (type $2 (func (param i32))) (type $3 (func)) (type $4 (func (param i32 i32) (result i32))) - (type $5 (func (param i32 i32 i32))) - (type $6 (func (param i32) (result i64))) - (type $7 (func (param i32 i32 i32 i32))) - (type $8 (func (param i32 i32 i64) (result i32))) - (type $9 (func (result i32))) + (type $5 (func (param i32) (result i64))) + (type $6 (func (param i32 i32 i32))) + (type $7 (func (param i32 i32) (result i64))) + (type $8 (func (param i32 i32 i32 i32))) + (type $9 (func (param i32 i32 i64) (result i32))) + (type $10 (func (result i32))) (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) + (global $~argumentsLength (mut i32) (i32.const 0)) (global $~lib/rt/itcms/total (mut i32) (i32.const 0)) (global $~lib/rt/itcms/threshold (mut i32) (i32.const 0)) (global $~lib/rt/itcms/state (mut i32) (i32.const 0)) @@ -24,26 +26,31 @@ (global $~lib/rt/itcms/fromSpace (mut i32) (i32.const 0)) (global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0)) (global $~lib/native/ASC_LOW_MEMORY_LIMIT i32 (i32.const 0)) - (global $~lib/rt/__rtti_base i32 (i32.const 416)) - (global $~lib/memory/__data_end i32 (i32.const 440)) - (global $~lib/memory/__stack_pointer (mut i32) (i32.const 33208)) - (global $~lib/memory/__heap_base i32 (i32.const 33208)) + (global $~lib/rt/__rtti_base i32 (i32.const 480)) + (global $~lib/memory/__data_end i32 (i32.const 512)) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 33280)) + (global $~lib/memory/__heap_base i32 (i32.const 33280)) (memory $0 1) - (data $0 (i32.const 12) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00(\00\00\00A\00l\00l\00o\00c\00a\00t\00i\00o\00n\00 \00t\00o\00o\00 \00l\00a\00r\00g\00e\00\00\00\00\00") - (data $1 (i32.const 76) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00 \00\00\00~\00l\00i\00b\00/\00r\00t\00/\00i\00t\00c\00m\00s\00.\00t\00s\00\00\00\00\00\00\00\00\00\00\00\00\00") - (data $2 (i32.const 144) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data $0 (i32.const 12) "\1c\00\00\00\00\00\00\00\00\00\00\00\04\00\00\00\08\00\00\00\01\00\00\00\00\00\00\00\00\00\00\00") + (data $1 (i32.const 44) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00(\00\00\00A\00l\00l\00o\00c\00a\00t\00i\00o\00n\00 \00t\00o\00o\00 \00l\00a\00r\00g\00e\00\00\00\00\00") + (data $2 (i32.const 108) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00 \00\00\00~\00l\00i\00b\00/\00r\00t\00/\00i\00t\00c\00m\00s\00.\00t\00s\00\00\00\00\00\00\00\00\00\00\00\00\00") (data $3 (i32.const 176) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") - (data $4 (i32.const 204) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00$\00\00\00I\00n\00d\00e\00x\00 \00o\00u\00t\00 \00o\00f\00 \00r\00a\00n\00g\00e\00\00\00\00\00\00\00\00\00") - (data $5 (i32.const 268) ",\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\14\00\00\00~\00l\00i\00b\00/\00r\00t\00.\00t\00s\00\00\00\00\00\00\00\00\00") - (data $6 (i32.const 320) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") - (data $7 (i32.const 348) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\1e\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") - (data $8 (i32.const 416) "\05\00\00\00 \00\00\00 \00\00\00 \00\00\00\00\00\00\00 \00\00\00") - (table $0 1 1 funcref) - (elem $0 (i32.const 1)) + (data $4 (i32.const 208) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data $5 (i32.const 236) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00$\00\00\00I\00n\00d\00e\00x\00 \00o\00u\00t\00 \00o\00f\00 \00r\00a\00n\00g\00e\00\00\00\00\00\00\00\00\00") + (data $6 (i32.const 300) ",\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\14\00\00\00~\00l\00i\00b\00/\00r\00t\00.\00t\00s\00\00\00\00\00\00\00\00\00") + (data $7 (i32.const 352) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data $8 (i32.const 380) "<\00\00\00\00\00\00\00\00\00\00\00\02\00\00\00\1e\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data $9 (i32.const 444) "\1c\00\00\00\00\00\00\00\00\00\00\00\06\00\00\00\08\00\00\00\02\00\00\00\00\00\00\00\00\00\00\00") + (data $10 (i32.const 480) "\07\00\00\00 \00\00\00 \00\00\00 \00\00\00\00\00\00\00\00\00\00\00 \00\00\00\00\00\00\00") + (table $0 3 3 funcref) + (elem $0 (i32.const 1) $features/tail-calls/inc $features/tail-calls/maybeBox) (export "direct" (func $features/tail-calls/direct)) (export "directVoid" (func $features/tail-calls/directVoid)) + (export "indirect" (func $features/tail-calls/indirect)) (export "converted" (func $features/tail-calls/converted)) + (export "convertedIndirect" (func $features/tail-calls/convertedIndirect)) (export "nullable" (func $features/tail-calls/nullable)) + (export "nullableIndirect" (func $features/tail-calls/nullableIndirect)) (export "memory" (memory $0)) (start $~start) (func $features/tail-calls/inc (param $x i32) (result i32) @@ -61,12 +68,30 @@ (func $features/tail-calls/directVoid return_call $features/tail-calls/noop ) + (func $features/tail-calls/invoke (param $fn i32) (param $x i32) (result i32) + local.get $x + i32.const 1 + global.set $~argumentsLength + local.get $fn + i32.load + return_call_indirect (type $0) + ) (func $features/tail-calls/converted (param $x i32) (result i64) local.get $x call $features/tail-calls/inc i64.extend_i32_s return ) + (func $features/tail-calls/widenInvoke (param $fn i32) (param $x i32) (result i64) + local.get $x + i32.const 1 + global.set $~argumentsLength + local.get $fn + i32.load + call_indirect (type $0) + i64.extend_i32_s + return + ) (func $~lib/rt/itcms/Object#set:nextWithColor (param $this i32) (param $nextWithColor i32) local.get $this local.get $nextWithColor @@ -131,7 +156,7 @@ i32.eqz if i32.const 0 - i32.const 96 + i32.const 128 i32.const 160 i32.const 16 call $~lib/builtins/abort @@ -201,7 +226,7 @@ i32.eqz if i32.const 0 - i32.const 96 + i32.const 128 i32.const 128 i32.const 18 call $~lib/builtins/abort @@ -218,7 +243,7 @@ i32.eqz if i32.const 0 - i32.const 96 + i32.const 128 i32.const 132 i32.const 16 call $~lib/builtins/abort @@ -248,8 +273,8 @@ i32.load i32.gt_u if - i32.const 224 - i32.const 288 + i32.const 256 + i32.const 320 i32.const 21 i32.const 28 call $~lib/builtins/abort @@ -317,7 +342,7 @@ i32.eqz if (result i32) i32.const 0 - i32.const 96 + i32.const 128 i32.const 148 i32.const 30 call $~lib/builtins/abort @@ -469,7 +494,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 268 i32.const 14 call $~lib/builtins/abort @@ -489,7 +514,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 270 i32.const 14 call $~lib/builtins/abort @@ -552,7 +577,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 284 i32.const 14 call $~lib/builtins/abort @@ -705,7 +730,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 201 i32.const 14 call $~lib/builtins/abort @@ -722,7 +747,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 203 i32.const 14 call $~lib/builtins/abort @@ -811,7 +836,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 221 i32.const 16 call $~lib/builtins/abort @@ -854,7 +879,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 233 i32.const 14 call $~lib/builtins/abort @@ -872,7 +897,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 234 i32.const 14 call $~lib/builtins/abort @@ -940,7 +965,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 251 i32.const 14 call $~lib/builtins/abort @@ -1057,7 +1082,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 382 i32.const 14 call $~lib/builtins/abort @@ -1103,7 +1128,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 389 i32.const 16 call $~lib/builtins/abort @@ -1135,7 +1160,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 402 i32.const 5 call $~lib/builtins/abort @@ -1379,7 +1404,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 562 i32.const 3 call $~lib/builtins/abort @@ -1599,7 +1624,7 @@ i32.eqz if i32.const 0 - i32.const 96 + i32.const 128 i32.const 229 i32.const 20 call $~lib/builtins/abort @@ -1710,8 +1735,8 @@ i32.const 1073741820 i32.gt_u if - i32.const 32 - i32.const 368 + i32.const 64 + i32.const 400 i32.const 461 i32.const 29 call $~lib/builtins/abort @@ -1812,7 +1837,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 334 i32.const 14 call $~lib/builtins/abort @@ -1883,7 +1908,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 347 i32.const 18 call $~lib/builtins/abort @@ -2040,7 +2065,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 361 i32.const 14 call $~lib/builtins/abort @@ -2155,7 +2180,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 499 i32.const 16 call $~lib/builtins/abort @@ -2175,7 +2200,7 @@ i32.eqz if i32.const 0 - i32.const 368 + i32.const 400 i32.const 501 i32.const 14 call $~lib/builtins/abort @@ -2223,8 +2248,8 @@ i32.const 1073741804 i32.ge_u if - i32.const 32 - i32.const 96 + i32.const 64 + i32.const 128 i32.const 261 i32.const 31 call $~lib/builtins/abort @@ -2283,12 +2308,20 @@ local.get $x return_call $features/tail-calls/maybeBox ) + (func $features/tail-calls/maybeInvoke (param $fn i32) (param $x i32) (result i32) + local.get $x + i32.const 1 + global.set $~argumentsLength + local.get $fn + i32.load + return_call_indirect (type $0) + ) (func $~lib/rt/__visit_globals (param $0 i32) (local $1 i32) - i32.const 224 + i32.const 256 local.get $0 call $~lib/rt/itcms/__visit - i32.const 32 + i32.const 64 local.get $0 call $~lib/rt/itcms/__visit ) @@ -2304,30 +2337,66 @@ ) (func $~lib/object/Object~visit (param $0 i32) (param $1 i32) ) + (func $~lib/function/Function<%28i32%29=>i32>#get:_env (param $this i32) (result i32) + local.get $this + i32.load offset=4 + ) + (func $~lib/function/Function<%28i32%29=>i32>~visit (param $0 i32) (param $1 i32) + local.get $0 + local.get $1 + call $~lib/object/Object~visit + local.get $0 + local.get $1 + call $~lib/function/Function<%28i32%29=>i32>#__visit + ) + (func $~lib/function/Function<%28i32%29=>features/tail-calls/Box|null>#get:_env (param $this i32) (result i32) + local.get $this + i32.load offset=4 + ) + (func $~lib/function/Function<%28i32%29=>features/tail-calls/Box|null>~visit (param $0 i32) (param $1 i32) + local.get $0 + local.get $1 + call $~lib/object/Object~visit + local.get $0 + local.get $1 + call $~lib/function/Function<%28i32%29=>features/tail-calls/Box|null>#__visit + ) (func $~lib/rt/__visit_members (param $0 i32) (param $1 i32) block $invalid - block $features/tail-calls/Box - block $~lib/arraybuffer/ArrayBufferView - block $~lib/string/String - block $~lib/arraybuffer/ArrayBuffer - block $~lib/object/Object - local.get $0 - i32.const 8 - i32.sub - i32.load - br_table $~lib/object/Object $~lib/arraybuffer/ArrayBuffer $~lib/string/String $~lib/arraybuffer/ArrayBufferView $features/tail-calls/Box $invalid + block $~lib/function/Function<%28i32%29=>features/tail-calls/Box|null> + block $features/tail-calls/Box + block $~lib/function/Function<%28i32%29=>i32> + block $~lib/arraybuffer/ArrayBufferView + block $~lib/string/String + block $~lib/arraybuffer/ArrayBuffer + block $~lib/object/Object + local.get $0 + i32.const 8 + i32.sub + i32.load + br_table $~lib/object/Object $~lib/arraybuffer/ArrayBuffer $~lib/string/String $~lib/arraybuffer/ArrayBufferView $~lib/function/Function<%28i32%29=>i32> $features/tail-calls/Box $~lib/function/Function<%28i32%29=>features/tail-calls/Box|null> $invalid + end + return + end + return end return end + local.get $0 + local.get $1 + call $~lib/arraybuffer/ArrayBufferView~visit return end + local.get $0 + local.get $1 + call $~lib/function/Function<%28i32%29=>i32>~visit return end - local.get $0 - local.get $1 - call $~lib/arraybuffer/ArrayBufferView~visit return end + local.get $0 + local.get $1 + call $~lib/function/Function<%28i32%29=>features/tail-calls/Box|null>~visit return end unreachable @@ -2341,13 +2410,13 @@ i32.const 1 i32.shr_u global.set $~lib/rt/itcms/threshold - i32.const 144 + i32.const 176 call $~lib/rt/itcms/initLazy global.set $~lib/rt/itcms/pinSpace - i32.const 176 + i32.const 208 call $~lib/rt/itcms/initLazy global.set $~lib/rt/itcms/toSpace - i32.const 320 + i32.const 352 call $~lib/rt/itcms/initLazy global.set $~lib/rt/itcms/fromSpace ) @@ -2356,14 +2425,52 @@ global.get $~lib/memory/__data_end i32.lt_s if - i32.const 33232 - i32.const 33280 + i32.const 33312 + i32.const 33360 i32.const 1 i32.const 1 call $~lib/builtins/abort unreachable end ) + (func $features/tail-calls/indirect (param $x i32) (result i32) + (local $1 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + i32.const 32 + local.set $1 + global.get $~lib/memory/__stack_pointer + local.get $1 + i32.store + local.get $1 + local.get $x + return_call $features/tail-calls/invoke + ) + (func $features/tail-calls/convertedIndirect (param $x i32) (result i64) + (local $1 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + i32.const 32 + local.set $1 + global.get $~lib/memory/__stack_pointer + local.get $1 + i32.store + local.get $1 + local.get $x + return_call $features/tail-calls/widenInvoke + ) (func $features/tail-calls/Box#constructor (param $this i32) (result i32) (local $1 i32) global.get $~lib/memory/__stack_pointer @@ -2379,7 +2486,7 @@ if global.get $~lib/memory/__stack_pointer i32.const 0 - i32.const 4 + i32.const 5 call $~lib/rt/itcms/__new local.tee $this i32.store @@ -2402,6 +2509,73 @@ global.set $~lib/memory/__stack_pointer local.get $1 ) + (func $features/tail-calls/nullableIndirect (param $x i32) (result i32) + (local $1 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + i32.const 464 + local.set $1 + global.get $~lib/memory/__stack_pointer + local.get $1 + i32.store + local.get $1 + local.get $x + return_call $features/tail-calls/maybeInvoke + ) + (func $~lib/function/Function<%28i32%29=>i32>#__visit (param $this i32) (param $cookie i32) + (local $2 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + local.get $this + local.set $2 + global.get $~lib/memory/__stack_pointer + local.get $2 + i32.store + local.get $2 + call $~lib/function/Function<%28i32%29=>i32>#get:_env + local.get $cookie + call $~lib/rt/itcms/__visit + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) + (func $~lib/function/Function<%28i32%29=>features/tail-calls/Box|null>#__visit (param $this i32) (param $cookie i32) + (local $2 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + call $~stack_check + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + local.get $this + local.set $2 + global.get $~lib/memory/__stack_pointer + local.get $2 + i32.store + local.get $2 + call $~lib/function/Function<%28i32%29=>features/tail-calls/Box|null>#get:_env + local.get $cookie + call $~lib/rt/itcms/__visit + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) (func $~lib/object/Object#constructor (param $this i32) (result i32) (local $1 i32) global.get $~lib/memory/__stack_pointer diff --git a/tests/compiler/features/tail-calls.release.wat b/tests/compiler/features/tail-calls.release.wat index 9312da098d..065dfd2182 100644 --- a/tests/compiler/features/tail-calls.release.wat +++ b/tests/compiler/features/tail-calls.release.wat @@ -2,8 +2,8 @@ (type $0 (func (param i32) (result i32))) (type $1 (func)) (type $2 (func (param i32))) - (type $3 (func (param i32 i32))) - (type $4 (func (param i32) (result i64))) + (type $3 (func (param i32) (result i64))) + (type $4 (func (param i32 i32))) (type $5 (func (param i32 i32 i32 i32))) (type $6 (func (param i32 i32 i64))) (type $7 (func (result i32))) @@ -18,26 +18,36 @@ (global $~lib/rt/itcms/white (mut i32) (i32.const 0)) (global $~lib/rt/itcms/fromSpace (mut i32) (i32.const 0)) (global $~lib/rt/tlsf/ROOT (mut i32) (i32.const 0)) - (global $~lib/memory/__stack_pointer (mut i32) (i32.const 34232)) + (global $~lib/memory/__stack_pointer (mut i32) (i32.const 34304)) (memory $0 1) - (data $0 (i32.const 1036) "<") - (data $0.1 (i32.const 1048) "\02\00\00\00(\00\00\00A\00l\00l\00o\00c\00a\00t\00i\00o\00n\00 \00t\00o\00o\00 \00l\00a\00r\00g\00e") - (data $1 (i32.const 1100) "<") - (data $1.1 (i32.const 1112) "\02\00\00\00 \00\00\00~\00l\00i\00b\00/\00r\00t\00/\00i\00t\00c\00m\00s\00.\00t\00s") - (data $4 (i32.const 1228) "<") - (data $4.1 (i32.const 1240) "\02\00\00\00$\00\00\00I\00n\00d\00e\00x\00 \00o\00u\00t\00 \00o\00f\00 \00r\00a\00n\00g\00e") - (data $5 (i32.const 1292) ",") - (data $5.1 (i32.const 1304) "\02\00\00\00\14\00\00\00~\00l\00i\00b\00/\00r\00t\00.\00t\00s") - (data $7 (i32.const 1372) "<") - (data $7.1 (i32.const 1384) "\02\00\00\00\1e\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s") - (data $8 (i32.const 1440) "\05\00\00\00 \00\00\00 \00\00\00 \00\00\00\00\00\00\00 ") - (export "direct" (func $features/tail-calls/direct)) + (data $0 (i32.const 1036) "\1c") + (data $0.1 (i32.const 1048) "\04\00\00\00\08\00\00\00\01") + (data $1 (i32.const 1068) "<") + (data $1.1 (i32.const 1080) "\02\00\00\00(\00\00\00A\00l\00l\00o\00c\00a\00t\00i\00o\00n\00 \00t\00o\00o\00 \00l\00a\00r\00g\00e") + (data $2 (i32.const 1132) "<") + (data $2.1 (i32.const 1144) "\02\00\00\00 \00\00\00~\00l\00i\00b\00/\00r\00t\00/\00i\00t\00c\00m\00s\00.\00t\00s") + (data $5 (i32.const 1260) "<") + (data $5.1 (i32.const 1272) "\02\00\00\00$\00\00\00I\00n\00d\00e\00x\00 \00o\00u\00t\00 \00o\00f\00 \00r\00a\00n\00g\00e") + (data $6 (i32.const 1324) ",") + (data $6.1 (i32.const 1336) "\02\00\00\00\14\00\00\00~\00l\00i\00b\00/\00r\00t\00.\00t\00s") + (data $8 (i32.const 1404) "<") + (data $8.1 (i32.const 1416) "\02\00\00\00\1e\00\00\00~\00l\00i\00b\00/\00r\00t\00/\00t\00l\00s\00f\00.\00t\00s") + (data $9 (i32.const 1468) "\1c") + (data $9.1 (i32.const 1480) "\06\00\00\00\08\00\00\00\02") + (data $10 (i32.const 1504) "\07\00\00\00 \00\00\00 \00\00\00 ") + (data $10.1 (i32.const 1528) " ") + (table $0 3 3 funcref) + (elem $0 (i32.const 1) $features/tail-calls/inc $features/tail-calls/maybeBox) + (export "direct" (func $features/tail-calls/inc)) (export "directVoid" (func $features/tail-calls/directVoid)) + (export "indirect" (func $features/tail-calls/indirect)) (export "converted" (func $features/tail-calls/converted)) + (export "convertedIndirect" (func $features/tail-calls/convertedIndirect)) (export "nullable" (func $features/tail-calls/nullable)) + (export "nullableIndirect" (func $features/tail-calls/nullableIndirect)) (export "memory" (memory $0)) (start $~start) - (func $features/tail-calls/direct (param $0 i32) (result i32) + (func $features/tail-calls/inc (param $0 i32) (result i32) local.get $0 i32.const 1 i32.add @@ -53,9 +63,9 @@ (func $~lib/rt/itcms/visitRoots (local $0 i32) (local $1 i32) - i32.const 1248 + i32.const 1280 call $~lib/rt/itcms/__visit - i32.const 1056 + i32.const 1088 call $~lib/rt/itcms/__visit global.get $~lib/rt/itcms/pinSpace local.tee $1 @@ -76,7 +86,7 @@ i32.ne if i32.const 0 - i32.const 1120 + i32.const 1152 i32.const 160 i32.const 16 call $~lib/builtins/abort @@ -124,7 +134,7 @@ i32.eqz if i32.const 0 - i32.const 1120 + i32.const 1152 i32.const 148 i32.const 30 call $~lib/builtins/abort @@ -133,7 +143,7 @@ local.get $0 global.set $~lib/rt/itcms/iter end - block $__inlined_func$~lib/rt/itcms/Object#unlink$113 + block $__inlined_func$~lib/rt/itcms/Object#unlink$123 local.get $1 i32.load offset=4 i32.const -4 @@ -145,19 +155,19 @@ i32.load offset=8 i32.eqz local.get $1 - i32.const 34232 + i32.const 34304 i32.lt_u i32.and i32.eqz if i32.const 0 - i32.const 1120 + i32.const 1152 i32.const 128 i32.const 18 call $~lib/builtins/abort unreachable end - br $__inlined_func$~lib/rt/itcms/Object#unlink$113 + br $__inlined_func$~lib/rt/itcms/Object#unlink$123 end local.get $1 i32.load offset=8 @@ -165,7 +175,7 @@ i32.eqz if i32.const 0 - i32.const 1120 + i32.const 1152 i32.const 132 i32.const 16 call $~lib/builtins/abort @@ -194,12 +204,12 @@ i32.const 1 else local.get $0 - i32.const 1440 + i32.const 1504 i32.load i32.gt_u if - i32.const 1248 - i32.const 1312 + i32.const 1280 + i32.const 1344 i32.const 21 i32.const 28 call $~lib/builtins/abort @@ -208,7 +218,7 @@ local.get $0 i32.const 2 i32.shl - i32.const 1444 + i32.const 1508 i32.add i32.load i32.const 32 @@ -260,7 +270,7 @@ i32.eqz if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 268 i32.const 14 call $~lib/builtins/abort @@ -274,7 +284,7 @@ i32.lt_u if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 270 i32.const 14 call $~lib/builtins/abort @@ -320,7 +330,7 @@ i32.eqz if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 284 i32.const 14 call $~lib/builtins/abort @@ -403,7 +413,7 @@ i32.eqz if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 201 i32.const 14 call $~lib/builtins/abort @@ -417,7 +427,7 @@ i32.eqz if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 203 i32.const 14 call $~lib/builtins/abort @@ -478,7 +488,7 @@ i32.eqz if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 221 i32.const 16 call $~lib/builtins/abort @@ -511,7 +521,7 @@ i32.lt_u if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 233 i32.const 14 call $~lib/builtins/abort @@ -526,7 +536,7 @@ i32.ne if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 234 i32.const 14 call $~lib/builtins/abort @@ -577,7 +587,7 @@ i32.eqz if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 251 i32.const 14 call $~lib/builtins/abort @@ -649,7 +659,7 @@ i64.lt_u if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 382 i32.const 14 call $~lib/builtins/abort @@ -674,7 +684,7 @@ i32.gt_u if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 389 i32.const 16 call $~lib/builtins/abort @@ -701,7 +711,7 @@ i32.gt_u if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 402 i32.const 5 call $~lib/builtins/abort @@ -773,10 +783,10 @@ if unreachable end - i32.const 34240 + i32.const 34304 i32.const 0 i32.store - i32.const 35808 + i32.const 35872 i32.const 0 i32.store loop $for-loop|0 @@ -787,7 +797,7 @@ local.get $0 i32.const 2 i32.shl - i32.const 34240 + i32.const 34304 i32.add i32.const 0 i32.store offset=4 @@ -805,7 +815,7 @@ i32.add i32.const 2 i32.shl - i32.const 34240 + i32.const 34304 i32.add i32.const 0 i32.store offset=96 @@ -823,14 +833,14 @@ br $for-loop|0 end end - i32.const 34240 - i32.const 35812 + i32.const 34304 + i32.const 35876 memory.size i64.extend_i32_s i64.const 16 i64.shl call $~lib/rt/tlsf/addMemory - i32.const 34240 + i32.const 34304 global.set $~lib/rt/tlsf/ROOT ) (func $~lib/rt/itcms/step (result i32) @@ -915,7 +925,7 @@ local.set $0 loop $while-continue|0 local.get $0 - i32.const 34232 + i32.const 34304 i32.lt_u if local.get $0 @@ -1004,14 +1014,14 @@ i32.ne if i32.const 0 - i32.const 1120 + i32.const 1152 i32.const 229 i32.const 20 call $~lib/builtins/abort unreachable end local.get $0 - i32.const 34232 + i32.const 34304 i32.lt_u if local.get $0 @@ -1034,7 +1044,7 @@ i32.const 4 i32.add local.tee $0 - i32.const 34232 + i32.const 34304 i32.ge_u if global.get $~lib/rt/tlsf/ROOT @@ -1063,7 +1073,7 @@ end if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 562 i32.const 3 call $~lib/builtins/abort @@ -1128,7 +1138,7 @@ i32.eqz if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 347 i32.const 18 call $~lib/builtins/abort @@ -1256,7 +1266,7 @@ i32.eqz if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 499 i32.const 16 call $~lib/builtins/abort @@ -1271,7 +1281,7 @@ i32.lt_u if i32.const 0 - i32.const 1392 + i32.const 1424 i32.const 501 i32.const 14 call $~lib/builtins/abort @@ -1379,7 +1389,7 @@ memory.fill local.get $0 ) - (func $features/tail-calls/nullable (param $0 i32) (result i32) + (func $features/tail-calls/maybeBox (param $0 i32) (result i32) (local $1 i32) block $folding-inner0 local.get $0 @@ -1389,14 +1399,14 @@ i32.sub global.set $~lib/memory/__stack_pointer global.get $~lib/memory/__stack_pointer - i32.const 1464 + i32.const 1536 i32.lt_s br_if $folding-inner0 global.get $~lib/memory/__stack_pointer i64.const 0 i64.store global.get $~lib/memory/__stack_pointer - i32.const 4 + i32.const 5 call $~lib/rt/itcms/__new local.tee $0 i32.store @@ -1409,7 +1419,7 @@ i32.sub global.set $~lib/memory/__stack_pointer global.get $~lib/memory/__stack_pointer - i32.const 1464 + i32.const 1536 i32.lt_s br_if $folding-inner0 global.get $~lib/memory/__stack_pointer @@ -1441,37 +1451,81 @@ local.get $0 return end - i32.const 34256 - i32.const 34304 + i32.const 34336 + i32.const 34384 i32.const 1 i32.const 1 call $~lib/builtins/abort unreachable ) + (func $features/tail-calls/nullable (param $0 i32) (result i32) + local.get $0 + return_call $features/tail-calls/maybeBox + ) + (func $~lib/function/Function<%28i32%29=>i32>~visit (param $0 i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1536 + i32.lt_s + if + i32.const 34336 + i32.const 34384 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + global.get $~lib/memory/__stack_pointer + local.get $0 + i32.store + local.get $0 + i32.load offset=4 + call $~lib/rt/itcms/__visit + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.add + global.set $~lib/memory/__stack_pointer + ) (func $~lib/rt/__visit_members (param $0 i32) block $invalid - block $features/tail-calls/Box - block $~lib/arraybuffer/ArrayBufferView - block $~lib/string/String - block $~lib/arraybuffer/ArrayBuffer - block $~lib/object/Object - local.get $0 - i32.const 8 - i32.sub - i32.load - br_table $~lib/object/Object $~lib/arraybuffer/ArrayBuffer $~lib/string/String $~lib/arraybuffer/ArrayBufferView $features/tail-calls/Box $invalid + block $~lib/function/Function<%28i32%29=>features/tail-calls/Box|null> + block $features/tail-calls/Box + block $~lib/function/Function<%28i32%29=>i32> + block $~lib/arraybuffer/ArrayBufferView + block $~lib/string/String + block $~lib/arraybuffer/ArrayBuffer + block $~lib/object/Object + local.get $0 + i32.const 8 + i32.sub + i32.load + br_table $~lib/object/Object $~lib/arraybuffer/ArrayBuffer $~lib/string/String $~lib/arraybuffer/ArrayBufferView $~lib/function/Function<%28i32%29=>i32> $features/tail-calls/Box $~lib/function/Function<%28i32%29=>features/tail-calls/Box|null> $invalid + end + return + end + return end return end + local.get $0 + i32.load + call $~lib/rt/itcms/__visit return end + local.get $0 + call $~lib/function/Function<%28i32%29=>i32>~visit return end - local.get $0 - i32.load - call $~lib/rt/itcms/__visit return end + local.get $0 + call $~lib/function/Function<%28i32%29=>i32>~visit return end unreachable @@ -1480,19 +1534,11 @@ memory.size i32.const 16 i32.shl - i32.const 34232 + i32.const 34304 i32.sub i32.const 1 i32.shr_u global.set $~lib/rt/itcms/threshold - i32.const 1172 - i32.const 1168 - i32.store - i32.const 1176 - i32.const 1168 - i32.store - i32.const 1168 - global.set $~lib/rt/itcms/pinSpace i32.const 1204 i32.const 1200 i32.store @@ -1500,14 +1546,104 @@ i32.const 1200 i32.store i32.const 1200 + global.set $~lib/rt/itcms/pinSpace + i32.const 1236 + i32.const 1232 + i32.store + i32.const 1240 + i32.const 1232 + i32.store + i32.const 1232 global.set $~lib/rt/itcms/toSpace - i32.const 1348 - i32.const 1344 + i32.const 1380 + i32.const 1376 i32.store - i32.const 1352 - i32.const 1344 + i32.const 1384 + i32.const 1376 i32.store - i32.const 1344 + i32.const 1376 global.set $~lib/rt/itcms/fromSpace ) + (func $features/tail-calls/indirect (param $0 i32) (result i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1536 + i32.lt_s + if + i32.const 34336 + i32.const 34384 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + global.get $~lib/memory/__stack_pointer + i32.const 1056 + i32.store + local.get $0 + i32.const 1056 + i32.load + return_call_indirect (type $0) + ) + (func $features/tail-calls/convertedIndirect (param $0 i32) (result i64) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1536 + i32.lt_s + if + i32.const 34336 + i32.const 34384 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + global.get $~lib/memory/__stack_pointer + i32.const 1056 + i32.store + local.get $0 + i32.const 1056 + i32.load + call_indirect (type $0) + i64.extend_i32_s + ) + (func $features/tail-calls/nullableIndirect (param $0 i32) (result i32) + global.get $~lib/memory/__stack_pointer + i32.const 4 + i32.sub + global.set $~lib/memory/__stack_pointer + global.get $~lib/memory/__stack_pointer + i32.const 1536 + i32.lt_s + if + i32.const 34336 + i32.const 34384 + i32.const 1 + i32.const 1 + call $~lib/builtins/abort + unreachable + end + global.get $~lib/memory/__stack_pointer + i32.const 0 + i32.store + global.get $~lib/memory/__stack_pointer + i32.const 1488 + i32.store + local.get $0 + i32.const 1488 + i32.load + return_call_indirect (type $0) + ) ) diff --git a/tests/compiler/features/tail-calls.ts b/tests/compiler/features/tail-calls.ts index 1a09579ef4..d820514541 100644 --- a/tests/compiler/features/tail-calls.ts +++ b/tests/compiler/features/tail-calls.ts @@ -12,16 +12,40 @@ export function directVoid(): void { return noop(); } +function invoke(fn: (x: i32) => i32, x: i32): i32 { + return fn(x); +} + +export function indirect(x: i32): i32 { + return invoke(inc, x); +} + export function converted(x: i32): i64 { return inc(x); } +function widenInvoke(fn: (x: i32) => i32, x: i32): i64 { + return fn(x); +} + +export function convertedIndirect(x: i32): i64 { + return widenInvoke(inc, x); +} + class Box {} function maybeBox(x: i32): Box | null { return x ? new Box() : null; } +function maybeInvoke(fn: (x: i32) => Box | null, x: i32): Box | null { + return fn(x); +} + export function nullable(x: i32): Box | null { return maybeBox(x); } + +export function nullableIndirect(x: i32): Box | null { + return maybeInvoke(maybeBox, x); +} From c33df89c8ea0da49626b79ff4d0298e739958954 Mon Sep 17 00:00:00 2001 From: Jairus Tanaka Date: Thu, 23 Apr 2026 23:04:25 -0400 Subject: [PATCH 4/4] chore: remove random formatting artifacts --- src/compiler.ts | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/compiler.ts b/src/compiler.ts index 7db8e57923..3ab0e76b40 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -6152,8 +6152,7 @@ export class Compiler extends DiagnosticEmitter { Constraints.ConvImplicit | Constraints.IsThis ); } - if (!this.canTailReturn(constraints, contextualType, functionInstance.signature.returnType)) - constraints &= ~Constraints.WillReturn; + if (!this.canTailReturn(constraints, contextualType, functionInstance.signature.returnType)) constraints &= ~Constraints.WillReturn; return this.compileCallDirect( functionInstance, expression.args, @@ -6168,8 +6167,7 @@ export class Compiler extends DiagnosticEmitter { let functionArg = this.compileExpression(expression.expression, Type.auto); let signature = this.currentType.getSignature(); if (signature) { - if (!this.canTailReturn(constraints, contextualType, signature.returnType)) - constraints &= ~Constraints.WillReturn; + if (!this.canTailReturn(constraints, contextualType, signature.returnType)) constraints &= ~Constraints.WillReturn; return this.compileCallIndirect( signature, functionArg, @@ -6483,13 +6481,7 @@ export class Compiler extends DiagnosticEmitter { operands[index] = paramExpr; } assert(index == numArgumentsInclThis); - return this.makeCallDirect( - instance, - operands, - reportNode, - (constraints & Constraints.WillDrop) != 0, - (constraints & Constraints.WillReturn) != 0 - ); + return this.makeCallDirect(instance, operands, reportNode, (constraints & Constraints.WillDrop) != 0, (constraints & Constraints.WillReturn) != 0); } makeCallInline( @@ -7097,14 +7089,7 @@ export class Compiler extends DiagnosticEmitter { ); } assert(index == numArgumentsInclThis); - return this.makeCallIndirect( - signature, - functionArg, - reportNode, - operands, - immediatelyDropped, - (constraints & Constraints.WillReturn) != 0 - ); + return this.makeCallIndirect(signature, functionArg, reportNode, operands, immediatelyDropped, (constraints & Constraints.WillReturn) != 0); } /** Creates an indirect call to a first-class function. */ @@ -7165,7 +7150,7 @@ export class Compiler extends DiagnosticEmitter { ], sizeTypeRef); } if (operands) this.operandsTostack(signature, operands); - let expr = isReturn // TODO: handle multiple tables here + let expr = isReturn ? module.return_call_indirect( null, module.load(4, false, functionArg, TypeRef.I32),