defmodule QuickBEAM.WASM.Parser do @moduledoc false alias QuickBEAM.WASM.{Function, Module} @wasm_magic <<0x00, 0x61, 0x73, 0x6D>> @wasm_version_1 <<0x01, 0x00, 0x00, 0x00>> @section_custom 0 @section_type 1 @section_import 2 @section_function 3 @section_table 4 @section_memory 5 @section_global 6 @section_export 7 @section_start 8 @section_element 9 @section_code 10 @section_data 11 @section_data_count 12 @section_tag 13 # ── Public API ───────────────────────────────────────── @spec parse(binary()) :: {:ok, Module.t()} | {:error, String.t()} def parse(<<@wasm_magic, @wasm_version_1, rest::binary>>) do mod = %Module{version: 1} parse_sections(rest, mod) end def parse(<<@wasm_magic, v1, v2, v3, v4, _::binary>>), do: {:error, "unsupported WASM version: #{v1}.#{v2}.#{v3}.#{v4}"} def parse(_), do: {:error, "not a WASM binary (missing magic header)"} @spec validate(binary()) :: boolean() def validate(binary) do case parse(binary) do {:ok, _} -> true {:error, _} -> false end end # ── Section dispatch ─────────────────────────────────── defp parse_sections(<<>>, mod), do: {:ok, finalize(mod)} defp parse_sections(<>, mod) do {size, rest} = decode_u32(rest) <> = rest mod = parse_section(id, section_data, mod) parse_sections(rest, mod) rescue MatchError -> {:error, "truncated section (id=#{id})"} end defp parse_section(@section_custom, data, mod) do {name, data} = decode_name(data) custom = %{name: name, data: data} %{mod | custom_sections: mod.custom_sections ++ [custom]} end defp parse_section(@section_type, data, mod) do {types, <<>>} = decode_vec(data, &decode_functype/1) %{mod | types: types} end defp parse_section(@section_import, data, mod) do {imports, <<>>} = decode_vec(data, &decode_import/1) %{mod | imports: imports} end defp parse_section(@section_function, data, mod) do {type_indices, <<>>} = decode_vec(data, &decode_u32/1) funcs = Enum.with_index(type_indices, fn type_idx, i -> {i, type_idx} end) Map.put(mod, :_func_type_indices, funcs) end defp parse_section(@section_table, data, mod) do {tables, <<>>} = decode_vec(data, &decode_table/1) %{mod | tables: tables} end defp parse_section(@section_memory, data, mod) do {memories, <<>>} = decode_vec(data, &decode_memory/1) %{mod | memories: memories} end defp parse_section(@section_global, data, mod) do {globals, <<>>} = decode_vec(data, &decode_global/1) %{mod | globals: globals} end defp parse_section(@section_export, data, mod) do {exports, <<>>} = decode_vec(data, &decode_export/1) %{mod | exports: exports} end defp parse_section(@section_start, data, mod) do {func_idx, <<>>} = decode_u32(data) %{mod | start: func_idx} end defp parse_section(@section_element, data, mod) do {elements, <<>>} = decode_vec(data, &decode_element/1) %{mod | elements: elements} end defp parse_section(@section_code, data, mod) do {bodies, <<>>} = decode_vec(data, &decode_code_body/1) Map.put(mod, :_code_bodies, bodies) end defp parse_section(@section_data, data, mod) do {segments, <<>>} = decode_vec(data, &decode_data_segment/1) %{mod | data: segments} end defp parse_section(@section_data_count, data, mod) do {count, <<>>} = decode_u32(data) Map.put(mod, :_data_count, count) end defp parse_section(@section_tag, data, mod) do {tags, <<>>} = decode_vec(data, &decode_tag/1) %{mod | tags: tags} end defp parse_section(_id, _data, mod), do: mod # ── Finalization ─────────────────────────────────────── defp finalize(mod) do func_type_indices = Map.get(mod, :_func_type_indices, []) code_bodies = Map.get(mod, :_code_bodies, []) names = extract_names(mod.custom_sections) num_imports = Enum.count(mod.imports, &(&1.kind == :func)) functions = Enum.zip(func_type_indices, code_bodies) |> Enum.map(fn {{local_idx, type_idx}, {locals, opcodes}} -> func_idx = num_imports + local_idx type = Enum.at(mod.types, type_idx, %{params: [], results: []}) %Function{ index: func_idx, name: Map.get(names, func_idx), type_idx: type_idx, params: type.params, results: type.results, locals: locals, opcodes: opcodes } end) mod = mod |> Map.delete(:_func_type_indices) |> Map.delete(:_code_bodies) |> Map.delete(:_data_count) imports = Enum.map(mod.imports, &enrich_import(&1, mod.types)) exports = Enum.map(mod.exports, &enrich_export(&1, mod, func_type_indices)) %{mod | functions: functions, imports: imports, exports: exports} end defp extract_names(custom_sections) do case Enum.find(custom_sections, &(&1.name == "name")) do nil -> %{} %{data: data} -> parse_name_section(data) end end defp parse_name_section(data), do: parse_name_subsections(data, %{}) defp parse_name_subsections(<<>>, names), do: names defp parse_name_subsections(<>, names) do {size, rest} = decode_u32(rest) <> = rest names = case id do 1 -> parse_name_map(subsection) _ -> names end parse_name_subsections(rest, names) rescue _ -> names end defp parse_name_map(data) do {entries, _} = decode_vec(data, fn bin -> {idx, bin} = decode_u32(bin) {name, bin} = decode_name(bin) {{idx, name}, bin} end) Map.new(entries) end defp enrich_import(%{kind: :func, type_idx: type_idx} = import, types) do type = Enum.at(types, type_idx, %{params: [], results: []}) Map.merge(import, %{params: type.params, results: type.results}) end defp enrich_import(import, _types), do: import defp enrich_export(%{kind: :func, index: index} = export, mod, func_type_indices) do type_idx = function_type_idx(mod.imports, func_type_indices, index) type = Enum.at(mod.types, type_idx || -1, %{params: [], results: []}) Map.merge(export, %{params: type.params, results: type.results}) end defp enrich_export(%{kind: :memory, index: index} = export, mod, _func_type_indices) do Map.merge(export, Enum.at(mod.memories, index, %{})) end defp enrich_export(%{kind: :table, index: index} = export, mod, _func_type_indices) do Map.merge(export, Enum.at(mod.tables, index, %{})) end defp enrich_export(%{kind: :global, index: index} = export, mod, _func_type_indices) do global = mod.globals |> Enum.at(index, %{}) |> Map.drop([:init]) Map.merge(export, global) end defp enrich_export(export, _mod, _func_type_indices), do: export defp function_type_idx(imports, func_type_indices, index) do import_type_indices = imports |> Enum.filter(&(&1.kind == :func)) |> Enum.map(& &1.type_idx) if index < length(import_type_indices) do Enum.at(import_type_indices, index) else local_idx = index - length(import_type_indices) case Enum.at(func_type_indices, local_idx) do {_, type_idx} -> type_idx _ -> nil end end end # ── Type decoders ────────────────────────────────────── defp decode_functype(<<0x60, rest::binary>>) do {params, rest} = decode_vec(rest, &decode_valtype/1) {results, rest} = decode_vec(rest, &decode_valtype/1) {%{params: params, results: results}, rest} end defp decode_valtype(<<0x7F, rest::binary>>), do: {:i32, rest} defp decode_valtype(<<0x7E, rest::binary>>), do: {:i64, rest} defp decode_valtype(<<0x7D, rest::binary>>), do: {:f32, rest} defp decode_valtype(<<0x7C, rest::binary>>), do: {:f64, rest} defp decode_valtype(<<0x7B, rest::binary>>), do: {:v128, rest} defp decode_valtype(<<0x70, rest::binary>>), do: {:funcref, rest} defp decode_valtype(<<0x6F, rest::binary>>), do: {:externref, rest} defp decode_valtype(<>), do: {:unknown_valtype, <>} # ── Import decoders ──────────────────────────────────── defp decode_import(data) do {module_name, data} = decode_name(data) {field_name, data} = decode_name(data) {desc, data} = decode_import_desc(data) {Map.merge(%{module: module_name, name: field_name}, desc), data} end defp decode_import_desc(<<0x00, rest::binary>>) do {type_idx, rest} = decode_u32(rest) {%{kind: :func, type_idx: type_idx}, rest} end defp decode_import_desc(<<0x01, rest::binary>>) do {table, rest} = decode_table_type(rest) {Map.put(table, :kind, :table), rest} end defp decode_import_desc(<<0x02, rest::binary>>) do {mem, rest} = decode_limits(rest) {Map.put(mem, :kind, :memory), rest} end defp decode_import_desc(<<0x03, rest::binary>>) do {global, rest} = decode_global_type(rest) {Map.put(global, :kind, :global), rest} end defp decode_import_desc(<<0x04, rest::binary>>) do {tag, rest} = decode_tag_type(rest) {Map.put(tag, :kind, :tag), rest} end # ── Export decoders ──────────────────────────────────── defp decode_export(data) do {name, data} = decode_name(data) <> = data {index, data} = decode_u32(data) kind = case kind_byte do 0x00 -> :func 0x01 -> :table 0x02 -> :memory 0x03 -> :global 0x04 -> :tag end {%{name: name, kind: kind, index: index}, data} end # ── Table / Memory / Global decoders ─────────────────── defp decode_table(data), do: decode_table_type(data) defp decode_table_type(data) do {elem_type, data} = decode_valtype(data) {limits, data} = decode_limits(data) {Map.put(limits, :element, elem_type), data} end defp decode_memory(data), do: decode_limits(data) defp decode_limits(<<0x00, rest::binary>>) do {min, rest} = decode_u32(rest) {%{min: min, max: nil}, rest} end defp decode_limits(<<0x01, rest::binary>>) do {min, rest} = decode_u32(rest) {max, rest} = decode_u32(rest) {%{min: min, max: max}, rest} end defp decode_global(data) do {type, data} = decode_global_type(data) {init_opcodes, data} = decode_expr(data) {Map.put(type, :init, init_opcodes), data} end defp decode_global_type(data) do {valtype, data} = decode_valtype(data) <> = data {%{type: valtype, mutable: mutability == 1}, data} end defp decode_tag_type(<<0x00, rest::binary>>) do {type_idx, rest} = decode_u32(rest) {%{type_idx: type_idx}, rest} end defp decode_tag(data), do: decode_tag_type(data) # ── Element decoders ─────────────────────────────────── defp decode_element(<<0x00, rest::binary>>) do {offset, rest} = decode_expr(rest) {func_indices, rest} = decode_vec(rest, &decode_u32/1) {%{mode: :active, table_idx: 0, offset: offset, init: func_indices}, rest} end defp decode_element(<>) when kind in 1..7 do {%{mode: :unhandled_element_kind, kind: kind, data: rest}, <<>>} end # ── Data segment decoders ────────────────────────────── defp decode_data_segment(<<0x00, rest::binary>>) do {offset, rest} = decode_expr(rest) {size, rest} = decode_u32(rest) <> = rest {%{memory_idx: 0, offset: offset, bytes: bytes}, rest} end defp decode_data_segment(<<0x01, rest::binary>>) do {size, rest} = decode_u32(rest) <> = rest {%{memory_idx: nil, offset: nil, bytes: bytes}, rest} end defp decode_data_segment(<<0x02, rest::binary>>) do {mem_idx, rest} = decode_u32(rest) {offset, rest} = decode_expr(rest) {size, rest} = decode_u32(rest) <> = rest {%{memory_idx: mem_idx, offset: offset, bytes: bytes}, rest} end # ── Code body decoder ────────────────────────────────── defp decode_code_body(data) do {body_size, data} = decode_u32(data) <> = data {locals, body} = decode_locals(body) opcodes = decode_instructions(body) {{locals, opcodes}, rest} end defp decode_locals(data) do {groups, data} = decode_vec(data, fn bin -> {count, bin} = decode_u32(bin) {type, bin} = decode_valtype(bin) {{count, type}, bin} end) locals = Enum.flat_map(groups, fn {count, type} -> List.duplicate(type, count) end) {locals, data} end # ── Expression decoder (for init exprs) ──────────────── defp decode_expr(data) do {opcodes, rest} = decode_instructions_until_end(data, 0, []) {opcodes, rest} end # ── Instruction decoder ──────────────────────────────── defp decode_instructions(data) do {opcodes, _} = decode_instructions_until_end(data, 0, []) opcodes end defp decode_instructions_until_end(<<>>, _offset, acc), do: {Enum.reverse(acc), <<>>} defp decode_instructions_until_end(<<0x0B, rest::binary>>, offset, acc) do {Enum.reverse([{offset, :end} | acc]), rest} end defp decode_instructions_until_end(data, offset, acc) do {opcode, rest} = decode_one_instruction(data, offset) next_offset = offset + (byte_size(data) - byte_size(rest)) decode_instructions_until_end(rest, next_offset, [opcode | acc]) end # Control defp decode_one_instruction(<<0x00, rest::binary>>, off), do: {{off, :unreachable}, rest} defp decode_one_instruction(<<0x01, rest::binary>>, off), do: {{off, :nop}, rest} defp decode_one_instruction(<<0x02, rest::binary>>, off) do {bt, rest} = decode_blocktype(rest) {{off, :block, bt}, rest} end defp decode_one_instruction(<<0x03, rest::binary>>, off) do {bt, rest} = decode_blocktype(rest) {{off, :loop, bt}, rest} end defp decode_one_instruction(<<0x04, rest::binary>>, off) do {bt, rest} = decode_blocktype(rest) {{off, :if, bt}, rest} end defp decode_one_instruction(<<0x05, rest::binary>>, off), do: {{off, :else}, rest} defp decode_one_instruction(<<0x0B, rest::binary>>, off), do: {{off, :end}, rest} defp decode_one_instruction(<<0x0C, rest::binary>>, off) do {label, rest} = decode_u32(rest) {{off, :br, label}, rest} end defp decode_one_instruction(<<0x0D, rest::binary>>, off) do {label, rest} = decode_u32(rest) {{off, :br_if, label}, rest} end defp decode_one_instruction(<<0x0E, rest::binary>>, off) do {labels, rest} = decode_vec(rest, &decode_u32/1) {default, rest} = decode_u32(rest) {{off, :br_table, labels, default}, rest} end defp decode_one_instruction(<<0x0F, rest::binary>>, off), do: {{off, :return}, rest} defp decode_one_instruction(<<0x10, rest::binary>>, off) do {idx, rest} = decode_u32(rest) {{off, :call, idx}, rest} end defp decode_one_instruction(<<0x11, rest::binary>>, off) do {type_idx, rest} = decode_u32(rest) {table_idx, rest} = decode_u32(rest) {{off, :call_indirect, table_idx, type_idx}, rest} end defp decode_one_instruction(<<0x12, rest::binary>>, off) do {idx, rest} = decode_u32(rest) {{off, :return_call, idx}, rest} end defp decode_one_instruction(<<0x13, rest::binary>>, off) do {type_idx, rest} = decode_u32(rest) {table_idx, rest} = decode_u32(rest) {{off, :return_call_indirect, table_idx, type_idx}, rest} end # Parametric defp decode_one_instruction(<<0x1A, rest::binary>>, off), do: {{off, :drop}, rest} defp decode_one_instruction(<<0x1B, rest::binary>>, off), do: {{off, :select}, rest} defp decode_one_instruction(<<0x1C, rest::binary>>, off) do {types, rest} = decode_vec(rest, &decode_valtype/1) {{off, :select, types}, rest} end # Variable defp decode_one_instruction(<<0x20, rest::binary>>, off) do {idx, rest} = decode_u32(rest) {{off, :local_get, idx}, rest} end defp decode_one_instruction(<<0x21, rest::binary>>, off) do {idx, rest} = decode_u32(rest) {{off, :local_set, idx}, rest} end defp decode_one_instruction(<<0x22, rest::binary>>, off) do {idx, rest} = decode_u32(rest) {{off, :local_tee, idx}, rest} end defp decode_one_instruction(<<0x23, rest::binary>>, off) do {idx, rest} = decode_u32(rest) {{off, :global_get, idx}, rest} end defp decode_one_instruction(<<0x24, rest::binary>>, off) do {idx, rest} = decode_u32(rest) {{off, :global_set, idx}, rest} end # Table defp decode_one_instruction(<<0x25, rest::binary>>, off) do {idx, rest} = decode_u32(rest) {{off, :table_get, idx}, rest} end defp decode_one_instruction(<<0x26, rest::binary>>, off) do {idx, rest} = decode_u32(rest) {{off, :table_set, idx}, rest} end # Memory load/store defp decode_one_instruction(<>, off) when opcode in 0x28..0x3E do {align, rest} = decode_u32(rest) {mem_offset, rest} = decode_u32(rest) name = memory_op_name(opcode) {{off, name, align, mem_offset}, rest} end # Memory size/grow defp decode_one_instruction(<<0x3F, 0x00, rest::binary>>, off), do: {{off, :memory_size, 0}, rest} defp decode_one_instruction(<<0x40, 0x00, rest::binary>>, off), do: {{off, :memory_grow, 0}, rest} # Numeric constants defp decode_one_instruction(<<0x41, rest::binary>>, off) do {val, rest} = decode_i32(rest) {{off, :i32_const, val}, rest} end defp decode_one_instruction(<<0x42, rest::binary>>, off) do {val, rest} = decode_i64(rest) {{off, :i64_const, val}, rest} end defp decode_one_instruction(<<0x43, val::little-float-32, rest::binary>>, off), do: {{off, :f32_const, val}, rest} defp decode_one_instruction(<<0x44, val::little-float-64, rest::binary>>, off), do: {{off, :f64_const, val}, rest} # i32 comparison/arithmetic defp decode_one_instruction(<<0x45, rest::binary>>, off), do: {{off, :i32_eqz}, rest} defp decode_one_instruction(<<0x46, rest::binary>>, off), do: {{off, :i32_eq}, rest} defp decode_one_instruction(<<0x47, rest::binary>>, off), do: {{off, :i32_ne}, rest} defp decode_one_instruction(<<0x48, rest::binary>>, off), do: {{off, :i32_lt_s}, rest} defp decode_one_instruction(<<0x49, rest::binary>>, off), do: {{off, :i32_lt_u}, rest} defp decode_one_instruction(<<0x4A, rest::binary>>, off), do: {{off, :i32_gt_s}, rest} defp decode_one_instruction(<<0x4B, rest::binary>>, off), do: {{off, :i32_gt_u}, rest} defp decode_one_instruction(<<0x4C, rest::binary>>, off), do: {{off, :i32_le_s}, rest} defp decode_one_instruction(<<0x4D, rest::binary>>, off), do: {{off, :i32_le_u}, rest} defp decode_one_instruction(<<0x4E, rest::binary>>, off), do: {{off, :i32_ge_s}, rest} defp decode_one_instruction(<<0x4F, rest::binary>>, off), do: {{off, :i32_ge_u}, rest} # i64 comparison defp decode_one_instruction(<<0x50, rest::binary>>, off), do: {{off, :i64_eqz}, rest} defp decode_one_instruction(<<0x51, rest::binary>>, off), do: {{off, :i64_eq}, rest} defp decode_one_instruction(<<0x52, rest::binary>>, off), do: {{off, :i64_ne}, rest} defp decode_one_instruction(<<0x53, rest::binary>>, off), do: {{off, :i64_lt_s}, rest} defp decode_one_instruction(<<0x54, rest::binary>>, off), do: {{off, :i64_lt_u}, rest} defp decode_one_instruction(<<0x55, rest::binary>>, off), do: {{off, :i64_gt_s}, rest} defp decode_one_instruction(<<0x56, rest::binary>>, off), do: {{off, :i64_gt_u}, rest} defp decode_one_instruction(<<0x57, rest::binary>>, off), do: {{off, :i64_le_s}, rest} defp decode_one_instruction(<<0x58, rest::binary>>, off), do: {{off, :i64_le_u}, rest} defp decode_one_instruction(<<0x59, rest::binary>>, off), do: {{off, :i64_ge_s}, rest} defp decode_one_instruction(<<0x5A, rest::binary>>, off), do: {{off, :i64_ge_u}, rest} # f32 comparison defp decode_one_instruction(<<0x5B, rest::binary>>, off), do: {{off, :f32_eq}, rest} defp decode_one_instruction(<<0x5C, rest::binary>>, off), do: {{off, :f32_ne}, rest} defp decode_one_instruction(<<0x5D, rest::binary>>, off), do: {{off, :f32_lt}, rest} defp decode_one_instruction(<<0x5E, rest::binary>>, off), do: {{off, :f32_gt}, rest} defp decode_one_instruction(<<0x5F, rest::binary>>, off), do: {{off, :f32_le}, rest} defp decode_one_instruction(<<0x60, rest::binary>>, off), do: {{off, :f32_ge}, rest} # f64 comparison defp decode_one_instruction(<<0x61, rest::binary>>, off), do: {{off, :f64_eq}, rest} defp decode_one_instruction(<<0x62, rest::binary>>, off), do: {{off, :f64_ne}, rest} defp decode_one_instruction(<<0x63, rest::binary>>, off), do: {{off, :f64_lt}, rest} defp decode_one_instruction(<<0x64, rest::binary>>, off), do: {{off, :f64_gt}, rest} defp decode_one_instruction(<<0x65, rest::binary>>, off), do: {{off, :f64_le}, rest} defp decode_one_instruction(<<0x66, rest::binary>>, off), do: {{off, :f64_ge}, rest} # i32 arithmetic defp decode_one_instruction(<<0x67, rest::binary>>, off), do: {{off, :i32_clz}, rest} defp decode_one_instruction(<<0x68, rest::binary>>, off), do: {{off, :i32_ctz}, rest} defp decode_one_instruction(<<0x69, rest::binary>>, off), do: {{off, :i32_popcnt}, rest} defp decode_one_instruction(<<0x6A, rest::binary>>, off), do: {{off, :i32_add}, rest} defp decode_one_instruction(<<0x6B, rest::binary>>, off), do: {{off, :i32_sub}, rest} defp decode_one_instruction(<<0x6C, rest::binary>>, off), do: {{off, :i32_mul}, rest} defp decode_one_instruction(<<0x6D, rest::binary>>, off), do: {{off, :i32_div_s}, rest} defp decode_one_instruction(<<0x6E, rest::binary>>, off), do: {{off, :i32_div_u}, rest} defp decode_one_instruction(<<0x6F, rest::binary>>, off), do: {{off, :i32_rem_s}, rest} defp decode_one_instruction(<<0x70, rest::binary>>, off), do: {{off, :i32_rem_u}, rest} defp decode_one_instruction(<<0x71, rest::binary>>, off), do: {{off, :i32_and}, rest} defp decode_one_instruction(<<0x72, rest::binary>>, off), do: {{off, :i32_or}, rest} defp decode_one_instruction(<<0x73, rest::binary>>, off), do: {{off, :i32_xor}, rest} defp decode_one_instruction(<<0x74, rest::binary>>, off), do: {{off, :i32_shl}, rest} defp decode_one_instruction(<<0x75, rest::binary>>, off), do: {{off, :i32_shr_s}, rest} defp decode_one_instruction(<<0x76, rest::binary>>, off), do: {{off, :i32_shr_u}, rest} defp decode_one_instruction(<<0x77, rest::binary>>, off), do: {{off, :i32_rotl}, rest} defp decode_one_instruction(<<0x78, rest::binary>>, off), do: {{off, :i32_rotr}, rest} # i64 arithmetic defp decode_one_instruction(<<0x79, rest::binary>>, off), do: {{off, :i64_clz}, rest} defp decode_one_instruction(<<0x7A, rest::binary>>, off), do: {{off, :i64_ctz}, rest} defp decode_one_instruction(<<0x7B, rest::binary>>, off), do: {{off, :i64_popcnt}, rest} defp decode_one_instruction(<<0x7C, rest::binary>>, off), do: {{off, :i64_add}, rest} defp decode_one_instruction(<<0x7D, rest::binary>>, off), do: {{off, :i64_sub}, rest} defp decode_one_instruction(<<0x7E, rest::binary>>, off), do: {{off, :i64_mul}, rest} defp decode_one_instruction(<<0x7F, rest::binary>>, off), do: {{off, :i64_div_s}, rest} defp decode_one_instruction(<<0x80, rest::binary>>, off), do: {{off, :i64_div_u}, rest} defp decode_one_instruction(<<0x81, rest::binary>>, off), do: {{off, :i64_rem_s}, rest} defp decode_one_instruction(<<0x82, rest::binary>>, off), do: {{off, :i64_rem_u}, rest} defp decode_one_instruction(<<0x83, rest::binary>>, off), do: {{off, :i64_and}, rest} defp decode_one_instruction(<<0x84, rest::binary>>, off), do: {{off, :i64_or}, rest} defp decode_one_instruction(<<0x85, rest::binary>>, off), do: {{off, :i64_xor}, rest} defp decode_one_instruction(<<0x86, rest::binary>>, off), do: {{off, :i64_shl}, rest} defp decode_one_instruction(<<0x87, rest::binary>>, off), do: {{off, :i64_shr_s}, rest} defp decode_one_instruction(<<0x88, rest::binary>>, off), do: {{off, :i64_shr_u}, rest} defp decode_one_instruction(<<0x89, rest::binary>>, off), do: {{off, :i64_rotl}, rest} defp decode_one_instruction(<<0x8A, rest::binary>>, off), do: {{off, :i64_rotr}, rest} # f32 arithmetic defp decode_one_instruction(<<0x8B, rest::binary>>, off), do: {{off, :f32_abs}, rest} defp decode_one_instruction(<<0x8C, rest::binary>>, off), do: {{off, :f32_neg}, rest} defp decode_one_instruction(<<0x8D, rest::binary>>, off), do: {{off, :f32_ceil}, rest} defp decode_one_instruction(<<0x8E, rest::binary>>, off), do: {{off, :f32_floor}, rest} defp decode_one_instruction(<<0x8F, rest::binary>>, off), do: {{off, :f32_trunc}, rest} defp decode_one_instruction(<<0x90, rest::binary>>, off), do: {{off, :f32_nearest}, rest} defp decode_one_instruction(<<0x91, rest::binary>>, off), do: {{off, :f32_sqrt}, rest} defp decode_one_instruction(<<0x92, rest::binary>>, off), do: {{off, :f32_add}, rest} defp decode_one_instruction(<<0x93, rest::binary>>, off), do: {{off, :f32_sub}, rest} defp decode_one_instruction(<<0x94, rest::binary>>, off), do: {{off, :f32_mul}, rest} defp decode_one_instruction(<<0x95, rest::binary>>, off), do: {{off, :f32_div}, rest} defp decode_one_instruction(<<0x96, rest::binary>>, off), do: {{off, :f32_min}, rest} defp decode_one_instruction(<<0x97, rest::binary>>, off), do: {{off, :f32_max}, rest} defp decode_one_instruction(<<0x98, rest::binary>>, off), do: {{off, :f32_copysign}, rest} # f64 arithmetic defp decode_one_instruction(<<0x99, rest::binary>>, off), do: {{off, :f64_abs}, rest} defp decode_one_instruction(<<0x9A, rest::binary>>, off), do: {{off, :f64_neg}, rest} defp decode_one_instruction(<<0x9B, rest::binary>>, off), do: {{off, :f64_ceil}, rest} defp decode_one_instruction(<<0x9C, rest::binary>>, off), do: {{off, :f64_floor}, rest} defp decode_one_instruction(<<0x9D, rest::binary>>, off), do: {{off, :f64_trunc}, rest} defp decode_one_instruction(<<0x9E, rest::binary>>, off), do: {{off, :f64_nearest}, rest} defp decode_one_instruction(<<0x9F, rest::binary>>, off), do: {{off, :f64_sqrt}, rest} defp decode_one_instruction(<<0xA0, rest::binary>>, off), do: {{off, :f64_add}, rest} defp decode_one_instruction(<<0xA1, rest::binary>>, off), do: {{off, :f64_sub}, rest} defp decode_one_instruction(<<0xA2, rest::binary>>, off), do: {{off, :f64_mul}, rest} defp decode_one_instruction(<<0xA3, rest::binary>>, off), do: {{off, :f64_div}, rest} defp decode_one_instruction(<<0xA4, rest::binary>>, off), do: {{off, :f64_min}, rest} defp decode_one_instruction(<<0xA5, rest::binary>>, off), do: {{off, :f64_max}, rest} defp decode_one_instruction(<<0xA6, rest::binary>>, off), do: {{off, :f64_copysign}, rest} # Conversions defp decode_one_instruction(<<0xA7, rest::binary>>, off), do: {{off, :i32_wrap_i64}, rest} defp decode_one_instruction(<<0xA8, rest::binary>>, off), do: {{off, :i32_trunc_f32_s}, rest} defp decode_one_instruction(<<0xA9, rest::binary>>, off), do: {{off, :i32_trunc_f32_u}, rest} defp decode_one_instruction(<<0xAA, rest::binary>>, off), do: {{off, :i32_trunc_f64_s}, rest} defp decode_one_instruction(<<0xAB, rest::binary>>, off), do: {{off, :i32_trunc_f64_u}, rest} defp decode_one_instruction(<<0xAC, rest::binary>>, off), do: {{off, :i64_extend_i32_s}, rest} defp decode_one_instruction(<<0xAD, rest::binary>>, off), do: {{off, :i64_extend_i32_u}, rest} defp decode_one_instruction(<<0xAE, rest::binary>>, off), do: {{off, :i64_trunc_f32_s}, rest} defp decode_one_instruction(<<0xAF, rest::binary>>, off), do: {{off, :i64_trunc_f32_u}, rest} defp decode_one_instruction(<<0xB0, rest::binary>>, off), do: {{off, :i64_trunc_f64_s}, rest} defp decode_one_instruction(<<0xB1, rest::binary>>, off), do: {{off, :i64_trunc_f64_u}, rest} defp decode_one_instruction(<<0xB2, rest::binary>>, off), do: {{off, :f32_convert_i32_s}, rest} defp decode_one_instruction(<<0xB3, rest::binary>>, off), do: {{off, :f32_convert_i32_u}, rest} defp decode_one_instruction(<<0xB4, rest::binary>>, off), do: {{off, :f32_convert_i64_s}, rest} defp decode_one_instruction(<<0xB5, rest::binary>>, off), do: {{off, :f32_convert_i64_u}, rest} defp decode_one_instruction(<<0xB6, rest::binary>>, off), do: {{off, :f32_demote_f64}, rest} defp decode_one_instruction(<<0xB7, rest::binary>>, off), do: {{off, :f64_convert_i32_s}, rest} defp decode_one_instruction(<<0xB8, rest::binary>>, off), do: {{off, :f64_convert_i32_u}, rest} defp decode_one_instruction(<<0xB9, rest::binary>>, off), do: {{off, :f64_convert_i64_s}, rest} defp decode_one_instruction(<<0xBA, rest::binary>>, off), do: {{off, :f64_convert_i64_u}, rest} defp decode_one_instruction(<<0xBB, rest::binary>>, off), do: {{off, :f64_promote_f32}, rest} defp decode_one_instruction(<<0xBC, rest::binary>>, off), do: {{off, :i32_reinterpret_f32}, rest} defp decode_one_instruction(<<0xBD, rest::binary>>, off), do: {{off, :i64_reinterpret_f64}, rest} defp decode_one_instruction(<<0xBE, rest::binary>>, off), do: {{off, :f32_reinterpret_i32}, rest} defp decode_one_instruction(<<0xBF, rest::binary>>, off), do: {{off, :f64_reinterpret_i64}, rest} # Sign extension defp decode_one_instruction(<<0xC0, rest::binary>>, off), do: {{off, :i32_extend8_s}, rest} defp decode_one_instruction(<<0xC1, rest::binary>>, off), do: {{off, :i32_extend16_s}, rest} defp decode_one_instruction(<<0xC2, rest::binary>>, off), do: {{off, :i64_extend8_s}, rest} defp decode_one_instruction(<<0xC3, rest::binary>>, off), do: {{off, :i64_extend16_s}, rest} defp decode_one_instruction(<<0xC4, rest::binary>>, off), do: {{off, :i64_extend32_s}, rest} # Reference instructions defp decode_one_instruction(<<0xD0, rest::binary>>, off) do {ht, rest} = decode_valtype(rest) {{off, :ref_null, ht}, rest} end defp decode_one_instruction(<<0xD1, rest::binary>>, off), do: {{off, :ref_is_null}, rest} defp decode_one_instruction(<<0xD2, rest::binary>>, off) do {idx, rest} = decode_u32(rest) {{off, :ref_func, idx}, rest} end # 0xFC prefix — saturating truncation + bulk memory + table ops defp decode_one_instruction(<<0xFC, rest::binary>>, off) do {sub, rest} = decode_u32(rest) decode_fc_instruction(sub, rest, off) end # Catch-all for unknown opcodes defp decode_one_instruction(<>, off), do: {{off, :unknown, byte}, rest} # ── 0xFC sub-opcodes ─────────────────────────────────── defp decode_fc_instruction(0, rest, off), do: {{off, :i32_trunc_sat_f32_s}, rest} defp decode_fc_instruction(1, rest, off), do: {{off, :i32_trunc_sat_f32_u}, rest} defp decode_fc_instruction(2, rest, off), do: {{off, :i32_trunc_sat_f64_s}, rest} defp decode_fc_instruction(3, rest, off), do: {{off, :i32_trunc_sat_f64_u}, rest} defp decode_fc_instruction(4, rest, off), do: {{off, :i64_trunc_sat_f32_s}, rest} defp decode_fc_instruction(5, rest, off), do: {{off, :i64_trunc_sat_f32_u}, rest} defp decode_fc_instruction(6, rest, off), do: {{off, :i64_trunc_sat_f64_s}, rest} defp decode_fc_instruction(7, rest, off), do: {{off, :i64_trunc_sat_f64_u}, rest} defp decode_fc_instruction(8, rest, off) do {data_idx, rest} = decode_u32(rest) <<0x00, rest::binary>> = rest {{off, :memory_init, data_idx, 0}, rest} end defp decode_fc_instruction(9, rest, off) do {data_idx, rest} = decode_u32(rest) {{off, :data_drop, data_idx}, rest} end defp decode_fc_instruction(10, <<0x00, 0x00, rest::binary>>, off), do: {{off, :memory_copy, 0, 0}, rest} defp decode_fc_instruction(11, <<0x00, rest::binary>>, off), do: {{off, :memory_fill, 0}, rest} defp decode_fc_instruction(12, rest, off) do {elem_idx, rest} = decode_u32(rest) {table_idx, rest} = decode_u32(rest) {{off, :table_init, table_idx, elem_idx}, rest} end defp decode_fc_instruction(13, rest, off) do {elem_idx, rest} = decode_u32(rest) {{off, :elem_drop, elem_idx}, rest} end defp decode_fc_instruction(14, rest, off) do {dst, rest} = decode_u32(rest) {src, rest} = decode_u32(rest) {{off, :table_copy, dst, src}, rest} end defp decode_fc_instruction(15, rest, off) do {table_idx, rest} = decode_u32(rest) {{off, :table_grow, table_idx}, rest} end defp decode_fc_instruction(16, rest, off) do {table_idx, rest} = decode_u32(rest) {{off, :table_size, table_idx}, rest} end defp decode_fc_instruction(17, rest, off) do {table_idx, rest} = decode_u32(rest) {{off, :table_fill, table_idx}, rest} end defp decode_fc_instruction(sub, rest, off), do: {{off, :unknown_fc, sub}, rest} # ── Block type decoder ───────────────────────────────── defp decode_blocktype(<<0x40, rest::binary>>), do: {:void, rest} defp decode_blocktype(<<0x7F, rest::binary>>), do: {:i32, rest} defp decode_blocktype(<<0x7E, rest::binary>>), do: {:i64, rest} defp decode_blocktype(<<0x7D, rest::binary>>), do: {:f32, rest} defp decode_blocktype(<<0x7C, rest::binary>>), do: {:f64, rest} defp decode_blocktype(<<0x7B, rest::binary>>), do: {:v128, rest} defp decode_blocktype(<<0x70, rest::binary>>), do: {:funcref, rest} defp decode_blocktype(<<0x6F, rest::binary>>), do: {:externref, rest} defp decode_blocktype(data) do {idx, rest} = decode_s33(data) {{:type, idx}, rest} end # ── Memory op name lookup ────────────────────────────── @memory_ops %{ 0x28 => :i32_load, 0x29 => :i64_load, 0x2A => :f32_load, 0x2B => :f64_load, 0x2C => :i32_load8_s, 0x2D => :i32_load8_u, 0x2E => :i32_load16_s, 0x2F => :i32_load16_u, 0x30 => :i64_load8_s, 0x31 => :i64_load8_u, 0x32 => :i64_load16_s, 0x33 => :i64_load16_u, 0x34 => :i64_load32_s, 0x35 => :i64_load32_u, 0x36 => :i32_store, 0x37 => :i64_store, 0x38 => :f32_store, 0x39 => :f64_store, 0x3A => :i32_store8, 0x3B => :i32_store16, 0x3C => :i64_store8, 0x3D => :i64_store16, 0x3E => :i64_store32 } defp memory_op_name(opcode), do: Map.fetch!(@memory_ops, opcode) # ── LEB128 decoders ──────────────────────────────────── defp decode_u32(data), do: decode_uleb128(data, 0, 0) defp decode_uleb128(<>, result, shift) do value = Bitwise.bor(result, Bitwise.bsl(Bitwise.band(byte, 0x7F), shift)) if Bitwise.band(byte, 0x80) == 0 do {value, rest} else decode_uleb128(rest, value, shift + 7) end end defp decode_i32(data), do: decode_sleb128(data, 0, 0, 32) defp decode_i64(data), do: decode_sleb128(data, 0, 0, 64) defp decode_s33(data), do: decode_sleb128(data, 0, 0, 33) defp decode_sleb128(<>, result, shift, size) do value = Bitwise.bor(result, Bitwise.bsl(Bitwise.band(byte, 0x7F), shift)) shift = shift + 7 if Bitwise.band(byte, 0x80) == 0 do value = if shift < size and Bitwise.band(byte, 0x40) != 0 do Bitwise.bor(value, Bitwise.bsl(-1, shift)) else value end {value, rest} else decode_sleb128(rest, value, shift, size) end end # ── Vec / Name decoders ──────────────────────────────── defp decode_vec(data, decoder) do {count, data} = decode_u32(data) decode_n(data, count, decoder, []) end defp decode_n(data, 0, _decoder, acc), do: {Enum.reverse(acc), data} defp decode_n(data, n, decoder, acc) do {item, data} = decoder.(data) decode_n(data, n - 1, decoder, [item | acc]) end defp decode_name(data) do {len, data} = decode_u32(data) <> = data {name, data} end end