diff --git a/src/engine/CodeValidator.v3 b/src/engine/CodeValidator.v3 index bb1d49e14..8f84c0cf9 100644 --- a/src/engine/CodeValidator.v3 +++ b/src/engine/CodeValidator.v3 @@ -30,6 +30,8 @@ class CodeValidator(extensions: Extension.set, limits: Limits, module: Module, e var handler_pc_index_map = Vector<(int, Array)>.new(); var handler_dest_map = Vector.new(); var handler_dests = Vector.new(); + // (pc, handler table) of every resume site; see {ResumeSites} + def resume_sites = Vector<(int, Array)>.new(); // Used for code validator callbacks that inspect immediates private var imm_codeptr: CodePtr; @@ -102,6 +104,8 @@ class CodeValidator(extensions: Extension.set, limits: Limits, module: Module, e Ranges.quicksort<(int, Array)>(handlers.handler_pc_index_map, fun (a, b) => a.0 < b.0); } if (handler_dests.length > 0) handlers.handler_dests = handler_dests.extract(); + if (resume_sites.length > 0) handlers.resume_sites = if(err.ok(), finishResumeSites(func)); + resume_sites.resize(0); if (frame_var_tags.length > 0) func.frame_var_tags = frame_var_tags.extract(); // Report metrics and return success. @@ -160,8 +164,25 @@ class CodeValidator(extensions: Extension.set, limits: Limits, module: Module, e ex_handlers.resize(0); suspend_handlers.resize(0); switch_handlers.resize(0); + resume_sites.resize(0); frame_var_tags.resize(0); } + // Resolves the handler entries of the recorded resume sites and lays the tables out by pc. + private def finishResumeSites(func: FuncDecl) -> Array> { + var result = Array>.new(func.orig_bytecode.length); + for (i < resume_sites.length) { + var site = resume_sites[i], table = site.1; + for (j < ResumeSites.header(table).count) { + var e = ResumeSites.entry(table, j); + var entry = func.sidetable.getCatchEntry(e.stp_pos); + e.handler_pc = entry.handler_pc; + e.stp_pos = entry.sidetable_pos; + e.vsp_slots = entry.val_stack_top + func.num_locals; + } + result[site.0] = table; + } + return result; + } // Add a new handler to the given handler entry list. // {index == -1} to allocate a new one; otherwise write onto the given index. // {target == null} to always generate a new dest. @@ -1544,6 +1565,10 @@ class CodeValidator(extensions: Extension.set, limits: Limits, module: Module, e def readAndCheckContHandlerTable(cont: ContDecl, handlers: Array) { var sidetable_pos = ctlxfer.sidetable.length; var handler_indices = Array.new(handlers.length); + var n_suspend = 0; + for (h in handlers) if (SuspensionHandler.Suspend.?(h)) n_suspend++; + var table = ResumeSites.alloc(n_suspend, handlers.length - n_suspend); + var suspend_rank = 0, switch_rank = 0; for (i < handlers.length) { // Same sidetable structure is used for both {suspend} and {switch}. var sidetable_entry = sidetable_pos + (i * Sidetable_CatchEntry.size / 4); @@ -1552,14 +1577,22 @@ class CodeValidator(extensions: Extension.set, limits: Limits, module: Module, e match (handlers[i]) { Suspend(tag_index, depth) => { var tag = module.tags[tag_index]; + var pos = suspend_rank++; + var e = ResumeSites.entry(table, pos); + e.tag_index = tag.tag_index; + e.stp_pos = sidetable_entry; var target = getControl(depth); if (target == null) return; checkContHandle(i, tag, cont, target); ctlxfer.refC(target, tag, false, "refH[suspend]"); - handler_indices[i] = addExHandler(-1, opcode_pos, i, suspend_handlers, info, target, false, tag.tag_index, (resume_pos, resume_pos + 1)); + handler_indices[pos] = addExHandler(-1, opcode_pos, i, suspend_handlers, info, target, false, tag.tag_index, (resume_pos, resume_pos + 1)); } Switch(tag_index) => { var tag = module.tags[tag_index]; + var pos = n_suspend + switch_rank++; + var e = ResumeSites.entry(table, pos); + e.tag_index = tag.tag_index; + e.stp_pos = sidetable_entry; var expected = cont.sig.results; var tag_type = module.heaptypes[tag.sig_index]; if (!SigDecl.?(tag_type)) return err_atpc().ExpectedSignature(tag_type); @@ -1574,11 +1607,12 @@ class CodeValidator(extensions: Extension.set, limits: Limits, module: Module, e } } ctlxfer.refC(null, tag, false, "refH[switch]"); - handler_indices[i] = addExHandler(-1, opcode_pos, i, switch_handlers, info, null, true, tag.tag_index, (resume_pos, resume_pos + 1)); + handler_indices[pos] = addExHandler(-1, opcode_pos, i, switch_handlers, info, null, true, tag.tag_index, (resume_pos, resume_pos + 1)); } } } handler_pc_index_map.put((opcode_pos, handler_indices)); + resume_sites.put((opcode_pos, table)); } def checkContHandle(i: int, tag: TagDecl, cont: ContDecl, target: ControlEntry) { var p = labelArgs(target); diff --git a/src/engine/Module.v3 b/src/engine/Module.v3 index 84acdb813..4f75c2845 100644 --- a/src/engine/Module.v3 +++ b/src/engine/Module.v3 @@ -127,6 +127,8 @@ class FuncHandlerInfo { var handler_dest_map: Array = NO_DEST_MAP; var handler_dests: Array = NO_DESTS; var handler_pc_index_map = NO_PC_MAP; + // Handler tables indexed by pc; null for functions without a resume and at other pcs. + var resume_sites: Array>; def get_handler_dest(index: int) -> TargetHandlerDest { // XXX: flatten this lookup with an {Array.new(n_handlers)} where each elem is its target dest? @@ -134,6 +136,36 @@ class FuncHandlerInfo { } } +// Handler table of one resume site: suspend clauses first, then switch clauses. +layout ResumeSiteHeader { + +0 count: i32; + +4 switch_start_index: i32; + =8; +} +layout ResumeHandlerEntry { + +0 tag_index: i32; + +4 handler_pc: i32; + +8 stp_pos: i32; + +12 vsp_slots: i32; + +16 stub_addr: i64; + =24; +} +component ResumeSites { + def alloc(n_suspend: int, n_switch: int) -> Array { + var table = Array.new(ResumeSiteHeader.size + (n_suspend + n_switch) * ResumeHandlerEntry.size); + var h = header(table); + h.count = n_suspend + n_switch; + h.switch_start_index = n_suspend; + return table; + } + def header(table: Array) -> Ref { + return Ref.at(table, 0); + } + def entry(table: Array, i: int) -> Ref { + return Ref.at(table, ResumeSiteHeader.size + i * ResumeHandlerEntry.size); + } +} + // Function declaration, including signature and code. class FuncDecl(sig_index: int) extends Decl { var sig: SigDecl; diff --git a/src/engine/Sidetable.v3 b/src/engine/Sidetable.v3 index d8e99392b..0807b1409 100644 --- a/src/engine/Sidetable.v3 +++ b/src/engine/Sidetable.v3 @@ -167,11 +167,11 @@ layout Sidetable_LexicalRethrowEntry { =16; } -// Entries for catch store the handler pc, destination value stack top, and the sidetable pointer. +// Entries for catch and suspension handlers: handler pc, value stack top, and sidetable delta. layout Sidetable_CatchEntry { +0 handler_pc: u32; +4 val_stack_top: u32; - +8 stp: u32; + +12 stp_delta: u32; =16; } @@ -182,15 +182,6 @@ layout Sidetable_ResumeEntry { =16; } -// Entries for suspension handlers to store the pc, the tag and the stp. -layout Sidetable_SuspendHandlerEntry { - +0 handler_pc: u32; - +4 valcount: u32; - +8 popcount: u32; - +12 stp: u32; - =16; -} - // Entries for memory accesses store the access kind, which classifies how complex it is to decode // the immediates and caches information from the associated memory declaration. layout Sidetable_MemArg { diff --git a/src/engine/compiler/MacroAssembler.v3 b/src/engine/compiler/MacroAssembler.v3 index c97e6abe1..adc7c2da1 100644 --- a/src/engine/compiler/MacroAssembler.v3 +++ b/src/engine/compiler/MacroAssembler.v3 @@ -180,6 +180,9 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { def emit_v3_Instance_globals_r_r(dst: Reg, ptr: Reg) { emit_mov_r_m(ValueKind.REF, dst, MasmAddr(ptr, getOffsets().Instance_globals)); } + def emit_v3_Instance_tags_r_r(dst: Reg, ptr: Reg) { + emit_mov_r_m(ValueKind.REF, dst, MasmAddr(ptr, getOffsets().Instance_tags)); + } def emit_v3_Table_elems_r_r(dst: Reg, ptr: Reg) { emit_mov_r_m(ValueKind.REF, dst, MasmAddr(ptr, getOffsets().Table_elems)); } @@ -227,6 +230,21 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { def emit_push_X86_64Stack_rsp_r_r(stk: Reg); def emit_pop_X86_64Stack_rsp_r_r(stk: Reg); + // Sets {stk}.rsp to sp - 8, the slot the next call fills with the resume address. + def emit_reserve_return_slot(stk: Reg); + + // Finds the suspend or switch clause for {r_tag} above {r_cur}, or branches to {l_trap}. + def emit_handler_search(is_switch: bool, r_tag: Reg, r_cur: Reg, r_parent: Reg, r_prev: Reg, r_entry: Reg, r_cnt: Reg, r_ptags: Reg, l_trap: MasmLabel); + // Replaces {r_parent}'s child chain {r_prev} with {r_target}'s chain, return route included; {r_bottom} gets its bottom. + def emit_replace_child_chain(r_parent: Reg, r_prev: Reg, r_target: Reg, r_bottom: Reg, r_tmp: Reg); + // Redirects the top frame of {r_parent} to the handler {r_entry}. {r_dst} gets the handler's vsp. + def emit_handler_fixup(r_parent: Reg, r_entry: Reg, r_dst: Reg, r_psfp: Reg); + // Copies {n} value-stack slots, tags included, from {r_src} to {r_dst}, fully unrolled. + def emit_value_copy_n(r_dst: Reg, r_src: Reg, n: int, r_xmm: Reg); + // Writes continuation into the value-stack slot at {r_addr} + {offset}. + def emit_store_continuation(r_addr: Reg, offset: int, r_cont: Reg); + // Moves rsp onto {r_stack}, pops the landing address the fixup stored there, saves that rsp on {r_stack} and jumps. + def emit_enter_stack(r_stack: Reg); def scaleOf(kind: ValueKind) -> int { match (kind) { @@ -279,11 +297,13 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { def emit_mov_r_d64(reg: Reg, val: u64); def emit_mov_r_q(reg: Reg, low: u64, high: u64); def emit_mov_r_Object(reg: Reg, obj: Object); + def emit_mov_r_Array(reg: Reg, array: Array); def emit_mov_r_Function(reg: Reg, func: Function); def emit_mov_r_Instance(reg: Reg, instance: Instance); def emit_mov_r_Continuation(reg: Reg, cont: Continuation); def emit_mov_m_r(kind: ValueKind, addr: MasmAddr, reg: Reg); + def emit_mov_m_b(addr: MasmAddr, val: byte); def emit_mov_m_i(addr: MasmAddr, val: int); def emit_mov_m_ri(addr: MasmAddr, val: int); def emit_mov_m_l(addr: MasmAddr, val: long); @@ -338,6 +358,7 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { def emit_call_runtime_callHost(func_arg: Reg); def emit_call_runtime_SUSPEND(); def emit_call_runtime_SWITCH(); + def emit_call_runtime_ALLOC_CONT(); def emit_call_runtime_RESUME_THROW(); def emit_call_runtime_RESUME_THROW_REF(); def emit_jump_to_trap_at(reason: TrapReason); @@ -362,6 +383,18 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { // stk.state_ = state; def emit_set_stack_state(stk: Reg, state: StackState); + // Publishes the handler table. + def emit_publish_handlers(r_stack: Reg, r_table: Reg, tier: HandlerTier) { + var offsets = getOffsets(); + emit_mov_m_r(ValueKind.REF, MasmAddr(r_stack, offsets.X86_64Stack_handlers), r_table); + emit_mov_m_b(MasmAddr(r_stack, offsets.X86_64Stack_handler_tier), byte.view(tier.tag)); + } + // Clears the published handler table of {r_stack}.. + def emit_clear_handlers(r_stack: Reg) { + var offsets = getOffsets(); + emit_mov_m_l(MasmAddr(r_stack, offsets.X86_64Stack_handlers), 0); + emit_mov_m_b(MasmAddr(r_stack, offsets.X86_64Stack_handler_tier), byte.view(HandlerTier.NONE.tag)); + } // This should be called after all stack value transfers, but before the // return address is pushed onto parent's {sp}. // Chains {cont} on {parent} by: @@ -370,42 +403,34 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { // Destructive on {parent}. def emit_cont_mv(from_vsp: Reg, contStack: Reg, n_vals: Reg, tmp1: Reg, tmp2: Reg, xmm0: Reg); - // Validates {cont} and: - // - Mark {cont} as used - // - Move {cont.stack} to {destContStack} - def emit_validate_and_consume_cont(destContStack: Reg, cont: Reg) { - // {cont} is a register matching {Continuations.valueKind}'s register class: - // - boxed mode: GPR holding a {Continuation *} directly. - // - unboxed mode: XMM holding (stack, version) in low/high 8 bytes. + // Validates the continuation in the value-stack slot {slot}, marks it used and moves its stack to {destContStack}. + def emit_validate_and_consume_cont(destContStack: Reg, slot: MasmAddr) { var scratch = regConfig.scratch, offsets = getOffsets(); - if (FeatureDisable.unboxedConts) { // boxed continuation mode - // Check for {cont == null}. - emit_br_r(cont, MasmBrCond.REF_NULL, newTrapLabel(TrapReason.NULL_DEREF)); - // Check for {cont.stack == null} (true when {cont} is already used). - emit_mov_r_m(ValueKind.REF, scratch, MasmAddr(cont, offsets.Continuation_stack)); + emit_mov_r_m(ValueKind.REF, destContStack, slot); + emit_br_r(destContStack, MasmBrCond.REF_NULL, newTrapLabel(TrapReason.NULL_DEREF)); + if (FeatureDisable.unboxedConts) { + emit_mov_r_m(ValueKind.REF, scratch, MasmAddr(destContStack, offsets.Continuation_stack)); emit_br_r(scratch, MasmBrCond.REF_NULL, newTrapLabel(TrapReason.USED_CONTINUATION)); - // Consume {cont} by setting {cont.stack} to null. - emit_mov_m_l(MasmAddr(cont, offsets.Continuation_stack), 0); - // Set {destContStack} to {cont.stack} (its value before being cleared). + emit_mov_m_l(MasmAddr(destContStack, offsets.Continuation_stack), 0); emit_mov_r_r(ValueKind.REF, destContStack, scratch); - } else { // unboxed continuation mode - // Set {destContStack} to {cont.stack} - emit_ref_from_ref_u64(destContStack, cont); - // Check for {cont == null}. - emit_br_r(destContStack, MasmBrCond.REF_NULL, newTrapLabel(TrapReason.NULL_DEREF)); - emit_u64_from_ref_u64(scratch, cont); - // Check for {cont.stack == null} (true when {cont} is already used). + } else { + emit_mov_r_m(ValueKind.REF, scratch, slot.plus(8)); emit_validate_cont_version_unboxed(destContStack, scratch); - // Consume {cont} by incrementing {destContStack.version}. emit_inc_stack_version(destContStack); } } + // Loads the stack of the continuation in the value-stack slot {slot} into {dst}, without checks. + def emit_load_cont_stack(dst: Reg, slot: MasmAddr) { + emit_mov_r_m(ValueKind.REF, dst, slot); + if (FeatureDisable.unboxedConts) emit_v3_Continuation_stack_r_r(dst, dst); + } def emit_validate_cont_version_unboxed(stack: Reg, version: Reg); def emit_inc_stack_version(stack: Reg); - // Called as the last step during resume. Switches {curStack} to the top of {cont}. - def emit_chain_cont_to_parent(parent: Reg, contStack: Reg) { + // Publishes {r_table} on {parent} and chains {contStack} under it; destructive on {parent}. + def emit_chain_cont_to_parent(parent: Reg, contStack: Reg, r_table: Reg, tier: HandlerTier) { var scratch = regConfig.scratch, offsets = getOffsets(); + emit_publish_handlers(parent, r_table, tier); emit_set_stack_state(parent, StackState.CALL_CHILD); // XXX: not needed for {switch} emit_mov_r_r(ValueKind.REF, scratch, contStack); emit_set_stack_state(scratch, StackState.RUNNING); @@ -422,6 +447,21 @@ class MacroAssembler(valuerep: Tagging, regConfig: RegConfig) { // contStack.cont_bottom = null; emit_mov_m_i(MasmAddr(contStack, offsets.X86_64Stack_bottom), 0); } + // Marks {cur} suspended with {prev} as its bottom. Need {arity} values to resume it. + def emit_suspend_stack(cur: Reg, vsp: Reg, prev: Reg, arity: int) { + var offsets = getOffsets(); + emit_v3_set_X86_64Stack_vsp_r_r(cur, vsp); + emit_set_stack_state(cur, StackState.SUSPENDED); + emit_mov_m_i(MasmAddr(cur, offsets.X86_64Stack_params_arity), arity); + emit_mov_m_r(ValueKind.REF, MasmAddr(cur, offsets.X86_64Stack_bottom), prev); + } + // Copies the {n} values at {vsp} and {cont} onto {dest} at {dst}, leaving {dst} past them. + def emit_pass_values(dest: Reg, dst: Reg, vsp: Reg, n: int, cont: Reg, xmm: Reg) { + emit_value_copy_n(dst, vsp, n, xmm); + emit_store_continuation(dst, n * valuerep.slot_size, cont); + emit_addw_r_i(dst, (n + 1) * valuerep.slot_size); + emit_v3_set_X86_64Stack_vsp_r_r(dest, dst); + } def emit_switch_to_stack(r_stack: Reg) { var scratch = regConfig.scratch; emit_set_curstack(r_stack); diff --git a/src/engine/compiler/SinglePassCompiler.v3 b/src/engine/compiler/SinglePassCompiler.v3 index 7e72a8cc8..1d5e7a0eb 100644 --- a/src/engine/compiler/SinglePassCompiler.v3 +++ b/src/engine/compiler/SinglePassCompiler.v3 @@ -880,7 +880,7 @@ class SinglePassCompiler(xenv: SpcExecEnv, masm: MacroAssembler, regAlloc: RegAl m = whamm_instance.module; new_local_base_sp = u31.view(state.sp) - u31.view(whamm_sig.length); // XXX - func_body_ctl.val_stack_top = new_local_base_sp; // correct val_stack_top for whamm arg count + func_body_ctl.val_stack_top = new_local_base_sp; // correct val_stack_top for whamm arg count } // create merge state based on outer function's base sp given inlined function's results @@ -1088,7 +1088,7 @@ class SinglePassCompiler(xenv: SpcExecEnv, masm: MacroAssembler, regAlloc: RegAl Trace.OUT.put1(" catch tag[%s]", tag_display).ln(); Trace.OUT.put2(" handler dest #%d recorded (handler=#%d)", dest_id, handler_id).ln(); } - handler_dest_info[dest_id] = SpcHandlerInfo(false, false, dest_label, masm.newLabel(it.pc), reset_state); + handler_dest_info[dest_id] = SpcHandlerInfo(false, dest_label, masm.newLabel(it.pc), reset_state); // Reset reachability. ctl_top.reachable = true; } @@ -1124,16 +1124,18 @@ class SinglePassCompiler(xenv: SpcExecEnv, masm: MacroAssembler, regAlloc: RegAl if (Trace.compiler) { Trace.OUT.put2(" handler dest #%d recorded (handler=#%d)", dest_id, handler_id).ln(); } - handler_dest_info[dest_id] = SpcHandlerInfo(false, control.opcode == Opcode.RETURN.code, control.label, masm.newLabel(it.pc), control.merge_state); + handler_dest_info[dest_id] = SpcHandlerInfo(control.opcode == Opcode.RETURN.code, control.label, masm.newLabel(it.pc), control.merge_state); } } - def buildSwitchHandler(pc: int, inline_index: int) { - var handler_id = getHandlerIndex(pc, inline_index); - var dest_id = func.handlers.handler_dest_map[handler_id]; - if (Trace.compiler) { - Trace.OUT.put2(" dummy handler dest #%d recorded (handler=#%d)", dest_id, handler_id).ln(); + // Records a handler dest per suspend clause, numbered in the table's suspend-first order. + def buildResumeHandlerDests(pos: int, handlers: Range) { + var suspend_rank = 0; + for (h in handlers) { + match (h) { + Suspend(tag_index, depth) => buildHandlerDest(state.getControl(depth), pos, suspend_rank++); + _ => ; + } } - handler_dest_info[dest_id] = SpcHandlerInfo(true, false, null, null, null); } def emitSelect(valcount: u32) { var sv = pop(); @@ -1449,31 +1451,40 @@ class SinglePassCompiler(xenv: SpcExecEnv, masm: MacroAssembler, regAlloc: RegAl var n_binds = u32.!(in_cont.sig.params.length - out_cont.sig.params.length); emit_call_runtime_op2n(Opcode.CONT_BIND, in_cont_id, out_cont_id, n_binds + 1, ValueTypes.ONE_CONTREF_TYPE, true); } + // Publishes the handler table of the resume at {it.pc} on {curStack}. + // Parks the current stack, publishes the site's handler table, chains {contStack} under it and switches. + def emitResumeChain(curStack: Reg, contStack: Reg, results: Array) { + var stub_resume = masm.newLabel(it.pc), end = masm.newLabel(it.pc); + emit_compute_vsp(regs.vsp, state.sp); + emit_spill_vsp(xenv.vsp); + masm.emit_get_curstack(curStack); + masm.emit_v3_set_X86_64Stack_vsp_r_r(curStack, xenv.vsp); + masm.emit_reserve_return_slot(curStack); + masm.emit_mov_r_Array(xenv.scratch, func.handlers.resume_sites[it.pc]); + masm.emit_chain_cont_to_parent(curStack, contStack, xenv.scratch, HandlerTier.SPC); + masm.emit_call_label(stub_resume); + /* === CHILD STACK RUNNING === */ + state.pushResults(results); + emit_reload_regs(); + state.emitRestoreAll(resolver); + masm.emit_br(end); + masm.bindLabel(stub_resume); + masm.emit_switch_to_stack(contStack); + masm.bindLabel(end); + } def visit_RESUME(cont_id: u31, handlers: Range) { var pos = it.pc; - // record reachable label positions for handler stub - for (i < handlers.length) { - var handler = handlers[i]; - match (handler) { - Suspend(tag_index, depth) => buildHandlerDest(state.getControl(depth), pos, i); - Switch(tag_index) => buildSwitchHandler(pos, i); - } - } + buildResumeHandlerDests(pos, handlers); - var stub_resume = masm.newLabel(it.pc), end = masm.newLabel(it.pc); var cont_decl = ContDecl.!(module.heaptypes[cont_id]); if (checkForConstNull(state.peek())) return; - var vsp = allocTmpFixed(ValueKind.REF, regs.vsp); - var top = popReg(); - var cont = allocTmp(Continuations.valueKind); - var contStack = allocTmp(ValueKind.REF); - masm.emit_mov_r_r(Continuations.valueKind, cont, top.reg); - masm.emit_validate_and_consume_cont(contStack, cont); - - // transfer params to child stack // XXX: not necessary to store the entire value stack to memory state.emitSaveAll(resolver, SpillMode.SAVE_AND_FREE_REGS); + var vsp = allocTmpFixed(ValueKind.REF, regs.vsp); + var contStack = allocTmp(ValueKind.REF); + masm.emit_validate_and_consume_cont(contStack, masm.slotAddr(state.sp - 1)); + state.pop(); var curStack = allocTmp(ValueKind.REF); var nvals = allocTmp(ValueKind.REF); var tmp0 = allocTmp(ValueKind.REF); @@ -1487,143 +1498,125 @@ class SinglePassCompiler(xenv: SpcExecEnv, masm: MacroAssembler, regAlloc: RegAl dropN(u32.!(cont_decl.sig.params.length)); state.emitRestoreAll(resolver); - // context switch: store to old stack - masm.emit_get_curstack(curStack); - emit_compute_vsp(regs.vsp, state.sp); - emit_spill_vsp(xenv.vsp); - masm.emit_v3_set_X86_64Stack_vsp_r_r(curStack, xenv.vsp); - masm.emit_v3_set_X86_64Stack_rsp_r_r(curStack, xenv.sp); - - // reserve space for call addr - masm.emit_push_X86_64Stack_rsp_r_r(curStack); - masm.emit_chain_cont_to_parent(curStack, contStack); - - masm.emit_call_label(stub_resume); - /* === CHILD STACK RUNNING === */ - - // clean up after stack switch - state.pushResults(cont_decl.sig.results); - emit_reload_regs(); - state.emitRestoreAll(resolver); - - // skip {stub_resume} - masm.emit_br(end); - masm.bindLabel(stub_resume); - masm.emit_switch_to_stack(contStack); - masm.bindLabel(end); + emitResumeChain(curStack, contStack, cont_decl.sig.results); } def visit_SUSPEND(tag_index: u31) { - var stub_suspend = masm.newLabel(it.pc), end = masm.newLabel(it.pc); - + var sig = SigDecl.!(module.heaptypes[module.tags[tag_index].sig_index]); state.emitSaveAll(resolver, SpillMode.SAVE_AND_FREE_REGS); - - emit_compute_vsp(regs.vsp, state.sp); - emit_spill_vsp(xenv.vsp); - masm.emit_store_curstack_vsp(regs.vsp); - masm.emit_get_curstack(regs.runtime_arg0); - masm.emit_v3_set_X86_64Stack_rsp_r_r(regs.runtime_arg0, regs.sp); - masm.emit_push_X86_64Stack_rsp_r_r(regs.runtime_arg0); - emit_load_instance(regs.runtime_arg1); - masm.emit_mov_r_i(regs.runtime_arg2, tag_index); - masm.emit_call_runtime_SUSPEND(); - - var sig_index = module.tags[tag_index].sig_index; - state.popArgsAndPushResults(SigDecl.!(module.heaptypes[sig_index])); - masm.emit_call_label(stub_suspend); - - /* === PARENT STACK RUNNING === */ - - emit_reload_regs(); - state.emitRestoreAll(resolver); - - // skip {stub_suspend} - masm.emit_br(end); - masm.bindLabel(stub_suspend); { - // load new stack pointers - masm.emit_get_curstack(regs.scratch); - // unlike fast-int, the ptr offset for RT calls is done outside of the `call_runtime_...` - // shorthands, so manually popping %rsp is needed - masm.emit_pop_X86_64Stack_rsp_r_r(regs.scratch); - // pop and go to handler stack's stub - masm.emit_v3_X86_64Stack_rsp_r_r(regs.sp, regs.scratch); - masm.emit_pop_X86_64Stack_rsp_r_r(regs.scratch); - masm.emit_pop_r(ValueKind.REF, xenv.scratch); - masm.emit_jump_r(xenv.scratch); - } - masm.bindLabel(end); + emitInlineSuspend(sig, tag_index); } def visit_SWITCH(target_cont_idx: u31, tag_index: u31) { - var stub_switch = masm.newLabel(it.pc), end = masm.newLabel(it.pc); - + var target_decl = ContDecl.!(module.heaptypes[target_cont_idx]); + var target_top = target_decl.sig.params[target_decl.sig.params.length - 1]; + var this_decl = ContDecl.!(HeapType.Cont.!(ValueType.Ref.!(target_top).heap).cont); state.emitSaveAll(resolver, SpillMode.SAVE_AND_FREE_REGS); - + emitInlineSwitch(target_decl, this_decl, tag_index); + } + // Spills vsp and reserves the resume slot so the current stack can be left and resumed later. + def emitPrepareToLeaveStack(vsp: Reg, cur: Reg) { + emit_compute_vsp(vsp, state.sp); + emit_spill_vsp(vsp); + masm.emit_get_curstack(cur); + masm.emit_reserve_return_slot(cur); + } + // Boxed continuations only: allocates the continuation object of the current stack into a new temporary. + def emitAllocContinuation() -> Reg { emit_compute_vsp(regs.vsp, state.sp); - emit_spill_vsp(xenv.vsp); + emit_spill_vsp(regs.vsp); masm.emit_store_curstack_vsp(regs.vsp); masm.emit_get_curstack(regs.runtime_arg0); masm.emit_v3_set_X86_64Stack_rsp_r_r(regs.runtime_arg0, regs.sp); masm.emit_push_X86_64Stack_rsp_r_r(regs.runtime_arg0); - emit_load_instance(regs.runtime_arg1); - masm.emit_mov_r_i(regs.runtime_arg2, target_cont_idx); - masm.emit_mov_r_i(regs.runtime_arg3, tag_index); - masm.emit_call_runtime_SWITCH(); - - // unraveling all relevant signatures - var target_cont_decl = ContDecl.!(module.heaptypes[target_cont_idx]); - var target_top_val = target_cont_decl.sig.params[target_cont_decl.sig.params.length - 1]; - var this_cont_decl = ContDecl.!(HeapType.Cont.!(ValueType.Ref.!(target_top_val).heap).cont); - - state.pop(); // target_cont - state.sp -= u32.view(target_cont_decl.sig.params.length - 1); // params to target_cont - state.pushResults(this_cont_decl.sig.params); // params to this_cont - masm.emit_call_label(stub_switch); - - // /* === SUSPENDED === */ - + masm.emit_call_runtime_ALLOC_CONT(); + masm.emit_get_curstack(regs.scratch); + masm.emit_pop_X86_64Stack_rsp_r_r(regs.scratch); + emit_reload_regs(); + var cont = allocTmp(ValueKind.REF); + if (cont != regs.runtime_ret0) masm.emit_mov_r_r(ValueKind.REF, cont, regs.runtime_ret0); + return cont; + } + // Calls the stub that {emitEnter} emits to enter another stack, and lands the resumed frame after the call. + def emitLeaveStack(emitEnter: void -> void) { + var stub = masm.newLabel(it.pc), end = masm.newLabel(it.pc); + masm.emit_call_label(stub); + /* === OTHER STACK RUNNING === */ emit_reload_regs(); state.emitRestoreAll(resolver); - - // skip {stub_switch} masm.emit_br(end); - masm.bindLabel(stub_switch); { - // load new stack pointers - masm.emit_get_curstack(regs.scratch); - // unlike fast-int, the ptr offset for RT calls is done outside of the `call_runtime_...` - // shorthands, so manually popping %rsp is needed - masm.emit_pop_X86_64Stack_rsp_r_r(regs.scratch); - // pop and go to handler stack's stub - masm.emit_v3_X86_64Stack_rsp_r_r(regs.sp, regs.scratch); - masm.emit_pop_X86_64Stack_rsp_r_r(regs.scratch); - masm.emit_pop_r(ValueKind.REF, xenv.scratch); - masm.emit_jump_r(xenv.scratch); - } + masm.bindLabel(stub); + emitEnter(); masm.bindLabel(end); } + // Makes {dest} the running stack and enters it. + def emitEnterStack(dest: Reg) { + masm.emit_set_stack_state(dest, StackState.RUNNING); + masm.emit_set_curstack(dest); + emitLeaveStack(fun => masm.emit_enter_stack(dest)); + } + // Loads the module tag {tag_index} of this instance into {r}. + def emit_load_tag(r: Reg, tag_index: u31) { + emit_load_instance(r); + masm.emit_v3_Instance_tags_r_r(r, r); + masm.emit_v3_Array_elem_r_ri(ValueKind.REF, r, r, int.!(tag_index)); + } + // Finds the handler stack, redirects its top frame, moves the payload and switches to it. + def emitInlineSuspend(sig: SigDecl, tag_index: u31) { + var n = sig.params.length, slot_size = masm.valuerep.slot_size, offsets = masm.getOffsets(); + var vsp = allocTmp(ValueKind.REF), cur = allocTmp(ValueKind.REF), prev = allocTmp(ValueKind.REF), parent = allocTmp(ValueKind.REF); + var tag = allocTmp(ValueKind.REF), entry = allocTmp(ValueKind.REF), cnt = allocTmp(ValueKind.REF), ptags = allocTmp(ValueKind.REF); + var xmm = allocTmp(ValueKind.V128); + var cont = if(FeatureDisable.unboxedConts, emitAllocContinuation(), cur); + emitPrepareToLeaveStack(vsp, cur); + emit_load_tag(tag, tag_index); + masm.emit_handler_search(false, tag, cur, parent, prev, entry, cnt, ptags, masm.newTrapLabel(TrapReason.INVALID_SUSPEND)); + var dst = tag, psfp = cnt; + masm.emit_handler_fixup(parent, entry, dst, psfp); + if (n > 0) masm.emit_addw_r_i(vsp, 0 - n * slot_size); + masm.emit_pass_values(parent, dst, vsp, n, cont, xmm); + masm.emit_mov_m_r(ValueKind.REF, MasmAddr(psfp, frame.vsp_slot.offset), dst); + masm.emit_suspend_stack(cur, vsp, prev, sig.results.length); + masm.emit_mov_m_l(MasmAddr(prev, offsets.X86_64Stack_parent), 0); + masm.emit_clear_handlers(parent); + state.popArgsAndPushResults(sig); + emitEnterStack(parent); + } + // Consumes the target continuation, splices its chain under the switch handler and switches to it. + def emitInlineSwitch(target_decl: ContDecl, this_decl: ContDecl, tag_index: u31) { + var n = target_decl.sig.params.length - 1, slot_size = masm.valuerep.slot_size; + var vsp = allocTmp(ValueKind.REF), cur = allocTmp(ValueKind.REF), target = allocTmp(ValueKind.REF); + var prev = allocTmp(ValueKind.REF), parent = allocTmp(ValueKind.REF), tag = allocTmp(ValueKind.REF); + var entry = allocTmp(ValueKind.REF), cnt = allocTmp(ValueKind.REF), ptags = allocTmp(ValueKind.REF); + var xmm = allocTmp(ValueKind.V128); + var cont = if(FeatureDisable.unboxedConts, emitAllocContinuation(), cur); + emitPrepareToLeaveStack(vsp, cur); + masm.emit_validate_and_consume_cont(target, MasmAddr(vsp, masm.valuerep.tag_size - slot_size)); + emit_load_tag(tag, tag_index); + masm.emit_handler_search(true, tag, cur, parent, prev, entry, cnt, ptags, masm.newTrapLabel(TrapReason.INVALID_SUSPEND)); + var bottom = entry, dst = tag, tmp = cnt; + masm.emit_replace_child_chain(parent, prev, target, bottom, tmp); + masm.emit_addw_r_i(vsp, 0 - (n + 1) * slot_size); + masm.emit_suspend_stack(cur, vsp, prev, this_decl.sig.params.length); + masm.emit_v3_X86_64Stack_vsp_r_r(dst, target); + masm.emit_pass_values(target, dst, vsp, n, cont, xmm); + state.pop(); + state.sp -= u32.view(n); + state.pushResults(this_decl.sig.params); + emitEnterStack(target); + } def visit_RESUME_THROW(cont_id: u31, tag_id: u31, handlers: Range) { var pos = it.pc; - // record reachable label positions for handler stub - for (i < handlers.length) { - var handler = handlers[i]; - match (handler) { - Suspend(tag_index, depth) => buildHandlerDest(state.getControl(depth), pos, i); - Switch(tag_index) => buildSwitchHandler(pos, i); - } - } + buildResumeHandlerDests(pos, handlers); - var stub_resume = masm.newLabel(it.pc), end = masm.newLabel(it.pc); var cont_decl = ContDecl.!(module.heaptypes[cont_id]); var tag = module.tags[tag_id]; if (checkForConstNull(state.peek())) return; + state.emitSaveAll(resolver, SpillMode.SAVE_AND_FREE_REGS); var vsp = allocTmpFixed(ValueKind.REF, regs.vsp); var curStack = allocTmp(ValueKind.REF); - - var cont = allocTmp(Continuations.valueKind); var contStack = allocTmp(ValueKind.REF); - masm.emit_mov_r_r(Continuations.valueKind, cont, popReg().reg); - masm.emit_validate_and_consume_cont(contStack, cont); - - state.emitSaveAll(resolver, SpillMode.SAVE_AND_FREE_REGS); + masm.emit_validate_and_consume_cont(contStack, masm.slotAddr(state.sp - 1)); + state.pop(); masm.emit_mov_r_r(ValueKind.REF, regs.runtime_arg2, contStack); emit_compute_vsp(regs.vsp, state.sp); emit_spill_vsp(xenv.vsp); @@ -1642,67 +1635,25 @@ class SinglePassCompiler(xenv: SpcExecEnv, masm: MacroAssembler, regAlloc: RegAl /* === only reachable when continuation handles the error === */ emit_reload_regs(); state.emitRestoreAll(resolver); - masm.emit_mov_r_r(Continuations.valueKind, cont, popReg().reg); - // extract {cont.stack} into contStack: in boxed mode {cont} is a {Continuation *} - // (load .stack field); in unboxed mode {cont} is XMM with low 8 bytes = stack. - if (FeatureDisable.unboxedConts) { - masm.emit_v3_Continuation_stack_r_r(contStack, cont); - } else { - masm.emit_ref_from_ref_u64(contStack, cont); - } - - // save %vsp to frame - emit_compute_vsp(regs.vsp, state.sp); - emit_spill_vsp(xenv.vsp); - - // chain {contStack} to {curStack}, then set {m_curStack} to {cont.top}. - masm.emit_get_curstack(curStack); - masm.emit_v3_set_X86_64Stack_vsp_r_r(curStack, xenv.vsp); - masm.emit_v3_set_X86_64Stack_rsp_r_r(curStack, xenv.sp); - - // reserve space for call addr - masm.emit_push_X86_64Stack_rsp_r_r(curStack); - masm.emit_chain_cont_to_parent(curStack, contStack); - - masm.emit_call_label(stub_resume); - /* === CHILD STACK RUNNING === */ - - // clean up after stack switch - state.pushResults(cont_decl.sig.results); - emit_reload_regs(); - state.emitRestoreAll(resolver); + masm.emit_load_cont_stack(contStack, masm.slotAddr(state.sp - 1)); + state.pop(); - // skip {stub_resume} - masm.emit_br(end); - masm.bindLabel(stub_resume); - masm.emit_switch_to_stack(contStack); - masm.bindLabel(end); + emitResumeChain(curStack, contStack, cont_decl.sig.results); } def visit_RESUME_THROW_REF(cont_id: u31, handlers: Range) { var pos = it.pc; - // record reachable label positions for handler stub - for (i < handlers.length) { - var handler = handlers[i]; - match (handler) { - Suspend(tag_index, depth) => buildHandlerDest(state.getControl(depth), pos, i); - Switch(tag_index) => buildSwitchHandler(pos, i); - } - } + buildResumeHandlerDests(pos, handlers); - var stub_resume = masm.newLabel(it.pc), end = masm.newLabel(it.pc); var cont_decl = ContDecl.!(module.heaptypes[cont_id]); if (checkForConstNull(state.peek())) return; - var vsp = allocTmpFixed(ValueKind.REF, regs.vsp); - var curStack = allocTmp(ValueKind.REF); - - var cont = allocTmp(Continuations.valueKind); var contStack = allocTmp(ValueKind.REF); - masm.emit_mov_r_r(Continuations.valueKind, cont, popReg().reg); - masm.emit_validate_and_consume_cont(contStack, cont); - + masm.emit_validate_and_consume_cont(contStack, masm.slotAddr(state.sp - 1)); + state.pop(); popFixedReg(regs.runtime_arg3); state.emitSaveAll(resolver, SpillMode.SAVE_AND_FREE_REGS); + var vsp = allocTmpFixed(ValueKind.REF, regs.vsp); + var curStack = allocTmp(ValueKind.REF); masm.emit_mov_r_r(ValueKind.REF, regs.runtime_arg2, contStack); emit_compute_vsp(regs.vsp, state.sp); emit_spill_vsp(xenv.vsp); @@ -1719,41 +1670,10 @@ class SinglePassCompiler(xenv: SpcExecEnv, masm: MacroAssembler, regAlloc: RegAl /* === only reachable when continuation handles the error === */ emit_reload_regs(); state.emitRestoreAll(resolver); - masm.emit_mov_r_r(Continuations.valueKind, cont, popReg().reg); - // extract {cont.stack} into contStack: in boxed mode {cont} is a {Continuation *} - // (load .stack field); in unboxed mode {cont} is XMM with low 8 bytes = stack. - if (FeatureDisable.unboxedConts) { - masm.emit_v3_Continuation_stack_r_r(contStack, cont); - } else { - masm.emit_ref_from_ref_u64(contStack, cont); - } - - // save %vsp to frame - emit_compute_vsp(regs.vsp, state.sp); - emit_spill_vsp(xenv.vsp); - - // chain {contStack} to {curStack}, then set {m_curStack} to {cont.top}. - masm.emit_get_curstack(curStack); - masm.emit_v3_set_X86_64Stack_vsp_r_r(curStack, xenv.vsp); - masm.emit_v3_set_X86_64Stack_rsp_r_r(curStack, xenv.sp); - - // reserve space for call addr - masm.emit_push_X86_64Stack_rsp_r_r(curStack); - masm.emit_chain_cont_to_parent(curStack, contStack); - - masm.emit_call_label(stub_resume); - /* === CHILD STACK RUNNING === */ - - // clean up after stack switch - state.pushResults(cont_decl.sig.results); - emit_reload_regs(); - state.emitRestoreAll(resolver); + masm.emit_load_cont_stack(contStack, masm.slotAddr(state.sp - 1)); + state.pop(); - // skip {stub_resume} - masm.emit_br(end); - masm.bindLabel(stub_resume); - masm.emit_switch_to_stack(contStack); - masm.bindLabel(end); + emitResumeChain(curStack, contStack, cont_decl.sig.results); } private def checkForConstNull(sv: SpcVal) -> bool { if (sv.isConst() && sv.const == 0) { @@ -2771,7 +2691,7 @@ class SinglePassCompiler(xenv: SpcExecEnv, masm: MacroAssembler, regAlloc: RegAl var bi = BytecodeIterator.new().reset(func); while (bi.more()) { match (bi.current()) { - RETURN_CALL, RETURN_CALL_INDIRECT, RETURN_CALL_REF => + RETURN_CALL, RETURN_CALL_INDIRECT, RETURN_CALL_REF => return no("uses return instruction"); TRY, CATCH, THROW, RETHROW, THROW_REF, DELEGATE, CATCH_ALL, TRY_TABLE => return no("uses exception handling instruction"); @@ -3509,4 +3429,4 @@ type WhammInlineConfig(swap_membase: bool, swap_instance: bool, is_inlined: bool // // The SPC emits a stub at {stub_label} for each handler in the function. The stub restores the // expected state of the environment, then jumps to {dest_label} to continue execution at handler. -type SpcHandlerInfo(is_dummy: bool, func_end: bool, dest_label: MasmLabel, stub_label: MasmLabel, merge_state: Array); +type SpcHandlerInfo(func_end: bool, dest_label: MasmLabel, stub_label: MasmLabel, merge_state: Array); diff --git a/src/engine/v3/V3Interpreter.v3 b/src/engine/v3/V3Interpreter.v3 index 09abc5e90..022056c19 100644 --- a/src/engine/v3/V3Interpreter.v3 +++ b/src/engine/v3/V3Interpreter.v3 @@ -1695,7 +1695,7 @@ class V3Interpreter extends WasmStack { // XXX: do not use sidetable for this var entry = frame.func.decl.sidetable.getResumeEntry(frame.stp); frame.stp += Sidetable_ResumeEntry.size / 4; - frame.stp += entry.nhandlers * Sidetable_SuspendHandlerEntry.size / 4; + frame.stp += entry.nhandlers * Sidetable_CatchEntry.size / 4; match (opcode) { RESUME, RESUME_THROW, RESUME_THROW_REF => ; _ => fail(Strings.format1("expected resume instruction in child terminate, got %s", opcode.name)); diff --git a/src/engine/x86-64/V3Offsets.v3 b/src/engine/x86-64/V3Offsets.v3 index 9fb90e5d5..ca689e6e0 100644 --- a/src/engine/x86-64/V3Offsets.v3 +++ b/src/engine/x86-64/V3Offsets.v3 @@ -10,6 +10,7 @@ class V3Offsets { private def sig = SigCache.i_i; private def cont_decl = ContDecl.new(false, [], HeapType.ANY); private def decl = FuncDecl.new(0); + private def hinfo = decl.handlers; private def i = Instance.new(module, null); private def t = Table.new(ValueTypes.FUNCREF, TableDecl.new(ValueTypes.FUNCREF, SizeConstraint(false, 0, Max.Set(1)))); private def wf = WasmFunction.new(i, decl); @@ -33,6 +34,9 @@ class V3Offsets { def FuncDecl_tierup_trigger = int.view(Pointer.atField(decl.tierup_trigger) - Pointer.atObject(decl)); def FuncDecl_entry_probed = int.view(Pointer.atField(decl.entry_probed) - Pointer.atObject(decl)); def FuncDecl_frame_var_tags = int.view(Pointer.atField(decl.frame_var_tags) - Pointer.atObject(decl)); + def FuncDecl_handlers = int.view(Pointer.atField(decl.handlers) - Pointer.atObject(decl)); + + def FuncHandlerInfo_resume_sites = int.view(Pointer.atField(hinfo.resume_sites) - Pointer.atObject(hinfo)); def SigDecl_params = int.view(Pointer.atField(sig.params) - Pointer.atObject(sig)); def SigDecl_results = int.view(Pointer.atField(sig.results) - Pointer.atObject(sig)); @@ -47,6 +51,7 @@ class V3Offsets { def Instance_sig_ids = int.view(Pointer.atField(i.sig_ids) - Pointer.atObject(i)); def Instance_dropped_elems = int.view(Pointer.atField(i.dropped_elems) - Pointer.atObject(i)); def Instance_dropped_data = int.view(Pointer.atField(i.dropped_data) - Pointer.atObject(i)); + def Instance_tags = int.view(Pointer.atField(i.tags) - Pointer.atObject(i)); def Table_funcs = int.view(Pointer.atField(t.funcs) - Pointer.atObject(t)); def Table_elems = int.view(Pointer.atField(t.elems) - Pointer.atObject(t)); @@ -72,8 +77,11 @@ class V3Offsets { def X86_64Stack_parent = int.view(Pointer.atField(vs.parent) - Pointer.atObject(vs)); def X86_64Stack_bottom = int.view(Pointer.atField(vs.cont_bottom) - Pointer.atObject(vs)); def X86_64Stack_state = int.view(Pointer.atField(vs.state_) - Pointer.atObject(vs)); + def X86_64Stack_params_arity = int.view(Pointer.atField(vs.params_arity) - Pointer.atObject(vs)); def X86_64Stack_return_results = int.view(Pointer.atField(vs.return_results) - Pointer.atObject(vs)); def X86_64Stack_next_stack = int.view(Pointer.atField(vs.next_stack) - Pointer.atObject(vs)); + def X86_64Stack_handlers = int.view(Pointer.atField(vs.handlers) - Pointer.atObject(vs)); + def X86_64Stack_handler_tier = int.view(Pointer.atField(vs.handler_tier) - Pointer.atObject(vs)); def WasmFunction_typeId = Pointer.atObject(wf).load(); def ContDecl_typeId = Pointer.atObject(cont_decl).load(); diff --git a/src/engine/x86-64/X86_64Interpreter.v3 b/src/engine/x86-64/X86_64Interpreter.v3 index 34563cf64..79c8e0889 100644 --- a/src/engine/x86-64/X86_64Interpreter.v3 +++ b/src/engine/x86-64/X86_64Interpreter.v3 @@ -2494,17 +2494,12 @@ class X86_64InterpreterGen(ic: X86_64InterpreterCode, w: DataWriter) { } bindHandler(Opcode.RESUME); { var cont_id = xenv.tmp0; - computeCurIpForTrap(-1); - computePcFromCurIp(); - saveCallerIVars(); + saveResumeIVars(); genReadUleb32(G(cont_id)); // read signature index - // {cont} register matches {Continuations.valueKind}: GPR in boxed mode, - // XMM in unboxed mode. Pick a free reg of the right class. - var cont = if(FeatureDisable.unboxedConts, xenv.tmp3, xenv.xmm0); var contStack = xenv.tmp1; - genPopCont(cont); - masm.emit_validate_and_consume_cont(contStack, cont); + decrementVsp(); + masm.emit_validate_and_consume_cont(contStack, MasmAddr(xenv.vsp, valuerep.tag_size)); // move parameter values from %curStack to %cont.top var r_nvals = cont_id; // reuse due to non-overlapping live ranges @@ -2515,38 +2510,17 @@ class X86_64InterpreterGen(ic: X86_64InterpreterCode, w: DataWriter) { masm.emit_v3_Array_length_r_r(r_nvals, xenv.scratch); masm.emit_cont_mv(xenv.vsp, contStack, r_nvals, xenv.tmp3, xenv.tmp4, xenv.xmm0); - // chain {cont to {curStack}, then set {m_curStack} to {cont.top}. - var curStack = xenv.tmp4; - masm.emit_get_curstack(curStack); - masm.emit_v3_set_X86_64Stack_vsp_r_r(curStack, xenv.vsp); - masm.emit_v3_set_X86_64Stack_rsp_r_r(curStack, xenv.sp); - // reserve space for call addr - masm.emit_push_X86_64Stack_rsp_r_r(curStack); - masm.emit_chain_cont_to_parent(curStack, contStack); - - var stub_resume = X86_64Label.new(); - asm.call_rel_far(stub_resume); - /* === CHILD STACK RETURNS HERE === */ - genOnResumeFinish(false); - endHandler(); - // switch to child stack - asm.bind(stub_resume); - masm.emit_switch_to_stack(contStack); + genResumeChain(xenv.tmp4, contStack, false); } bindHandler(Opcode.RESUME_THROW); { var tag_id = xenv.tmp0; - computeCurIpForTrap(-1); - computePcFromCurIp(); - saveCallerIVars(); + saveResumeIVars(); genSkipLeb(); // skip signature index genReadUleb32(G(tag_id)); // read tag index - // {cont} register matches {Continuations.valueKind}: GPR in boxed mode, - // XMM in unboxed mode. - var cont = if(FeatureDisable.unboxedConts, xenv.tmp3, xenv.xmm0); var contStack = xenv.tmp1; - genPopCont(cont); - masm.emit_validate_and_consume_cont(contStack, cont); + decrementVsp(); + masm.emit_validate_and_consume_cont(contStack, MasmAddr(xenv.vsp, valuerep.tag_size)); var curStack = xenv.tmp4; masm.emit_get_curstack(curStack); @@ -2556,44 +2530,20 @@ class X86_64InterpreterGen(ic: X86_64InterpreterCode, w: DataWriter) { ); restoreCallerIVars(); /* === only reachable when continuation handles the error === */ - genPopCont(cont); // XXX: make RT function return {Continuation} directly - // extract {cont.stack} into contStack; mode-aware as in SPC RESUME_THROW. - if (FeatureDisable.unboxedConts) { - masm.emit_v3_Continuation_stack_r_r(contStack, cont); - } else { - masm.emit_ref_from_ref_u64(contStack, cont); - } + decrementVsp(); // XXX: make RT function return {Continuation} directly + masm.emit_load_cont_stack(contStack, MasmAddr(xenv.vsp, valuerep.tag_size)); - // chain {cont} to {curStack}, then set {m_curStack} to {cont.top}. - masm.emit_get_curstack(curStack); - masm.emit_v3_set_X86_64Stack_vsp_r_r(curStack, xenv.vsp); - masm.emit_v3_set_X86_64Stack_rsp_r_r(curStack, xenv.sp); - // reserve space for call addr - masm.emit_push_X86_64Stack_rsp_r_r(curStack); - masm.emit_chain_cont_to_parent(curStack, contStack); - - var stub_resume = X86_64Label.new(); - asm.call_rel_far(stub_resume); - /* === CHILD STACK RETURNS HERE === */ - genOnResumeFinish(true); - endHandler(); - asm.bind(stub_resume); - masm.emit_switch_to_stack(contStack); + genResumeChain(curStack, contStack, true); } bindHandler(Opcode.RESUME_THROW_REF); { - computeCurIpForTrap(-1); - computePcFromCurIp(); - saveCallerIVars(); + saveResumeIVars(); genSkipLeb(); // skip signature index - // {cont} register matches {Continuations.valueKind}: GPR in boxed mode, - // XMM in unboxed mode. - var cont = if(FeatureDisable.unboxedConts, xenv.tmp3, xenv.xmm0); var ex = xenv.tmp0; var contStack = xenv.tmp1; - genPopCont(cont); + decrementVsp(); + masm.emit_validate_and_consume_cont(contStack, MasmAddr(xenv.vsp, valuerep.tag_size)); genPopInto(G(ex)); - masm.emit_validate_and_consume_cont(contStack, cont); var curStack = xenv.tmp4; masm.emit_get_curstack(curStack); @@ -2603,29 +2553,10 @@ class X86_64InterpreterGen(ic: X86_64InterpreterCode, w: DataWriter) { ); restoreCallerIVars(); /* === only reachable when continuation handles the error === */ - genPopCont(cont); // XXX: make RT function return {Continuation} directly - // extract {cont.stack} into contStack; mode-aware as in SPC RESUME_THROW_REF. - if (FeatureDisable.unboxedConts) { - masm.emit_v3_Continuation_stack_r_r(contStack, cont); - } else { - masm.emit_ref_from_ref_u64(contStack, cont); - } + decrementVsp(); // XXX: make RT function return {Continuation} directly + masm.emit_load_cont_stack(contStack, MasmAddr(xenv.vsp, valuerep.tag_size)); - // chain {cont} to {curStack}, then set {m_curStack} to {cont.top}. - masm.emit_get_curstack(curStack); - masm.emit_v3_set_X86_64Stack_vsp_r_r(curStack, xenv.vsp); - masm.emit_v3_set_X86_64Stack_rsp_r_r(curStack, xenv.sp); - // reserve space for call addr - masm.emit_push_X86_64Stack_rsp_r_r(curStack); - masm.emit_chain_cont_to_parent(curStack, contStack); - - var stub_resume = X86_64Label.new(); - asm.call_rel_far(stub_resume); - /* === CHILD STACK RETURNS HERE === */ - genOnResumeFinish(false); - endHandler(); - asm.bind(stub_resume); - masm.emit_switch_to_stack(contStack); + genResumeChain(curStack, contStack, false); } bindHandler(Opcode.SUSPEND); { var tag_index = r_tmp0; @@ -4210,6 +4141,34 @@ class X86_64InterpreterGen(ic: X86_64InterpreterCode, w: DataWriter) { def computePcFromCurIp() { if (!FeatureDisable.stacktraces) asm.q.sub_r_m(r_curpc, m_code); } + def saveResumeIVars() { + masm.emit_compute_pc(-1); + saveCallerIVars(); + if (FeatureDisable.stacktraces) masm.emit_save_ivar(xenv.curpc); + } + // Loads the handler table of the resume at the frame's saved pc into {r_tmp}. + def genLoadHandlerTable(r_tmp: Reg) { + var t = G(r_tmp); + masm.emit_restore_ivar(xenv.curpc); // runtime calls clobber {r_curpc} + asm.movq_r_m(t, r_func_decl.plus(offsets.FuncDecl_handlers)); + asm.movq_r_m(t, t.plus(offsets.FuncHandlerInfo_resume_sites)); + masm.emit_v3_Array_elem_r_rr(ValueKind.REF, r_tmp, r_tmp, xenv.curpc); + } + // Parks the current stack, publishes the handler table, chains {contStack} under it and switches. + def genResumeChain(curStack: Reg, contStack: Reg, skip_tag: bool) { + masm.emit_get_curstack(curStack); + masm.emit_v3_set_X86_64Stack_vsp_r_r(curStack, xenv.vsp); + masm.emit_reserve_return_slot(curStack); + genLoadHandlerTable(xenv.tmp0); + masm.emit_chain_cont_to_parent(curStack, contStack, xenv.tmp0, HandlerTier.INTERPRETER); + var stub_resume = X86_64Label.new(); + asm.call_rel_far(stub_resume); + /* === CHILD STACK RETURNS HERE === */ + genOnResumeFinish(skip_tag); + endHandler(); + asm.bind(stub_resume); + masm.emit_switch_to_stack(contStack); + } // All traps are generated out-of-line and call into the runtime. def genTraps() { w.atEnd(); @@ -4278,21 +4237,6 @@ class X86_64InterpreterGen(ic: X86_64InterpreterCode, w: DataWriter) { decrementVsp(); asm.movq_r_m(reg, vsph[0].value); } - // Pops a continuation value from the value stack into {reg}, which must - // match {Continuations.valueKind}'s register class: - // - boxed mode: GPR receives the 8-byte Continuation pointer. - // - unboxed mode: XMM receives the 16-byte (stack, version) pair. - private def genPopCont(reg: Reg) { - decrementVsp(); - if (FeatureDisable.unboxedConts) { - asm.movq_r_m(G(reg), vsph[0].value); - } else { - asm.movq_r_m(r_scratch, vsph[0].value); - asm.pinsrq_s_r_i(X(reg), r_scratch, 0); - asm.movd_r_m(r_scratch, vsph[0].value.plus(8)); - asm.pinsrq_s_r_i(X(reg), r_scratch, 1); - } - } // Given {idx} containing a heap-type SLEB value (negative = abstract heap-type code, // non-negative = index into instance.heaptypes), emits code that jumps to {is_cont_label} // if the encoded type is a continuation, and falls through otherwise. @@ -4355,7 +4299,7 @@ class X86_64InterpreterGen(ic: X86_64InterpreterCode, w: DataWriter) { // adjust sidetable asm.q.add_r_i(r_stp, Sidetable_ResumeEntry.size); // calculate length of handler entries in sidetable - asm.q.imul_r_i(handler_length, Sidetable_SuspendHandlerEntry.size); + asm.q.imul_r_i(handler_length, Sidetable_CatchEntry.size); asm.q.add_r_r(r_stp, handler_length); } } diff --git a/src/engine/x86-64/X86_64MacroAssembler.v3 b/src/engine/x86-64/X86_64MacroAssembler.v3 index 9e341f030..a901a1e2a 100644 --- a/src/engine/x86-64/X86_64MacroAssembler.v3 +++ b/src/engine/x86-64/X86_64MacroAssembler.v3 @@ -133,6 +133,10 @@ class X86_64MacroAssembler extends MacroAssembler { def emit_pop_X86_64Stack_rsp_r_r(stk: Reg) { asm.q.add_m_i(G(stk).plus(offsets.X86_64Stack_rsp), Pointer.SIZE); } + def emit_reserve_return_slot(stk: Reg) { + asm.q.lea(scratch, R.RSP.plus(0 - Pointer.SIZE)); + asm.movq_m_r(G(stk).plus(offsets.X86_64Stack_rsp), scratch); + } def emit_loadbsx_r_r_r_i(kind: ValueKind, dst: Reg, base: Reg, index: Reg, offset: u32) { var t = handle_large_offset(index, offset); @@ -284,6 +288,9 @@ class X86_64MacroAssembler extends MacroAssembler { def emit_mov_r_Object(reg: Reg, obj: Object) { addEmbeddedRefOffset(asm.movq_r_p(G(reg), long.view(Pointer.atObject(obj)))); } + def emit_mov_r_Array(reg: Reg, array: Array) { + addEmbeddedRefOffset(asm.movq_r_p(G(reg), long.view(Pointer.atObject(array)))); + } def emit_mov_r_Function(reg: Reg, func: Function) { addEmbeddedRefOffset(asm.movq_r_p(G(reg), long.view(Pointer.atObject(func)))); } @@ -313,6 +320,9 @@ class X86_64MacroAssembler extends MacroAssembler { V128, REF_U64 => asm.movdqu_m_s(addr, X(reg)); } } + def emit_mov_m_b(ma: MasmAddr, val: byte) { + asm.movb_m_i(A(ma), val); + } def emit_mov_m_i(ma: MasmAddr, val: int) { asm.movd_m_i(A(ma), val); } @@ -871,6 +881,9 @@ class X86_64MacroAssembler extends MacroAssembler { def emit_call_runtime_SWITCH() { emit_call_runtime(X86_64RT.runtime_handle_switch); } + def emit_call_runtime_ALLOC_CONT() { + emit_call_runtime(X86_64RT.runtime_alloc_cont); + } def emit_call_runtime_RESUME_THROW() { emit_call_runtime(X86_64RT.runtime_handle_resume_throw); } @@ -948,21 +961,121 @@ class X86_64MacroAssembler extends MacroAssembler { asm.jc_rel_near(C.Z, done); asm.d.shl_r_i(r_count, valuerep.slot_size_log); asm.bind(copy); - if (valuerep.value_size == 16) { // 16-byte (SIMD) values - asm.movdqu_s_m(r_xmm0, r_src.plusR(r_count, 1, - valuerep.value_size)); - asm.movdqu_m_s(r_dst.plusR(r_count, 1, - valuerep.value_size), r_xmm0); - } else { // 8-byte values - asm.movq_r_m(scratch, r_src.plusR(r_count, 1, - Pointer.SIZE)); - asm.movq_m_r(r_dst.plusR(r_count, 1, - Pointer.SIZE), scratch); - } - if (valuerep.tagged) { - asm.movb_r_m(scratch, r_src.plusR(r_count, 1, - valuerep.slot_size)); // copy tags - asm.movb_m_r(r_dst.plusR(r_count, 1, - valuerep.slot_size), scratch); - } + emit_slot_copy(r_dst.plusR(r_count, 1, - valuerep.slot_size), r_src.plusR(r_count, 1, - valuerep.slot_size), r_xmm0); asm.d.sub_r_i(r_count, valuerep.slot_size); asm.jc_rel_near(C.NZ, copy); asm.bind(done); } + def emit_value_copy_n(r_dst: Reg, r_src: Reg, n: int, r_xmm: Reg) { + var dst = G(r_dst), src = G(r_src), xmm = X(r_xmm); + for (i < n) emit_slot_copy(dst.plus(i * valuerep.slot_size), src.plus(i * valuerep.slot_size), xmm); + } + // Copies one value-stack slot, tag and payload, from the slot at {src} to the slot at {dst}. + def emit_slot_copy(dst: X86_64Addr, src: X86_64Addr, r_xmm0: X86_64Xmmr) { + var payload = valuerep.slot_size - valuerep.value_size; + if (valuerep.value_size == 16) { + asm.movdqu_s_m(r_xmm0, src.plus(payload)); + asm.movdqu_m_s(dst.plus(payload), r_xmm0); + } else { + asm.movq_r_m(scratch, src.plus(payload)); + asm.movq_m_r(dst.plus(payload), scratch); + } + if (valuerep.tagged) { + asm.movb_r_m(scratch, src); + asm.movb_m_r(dst, scratch); + } + } + def emit_store_continuation(r_addr: Reg, offset: int, r_cont: Reg) { + var addr = G(r_addr), cont = G(r_cont); + asm.movb_m_i(addr.plus(offset), Continuations.valueTag); + asm.movq_m_r(addr.plus(offset + valuerep.tag_size), cont); + if (FeatureDisable.unboxedConts) return; + asm.movq_r_m(scratch, cont.plus(offsets.X86_64Stack_version)); + asm.movq_m_r(addr.plus(offset + valuerep.tag_size + 8), scratch); + } + def emit_enter_stack(r_stack: Reg) { + var stack = G(r_stack); + asm.movq_r_m(R.RSP, stack.plus(offsets.X86_64Stack_rsp)); + asm.popq_r(scratch); + asm.movq_m_r(stack.plus(offsets.X86_64Stack_rsp), R.RSP); + asm.ijmp_r(scratch); + } + def emit_handler_search(is_switch: bool, r_tag: Reg, r_cur: Reg, r_parent: Reg, r_prev: Reg, r_entry: Reg, r_cnt: Reg, r_ptags: Reg, l_trap: MasmLabel) { + var tag = G(r_tag), parent = G(r_parent), prev = G(r_prev), entry = G(r_entry), cnt = G(r_cnt), ptags = G(r_ptags); + var l_link = X86_64Label.new(), l_entry = X86_64Label.new(), l_climb = X86_64Label.new(), l_found = X86_64Label.new(); + asm.movq_r_r(prev, G(r_cur)); + asm.bind(l_link); + asm.movq_r_m(parent, prev.plus(offsets.X86_64Stack_parent)); + asm.test_r_r(parent, parent); + asm.jc_rel_far(C.Z, X86_64MasmLabel.!(l_trap).label); + asm.movq_r_m(ptags, parent.plus(offsets.X86_64Stack_rsp)); + asm.movq_r_m(ptags, ptags.plus(Pointer.SIZE + X86_64InterpreterFrame.instance.offset)); + asm.movq_r_m(ptags, ptags.plus(offsets.Instance_tags)); + asm.movq_r_m(entry, parent.plus(offsets.X86_64Stack_handlers)); + if (is_switch) { + asm.movd_r_m(cnt, entry.plus(offsets.Array_contents + ResumeSiteHeader.count.offset)); + asm.movd_r_m(scratch, entry.plus(offsets.Array_contents + ResumeSiteHeader.switch_start_index.offset)); + asm.d.sub_r_r(cnt, scratch); + asm.q.imul_r_i(scratch, ResumeHandlerEntry.size); + asm.q.lea(entry, X86_64Addr.new(entry, scratch, 1, offsets.Array_contents + ResumeSiteHeader.size)); + } else { + asm.movd_r_m(cnt, entry.plus(offsets.Array_contents + ResumeSiteHeader.switch_start_index.offset)); + asm.q.add_r_i(entry, offsets.Array_contents + ResumeSiteHeader.size); + } + asm.d.test_r_r(cnt, cnt); + asm.jc_rel_near(C.Z, l_climb); + asm.bind(l_entry); + asm.movd_r_m(scratch, entry.plus(ResumeHandlerEntry.tag_index.offset)); + asm.q.cmp_r_m(tag, X86_64Addr.new(ptags, scratch, 8, offsets.Array_contents)); + asm.jc_rel_near(C.Z, l_found); + asm.q.add_r_i(entry, ResumeHandlerEntry.size); + asm.d.dec_r(cnt); + asm.jc_rel_near(C.NZ, l_entry); + asm.bind(l_climb); + asm.movq_r_r(prev, parent); + asm.jmp_rel_near(l_link); + asm.bind(l_found); + } + def emit_replace_child_chain(r_parent: Reg, r_prev: Reg, r_target: Reg, r_bottom: Reg, r_tmp: Reg) { + var target = G(r_target), parent = G(r_parent), prev = G(r_prev), bottom = G(r_bottom), tmp = G(r_tmp); + asm.movq_r_m(bottom, target.plus(offsets.X86_64Stack_bottom)); + asm.movq_m_r(bottom.plus(offsets.X86_64Stack_parent), parent); + asm.movq_r_m(tmp, prev.plus(offsets.X86_64Stack_parent_rsp_ptr)); + asm.movq_r_m(scratch, tmp.plus(0)); + asm.movq_m_i(tmp.plus(0), 0); + asm.movq_r_m(tmp, bottom.plus(offsets.X86_64Stack_parent_rsp_ptr)); + asm.movq_m_r(tmp.plus(0), scratch); + asm.movq_m_i(prev.plus(offsets.X86_64Stack_parent), 0); + } + def emit_handler_fixup(r_parent: Reg, r_entry: Reg, r_dst: Reg, r_psfp: Reg) { + var parent = G(r_parent), entry = G(r_entry), dst = G(r_dst), psfp = G(r_psfp); + var l_int = X86_64Label.new(), l_done = X86_64Label.new(); + asm.movq_r_m(psfp, parent.plus(offsets.X86_64Stack_rsp)); + asm.q.add_r_i(psfp, Pointer.SIZE); + asm.movd_r_m(scratch, entry.plus(ResumeHandlerEntry.handler_pc.offset)); + asm.movd_m_r(psfp.plus(X86_64InterpreterFrame.curpc.offset), scratch); + asm.cmpb_m_i(parent.plus(offsets.X86_64Stack_handler_tier), HandlerTier.SPC.tag); + asm.jc_rel_near(C.NZ, l_int); + asm.movq_m_i(psfp.plus(X86_64InterpreterFrame.accessor.offset), 0); + asm.movq_r_m(scratch, entry.plus(ResumeHandlerEntry.stub_addr.offset)); + asm.movq_m_r(psfp.plus(0 - Pointer.SIZE), scratch); + asm.jmp_rel_near(l_done); + asm.bind(l_int); + asm.movq_r_m(dst, psfp.plus(X86_64InterpreterFrame.code.offset)); + asm.q.lea(scratch, X86_64Addr.new(dst, scratch, 1, offsets.Array_contents)); + asm.movq_m_r(psfp.plus(X86_64InterpreterFrame.ip.offset), scratch); + asm.movd_r_m(scratch, entry.plus(ResumeHandlerEntry.stp_pos.offset)); + asm.movq_r_m(dst, psfp.plus(X86_64InterpreterFrame.sidetable.offset)); + asm.q.lea(scratch, X86_64Addr.new(dst, scratch, 4, offsets.Array_contents)); + asm.movq_m_r(psfp.plus(X86_64InterpreterFrame.stp.offset), scratch); + var ic = X86_64PreGenStubs.getInterpreterCode(); + asm.movq_r_l(scratch, (ic.start + ic.header.intSuspendEntryOffset) - Pointer.NULL); + asm.movq_m_r(psfp.plus(0 - Pointer.SIZE), scratch); + asm.bind(l_done); + asm.movd_r_m(dst, entry.plus(ResumeHandlerEntry.vsp_slots.offset)); + asm.q.shl_r_i(dst, valuerep.slot_size_log); + asm.q.add_r_m(dst, psfp.plus(X86_64InterpreterFrame.vfp.offset)); + } def emit_get_curstack(r: Reg) { var offsets = getOffsets(); @@ -1574,7 +1687,7 @@ class X86_64MacroAssembler extends MacroAssembler { } def emit_set_stack_state(stk: Reg, state: StackState) { - emit_mov_m_i(MasmAddr(stk, offsets.X86_64Stack_state), state.tag); + emit_mov_m_b(MasmAddr(stk, offsets.X86_64Stack_state), byte.view(state.tag)); } // Moves values from the current stack to %cont.top. diff --git a/src/engine/x86-64/X86_64Runtime.v3 b/src/engine/x86-64/X86_64Runtime.v3 index 0289a2f84..8e837c9b4 100644 --- a/src/engine/x86-64/X86_64Runtime.v3 +++ b/src/engine/x86-64/X86_64Runtime.v3 @@ -106,6 +106,10 @@ component X86_64Runtime { def runtime_CONT_NEW(stack: X86_64Stack, instance: Instance, cont_index: u31) -> Throwable { return Runtime.CONT_NEW(stack, instance, cont_index); } + // Allocates the boxed continuation of {stack}. + def runtime_alloc_cont(stack: X86_64Stack) -> Continuation { + return Continuations.makeContinuation(stack); + } // If the given continuation handles the thrown error, a modified {Continuation} is pushed // onto the value stack. Otherwise, trap on the current stack. // XXX: eliminate returning {Throwable}? @@ -150,6 +154,8 @@ component X86_64Runtime { var bottom = cont.stack.cont_bottom; curStack = X86_64Stack.!(bottom.parent); curStack.state_ = StackState.RUNNING; + curStack.handlers = null; + curStack.handler_tier = HandlerTier.NONE; curStack.pushN(vals); curStack.push(Value.Cont(cont)); bottom.parent = null; diff --git a/src/engine/x86-64/X86_64SinglePassCompiler.v3 b/src/engine/x86-64/X86_64SinglePassCompiler.v3 index eb56cf68d..e2d59d20b 100644 --- a/src/engine/x86-64/X86_64SinglePassCompiler.v3 +++ b/src/engine/x86-64/X86_64SinglePassCompiler.v3 @@ -1579,6 +1579,22 @@ component X86_64Spc { decl.tierup_trigger = int.max; return addr; } + // Records the stub address of every suspend clause in the function's resume site tables. + def fillHandlerStubAddrs(decl: FuncDecl) { + var handlers = decl.handlers; + if (handlers.resume_sites == null) return; + var entry = decl.target_code.spc_entry; + for (site in handlers.handler_pc_index_map) { + var table = handlers.resume_sites[site.0]; + if (table == null) continue; + for (i < ResumeSites.header(table).switch_start_index) { + var dest = handlers.get_handler_dest(site.1[i]); + if (dest.stub_label == null) continue; + var stub = entry + X86_64MasmLabel.!(dest.stub_label).label.pos; + ResumeSites.entry(table, i).stub_addr = stub - Pointer.NULL; + } + } + } def estimateCodeSizeFor(decl: FuncDecl) -> int { // TODO: very imprecise estimate return 60 + decl.orig_bytecode.length * 20 * (4 << byte.view(SpcTuning.maxInlineDepth)); diff --git a/src/engine/x86-64/X86_64Stack.v3 b/src/engine/x86-64/X86_64Stack.v3 index 9d431f62d..4e6e749e5 100644 --- a/src/engine/x86-64/X86_64Stack.v3 +++ b/src/engine/x86-64/X86_64Stack.v3 @@ -24,6 +24,10 @@ class X86_64Stack extends WasmStack { var return_results: Array; var state_: StackState; + // Handler table (for inline suspend/switch). + var handlers: Array; + var handler_tier: HandlerTier; + // Single-linked list for stack manager cache. var next_stack: X86_64Stack; @@ -107,6 +111,7 @@ class X86_64Stack extends WasmStack { var ex = Exception.!(thrown); var result = walk(findExHandler, ex, prev_srp, true); var pos = result.1; + clearCrossedHandlers(pos.stack); var new_srp = pos.frame.srp(); if (Trace.stack) { if (result.0) Trace.OUT.put1("walk found handler at frame[sfp=0x%x]", pos.frame.sfp - Pointer.NULL).ln(); @@ -120,6 +125,7 @@ class X86_64Stack extends WasmStack { var trace = if (gatherTrace, Vector<(WasmFunction, int)>.new()); var result = walk(if(gatherTrace, addFrameToTrace), trace, prev_srp, true); var new_srp = result.1.frame.srp(); + clearCrossedHandlers(result.1.stack); // Unwind this stack to the return parent stub and overwrite the caller's return IP. this.vsp = mapping.range.start; // clear value stack if (new_srp != prev_srp) unwind(prev_srp, new_srp); @@ -226,6 +232,7 @@ class X86_64Stack extends WasmStack { var h = X86_64FrameHandles.from(rsp + Pointer.SIZE); var func = h.wasm_func(); var handler = func.decl.findSuspensionHandler(func.instance, tag, h.computePc()); + shadowCheckHandlerTable(false, h, func, tag, handler); if (handler.handler_pc < 0) { // not found if (Trace.stack) Trace.OUT.puts(" suspension handler not found, continue to unwind").ln(); return handler; @@ -264,6 +271,7 @@ class X86_64Stack extends WasmStack { var h = X86_64FrameHandles.from(rsp + Pointer.SIZE); var func = h.wasm_func(); var handler = func.decl.findSwitchHandler(func.instance, tag, h.computePc()); + shadowCheckHandlerTable(true, h, func, tag, handler); if (handler.handler_pc < 0) { // not found if (Trace.stack) Trace.OUT.puts(" switch handler not found, continue to unwind").ln(); return handler; @@ -271,6 +279,34 @@ class X86_64Stack extends WasmStack { if (Trace.stack) Trace.OUT.puts(" found switch handler").ln(); return handler; } + // TODO[rv]: remove after fully tested + private def shadowCheckHandlerTable(is_switch: bool, h: X86_64FrameHandle, func: WasmFunction, tag: Tag, handler: ExHandler) { + if (handlers == null) return fatal("no handler table published on a stack inside a resume"); + var tier = if(X86_64FrameHandle.Spc.?(h), HandlerTier.SPC, HandlerTier.INTERPRETER); + if (handler_tier != tier) return fatal(Strings.format2("handler table published by tier %s for a tier %s frame", handler_tier.name, tier.name)); + var decl = func.decl, sites = decl.handlers.resume_sites; + if (sites == null || sites[h.computePc()] != handlers) return fatal("published handler table is not the top frame's resume site"); + if (h.instance() != func.instance) return fatal("frame instance differs from its function's instance"); + var tags = h.instance().tags, hdr = ResumeSites.header(handlers); + var start = if(is_switch, hdr.switch_start_index, 0), end = if(is_switch, hdr.count, hdr.switch_start_index); + var found = -1; + for (i = start; i < end; i++) { + if (tags[ResumeSites.entry(handlers, i).tag_index] != tag) continue; + found = i; + break; + } + if ((found < 0) != (handler.handler_pc < 0)) return fatal("handler table and decoder disagree on finding a handler"); + if (found < 0) return; + var e = ResumeSites.entry(handlers, found); + if (e.handler_pc != handler.handler_pc) return fatal("handler table has the wrong handler_pc"); + if (e.stp_pos != handler.sidetable_pos) return fatal("handler table has the wrong sidetable_pos"); + if (e.vsp_slots != handler.val_stack_top + decl.num_locals) return fatal("handler table has the wrong vsp_slots"); + if (!is_switch && X86_64FrameHandle.Spc.?(h)) { + var dest = decl.handlers.get_handler_dest(handler.index); + var stub = decl.target_code.spc_entry + X86_64MasmLabel.!(dest.stub_label).label.pos; + if (e.stub_addr != (stub - Pointer.NULL)) return fatal("handler table has the wrong stub_addr"); + } + } private def findExHandler(ip: Pointer, code: RiUserCode, pos: StackFramePos, ex: Exception) -> bool { var stack = pos.stack; var frame = pos.frame; @@ -332,6 +368,7 @@ class X86_64Stack extends WasmStack { // Unwind the stack, skipping the top frame (which might have triggered the overflow) var result = walk(null, (), sfp + -RETADDR_SIZE, true); var new_srp = result.1.frame.srp(); + clearCrossedHandlers(result.1.stack); // Unwind without updating any return addresses. this.vsp = mapping.range.start; // clear value stack if (new_srp != this.rsp) unwind(Pointer.NULL, new_srp); @@ -350,6 +387,16 @@ class X86_64Stack extends WasmStack { cont_bottom = null; parent_rsp_ptr = Pointer.NULL; func = null; + handlers = null; + handler_tier = HandlerTier.NONE; + } + // TODO[rv]: remove after fully tested + private def clearCrossedHandlers(stop: X86_64Stack) { + for (s = this; s != stop && s.parent != null; s = X86_64Stack.!(s.parent)) { + var p = X86_64Stack.!(s.parent); + p.handlers = null; + p.handler_tier = HandlerTier.NONE; + } } def pushRspPointer(p: Pointer) -> Pointer { rsp = rsp + -Pointer.SIZE; @@ -653,6 +700,8 @@ class X86_64Stack extends WasmStack { } } +enum HandlerTier { NONE, INTERPRETER, SPC } + def G = X86_64MasmRegs.toGpr, X = X86_64MasmRegs.toXmmr; component X86_64Stacks { @@ -868,6 +917,8 @@ def genStackReturnParentStub(ic: X86_64InterpreterCode, w: DataWriter) { masm.emit_mov_m_l(MasmAddr(r_stack, masm.offsets.X86_64Stack_parent), 0); // mov [%stack.parent_rsp_ptr], nullptr masm.emit_mov_m_l(MasmAddr(r_stack, masm.offsets.X86_64Stack_parent_rsp_ptr), 0); + // clear its published handler table + masm.emit_clear_handlers(r_parent); // l_return: masm.bindLabel(l_return); diff --git a/src/engine/x86-64/X86_64Target.v3 b/src/engine/x86-64/X86_64Target.v3 index 5ba77391f..77151b66b 100644 --- a/src/engine/x86-64/X86_64Target.v3 +++ b/src/engine/x86-64/X86_64Target.v3 @@ -61,6 +61,7 @@ component Target { } } f.target_code = TargetCode(addr); + X86_64Spc.fillHandlerStubAddrs(f); Debug.afterCompile(f, u64.view(addr - Pointer.NULL)); } def pregenIntoFile(filename: string) -> ErrorBuilder { @@ -188,9 +189,6 @@ type SpcResultForStub(wf: WasmFunction, entrypoint: Pointer, thrown: Throwable) class TargetHandlerDest(is_dummy: bool) { var stub_label: MasmLabel; var dest_label: MasmLabel; - - // Full signature of the stack at dest. - var dest_stack: Array; } class X86_64ExecutionStrategy extends ExecutionStrategy { diff --git a/test/regress/ext:stack-switching/inline_suspend_deep0.bin.wast b/test/regress/ext:stack-switching/inline_suspend_deep0.bin.wast new file mode 100644 index 000000000..5139d2cf9 --- /dev/null +++ b/test/regress/ext:stack-switching/inline_suspend_deep0.bin.wast @@ -0,0 +1,22 @@ +(module definition binary + "\00\61\73\6d\01\00\00\00\01\aa\80\80\80\00\08\5f" + "\01\7f\00\60\00\01\7f\5d\01\60\01\7f\01\7f\5d\03" + "\60\00\00\60\04\7f\63\00\63\02\7c\01\7f\60\00\05" + "\7f\63\00\63\02\7c\63\04\03\86\80\80\80\00\05\01" + "\01\01\01\01\0d\85\80\80\80\00\02\00\05\00\06\07" + "\88\80\80\80\00\01\04\6d\61\69\6e\00\04\09\88\80" + "\80\80\00\01\03\00\04\00\01\02\03\0a\9d\81\80\80" + "\00\05\84\80\80\80\00\00\41\2a\0b\9c\80\80\80\00" + "\00\41\e8\07\41\07\41\05\fb\00\00\d2\00\e0\02\44" + "\00\00\00\00\00\00\04\40\e2\01\6a\0b\98\80\80\80" + "\00\00\02\63\02\41\e4\00\d2\01\e0\02\e3\02\01\00" + "\00\00\6a\0f\0b\1a\41\7f\0b\8c\80\80\80\00\00\41" + "\0a\d2\02\e0\02\e3\02\00\6a\0b\bf\80\80\80\00\05" + "\01\63\04\01\63\02\01\63\00\01\7c\01\7f\02\07\d2" + "\03\e0\02\e3\02\01\00\01\00\0f\0b\21\00\21\03\21" + "\01\21\02\21\04\20\04\20\01\e3\02\00\6a\20\02\fb" + "\02\00\00\6a\20\03\aa\6a\20\00\e3\04\00\0b" +) +(module instance) +(assert_return (invoke "main") (i32.const 0x48e)) +(assert_return (invoke "main") (i32.const 0x48e)) diff --git a/test/regress/ext:stack-switching/inline_suspend_deep0.wast b/test/regress/ext:stack-switching/inline_suspend_deep0.wast new file mode 100644 index 000000000..50de00309 --- /dev/null +++ b/test/regress/ext:stack-switching/inline_suspend_deep0.wast @@ -0,0 +1,55 @@ +(module + (type $s (struct (field i32))) + (type $f (func (result i32))) + (type $c (cont $f)) + (type $g (func (param i32) (result i32))) + (type $cg (cont $g)) + (tag $u) + (tag $t (param i32 (ref null $s) (ref null $c) f64) (result i32)) + + (func $forty_two (result i32) (i32.const 42)) + (func $leaf (result i32) + (i32.add (i32.const 1000) + (suspend $t (i32.const 7) (struct.new $s (i32.const 5)) (cont.new $c (ref.func $forty_two)) (f64.const 2.5))) + ) + (func $mid2 (result i32) + (block $h (result (ref null $c)) + (return (i32.add (i32.const 100) (resume $c (on $u $h) (cont.new $c (ref.func $leaf))))) + ) + (drop) + (i32.const -1) + ) + (func $mid1 (result i32) + (i32.add (i32.const 10) (resume $c (cont.new $c (ref.func $mid2)))) + ) + (elem declare func $forty_two $leaf $mid2 $mid1) + + (func (export "main") (result i32) + (local $k (ref null $cg)) + (local $fresh (ref null $c)) + (local $st (ref null $s)) + (local $d f64) + (local $i i32) + (block $h (result i32 (ref null $s) (ref null $c) f64 (ref null $cg)) + (return (resume $c (on $t $h) (cont.new $c (ref.func $mid1)))) + ) + (local.set $k) + (local.set $d) + (local.set $fresh) + (local.set $st) + (local.set $i) + ;; 7 + 42 + 5 + 2 = 56; leaf 1056, mid2 1156, mid1 1166 + (local.get $i) + (resume $c (local.get $fresh)) + (i32.add) + (struct.get $s 0 (local.get $st)) + (i32.add) + (i32.trunc_f64_s (local.get $d)) + (i32.add) + (local.get $k) + (resume $cg) + ) +) + +(assert_return (invoke "main") (i32.const 1166)) +(assert_return (invoke "main") (i32.const 1166)) diff --git a/test/regress/ext:stack-switching/inline_suspend_int_parent0.bin.wast b/test/regress/ext:stack-switching/inline_suspend_int_parent0.bin.wast new file mode 100644 index 000000000..c76c7b201 --- /dev/null +++ b/test/regress/ext:stack-switching/inline_suspend_int_parent0.bin.wast @@ -0,0 +1,38 @@ +(module definition binary + "\00\61\73\6d\01\00\00\00\01\a3\80\80\80\00\07\60" + "\00\01\7f\5d\00\60\01\7f\01\7f\5d\02\5f\01\7f\00" + "\60\03\7f\7c\63\04\01\7f\60\00\04\7f\7c\63\04\63" + "\03\03\86\80\80\80\00\05\00\00\00\00\00\0d\83\80" + "\80\80\00\01\00\05\07\90\80\80\80\00\02\04\6d\61" + "\69\6e\00\03\05\6d\61\69\6e\32\00\04\09\85\80\80" + "\80\00\01\03\00\01\00\0a\cd\81\80\80\00\05\97\80" + "\80\80\00\00\41\0a\41\01\44\00\00\00\00\00\00\f8" + "\3f\41\03\fb\00\04\e2\00\6a\0b\ca\80\80\80\00\04" + "\01\63\03\01\63\04\01\7c\02\7f\02\06\d2\00\e0\01" + "\e3\01\01\00\00\00\0f\0b\21\00\21\01\21\02\21\03" + "\41\04\21\04\03\40\20\03\20\01\fb\02\04\00\6a\21" + "\03\20\04\41\01\6b\21\04\20\04\0d\00\0b\20\03\20" + "\02\aa\6a\20\00\e3\03\00\0b\ca\80\80\80\00\04\01" + "\63\03\01\63\04\01\7c\02\7f\02\06\d2\00\e0\01\e3" + "\01\01\00\00\00\0f\0b\21\00\21\01\21\02\21\03\41" + "\04\21\04\03\40\20\03\20\01\fb\02\04\00\6a\21\03" + "\20\04\41\01\6b\21\04\20\04\0d\00\0b\20\03\20\02" + "\aa\6a\20\00\e3\03\00\0b\84\80\80\80\00\00\10\01" + "\0b\84\80\80\80\00\00\10\02\0b" +) +(module instance) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main") (i32.const 0x18)) +(assert_return (invoke "main2") (i32.const 0x18)) +(assert_return (invoke "main2") (i32.const 0x18)) +(assert_return (invoke "main2") (i32.const 0x18)) diff --git a/test/regress/ext:stack-switching/inline_suspend_int_parent0.wast b/test/regress/ext:stack-switching/inline_suspend_int_parent0.wast new file mode 100644 index 000000000..ceba33d41 --- /dev/null +++ b/test/regress/ext:stack-switching/inline_suspend_int_parent0.wast @@ -0,0 +1,76 @@ +(module + (type $f (func (result i32))) + (type $c (cont $f)) + (type $fs (func (param i32) (result i32))) + (type $cs (cont $fs)) + (type $s (struct (field i32))) + (tag $t (param i32 f64 (ref null $s)) (result i32)) + + (func $child (result i32) + (i32.add (i32.const 10) (suspend $t (i32.const 1) (f64.const 1.5) (struct.new $s (i32.const 3)))) + ) + (elem declare func $child) + + (func $run1 (result i32) + (local $k (ref null $cs)) + (local $st (ref null $s)) + (local $d f64) + (local $i i32) + (local $n i32) + (block $h (result i32 f64 (ref null $s) (ref null $cs)) + (return (resume $c (on $t $h) (cont.new $c (ref.func $child)))) + ) + (local.set $k) + (local.set $st) + (local.set $d) + (local.set $i) + (local.set $n (i32.const 4)) + (loop $l + (local.set $i (i32.add (local.get $i) (struct.get $s 0 (local.get $st)))) + (local.set $n (i32.sub (local.get $n) (i32.const 1))) + (br_if $l (local.get $n)) + ) + ;; 1 + 4 * 3 + 1 = 14; child returns 24 + (resume $cs (i32.add (local.get $i) (i32.trunc_f64_s (local.get $d))) (local.get $k)) + ) + (func $run2 (result i32) + (local $k (ref null $cs)) + (local $st (ref null $s)) + (local $d f64) + (local $i i32) + (local $n i32) + (block $h (result i32 f64 (ref null $s) (ref null $cs)) + (return (resume $c (on $t $h) (cont.new $c (ref.func $child)))) + ) + (local.set $k) + (local.set $st) + (local.set $d) + (local.set $i) + (local.set $n (i32.const 4)) + (loop $l + (local.set $i (i32.add (local.get $i) (struct.get $s 0 (local.get $st)))) + (local.set $n (i32.sub (local.get $n) (i32.const 1))) + (br_if $l (local.get $n)) + ) + (resume $cs (i32.add (local.get $i) (i32.trunc_f64_s (local.get $d))) (local.get $k)) + ) + (func (export "main") (result i32) (call $run1)) + (func (export "main2") (result i32) (call $run2)) +) + +(assert_return (invoke "main") (i32.const 24)) +(assert_return (invoke "main") (i32.const 24)) +(assert_return (invoke "main") (i32.const 24)) +(assert_return (invoke "main") (i32.const 24)) +(assert_return (invoke "main") (i32.const 24)) +(assert_return (invoke "main") (i32.const 24)) +(assert_return (invoke "main") (i32.const 24)) +(assert_return (invoke "main") (i32.const 24)) +(assert_return (invoke "main") (i32.const 24)) +(assert_return (invoke "main") (i32.const 24)) +(assert_return (invoke "main") (i32.const 24)) +(assert_return (invoke "main") (i32.const 24)) +;; compiled suspender, interpreted resumer +(assert_return (invoke "main2") (i32.const 24)) +(assert_return (invoke "main2") (i32.const 24)) +(assert_return (invoke "main2") (i32.const 24)) diff --git a/test/regress/ext:stack-switching/inline_switch_then_suspend0.bin.wast b/test/regress/ext:stack-switching/inline_switch_then_suspend0.bin.wast new file mode 100644 index 000000000..e8fc7ed0e --- /dev/null +++ b/test/regress/ext:stack-switching/inline_switch_then_suspend0.bin.wast @@ -0,0 +1,24 @@ +(module definition binary + "\00\61\73\6d\01\00\00\00\01\9c\80\80\80\00\07\60" + "\00\01\7f\5d\00\60\01\63\01\01\7f\5d\02\60\01\7f" + "\01\7f\5d\04\60\00\02\7f\63\05\03\87\80\80\80\00" + "\06\02\02\00\00\00\00\0d\85\80\80\80\00\02\00\00" + "\00\04\07\ae\80\80\80\00\02\14\73\75\73\70\65\6e" + "\64\5f\61\66\74\65\72\5f\73\77\69\74\63\68\00\04" + "\13\72\65\74\75\72\6e\5f\61\66\74\65\72\5f\73\77" + "\69\74\63\68\00\05\09\88\80\80\80\00\01\03\00\04" + "\00\01\02\03\0a\fb\80\80\80\00\06\8a\80\80\80\00" + "\00\41\c8\01\41\03\e2\01\6a\0b\85\80\80\80\00\00" + "\41\cd\00\0b\8c\80\80\80\00\00\d2\00\e0\03\e6\03" + "\00\41\e3\00\0b\8c\80\80\80\00\00\d2\01\e0\03\e6" + "\03\00\41\e3\00\0b\9f\80\80\80\00\01\01\63\05\02" + "\06\d2\02\e0\01\e3\01\02\01\00\00\01\00\0f\0b\21" + "\00\41\01\6a\20\00\e3\05\00\0b\96\80\80\80\00\00" + "\02\06\d2\03\e0\01\e3\01\02\01\00\00\01\00\0f\0b" + "\1a\1a\41\7f\0b" +) +(module instance) +(assert_return (invoke "suspend_after_switch") (i32.const 0xcc)) +(assert_return (invoke "return_after_switch") (i32.const 0x4d)) +(assert_return (invoke "suspend_after_switch") (i32.const 0xcc)) +(assert_return (invoke "return_after_switch") (i32.const 0x4d)) diff --git a/test/regress/ext:stack-switching/inline_switch_then_suspend0.wast b/test/regress/ext:stack-switching/inline_switch_then_suspend0.wast new file mode 100644 index 000000000..259d14e4a --- /dev/null +++ b/test/regress/ext:stack-switching/inline_switch_then_suspend0.wast @@ -0,0 +1,51 @@ +(module + (type $f1 (func (result i32))) + (type $c1 (cont $f1)) + (type $f2 (func (param (ref null $c1)) (result i32))) + (type $c2 (cont $f2)) + (type $fs (func (param i32) (result i32))) + (type $cs (cont $fs)) + (tag $sw (result i32)) + (tag $t (param i32) (result i32)) + + (func $c_suspends (param $k (ref null $c1)) (result i32) + (i32.add (i32.const 200) (suspend $t (i32.const 3))) + ) + (func $c_returns (param $k (ref null $c1)) (result i32) + (i32.const 77) + ) + (func $b_to_suspender (result i32) + (switch $c2 $sw (cont.new $c2 (ref.func $c_suspends))) + (i32.const 99) + ) + (func $b_to_returner (result i32) + (switch $c2 $sw (cont.new $c2 (ref.func $c_returns))) + (i32.const 99) + ) + (elem declare func $c_suspends $c_returns $b_to_suspender $b_to_returner) + + (func (export "suspend_after_switch") (result i32) + (local $k (ref null $cs)) + (block $h (result i32 (ref null $cs)) + (return (resume $c1 (on $sw switch) (on $t $h) (cont.new $c1 (ref.func $b_to_suspender)))) + ) + (local.set $k) + (i32.const 1) + (i32.add) + (local.get $k) + (resume $cs) + ) + (func (export "return_after_switch") (result i32) + (block $h (result i32 (ref null $cs)) + (return (resume $c1 (on $sw switch) (on $t $h) (cont.new $c1 (ref.func $b_to_returner)))) + ) + (drop) + (drop) + (i32.const -1) + ) +) + +(assert_return (invoke "suspend_after_switch") (i32.const 204)) +(assert_return (invoke "return_after_switch") (i32.const 77)) +(assert_return (invoke "suspend_after_switch") (i32.const 204)) +(assert_return (invoke "return_after_switch") (i32.const 77)) diff --git a/test/regress/ext:stack-switching/inline_switch_used0.bin.wast b/test/regress/ext:stack-switching/inline_switch_used0.bin.wast new file mode 100644 index 000000000..20bb40cdd --- /dev/null +++ b/test/regress/ext:stack-switching/inline_switch_used0.bin.wast @@ -0,0 +1,18 @@ +(module definition binary + "\00\61\73\6d\01\00\00\00\01\8f\80\80\80\00\04\60" + "\00\01\7f\5d\00\60\01\63\01\01\7f\5d\02\03\86\80" + "\80\80\00\05\02\00\00\00\00\0d\83\80\80\80\00\01" + "\00\00\07\8d\80\80\80\00\02\04\75\73\65\64\00\03" + "\02\6f\6b\00\04\09\87\80\80\80\00\01\03\00\03\00" + "\01\02\0a\d9\80\80\80\00\05\84\80\80\80\00\00\41" + "\05\0b\99\80\80\80\00\01\01\63\03\d2\00\e0\03\21" + "\00\20\00\e1\03\03\1a\20\00\e6\03\00\41\e3\00\0b" + "\8c\80\80\80\00\00\d2\00\e0\03\e6\03\00\41\e3\00" + "\0b\8b\80\80\80\00\00\d2\01\e0\01\e3\01\01\01\00" + "\0b\8b\80\80\80\00\00\d2\02\e0\01\e3\01\01\01\00" + "\0b" +) +(module instance) +(assert_return (invoke "ok") (i32.const 0x5)) +(assert_trap (invoke "used") "continuation already consumed") +(assert_return (invoke "ok") (i32.const 0x5)) diff --git a/test/regress/ext:stack-switching/inline_switch_used0.wast b/test/regress/ext:stack-switching/inline_switch_used0.wast new file mode 100644 index 000000000..a54034bd8 --- /dev/null +++ b/test/regress/ext:stack-switching/inline_switch_used0.wast @@ -0,0 +1,32 @@ +(module + (type $f1 (func (result i32))) + (type $c1 (cont $f1)) + (type $f2 (func (param (ref null $c1)) (result i32))) + (type $c2 (cont $f2)) + (tag $sw (result i32)) + + (func $c (param $k (ref null $c1)) (result i32) (i32.const 5)) + (func $b_used (result i32) + (local $x (ref null $c2)) + (local.set $x (cont.new $c2 (ref.func $c))) + (drop (cont.bind $c2 $c2 (local.get $x))) + (switch $c2 $sw (local.get $x)) + (i32.const 99) + ) + (func $b_ok (result i32) + (switch $c2 $sw (cont.new $c2 (ref.func $c))) + (i32.const 99) + ) + (elem declare func $c $b_used $b_ok) + + (func (export "used") (result i32) + (resume $c1 (on $sw switch) (cont.new $c1 (ref.func $b_used))) + ) + (func (export "ok") (result i32) + (resume $c1 (on $sw switch) (cont.new $c1 (ref.func $b_ok))) + ) +) + +(assert_return (invoke "ok") (i32.const 5)) +(assert_trap (invoke "used") "continuation already consumed") +(assert_return (invoke "ok") (i32.const 5)) diff --git a/test/regress/ext:stack-switching/publish_empty0.bin.wast b/test/regress/ext:stack-switching/publish_empty0.bin.wast new file mode 100644 index 000000000..fb357ee2e --- /dev/null +++ b/test/regress/ext:stack-switching/publish_empty0.bin.wast @@ -0,0 +1,15 @@ +(module definition binary + "\00\61\73\6d\01\00\00\00\01\94\80\80\80\00\05\60" + "\00\01\7f\5d\00\60\01\7f\01\7f\5d\02\60\00\02\7f" + "\63\03\03\85\80\80\80\00\04\00\00\00\00\0d\83\80" + "\80\80\00\01\00\02\07\88\80\80\80\00\01\04\6d\61" + "\69\6e\00\03\09\87\80\80\80\00\01\03\00\03\00\01" + "\02\0a\d4\80\80\80\00\04\89\80\80\80\00\00\41\01" + "\41\05\e2\00\6a\0b\8c\80\80\80\00\00\41\0a\d2\00" + "\e0\01\e3\01\00\6a\0b\8d\80\80\80\00\00\41\e4\00" + "\d2\01\e0\01\e3\01\00\6a\0b\9d\80\80\80\00\01\01" + "\63\03\02\04\d2\02\e0\01\e3\01\01\00\00\00\0f\0b" + "\21\00\41\02\6c\20\00\e3\03\00\0b" +) +(module instance) +(assert_return (invoke "main") (i32.const 0x79)) diff --git a/test/regress/ext:stack-switching/publish_empty0.wast b/test/regress/ext:stack-switching/publish_empty0.wast new file mode 100644 index 000000000..810b07372 --- /dev/null +++ b/test/regress/ext:stack-switching/publish_empty0.wast @@ -0,0 +1,34 @@ +;; Resumes without clauses in the middle of a chain publish empty tables; the suspend walks through them. +(module + (type $f (func (result i32))) + (type $c (cont $f)) + (type $fs (func (param i32) (result i32))) + (type $cs (cont $fs)) + (tag $t (param i32) (result i32)) + + (func $leaf (result i32) + (i32.add (i32.const 1) (suspend $t (i32.const 5))) + ) + (func $mid (result i32) + (i32.add (i32.const 10) (resume $c (cont.new $c (ref.func $leaf)))) + ) + (func $mid2 (result i32) + (i32.add (i32.const 100) (resume $c (cont.new $c (ref.func $mid)))) + ) + (elem declare func $leaf $mid $mid2) + + (func (export "main") (result i32) + (local $k (ref null $cs)) + (block $h (result i32 (ref null $cs)) + (return (resume $c (on $t $h) (cont.new $c (ref.func $mid2)))) + ) + (local.set $k) + ;; 5 arrives through two clause-less resumes; answer 10: leaf 11, mid 21, mid2 121 + (i32.const 2) + (i32.mul) + (local.get $k) + (resume $cs) + ) +) + +(assert_return (invoke "main") (i32.const 121)) diff --git a/test/regress/ext:stack-switching/publish_mixed_tier0.bin.wast b/test/regress/ext:stack-switching/publish_mixed_tier0.bin.wast new file mode 100644 index 000000000..fabe973cb --- /dev/null +++ b/test/regress/ext:stack-switching/publish_mixed_tier0.bin.wast @@ -0,0 +1,50 @@ +(module definition binary + "\00\61\73\6d\01\00\00\00\01\94\80\80\80\00\05\60" + "\00\01\7f\5d\00\60\01\7f\01\7f\5d\02\60\00\02\7f" + "\63\03\03\85\80\80\80\00\04\00\00\02\00\0d\83\80" + "\80\80\00\01\00\02\07\90\80\80\80\00\02\04\6d\61" + "\69\6e\00\02\05\6d\61\69\6e\32\00\03\09\86\80\80" + "\80\00\01\03\00\02\00\01\0a\ec\80\80\80\00\04\89" + "\80\80\80\00\00\41\0a\41\01\e2\00\6a\0b\89\80\80" + "\80\00\00\41\14\41\02\e2\00\6a\0b\a7\80\80\80\00" + "\01\01\63\03\02\04\20\00\04\64\00\d2\01\05\d2\00" + "\0b\e0\01\e3\01\01\00\00\00\0f\0b\21\01\41\e4\00" + "\6a\20\01\e3\03\00\0b\9e\80\80\80\00\01\01\63\03" + "\02\04\d2\00\e0\01\e3\01\01\00\00\00\0f\0b\21\00" + "\41\e4\00\6a\20\00\e3\03\00\0b" +) +(module instance) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x0)) (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x1)) (i32.const 0x7a)) +(assert_return (invoke "main" (i32.const 0x1)) (i32.const 0x7a)) +(assert_return (invoke "main" (i32.const 0x1)) (i32.const 0x7a)) +(assert_return (invoke "main2") (i32.const 0x6f)) +(assert_return (invoke "main2") (i32.const 0x6f)) +(assert_return (invoke "main2") (i32.const 0x6f)) +(assert_return (invoke "main" (i32.const 0x1)) (i32.const 0x7a)) +(assert_return (invoke "main" (i32.const 0x1)) (i32.const 0x7a)) +(assert_return (invoke "main" (i32.const 0x1)) (i32.const 0x7a)) +(assert_return (invoke "main" (i32.const 0x1)) (i32.const 0x7a)) +(assert_return (invoke "main" (i32.const 0x1)) (i32.const 0x7a)) +(assert_return (invoke "main" (i32.const 0x1)) (i32.const 0x7a)) +(assert_return (invoke "main" (i32.const 0x1)) (i32.const 0x7a)) +(assert_return (invoke "main" (i32.const 0x1)) (i32.const 0x7a)) +(assert_return (invoke "main2") (i32.const 0x6f)) +(assert_return (invoke "main2") (i32.const 0x6f)) +(assert_return (invoke "main2") (i32.const 0x6f)) +(assert_return (invoke "main2") (i32.const 0x6f)) +(assert_return (invoke "main2") (i32.const 0x6f)) +(assert_return (invoke "main2") (i32.const 0x6f)) +(assert_return (invoke "main2") (i32.const 0x6f)) +(assert_return (invoke "main2") (i32.const 0x6f)) diff --git a/test/regress/ext:stack-switching/publish_mixed_tier0.wast b/test/regress/ext:stack-switching/publish_mixed_tier0.wast new file mode 100644 index 000000000..25ff55028 --- /dev/null +++ b/test/regress/ext:stack-switching/publish_mixed_tier0.wast @@ -0,0 +1,78 @@ +;; Run every combination of interpreted and compiled resumer and suspender. +(module + (type $f (func (result i32))) + (type $c (cont $f)) + (type $fs (func (param i32) (result i32))) + (type $cs (cont $fs)) + (tag $t (param i32) (result i32)) + + (func $childA (result i32) (i32.add (i32.const 10) (suspend $t (i32.const 1)))) + (func $childB (result i32) (i32.add (i32.const 20) (suspend $t (i32.const 2)))) + (elem declare func $childA $childB) + + (func (export "main") (param $which i32) (result i32) + (local $k (ref null $cs)) + (block $h (result i32 (ref null $cs)) + (return (resume $c (on $t $h) + (cont.new $c + (if (result (ref $f)) (local.get $which) + (then (ref.func $childB)) + (else (ref.func $childA)))))) + ) + (local.set $k) + (i32.const 100) + (i32.add) + (local.get $k) + (resume $cs) + ) + (func (export "main2") (result i32) + (local $k (ref null $cs)) + (block $h (result i32 (ref null $cs)) + (return (resume $c (on $t $h) (cont.new $c (ref.func $childA)))) + ) + (local.set $k) + (i32.const 100) + (i32.add) + (local.get $k) + (resume $cs) + ) +) + +;; main and childA get compiled along the way +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +(assert_return (invoke "main" (i32.const 0)) (i32.const 111)) +;; compiled resumer, interpreted suspender +(assert_return (invoke "main" (i32.const 1)) (i32.const 122)) +(assert_return (invoke "main" (i32.const 1)) (i32.const 122)) +(assert_return (invoke "main" (i32.const 1)) (i32.const 122)) +;; interpreted resumer, compiled suspender +(assert_return (invoke "main2") (i32.const 111)) +(assert_return (invoke "main2") (i32.const 111)) +(assert_return (invoke "main2") (i32.const 111)) +;; childB and main2 get compiled too +(assert_return (invoke "main" (i32.const 1)) (i32.const 122)) +(assert_return (invoke "main" (i32.const 1)) (i32.const 122)) +(assert_return (invoke "main" (i32.const 1)) (i32.const 122)) +(assert_return (invoke "main" (i32.const 1)) (i32.const 122)) +(assert_return (invoke "main" (i32.const 1)) (i32.const 122)) +(assert_return (invoke "main" (i32.const 1)) (i32.const 122)) +(assert_return (invoke "main" (i32.const 1)) (i32.const 122)) +(assert_return (invoke "main" (i32.const 1)) (i32.const 122)) +(assert_return (invoke "main2") (i32.const 111)) +(assert_return (invoke "main2") (i32.const 111)) +(assert_return (invoke "main2") (i32.const 111)) +(assert_return (invoke "main2") (i32.const 111)) +(assert_return (invoke "main2") (i32.const 111)) +(assert_return (invoke "main2") (i32.const 111)) +(assert_return (invoke "main2") (i32.const 111)) +(assert_return (invoke "main2") (i32.const 111)) diff --git a/test/regress/ext:stack-switching/publish_switch_first0.bin.wast b/test/regress/ext:stack-switching/publish_switch_first0.bin.wast new file mode 100644 index 000000000..b908e8d48 --- /dev/null +++ b/test/regress/ext:stack-switching/publish_switch_first0.bin.wast @@ -0,0 +1,24 @@ +(module definition binary + "\00\61\73\6d\01\00\00\00\01\9c\80\80\80\00\07\60" + "\00\01\7f\5d\00\60\01\63\01\01\7f\5d\02\60\01\7f" + "\01\7f\5d\04\60\00\02\7f\63\05\03\86\80\80\80\00" + "\05\02\00\00\04\04\0d\85\80\80\80\00\02\00\00\00" + "\04\07\a0\80\80\80\00\02\0c\73\77\69\74\63\68\5f" + "\66\69\72\73\74\00\03\0d\73\75\73\70\65\6e\64\5f" + "\66\69\72\73\74\00\04\09\87\80\80\80\00\01\03\00" + "\03\00\01\02\0a\87\81\80\80\00\05\87\80\80\80\00" + "\00\20\00\1a\41\0d\0b\8c\80\80\80\00\00\d2\00\e0" + "\03\e6\03\00\41\e3\00\0b\8a\80\80\80\00\00\41\e4" + "\00\41\07\e2\01\6a\0b\a8\80\80\80\00\01\01\63\05" + "\02\06\20\00\04\64\00\d2\01\05\d2\02\0b\e0\01\e3" + "\01\02\01\00\00\01\00\0f\0b\21\01\1a\41\08\20\01" + "\e3\05\00\0b\a8\80\80\80\00\01\01\63\05\02\06\20" + "\00\04\64\00\d2\01\05\d2\02\0b\e0\01\e3\01\02\00" + "\01\00\01\00\0f\0b\21\01\1a\41\08\20\01\e3\05\00" + "\0b" +) +(module instance) +(assert_return (invoke "switch_first" (i32.const 0x1)) (i32.const 0xd)) +(assert_return (invoke "switch_first" (i32.const 0x0)) (i32.const 0x6c)) +(assert_return (invoke "suspend_first" (i32.const 0x1)) (i32.const 0xd)) +(assert_return (invoke "suspend_first" (i32.const 0x0)) (i32.const 0x6c)) diff --git a/test/regress/ext:stack-switching/publish_switch_first0.wast b/test/regress/ext:stack-switching/publish_switch_first0.wast new file mode 100644 index 000000000..348de34f6 --- /dev/null +++ b/test/regress/ext:stack-switching/publish_switch_first0.wast @@ -0,0 +1,56 @@ +;; A resume site with both a switch clause and a suspend clause, in either order. +(module + (type $f1 (func (result i32))) + (type $c1 (cont $f1)) + (type $f2 (func (param (ref null $c1)) (result i32))) + (type $c2 (cont $f2)) + (type $f3 (func (param i32) (result i32))) + (type $c3 (cont $f3)) + (tag $a (result i32)) + (tag $b (param i32) (result i32)) + + (func $switchee (param (ref null $c1)) (result i32) + (drop (local.get 0)) + (i32.const 13) + ) + (func $switcher (result i32) + (switch $c2 $a (cont.new $c2 (ref.func $switchee))) + (i32.const 99) + ) + (func $suspender (result i32) + (i32.add (i32.const 100) (suspend $b (i32.const 7))) + ) + (elem declare func $switchee $switcher $suspender) + + (func (export "switch_first") (param $which i32) (result i32) + (local $k (ref null $c3)) + (block $h (result i32 (ref null $c3)) + (return (resume $c1 (on $a switch) (on $b $h) + (cont.new $c1 + (if (result (ref $f1)) (local.get $which) + (then (ref.func $switcher)) + (else (ref.func $suspender)))))) + ) + (local.set $k) + (drop) + (resume $c3 (i32.const 8) (local.get $k)) + ) + (func (export "suspend_first") (param $which i32) (result i32) + (local $k (ref null $c3)) + (block $h (result i32 (ref null $c3)) + (return (resume $c1 (on $b $h) (on $a switch) + (cont.new $c1 + (if (result (ref $f1)) (local.get $which) + (then (ref.func $switcher)) + (else (ref.func $suspender)))))) + ) + (local.set $k) + (drop) + (resume $c3 (i32.const 8) (local.get $k)) + ) +) + +(assert_return (invoke "switch_first" (i32.const 1)) (i32.const 13)) +(assert_return (invoke "switch_first" (i32.const 0)) (i32.const 108)) +(assert_return (invoke "suspend_first" (i32.const 1)) (i32.const 13)) +(assert_return (invoke "suspend_first" (i32.const 0)) (i32.const 108)) diff --git a/test/regress/ext:stack-switching/publish_two_instances0.bin.wast b/test/regress/ext:stack-switching/publish_two_instances0.bin.wast new file mode 100644 index 000000000..6c0323cc6 --- /dev/null +++ b/test/regress/ext:stack-switching/publish_two_instances0.bin.wast @@ -0,0 +1,33 @@ +(module definition $M binary + "\00\61\73\6d\01\00\00\00\01\8a\80\80\80\00\03\60" + "\00\00\5d\00\60\00\01\7f\03\83\80\80\80\00\02\00" + "\02\0d\83\80\80\80\00\01\00\00\07\94\80\80\80\00" + "\03\01\74\04\00\05\63\68\69\6c\64\00\00\04\6d\61" + "\69\6e\00\01\09\85\80\80\80\00\01\03\00\01\00\0a" + "\a5\80\80\80\00\02\84\80\80\80\00\00\e2\00\0b\96" + "\80\80\80\00\00\02\63\01\d2\00\e0\01\e3\01\01\00" + "\00\00\41\00\0f\0b\1a\41\01\0b" +) +(module instance $I1 $M) +(module instance $I2 $M) +(register "I1" $I1) +(register "I2" $I2) +(assert_return (invoke $I1 "main") (i32.const 0x1)) +(assert_return (invoke $I2 "main") (i32.const 0x1)) +(module definition $N binary + "\00\61\73\6d\01\00\00\00\01\8b\80\80\80\00\03\60" + "\00\00\5d\00\60\01\7f\01\7f\02\a7\80\80\80\00\04" + "\02\49\32\01\74\04\00\00\02\49\31\01\74\04\00\00" + "\02\49\31\05\63\68\69\6c\64\00\00\02\49\32\05\63" + "\68\69\6c\64\00\00\03\82\80\80\80\00\01\02\07\88" + "\80\80\80\00\01\04\6d\61\69\6e\00\02\09\86\80\80" + "\80\00\01\03\00\02\00\01\0a\b0\80\80\80\00\01\aa" + "\80\80\80\00\00\02\63\01\02\63\01\20\00\04\64\00" + "\d2\00\05\d2\01\0b\e0\01\e3\01\02\00\00\00\00\01" + "\01\41\00\0f\0b\1a\41\02\0f\0b\1a\41\01\0b" +) +(module instance $J $N) +(assert_return (invoke $J "main" (i32.const 0x1)) (i32.const 0x1)) +(assert_return (invoke $J "main" (i32.const 0x0)) (i32.const 0x2)) +(assert_return (invoke $J "main" (i32.const 0x1)) (i32.const 0x1)) +(assert_return (invoke $J "main" (i32.const 0x0)) (i32.const 0x2)) diff --git a/test/regress/ext:stack-switching/publish_two_instances0.wast b/test/regress/ext:stack-switching/publish_two_instances0.wast new file mode 100644 index 000000000..abff241e2 --- /dev/null +++ b/test/regress/ext:stack-switching/publish_two_instances0.wast @@ -0,0 +1,55 @@ +;; Tag identity is per instance. +(module definition $M + (type $f (func)) + (type $c (cont $f)) + (tag $t (export "t")) + (func $child (export "child") (suspend $t)) + (elem declare func $child) + (func (export "main") (result i32) + (block $h (result (ref null $c)) + (resume $c (on $t $h) (cont.new $c (ref.func $child))) + (return (i32.const 0)) + ) + (drop) + (i32.const 1) + ) +) +(module instance $I1 $M) +(module instance $I2 $M) +(register "I1" $I1) +(register "I2" $I2) + +(assert_return (invoke $I1 "main") (i32.const 1)) +(assert_return (invoke $I2 "main") (i32.const 1)) + +(module definition $N + (type $f (func)) + (type $c (cont $f)) + (tag $t2 (import "I2" "t")) + (tag $t1 (import "I1" "t")) + (func $child1 (import "I1" "child")) + (func $child2 (import "I2" "child")) + (elem declare func $child1 $child2) + (func (export "main") (param $which i32) (result i32) + (block $h1 (result (ref null $c)) + (block $h2 (result (ref null $c)) + (resume $c (on $t2 $h2) (on $t1 $h1) + (cont.new $c + (if (result (ref $f)) (local.get $which) + (then (ref.func $child1)) + (else (ref.func $child2))))) + (return (i32.const 0)) + ) + (drop) + (return (i32.const 2)) + ) + (drop) + (i32.const 1) + ) +) +(module instance $J $N) + +(assert_return (invoke $J "main" (i32.const 1)) (i32.const 1)) +(assert_return (invoke $J "main" (i32.const 0)) (i32.const 2)) +(assert_return (invoke $J "main" (i32.const 1)) (i32.const 1)) +(assert_return (invoke $J "main" (i32.const 0)) (i32.const 2)) diff --git a/test/regress/ext:stack-switching/publish_unwind0.bin.wast b/test/regress/ext:stack-switching/publish_unwind0.bin.wast new file mode 100644 index 000000000..6811037c3 --- /dev/null +++ b/test/regress/ext:stack-switching/publish_unwind0.bin.wast @@ -0,0 +1,32 @@ +(module definition binary + "\00\61\73\6d\01\00\00\00\01\9e\80\80\80\00\07\60" + "\00\01\7f\5d\00\60\01\7f\01\7f\5d\02\60\01\7f\00" + "\60\01\64\00\01\7f\60\00\02\7f\63\03\03\8b\80\80" + "\80\00\0a\00\00\00\00\05\05\00\00\00\00\0d\85\80" + "\80\80\00\02\00\04\00\02\07\c3\80\80\80\00\04\11" + "\63\61\74\63\68\5f\74\68\65\6e\5f\72\65\73\75\6d" + "\65\00\06\17\72\65\73\75\6d\65\5f\74\68\72\6f\77" + "\5f\69\6e\74\6f\5f\63\68\61\69\6e\00\07\04\74\72" + "\61\70\00\08\0a\61\66\74\65\72\5f\74\72\61\70\00" + "\09\09\88\80\80\80\00\01\03\00\04\00\01\02\03\0a" + "\c3\81\80\80\00\0a\86\80\80\80\00\00\41\03\08\00" + "\0b\89\80\80\80\00\00\41\28\41\04\e2\01\6a\0b\83" + "\80\80\80\00\00\00\0b\98\80\80\80\00\00\02\7f\1f" + "\7f\01\00\00\00\d2\01\e0\01\e3\01\00\0b\0f\0b\41" + "\d0\0f\6a\0b\9d\80\80\80\00\01\01\63\03\02\06\20" + "\00\e0\01\e3\01\01\00\01\00\0f\0b\21\01\41\02\6a" + "\20\01\e3\03\00\0b\94\80\80\80\00\00\02\7f\1f\7f" + "\01\00\00\00\20\00\e0\01\e3\01\00\0b\0f\0b\0b\8b" + "\80\80\80\00\00\d2\00\10\05\d2\01\10\04\6a\0b\9e" + "\80\80\80\00\01\01\63\03\02\06\d2\03\e0\01\e3\01" + "\01\00\01\00\0f\0b\21\00\1a\41\05\20\00\e4\03\00" + "\00\0b\86\80\80\80\00\00\d2\02\10\04\0b\86\80\80" + "\80\00\00\d2\01\10\04\0b" +) +(module instance) +(assert_return (invoke "catch_then_resume") (i32.const 0x31)) +(assert_return (invoke "resume_throw_into_chain") (i32.const 0x7d5)) +(assert_trap (invoke "trap") "unreachable") +(assert_return (invoke "after_trap") (i32.const 0x2e)) +(assert_return (invoke "catch_then_resume") (i32.const 0x31)) +(assert_return (invoke "resume_throw_into_chain") (i32.const 0x7d5)) diff --git a/test/regress/ext:stack-switching/publish_unwind0.wast b/test/regress/ext:stack-switching/publish_unwind0.wast new file mode 100644 index 000000000..bba8c0452 --- /dev/null +++ b/test/regress/ext:stack-switching/publish_unwind0.wast @@ -0,0 +1,72 @@ +;; Resumes left through exceptions and traps must leave the parent stack clean for later resumes. +(module + (type $f (func (result i32))) + (type $c (cont $f)) + (type $fs (func (param i32) (result i32))) + (type $cs (cont $fs)) + (tag $ex (param i32)) + (tag $t (param i32) (result i32)) + + (func $thrower (result i32) (throw $ex (i32.const 3))) + (func $suspender (result i32) (i32.add (i32.const 40) (suspend $t (i32.const 4)))) + (func $trapper (result i32) (unreachable)) + ;; catches an exception thrown into the child it resumes + (func $catching_middle (result i32) + (block $caught (result i32) + (try_table (result i32) (catch $ex $caught) + (resume $c (cont.new $c (ref.func $suspender))) + ) + (return) + ) + (i32.add (i32.const 2000)) + ) + (elem declare func $thrower $suspender $trapper $catching_middle) + + ;; shared resume site with a handler for $t + (func $run (param $child (ref $f)) (result i32) + (local $k (ref null $cs)) + (block $h (result i32 (ref null $cs)) + (return (resume $c (on $t $h) (cont.new $c (local.get $child)))) + ) + (local.set $k) + (i32.const 2) + (i32.add) + (local.get $k) + (resume $cs) + ) + + ;; the child throws and the parent catches at its resume, then resumes a suspender elsewhere + (func $resume_catching (param $child (ref $f)) (result i32) + (block $caught (result i32) + (try_table (result i32) (catch $ex $caught) + (resume $c (cont.new $c (local.get $child))) + ) + (return) + ) + ) + (func (export "catch_then_resume") (result i32) + (call $resume_catching (ref.func $thrower)) + (call $run (ref.func $suspender)) + (i32.add) + ) + ;; an exception thrown into a suspended chain is caught by the middle stack, unwinding its resume + (func (export "resume_throw_into_chain") (result i32) + (local $k (ref null $cs)) + (block $h (result i32 (ref null $cs)) + (return (resume $c (on $t $h) (cont.new $c (ref.func $catching_middle)))) + ) + (local.set $k) + (drop) + (resume_throw $cs $ex (i32.const 5) (local.get $k)) + ) + ;; the child traps; the next runs start from fresh stacks + (func (export "trap") (result i32) (call $run (ref.func $trapper))) + (func (export "after_trap") (result i32) (call $run (ref.func $suspender))) +) + +(assert_return (invoke "catch_then_resume") (i32.const 49)) +(assert_return (invoke "resume_throw_into_chain") (i32.const 2005)) +(assert_trap (invoke "trap") "unreachable") +(assert_return (invoke "after_trap") (i32.const 46)) +(assert_return (invoke "catch_then_resume") (i32.const 49)) +(assert_return (invoke "resume_throw_into_chain") (i32.const 2005)) diff --git a/test/stress/suspend_gc0.bin.wast b/test/stress/suspend_gc0.bin.wast new file mode 100644 index 000000000..194f04af3 --- /dev/null +++ b/test/stress/suspend_gc0.bin.wast @@ -0,0 +1,21 @@ +(module definition binary + "\00\61\73\6d\01\00\00\00\01\a4\80\80\80\00\08\60" + "\00\01\7f\5d\00\60\01\7f\01\7f\5d\02\5f\01\7f\00" + "\5e\7f\01\60\02\7f\63\04\01\7f\60\00\03\7f\63\04" + "\63\03\03\84\80\80\80\00\03\00\02\02\0d\83\80\80" + "\80\00\01\00\06\06\88\80\80\80\00\01\7f\01\41\b9" + "\e0\00\0b\07\88\80\80\80\00\01\04\6d\61\69\6e\00" + "\02\09\85\80\80\80\00\01\03\00\01\01\0a\9e\81\80" + "\80\00\03\a2\80\80\80\00\00\23\00\23\00\41\0d\74" + "\73\24\00\23\00\23\00\41\11\76\73\24\00\23\00\23" + "\00\41\05\74\73\24\00\23\00\0b\a2\80\80\80\00\01" + "\01\7f\03\40\20\01\20\00\20\00\fb\00\04\e2\00\6a" + "\21\01\20\00\41\01\6b\21\00\20\00\0d\00\0b\20\01" + "\0b\ca\80\80\80\00\03\01\63\03\01\63\04\03\7f\d2" + "\01\e0\03\21\01\20\00\21\05\03\40\02\07\20\04\20" + "\05\20\01\e3\03\01\00\00\00\6a\0f\0b\21\01\21\02" + "\21\03\10\00\41\ff\01\71\fb\07\05\1a\20\04\20\02" + "\fb\02\04\00\6a\21\04\41\01\21\05\0c\00\0b\00\0b" +) +(module instance) +(assert_return (invoke "main" (i32.const 0x17d_7840)) (i32.const 0x95ba_3c60)) diff --git a/test/stress/suspend_gc0.wast b/test/stress/suspend_gc0.wast new file mode 100644 index 000000000..8a52ded49 --- /dev/null +++ b/test/stress/suspend_gc0.wast @@ -0,0 +1,53 @@ +;; Collections land at arbitrary points of the inline suspend sequence. +(module + (type $f (func (result i32))) + (type $c (cont $f)) + (type $fs (func (param i32) (result i32))) + (type $cs (cont $fs)) + (type $s (struct (field i32))) + (type $arr (array (mut i32))) + (tag $t (param i32 (ref null $s)) (result i32)) + (global $seed (mut i32) (i32.const 12345)) + + (func $rand (result i32) + (global.set $seed (i32.xor (global.get $seed) (i32.shl (global.get $seed) (i32.const 13)))) + (global.set $seed (i32.xor (global.get $seed) (i32.shr_u (global.get $seed) (i32.const 17)))) + (global.set $seed (i32.xor (global.get $seed) (i32.shl (global.get $seed) (i32.const 5)))) + (global.get $seed) + ) + (func $gen (param $n i32) (result i32) + (local $acc i32) + (loop $l + (local.set $acc (i32.add (local.get $acc) (suspend $t (local.get $n) (struct.new $s (local.get $n))))) + (local.set $n (i32.sub (local.get $n) (i32.const 1))) + (br_if $l (local.get $n)) + ) + (local.get $acc) + ) + (elem declare func $gen) + + (func (export "main") (param $n i32) (result i32) + (local $k (ref null $cs)) + (local $st (ref null $s)) + (local $i i32) + (local $sum i32) + (local $reply i32) + (local.set $k (cont.new $cs (ref.func $gen))) + (local.set $reply (local.get $n)) + (loop $l + (block $h (result i32 (ref null $s) (ref null $cs)) + (return (i32.add (local.get $sum) (resume $cs (on $t $h) (local.get $reply) (local.get $k)))) + ) + (local.set $k) + (local.set $st) + (local.set $i) + (drop (array.new_default $arr (i32.and (call $rand) (i32.const 255)))) + (local.set $sum (i32.add (local.get $sum) (struct.get $s 0 (local.get $st)))) + (local.set $reply (i32.const 1)) + (br $l) + ) + (unreachable) + ) +) + +(assert_return (invoke "main" (i32.const 25000000)) (i32.const 2512010336))