//! Frontend: convert Wasm to IR.

#![allow(dead_code)]

use crate::entity::EntityRef;
use crate::errors::FrontendError;
use crate::ir::*;
use crate::op_traits::{op_inputs, op_outputs};
use crate::ops::Operator;
use crate::pool::ListRef;
use addr2line::gimli;
use anyhow::{bail, Result};
use fxhash::{FxHashMap, FxHashSet};
use log::trace;
use std::convert::TryFrom;
use wasmparser::{
    BlockType, DataKind, ExternalKind, Name, NameSectionReader, Parser, Payload, TypeRef,
};

#[derive(Clone, Copy, Debug, Default)]
pub struct FrontendOptions {
    pub debug: bool,
}

pub fn wasm_to_ir<'a>(bytes: &'a [u8], options: &FrontendOptions) -> Result<Module<'a>> {
    let mut module = Module::with_orig_bytes(bytes);
    let parser = Parser::new(0);
    let mut next_func = 0;
    let mut dwarf = gimli::Dwarf::default();
    let mut extra_sections = ExtraSections::default();
    for payload in parser.parse_all(bytes) {
        let payload = payload?;
        handle_payload(
            &mut module,
            payload,
            &mut next_func,
            &mut dwarf,
            &mut extra_sections,
        )?;
    }
    dwarf.locations =
        gimli::LocationLists::new(extra_sections.debug_loc, extra_sections.debug_loclists);
    dwarf.ranges =
        gimli::RangeLists::new(extra_sections.debug_ranges, extra_sections.debug_rnglists);

    if options.debug {
        let debug_map = DebugMap::from_dwarf(dwarf, &mut module.debug, extra_sections.code_offset)?;
        module.debug_map = debug_map;
    }

    Ok(module)
}

fn parse_init_expr<'a>(init_expr: &wasmparser::ConstExpr<'a>) -> Result<Option<u64>> {
    let operators = init_expr
        .get_operators_reader()
        .into_iter()
        .collect::<Result<Vec<wasmparser::Operator>, _>>()?;
    if operators.len() == 1 && matches!(&operators[0], &wasmparser::Operator::End) {
        return Ok(None);
    }
    if operators.len() != 2 || !matches!(&operators[1], &wasmparser::Operator::End) {
        bail!(FrontendError::UnsupportedFeature(format!(
            "Unsupported operator seq in base-address expr: {:?}",
            operators
        )));
    }
    Ok(match &operators[0] {
        &wasmparser::Operator::I32Const { value } => Some(value as u64),
        &wasmparser::Operator::I64Const { value } => Some(value as u64),
        &wasmparser::Operator::F32Const { value } => Some(value.bits() as u64),
        &wasmparser::Operator::F64Const { value } => Some(value.bits()),
        op => anyhow::bail!(FrontendError::UnsupportedFeature(format!(
            "Unsupported data segment base-address operator: {:?}",
            op
        ))),
    })
}

#[derive(Default)]
struct ExtraSections<'a> {
    debug_loc: gimli::DebugLoc<gimli::EndianSlice<'a, gimli::LittleEndian>>,
    debug_loclists: gimli::DebugLocLists<gimli::EndianSlice<'a, gimli::LittleEndian>>,
    debug_ranges: gimli::DebugRanges<gimli::EndianSlice<'a, gimli::LittleEndian>>,
    debug_rnglists: gimli::DebugRngLists<gimli::EndianSlice<'a, gimli::LittleEndian>>,
    code_offset: u32,
}

fn handle_payload<'a>(
    module: &mut Module<'a>,
    payload: Payload<'a>,
    next_func: &mut usize,
    dwarf: &mut gimli::Dwarf<gimli::EndianSlice<'a, gimli::LittleEndian>>,
    extra_sections: &mut ExtraSections<'a>,
) -> Result<()> {
    trace!("Wasm parser item: {:?}", payload);
    match payload {
        Payload::TypeSection(reader) => {
            for rec_group in reader {
                for ty in rec_group?.into_types() {
                    match &ty.composite_type {
                        wasmparser::CompositeType::Func(fty) => {
                            module.signatures.push(fty.into());
                        }
                        _ => bail!(FrontendError::UnsupportedFeature(
                            "non-function type in type section".into()
                        )),
                    }
                }
            }
        }
        Payload::ImportSection(reader) => {
            for import in reader {
                let import = import?;
                let module_name = import.module.to_owned();
                let name = import.name.to_owned();
                let kind = match import.ty {
                    TypeRef::Func(sig_idx) => {
                        let func = module
                            .funcs
                            .push(FuncDecl::Import(Signature::from(sig_idx), "".to_owned()));
                        *next_func += 1;
                        ImportKind::Func(func)
                    }
                    TypeRef::Global(ty) => {
                        let mutable = ty.mutable;
                        let ty = ty.content_type.into();
                        let global = module.globals.push(GlobalData {
                            ty,
                            value: None,
                            mutable,
                        });
                        ImportKind::Global(global)
                    }
                    TypeRef::Table(ty) => {
                        let table = module.frontend_add_table(
                            ty.element_type.into(),
                            ty.initial,
                            ty.maximum,
                        );
                        ImportKind::Table(table)
                    }
                    TypeRef::Memory(mem) => {
                        let mem = module.memories.push(MemoryData {
                            initial_pages: mem.initial as usize,
                            maximum_pages: mem.maximum.map(|max| max as usize),
                            segments: vec![],
                            memory64: mem.memory64,
                            shared: mem.shared
                        });
                        ImportKind::Memory(mem)
                    }
                    t => {
                        bail!(FrontendError::UnsupportedFeature(format!(
                            "Unknown import type: {:?}",
                            t
                        )));
                    }
                };
                module.imports.push(Import {
                    module: module_name,
                    name,
                    kind,
                });
            }
        }
        Payload::GlobalSection(reader) => {
            for global in reader {
                let global = global?;
                let mutable = global.ty.mutable;
                let ty = global.ty.content_type.into();
                let init_expr = parse_init_expr(&global.init_expr)?;
                module.globals.push(GlobalData {
                    ty,
                    value: init_expr,
                    mutable,
                });
            }
        }
        Payload::TableSection(reader) => {
            for table in reader {
                let table = table?;
                module.frontend_add_table(
                    table.ty.element_type.into(),
                    table.ty.initial,
                    table.ty.maximum,
                );
            }
        }
        Payload::FunctionSection(reader) => {
            for sig_idx in reader {
                let sig_idx = Signature::from(sig_idx?);
                module.funcs.push(FuncDecl::Body(
                    sig_idx,
                    "".to_owned(),
                    FunctionBody::default(),
                ));
            }
        }
        Payload::CodeSectionStart { range, .. } => {
            extra_sections.code_offset = range.start as u32;
        }
        Payload::CodeSectionEntry(body) => {
            let func_idx = Func::new(*next_func);
            *next_func += 1;

            let sig = module.funcs[func_idx].sig();
            let name = module.funcs[func_idx].name().to_owned();
            module.funcs[func_idx] = FuncDecl::Lazy(sig, name, body);
        }
        Payload::ExportSection(reader) => {
            for export in reader {
                let export = export?;
                let name = export.name.to_owned();
                let kind = match export.kind {
                    ExternalKind::Func => Some(ExportKind::Func(Func::from(export.index))),
                    ExternalKind::Table => Some(ExportKind::Table(Table::from(export.index))),
                    ExternalKind::Global => Some(ExportKind::Global(Global::from(export.index))),
                    ExternalKind::Memory => Some(ExportKind::Memory(Memory::from(export.index))),
                    _ => None,
                };
                if let Some(kind) = kind {
                    module.exports.push(Export { name, kind });
                }
            }
        }
        Payload::MemorySection(reader) => {
            for memory in reader {
                let memory = memory?;
                module.memories.push(MemoryData {
                    initial_pages: memory.initial as usize,
                    maximum_pages: memory.maximum.map(|max| max as usize),
                    segments: vec![],
                    memory64: memory.memory64,
                    shared: memory.shared
                });
            }
        }
        Payload::DataSection(reader) => {
            for segment in reader {
                let segment = segment?;
                match &segment.kind {
                    DataKind::Passive => {}
                    DataKind::Active {
                        memory_index,
                        offset_expr,
                    } => {
                        let data = segment.data.to_vec();
                        let memory = Memory::from(*memory_index);
                        let offset = parse_init_expr(offset_expr)?.unwrap_or(0) as usize;
                        module.memories[memory]
                            .segments
                            .push(MemorySegment { offset, data });
                    }
                }
            }
        }
        Payload::CustomSection(reader) if reader.name() == "name" => {
            let name_reader = NameSectionReader::new(reader.data(), reader.data_offset());
            for subsection in name_reader {
                let subsection = subsection?;
                match subsection {
                    Name::Function(names) => {
                        for name in names {
                            let name = name?;
                            module.funcs[Func::new(name.index as usize)].set_name(name.name);
                        }
                    }
                    _ => {}
                }
            }
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_info" => {
            dwarf.debug_info = gimli::DebugInfo::new(reader.data(), gimli::LittleEndian);
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_abbrev" => {
            dwarf.debug_abbrev = gimli::DebugAbbrev::new(reader.data(), gimli::LittleEndian);
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_addr" => {
            dwarf.debug_addr =
                gimli::DebugAddr::from(gimli::EndianSlice::new(reader.data(), gimli::LittleEndian));
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_aranges" => {
            dwarf.debug_aranges = gimli::DebugAranges::new(reader.data(), gimli::LittleEndian);
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_line" => {
            dwarf.debug_line = gimli::DebugLine::new(reader.data(), gimli::LittleEndian);
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_line_str" => {
            dwarf.debug_line_str = gimli::DebugLineStr::new(reader.data(), gimli::LittleEndian);
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_str" => {
            dwarf.debug_str = gimli::DebugStr::new(reader.data(), gimli::LittleEndian);
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_str_offsets" => {
            dwarf.debug_str_offsets = gimli::DebugStrOffsets::from(gimli::EndianSlice::new(
                reader.data(),
                gimli::LittleEndian,
            ));
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_types" => {
            dwarf.debug_types = gimli::DebugTypes::new(reader.data(), gimli::LittleEndian);
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_loc" => {
            extra_sections.debug_loc = gimli::DebugLoc::new(reader.data(), gimli::LittleEndian);
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_loclists" => {
            extra_sections.debug_loclists =
                gimli::DebugLocLists::new(reader.data(), gimli::LittleEndian);
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_ranges" => {
            extra_sections.debug_ranges =
                gimli::DebugRanges::new(reader.data(), gimli::LittleEndian);
        }
        Payload::CustomSection(reader) if reader.name() == ".debug_rnglists" => {
            extra_sections.debug_rnglists =
                gimli::DebugRngLists::new(reader.data(), gimli::LittleEndian);
        }
        Payload::CustomSection(reader) => {
            module
                .custom_sections
                .insert(reader.name().to_owned(), reader.data().to_owned());
        }
        Payload::Version { .. } => {}
        Payload::ElementSection(reader) => {
            for element in reader {
                let element = element?;
                match &element.kind {
                    wasmparser::ElementKind::Passive => {}
                    wasmparser::ElementKind::Declared => {}
                    wasmparser::ElementKind::Active {
                        table_index,
                        offset_expr,
                    } => {
                        let table = Table::from(table_index.unwrap_or(0));
                        let offset = parse_init_expr(&offset_expr)?.unwrap_or(0) as usize;
                        let funcs = match element.items {
                            wasmparser::ElementItems::Functions(items) => {
                                let mut funcs = vec![];
                                for item in items {
                                    let item = item?;
                                    let func = Func::from(item);
                                    funcs.push(func);
                                }
                                funcs
                            }
                            wasmparser::ElementItems::Expressions(_, const_exprs) => {
                                let mut funcs = vec![];
                                for const_expr in const_exprs {
                                    let const_expr = const_expr?;
                                    let mut func = None;
                                    for op in const_expr.get_operators_reader() {
                                        let op = op?;
                                        match op {
                                            wasmparser::Operator::End => {}
                                            wasmparser::Operator::RefFunc { function_index } => {
                                                func = Some(Func::from(function_index));
                                            }
                                            wasmparser::Operator::RefNull { .. } => {
                                                func = Some(Func::invalid());
                                            }
                                            _ => panic!("Unsupported table-init op: {:?}", op),
                                        }
                                    }
                                    funcs.push(func.unwrap_or(Func::invalid()));
                                }
                                funcs
                            }
                        };

                        let table_items = module.tables[table].func_elements.as_mut().unwrap();
                        let new_size = offset.checked_add(funcs.len()).ok_or_else(|| {
                            FrontendError::TooLarge(format!(
                                "Overflowing element offset + length: {} + {}",
                                offset,
                                funcs.len()
                            ))
                        })?;
                        if new_size > table_items.len() {
                            static MAX_TABLE: usize = 100_000;
                            if new_size > MAX_TABLE {
                                bail!(FrontendError::TooLarge(format!(
                                    "Too many table elements: {:?}",
                                    new_size
                                )));
                            }
                            table_items.resize(new_size, Func::invalid());
                        }
                        table_items[offset..new_size].copy_from_slice(&funcs[..]);
                    }
                }
            }
        }
        Payload::End(_) => {}
        Payload::StartSection { func, .. } => {
            module.start_func = Some(Func::from(func));
        }
        payload => {
            log::warn!("Skipping section: {:?}", payload);
        }
    }

    Ok(())
}

