From be0f2e364c161d052a35fe061b73c8bdf538bf3f Mon Sep 17 00:00:00 2001 From: Zxilly Date: Thu, 30 May 2024 17:27:04 +0800 Subject: [PATCH] perf: remove unnecessary json encode from wasm --- .golangci.yaml | 6 +-- cmd/wasm/{main_wasm.go => main_js_wasm.go} | 23 +++-------- internal/disasm/extract.go | 5 ++- .../{disasm_wasm.go => disasm_js_wasm.go} | 0 internal/entity/file.go | 37 ----------------- internal/entity/file_js_wasm.go | 15 +++++++ internal/entity/file_normal.go | 40 +++++++++++++++++++ internal/entity/package_js_wasm.go | 34 ++++++++++++++++ internal/entity/section_js_wasm.go | 22 ++++++++++ internal/entity/symbol_js_wasm.go | 16 ++++++++ internal/printer/wasm/obj_js_wasm.go | 13 ++++++ internal/result/result_js_wasm.go | 25 ++++++++++++ scripts/wasm.py | 2 +- ui/src/explorer/app.tsx | 19 +++++---- ui/src/explorer/file_selector.tsx | 2 +- ui/src/explorer/gsa.d.ts | 1 - ui/src/tool/utils.ts | 26 ------------ 17 files changed, 189 insertions(+), 97 deletions(-) rename cmd/wasm/{main_wasm.go => main_js_wasm.go} (54%) rename internal/{disasm_wasm.go => disasm_js_wasm.go} (100%) create mode 100644 internal/entity/file_js_wasm.go create mode 100644 internal/entity/file_normal.go create mode 100644 internal/entity/package_js_wasm.go create mode 100644 internal/entity/section_js_wasm.go create mode 100644 internal/entity/symbol_js_wasm.go create mode 100644 internal/printer/wasm/obj_js_wasm.go create mode 100644 internal/result/result_js_wasm.go delete mode 100644 ui/src/explorer/gsa.d.ts diff --git a/.golangci.yaml b/.golangci.yaml index 7a6f4a361f..9657e2fa6f 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -69,15 +69,13 @@ linters-settings: # Default: false require-specific: true - # define the import orders gci: sections: - # Standard section: captures all standard packages. - standard - # Default section: catchall that is not standard or custom - default - # linters that related to local tool, so they should be separated + - prefix(syscall/js) # fixme: workaround, wait for https://github.com/daixiang0/gci/pull/208 merged - localmodule + custom-order: true staticcheck: # SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks diff --git a/cmd/wasm/main_wasm.go b/cmd/wasm/main_js_wasm.go similarity index 54% rename from cmd/wasm/main_wasm.go rename to cmd/wasm/main_js_wasm.go index c4b3fd30fc..48509317c5 100644 --- a/cmd/wasm/main_wasm.go +++ b/cmd/wasm/main_js_wasm.go @@ -7,10 +7,9 @@ import ( "fmt" "log/slog" "syscall/js" - "unsafe" "github.com/Zxilly/go-size-analyzer/internal" - "github.com/Zxilly/go-size-analyzer/internal/printer" + "github.com/Zxilly/go-size-analyzer/internal/printer/wasm" "github.com/Zxilly/go-size-analyzer/internal/utils" ) @@ -18,12 +17,13 @@ func analyze(_ js.Value, args []js.Value) any { utils.InitLogger(slog.LevelDebug) name := args[0].String() - data := make([]byte, args[1].Length()) + length := args[1].Length() + data := make([]byte, length) js.CopyBytesToGo(data, args[1]) reader := bytes.NewReader(data) - result, err := internal.Analyze(name, reader, uint64(len(data)), internal.Options{ + result, err := internal.Analyze(name, reader, uint64(length), internal.Options{ SkipDisasm: true, }) if err != nil { @@ -31,25 +31,14 @@ func analyze(_ js.Value, args []js.Value) any { return js.ValueOf(nil) } - buf := new(bytes.Buffer) - err = printer.JSON(result, &printer.JSONOption{ - Writer: buf, - Indent: nil, - HideDetail: true, - }) - - if err != nil { - fmt.Printf("Error: %v\n", err) - return js.ValueOf(nil) - } - - return js.ValueOf(unsafe.String(unsafe.SliceData(buf.Bytes()), buf.Len())) + return wasm.JavaScript(result) } func main() { utils.ApplyMemoryLimit() js.Global().Set("gsa_analyze", js.FuncOf(analyze)) + js.Global().Get("console").Call("log", "Go size analyzer initialized") select {} } diff --git a/internal/disasm/extract.go b/internal/disasm/extract.go index de8d66ed91..402f1dd82e 100644 --- a/internal/disasm/extract.go +++ b/internal/disasm/extract.go @@ -17,10 +17,11 @@ func extractAmd64(code []byte, pc uint64) []PossibleStr { for len(code) > 0 { inst, err := x86asm.Decode(code, 64) - size := inst.Len - if err != nil || size == 0 || inst.Op == 0 { + size := 0 + if err != nil || inst.Len == 0 || inst.Op == 0 { size = 1 } else { + size = inst.Len if inst.Op != x86asm.NOP { insts = append(insts, x86PosInst{pc: pc, inst: inst}) } diff --git a/internal/disasm_wasm.go b/internal/disasm_js_wasm.go similarity index 100% rename from internal/disasm_wasm.go rename to internal/disasm_js_wasm.go diff --git a/internal/entity/file.go b/internal/entity/file.go index 423518603b..f9b2fc07b3 100644 --- a/internal/entity/file.go +++ b/internal/entity/file.go @@ -1,10 +1,5 @@ package entity -import ( - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" -) - type File struct { FilePath string `json:"file_path"` Functions []*Function `json:"functions"` @@ -12,38 +7,6 @@ type File struct { Pkg *Package `json:"-"` } -var FileMarshalerCompact = json.MarshalFuncV2[File](func(encoder *jsontext.Encoder, file File, options json.Options) error { - err := encoder.WriteToken(jsontext.ObjectStart) - if err != nil { - return err - } - - if err = json.MarshalEncode(encoder, "file_path", options); err != nil { - return err - } - if err = json.MarshalEncode(encoder, file.FilePath, options); err != nil { - return err - } - if err = json.MarshalEncode(encoder, "size", options); err != nil { - return err - } - if err = json.MarshalEncode(encoder, file.FullSize(), options); err != nil { - return err - } - if err = json.MarshalEncode(encoder, "pcln_size", options); err != nil { - return err - } - if err = json.MarshalEncode(encoder, file.PclnSize(), options); err != nil { - return err - } - - err = encoder.WriteToken(jsontext.ObjectEnd) - if err != nil { - return err - } - return nil -}) - func (f *File) FullSize() uint64 { size := uint64(0) for _, fn := range f.Functions { diff --git a/internal/entity/file_js_wasm.go b/internal/entity/file_js_wasm.go new file mode 100644 index 0000000000..ec8c66fee6 --- /dev/null +++ b/internal/entity/file_js_wasm.go @@ -0,0 +1,15 @@ +//go:build js && wasm + +package entity + +import ( + "syscall/js" +) + +func (f *File) MarshalJavaScript() js.Value { + return js.ValueOf(map[string]any{ + "file_path": f.FilePath, + "size": f.FullSize(), + "pcln_size": f.PclnSize(), + }) +} diff --git a/internal/entity/file_normal.go b/internal/entity/file_normal.go new file mode 100644 index 0000000000..11730fab08 --- /dev/null +++ b/internal/entity/file_normal.go @@ -0,0 +1,40 @@ +//go:build !js && !wasm + +package entity + +import ( + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" +) + +var FileMarshalerCompact = json.MarshalFuncV2[File](func(encoder *jsontext.Encoder, file File, options json.Options) error { + err := encoder.WriteToken(jsontext.ObjectStart) + if err != nil { + return err + } + + if err = json.MarshalEncode(encoder, "file_path", options); err != nil { + return err + } + if err = json.MarshalEncode(encoder, file.FilePath, options); err != nil { + return err + } + if err = json.MarshalEncode(encoder, "size", options); err != nil { + return err + } + if err = json.MarshalEncode(encoder, file.FullSize(), options); err != nil { + return err + } + if err = json.MarshalEncode(encoder, "pcln_size", options); err != nil { + return err + } + if err = json.MarshalEncode(encoder, file.PclnSize(), options); err != nil { + return err + } + + err = encoder.WriteToken(jsontext.ObjectEnd) + if err != nil { + return err + } + return nil +}) diff --git a/internal/entity/package_js_wasm.go b/internal/entity/package_js_wasm.go new file mode 100644 index 0000000000..5c19c3870e --- /dev/null +++ b/internal/entity/package_js_wasm.go @@ -0,0 +1,34 @@ +//go:build js && wasm + +package entity + +import ( + "syscall/js" + + "github.com/samber/lo" +) + +func (m PackageMap) MarshalJavaScript() js.Value { + ret := map[string]any{} + + for k, v := range m { + ret[k] = v.MarshalJavaScript() + } + + return js.ValueOf(ret) +} + +func (p *Package) MarshalJavaScript() js.Value { + var symbols, files []any + symbols = lo.Map(p.Symbols, func(s *Symbol, _ int) any { return s.MarshalJavaScript() }) + files = lo.Map(p.Files, func(f *File, _ int) any { return f.MarshalJavaScript() }) + + return js.ValueOf(map[string]any{ + "name": p.Name, + "type": p.Type, + "size": p.Size, + "symbols": symbols, + "subPackages": p.SubPackages.MarshalJavaScript(), + "files": files, + }) +} diff --git a/internal/entity/section_js_wasm.go b/internal/entity/section_js_wasm.go new file mode 100644 index 0000000000..4ec3c40546 --- /dev/null +++ b/internal/entity/section_js_wasm.go @@ -0,0 +1,22 @@ +//go:build js && wasm + +package entity + +import ( + "syscall/js" +) + +func (s Section) MarshalJavaScript() js.Value { + return js.ValueOf(map[string]any{ + "name": s.Name, + "size": s.Size, + "file_size": s.FileSize, + "known_size": s.KnownSize, + "offset": s.Offset, + "end": s.End, + "addr": s.Addr, + "addr_end": s.AddrEnd, + "only_in_memory": s.OnlyInMemory, + "debug": s.Debug, + }) +} diff --git a/internal/entity/symbol_js_wasm.go b/internal/entity/symbol_js_wasm.go new file mode 100644 index 0000000000..a73ce433bc --- /dev/null +++ b/internal/entity/symbol_js_wasm.go @@ -0,0 +1,16 @@ +//go:build js && wasm + +package entity + +import ( + "syscall/js" +) + +func (s *Symbol) MarshalJavaScript() js.Value { + return js.ValueOf(map[string]any{ + "name": s.Name, + "addr": s.Addr, + "size": s.Size, + "type": s.Type, + }) +} diff --git a/internal/printer/wasm/obj_js_wasm.go b/internal/printer/wasm/obj_js_wasm.go new file mode 100644 index 0000000000..54cda397a2 --- /dev/null +++ b/internal/printer/wasm/obj_js_wasm.go @@ -0,0 +1,13 @@ +//go:build js && wasm + +package wasm + +import ( + "syscall/js" + + "github.com/Zxilly/go-size-analyzer/internal/result" +) + +func JavaScript(r *result.Result) js.Value { + return r.MarshalJavaScript() +} diff --git a/internal/result/result_js_wasm.go b/internal/result/result_js_wasm.go new file mode 100644 index 0000000000..6cbd7654b5 --- /dev/null +++ b/internal/result/result_js_wasm.go @@ -0,0 +1,25 @@ +//go:build js && wasm + +package result + +import ( + "syscall/js" + + "github.com/samber/lo" + + "github.com/Zxilly/go-size-analyzer/internal/entity" +) + +func (r *Result) MarshalJavaScript() js.Value { + var sections []any + sections = lo.Map(r.Sections, func(s *entity.Section, _ int) any { + return s.MarshalJavaScript() + }) + + return js.ValueOf(map[string]any{ + "name": r.Name, + "size": r.Size, + "packages": r.Packages.MarshalJavaScript(), + "sections": sections, + }) +} diff --git a/scripts/wasm.py b/scripts/wasm.py index 92fc628e7f..e6d3fe2b65 100644 --- a/scripts/wasm.py +++ b/scripts/wasm.py @@ -45,7 +45,7 @@ def require_binaryen(): "build", "-trimpath", "-o", tmp_file.name, - "./cmd/wasm/main_wasm.go" + "./cmd/wasm/main_js_wasm.go" ], text=True, cwd=get_project_root(), diff --git a/ui/src/explorer/app.tsx b/ui/src/explorer/app.tsx index 2423168298..4e4efe1a0a 100644 --- a/ui/src/explorer/app.tsx +++ b/ui/src/explorer/app.tsx @@ -2,7 +2,6 @@ import React, {ReactNode, useEffect, useMemo} from "react"; import {useAsync} from "react-use"; import gsa from "../../gsa.wasm?init"; import {Entry} from "../tool/entry.ts"; -import {loadDataFromWasmResult} from "../tool/utils.ts"; import {Dialog, DialogContent, DialogContentText, DialogTitle} from "@mui/material"; import {FileSelector} from "./file_selector.tsx"; import TreeMap from "../TreeMap.tsx"; @@ -15,6 +14,8 @@ type ModalState = { content: ReactNode } +declare function gsa_analyze(name: string, data: Uint8Array): import("../generated/schema.ts").Result; + export const App: React.FC = () => { const go = useMemo(() => new Go(), []) @@ -34,7 +35,7 @@ export const App: React.FC = () => { const [modalState, setModalState] = React.useState({isOpen: false}) - const {value: jsonResult, loading: analyzing} = useAsync(async () => { + const {value: result, loading: analyzing} = useAsync(async () => { if (!file) { return } @@ -42,16 +43,18 @@ export const App: React.FC = () => { const bytes = await file.arrayBuffer() const uint8 = new Uint8Array(bytes) - return gsa_analyze(file.name, uint8) + const r = gsa_analyze(file.name, uint8) + console.log(r) + return r }, [file]) const entry = useMemo(() => { - if (!jsonResult) { + if (!result) { return null } - return new Entry(loadDataFromWasmResult(jsonResult)) - }, [jsonResult]) + return new Entry(result) + }, [result]) useEffect(() => { if (loadError) { @@ -84,7 +87,7 @@ export const App: React.FC = () => { title: "Analyzing", content: Analyzing binary... }) - } else if (!analyzing && !jsonResult && !entry) { + } else if (!analyzing && !result && !entry) { setModalState({ isOpen: true, title: "Error", @@ -95,7 +98,7 @@ export const App: React.FC = () => { } else { setModalState({isOpen: false}) } - }, [loadError, loading, file, jsonResult, analyzing, inst, entry]) + }, [loadError, loading, file, result, analyzing, inst, entry]) return <> The selected binary {pendingFile?.name} has a size of {formatBytes(pendingFile?.size || 0)}. - It is not recommended to use the wasm version for binary files larger than 30MB. + It is not recommended to use the wasm version for binary files larger than 30 MB. diff --git a/ui/src/explorer/gsa.d.ts b/ui/src/explorer/gsa.d.ts deleted file mode 100644 index 0a74c63b03..0000000000 --- a/ui/src/explorer/gsa.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare function gsa_analyze(name: string, data: Uint8Array): string; \ No newline at end of file diff --git a/ui/src/tool/utils.ts b/ui/src/tool/utils.ts index 86adf46583..a3f0326394 100644 --- a/ui/src/tool/utils.ts +++ b/ui/src/tool/utils.ts @@ -1,6 +1,5 @@ import {Result} from "../schema/schema.ts"; import {parseResult} from "../generated/schema.ts"; -import {useCallback, useRef} from 'react'; export function loadDataFromEmbed(): Result { const doc = document.querySelector("#data")!; @@ -11,15 +10,6 @@ export function loadDataFromEmbed(): Result { return ret; } -export function loadDataFromWasmResult(data: string): Result { - const ret = parseResult(data); - if (ret === null) { - throw new Error("Failed to parse data"); - } - return ret; -} - - export function formatBytes(bytes: number) { if (bytes == 0) return '0 B'; const k = 1024, @@ -40,19 +30,3 @@ export function trimPrefix(str: string, prefix: string) { return str } } - -export function useThrottle) => ReturnType>(func: T, delay: number): (...args: Parameters) => void { - const lastCall = useRef(0); - const lastFunc = useRef(func); - - lastFunc.current = func; - - return useCallback((...args: Parameters) => { - const now = Date.now(); - - if (now - lastCall.current >= delay) { - lastCall.current = now; - lastFunc.current(...args); - } - }, [delay]); -}