defmodule WaspVM.Decoder do alias WaspVM.LEB128 alias WaspVM.Module alias WaspVM.Decoder.TypeSectionParser alias WaspVM.Decoder.FunctionSectionParser alias WaspVM.Decoder.GlobalSectionParser alias WaspVM.Decoder.TableSectionParser alias WaspVM.Decoder.MemorySectionParser alias WaspVM.Decoder.ExportSectionParser alias WaspVM.Decoder.ImportSectionParser alias WaspVM.Decoder.StartSectionParser alias WaspVM.Decoder.ElementSectionParser alias WaspVM.Decoder.CodeSectionParser alias WaspVM.Decoder.CustomSectionParser alias WaspVM.Decoder.DataSectionParser require IEx @moduledoc false def decode_file(file_path, parallel \\ true) do {:ok, bin} = File.read(file_path) decode(bin, parallel) end def decode(bin, parallel \\ true) def decode(bin, parallel) when is_binary(bin), do: begin_split(bin, parallel) def decode(bin, _parallel) do {fun, arity} = __ENV__.function raise "Invalid data provided for #{fun}/#{arity}. Must be binary, got: #{inspect(bin)}" end defp begin_split(bin, parallel) do <> = bin if magic != <<0, 97, 115, 109>> do raise "Malformed or incomplete binary" else %Module{magic: magic, version: version} |> split_sections(rest, parallel) |> resolve_globals() end end defp split_sections(module, <<>>, true), do: parallel_decode(module) defp split_sections(module, <<>>, false), do: module defp split_sections(module, bin, parallel) do <> = bin {unparsed_section, rest} = split_section(section_code, rest) module = Module.add_section(module, section_code, unparsed_section) module = if !parallel do section = Map.get(module.sections, section_code) {k, v} = parse_section({section_code, section}) Map.put(module, k, v) else module end split_sections(module, rest, parallel) end defp split_section(sec_code, bin) when sec_code >= 0 do {size, rest} = LEB128.decode_unsigned(bin) <> = rest {section, rest} end defp parallel_decode(module) do module.sections |> Task.async_stream(&parse_section/1, timeout: :infinity, max_concurrency: 12) |> Enum.reduce(module, fn {:ok, {k, v}}, a -> Map.put(a, k, v) end) end defp resolve_globals(module) do resolve = fn v -> if !is_number(v.offset) do [{:get_global, i}] = v.offset val = Enum.at(module.globals, i).initial Map.put(v, :offset, val) else v end end elements = Enum.map(module.elements, & resolve.(&1)) data = Enum.map(module.data, & resolve.(&1)) Map.merge(module, %{elements: elements, data: data}) end defp parse_section({0, section}), do: CustomSectionParser.parse(section) defp parse_section({1, section}), do: TypeSectionParser.parse(section) defp parse_section({2, section}), do: ImportSectionParser.parse(section) defp parse_section({3, section}), do: FunctionSectionParser.parse(section) defp parse_section({4, section}), do: TableSectionParser.parse(section) defp parse_section({5, section}), do: MemorySectionParser.parse(section) defp parse_section({6, section}), do: GlobalSectionParser.parse(section) defp parse_section({7, section}), do: ExportSectionParser.parse(section) defp parse_section({8, section}), do: StartSectionParser.parse(section) defp parse_section({9, section}), do: ElementSectionParser.parse(section) defp parse_section({10, section}), do: CodeSectionParser.parse(section) defp parse_section({11, section}), do: DataSectionParser.parse(section) defp parse_section({_, section}), do: section end