struct DebugLocReader<'a> {
    code_offset: u32,
    locs: &'a [(u32, u32, SourceLoc)],
}

impl<'a> DebugLocReader<'a> {
    fn new(module: &'a Module, func_offset_in_file: u32) -> Self {
        let code_offset = module.debug_map.code_offset;
        let func_address = func_offset_in_file - code_offset;
        let start = match module
            .debug_map
            .tuples
            .binary_search_by(|&(start, len, _)| {
                use std::cmp::Ordering::*;
                if start > func_address {
                    Greater
                } else if (start + len) <= func_address {
                    Less
                } else {
                    Equal
                }
            }) {
            Ok(idx) => idx,
            Err(idx) => idx,
        };
        DebugLocReader {
            code_offset,
            locs: &module.debug_map.tuples[start..],
        }
    }

    fn get_loc(&mut self, offset: usize) -> SourceLoc {
        let address = u32::try_from(offset).unwrap() - self.code_offset;
        while self.locs.len() > 0 {
            let (start, len, loc) = self.locs[0];
            if address < start {
                break;
            }
            if address < start + len {
                return loc;
            }
            self.locs = &self.locs[1..];
        }
        SourceLoc::invalid()
    }
}

pub(crate) fn parse_body<'a>(
    module: &'a Module,
    my_sig: Signature,
    body: &mut wasmparser::FunctionBody,
) -> Result<FunctionBody> {
    let mut ret: FunctionBody = FunctionBody::default();

    let mut debug_locs = DebugLocReader::new(module, body.range().start as u32);

    for &param in &module.signatures[my_sig].params[..] {
        ret.locals.push(param.into());
    }
    ret.n_params = module.signatures[my_sig].params.len();
    for &r in &module.signatures[my_sig].returns[..] {
        ret.rets.push(r.into());
    }

    let mut locals = body.get_locals_reader()?;
    for _ in 0..locals.get_count() {
        let (count, ty) = locals.read()?;
        for _ in 0..count {
            ret.locals.push(ty.into());
        }
    }
    let locals = ret.locals.clone();

    trace!(
        "Parsing function body: locals = {:?} sig = {:?}",
        ret.locals,
        module.signatures[my_sig]
    );

    let mut builder = FunctionBodyBuilder::new(module, my_sig, &mut ret);
    let entry = Block::new(0);
    builder.body.entry = entry;
    builder.locals.seal_block_preds(entry, &mut builder.body);
    builder.locals.start_block(entry);

    for (arg_idx, &arg_ty) in module.signatures[my_sig].params.iter().enumerate() {
        let local_idx = Local::new(arg_idx);
        builder.body.add_blockparam(entry, arg_ty);
        let value = builder.body.blocks[entry].params.last().unwrap().1;
        trace!("defining local {} to value {}", local_idx, value);
        builder.locals.declare(local_idx, arg_ty);
        builder.locals.set(local_idx, value);
    }

    let n_args = module.signatures[my_sig].params.len();
    for (offset, local_ty) in locals.values().enumerate() {
        let local_idx = Local::new(n_args + offset);
        builder.locals.declare(local_idx, *local_ty);
    }

    let ops = body.get_operators_reader()?;
    for item in ops.into_iter_with_offsets() {
        let (op, offset) = item?;
        let loc = debug_locs.get_loc(offset);
        if builder.reachable {
            builder.handle_op(op, loc)?;
        } else {
            builder.handle_op_unreachable(op)?;
        }
    }

    if builder.reachable {
        builder.handle_op(wasmparser::Operator::Return, SourceLoc::invalid())?;
    }

    for block in builder.body.blocks.iter() {
        log::trace!("checking if block is sealed: {}", block);
        debug_assert!(builder.locals.is_sealed(block));
    }
    for value in builder.body.values.values() {
        debug_assert!(!matches!(value, &ValueDef::Placeholder(_)));
    }

    trace!("Final function body:{:?}", ret);

    Ok(ret)
}

#[derive(Debug, Clone, Default)]
struct LocalTracker {
    /// Types of locals, as declared.
    types: FxHashMap<Local, Type>,
    /// The current block.
    cur_block: Block,
    /// In some block?
    in_block: bool,
    /// Is the given block sealed?
    block_sealed: FxHashSet<Block>,
    /// The local-to-value mapping at the start of a block.
    block_start: FxHashMap<Block, FxHashMap<Local, Value>>,
    /// The local-to-value mapping at the end of a block.
    block_end: FxHashMap<Block, FxHashMap<Local, Value>>,
    in_cur_block: FxHashMap<Local, Value>,
    incomplete_phis: FxHashMap<Block, Vec<(Local, Value)>>,
}

impl LocalTracker {
    pub fn declare(&mut self, local: Local, ty: Type) {
        let was_present = self.types.insert(local, ty).is_some();
        assert!(!was_present);
    }

    pub fn start_block(&mut self, block: Block) {
        log::trace!("start_block: block {}", block);
        assert!(!self.in_block);
        self.in_block = true;
        self.cur_block = block;
    }

    pub fn finish_block(&mut self, reachable: bool) {
        if !self.in_block {
            assert!(!reachable);
            return;
        }
        log::trace!("finish_block: block {}", self.cur_block);
        if reachable {
            let mapping = std::mem::take(&mut self.in_cur_block);
            log::trace!(" -> mapping: {:?}", mapping);
            let old_mapping = self.block_end.insert(self.cur_block, mapping);
            assert!(
                old_mapping.is_none(),
                "Mapping already present for {}: {:?}",
                self.cur_block,
                old_mapping
            );
        } else {
            self.in_cur_block.clear();
            self.block_end.insert(self.cur_block, FxHashMap::default());
        }
        self.in_block = false;
    }

    pub fn seal_block_preds(&mut self, block: Block, body: &mut FunctionBody) {
        log::trace!("seal_block_preds: block {}", block);
        let not_sealed = self.block_sealed.insert(block);
        assert!(not_sealed);
        for (local, phi_value) in self
            .incomplete_phis
            .remove(&block)
            .unwrap_or_else(|| vec![])
        {
            self.compute_blockparam(body, block, local, phi_value);
        }
    }

    fn is_sealed(&self, block: Block) -> bool {
        self.block_sealed.contains(&block)
    }

    pub fn set(&mut self, local: Local, value: Value) {
        log::trace!("set: local {} value {:?}", local, value);
        self.in_cur_block.insert(local, value);
    }

    fn get_in_block(&mut self, body: &mut FunctionBody, at_block: Block, local: Local) -> Value {
        log::trace!(
            "get_in_block: at_block {} local {} cur_block {}",
            at_block,
            local,
            self.cur_block
        );
        let ty = body.locals[local];

        if self.cur_block == at_block {
            if let Some(&value) = self.in_cur_block.get(&local) {
                log::trace!(" -> {:?}", value);
                return value;
            }
        }

        if self.is_sealed(at_block) {
            if let Some(end_mapping) = self.block_end.get(&at_block) {
                if let Some(&value) = end_mapping.get(&local) {
                    log::trace!(" -> from end_mapping: {:?}", value);
                    return value;
                }
            }

            if body.blocks[at_block].preds.is_empty() {
                let value = self.create_default_value(body, ty, at_block);
                log::trace!(" -> created default: {:?}", value);
                return value;
            }

            let placeholder = body.add_placeholder(ty);
            body.mark_value_as_local(placeholder, local);
            if at_block == self.cur_block {
                self.in_cur_block.insert(local, placeholder);
            } else {
                self.block_end
                    .get_mut(&at_block)
                    .unwrap()
                    .insert(local, placeholder);
            }
            log::trace!(" -> created placeholder: {:?}", placeholder);
            self.compute_blockparam(body, at_block, local, placeholder);
            placeholder
        } else {
            if let Some(end_mapping) = self.block_end.get(&at_block) {
                if let Some(&value) = end_mapping.get(&local) {
                    log::trace!(" -> from end_mapping: {:?}", value);
                    return value;
                }
            }

            let placeholder = body.add_placeholder(ty);
            body.mark_value_as_local(placeholder, local);
            if at_block == self.cur_block {
                self.in_cur_block.insert(local, placeholder);
            } else {
                self.block_end
                    .get_mut(&at_block)
                    .unwrap()
                    .insert(local, placeholder);
            }
            log::trace!(
                " -> created placeholder and added as incomplete phi: {:?}",
                placeholder
            );
            self.incomplete_phis
                .entry(at_block)
                .or_insert_with(|| vec![])
                .push((local, placeholder));

            placeholder
        }
    }

    pub fn get(&mut self, body: &mut FunctionBody, local: Local) -> Value {
        self.get_in_block(body, self.cur_block, local)
    }

    fn create_default_value(
        &mut self,
        body: &mut FunctionBody,
        ty: Type,
        at_block: Block,
    ) -> Value {
        let types = body.single_type_list(ty);
        let val = match ty {
            Type::I32 => body.add_value(ValueDef::Operator(
                Operator::I32Const { value: 0 },
                ListRef::default(),
                types,
            )),
            Type::I64 => body.add_value(ValueDef::Operator(
                Operator::I64Const { value: 0 },
                ListRef::default(),
                types,
            )),
            Type::F32 => body.add_value(ValueDef::Operator(
                Operator::F32Const { value: 0 },
                ListRef::default(),
                types,
            )),
            Type::F64 => body.add_value(ValueDef::Operator(
                Operator::F64Const { value: 0 },
                ListRef::default(),
                types,
            )),
            Type::V128 => body.add_value(ValueDef::Operator(
                Operator::V128Const { value: 0 },
                ListRef::default(),
                types,
            )),
            _ => todo!("unsupported type: {:?}", ty),
        };
        body.append_to_block(at_block, val);
        log::trace!(
            "created default value {} of type {} at block {}",
            val,
            ty,
            at_block
        );
        val
    }

    fn compute_blockparam(
        &mut self,
        body: &mut FunctionBody,
        block: Block,
        local: Local,
        value: Value,
    ) {
        log::trace!(
            "compute_blockparam: block {} local {} value {:?}",
            block,
            local,
            value
        );
        let mut results: Vec<Value> = vec![];
        let preds = body.blocks[block].preds.clone();
        for pred in preds {
            let pred_value = self.get_in_block(body, pred, local);
            log::trace!(
                "compute_blockparam: block {} local {} value {:?}: pred {} -> {:?}",
                block,
                local,
                value,
                pred,
                pred_value
            );
            results.push(pred_value);
        }

        let mut non_self = results.iter().filter(|&&v| v != value);
        let trivial_alias = match non_self.next() {
            None => None,
            Some(&first) if non_self.all(|&v| v == first) && body.resolve_alias(first) != value => {
                Some(first)
            }
            Some(_) => None,
        };

        if let Some(v) = trivial_alias {
            log::trace!(
                "compute_blockparam: block {} local {} value {:?}: alias to {:?}",
                block,
                local,
                value,
                v
            );
            body.set_alias(value, v);
        } else {
            log::trace!(
                "compute_blockparam: block {} local {} value {:?}: making blockparam",
                block,
                local,
                value,
            );
            body.replace_placeholder_with_blockparam(block, value);
            for (i, (&pred, result)) in body.blocks[block]
                .preds
                .clone()
                .iter()
                .zip(results.into_iter())
                .enumerate()
            {
                let index = body.blocks[block].pos_in_pred_succ[i];
                body.blocks[pred].terminator.update_target(index, |target| {
                    log::trace!(
                        "compute_blockparam: block {} local {} value {:?}: in pred {}, adding branch arg {:?}",
                        block,
                        local,
                        value,
                        pred,
                        result,
                    );
                    target.args.push(result);
                });
            }
        }
    }
}

#[derive(Debug)]
struct FunctionBodyBuilder<'a, 'b> {
    module: &'b Module<'a>,
    my_sig: Signature,
    body: &'b mut FunctionBody,
    locals: LocalTracker,
    cur_block: Block,
    reachable: bool,
    ctrl_stack: Vec<Frame>,
    op_stack: Vec<(Type, Value)>,
}

#[derive(Clone, Debug)]
enum Frame {
    Block {
        start_depth: usize,
        out: Block,
        params: Vec<Type>,
        results: Vec<Type>,
        out_reachable: bool,
    },
    Loop {
        start_depth: usize,
        header: Block,
        out: Block,
        params: Vec<Type>,
        results: Vec<Type>,
    },
    If {
        start_depth: usize,
        out: Block,
        el: Block,
        param_values: Vec<(Type, Value)>,
        params: Vec<Type>,
        results: Vec<Type>,
        head_reachable: bool,
        merge_reachable: bool,
    },
    Else {
        start_depth: usize,
        out: Block,
        params: Vec<Type>,
        results: Vec<Type>,
        merge_reachable: bool,
    },
}

impl Frame {
    fn start_depth(&self) -> usize {
        match self {
            Frame::Block { start_depth, .. }
            | Frame::Loop { start_depth, .. }
            | Frame::If { start_depth, .. }
            | Frame::Else { start_depth, .. } => *start_depth,
        }
    }

    fn br_args(&self) -> &[Type] {
        match self {
            Frame::Block { results, .. }
            | Frame::If { results, .. }
            | Frame::Else { results, .. } => &results[..],
            Frame::Loop { params, .. } => &params[..],
        }
    }

    fn br_target(&self) -> Block {
        match self {
            Frame::Block { out, .. } => *out,
            Frame::Loop { header, .. } => *header,
            Frame::If { out, .. } | Frame::Else { out, .. } => *out,
        }
    }

    fn out(&self) -> Block {
        match self {
            Frame::Block { out, .. }
            | Frame::Loop { out, .. }
            | Frame::If { out, .. }
            | Frame::Else { out, .. } => *out,
        }
    }

    fn params(&self) -> &[Type] {
        match self {
            Frame::Block { params, .. }
            | Frame::Loop { params, .. }
            | Frame::If { params, .. }
            | Frame::Else { params, .. } => &params[..],
        }
    }

    fn results(&self) -> &[Type] {
        match self {
            Frame::Block { results, .. }
            | Frame::Loop { results, .. }
            | Frame::If { results, .. }
            | Frame::Else { results, .. } => &results[..],
        }
    }

    fn set_reachable(&mut self) {
        match self {
            Frame::Block { out_reachable, .. } => *out_reachable = true,
            Frame::If {
                merge_reachable, ..
            }
            | Frame::Else {
                merge_reachable, ..
            } => *merge_reachable = true,
            _ => {}
        }
    }
}

impl<'a, 'b> FunctionBodyBuilder<'a, 'b> {
    fn new(module: &'b Module<'a>, my_sig: Signature, body: &'b mut FunctionBody) -> Self {
        body.blocks.push(BlockDef::default());
        let mut ret = Self {
            module,
            my_sig,
            body,
            ctrl_stack: vec![],
            op_stack: vec![],
            cur_block: Block::new(0),
            reachable: true,
            locals: LocalTracker::default(),
        };

        // Push initial implicit Block.
        let results = module.signatures[my_sig].returns.to_vec();
        let out = ret.body.add_block();
        ret.add_block_params(out, &results[..]);
        ret.ctrl_stack.push(Frame::Block {
            start_depth: 0,
            out,
            params: vec![],
            results,
            out_reachable: false,
        });
        ret
    }

    fn pop_n(&mut self, n: usize) -> Vec<Value> {
        assert!(self.reachable);
        let new_top = self.op_stack.len() - n;
        let ret = self.op_stack[new_top..]
            .iter()
            .map(|(_ty, value)| *value)
            .collect::<Vec<_>>();
        self.op_stack.truncate(new_top);
        ret
    }

    fn pop_1(&mut self) -> Value {
        assert!(self.reachable);
        self.op_stack.pop().unwrap().1
    }

    fn block_results(&mut self, tys: &[Type], start_depth: usize, at_block: Block) -> Vec<Value> {
        if self.op_stack.len() < start_depth + tys.len() {
            tys.iter()
                .map(|&ty| {
                    self.locals
                        .create_default_value(&mut self.body, ty, at_block)
                })
                .collect()
        } else {
            self.pop_n(tys.len())
        }
    }

    fn handle_op(&mut self, op: wasmparser::Operator<'a>, loc: SourceLoc) -> Result<()> {
        trace!("handle_op: {:?}", op);
        trace!("op_stack = {:?}", self.op_stack);
        trace!("ctrl_stack = {:?}", self.ctrl_stack);
        trace!("locals = {:?}", self.locals);

        debug_assert!(self.reachable);

        if self.handle_ctrl_op(op.clone())? {
            return Ok(());
        }

        match &op {
            wasmparser::Operator::Unreachable => {
                self.emit_unreachable();
            }

            wasmparser::Operator::LocalGet { local_index } => {
                let local_index = Local::from(*local_index);
                let ty = self.body.locals[local_index];
                let value = self.locals.get(&mut self.body, local_index);
                self.op_stack.push((ty, value));
            }

            wasmparser::Operator::LocalSet { local_index } => {
                let local_index = Local::from(*local_index);
                let (_, value) = self.op_stack.pop().unwrap();
                self.locals.set(local_index, value);
            }

            wasmparser::Operator::LocalTee { local_index } => {
                let local_index = Local::from(*local_index);
                let (_ty, value) = *self.op_stack.last().unwrap();
                self.locals.set(local_index, value);
            }

            wasmparser::Operator::Call { .. }
            | wasmparser::Operator::CallIndirect { .. }
            | wasmparser::Operator::Select
            | wasmparser::Operator::TypedSelect { .. }
            | wasmparser::Operator::GlobalGet { .. }
            | wasmparser::Operator::GlobalSet { .. }
            | wasmparser::Operator::I32Load { .. }
            | wasmparser::Operator::I64Load { .. }
            | wasmparser::Operator::F32Load { .. }
            | wasmparser::Operator::F64Load { .. }
            | wasmparser::Operator::I32Load8S { .. }
            | wasmparser::Operator::I32Load8U { .. }
            | wasmparser::Operator::I32Load16S { .. }
            | wasmparser::Operator::I32Load16U { .. }
            | wasmparser::Operator::I64Load8S { .. }
            | wasmparser::Operator::I64Load8U { .. }
            | wasmparser::Operator::I64Load16S { .. }
            | wasmparser::Operator::I64Load16U { .. }
            | wasmparser::Operator::I64Load32S { .. }
            | wasmparser::Operator::I64Load32U { .. }
            | wasmparser::Operator::I32Store { .. }
            | wasmparser::Operator::I64Store { .. }
            | wasmparser::Operator::F32Store { .. }
            | wasmparser::Operator::F64Store { .. }
            | wasmparser::Operator::I32Store8 { .. }
            | wasmparser::Operator::I32Store16 { .. }
            | wasmparser::Operator::I64Store8 { .. }
            | wasmparser::Operator::I64Store16 { .. }
            | wasmparser::Operator::I64Store32 { .. }
            | wasmparser::Operator::MemorySize { .. }
            | wasmparser::Operator::MemoryGrow { .. }
            | wasmparser::Operator::I32Const { .. }
            | wasmparser::Operator::I64Const { .. }
            | wasmparser::Operator::F32Const { .. }
            | wasmparser::Operator::F64Const { .. }
            | wasmparser::Operator::I32Eqz
            | wasmparser::Operator::I32Eq
            | wasmparser::Operator::I32Ne
            | wasmparser::Operator::I32LtS
            | wasmparser::Operator::I32LtU
            | wasmparser::Operator::I32GtS
            | wasmparser::Operator::I32GtU
            | wasmparser::Operator::I32LeS
            | wasmparser::Operator::I32LeU
            | wasmparser::Operator::I32GeS
            | wasmparser::Operator::I32GeU
            | wasmparser::Operator::I64Eqz
            | wasmparser::Operator::I64Eq
            | wasmparser::Operator::I64Ne
            | wasmparser::Operator::I64LtS
            | wasmparser::Operator::I64LtU
            | wasmparser::Operator::I64GtU
            | wasmparser::Operator::I64GtS
            | wasmparser::Operator::I64LeS
            | wasmparser::Operator::I64LeU
            | wasmparser::Operator::I64GeS
            | wasmparser::Operator::I64GeU
            | wasmparser::Operator::F32Eq
            | wasmparser::Operator::F32Ne
            | wasmparser::Operator::F32Lt
            | wasmparser::Operator::F32Gt
            | wasmparser::Operator::F32Le
            | wasmparser::Operator::F32Ge
            | wasmparser::Operator::F64Eq
            | wasmparser::Operator::F64Ne
            | wasmparser::Operator::F64Lt
            | wasmparser::Operator::F64Gt
            | wasmparser::Operator::F64Le
            | wasmparser::Operator::F64Ge
            | wasmparser::Operator::I32Clz
            | wasmparser::Operator::I32Ctz
            | wasmparser::Operator::I32Popcnt
            | wasmparser::Operator::I32Add
            | wasmparser::Operator::I32Sub
            | wasmparser::Operator::I32Mul
            | wasmparser::Operator::I32DivS
            | wasmparser::Operator::I32DivU
            | wasmparser::Operator::I32RemS
            | wasmparser::Operator::I32RemU
            | wasmparser::Operator::I32And
            | wasmparser::Operator::I32Or
            | wasmparser::Operator::I32Xor
            | wasmparser::Operator::I32Shl
            | wasmparser::Operator::I32ShrS
            | wasmparser::Operator::I32ShrU
            | wasmparser::Operator::I32Rotl
            | wasmparser::Operator::I32Rotr
            | wasmparser::Operator::I64Clz
            | wasmparser::Operator::I64Ctz
            | wasmparser::Operator::I64Popcnt
            | wasmparser::Operator::I64Add
            | wasmparser::Operator::I64Sub
            | wasmparser::Operator::I64Mul
            | wasmparser::Operator::I64DivS
            | wasmparser::Operator::I64DivU
            | wasmparser::Operator::I64RemS
            | wasmparser::Operator::I64RemU
            | wasmparser::Operator::I64And
            | wasmparser::Operator::I64Or
            | wasmparser::Operator::I64Xor
            | wasmparser::Operator::I64Shl
            | wasmparser::Operator::I64ShrS
            | wasmparser::Operator::I64ShrU
            | wasmparser::Operator::I64Rotl
            | wasmparser::Operator::I64Rotr
            | wasmparser::Operator::F32Abs
            | wasmparser::Operator::F32Neg
            | wasmparser::Operator::F32Ceil
            | wasmparser::Operator::F32Floor
            | wasmparser::Operator::F32Trunc
            | wasmparser::Operator::F32Nearest
            | wasmparser::Operator::F32Sqrt
            | wasmparser::Operator::F32Add
            | wasmparser::Operator::F32Sub
            | wasmparser::Operator::F32Mul
            | wasmparser::Operator::F32Div
            | wasmparser::Operator::F32Min
            | wasmparser::Operator::F32Max
            | wasmparser::Operator::F32Copysign
            | wasmparser::Operator::F64Abs
            | wasmparser::Operator::F64Neg
            | wasmparser::Operator::F64Ceil
            | wasmparser::Operator::F64Floor
            | wasmparser::Operator::F64Trunc
            | wasmparser::Operator::F64Nearest
            | wasmparser::Operator::F64Sqrt
            | wasmparser::Operator::F64Add
            | wasmparser::Operator::F64Sub
            | wasmparser::Operator::F64Mul
            | wasmparser::Operator::F64Div
            | wasmparser::Operator::F64Min
            | wasmparser::Operator::F64Max
            | wasmparser::Operator::F64Copysign
            | wasmparser::Operator::I32WrapI64
            | wasmparser::Operator::I32TruncF32S
            | wasmparser::Operator::I32TruncF32U
            | wasmparser::Operator::I32TruncF64S
            | wasmparser::Operator::I32TruncF64U
            | wasmparser::Operator::I64ExtendI32S
            | wasmparser::Operator::I64ExtendI32U
            | wasmparser::Operator::I64TruncF32S
            | wasmparser::Operator::I64TruncF32U
            | wasmparser::Operator::I64TruncF64S
            | wasmparser::Operator::I64TruncF64U
            | wasmparser::Operator::F32ConvertI32S
            | wasmparser::Operator::F32ConvertI32U
            | wasmparser::Operator::F32ConvertI64S
            | wasmparser::Operator::F32ConvertI64U
            | wasmparser::Operator::F32DemoteF64
            | wasmparser::Operator::F64ConvertI32S
            | wasmparser::Operator::F64ConvertI32U
            | wasmparser::Operator::F64ConvertI64S
            | wasmparser::Operator::F64ConvertI64U
            | wasmparser::Operator::F64PromoteF32
            | wasmparser::Operator::I32Extend8S
            | wasmparser::Operator::I32Extend16S
            | wasmparser::Operator::I64Extend8S
            | wasmparser::Operator::I64Extend16S
            | wasmparser::Operator::I64Extend32S
            | wasmparser::Operator::I32TruncSatF32S
            | wasmparser::Operator::I32TruncSatF32U
            | wasmparser::Operator::I32TruncSatF64S
            | wasmparser::Operator::I32TruncSatF64U
            | wasmparser::Operator::I64TruncSatF32S
            | wasmparser::Operator::I64TruncSatF32U
            | wasmparser::Operator::I64TruncSatF64S
            | wasmparser::Operator::I64TruncSatF64U
            | wasmparser::Operator::F32ReinterpretI32
            | wasmparser::Operator::F64ReinterpretI64
            | wasmparser::Operator::I32ReinterpretF32
            | wasmparser::Operator::I64ReinterpretF64
            | wasmparser::Operator::TableGet { .. }
            | wasmparser::Operator::TableSet { .. }
            | wasmparser::Operator::TableGrow { .. }
            | wasmparser::Operator::TableSize { .. }
            | wasmparser::Operator::MemoryCopy { .. }
            | wasmparser::Operator::MemoryFill { .. }
            | wasmparser::Operator::V128Load { .. }
            | wasmparser::Operator::V128Load8x8S { .. }
            | wasmparser::Operator::V128Load8x8U { .. }
            | wasmparser::Operator::V128Load16x4S { .. }
            | wasmparser::Operator::V128Load16x4U { .. }
            | wasmparser::Operator::V128Load32x2S { .. }
            | wasmparser::Operator::V128Load32x2U { .. }
            | wasmparser::Operator::V128Load8Splat { .. }
            | wasmparser::Operator::V128Load16Splat { .. }
            | wasmparser::Operator::V128Load32Splat { .. }
            | wasmparser::Operator::V128Load64Splat { .. }
            | wasmparser::Operator::V128Load32Zero { .. }
            | wasmparser::Operator::V128Load64Zero { .. }
            | wasmparser::Operator::V128Store { .. }
            | wasmparser::Operator::V128Load8Lane { .. }
            | wasmparser::Operator::V128Load16Lane { .. }
            | wasmparser::Operator::V128Load32Lane { .. }
            | wasmparser::Operator::V128Load64Lane { .. }
            | wasmparser::Operator::V128Store8Lane { .. }
            | wasmparser::Operator::V128Store16Lane { .. }
            | wasmparser::Operator::V128Store32Lane { .. }
            | wasmparser::Operator::V128Store64Lane { .. }
            | wasmparser::Operator::V128Const { .. }
            | wasmparser::Operator::I8x16Shuffle { .. }
            | wasmparser::Operator::I8x16ExtractLaneS { .. }
            | wasmparser::Operator::I8x16ExtractLaneU { .. }
            | wasmparser::Operator::I8x16ReplaceLane { .. }
            | wasmparser::Operator::I16x8ExtractLaneS { .. }
            | wasmparser::Operator::I16x8ExtractLaneU { .. }
            | wasmparser::Operator::I16x8ReplaceLane { .. }
            | wasmparser::Operator::I32x4ExtractLane { .. }
            | wasmparser::Operator::I32x4ReplaceLane { .. }
            | wasmparser::Operator::I64x2ExtractLane { .. }
            | wasmparser::Operator::I64x2ReplaceLane { .. }
            | wasmparser::Operator::F32x4ExtractLane { .. }
            | wasmparser::Operator::F32x4ReplaceLane { .. }
            | wasmparser::Operator::F64x2ExtractLane { .. }
            | wasmparser::Operator::F64x2ReplaceLane { .. }
            | wasmparser::Operator::I8x16Swizzle
            | wasmparser::Operator::I8x16Splat
            | wasmparser::Operator::I16x8Splat
            | wasmparser::Operator::I32x4Splat
            | wasmparser::Operator::I64x2Splat
            | wasmparser::Operator::F32x4Splat
            | wasmparser::Operator::F64x2Splat
            | wasmparser::Operator::I8x16Eq
            | wasmparser::Operator::I8x16Ne
            | wasmparser::Operator::I8x16LtS
            | wasmparser::Operator::I8x16LtU
            | wasmparser::Operator::I8x16GtS
            | wasmparser::Operator::I8x16GtU
            | wasmparser::Operator::I8x16LeS
            | wasmparser::Operator::I8x16LeU
            | wasmparser::Operator::I8x16GeS
            | wasmparser::Operator::I8x16GeU
            | wasmparser::Operator::I16x8Eq
            | wasmparser::Operator::I16x8Ne
            | wasmparser::Operator::I16x8LtS
            | wasmparser::Operator::I16x8LtU
            | wasmparser::Operator::I16x8GtS
            | wasmparser::Operator::I16x8GtU
            | wasmparser::Operator::I16x8LeS
            | wasmparser::Operator::I16x8LeU
            | wasmparser::Operator::I16x8GeS
            | wasmparser::Operator::I16x8GeU
            | wasmparser::Operator::I32x4Eq
            | wasmparser::Operator::I32x4Ne
            | wasmparser::Operator::I32x4LtS
            | wasmparser::Operator::I32x4LtU
            | wasmparser::Operator::I32x4GtS
            | wasmparser::Operator::I32x4GtU
            | wasmparser::Operator::I32x4LeS
            | wasmparser::Operator::I32x4LeU
            | wasmparser::Operator::I32x4GeS
            | wasmparser::Operator::I32x4GeU
            | wasmparser::Operator::I64x2Eq
            | wasmparser::Operator::I64x2Ne
            | wasmparser::Operator::I64x2LtS
            | wasmparser::Operator::I64x2GtS
            | wasmparser::Operator::I64x2LeS
            | wasmparser::Operator::I64x2GeS
            | wasmparser::Operator::F32x4Eq
            | wasmparser::Operator::F32x4Ne
            | wasmparser::Operator::F32x4Lt
            | wasmparser::Operator::F32x4Gt
            | wasmparser::Operator::F32x4Le
            | wasmparser::Operator::F32x4Ge
            | wasmparser::Operator::F64x2Eq
            | wasmparser::Operator::F64x2Ne
            | wasmparser::Operator::F64x2Lt
            | wasmparser::Operator::F64x2Gt
            | wasmparser::Operator::F64x2Le
            | wasmparser::Operator::F64x2Ge
            | wasmparser::Operator::V128Not
            | wasmparser::Operator::V128And
            | wasmparser::Operator::V128AndNot
            | wasmparser::Operator::V128Or
            | wasmparser::Operator::V128Xor
            | wasmparser::Operator::V128Bitselect
            | wasmparser::Operator::V128AnyTrue
            | wasmparser::Operator::I8x16Abs
            | wasmparser::Operator::I8x16Neg
            | wasmparser::Operator::I8x16Popcnt
            | wasmparser::Operator::I8x16AllTrue
            | wasmparser::Operator::I8x16Bitmask
            | wasmparser::Operator::I8x16NarrowI16x8S
            | wasmparser::Operator::I8x16NarrowI16x8U
            | wasmparser::Operator::I8x16Shl
            | wasmparser::Operator::I8x16ShrS
            | wasmparser::Operator::I8x16ShrU
            | wasmparser::Operator::I8x16Add
            | wasmparser::Operator::I8x16AddSatS
            | wasmparser::Operator::I8x16AddSatU
            | wasmparser::Operator::I8x16Sub
            | wasmparser::Operator::I8x16SubSatS
            | wasmparser::Operator::I8x16SubSatU
            | wasmparser::Operator::I8x16MinS
            | wasmparser::Operator::I8x16MinU
            | wasmparser::Operator::I8x16MaxS
            | wasmparser::Operator::I8x16MaxU
            | wasmparser::Operator::I8x16AvgrU
            | wasmparser::Operator::I16x8ExtAddPairwiseI8x16S
            | wasmparser::Operator::I16x8ExtAddPairwiseI8x16U
            | wasmparser::Operator::I16x8Abs
            | wasmparser::Operator::I16x8Neg
            | wasmparser::Operator::I16x8Q15MulrSatS
            | wasmparser::Operator::I16x8AllTrue
            | wasmparser::Operator::I16x8Bitmask
            | wasmparser::Operator::I16x8NarrowI32x4S
            | wasmparser::Operator::I16x8NarrowI32x4U
            | wasmparser::Operator::I16x8ExtendLowI8x16S
            | wasmparser::Operator::I16x8ExtendHighI8x16S
            | wasmparser::Operator::I16x8ExtendLowI8x16U
            | wasmparser::Operator::I16x8ExtendHighI8x16U
            | wasmparser::Operator::I16x8Shl
            | wasmparser::Operator::I16x8ShrS
            | wasmparser::Operator::I16x8ShrU
            | wasmparser::Operator::I16x8Add
            | wasmparser::Operator::I16x8AddSatS
            | wasmparser::Operator::I16x8AddSatU
            | wasmparser::Operator::I16x8Sub
            | wasmparser::Operator::I16x8SubSatS
            | wasmparser::Operator::I16x8SubSatU
            | wasmparser::Operator::I16x8Mul
            | wasmparser::Operator::I16x8MinS
            | wasmparser::Operator::I16x8MinU
            | wasmparser::Operator::I16x8MaxS
            | wasmparser::Operator::I16x8MaxU
            | wasmparser::Operator::I16x8AvgrU
            | wasmparser::Operator::I16x8ExtMulLowI8x16S
            | wasmparser::Operator::I16x8ExtMulHighI8x16S
            | wasmparser::Operator::I16x8ExtMulLowI8x16U
            | wasmparser::Operator::I16x8ExtMulHighI8x16U
            | wasmparser::Operator::I32x4ExtAddPairwiseI16x8S
            | wasmparser::Operator::I32x4ExtAddPairwiseI16x8U
            | wasmparser::Operator::I32x4Abs
            | wasmparser::Operator::I32x4Neg
            | wasmparser::Operator::I32x4AllTrue
            | wasmparser::Operator::I32x4Bitmask
            | wasmparser::Operator::I32x4ExtendLowI16x8S
            | wasmparser::Operator::I32x4ExtendHighI16x8S
            | wasmparser::Operator::I32x4ExtendLowI16x8U
            | wasmparser::Operator::I32x4ExtendHighI16x8U
            | wasmparser::Operator::I32x4Shl
            | wasmparser::Operator::I32x4ShrS
            | wasmparser::Operator::I32x4ShrU
            | wasmparser::Operator::I32x4Add
            | wasmparser::Operator::I32x4Sub
            | wasmparser::Operator::I32x4Mul
            | wasmparser::Operator::I32x4MinS
            | wasmparser::Operator::I32x4MinU
            | wasmparser::Operator::I32x4MaxS
            | wasmparser::Operator::I32x4MaxU
            | wasmparser::Operator::I32x4DotI16x8S
            | wasmparser::Operator::I32x4ExtMulLowI16x8S
            | wasmparser::Operator::I32x4ExtMulHighI16x8S
            | wasmparser::Operator::I32x4ExtMulLowI16x8U
            | wasmparser::Operator::I32x4ExtMulHighI16x8U
            | wasmparser::Operator::I64x2Abs
            | wasmparser::Operator::I64x2Neg
            | wasmparser::Operator::I64x2AllTrue
            | wasmparser::Operator::I64x2Bitmask
            | wasmparser::Operator::I64x2ExtendLowI32x4S
            | wasmparser::Operator::I64x2ExtendHighI32x4S
            | wasmparser::Operator::I64x2ExtendLowI32x4U
            | wasmparser::Operator::I64x2ExtendHighI32x4U
            | wasmparser::Operator::I64x2Shl
            | wasmparser::Operator::I64x2ShrS
            | wasmparser::Operator::I64x2ShrU
            | wasmparser::Operator::I64x2Add
            | wasmparser::Operator::I64x2Sub
            | wasmparser::Operator::I64x2Mul
            | wasmparser::Operator::I64x2ExtMulLowI32x4S
            | wasmparser::Operator::I64x2ExtMulHighI32x4S
            | wasmparser::Operator::I64x2ExtMulLowI32x4U
            | wasmparser::Operator::I64x2ExtMulHighI32x4U
            | wasmparser::Operator::F32x4Ceil
            | wasmparser::Operator::F32x4Floor
            | wasmparser::Operator::F32x4Trunc
            | wasmparser::Operator::F32x4Nearest
            | wasmparser::Operator::F32x4Abs
            | wasmparser::Operator::F32x4Neg
            | wasmparser::Operator::F32x4Sqrt
            | wasmparser::Operator::F32x4Add
            | wasmparser::Operator::F32x4Sub
            | wasmparser::Operator::F32x4Mul
            | wasmparser::Operator::F32x4Div
            | wasmparser::Operator::F32x4Min
            | wasmparser::Operator::F32x4Max
            | wasmparser::Operator::F32x4PMin
            | wasmparser::Operator::F32x4PMax
            | wasmparser::Operator::F64x2Ceil
            | wasmparser::Operator::F64x2Floor
            | wasmparser::Operator::F64x2Trunc
            | wasmparser::Operator::F64x2Nearest
            | wasmparser::Operator::F64x2Abs
            | wasmparser::Operator::F64x2Neg
            | wasmparser::Operator::F64x2Sqrt
            | wasmparser::Operator::F64x2Add
            | wasmparser::Operator::F64x2Sub
            | wasmparser::Operator::F64x2Mul
            | wasmparser::Operator::F64x2Div
            | wasmparser::Operator::F64x2Min
            | wasmparser::Operator::F64x2Max
            | wasmparser::Operator::F64x2PMin
            | wasmparser::Operator::F64x2PMax
            | wasmparser::Operator::I32x4TruncSatF32x4S
            | wasmparser::Operator::I32x4TruncSatF32x4U
            | wasmparser::Operator::F32x4ConvertI32x4S
            | wasmparser::Operator::F32x4ConvertI32x4U
            | wasmparser::Operator::I32x4TruncSatF64x2SZero
            | wasmparser::Operator::I32x4TruncSatF64x2UZero
            | wasmparser::Operator::F64x2ConvertLowI32x4S
            | wasmparser::Operator::F64x2ConvertLowI32x4U
            | wasmparser::Operator::F32x4DemoteF64x2Zero
            | wasmparser::Operator::F64x2PromoteLowF32x4
            | wasmparser::Operator::CallRef { .. }
            | wasmparser::Operator::RefIsNull
            | wasmparser::Operator::RefFunc { .. } => {
                self.emit(Operator::try_from(&op).unwrap(), loc)?
            }

            wasmparser::Operator::Nop => {}

            wasmparser::Operator::Drop => {
                let _ = self.pop_1();
            }

            wasmparser::Operator::Br { relative_depth }
            | wasmparser::Operator::BrIf { relative_depth } => {
                let cond = match &op {
                    wasmparser::Operator::Br { .. } => None,
                    wasmparser::Operator::BrIf { .. } => Some(self.pop_1()),
                    _ => unreachable!(),
                };
                // Get the frame we're branching to.
                let frame = self.relative_frame(*relative_depth);
                frame.set_reachable();
                let frame = frame.clone();
                log::trace!("Br/BrIf: dest frame {:?}", frame);
                // Finally, generate the branch itself.
                match cond {
                    None => {
                        // Get the args off the stack unconditionally.
                        let args = self.pop_n(frame.br_args().len());
                        self.emit_branch(frame.br_target(), &args[..]);
                        self.locals.finish_block(self.reachable);
                        self.reachable = false;
                    }
                    Some(cond) => {
                        let cont = self.body.add_block();
                        // Get the args off the stack but leave for the fallthrough.
                        let args = self.op_stack[self.op_stack.len() - frame.br_args().len()..]
                            .iter()
                            .map(|(_ty, value)| *value)
                            .collect::<Vec<_>>();
                        self.emit_cond_branch(cond, frame.br_target(), &args[..], cont, &[]);
                        self.locals.seal_block_preds(cont, &mut self.body);
                        self.cur_block = cont;
                        self.locals.finish_block(self.reachable);
                        self.locals.start_block(cont);
                    }
                }
            }

            wasmparser::Operator::BrTable { targets } => {
                // Get the selector index.
                let index = self.pop_1();
                // Get the signature of the default frame; this tells
                // us the signature of all frames (since wasmparser
                // validates the input for us). Pop that many args.
                let default_frame = self.relative_frame(targets.default());
                default_frame.set_reachable();
                let default_term_target = default_frame.br_target();
                let arg_len = default_frame.br_args().len();
                let args = self.pop_n(arg_len);
                // Generate a branch terminator with the same args for
                // every branch target.
                let mut term_targets = vec![];
                for target in targets.targets() {
                    let target = target?;
                    let frame = self.relative_frame(target);
                    frame.set_reachable();
                    assert_eq!(frame.br_args().len(), args.len());
                    let block = frame.br_target();
                    term_targets.push(block);
                }
                self.emit_br_table(index, default_term_target, &term_targets[..], &args[..]);
                self.locals.finish_block(self.reachable);
                self.reachable = false;
            }

            wasmparser::Operator::Return => {
                let retvals = self.pop_n(self.module.signatures[self.my_sig].returns.len());
                self.emit_ret(&retvals[..]);
                self.reachable = false;
            }
            wasmparser::Operator::ReturnCall { function_index } => {
                let sig = self.module.funcs[Func::new(*function_index as usize)].sig();
                let retvals = self.pop_n(self.module.signatures[sig].params.len());
                self.emit_term(Terminator::ReturnCall {
                    func: Func::new(*function_index as usize),
                    args: retvals,
                });
                self.reachable = false;
            }
            wasmparser::Operator::ReturnCallIndirect {
                type_index,
                table_index,
            } => {
                let retvals = self.pop_n(
                    self.module.signatures[Signature::new(*type_index as usize)]
                        .params
                        .len(),
                );
                self.emit_term(Terminator::ReturnCallIndirect {
                    sig: Signature::new(*type_index as usize),
                    table: Table::new(*table_index as usize),
                    args: retvals,
                });
                self.reachable = false;
            }
            wasmparser::Operator::ReturnCallRef {
                type_index,
                // table_index,
            } => {
                let retvals = self.pop_n(
                    self.module.signatures[Signature::new(*type_index as usize)]
                        .params
                        .len(),
                );
                self.emit_term(Terminator::ReturnCallRef {
                    sig: Signature::new(*type_index as usize),
                    // table: Table::new(*table_index as usize),
                    args: retvals,
                });
                self.reachable = false;
            }
            _ => bail!(FrontendError::UnsupportedFeature(format!(
                "Unsupported operator: {:?}",
                op
            ))),
        }

        Ok(())
    }

    fn handle_op_unreachable(&mut self, op: wasmparser::Operator<'a>) -> Result<()> {
        trace!("handle_op_unreachable: {:?}", op);
        trace!("op_stack = {:?}", self.op_stack);
        trace!("ctrl_stack = {:?}", self.ctrl_stack);

        debug_assert!(!self.reachable);

        self.handle_ctrl_op(op)?;

        Ok(())
    }

    fn handle_ctrl_op(&mut self, op: wasmparser::Operator<'a>) -> Result<bool> {
        log::trace!(
            "handle_ctrl_op: op {:?} reachable {} cur_block {}",
            op,
            self.reachable,
            self.cur_block
        );
        log::trace!("ctrl stack: {:?}", self.ctrl_stack);
        match &op {
            wasmparser::Operator::End => {
                let frame = self.ctrl_stack.pop();
                match &frame {
                    None => {
                        if self.reachable {
                            let retvals =
                                self.pop_n(self.module.signatures[self.my_sig].returns.len());
                            self.emit_ret(&retvals[..]);
                        } else {
                            self.emit_unreachable();
                        }
                    }
                    Some(Frame::Block {
                        start_depth,
                        out,
                        ref results,
                        ..
                    })
                    | Some(Frame::Loop {
                        start_depth,
                        out,
                        ref results,
                        ..
                    }) => {
                        // Generate a branch to the out-block with
                        // blockparams for the results.
                        let was_reachable = self.reachable;
                        if self.reachable {
                            let result_values =
                                self.block_results(&results[..], *start_depth, self.cur_block);
                            self.emit_branch(*out, &result_values[..]);
                        }
                        self.op_stack.truncate(*start_depth);
                        // Seal the out-block: no more edges will be
                        // added to it. Also, if we're ending a loop,
                        // seal thea header: no more back-edges will
                        // be added to it.
                        self.locals.seal_block_preds(*out, &mut self.body);
                        if let Some(Frame::Loop { header, .. }) = &frame {
                            self.locals.seal_block_preds(*header, &mut self.body);
                        }
                        // Set `cur_block` only if currently set (otherwise, unreachable!)
                        self.cur_block = *out;
                        self.locals.finish_block(self.reachable);
                        self.locals.start_block(*out);
                        self.reachable = was_reachable
                            || match &frame {
                                Some(Frame::Block { out_reachable, .. }) => *out_reachable,
                                _ => false,
                            };
                        self.push_block_params(results.len());
                    }
                    Some(Frame::If {
                        start_depth,
                        out,
                        el,
                        ref param_values,
                        ref results,
                        head_reachable,
                        merge_reachable,
                        ..
                    }) => {
                        // Generate a branch to the out-block with
                        // blockparams for the results.
                        let was_reachable = self.reachable;
                        if self.reachable {
                            let result_values =
                                self.block_results(&results[..], *start_depth, self.cur_block);
                            self.emit_branch(*out, &result_values[..]);
                        }
                        assert!(self.op_stack.len() >= *start_depth);
                        self.op_stack.truncate(*start_depth);
                        if *head_reachable {
                            // No `else`, so we need to generate a trivial
                            // branch in the else-block. If the if-block-type
                            // has results, they must be exactly the params.
                            let else_result_values = param_values;
                            assert_eq!(else_result_values.len(), results.len());
                            let else_result_values = else_result_values
                                .iter()
                                .map(|(_ty, value)| *value)
                                .collect::<Vec<_>>();
                            self.locals.finish_block(self.reachable);
                            self.locals.start_block(*el);
                            self.cur_block = *el;
                            self.reachable = *head_reachable;
                            self.emit_branch(*out, &else_result_values[..]);
                            assert_eq!(self.op_stack.len(), *start_depth);
                        }
                        self.cur_block = *out;
                        let else_reachable = self.reachable;
                        self.reachable = *head_reachable || was_reachable || *merge_reachable;
                        self.locals.seal_block_preds(*out, &mut self.body);
                        self.locals.finish_block(else_reachable);
                        self.locals.start_block(*out);
                        self.push_block_params(results.len());
                    }
                    Some(Frame::Else {
                        out,
                        ref results,
                        start_depth,
                        merge_reachable,
                        ..
                    }) => {
                        // Generate a branch to the out-block with
                        // blockparams for the results.
                        let was_reachable = self.reachable;
                        if self.reachable {
                            let result_values =
                                self.block_results(&results[..], *start_depth, self.cur_block);
                            self.emit_branch(*out, &result_values[..]);
                        }
                        self.op_stack.truncate(*start_depth);
                        self.cur_block = *out;
                        self.reachable = *merge_reachable || self.reachable;
                        self.locals.seal_block_preds(*out, &mut self.body);
                        self.locals.finish_block(was_reachable);
                        self.locals.start_block(*out);
                        self.push_block_params(results.len());
                    }
                }
            }

            wasmparser::Operator::Block { blockty } => {
                let (params, results) = self.block_params_and_results(*blockty);
                let out = self.body.add_block();
                self.add_block_params(out, &results[..]);
                let start_depth = if self.reachable {
                    self.op_stack.len() - params.len()
                } else {
                    self.op_stack.len()
                };
                self.ctrl_stack.push(Frame::Block {
                    start_depth,
                    out,
                    params,
                    results,
                    out_reachable: false,
                });
            }

            wasmparser::Operator::Loop { blockty } => {
                let (params, results) = self.block_params_and_results(*blockty);
                let header = self.body.add_block();
                self.add_block_params(header, &params[..]);
                let initial_args = if self.reachable {
                    self.pop_n(params.len())
                } else {
                    vec![Value::invalid(); params.len()]
                };
                let start_depth = self.op_stack.len();
                self.emit_branch(header, &initial_args[..]);
                self.cur_block = header;
                self.locals.finish_block(self.reachable);
                self.locals.start_block(header);
                self.push_block_params(params.len());
                let out = self.body.add_block();
                self.add_block_params(out, &results[..]);
                self.ctrl_stack.push(Frame::Loop {
                    start_depth,
                    header,
                    out,
                    params,
                    results,
                });
            }

            wasmparser::Operator::If { blockty } => {
                let (params, results) = self.block_params_and_results(*blockty);
                let if_true = self.body.add_block();
                let if_false = self.body.add_block();
                let join = self.body.add_block();
                self.add_block_params(join, &results[..]);
                let (cond, param_values) = if self.reachable {
                    let cond = self.pop_1();
                    let param_values = self.op_stack[self.op_stack.len() - params.len()..].to_vec();
                    (cond, param_values)
                } else {
                    (
                        Value::invalid(),
                        params.iter().map(|&ty| (ty, Value::invalid())).collect(),
                    )
                };
                let start_depth = if self.reachable {
                    self.op_stack.len() - params.len()
                } else {
                    self.op_stack.len()
                };
                self.ctrl_stack.push(Frame::If {
                    start_depth,
                    out: join,
                    el: if_false,
                    param_values,
                    params,
                    results,
                    head_reachable: self.reachable,
                    merge_reachable: false,
                });
                self.emit_cond_branch(cond, if_true, &[], if_false, &[]);
                self.locals.seal_block_preds(if_true, &mut self.body);
                self.locals.seal_block_preds(if_false, &mut self.body);
                self.cur_block = if_true;
                self.locals.finish_block(self.reachable);
                self.locals.start_block(if_true);
            }

            wasmparser::Operator::Else => {
                if let Frame::If {
                    start_depth,
                    out,
                    el,
                    param_values,
                    params,
                    results,
                    head_reachable,
                    merge_reachable,
                } = self.ctrl_stack.pop().unwrap()
                {
                    if self.reachable {
                        let if_results =
                            self.block_results(&results[..], start_depth, self.cur_block);
                        self.emit_branch(out, &if_results[..]);
                    }
                    self.op_stack.truncate(start_depth);
                    self.op_stack.extend(param_values);
                    self.ctrl_stack.push(Frame::Else {
                        start_depth,
                        out,
                        params,
                        results,
                        merge_reachable: merge_reachable || self.reachable,
                    });
                    self.cur_block = el;
                    self.locals.finish_block(self.reachable);
                    self.locals.start_block(el);
                    self.reachable = head_reachable;
                } else {
                    bail!(FrontendError::Internal(format!(
                        "Else without If on top of frame stack"
                    )));
                }
            }

            _ => return Ok(false),
        }

        Ok(true)
    }

    fn add_block_params(&mut self, block: Block, tys: &[Type]) {
        log::trace!("add_block_params: block {} tys {:?}", block, tys);
        for &ty in tys {
            self.body.add_blockparam(block, ty);
        }
    }

    fn block_params_and_results(&self, ty: BlockType) -> (Vec<Type>, Vec<Type>) {
        match ty {
            BlockType::Empty => (vec![], vec![]),
            BlockType::Type(ret_ty) => (vec![], vec![ret_ty.into()]),
            BlockType::FuncType(sig_idx) => {
                let sig = &self.module.signatures[Signature::from(sig_idx)];
                (
                    Vec::from(sig.params.clone()),
                    Vec::from(sig.returns.clone()),
                )
            }
        }
    }

    fn relative_frame(&mut self, relative_depth: u32) -> &mut Frame {
        let index = self.ctrl_stack.len() - 1 - relative_depth as usize;
        &mut self.ctrl_stack[index]
    }

    fn emit_branch(&mut self, target: Block, args: &[Value]) {
        log::trace!(
            "emit_branch: cur_block {:?} target {} args {:?}",
            self.cur_block,
            target,
            args
        );
        if self.reachable {
            let args = args.to_vec();
            let target = BlockTarget {
                block: target,
                args,
            };
            self.body
                .set_terminator(self.cur_block, Terminator::Br { target });
        }
    }

    fn emit_cond_branch(
        &mut self,
        cond: Value,
        if_true: Block,
        if_true_args: &[Value],
        if_false: Block,
        if_false_args: &[Value],
    ) {
        log::trace!(
            "emit_cond_branch: cur_block {:?} if_true {} args {:?} if_false {} args {:?}",
            self.cur_block,
            if_true,
            if_true_args,
            if_false,
            if_false_args
        );
        if self.reachable {
            let if_true_args = if_true_args.to_vec();
            let if_false_args = if_false_args.to_vec();
            self.body.set_terminator(
                self.cur_block,
                Terminator::CondBr {
                    cond,
                    if_true: BlockTarget {
                        block: if_true,
                        args: if_true_args,
                    },
                    if_false: BlockTarget {
                        block: if_false,
                        args: if_false_args,
                    },
                },
            );
        }
    }

    fn emit_br_table(
        &mut self,
        index: Value,
        default_target: Block,
        indexed_targets: &[Block],
        args: &[Value],
    ) {
        log::trace!(
            "emit_br_table: cur_block {:?} index {:?} default {} indexed {:?} args {:?}",
            self.cur_block,
            index,
            default_target,
            indexed_targets,
            args,
        );
        if self.reachable {
            let args = args.to_vec();
            let targets = indexed_targets
                .iter()
                .map(|&block| {
                    let args = args.clone();
                    BlockTarget { block, args }
                })
                .collect();

            let default_args = args;
            let default = BlockTarget {
                block: default_target,
                args: default_args,
            };

            self.body.set_terminator(
                self.cur_block,
                Terminator::Select {
                    value: index,
                    targets,
                    default,
                },
            );
        }
    }

    fn emit_ret(&mut self, values: &[Value]) {
        log::trace!(
            "emit_ret: cur_block {} reachable {} values {:?}",
            self.cur_block,
            self.reachable,
            values
        );
        if self.reachable {
            let values = values.to_vec();
            self.body
                .set_terminator(self.cur_block, Terminator::Return { values });
            self.reachable = false;
        }
    }

    fn emit_term(&mut self, t: Terminator) {
        log::trace!(
            "emit_term: cur_block {} reachable {} terminator {:?}",
            self.cur_block,
            self.reachable,
            t
        );
        if self.reachable {
            self.body.set_terminator(self.cur_block, t);
            self.reachable = false;
        }
    }

    fn emit_unreachable(&mut self) {
        log::trace!(
            "emit_unreachable: cur_block {} reachable {}",
            self.cur_block,
            self.reachable
        );
        if self.reachable {
            self.body
                .set_terminator(self.cur_block, Terminator::Unreachable);
            self.reachable = false;
        }
    }

    fn push_block_params(&mut self, num_params: usize) {
        log::trace!(
            "push_block_params: cur_block {:?}, {} params",
            self.cur_block,
            num_params
        );
        for i in 0..num_params {
            let (ty, value) = self.body.blocks[self.cur_block].params[i];
            log::trace!(" -> push {:?} ty {:?}", value, ty);
            self.op_stack.push((ty, value));
        }
    }

    fn emit(&mut self, op: Operator, loc: SourceLoc) -> Result<()> {
        let inputs = op_inputs(self.module, Some(&self.op_stack[..]), &op)?;
        let outputs = op_outputs(self.module, Some(&self.op_stack[..]), &op)?;

        log::trace!(
            "emit into block {:?}: op {:?} inputs {:?}",
            self.cur_block,
            op,
            inputs
        );

        let n_outputs = outputs.len();

        let input_operands = self.body.arg_pool.allocate(inputs.len(), Value::invalid());
        let args = &mut self.body.arg_pool[input_operands];
        for (i, &input) in inputs.into_iter().enumerate().rev() {
            let (stack_top_ty, stack_top) = self.op_stack.pop().unwrap();
            assert_eq!(stack_top_ty, input);
            args[i] = stack_top;
        }
        log::trace!(" -> operands: {:?}", input_operands);
        log::trace!(" -> ty {:?}", outputs);

        let outputs_list = if n_outputs == 1 {
            self.body.single_type_list(outputs[0])
        } else {
            self.body.type_pool.from_iter(outputs.iter().cloned())
        };

        let value = self
            .body
            .add_value(ValueDef::Operator(op, input_operands, outputs_list));
        log::trace!(" -> value: {:?}", value);

        if self.reachable {
            self.body.append_to_block(self.cur_block, value);
        }
        self.body.source_locs[value] = loc;

        if n_outputs == 1 {
            let output_ty = outputs[0];
            self.op_stack.push((output_ty, value));
        } else {
            for (i, &output_ty) in outputs.into_iter().enumerate() {
                let pick = self
                    .body
                    .add_value(ValueDef::PickOutput(value, i as u32, output_ty));
                if self.reachable {
                    self.body.append_to_block(self.cur_block, pick);
                }
                self.op_stack.push((output_ty, pick));
                log::trace!(" -> pick {}: {:?} ty {:?}", i, pick, output_ty);
            }
        }

        Ok(())
    }
}