├── internal ├── isa │ ├── prop │ │ └── dummy.go │ ├── program │ │ ├── dummy.go │ │ ├── amd64.go │ │ └── arm64.go │ ├── reglayout │ │ ├── dummy.go │ │ ├── arm64.go │ │ └── amd64.go │ ├── amd64 │ │ ├── memorycopy.bin │ │ ├── memoryfill.bin │ │ ├── in │ │ │ ├── bit.go │ │ │ ├── round.go │ │ │ ├── nodebug.go │ │ │ ├── sib_test.go │ │ │ ├── imm.go │ │ │ ├── assume.go │ │ │ ├── sib.go │ │ │ ├── mod.go │ │ │ ├── rex.go │ │ │ ├── mod_test.go │ │ │ ├── rex_test.go │ │ │ ├── debug.go │ │ │ └── imm_test.go │ │ ├── nonabi │ │ │ └── retpoline.go │ │ ├── features_disabled.go │ │ ├── features_detected.go │ │ ├── memoryfill.S │ │ ├── regs.go │ │ ├── memorycopy.S │ │ ├── routines.go │ │ ├── linker.go │ │ ├── select.go │ │ └── unary.go │ └── arm64 │ │ ├── memorycopy.bin │ │ ├── memoryfill.bin │ │ ├── in │ │ ├── assume.go │ │ └── alias.go │ │ ├── nop_test.go │ │ ├── outbuf.go │ │ ├── regs.go │ │ ├── memoryfill.S │ │ ├── asm_test.s │ │ ├── memorycopy.S │ │ ├── routines.go │ │ ├── unary.go │ │ ├── imm.go │ │ ├── select.go │ │ ├── convert.go │ │ ├── memory.go │ │ └── linker.go ├── gen │ ├── atomic │ │ ├── atomic.go │ │ ├── atomic_arm64.s │ │ └── atomic_amd64.s │ ├── debug │ │ ├── nodebug.go │ │ └── debug.go │ ├── codegen │ │ ├── amd64.go │ │ ├── arm64.go │ │ ├── isa_test.go │ │ ├── breakpoint.go │ │ ├── global.go │ │ └── local.go │ ├── reg │ │ └── reg.go │ ├── storage │ │ └── storage.go │ ├── debugger.go │ ├── link │ │ └── link.go │ ├── rodata │ │ └── rodata.go │ ├── prog.go │ ├── func_test.go │ ├── condition │ │ └── condition.go │ ├── regalloc │ │ ├── regalloc_test.go │ │ └── regalloc.go │ ├── func.go │ └── operand │ │ └── operand.go ├── data │ └── databuffer.go ├── module │ ├── error.go │ └── module.go ├── panic.go ├── count │ └── countreader.go ├── code │ └── codebuffer.go ├── errors │ └── errors.go ├── typedecode │ └── typedecode.go ├── test │ └── library │ │ └── testlibrary.go ├── pan │ └── pan.go ├── obj │ └── obj.go ├── event │ └── event.go ├── initexpr │ └── initexpr.go ├── datalayout │ └── datalayout.go ├── section │ └── section.go └── loader │ └── loader.go ├── testdata ├── hello.wasm ├── rust │ ├── test.wasm │ └── test.rs └── hello.c ├── testsuite ├── testdata │ ├── library.wasm │ ├── specdata.py │ ├── library.go │ └── library │ │ ├── compile.sh │ │ └── library.c ├── main_test.go ├── go.mod ├── service.go ├── go.sum ├── resolver.go ├── runtime.go └── wa.go ├── .gitignore ├── .gitmodules ├── staticcheck.conf ├── object ├── doc.go ├── debug │ ├── doc.go │ ├── debugmapper_test.go │ ├── dump │ │ ├── nop.go │ │ ├── arm64.go │ │ └── text.go │ └── insnmap.go ├── abi │ └── object.go ├── stack │ ├── entry.go │ ├── stacktrace.go │ └── stacktrace │ │ └── printstack.go ├── callmap_test.go ├── funcmap.go └── callmap.go ├── go.mod ├── compile ├── version.go ├── table.go └── benchmark_test.go ├── wa ├── page.go ├── opcode │ └── opcode.go ├── globaltype.go ├── type.go └── functype.go ├── buffer ├── sizeerror.go ├── limited.go ├── static.go └── dynamic.go ├── go.sum ├── section ├── sectionid.go ├── sectionmap.go ├── custom.go ├── copy.go └── names.go ├── lib_test.go ├── fuzz_test.go ├── binding ├── export.go └── bind.go ├── LICENSE ├── trap └── trapid.go ├── errors └── errors.go ├── Makefile ├── section_test.go └── README.md /internal/isa/prop/dummy.go: -------------------------------------------------------------------------------- 1 | package prop 2 | -------------------------------------------------------------------------------- /internal/isa/program/dummy.go: -------------------------------------------------------------------------------- 1 | package program 2 | -------------------------------------------------------------------------------- /internal/isa/reglayout/dummy.go: -------------------------------------------------------------------------------- 1 | package reglayout 2 | -------------------------------------------------------------------------------- /testdata/hello.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gate-computer/wag/HEAD/testdata/hello.wasm -------------------------------------------------------------------------------- /testdata/rust/test.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gate-computer/wag/HEAD/testdata/rust/test.wasm -------------------------------------------------------------------------------- /testsuite/testdata/library.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gate-computer/wag/HEAD/testsuite/testdata/library.wasm -------------------------------------------------------------------------------- /internal/isa/amd64/memorycopy.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gate-computer/wag/HEAD/internal/isa/amd64/memorycopy.bin -------------------------------------------------------------------------------- /internal/isa/amd64/memoryfill.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gate-computer/wag/HEAD/internal/isa/amd64/memoryfill.bin -------------------------------------------------------------------------------- /internal/isa/arm64/memorycopy.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gate-computer/wag/HEAD/internal/isa/arm64/memorycopy.bin -------------------------------------------------------------------------------- /internal/isa/arm64/memoryfill.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gate-computer/wag/HEAD/internal/isa/arm64/memoryfill.bin -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.elf 2 | *.o 3 | bench-new.txt 4 | bench-old.txt 5 | config.mk 6 | testsuite/testdata/include 7 | testsuite/testdata/specdata 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "testsuite/testdata/spec"] 2 | path = testsuite/testdata/spec 3 | url = https://github.com/WebAssembly/spec.git 4 | -------------------------------------------------------------------------------- /staticcheck.conf: -------------------------------------------------------------------------------- 1 | checks = [ 2 | "all", 3 | 4 | "-ST1000", 5 | "-ST1003", 6 | "-ST1016", 7 | "-ST1020", 8 | "-ST1021", 9 | "-ST1022", 10 | ] 11 | -------------------------------------------------------------------------------- /internal/gen/atomic/atomic.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package atomic 6 | 7 | func PutUint32(b []byte, val uint32) 8 | -------------------------------------------------------------------------------- /object/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package object contains ObjectMapper implementations. 6 | package object 7 | -------------------------------------------------------------------------------- /object/debug/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package debug contains DebugObjectMapper implementations. 6 | package debug 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gate.computer/wag 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca 7 | github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e 8 | golang.org/x/sys v0.25.0 9 | import.name/pan v0.3.0 10 | ) 11 | -------------------------------------------------------------------------------- /compile/version.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package compile 6 | 7 | // ABI version of generated machine code. 8 | const ObjectVersion = 1 9 | -------------------------------------------------------------------------------- /internal/data/databuffer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package data 6 | 7 | type Buffer interface { 8 | Bytes() []byte 9 | ResizeBytes(n int) []byte 10 | } 11 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/bit.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | func bit(b bool) (i uint32) { 8 | if b { 9 | i = 1 10 | } 11 | return 12 | } 13 | -------------------------------------------------------------------------------- /internal/isa/amd64/nonabi/retpoline.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package nonabi 6 | 7 | const ( 8 | TextAddrRetpoline = 0x60 9 | TextAddrRetpolineSetup = 0x70 10 | ) 11 | -------------------------------------------------------------------------------- /internal/isa/arm64/in/assume.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | import ( 8 | "gate.computer/wag/internal/gen/reg" 9 | ) 10 | 11 | const ( 12 | RegFakeSP = reg.R(29) 13 | ) 14 | -------------------------------------------------------------------------------- /wa/page.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package wa contains miscellaneous WebAssembly-related things. 6 | package wa 7 | 8 | const ( 9 | PageBits = 16 10 | PageSize = 1 << PageBits 11 | ) 12 | -------------------------------------------------------------------------------- /internal/isa/amd64/features_disabled.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build wagamd64 6 | 7 | package amd64 8 | 9 | const ( 10 | haveLZCNT = false 11 | havePOPCNT = false 12 | haveTZCNT = false 13 | ) 14 | -------------------------------------------------------------------------------- /internal/isa/arm64/nop_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build arm64 && !cgo 6 | 7 | package arm64 8 | 9 | import ( 10 | "testing" 11 | ) 12 | 13 | func Test(t *testing.T) { 14 | t.Skip("tests require cgo") 15 | } 16 | -------------------------------------------------------------------------------- /internal/isa/program/amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (amd64 || wagamd64) && !wagarm64 6 | 7 | package program 8 | 9 | const ( 10 | NumTrapLinkRewindSuspended = 2 11 | NumTrapLinkTruncOverflow = 4 12 | ) 13 | -------------------------------------------------------------------------------- /internal/isa/program/arm64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package program 8 | 9 | const ( 10 | NumTrapLinkRewindSuspended = 1 11 | NumTrapLinkTruncOverflow = 0 12 | ) 13 | -------------------------------------------------------------------------------- /internal/gen/atomic/atomic_arm64.s: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "textflag.h" 6 | 7 | // PutUint32(b []byte, val uint32) 8 | TEXT ·PutUint32(SB),NOSPLIT,$0-28 9 | MOVD b+0(FP), R1 10 | MOVW val+24(FP), R0 11 | STLRW R0, (R1) 12 | RET 13 | -------------------------------------------------------------------------------- /internal/gen/atomic/atomic_amd64.s: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "textflag.h" 6 | 7 | // func PutUint32(b []byte, val uint32) 8 | TEXT ·PutUint32(SB),NOSPLIT,$0-28 9 | MOVQ b+0(FP), BX 10 | MOVL val+24(FP), AX 11 | XCHGL AX, (BX) 12 | RET 13 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/round.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | const ( 8 | // Immediate values for ROUNDSSD 9 | RoundModeNearest = 0x0 10 | RoundModeFloor = 0x1 11 | RoundModeCeil = 0x2 12 | RoundModeTrunc = 0x3 13 | ) 14 | -------------------------------------------------------------------------------- /testsuite/testdata/specdata.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from os import environ, makedirs 3 | from os.path import join 4 | from subprocess import run 5 | 6 | wast2json = environ.get("WAST2JSON", "wast2json") 7 | 8 | makedirs("specdata", exist_ok=True) 9 | 10 | for filename in glob("spec/test/core/*.wast"): 11 | run([wast2json, "--debug-names", join("..", filename)], cwd="specdata", check=True) 12 | -------------------------------------------------------------------------------- /object/debug/debugmapper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package debug 6 | 7 | import ( 8 | "testing" 9 | 10 | "gate.computer/wag/internal/obj" 11 | ) 12 | 13 | func TestInsnMap(*testing.T) { 14 | var _ obj.DebugObjectMapper = new(InsnMap) 15 | } 16 | -------------------------------------------------------------------------------- /testsuite/testdata/library.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | //go:generate go run gate.computer/cmd/gate-resource -d include include/rt.h 8 | //go:generate go run gate.computer/cmd/gate-librarian library.wasm -- library/compile.sh -c -o /dev/stdout 9 | -------------------------------------------------------------------------------- /internal/gen/debug/nodebug.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !(debug || gendebug) 6 | 7 | package debug 8 | 9 | const Enabled = false 10 | 11 | var Depth int 12 | 13 | func Printf(string, ...any) { 14 | panic("debug.Printf called without debug.Enabled") 15 | } 16 | -------------------------------------------------------------------------------- /internal/gen/codegen/amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (amd64 || wagamd64) && !wagarm64 6 | 7 | package codegen 8 | 9 | import ( 10 | "gate.computer/wag/internal/isa/amd64" 11 | ) 12 | 13 | var ( 14 | asm amd64.MacroAssembler 15 | linker amd64.Linker 16 | ) 17 | -------------------------------------------------------------------------------- /internal/gen/codegen/arm64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package codegen 8 | 9 | import ( 10 | "gate.computer/wag/internal/isa/arm64" 11 | ) 12 | 13 | var ( 14 | asm arm64.MacroAssembler 15 | linker arm64.Linker 16 | ) 17 | -------------------------------------------------------------------------------- /testsuite/testdata/library/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | library=$(dirname "$0") 4 | testdata="${library}/.." 5 | 6 | set -x 7 | 8 | exec ${WASM_CC:-${CC:-clang}} \ 9 | --target=wasm32 \ 10 | -std=c11 \ 11 | -Os \ 12 | -finline-functions \ 13 | -fomit-frame-pointer \ 14 | -Wall \ 15 | -Wextra \ 16 | -Wno-unused-parameter \ 17 | -nostdlib \ 18 | -I"${testdata}/include" \ 19 | $@ \ 20 | "${library}/library.c" 21 | -------------------------------------------------------------------------------- /internal/isa/reglayout/arm64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package reglayout 8 | 9 | const ( 10 | AllocIntFirst = 2 11 | AllocIntLast = 25 12 | 13 | AllocFloatFirst = 2 14 | AllocFloatLast = 31 15 | 16 | Radix = 32 17 | ) 18 | -------------------------------------------------------------------------------- /internal/gen/codegen/isa_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package codegen 6 | 7 | import ( 8 | "testing" 9 | 10 | interfaces "gate.computer/wag/internal/isa" 11 | ) 12 | 13 | func TestISAInterfaces(*testing.T) { 14 | var _ interfaces.MacroAssembler = asm 15 | var _ interfaces.Linker = linker 16 | } 17 | -------------------------------------------------------------------------------- /internal/gen/reg/reg.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package reg 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | type R byte 12 | 13 | func (r R) String() string { 14 | return fmt.Sprintf("r%d", r) 15 | } 16 | 17 | const ( 18 | Result = R(0) 19 | ScratchISA = R(1) // for internal ISA implementation use 20 | ) 21 | -------------------------------------------------------------------------------- /internal/isa/reglayout/amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (amd64 || wagamd64) && !wagarm64 6 | 7 | package reglayout 8 | 9 | const ( 10 | AllocIntFirst = 5 // rbp 11 | AllocIntLast = 13 // r13 12 | 13 | AllocFloatFirst = 2 // xmm2 14 | AllocFloatLast = 15 // xmm15 15 | 16 | Radix = 16 17 | ) 18 | -------------------------------------------------------------------------------- /internal/isa/amd64/features_detected.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build amd64 && !wagamd64 6 | 7 | package amd64 8 | 9 | import ( 10 | "golang.org/x/sys/cpu" 11 | ) 12 | 13 | var ( 14 | haveLZCNT = cpu.X86.HasBMI1 && cpu.X86.HasPOPCNT // Intel && AMD 15 | havePOPCNT = cpu.X86.HasPOPCNT 16 | haveTZCNT = cpu.X86.HasBMI1 17 | ) 18 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/nodebug.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !((debug || indebug) && cgo) 6 | 7 | package in 8 | 9 | var debugPrinted bool 10 | 11 | func debugPrintInsn([]byte) { 12 | if !debugPrinted { 13 | println("wag/internal/isa/amd64/in: debugPrintIn called in non-debug build") 14 | debugPrinted = true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /object/debug/dump/nop.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !cgo 6 | 7 | package dump 8 | 9 | import ( 10 | "errors" 11 | "io" 12 | 13 | "gate.computer/wag/section" 14 | ) 15 | 16 | func Text(w io.Writer, text []byte, textAddr uintptr, funcAddrs []uint32, ns *section.NameSection) error { 17 | return errors.New("object/debug/dump.Text requires cgo") 18 | } 19 | -------------------------------------------------------------------------------- /object/abi/object.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package abi 6 | 7 | const ( 8 | TextAddrNoFunction = 0x00 // NoFunction trap handler. 9 | TextAddrExit = 0x10 // Exit routine. 10 | TextAddrResume = 0x20 // Return from import function or trap handler. 11 | TextAddrEnter = 0x30 // Call start and entry functions, and exit. 12 | TextAddrMask = 0xff 13 | ) 14 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/sib_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | import ( 8 | "testing" 9 | 10 | "gate.computer/wag/wa" 11 | ) 12 | 13 | func TestTypeScale(t *testing.T) { 14 | if s := TypeScale(wa.I32); s != Scale2 { 15 | t.Errorf("TypeScale(wa.I32) = 0x%x", s) 16 | } 17 | if s := TypeScale(wa.I64); s != Scale3 { 18 | t.Errorf("TypeScale(wa.I64) = 0x%x", s) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /testsuite/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "testing" 11 | 12 | "gate.computer/wag/internal/gen/codegen" 13 | ) 14 | 15 | func init() { 16 | codegen.UnsupportedOpBreakpoint = true 17 | } 18 | 19 | func TestMain(m *testing.M) { 20 | if err := generateSpecData(); err != nil { 21 | fmt.Fprintln(os.Stderr, err) 22 | os.Exit(1) 23 | } 24 | 25 | os.Exit(m.Run()) 26 | } 27 | -------------------------------------------------------------------------------- /internal/gen/debug/debug.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build debug || gendebug 6 | 7 | package debug 8 | 9 | import ( 10 | "fmt" 11 | ) 12 | 13 | const Enabled = true 14 | 15 | var Depth int 16 | 17 | func Printf(format string, args ...any) { 18 | if Depth < 0 { 19 | panic("negative DebugDepth") 20 | } 21 | 22 | for i := 0; i < Depth; i++ { 23 | print(" ") 24 | } 25 | 26 | print(fmt.Sprintf(format+"\n", args...)) 27 | } 28 | -------------------------------------------------------------------------------- /internal/module/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package module 6 | 7 | import ( 8 | internal "gate.computer/wag/internal/errors" 9 | ) 10 | 11 | func Error(text string) error { 12 | return internal.ModuleError(text) 13 | } 14 | 15 | func Errorf(format string, args ...any) error { 16 | return internal.ModuleErrorf(format, args...) 17 | } 18 | 19 | func WrapError(cause error, text string) error { 20 | return internal.WrapModuleError(cause, text) 21 | } 22 | -------------------------------------------------------------------------------- /testdata/rust/test.rs: -------------------------------------------------------------------------------- 1 | // rustc --target=wasm32-unknown-unknown --crate-type=cdylib -C opt-level=z -o test.wasm test.rs 2 | 3 | #![no_std] 4 | 5 | extern "C" { 6 | fn write(fd: i32, data: *const u8, size: usize); 7 | fn _exit(status: i32) -> !; 8 | } 9 | 10 | #[no_mangle] 11 | pub fn main() { 12 | let s = "hello, world\n"; 13 | unsafe { write(1, s.as_ptr(), s.len()) } 14 | } 15 | 16 | #[panic_handler] 17 | pub fn panic_fmt(_info: &::core::panic::PanicInfo) -> ! { 18 | let s = "panic\n"; 19 | unsafe { 20 | write(0, s.as_ptr(), s.len()); 21 | _exit(99) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/imm.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | func immSize(val int32) uint32 { 8 | var ( 9 | bit32 = bit(uint32(val+128) > 255) 10 | scale = bit32 << 1 11 | ) 12 | 13 | return 1 << scale 14 | } 15 | 16 | func immOpcodeSize(ops uint16, val int32) (op byte, size uint32) { 17 | var ( 18 | bit32 = bit(uint32(val+128) > 255) 19 | opPos = bit32 << 3 20 | scale = bit32 << 1 21 | ) 22 | 23 | op = byte(ops >> opPos) 24 | size = 1 << scale 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /object/stack/entry.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package stack provides runtime call stack manipulation functions. 6 | package stack 7 | 8 | import ( 9 | "encoding/binary" 10 | 11 | "gate.computer/wag/internal/obj" 12 | ) 13 | 14 | func InitFrame(startFuncAddr, entryFuncAddr uint32) (frame []byte) { 15 | frame = make([]byte, obj.Word*2) 16 | binary.LittleEndian.PutUint64(frame[0:], uint64(startFuncAddr)) 17 | binary.LittleEndian.PutUint64(frame[8:], uint64(entryFuncAddr)) 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /internal/panic.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package internal 6 | 7 | // Panic configures public wag API behavior. If this is changed to "1", API 8 | // functions will panic instead of returning error values. The stack traces 9 | // can be helpful for debugging. 10 | // 11 | // This can be set during linking: 12 | // 13 | // go build -ldflags="-X gate.computer/wag/internal.Panic=1" 14 | // 15 | // This is not a stable feature: it may change or disappear at any time. 16 | var Panic string 17 | 18 | func DontPanic() bool { 19 | return Panic == "" 20 | } 21 | -------------------------------------------------------------------------------- /internal/gen/storage/storage.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package storage 6 | 7 | type Storage uint8 8 | 9 | const ( 10 | Imm = Storage(iota) 11 | Stack 12 | Reg 13 | Flags 14 | Unreachable 15 | ) 16 | 17 | func (s Storage) String() string { 18 | switch s { 19 | case Imm: 20 | return "immediate" 21 | 22 | case Stack: 23 | return "stack" 24 | 25 | case Reg: 26 | return "register" 27 | 28 | case Flags: 29 | return "flags" 30 | 31 | case Unreachable: 32 | return "unreachable" 33 | 34 | default: 35 | return "" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /buffer/sizeerror.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package buffer implements compile.CodeBuffer and compile.DataBuffer. 6 | package buffer 7 | 8 | // ErrSizeLimit (or its wrapper) is propagated by buffer methods by panicking, 9 | // or returned by compiler functions when used with panicking buffer 10 | // implementations. 11 | var ErrSizeLimit error = err{} 12 | 13 | type err struct{} 14 | 15 | func (err) Error() string { return "buffer size limit exceeded" } 16 | func (err) PublicError() string { return "buffer size limit exceeded" } 17 | func (err) ResourceLimit() bool { return true } 18 | -------------------------------------------------------------------------------- /internal/isa/arm64/outbuf.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package arm64 8 | 9 | import ( 10 | "encoding/binary" 11 | 12 | "gate.computer/wag/internal/code" 13 | ) 14 | 15 | type outbuf struct { 16 | buf [128]byte 17 | size int 18 | } 19 | 20 | func (o outbuf) copy(dest []byte) { 21 | copy(dest, o.buf[:]) 22 | } 23 | 24 | func (o outbuf) addr(text *code.Buf) int32 { 25 | return text.Addr + int32(o.size) 26 | } 27 | 28 | func (o *outbuf) insn(i uint32) { 29 | binary.LittleEndian.PutUint32(o.buf[o.size:], i) 30 | o.size += 4 31 | } 32 | -------------------------------------------------------------------------------- /wa/opcode/opcode.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package opcode enumerates WebAssembly instructions. 6 | package opcode 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | type Opcode byte 13 | 14 | func (op Opcode) String() (s string) { 15 | s = strings[op] 16 | if s == "" { 17 | s = fmt.Sprintf("0x%02x", byte(op)) 18 | } 19 | return 20 | } 21 | 22 | type MiscOpcode uint32 23 | 24 | func (op MiscOpcode) String() (s string) { 25 | if uint32(op) >= uint32(len(miscStrings)) { 26 | return fmt.Sprintf("0x%02x 0x%02x", byte(MiscPrefix), uint32(op)) 27 | } 28 | return miscStrings[op] 29 | } 30 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/assume.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | import ( 8 | "gate.computer/wag/internal/gen/reg" 9 | ) 10 | 11 | const ( 12 | RegResult = reg.Result 13 | RegScratch = reg.ScratchISA 14 | RegZero = reg.R(2) 15 | RegStackLimit = reg.R(3) 16 | RegStack = reg.R(4) 17 | RegMemoryBase = reg.R(14) 18 | RegTextBase = reg.R(15) 19 | ) 20 | 21 | type BaseReg reg.R 22 | 23 | const ( 24 | BaseScratch = BaseReg(RegScratch) 25 | BaseZero = BaseReg(RegZero) 26 | BaseMemory = BaseReg(RegMemoryBase) 27 | BaseText = BaseReg(RegTextBase) 28 | ) 29 | -------------------------------------------------------------------------------- /object/callmap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package object 6 | 7 | import ( 8 | "testing" 9 | "unsafe" 10 | ) 11 | 12 | func TestCallSite(*testing.T) { 13 | var x CallSite 14 | 15 | if unsafe.Sizeof(x) != 8 { 16 | panic("CallSite has wrong size") 17 | } 18 | 19 | if unsafe.Offsetof(x.RetAddr) != 0 { 20 | panic("CallSite.RetAddr is at wrong offset") 21 | } 22 | 23 | if unsafe.Offsetof(x.StackOffset) != 4 { 24 | panic("CallSite.RetAddr is at wrong offset") 25 | } 26 | 27 | var array [1]CallSite 28 | 29 | if unsafe.Sizeof(array) != 8 { 30 | panic("CallSite array has wrong size") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /internal/gen/debugger.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gen 6 | 7 | import ( 8 | "gate.computer/wag/internal/loader" 9 | ) 10 | 11 | // Breakpoint information, for debugger support. 12 | type Breakpoint struct { 13 | Set bool // Set by the compiler if it implemented the breakpoint. 14 | } 15 | 16 | type Debugger struct { 17 | // Breakpoints are WebAssembly code offsets. They can be obtained from 18 | // DWARF debug info. 19 | Breakpoints map[uint32]Breakpoint 20 | 21 | CodeOffset int64 22 | } 23 | 24 | func (d *Debugger) SourceAddr(load *loader.L) uint32 { 25 | return uint32(load.Tell() - d.CodeOffset) 26 | } 27 | -------------------------------------------------------------------------------- /wa/globaltype.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wa 6 | 7 | type GlobalType byte 8 | 9 | func MakeGlobalType(t Type, mutable bool) (g GlobalType) { 10 | g = GlobalType(t) 11 | if mutable { 12 | g |= 0x80 13 | } 14 | return 15 | } 16 | 17 | func (g GlobalType) Type() Type { return Type(g & 0x7f) } 18 | func (g GlobalType) Mutable() bool { return g&0x80 != 0 } 19 | 20 | // Encode as WebAssembly. Result is undefined if GlobalType representation is 21 | // not valid. 22 | func (g GlobalType) Encode() (buf [2]byte) { 23 | buf[0] = g.Type().Encode() 24 | if g.Mutable() { 25 | buf[1] = 1 26 | } 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /testsuite/go.mod: -------------------------------------------------------------------------------- 1 | module gate.computer/wag/testsuite 2 | 3 | go 1.23 4 | 5 | require ( 6 | gate.computer v0.0.0-20240908135418-e8a041e28a98 7 | gate.computer/wag v0.36.0 8 | ) 9 | 10 | require ( 11 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 12 | github.com/godbus/dbus/v5 v5.1.0 // indirect 13 | github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e // indirect 14 | golang.org/x/sys v0.25.0 // indirect 15 | google.golang.org/protobuf v1.34.2 // indirect 16 | import.name/lock v0.0.0-20211205191324-f24933776f0b // indirect 17 | import.name/pan v0.3.0 // indirect 18 | kernel.org/pub/linux/libs/security/libcap/cap v1.2.66 // indirect 19 | kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 // indirect 20 | ) 21 | 22 | replace gate.computer/wag => ../ 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca h1:T54Ema1DU8ngI+aef9ZhAhNGQhcRTrWxVeG07F+c/Rw= 2 | github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= 3 | github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e h1:6J5obSn9umEThiYzWzndcPOZR0Qj/sVCZpH6V1G7yNE= 4 | github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e/go.mod h1:1K5hEzsMBLTPdRJKEHqBFJ8Zt2VRqDhomcQ11KH0WW4= 5 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 6 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 7 | import.name/pan v0.3.0 h1:AOyiNu/zWV3BHyDspOkpdzdWdBrlb87+LGH4fSwy3k4= 8 | import.name/pan v0.3.0/go.mod h1:jzAC3o4KYbMoLWPgtxfDXjtZxa+JikGUdbQGgaDqf7U= 9 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/sib.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | import ( 8 | "gate.computer/wag/internal/gen/reg" 9 | "gate.computer/wag/wa" 10 | ) 11 | 12 | type ( 13 | Scale byte 14 | Index byte 15 | Base byte 16 | ) 17 | 18 | const ( 19 | Scale0 = Scale(0 << 6) 20 | Scale1 = Scale(1 << 6) 21 | Scale2 = Scale(2 << 6) 22 | Scale3 = Scale(3 << 6) 23 | 24 | noIndex = Index(4 << 3) 25 | 26 | baseStack = Base(RegStack) 27 | ) 28 | 29 | func TypeScale(t wa.Type) Scale { return Scale(t.Size()>>3|2) << 6 } // Scale2 or Scale3 30 | func regIndex(r reg.R) Index { return Index((r & 7) << 3) } 31 | func regBase(r reg.R) Base { return Base(r & 7) } 32 | -------------------------------------------------------------------------------- /internal/count/countreader.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package count 6 | 7 | import ( 8 | "gate.computer/wag/binary" 9 | ) 10 | 11 | type Reader struct { 12 | R binary.Reader 13 | N uint32 14 | } 15 | 16 | func (r *Reader) Read(b []byte) (n int, err error) { 17 | n, err = r.R.Read(b) 18 | r.N += uint32(n) 19 | return 20 | } 21 | 22 | func (r *Reader) ReadByte() (b byte, err error) { 23 | b, err = r.R.ReadByte() 24 | if err == nil { 25 | r.N++ 26 | } 27 | return 28 | } 29 | 30 | func (r *Reader) UnreadByte() (err error) { 31 | err = r.R.UnreadByte() 32 | if err == nil { 33 | r.N-- 34 | } 35 | return 36 | } 37 | 38 | func (r *Reader) Tell() int64 { 39 | return int64(r.N) 40 | } 41 | -------------------------------------------------------------------------------- /section/sectionid.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package section contains binary stream manipulation utilities. 6 | package section 7 | 8 | import ( 9 | "gate.computer/wag/internal/module" 10 | ) 11 | 12 | type ID = module.SectionID 13 | 14 | const ( 15 | Custom = module.SectionCustom 16 | Type = module.SectionType 17 | Import = module.SectionImport 18 | Function = module.SectionFunction 19 | Table = module.SectionTable 20 | Memory = module.SectionMemory 21 | Global = module.SectionGlobal 22 | Export = module.SectionExport 23 | Start = module.SectionStart 24 | Element = module.SectionElement 25 | Code = module.SectionCode 26 | Data = module.SectionData 27 | ) 28 | -------------------------------------------------------------------------------- /internal/code/codebuffer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package code 6 | 7 | type Buffer interface { 8 | Bytes() []byte 9 | Extend(n int) []byte 10 | PutByte(byte) 11 | PutUint32(uint32) // Little-endian byte order. 12 | } 13 | 14 | // Buf is an optimized Buffer. The cached length (Addr) avoids interface 15 | // function calls. 16 | type Buf struct { 17 | Buffer 18 | Addr int32 19 | } 20 | 21 | func (buf *Buf) Extend(n int) (b []byte) { 22 | b = buf.Buffer.Extend(n) 23 | buf.Addr += int32(n) 24 | return 25 | } 26 | 27 | func (buf *Buf) PutByte(x byte) { 28 | buf.Buffer.PutByte(x) 29 | buf.Addr++ 30 | } 31 | 32 | func (buf *Buf) PutUint32(x uint32) { 33 | buf.Buffer.PutUint32(x) 34 | buf.Addr += 4 35 | } 36 | -------------------------------------------------------------------------------- /lib_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wag 6 | 7 | import ( 8 | "gate.computer/wag/binding" 9 | "gate.computer/wag/compile" 10 | "gate.computer/wag/internal/loader" 11 | "gate.computer/wag/internal/test/library" 12 | ) 13 | 14 | var testlib = *library.Load("testsuite/testdata/library.wasm", true, func(load *loader.L) library.L { 15 | mod, err := compile.LoadInitialSections(nil, load) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | lib, err := mod.AsLibrary() 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | for i := 0; i < lib.NumImportFuncs(); i++ { 26 | lib.SetImportFunc(i, binding.VectorIndexLastImport-i) // Dummy values. 27 | } 28 | 29 | return &lib 30 | }).(*compile.Library) 31 | -------------------------------------------------------------------------------- /internal/isa/arm64/in/alias.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | import ( 8 | "gate.computer/wag/internal/gen/reg" 9 | "gate.computer/wag/internal/obj" 10 | "gate.computer/wag/wa" 11 | ) 12 | 13 | func LogicalShiftLeft(rd, rn reg.R, uimm uint32, t wa.Size) uint32 { 14 | var s, r uint32 15 | if t == wa.Size32 { 16 | r = (32 - uimm) & 31 17 | s = (31 - uimm&31) 18 | } else { 19 | r = (64 - uimm) & 63 20 | s = (63 - uimm&63) 21 | } 22 | return UBFM.RdRnI6sI6r(rd, rn, s, r, t) 23 | } 24 | 25 | func PushReg(r reg.R, t wa.Type) uint32 { 26 | return STRpre.RtRnI9(r, RegFakeSP, Int9(-obj.Word), t) 27 | } 28 | 29 | func PopReg(r reg.R, t wa.Type) uint32 { 30 | return LDRpost.RtRnI9(r, RegFakeSP, Int9(obj.Word), t) 31 | } 32 | -------------------------------------------------------------------------------- /internal/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package errors 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | type moduleError struct { 12 | text string 13 | cause error 14 | } 15 | 16 | func ModuleError(text string) error { 17 | return &moduleError{text, nil} 18 | } 19 | 20 | func ModuleErrorf(format string, args ...any) error { 21 | return &moduleError{fmt.Sprintf(format, args...), nil} 22 | } 23 | 24 | func WrapModuleError(cause error, text string) error { 25 | return &moduleError{text, cause} 26 | } 27 | 28 | func (e *moduleError) Error() string { return e.text } 29 | func (e *moduleError) PublicError() string { return e.text } 30 | func (e *moduleError) ModuleError() bool { return true } 31 | func (e *moduleError) Unwrap() error { return e.cause } 32 | -------------------------------------------------------------------------------- /internal/gen/link/link.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package link 6 | 7 | import ( 8 | "errors" 9 | 10 | "gate.computer/wag/internal/pan" 11 | ) 12 | 13 | type L struct { 14 | Sites []int32 15 | Addr int32 16 | } 17 | 18 | func (l *L) AddSite(addr int32) { 19 | l.Sites = append(l.Sites, addr) 20 | } 21 | 22 | func (l *L) AddSites(addrs []int32) { 23 | l.Sites = append(l.Sites, addrs...) 24 | } 25 | 26 | func (l L) FinalAddr() int32 { 27 | if l.Addr == 0 { 28 | pan.Panic(errors.New("link address undefined while updating branch or call instruction")) 29 | } 30 | return l.Addr 31 | } 32 | 33 | type FuncL struct { 34 | L 35 | TableIndexes []int 36 | } 37 | 38 | func (fl *FuncL) AddTableIndex(index int) { 39 | fl.TableIndexes = append(fl.TableIndexes, index) 40 | } 41 | -------------------------------------------------------------------------------- /internal/typedecode/typedecode.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package typedecode 6 | 7 | import ( 8 | "gate.computer/wag/internal/module" 9 | "gate.computer/wag/internal/pan" 10 | "gate.computer/wag/wa" 11 | ) 12 | 13 | var valueTypes = [4]wa.Type{ 14 | wa.I32, 15 | wa.I64, 16 | wa.F32, 17 | wa.F64, 18 | } 19 | 20 | func Value(x int8) wa.Type { 21 | if i := uint(-1 - x); i < uint(len(valueTypes)) { 22 | return valueTypes[i] 23 | } 24 | panic(pan.Wrap(module.Errorf("unknown value type %d", x))) 25 | } 26 | 27 | func Block(x int8) (t wa.Type) { 28 | if x == -0x40 { // empty block type 29 | return 30 | } 31 | if i := uint(-1 - x); i < uint(len(valueTypes)) { 32 | return valueTypes[i] 33 | } 34 | panic(pan.Wrap(module.Errorf("unknown block type %d", x))) 35 | } 36 | -------------------------------------------------------------------------------- /internal/isa/arm64/regs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package arm64 8 | 9 | import ( 10 | "gate.computer/wag/internal/gen/reg" 11 | "gate.computer/wag/internal/isa/arm64/in" 12 | ) 13 | 14 | const ( 15 | RegResult = reg.Result 16 | RegScratch = reg.ScratchISA 17 | RegTrap = reg.R(2) // <- AllocIntFirst, AllocFloatFirst 18 | RegRestartSP = reg.R(3) 19 | _ = reg.R(25) // <- AllocIntLast 20 | RegMemoryBase = reg.R(26) 21 | RegTextBase = reg.R(27) 22 | RegStackLimit4 = reg.R(28) 23 | RegFakeSP = in.RegFakeSP 24 | RegLink = reg.R(30) 25 | RegScratch2 = reg.R(30) 26 | RegRealSP = reg.R(31) 27 | RegZero = reg.R(31) 28 | RegDiscard = reg.R(31) // <- AllocFloatLast 29 | ) 30 | -------------------------------------------------------------------------------- /internal/gen/codegen/breakpoint.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package codegen 6 | 7 | import ( 8 | "gate.computer/wag/internal/gen" 9 | "gate.computer/wag/internal/loader" 10 | "gate.computer/wag/trap" 11 | ) 12 | 13 | func makeDebugger(breakpoints map[uint32]gen.Breakpoint, load *loader.L) gen.Debugger { 14 | if len(breakpoints) == 0 { 15 | return gen.Debugger{} 16 | } 17 | 18 | return gen.Debugger{ 19 | Breakpoints: breakpoints, 20 | CodeOffset: load.Tell(), 21 | } 22 | } 23 | 24 | func genBreakpoint(f *gen.Func, load *loader.L) { 25 | addr := f.Debugger.SourceAddr(load) 26 | bp, found := f.Debugger.Breakpoints[addr] 27 | if !found { 28 | return 29 | } 30 | 31 | opSaveOperands(f) 32 | asm.Trap(f, trap.Breakpoint) 33 | 34 | bp.Set = true 35 | f.Debugger.Breakpoints[addr] = bp 36 | } 37 | -------------------------------------------------------------------------------- /internal/test/library/testlibrary.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package library 6 | 7 | import ( 8 | "bufio" 9 | "os" 10 | 11 | "gate.computer/wag/internal/loader" 12 | ) 13 | 14 | type L interface { 15 | LoadSections(r loader.Loader) (err error) 16 | NumImportFuncs() int 17 | SetImportFunc(i, vectorIndex int) 18 | } 19 | 20 | func Load(filename string, dummyBinding bool, loadLibrary func(*loader.L) L) L { 21 | f, err := os.Open(filename) 22 | if err != nil { 23 | panic(err) 24 | } 25 | defer f.Close() 26 | 27 | load := loader.New(bufio.NewReader(f), 0) 28 | lib := loadLibrary(load) 29 | 30 | if dummyBinding { 31 | for i := 0; i < lib.NumImportFuncs(); i++ { 32 | lib.SetImportFunc(i, i-1000) 33 | } 34 | } 35 | 36 | if err := lib.LoadSections(load); err != nil { 37 | panic(err) 38 | } 39 | 40 | return lib 41 | } 42 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/mod.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | import ( 8 | "gate.computer/wag/internal/gen/reg" 9 | ) 10 | 11 | type ( 12 | Mod byte 13 | ModRO byte 14 | ModRM byte 15 | ) 16 | 17 | const ( 18 | ModMem = Mod(0) 19 | ModMemDisp8 = Mod(64) 20 | ModMemDisp32 = Mod(128) 21 | ModReg = Mod(192) 22 | ) 23 | 24 | const ( 25 | ModRMSIB = ModRM(4) 26 | ) 27 | 28 | func dispModSize(disp int32) (mod Mod, size uint32) { 29 | var ( 30 | bit32 = bit(uint32(disp+128) > 255) 31 | bit8 = bit(disp != 0) &^ bit32 32 | 33 | size4 = bit32 << 2 34 | size1 = bit8 35 | 36 | mod32 = bit32 << 7 37 | mod8 = bit8 << 6 38 | ) 39 | 40 | mod = Mod(mod32 | mod8) 41 | size = size4 | size1 42 | return 43 | } 44 | 45 | func regRO(r reg.R) ModRO { return ModRO((r & 7) << 3) } 46 | func regRM(r reg.R) ModRM { return ModRM(r & 7) } 47 | -------------------------------------------------------------------------------- /internal/pan/pan.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pan 6 | 7 | import ( 8 | "io" 9 | 10 | "import.name/pan" 11 | ) 12 | 13 | type unexpectedEOF struct{} 14 | 15 | func (unexpectedEOF) Error() string { return "unexpected EOF" } 16 | func (unexpectedEOF) PublicError() string { return "unexpected EOF" } 17 | func (unexpectedEOF) ModuleError() bool { return true } 18 | func (unexpectedEOF) Unwrap() error { return io.ErrUnexpectedEOF } 19 | 20 | var z = new(pan.Zone) 21 | 22 | var Check = z.Check 23 | var Panic = z.Panic 24 | var Wrap = z.Wrap 25 | 26 | func Error(x any) error { 27 | err := z.Error(x) 28 | if err == nil { 29 | return nil 30 | } 31 | 32 | if err == io.EOF || err == io.ErrUnexpectedEOF { 33 | return unexpectedEOF{} 34 | } 35 | 36 | return err 37 | } 38 | 39 | func Must[T any](x T, err error) T { 40 | Check(err) 41 | return x 42 | } 43 | -------------------------------------------------------------------------------- /internal/isa/arm64/memoryfill.S: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | .globl _start 6 | 7 | // Input: 8 | // [x29, #24]: target address within wasm memory (32-bit) 9 | // [x29, #16]: value (8-bit) 10 | // [x29, #8]: region length (32-bit) 11 | // x0: wasm memory pages 12 | // x26: wasm memory address 13 | // 14 | // Output: 15 | // w0: nonzero causes trap 16 | // 17 | // Must be preserved: 18 | // x26 19 | // x27 20 | // x28 21 | // x29 22 | 23 | _start: 24 | // Arguments 25 | ldr w2, [x29, #24] 26 | ldrb w3, [x29, #16] 27 | ldr w4, [x29, #8] 28 | 29 | // Target bounds check 30 | add x5, x2, x4 31 | cmp x5, x0, lsl #16 32 | b.le .Lno_trap 33 | 34 | // Trap 35 | mov w0, #1 36 | b .Lret 37 | 38 | .Lno_trap: 39 | // Nothing to do? 40 | cbz w4, .Lret_ok 41 | 42 | // Absolute address 43 | add x0, x26, x2 44 | 45 | .Lloop: 46 | strb w3, [x0], #1 47 | subs w4, w4, #1 48 | b.ne .Lloop 49 | 50 | .Lret_ok: 51 | mov w0, #0 52 | 53 | .Lret: 54 | -------------------------------------------------------------------------------- /internal/gen/rodata/rodata.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package rodata 6 | 7 | import ( 8 | "gate.computer/wag/wa" 9 | ) 10 | 11 | const CommonsAddr = 128 12 | 13 | const ( 14 | // Masks are 16-byte aligned for amd64 SSE. 15 | Mask7fAddr32 = CommonsAddr + iota*16 16 | Mask7fAddr64 17 | Mask80Addr32 18 | Mask80Addr64 19 | Mask5f00Addr32 // 01011111000000000000000000000000 20 | Mask43e0Addr64 // 0100001111100000000000000000000000000000000000000000000000000000 21 | TableAddr 22 | ) 23 | 24 | type MaskBaseAddr int32 25 | 26 | const ( 27 | Mask7fBase = MaskBaseAddr(Mask7fAddr32) 28 | Mask80Base = MaskBaseAddr(Mask80Addr32) 29 | MaskTruncBase = MaskBaseAddr(Mask5f00Addr32) 30 | ) 31 | 32 | // MaskAddr calculates the text address for reading a mask for the given type 33 | // size. maskBaseAddr should be one of the Mask*Base constants. 34 | func MaskAddr(maskBaseAddr MaskBaseAddr, t wa.Type) int32 { 35 | return int32(maskBaseAddr) + int32((t.Size()&8)<<1) 36 | } 37 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/rex.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | import ( 8 | "gate.computer/wag/internal/gen/reg" 9 | "gate.computer/wag/wa" 10 | ) 11 | 12 | type rexWRXB byte 13 | 14 | const ( 15 | Rex = byte(64) 16 | RexW = rexWRXB(8) // 64-bit operand size 17 | RexR = rexWRXB(4) // extension of the ModR/M reg field 18 | RexX = rexWRXB(2) // extension of the SIB index field 19 | RexB = rexWRXB(1) // extension of the ModR/M r/m field, SIB base field, or Opcode reg field 20 | ) 21 | 22 | const ( 23 | OneSize = wa.Type(0) // for instructions which don't use RexW 24 | ) 25 | 26 | const ( 27 | RexMemory = RexB // RegMemoryBase >= 8 28 | ) 29 | 30 | func typeRexW(t wa.Type) rexWRXB { return rexWRXB(t & 8) } // RexW == 8 31 | 32 | func regRexR(r reg.R) rexWRXB { return rexWRXB(r>>3) << 2 } // 8..15 => 4 33 | func regRexX(r reg.R) rexWRXB { return rexWRXB(r>>3) << 1 } // 8..15 => 2 34 | func regRexB(r reg.R) rexWRXB { return rexWRXB(r>>3) << 0 } // 8..15 => 1 35 | -------------------------------------------------------------------------------- /internal/isa/arm64/asm_test.s: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build arm64 && !wagamd64 && cgo 6 | 7 | #include "textflag.h" 8 | 9 | // func clearCache(exe []byte) 10 | TEXT ·clearCache(SB),$0-24 11 | MOVD exe+0(FP), R0 12 | WORD $0xd50b7b20 // dc cvau, x0 13 | WORD $0xd5033b9f // dsb ish 14 | WORD $0xd50b7520 // ic ivau, x0 15 | WORD $0xd5033b9f // dsb ish 16 | WORD $0xd5033fdf // isb 17 | RET 18 | 19 | // func executeTestCode(exe []byte) uint64 20 | TEXT ·executeTestCode(SB),$0-32 21 | MOVD $0xbadc0debadb00613, R0 22 | MOVD R0, R1 23 | MOVD R0, R2 24 | MOVD R0, R3 25 | MOVD R0, R4 26 | MOVD R0, R5 27 | MOVD R0, R6 28 | MOVD R0, R7 29 | MOVD R0, R8 30 | MOVD R0, R9 31 | MOVD R0, R10 32 | MOVD R0, R11 33 | MOVD R0, R12 34 | MOVD R0, R13 35 | MOVD R0, R14 36 | MOVD R0, R15 37 | 38 | MOVD R0, R19 39 | MOVD R0, R20 40 | MOVD R0, R21 41 | MOVD R0, R22 42 | MOVD R0, R23 43 | MOVD R0, R24 44 | 45 | MOVD exe+0(FP), R25 46 | BL (R25) 47 | MOVD R0, ret+24(FP) 48 | RET 49 | -------------------------------------------------------------------------------- /internal/isa/amd64/memoryfill.S: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | .intel_syntax noprefix 6 | 7 | .globl _start 8 | 9 | // Input: 10 | // [rsp + 24]: target address within wasm memory (32-bit) 11 | // [rsp + 16]: value (8-bit) 12 | // [rsp + 8]: region length (32-bit) 13 | // eax: wasm memory pages 14 | // r14: wasm memory address 15 | // 16 | // Output: 17 | // ZF=0 causes trap 18 | // 19 | // Must be preserved: 20 | // rbx 21 | // r14 22 | // r15 23 | 24 | _start: 25 | // Arguments 26 | mov ebp, [rsp + 24] 27 | mov sil, [rsp + 16] 28 | mov edi, [rsp + 8] 29 | 30 | // Wasm memory bytes 31 | shl eax, 16 32 | 33 | // Target bounds check 34 | sub rax, rbp 35 | cmp rax, rdi 36 | jge .Lno_trap 37 | 38 | // Trap (clear zero flag) 39 | test rsp, rsp 40 | jmp .Lret 41 | 42 | .Lno_trap: 43 | // Nothing to do? 44 | test edi, edi 45 | jz .Lret 46 | 47 | // Absolute address 48 | add rbp, r14 49 | 50 | .Lloop: 51 | mov [rbp], sil 52 | inc rbp 53 | dec edi 54 | jnz .Lloop 55 | 56 | .Lret: 57 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/mod_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestDispModSize(t *testing.T) { 12 | for _, pair := range [][3]int{ 13 | {-0x80000000, int(ModMemDisp32), 4}, 14 | {-0x7fffffff, int(ModMemDisp32), 4}, 15 | {-0x10000, int(ModMemDisp32), 4}, 16 | {-0x82, int(ModMemDisp32), 4}, 17 | {-0x81, int(ModMemDisp32), 4}, 18 | {-0x80, int(ModMemDisp8), 1}, 19 | {-0x7f, int(ModMemDisp8), 1}, 20 | {-2, int(ModMemDisp8), 1}, 21 | {-1, int(ModMemDisp8), 1}, 22 | {0, int(ModMem), 0}, 23 | {1, int(ModMemDisp8), 1}, 24 | {2, int(ModMemDisp8), 1}, 25 | {0x7e, int(ModMemDisp8), 1}, 26 | {0x7f, int(ModMemDisp8), 1}, 27 | {0x80, int(ModMemDisp32), 4}, 28 | {0x81, int(ModMemDisp32), 4}, 29 | {0xffff, int(ModMemDisp32), 4}, 30 | {0x10000, int(ModMemDisp32), 4}, 31 | {0x7ffffffe, int(ModMemDisp32), 4}, 32 | {0x7fffffff, int(ModMemDisp32), 4}, 33 | } { 34 | if mod, size := dispModSize(int32(pair[0])); mod != Mod(pair[1]) || size != uint32(pair[2]) { 35 | t.Errorf("dispModSize(%d) = %d, %d", pair[0], mod, size) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/obj/obj.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package obj 6 | 7 | const ( 8 | Word = 8 // stack entry size 9 | ) 10 | 11 | // ObjectMapper gathers information about positions of (WebAssembly) functions, 12 | // function calls and traps within the text (machine code) section. 13 | type ObjectMapper interface { 14 | InitObjectMap(numImportFuncs, numOtherFuncs int) 15 | PutFuncAddr(addr uint32) 16 | PutCallSite(returnAddr uint32, stackOffset int32) 17 | } 18 | 19 | // DebugObjectMapper gathers information about positions of all (WebAssembly) 20 | // instructions within the text (machine code) section. 21 | type DebugObjectMapper interface { 22 | ObjectMapper 23 | PutInsnAddr(addr, sourceAddr uint32) 24 | PutDataBlock(addr uint32, length int32) 25 | } 26 | 27 | type DummyMapper struct{} 28 | 29 | func (DummyMapper) InitObjectMap(int, int) {} 30 | func (DummyMapper) PutFuncAddr(uint32) {} 31 | func (DummyMapper) PutCallSite(uint32, int32) {} 32 | 33 | type DummyDebugMapper struct{ DummyMapper } 34 | 35 | func (DummyDebugMapper) PutInsnAddr(uint32) {} 36 | func (DummyDebugMapper) PutDataBlock(uint32, int32) {} 37 | -------------------------------------------------------------------------------- /internal/gen/prog.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gen 6 | 7 | import ( 8 | "gate.computer/wag/internal/code" 9 | "gate.computer/wag/internal/gen/link" 10 | "gate.computer/wag/internal/isa/program" 11 | "gate.computer/wag/internal/module" 12 | "gate.computer/wag/internal/obj" 13 | "gate.computer/wag/trap" 14 | ) 15 | 16 | const ( 17 | VectorOffsetMemoryAddr = -4 * obj.Word 18 | VectorOffsetCurrentMemory = -3 * obj.Word 19 | VectorOffsetGrowMemory = -2 * obj.Word 20 | VectorOffsetTrapHandler = -1 * obj.Word 21 | ) 22 | 23 | type Prog struct { 24 | Module *module.M 25 | Text code.Buf 26 | Map obj.ObjectMapper 27 | FuncLinks []link.FuncL 28 | TrapLinks [trap.NumTraps]link.L 29 | TrapLinkRewindSuspended [program.NumTrapLinkRewindSuspended]link.L 30 | TrapLinkTruncOverflow [program.NumTrapLinkTruncOverflow]link.L 31 | MemoryCopyAddr int32 32 | MemoryFillAddr int32 33 | LastCallAddr int32 // Needed only by arm64 backend. 34 | 35 | ImportContext *module.Library // Set during import function generation. 36 | 37 | DebugMap obj.DebugObjectMapper 38 | Debugger Debugger 39 | } 40 | -------------------------------------------------------------------------------- /internal/event/event.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package event 6 | 7 | // Event handler is invoked from a single goroutine (per compilation). 8 | type Event int 9 | 10 | const ( 11 | // The init routine can be executed. It may cause NoFunction traps. 12 | // 13 | // The event handler is a good place to resolve the entry function address, 14 | // because the compiler doesn't mutate the ObjectMapper during event 15 | // handler invocation. CodeConfig.LastInitFunc must be greater or equal to 16 | // the entry function index - otherwise its address is not available during 17 | // Init event handling. 18 | // 19 | // This event is not necessarily delivered. 20 | Init = Event(iota) 21 | 22 | // All functions have been generated, but links to them haven't yet been 23 | // updated in previous functions. 24 | // 25 | // If required by ISA, code cache should be invalidated before the event 26 | // handler returns. 27 | // 28 | // This event is not necessarily delivered. 29 | FunctionBarrier 30 | ) 31 | 32 | func (e Event) String() string { 33 | switch e { 34 | case Init: 35 | return "Init" 36 | 37 | case FunctionBarrier: 38 | return "FunctionBarrier" 39 | 40 | default: 41 | return "" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /fuzz_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wag 6 | 7 | import ( 8 | "bytes" 9 | "os" 10 | "path/filepath" 11 | "testing" 12 | 13 | "gate.computer/wag/buffer" 14 | werrors "gate.computer/wag/errors" 15 | ) 16 | 17 | func FuzzCompile(f *testing.F) { 18 | filenames, err := filepath.Glob("testsuite/testdata/specdata/*.wasm") 19 | if err != nil { 20 | f.Fatal(err) 21 | } 22 | for _, filename := range filenames { 23 | wasm, err := os.ReadFile(filename) 24 | if err != nil { 25 | f.Fatal(err) 26 | } 27 | f.Add(wasm, "", uint8(255), uint8(255)) 28 | } 29 | 30 | f.Fuzz(func(t *testing.T, wasm []byte, entry string, text, data uint8) { 31 | config := &Config{ 32 | Text: buffer.NewLimited(nil, (int(text)+1)*4096), 33 | GlobalsMemory: buffer.NewLimited(nil, (int(data)+1)*4096), 34 | MemoryAlignment: 4096, 35 | Entry: entry, 36 | } 37 | 38 | if data == 0 { // Only one page. 39 | config.MemoryAlignment = 0 40 | } 41 | 42 | _, err := Compile(config, bytes.NewReader(wasm), testlib) 43 | if err == nil { 44 | return 45 | } 46 | if werrors.AsModuleError(err) != nil { 47 | return 48 | } 49 | if werrors.AsResourceLimit(err) != nil { 50 | return 51 | } 52 | 53 | t.Error(err) 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /testsuite/service.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | 10 | "gate.computer/gate/packet" 11 | "gate.computer/gate/service" 12 | ) 13 | 14 | const serviceName = "gate.computer/wag/testsuite" 15 | 16 | type server struct { 17 | service.InstanceBase 18 | handle func([]byte) []byte 19 | } 20 | 21 | func newServer(handler func([]byte) []byte) *server { 22 | return &server{ 23 | handle: handler, 24 | } 25 | } 26 | 27 | func (s *server) Properties() service.Properties { 28 | return service.Properties{ 29 | Service: service.Service{ 30 | Name: serviceName, 31 | Revision: "0", 32 | }, 33 | } 34 | } 35 | 36 | func (*server) Discoverable(context.Context) bool { 37 | return true 38 | } 39 | 40 | func (s *server) CreateInstance(context.Context, service.InstanceConfig, []byte) (service.Instance, error) { 41 | return s, nil 42 | } 43 | 44 | func (s *server) Handle(ctx context.Context, send chan<- packet.Thunk, received packet.Buf) (packet.Buf, error) { 45 | if received.Domain() != packet.DomainCall { 46 | return nil, nil 47 | } 48 | 49 | content := s.handle(received.Content()) 50 | reply := packet.MakeCall(received.Code(), len(content)) 51 | copy(reply.Content(), content) 52 | return reply, nil 53 | } 54 | -------------------------------------------------------------------------------- /binding/export.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package binding 6 | 7 | import ( 8 | "gate.computer/wag/compile" 9 | "gate.computer/wag/internal/module" 10 | "gate.computer/wag/wa" 11 | ) 12 | 13 | // EntryFunc looks up an export function which is suitable as an entry point. 14 | // Its result type must be void or i32, and it must not take any parameters. 15 | func EntryFunc(mod compile.Module, name string) (funcIndex uint32, err error) { 16 | funcIndex, sig, found := mod.ExportFunc(name) 17 | if !found { 18 | err = module.Errorf("entry function %q not found", name) 19 | return 20 | } 21 | 22 | if !IsEntryFuncType(sig) { 23 | err = module.Errorf("entry function %s%s has incompatible signature", name, sig) 24 | return 25 | } 26 | 27 | return 28 | } 29 | 30 | // IsEntryFuncType checks if the signature is suitable for an entry function. 31 | func IsEntryFuncType(sig wa.FuncType) bool { 32 | if len(sig.Params) == 0 { 33 | if len(sig.Results) == 0 { 34 | return true 35 | } 36 | if len(sig.Results) == 1 && sig.Results[0] == wa.I32 { 37 | return true 38 | } 39 | } 40 | return false 41 | } 42 | 43 | // IsStartFuncType checks if the signature is suitable for a start function. 44 | func IsStartFuncType(sig wa.FuncType) bool { 45 | return sig.Equal(wa.FuncType{}) 46 | } 47 | -------------------------------------------------------------------------------- /internal/isa/amd64/regs.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (amd64 || wagamd64) && !wagarm64 6 | 7 | package amd64 8 | 9 | import ( 10 | "gate.computer/wag/internal/gen/reg" 11 | "gate.computer/wag/internal/isa/amd64/in" 12 | ) 13 | 14 | const ( 15 | RegResult = in.RegResult // rax xmm0 16 | RegDividendLow = reg.R(0) // rax 17 | RegScratch = in.RegScratch // rcx xmm1 18 | RegCount = reg.R(1) // rcx 19 | RegZero = in.RegZero // rdx 20 | RegTrap = in.RegZero // rdx 21 | RegDividendHigh = reg.R(2) // rdx 22 | _ = reg.R(2) // xmm2 <- AllocFloatFirst 23 | RegStackLimit = in.RegStackLimit // rbx 24 | RegStackPtr = reg.R(4) // rsp 25 | RegRestartSP = reg.R(5) // rbp <- AllocIntFirst 26 | _ = reg.R(6) // rsi 27 | _ = reg.R(7) // rdi 28 | _ = reg.R(8) // r8 29 | _ = reg.R(9) // r9 30 | _ = reg.R(10) // r10 31 | _ = reg.R(11) // r11 32 | _ = reg.R(12) // r12 33 | _ = reg.R(13) // r13 <- AllocIntLast 34 | RegMemoryBase = in.RegMemoryBase // r14 35 | RegTextBase = in.RegTextBase // r15 36 | _ = reg.R(15) // xmm15 <- AllocFloatLast 37 | ) 38 | -------------------------------------------------------------------------------- /internal/isa/amd64/memorycopy.S: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | .intel_syntax noprefix 6 | 7 | .globl _start 8 | 9 | // Input: 10 | // [rsp+24]: target address within wasm memory (32-bit) 11 | // [rsp+16]: source address within wasm memory (32-bit) 12 | // [rsp+8]: region length (32-bit) 13 | // eax: wasm memory pages 14 | // r14: wasm memory address 15 | // 16 | // Output: 17 | // ZF=0 causes trap 18 | // 19 | // Must be preserved: 20 | // rbx 21 | // r14 22 | // r15 23 | 24 | _start: 25 | // Arguments 26 | mov ebp, [rsp+24] 27 | mov esi, [rsp+16] 28 | mov edi, [rsp+8] 29 | 30 | // Wasm memory bytes 31 | shl eax, 16 32 | 33 | // Bounds checks 34 | sub rax, rdi 35 | cmp rax, rbp 36 | jl .Ltrap 37 | cmp rax, rsi 38 | jge .Lno_trap 39 | 40 | .Ltrap: 41 | // Clear zero flag 42 | test rsp, rsp 43 | jmp .Lret 44 | 45 | .Lno_trap: 46 | // Nothing to do? 47 | test edi, edi 48 | jz .Lret 49 | 50 | // Absolute addresses 51 | add rbp, r14 52 | add rsi, r14 53 | 54 | // Determine direction 55 | cmp rsi, rbp 56 | jge .Lforward 57 | 58 | // Move pointers to end 59 | add rbp, rdi 60 | add rsi, rdi 61 | 62 | .Lbackward: 63 | dec rsi 64 | mov dl, [rsi] 65 | dec rbp 66 | mov [rbp], dl 67 | dec rdi 68 | jnz .Lbackward 69 | 70 | jmp .Lret 71 | 72 | .Lforward: 73 | mov dl, [rsi] 74 | inc rsi 75 | mov [rbp], dl 76 | inc rbp 77 | dec rdi 78 | jnz .Lforward 79 | 80 | .Lret: 81 | -------------------------------------------------------------------------------- /internal/isa/amd64/routines.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (amd64 || wagamd64) && !wagarm64 6 | 7 | package amd64 8 | 9 | import ( 10 | _ "embed" 11 | 12 | "gate.computer/wag/internal/gen" 13 | "gate.computer/wag/internal/isa/amd64/in" 14 | "gate.computer/wag/trap" 15 | ) 16 | 17 | func (MacroAssembler) Routines(p *gen.Prog) { 18 | p.MemoryCopyAddr = memoryRoutine(p, memorycopy) 19 | p.MemoryFillAddr = memoryRoutine(p, memoryfill) 20 | } 21 | 22 | func memoryRoutine(p *gen.Prog, bin []byte) int32 { 23 | asm.AlignFunc(p) 24 | addr := p.Text.Addr 25 | 26 | getCurrentMemoryPages(&p.Text) 27 | 28 | copy(p.Text.Extend(len(bin)), bin) 29 | 30 | in.JNEcd.Addr32(&p.Text, p.TrapLinks[trap.MemoryAccessOutOfBounds].Addr) 31 | 32 | // TODO: check suspend (resume after call) 33 | 34 | in.RET.Simple(&p.Text) 35 | 36 | return addr 37 | } 38 | 39 | //go:generate x86_64-linux-gnu-as -o memorycopy.o memorycopy.S 40 | //go:generate x86_64-linux-gnu-ld -o memorycopy.elf memorycopy.o 41 | //go:generate x86_64-linux-gnu-objcopy -O binary memorycopy.elf memorycopy.bin 42 | //go:embed memorycopy.bin 43 | var memorycopy []byte 44 | 45 | //go:generate x86_64-linux-gnu-as -o memoryfill.o memoryfill.S 46 | //go:generate x86_64-linux-gnu-ld -o memoryfill.elf memoryfill.o 47 | //go:generate x86_64-linux-gnu-objcopy -O binary memoryfill.elf memoryfill.bin 48 | //go:embed memoryfill.bin 49 | var memoryfill []byte 50 | -------------------------------------------------------------------------------- /internal/isa/arm64/memorycopy.S: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | .globl _start 6 | 7 | // Input: 8 | // [x29, #24]: target address within wasm memory (32-bit) 9 | // [x29, #16]: source address within wasm memory (32-bit) 10 | // [x29, #8]: region length (32-bit) 11 | // x0: wasm memory pages 12 | // x26: wasm memory address 13 | // 14 | // Output: 15 | // w0: nonzero causes trap 16 | // 17 | // Must be preserved: 18 | // x26 19 | // x27 20 | // x28 21 | // x29 22 | 23 | _start: 24 | // Arguments 25 | ldr w2, [x29, #24] 26 | ldr w3, [x29, #16] 27 | ldr w4, [x29, #8] 28 | 29 | // Bounds checks 30 | add x5, x2, x4 31 | cmp x5, x0, lsl #16 32 | b.gt .Ltrap 33 | add x5, x3, x4 34 | cmp x5, x0, lsl #16 35 | b.le .Lno_trap 36 | 37 | .Ltrap: 38 | mov w0, #1 39 | b .Lret 40 | 41 | .Lno_trap: 42 | // Nothing to do? 43 | cbz w4, .Lret_ok 44 | 45 | // Absolute addresses 46 | add x0, x26, x2 47 | add x1, x26, x3 48 | 49 | // Determine direction 50 | cmp x1, x0 51 | b.ge .Lforward 52 | 53 | // Move pointers to end 54 | add x0, x0, x4 55 | add x1, x1, x4 56 | 57 | .Lbackward: 58 | ldrb w2, [x1, #-1]! 59 | strb w2, [x0, #-1]! 60 | subs w4, w4, #1 61 | b.ne .Lbackward 62 | 63 | b .Lret_ok 64 | 65 | .Lforward: 66 | ldrb w2, [x1], #1 67 | strb w2, [x0], #1 68 | subs w4, w4, #1 69 | b.ne .Lforward 70 | 71 | .Lret_ok: 72 | mov w0, #0 73 | 74 | .Lret: 75 | // Clear address 76 | mov w1, #0 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Timo Savola. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following disclaimer 10 | in the documentation and/or other materials provided with the 11 | distribution. 12 | 3. Neither the name of the copyright holder nor the names of its 13 | contributors may be used to endorse or promote products derived from 14 | this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /internal/gen/codegen/global.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package codegen 6 | 7 | import ( 8 | "gate.computer/wag/internal/gen" 9 | "gate.computer/wag/internal/gen/operand" 10 | "gate.computer/wag/internal/loader" 11 | "gate.computer/wag/internal/module" 12 | "gate.computer/wag/internal/obj" 13 | "gate.computer/wag/internal/pan" 14 | "gate.computer/wag/wa/opcode" 15 | ) 16 | 17 | func globalOffset(f *gen.Func, index uint32) int32 { 18 | return (int32(index) - int32(len(f.Module.Globals))) * obj.Word 19 | } 20 | 21 | func loadGlobalIndex(f *gen.Func, load *loader.L, op opcode.Opcode) uint32 { 22 | index := load.Varuint32() 23 | if index >= uint32(len(f.Module.Globals)) { 24 | pan.Panic(module.Errorf("%s index out of bounds: %d", op, index)) 25 | } 26 | return index 27 | } 28 | 29 | func genGetGlobal(f *gen.Func, load *loader.L, op opcode.Opcode) { 30 | globalIndex := loadGlobalIndex(f, load, op) 31 | 32 | global := f.Module.Globals[globalIndex] 33 | r, _ := opAllocReg(f, global.Type) 34 | asm.LoadGlobal(&f.Prog, global.Type, r, globalOffset(f, globalIndex)) 35 | pushOperand(f, operand.Reg(global.Type, r)) 36 | } 37 | 38 | func genSetGlobal(f *gen.Func, load *loader.L, op opcode.Opcode) { 39 | globalIndex := loadGlobalIndex(f, load, op) 40 | 41 | global := f.Module.Globals[globalIndex] 42 | if !global.Mutable { 43 | pan.Panic(module.Errorf("%s: global %d is immutable", op, globalIndex)) 44 | } 45 | 46 | x := popOperand(f, global.Type) 47 | asm.StoreGlobal(f, globalOffset(f, globalIndex), x) 48 | } 49 | -------------------------------------------------------------------------------- /wa/type.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wa 6 | 7 | type ScalarCategory uint8 8 | 9 | const ( 10 | Int = ScalarCategory(0) 11 | Float = ScalarCategory(1) 12 | ) 13 | 14 | func (cat ScalarCategory) String() string { 15 | switch cat { 16 | case Int: 17 | return "int" 18 | 19 | case Float: 20 | return "float" 21 | 22 | default: 23 | return "" 24 | } 25 | } 26 | 27 | type Size uint8 28 | 29 | const ( 30 | Size32 = Size(4) 31 | Size64 = Size(8) 32 | ) 33 | 34 | type Type uint8 35 | 36 | const ( 37 | Void = Type(0) 38 | I32 = Type(4 | Int) 39 | I64 = Type(8 | Int) 40 | F32 = Type(4 | Float) 41 | F64 = Type(8 | Float) 42 | ) 43 | 44 | // Category of a non-void type. 45 | func (t Type) Category() ScalarCategory { 46 | return ScalarCategory(t & 1) 47 | } 48 | 49 | // Size in bytes. 50 | func (t Type) Size() Size { 51 | return Size(t) & (4 | 8) 52 | } 53 | 54 | func (t Type) String() string { 55 | switch t { 56 | case Void: 57 | return "void" 58 | 59 | case I32: 60 | return "i32" 61 | 62 | case I64: 63 | return "i64" 64 | 65 | case F32: 66 | return "f32" 67 | 68 | case F64: 69 | return "f64" 70 | 71 | default: 72 | return "" 73 | } 74 | } 75 | 76 | var typeEncoding = [16]byte{ 77 | Void: 0x00, 78 | I32: 0x7f, 79 | I64: 0x7e, 80 | F32: 0x7d, 81 | F64: 0x7c, 82 | } 83 | 84 | // Encode as WebAssembly. Result is undefined if Type representation is not 85 | // valid. 86 | func (t Type) Encode() byte { 87 | return typeEncoding[t&15] 88 | } 89 | -------------------------------------------------------------------------------- /trap/trapid.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package trap enumerates trap identifiers. 6 | package trap 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | type ID int 13 | 14 | const ( 15 | Exit = ID(iota) 16 | NoFunction // Recoverable (nonportable). 17 | Suspended // Recoverable (portable). 18 | Unreachable 19 | CallStackExhausted // Recoverable (portable). 20 | MemoryAccessOutOfBounds 21 | IndirectCallIndexOutOfBounds 22 | IndirectCallSignatureMismatch 23 | IntegerDivideByZero 24 | IntegerOverflow 25 | Breakpoint // Recoverable (portable). 26 | 27 | NumTraps 28 | ) 29 | 30 | func (id ID) String() string { 31 | switch id { 32 | case Exit: 33 | return "exit" 34 | 35 | case NoFunction: 36 | return "no function" 37 | 38 | case Suspended: 39 | return "suspended" 40 | 41 | case Unreachable: 42 | return "unreachable" 43 | 44 | case CallStackExhausted: 45 | return "call stack exhausted" 46 | 47 | case MemoryAccessOutOfBounds: 48 | return "memory access out of bounds" 49 | 50 | case IndirectCallIndexOutOfBounds: 51 | return "indirect call index out of bounds" 52 | 53 | case IndirectCallSignatureMismatch: 54 | return "indirect call signature mismatch" 55 | 56 | case IntegerDivideByZero: 57 | return "integer divide by zero" 58 | 59 | case IntegerOverflow: 60 | return "integer overflow" 61 | 62 | case Breakpoint: 63 | return "breakpoint" 64 | 65 | default: 66 | return fmt.Sprintf("unknown trap %d", id) 67 | } 68 | } 69 | 70 | func (id ID) Error() string { 71 | return "trap: " + id.String() 72 | } 73 | -------------------------------------------------------------------------------- /testdata/hello.c: -------------------------------------------------------------------------------- 1 | // clang --target=wasm32 -Oz -nostdlib -Wl,--allow-undefined -Wl,--no-entry -Wl,--export=main -o hello.wasm hello.c 2 | 3 | #define _GNU_SOURCE 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | int main(void) 20 | { 21 | const char *hello = "hello, "; 22 | const char *world = "world\n"; 23 | 24 | if (write(STDOUT_FILENO, hello, strlen(hello)) != strlen(hello)) 25 | return 1; 26 | 27 | char buf[128]; 28 | 29 | for (int i = 0; i < sizeof buf; i++) 30 | buf[i] = i; 31 | 32 | int zero = openat(AT_FDCWD, "/dev/zero", O_RDONLY); 33 | if (zero < 0) 34 | return 1; 35 | 36 | if (read(zero, buf, sizeof buf) != sizeof buf) 37 | return 1; 38 | 39 | if (close(zero) != 0) 40 | return 1; 41 | 42 | for (int i = 0; i < sizeof buf; i++) 43 | if (buf[i] != 0) 44 | return 1; 45 | 46 | int fds[2]; 47 | 48 | if (pipe2(fds, O_CLOEXEC) < 0) 49 | return 1; 50 | 51 | if (close(fds[0]) != 0) 52 | return 1; 53 | 54 | if (close(fds[1]) != 0) 55 | return 1; 56 | 57 | int out = openat(AT_FDCWD, "/dev/stdout", O_WRONLY); 58 | if (out < 0) 59 | return 1; 60 | 61 | if (write(out, world, strlen(world)) != strlen(world)) 62 | return 1; 63 | 64 | if (close(out) != 0) 65 | return 1; 66 | 67 | if (close(STDIN_FILENO) != 0) 68 | return 1; 69 | 70 | if (close(STDOUT_FILENO) != 0) 71 | return 1; 72 | 73 | if (close(STDERR_FILENO) != 0) 74 | return 1; 75 | 76 | if (close(STDERR_FILENO) == 0) 77 | return 1; 78 | 79 | _exit(0); 80 | return 1; 81 | } 82 | -------------------------------------------------------------------------------- /internal/gen/func_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gen 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func checkLocalOffsets(t *testing.T, f *Func, table [][2]int) { 12 | t.Helper() 13 | 14 | for _, pair := range table { 15 | if actual := f.LocalOffset(pair[0]); actual != int32(pair[1]) { 16 | t.Errorf("LocalOffset(%d) should be %d but is actually %d", pair[0], pair[1], actual) 17 | } 18 | } 19 | } 20 | 21 | func TestLocalOffset(t *testing.T) { 22 | const ( 23 | numParams = 5 24 | numOthers = 8 25 | ) 26 | 27 | t.Run("Valid", func(t *testing.T) { 28 | checkLocalOffsets(t, &Func{ 29 | NumParams: numParams, 30 | StackDepth: numOthers, 31 | }, [][2]int{ 32 | {0, 104}, // params 33 | {1, 96}, 34 | {2, 88}, 35 | {3, 80}, 36 | {4, 72}, 37 | {5, 56}, // non-params 38 | {6, 48}, 39 | {7, 40}, 40 | {8, 32}, 41 | {9, 24}, 42 | {10, 16}, 43 | {11, 8}, 44 | {12, 0}, 45 | }) 46 | }) 47 | 48 | t.Run("Valid+1", func(t *testing.T) { 49 | checkLocalOffsets(t, &Func{ 50 | NumParams: numParams, 51 | StackDepth: numOthers + 1, 52 | }, [][2]int{ 53 | {0, 112}, // params 54 | {1, 104}, 55 | {2, 96}, 56 | {3, 88}, 57 | {4, 80}, 58 | {5, 64}, // non-params 59 | {6, 56}, 60 | {7, 48}, 61 | {8, 40}, 62 | {9, 32}, 63 | {10, 24}, 64 | {11, 16}, 65 | {12, 8}, 66 | }) 67 | }) 68 | 69 | t.Run("IndexOutOfRange", func(t *testing.T) { 70 | defer func() { recover() }() 71 | f := Func{ 72 | NumParams: numParams, 73 | StackDepth: numOthers, 74 | } 75 | f.LocalOffset(numParams + numOthers) 76 | t.Fail() 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/rex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | import ( 8 | "testing" 9 | 10 | "gate.computer/wag/internal/gen/reg" 11 | "gate.computer/wag/wa" 12 | ) 13 | 14 | func TestTypeRexW(t *testing.T) { 15 | if bit := typeRexW(wa.I32); bit != 0 { 16 | t.Errorf("typeRexW(wa.I32) = 0x%x", bit) 17 | } 18 | if bit := typeRexW(wa.I64); bit != RexW { 19 | t.Errorf("typeRexW(wa.I64) = 0x%x", bit) 20 | } 21 | if bit := typeRexW(wa.F32); bit != 0 { 22 | t.Errorf("typeRexW(wa.F32) = 0x%x", bit) 23 | } 24 | if bit := typeRexW(wa.F64); bit != RexW { 25 | t.Errorf("typeRexW(wa.F64) = 0x%x", bit) 26 | } 27 | } 28 | 29 | func TestRegRexR(t *testing.T) { 30 | for r := reg.R(0); r <= reg.R(7); r++ { 31 | if bit := regRexR(r); bit != 0 { 32 | t.Errorf("regRexR(%s) = 0x%x", r, bit) 33 | } 34 | } 35 | for r := reg.R(8); r <= reg.R(15); r++ { 36 | if bit := regRexR(r); bit != RexR { 37 | t.Errorf("regRexR(%s) = 0x%x", r, bit) 38 | } 39 | } 40 | } 41 | 42 | func TestRegRexX(t *testing.T) { 43 | for r := reg.R(0); r <= reg.R(7); r++ { 44 | if bit := regRexX(r); bit != 0 { 45 | t.Errorf("regRexX(%s) = 0x%x", r, bit) 46 | } 47 | } 48 | for r := reg.R(8); r <= reg.R(15); r++ { 49 | if bit := regRexX(r); bit != RexX { 50 | t.Errorf("regRexX(%s) = 0x%x", r, bit) 51 | } 52 | } 53 | } 54 | 55 | func TestRegRexB(t *testing.T) { 56 | for r := reg.R(0); r <= reg.R(7); r++ { 57 | if bit := regRexB(r); bit != 0 { 58 | t.Errorf("regRexB(%s) = 0x%x", r, bit) 59 | } 60 | } 61 | for r := reg.R(8); r <= reg.R(15); r++ { 62 | if bit := regRexB(r); bit != RexB { 63 | t.Errorf("regRexB(%s) = 0x%x", r, bit) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /errors/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package errors exports common error types without unnecessary dependencies. 6 | package errors 7 | 8 | import ( 9 | "errors" 10 | ) 11 | 12 | // PublicError provides a message which can be used in communications. The 13 | // Error() method returns a message suitable for internal logging etc. 14 | // 15 | // If the PublicError methods an empty string, the error is effectively not 16 | // public. 17 | type PublicError interface { 18 | error 19 | PublicError() string 20 | } 21 | 22 | // AsPublicError returns the error if it is public (PublicError method returns 23 | // non-empty string). 24 | func AsPublicError(err error) PublicError { 25 | var e PublicError 26 | if errors.As(err, &e) && e.PublicError() != "" { 27 | return e 28 | } 29 | return nil 30 | } 31 | 32 | // ModuleError indicates that the error is caused by unsupported or malformed 33 | // WebAssembly module. 34 | type ModuleError interface { 35 | PublicError 36 | ModuleError() bool 37 | } 38 | 39 | // AsModuleError returns the error if it is a module error (ModuleError method 40 | // returns true). 41 | func AsModuleError(err error) ModuleError { 42 | var e ModuleError 43 | if errors.As(err, &e) && e.ModuleError() { 44 | return e 45 | } 46 | return nil 47 | } 48 | 49 | // ResourceLimit was reached. 50 | type ResourceLimit interface { 51 | PublicError 52 | ResourceLimit() bool 53 | } 54 | 55 | // AsResourceLimit returns the error if it is a resource limit error 56 | // (ResourceLimit method returns true). 57 | func AsResourceLimit(err error) ResourceLimit { 58 | var e ResourceLimit 59 | if errors.As(err, &e) && e.ResourceLimit() { 60 | return e 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /internal/isa/arm64/routines.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package arm64 8 | 9 | import ( 10 | _ "embed" 11 | 12 | "gate.computer/wag/internal/gen" 13 | "gate.computer/wag/internal/isa/arm64/in" 14 | "gate.computer/wag/trap" 15 | "gate.computer/wag/wa" 16 | ) 17 | 18 | func (MacroAssembler) Routines(p *gen.Prog) { 19 | p.MemoryCopyAddr = memoryRoutine(p, memorycopy) 20 | p.MemoryFillAddr = memoryRoutine(p, memoryfill) 21 | } 22 | 23 | func memoryRoutine(p *gen.Prog, bin []byte) int32 { 24 | addr := p.Text.Addr 25 | 26 | { 27 | var o outbuf 28 | o.insn(in.PushReg(RegLink, wa.I64)) 29 | o.copy(p.Text.Extend(o.size)) 30 | } 31 | 32 | getCurrentMemoryPages(&p.Text) 33 | 34 | copy(p.Text.Extend(len(bin)), bin) 35 | 36 | { 37 | var o outbuf 38 | o.insn(in.CBZ.RtI19(RegResult, 2, wa.Size32)) // Skip next instruction. 39 | o.insn(in.B.I26(in.Int26((p.TrapLinks[trap.MemoryAccessOutOfBounds].Addr - o.addr(&p.Text)) / 4))) 40 | // TODO: check suspend (resume after call) 41 | o.copy(p.Text.Extend(o.size)) 42 | } 43 | 44 | asm.Return(p, 0) 45 | 46 | return addr 47 | } 48 | 49 | //go:generate aarch64-linux-gnu-as -o memorycopy.o memorycopy.S 50 | //go:generate aarch64-linux-gnu-ld -o memorycopy.elf memorycopy.o 51 | //go:generate aarch64-linux-gnu-objcopy -O binary memorycopy.elf memorycopy.bin 52 | //go:embed memorycopy.bin 53 | var memorycopy []byte 54 | 55 | //go:generate aarch64-linux-gnu-as -o memoryfill.o memoryfill.S 56 | //go:generate aarch64-linux-gnu-ld -o memoryfill.elf memoryfill.o 57 | //go:generate aarch64-linux-gnu-objcopy -O binary memoryfill.elf memoryfill.bin 58 | //go:embed memoryfill.bin 59 | var memoryfill []byte 60 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO ?= go 2 | GOFMT ?= gofmt 3 | STATICCHECK ?= staticcheck 4 | WAST2JSON ?= wast2json 5 | WAT2WASM ?= wat2wasm 6 | PYTHON ?= python3 7 | PERFLOCK ?= perflock 8 | BENCHSTAT ?= benchstat 9 | 10 | PACKAGES := . $(patsubst %,./%/...,binary binding buffer compile errors internal object section trap wa) 11 | 12 | TEST := 13 | ifneq ($(TEST),) 14 | TESTFLAGS += -run="$(TEST)" -v 15 | endif 16 | 17 | BENCHFLAGS += -run=- -bench=. 18 | 19 | -include config.mk 20 | 21 | export GOFMT WAST2JSON WAT2WASM 22 | 23 | .PHONY: build 24 | build: 25 | GOARCH=amd64 $(GO) build $(BUILDFLAGS) $(PACKAGES) 26 | GOARCH=arm64 $(GO) build $(BUILDFLAGS) $(PACKAGES) 27 | $(GO) build $(BUILDFLAGS) -tags=wagamd64 $(PACKAGES) 28 | $(GO) build $(BUILDFLAGS) -tags=wagarm64 $(PACKAGES) 29 | 30 | GOARCH=amd64 $(GO) vet $(BUILDFLAGS) $(PACKAGES) 31 | GOARCH=arm64 $(GO) vet $(BUILDFLAGS) $(PACKAGES) 32 | $(GO) vet $(BUILDFLAGS) -tags=wagamd64 $(PACKAGES) 33 | $(GO) vet $(BUILDFLAGS) -tags=wagarm64 $(PACKAGES) 34 | 35 | $(STATICCHECK) ./... 36 | 37 | .PHONY: check 38 | check: build 39 | $(GO) test $(TESTFLAGS) $(PACKAGES) 40 | 41 | cd testsuite && $(GO) vet $(BUILDFLAGS) ./... 42 | cd testsuite && $(GO) test $(TESTFLAGS) ./... 43 | 44 | .PHONY: benchmark 45 | benchmark: 46 | @ $(PERFLOCK) true 47 | $(PERFLOCK) $(GO) test $(BENCHFLAGS) $(PACKAGES) | tee bench-new.txt 48 | [ ! -e bench-old.txt ] || $(BENCHSTAT) bench-old.txt bench-new.txt 49 | 50 | .PHONY: generate 51 | generate: 52 | GOARCH=amd64 $(GO) generate ./internal/isa/... 53 | GOARCH=arm64 $(GO) generate ./internal/isa/... 54 | cd testsuite && $(GO) generate 55 | 56 | .PHONY: library 57 | library: 58 | cd testsuite && $(GO) generate testdata/library.go 59 | 60 | .PHONY: clean 61 | clean: 62 | rm -f internal/isa/*/*.elf internal/isa/*/*.o 63 | rm -rf testsuite/testdata/include 64 | rm -rf testsuite/testdata/specdata 65 | -------------------------------------------------------------------------------- /internal/initexpr/initexpr.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package initexpr 6 | 7 | import ( 8 | "gate.computer/wag/internal/loader" 9 | "gate.computer/wag/internal/module" 10 | "gate.computer/wag/internal/pan" 11 | "gate.computer/wag/wa" 12 | "gate.computer/wag/wa/opcode" 13 | ) 14 | 15 | func Read(m *module.M, load *loader.L) (importIndex int, valueBits uint64, t wa.Type) { 16 | importIndex = -1 17 | 18 | switch op := opcode.Opcode(load.Byte()); op { 19 | case opcode.I32Const: 20 | valueBits = uint64(int64(load.Varint32())) 21 | t = wa.I32 22 | 23 | case opcode.I64Const: 24 | valueBits = uint64(load.Varint64()) 25 | t = wa.I64 26 | 27 | case opcode.F32Const: 28 | valueBits = uint64(load.Uint32()) 29 | t = wa.F32 30 | 31 | case opcode.F64Const: 32 | valueBits = load.Uint64() 33 | t = wa.F64 34 | 35 | case opcode.GetGlobal: 36 | i := load.Varuint32() 37 | if i >= uint32(len(m.ImportGlobals)) { 38 | pan.Panic(module.Errorf("import global index out of bounds in initializer expression: %d", i)) 39 | } 40 | importIndex = int(i) 41 | t = m.Globals[i].Type 42 | 43 | default: 44 | pan.Panic(module.Errorf("unsupported operation in initializer expression: %s", op)) 45 | } 46 | 47 | if op := opcode.Opcode(load.Byte()); op != opcode.End { 48 | pan.Panic(module.Errorf("unexpected operation in initializer expression when expecting end: %s", op)) 49 | } 50 | 51 | return 52 | } 53 | 54 | func ReadOffset(m *module.M, load *loader.L) uint32 { 55 | index, value, t := Read(m, load) 56 | if t != wa.I32 { 57 | pan.Panic(module.Errorf("offset initializer expression has invalid type: %s", t)) 58 | } 59 | 60 | value = m.EvaluateGlobalInitializer(index, value) 61 | return uint32(int32(int64(value))) 62 | } 63 | -------------------------------------------------------------------------------- /wa/functype.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wa 6 | 7 | type FuncType struct { 8 | Params []Type 9 | Results []Type 10 | } 11 | 12 | func (f FuncType) Equal(other FuncType) bool { 13 | if len(f.Params) != len(other.Params) { 14 | return false 15 | } 16 | if len(f.Results) != len(other.Results) { 17 | return false 18 | } 19 | 20 | for i := range f.Params { 21 | if f.Params[i] != other.Params[i] { 22 | return false 23 | } 24 | } 25 | 26 | for i := range f.Results { 27 | if f.Results[i] != other.Results[i] { 28 | return false 29 | } 30 | } 31 | 32 | return true 33 | } 34 | 35 | func (f FuncType) Compare(other FuncType) int { 36 | if n := compareTypes(f.Params, other.Params); n != 0 { 37 | return n 38 | } 39 | if n := compareTypes(f.Results, other.Results); n != 0 { 40 | return n 41 | } 42 | return 0 43 | } 44 | 45 | func compareTypes(a, b []Type) int { 46 | commonLen := len(a) 47 | if commonLen > len(b) { 48 | commonLen = len(b) 49 | } 50 | 51 | for i := 0; i < commonLen; i++ { 52 | if a[i] < b[i] { 53 | return -1 54 | } 55 | if a[i] > b[i] { 56 | return 1 57 | } 58 | } 59 | 60 | if len(a) < len(b) { 61 | return -1 62 | } 63 | if len(a) > len(b) { 64 | return 1 65 | } 66 | 67 | return 0 68 | } 69 | 70 | func (f FuncType) String() (s string) { 71 | s = "(" 72 | for i, t := range f.Params { 73 | if i > 0 { 74 | s += ", " 75 | } 76 | s += t.String() 77 | } 78 | s += ")" 79 | 80 | switch len(f.Results) { 81 | case 0: 82 | case 1: 83 | s += " " + f.Results[0].String() 84 | default: 85 | s += " (" 86 | for i, t := range f.Results { 87 | if i > 0 { 88 | s += ", " 89 | } 90 | s += t.String() 91 | } 92 | s += ")" 93 | } 94 | 95 | return 96 | } 97 | -------------------------------------------------------------------------------- /compile/table.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package compile 6 | 7 | import ( 8 | "sort" 9 | 10 | "gate.computer/wag/wa" 11 | ) 12 | 13 | type funcType struct { 14 | t wa.FuncType 15 | index uint32 16 | newIndex uint32 17 | } 18 | 19 | // funcTypeOrder groups identical types together, smallest index first. 20 | type funcTypeOrder []funcType 21 | 22 | func (a funcTypeOrder) Len() int { return len(a) } 23 | func (a funcTypeOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 24 | 25 | func (a funcTypeOrder) Less(i, j int) bool { 26 | if n := a[i].t.Compare(a[j].t); n != 0 { 27 | return n < 0 28 | } 29 | return a[i].index < a[j].index 30 | } 31 | 32 | func initTableTypes(m *Module) { 33 | // All function types. 34 | types := make([]funcType, 0, len(m.m.Types)) 35 | 36 | for typeIndex, t := range m.m.Types { 37 | types = append(types, funcType{ 38 | t: t, 39 | index: uint32(typeIndex), 40 | newIndex: uint32(typeIndex), 41 | }) 42 | } 43 | 44 | sort.Sort(funcTypeOrder(types)) 45 | 46 | // Number of elements which need type index remapping. 47 | var count int 48 | 49 | if len(types) > 0 { 50 | canonical := types[0].index 51 | 52 | for i := 1; i < len(types); i++ { 53 | prev := &types[i-1] 54 | curr := &types[i] 55 | 56 | if !prev.t.Equal(curr.t) { 57 | canonical = curr.index 58 | } else { 59 | curr.newIndex = canonical 60 | count++ 61 | } 62 | } 63 | } 64 | 65 | if count == 0 { 66 | return 67 | } 68 | 69 | // Type indexes mapped to canonical type indexes. 70 | m.m.CanonicalTypes = make(map[uint32]uint32, count) 71 | 72 | for _, curr := range types { 73 | if curr.newIndex != curr.index { 74 | m.m.CanonicalTypes[curr.index] = curr.newIndex 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /section_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package wag 6 | 7 | import ( 8 | "bytes" 9 | "os" 10 | "testing" 11 | 12 | "gate.computer/wag/compile" 13 | "gate.computer/wag/internal/loader" 14 | "gate.computer/wag/section" 15 | ) 16 | 17 | func TestSection(t *testing.T) { 18 | var ( 19 | sectionMap = new(section.Map) 20 | nameSectionMapping = new(section.MappedNameSection) 21 | imaginaryMapping = new(section.CustomMapping) 22 | loadConfig = compile.Config{ 23 | ModuleMapper: sectionMap, 24 | CustomSectionLoader: section.CustomLoader(map[string]section.CustomContentLoader{ 25 | section.CustomName: nameSectionMapping.Loader(sectionMap), 26 | "imaginary": imaginaryMapping.Loader(sectionMap), 27 | }), 28 | } 29 | ) 30 | 31 | data, err := os.ReadFile("testdata/hello.wasm") 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | 36 | r := loader.New(bytes.NewReader(data), 0) 37 | 38 | mod, err := compile.LoadInitialSections(&compile.ModuleConfig{Config: loadConfig}, r) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | for i := 0; i < mod.NumImportFuncs(); i++ { 44 | // Arbitrary (but existing) implementation. 45 | mod.SetImportFunc(i, uint32(testlib.NumImportFuncs())) 46 | } 47 | 48 | err = compile.LoadCodeSection(&compile.CodeConfig{Config: loadConfig}, r, mod, testlib) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | err = compile.LoadDataSection(&compile.DataConfig{Config: loadConfig}, r, mod) 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | 58 | err = compile.LoadCustomSections(&loadConfig, r) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | t.Logf("section map: %#v", sectionMap.Sections) 64 | t.Logf("name section mapping: %#v", nameSectionMapping.Mapping) 65 | t.Logf("imaginary mapping: %#v", imaginaryMapping) 66 | } 67 | -------------------------------------------------------------------------------- /buffer/limited.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package buffer 6 | 7 | import ( 8 | "encoding/binary" 9 | 10 | "gate.computer/wag/internal/pan" 11 | ) 12 | 13 | // Limited is a dynamic buffer with a maximum size. The default value is an 14 | // empty buffer that cannot grow. 15 | type Limited struct { 16 | d Dynamic 17 | } 18 | 19 | // MakeLimited buffer with a maximum size. The slice must be empty. 20 | // 21 | // This function can be used in field initializer expressions. The initialized 22 | // field must not be copied. 23 | func MakeLimited(b []byte, maxSize int) Limited { 24 | return Limited{MakeDynamicHint(b, maxSize)} 25 | } 26 | 27 | // NewLimited buffer with a maximum size. The slice must be empty. 28 | func NewLimited(b []byte, maxSize int) *Limited { 29 | l := MakeLimited(b, maxSize) 30 | return &l 31 | } 32 | 33 | // Len doesn't panic. 34 | func (l *Limited) Len() int { 35 | return l.d.Len() 36 | } 37 | 38 | // Bytes doesn't panic. 39 | func (l *Limited) Bytes() []byte { 40 | return l.d.Bytes() 41 | } 42 | 43 | // PutByte panics if the buffer is already full. 44 | func (l *Limited) PutByte(value byte) { 45 | if len(l.d.buf) >= l.d.maxSize { 46 | pan.Panic(ErrSizeLimit) 47 | } 48 | l.d.PutByte(value) 49 | } 50 | 51 | // Extend panics if 4 bytes cannot be appended to the buffer. 52 | func (l *Limited) PutUint32(i uint32) { 53 | binary.LittleEndian.PutUint32(l.Extend(4), i) 54 | } 55 | 56 | // Extend panics if n bytes cannot be appended to the buffer. 57 | func (l *Limited) Extend(n int) []byte { 58 | if len(l.d.buf)+n > l.d.maxSize { 59 | pan.Panic(ErrSizeLimit) 60 | } 61 | return l.d.Extend(n) 62 | } 63 | 64 | // ResizeBytes panics if n is larger than maximum buffer size. 65 | func (l *Limited) ResizeBytes(n int) []byte { 66 | if n > l.d.maxSize { 67 | pan.Panic(ErrSizeLimit) 68 | } 69 | return l.d.ResizeBytes(n) 70 | } 71 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/debug.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (debug || indebug) && cgo 6 | 7 | package in 8 | 9 | import ( 10 | "fmt" 11 | 12 | "github.com/knightsc/gapstone" 13 | ) 14 | 15 | const ( 16 | debugInstructionBytes = true 17 | debugImplicitRegisters = true 18 | ) 19 | 20 | var debugEngine gapstone.Engine 21 | 22 | func init() { 23 | engine, err := gapstone.New(gapstone.CS_ARCH_X86, gapstone.CS_MODE_64) 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | err = engine.SetOption(gapstone.CS_OPT_SYNTAX, gapstone.CS_OPT_SYNTAX_ATT) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | debugEngine = engine 34 | } 35 | 36 | func debugPrintInsn(data []byte) { 37 | var hex string 38 | 39 | if debugInstructionBytes { 40 | hex = " ;" 41 | for i, b := range data { 42 | if i > 0 && (i&3) == 0 { 43 | hex += " " 44 | } 45 | hex += fmt.Sprintf(" %02x", b) 46 | } 47 | } 48 | 49 | insns, err := debugEngine.Disasm(data, 0, 0) 50 | if err != nil || len(insns) == 0 { 51 | if debugInstructionBytes { 52 | print(fmt.Sprintf("indebug:%s\n", hex)) 53 | } 54 | panic(err) 55 | } 56 | 57 | prefix := "indebug" 58 | 59 | for _, insn := range insns { 60 | var ( 61 | read string 62 | write string 63 | ) 64 | 65 | if debugImplicitRegisters { 66 | if len(insn.RegistersRead) > 0 { 67 | read = " ; read" 68 | for _, r := range insn.RegistersRead { 69 | read += fmt.Sprintf(" r%d", r) 70 | } 71 | } 72 | 73 | if len(insn.RegistersWritten) > 0 { 74 | write = " ; write" 75 | for _, r := range insn.RegistersWritten { 76 | write += fmt.Sprintf(" r%d", r) 77 | } 78 | } 79 | } 80 | 81 | print(fmt.Sprintf("%7s: %-7s %-25s%s%s%s\n", prefix, insn.Mnemonic, insn.OpStr, hex, read, write)) 82 | 83 | prefix = "" 84 | 85 | if hex != "" { 86 | hex = " ;" 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /internal/gen/codegen/local.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package codegen 6 | 7 | import ( 8 | "gate.computer/wag/internal/gen" 9 | "gate.computer/wag/internal/gen/operand" 10 | "gate.computer/wag/internal/gen/storage" 11 | "gate.computer/wag/internal/loader" 12 | "gate.computer/wag/internal/module" 13 | "gate.computer/wag/internal/pan" 14 | "gate.computer/wag/wa" 15 | "gate.computer/wag/wa/opcode" 16 | ) 17 | 18 | func loadLocalIndex(f *gen.Func, load *loader.L, op opcode.Opcode) uint32 { 19 | index := load.Varuint32() 20 | if index >= uint32(len(f.LocalTypes)) { 21 | pan.Panic(module.Errorf("%s index out of bounds: %d", op, index)) 22 | } 23 | return index 24 | } 25 | 26 | func loadLocalIndexType(f *gen.Func, load *loader.L, op opcode.Opcode) (int, wa.Type) { 27 | index := loadLocalIndex(f, load, op) 28 | t := f.LocalTypes[index] 29 | return int(index), t 30 | } 31 | 32 | func genGetLocal(f *gen.Func, load *loader.L, op opcode.Opcode) { 33 | index, t := loadLocalIndexType(f, load, op) 34 | r, _ := opAllocReg(f, t) 35 | asm.LoadStack(&f.Prog, t, r, f.LocalOffset(index)) 36 | pushOperand(f, operand.Reg(t, r)) 37 | } 38 | 39 | func genSetLocal(f *gen.Func, load *loader.L, op opcode.Opcode) { 40 | index, t := loadLocalIndexType(f, load, op) 41 | value := popOperand(f, t) 42 | asm.StoreStack(f, f.LocalOffset(index), value) 43 | } 44 | 45 | func genTeeLocal(f *gen.Func, load *loader.L, op opcode.Opcode) { 46 | index, t := loadLocalIndexType(f, load, op) 47 | value := popOperand(f, t) 48 | 49 | switch value.Storage { 50 | default: 51 | r, _ := opAllocReg(f, t) 52 | asm.Move(f, r, value) 53 | value.SetReg(r) 54 | fallthrough 55 | 56 | case storage.Reg: 57 | asm.StoreStackReg(&f.Prog, t, f.LocalOffset(index), value.Reg()) 58 | 59 | case storage.Imm: 60 | asm.StoreStackImm(&f.Prog, t, f.LocalOffset(index), value.ImmValue()) 61 | } 62 | 63 | pushOperand(f, value) 64 | } 65 | -------------------------------------------------------------------------------- /object/funcmap.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package object 6 | 7 | import ( 8 | "math" 9 | "sort" 10 | ) 11 | 12 | // FuncMap implements compile.ObjectMapper. It stores function addresses, but 13 | // no call, trap or instruction information. 14 | // 15 | // FuncAddrs may be preallocated by initializing the field with a non-nil, 16 | // empty array. 17 | type FuncMap struct { 18 | FuncAddrs []uint32 19 | } 20 | 21 | func (m *FuncMap) InitObjectMap(numImportFuncs, numOtherFuncs int) { 22 | if len(m.FuncAddrs) > 0 { 23 | panic("FuncAddrs is not empty") 24 | } 25 | 26 | if num := numImportFuncs + numOtherFuncs; cap(m.FuncAddrs) < num { 27 | m.FuncAddrs = make([]uint32, 0, num) 28 | } 29 | } 30 | 31 | func (m *FuncMap) PutFuncAddr(addr uint32) { 32 | m.FuncAddrs = append(m.FuncAddrs, addr) 33 | } 34 | 35 | func (*FuncMap) PutCallSite(uint32, int32) {} 36 | 37 | func (m *FuncMap) FindFunc(addr uint32) (index int, found bool) { 38 | index = -1 39 | 40 | i := sort.Search(len(m.FuncAddrs), func(i int) bool { 41 | return m.FuncAddrs[i] >= addr 42 | }) 43 | if i < len(m.FuncAddrs) && m.FuncAddrs[i] == addr { 44 | index = i 45 | found = true 46 | } 47 | return 48 | } 49 | 50 | func (m *FuncMap) FindCall(retAddr uint32) (init bool, funcIndex, callIndex int, stackOffset int32, retOffset uint32) { 51 | funcIndex = -1 52 | callIndex = -1 53 | 54 | if len(m.FuncAddrs) == 0 { 55 | return 56 | } 57 | 58 | firstFuncAddr := m.FuncAddrs[0] 59 | if retAddr > 0 && retAddr < firstFuncAddr { 60 | init = true 61 | return 62 | } 63 | 64 | i := sort.Search(len(m.FuncAddrs), func(i int) bool { 65 | var funcEndAddr uint32 66 | 67 | i++ 68 | if i == len(m.FuncAddrs) { 69 | funcEndAddr = math.MaxUint32 70 | } else { 71 | funcEndAddr = m.FuncAddrs[i] 72 | } 73 | 74 | return retAddr < funcEndAddr 75 | }) 76 | if i < len(m.FuncAddrs) { 77 | funcIndex = i 78 | } 79 | return 80 | } 81 | -------------------------------------------------------------------------------- /object/debug/insnmap.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package debug 6 | 7 | import ( 8 | "sort" 9 | 10 | "gate.computer/wag/object" 11 | ) 12 | 13 | // Instruction mapping from machine code to WebAssembly. SourceOffset is zero 14 | // if ObjectOffset contains non-executable data interleaved with the code. 15 | type InsnMapping struct { 16 | ObjectOffset uint32 // Machine code offset in bytes. 17 | SourceOffset uint32 // WebAssembly code offset in bytes. 18 | BlockLen int32 // Length of data block (when SourceOffset is 0). 19 | } 20 | 21 | // InsnMap is an object map which stores all available function, call, trap and 22 | // instruction information. The Mapper method must be used to obtain an actual 23 | // ObjectMapper implementation. 24 | type InsnMap struct { 25 | object.CallMap 26 | Insns []InsnMapping 27 | } 28 | 29 | func (m *InsnMap) PutInsnAddr(objectOffset, sourceOffset uint32) { 30 | m.putMapping(objectOffset, sourceOffset, 0) 31 | } 32 | 33 | func (m *InsnMap) PutDataBlock(objectOffset uint32, length int32) { 34 | m.putMapping(objectOffset, 0, length) 35 | } 36 | 37 | func (m *InsnMap) putMapping(objectOffset, sourceOffset uint32, blockLen int32) { 38 | prev := len(m.Insns) - 1 39 | if prev >= 0 && m.Insns[prev].ObjectOffset == objectOffset { 40 | // Replace previous mapping because no machine code was generated. 41 | m.Insns[prev].SourceOffset = sourceOffset 42 | m.Insns[prev].BlockLen = blockLen 43 | } else { 44 | m.Insns = append(m.Insns, InsnMapping{objectOffset, sourceOffset, blockLen}) 45 | } 46 | } 47 | 48 | func (m *InsnMap) FindCall(retAddr uint32) (init bool, funcIndex, callIndex int, stackOffset int32, retOffset uint32) { 49 | init, funcIndex, callIndex, stackOffset, retOffset = m.CallMap.FindCall(retAddr) 50 | 51 | retIndex := sort.Search(len(m.Insns), func(i int) bool { 52 | return m.Insns[i].ObjectOffset >= retAddr 53 | }) 54 | if retIndex > 0 && retIndex < len(m.Insns) { 55 | retOffset = m.Insns[retIndex].SourceOffset 56 | } 57 | return 58 | } 59 | -------------------------------------------------------------------------------- /object/callmap.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package object 6 | 7 | import ( 8 | "sort" 9 | ) 10 | 11 | // CallSite represents an offset within the text section (machine code) where a 12 | // function call is made. 13 | // 14 | // The struct size or layout will not change between minor versions. 15 | type CallSite struct { 16 | RetAddr uint32 // The address immediately after the call instruction 17 | StackOffset int32 // Calling function's stack usage at time of call 18 | } 19 | 20 | func FindCallSite(a []CallSite, retAddr uint32) (i int, found bool) { 21 | i = sort.Search(len(a), func(i int) bool { 22 | return a[i].RetAddr >= retAddr 23 | }) 24 | found = i < len(a) && a[i].RetAddr == retAddr 25 | return 26 | } 27 | 28 | // CallMap implements compile.ObjectMapper. It stores function addresses, and 29 | // sites of function calls, suspension points and traps. Instruction 30 | // information is not stored. 31 | // 32 | // Initial CallSites capacity may be allocated by initializing the field with a 33 | // non-nil, empty array. 34 | type CallMap struct { 35 | FuncMap 36 | CallSites []CallSite 37 | } 38 | 39 | func (m *CallMap) InitObjectMap(numImportFuncs, numOtherFuncs int) { 40 | if len(m.CallSites) > 0 { 41 | panic("CallSites is not empty") 42 | } 43 | 44 | m.FuncMap.InitObjectMap(numImportFuncs, numOtherFuncs) 45 | 46 | if m.CallSites == nil { 47 | // Conservative guess (assuming there are no unused functions). 48 | m.CallSites = make([]CallSite, 0, numImportFuncs+numOtherFuncs) 49 | } 50 | } 51 | 52 | func (m *CallMap) PutCallSite(retAddr uint32, stackOffset int32) { 53 | m.CallSites = append(m.CallSites, CallSite{retAddr, stackOffset}) 54 | } 55 | 56 | func (m *CallMap) FindCall(retAddr uint32) (init bool, funcIndex, callIndex int, stackOffset int32, retOffset uint32) { 57 | init, funcIndex, callIndex, stackOffset, retOffset = m.FuncMap.FindCall(retAddr) 58 | 59 | if i, found := FindCallSite(m.CallSites, retAddr); found { 60 | callIndex = i 61 | stackOffset = m.CallSites[i].StackOffset 62 | } 63 | return 64 | } 65 | -------------------------------------------------------------------------------- /section/sectionmap.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package section 6 | 7 | import ( 8 | "gate.computer/wag/internal/module" 9 | ) 10 | 11 | // ByteRange expresses a location and a length within a byte stream. The 12 | // length is at most MaxUint32, and the inclusive start and exclusive end 13 | // offsets are in range [0,MaxInt64]. 14 | type ByteRange struct { 15 | Start int64 16 | Size uint32 17 | } 18 | 19 | // End of the range (exclusive). 20 | func (r ByteRange) End() int64 { 21 | return r.Start + int64(r.Size) 22 | } 23 | 24 | // Map of section positions within the WebAssebly binary module. Map must me 25 | // initialied with MakeMap or NewMap. 26 | // 27 | // Section offset is always nonzero for standard sections; if the section is 28 | // missing, it's the position where it would be. Section length is nonzero if 29 | // the section is present. 30 | // 31 | // Sections[Custom] holds information about the last (or latest) custom 32 | // section. Its offset is zero if there are no custom sections. 33 | type Map struct { 34 | Sections [module.NumSections]ByteRange 35 | } 36 | 37 | // PutSection location on the map. 38 | func (m *Map) PutSection(sectionID byte, sectionOffset int64, sectionSize, payloadSize uint32) error { 39 | m.Sections[sectionID] = ByteRange{sectionOffset, sectionSize} 40 | 41 | // Initialize other sections' offsets during the first invocation. The 42 | // assumption is that a valid WebAssembly module contains at least one 43 | // standard section, so PutSection will be invoked at least once; the empty 44 | // module might as well have its non-existent sections at offset 0. 45 | if ID(sectionID) != Custom { 46 | // Imaginary positions of missing standard sections. 47 | for i := int(sectionID) - 1; i > 0 && m.Sections[i].Start == 0; i-- { 48 | m.Sections[i].Start = sectionOffset 49 | } 50 | 51 | // Default positions of remaining standard sections. 52 | for i := int(sectionID) + 1; i < int(module.NumSections); i++ { 53 | m.Sections[i].Start = sectionOffset 54 | } 55 | } 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /testsuite/go.sum: -------------------------------------------------------------------------------- 1 | gate.computer v0.0.0-20240908135418-e8a041e28a98 h1:SRvlGyVjEoaz+0QfRh385bDYzTdO8lyqfsiJ6can6Nc= 2 | gate.computer v0.0.0-20240908135418-e8a041e28a98/go.mod h1:VJcMZrbFq0XGPgtGmBRBdGgpy+KmvCMjO5291XEt1vY= 3 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 4 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 5 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 6 | github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= 7 | github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 8 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 9 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 10 | github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e h1:6J5obSn9umEThiYzWzndcPOZR0Qj/sVCZpH6V1G7yNE= 11 | github.com/knightsc/gapstone v0.0.0-20211014144438-5e0e64002a6e/go.mod h1:1K5hEzsMBLTPdRJKEHqBFJ8Zt2VRqDhomcQ11KH0WW4= 12 | golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= 13 | golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 14 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 15 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 16 | import.name/lock v0.0.0-20211205191324-f24933776f0b h1:BGZe3RGI6lRASOz5fdX9i00k9f5BXdWMG4DG7qohCAY= 17 | import.name/lock v0.0.0-20211205191324-f24933776f0b/go.mod h1:A/P+1darHq1WWQZaqhNcXx0mdLynmjtDPIu6g0ZxuX8= 18 | import.name/pan v0.3.0 h1:AOyiNu/zWV3BHyDspOkpdzdWdBrlb87+LGH4fSwy3k4= 19 | import.name/pan v0.3.0/go.mod h1:jzAC3o4KYbMoLWPgtxfDXjtZxa+JikGUdbQGgaDqf7U= 20 | kernel.org/pub/linux/libs/security/libcap/cap v1.2.66 h1:3P0DgIuplTGQ4dQdbBSVN0bALFZ4KtE1PndMh1SyEIM= 21 | kernel.org/pub/linux/libs/security/libcap/cap v1.2.66/go.mod h1:uBDKZw0kXu6ULhetpF19Z7izd74o3u0nO2dXIPQGmb0= 22 | kernel.org/pub/linux/libs/security/libcap/psx v1.2.66 h1:ikIhPzfkSSAEwBOU+2DWhoF+xnGUhvlMTfQjBVhvzQY= 23 | kernel.org/pub/linux/libs/security/libcap/psx v1.2.66/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24= 24 | -------------------------------------------------------------------------------- /internal/isa/arm64/unary.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package arm64 8 | 9 | import ( 10 | "gate.computer/wag/internal/gen" 11 | "gate.computer/wag/internal/gen/condition" 12 | "gate.computer/wag/internal/gen/operand" 13 | "gate.computer/wag/internal/isa/arm64/in" 14 | "gate.computer/wag/internal/isa/prop" 15 | "gate.computer/wag/wa" 16 | ) 17 | 18 | func (MacroAssembler) Unary(f *gen.Func, props uint64, x operand.O) operand.O { 19 | var o outbuf 20 | 21 | switch props & prop.MaskUnary { 22 | case prop.UnaryIntEqz: 23 | r := o.getScratchReg(f, x) 24 | o.insn(in.SUBSi.RdRnI12S2(RegDiscard, r, 0, 0, x.Size())) 25 | o.copy(f.Text.Extend(o.size)) 26 | 27 | f.Regs.Free(x.Type, r) 28 | return operand.Flags(condition.Eq) 29 | 30 | case prop.UnaryIntClz: 31 | r := o.allocResultReg(f, x) 32 | o.insn(in.CLZ.RdRn(r, r, x.Size())) 33 | o.copy(f.Text.Extend(o.size)) 34 | 35 | return operand.Reg(x.Type, r) 36 | 37 | case prop.UnaryIntCtz: 38 | r := o.allocResultReg(f, x) 39 | o.insn(in.RBIT.RdRn(r, r, x.Size())) 40 | o.insn(in.CLZ.RdRn(r, r, x.Size())) 41 | o.copy(f.Text.Extend(o.size)) 42 | 43 | return operand.Reg(x.Type, r) 44 | 45 | case prop.UnaryIntPopcnt: 46 | count := f.Regs.AllocResult(x.Type) 47 | pop := o.getScratchReg(f, x) 48 | 49 | o.insn(in.MOVZ.RdI16Hw(count, 0, 0, wa.Size64)) 50 | o.insn(in.CBZ.RtI19(pop, 5, x.Size())) // Skip to end. 51 | 52 | // Loop: 53 | o.insn(in.ADDi.RdRnI12S2(count, count, 1, 0, wa.Size64)) 54 | o.insn(in.SUBi.RdRnI12S2(RegScratch2, pop, 1, 0, x.Size())) 55 | o.insn(in.ANDSs.RdRnI6RmS2(pop, pop, 0, RegScratch2, 0, x.Size())) 56 | o.insn(in.Bc.CondI19(in.NE, in.Int19(-3))) // Loop. 57 | o.copy(f.Text.Extend(o.size)) 58 | 59 | f.Regs.Free(x.Type, pop) 60 | return operand.Reg(x.Type, count) 61 | 62 | case prop.UnaryFloat: 63 | r := o.allocResultReg(f, x) 64 | o.insn(in.UnaryFloat(props>>8).Opcode().RdRn(r, r, x.Size())) 65 | o.copy(f.Text.Extend(o.size)) 66 | 67 | return operand.Reg(x.Type, r) 68 | } 69 | 70 | panic(props) 71 | } 72 | -------------------------------------------------------------------------------- /internal/isa/arm64/imm.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package arm64 8 | 9 | import ( 10 | "gate.computer/wag/internal/gen/reg" 11 | "gate.computer/wag/internal/isa/arm64/in" 12 | "gate.computer/wag/wa" 13 | ) 14 | 15 | func (o *outbuf) moveIntImm(r reg.R, val int64) { 16 | data := uint64(val) 17 | 18 | switch { 19 | case val == 0: 20 | o.insn(in.MOVZ.RdI16Hw(r, 0, 0, wa.Size64)) 21 | 22 | case val > 0: 23 | insn := in.MOVZ // First chunk clears surroundings. 24 | 25 | for i := uint32(0); i < 4; i++ { 26 | if chunk := in.Uint16(data); chunk != 0 { 27 | o.insn(insn.RdI16Hw(r, chunk, i, wa.Size64)) 28 | insn = in.MOVK // Secondary chunks keep surroundings. 29 | } 30 | data >>= 16 31 | } 32 | 33 | case val == -1: 34 | o.insn(in.MOVN.RdI16Hw(r, 0, 0, wa.Size64)) 35 | 36 | case val < -1: 37 | var i uint32 38 | 39 | for i = 0; i < 4; i++ { 40 | if chunk := uint16(data); chunk != 0xffff { 41 | o.insn(in.MOVN.RdI16Hw(r, uint32(^chunk), i, wa.Size64)) // Set surrounding bits. 42 | break 43 | } 44 | data >>= 16 45 | } 46 | 47 | for i++; i < 4; i++ { 48 | data >>= 16 49 | if chunk := uint16(data); chunk != 0xffff { 50 | o.insn(in.MOVK.RdI16Hw(r, uint32(chunk), i, wa.Size64)) // Keep surrounding bits. 51 | } 52 | } 53 | } 54 | } 55 | 56 | func (o *outbuf) moveUintImm32(r reg.R, data uint32) { 57 | switch { 58 | case data == 0: 59 | o.insn(in.MOVZ.RdI16Hw(r, 0, 0, wa.Size64)) 60 | 61 | default: 62 | insn := in.MOVZ // First chunk clears surroundings. 63 | 64 | if chunk := data & 0xffff; chunk != 0 { 65 | o.insn(insn.RdI16Hw(r, chunk, 0, wa.Size64)) 66 | insn = in.MOVK // Secondary chunks keep surroundings. 67 | } 68 | 69 | if chunk := data >> 16; chunk != 0 { 70 | o.insn(insn.RdI16Hw(r, chunk, 1, wa.Size64)) 71 | } 72 | } 73 | } 74 | 75 | // moveImm80 moves 0x80000000 or 0x8000000000000000 depending on size. 76 | func (o *outbuf) moveImm0x80(r reg.R, t wa.Size) { 77 | hw := uint32(t)>>2 | 1 // 1 or 3 78 | o.insn(in.MOVZ.RdI16Hw(r, 0x8000, hw, wa.Size64)) 79 | } 80 | -------------------------------------------------------------------------------- /internal/isa/amd64/linker.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (amd64 || wagamd64) && !wagarm64 6 | 7 | package amd64 8 | 9 | import ( 10 | "encoding/binary" 11 | 12 | "gate.computer/wag/internal/gen/atomic" 13 | "gate.computer/wag/internal/gen/link" 14 | "gate.computer/wag/internal/obj" 15 | ) 16 | 17 | var linker Linker 18 | 19 | type Linker struct{} 20 | 21 | // UpdateNearLoad modifies a 32-bit displacement. 22 | func (Linker) UpdateNearLoad(text []byte, insnAddr int32) { 23 | accessAddr := int32(len(text)) 24 | updateAddr32(text, insnAddr, accessAddr) 25 | } 26 | 27 | // UpdateNearBranch modifies the 8-bit relocation of a JMP or Jcc instruction. 28 | func (Linker) UpdateNearBranch(text []byte, originAddr int32) { 29 | labelAddr := int32(len(text)) 30 | updateAddr8(text, originAddr, labelAddr-originAddr) 31 | } 32 | 33 | // UpdateNearBranches modifies 8-bit relocations of JMP and Jcc instructions. 34 | func (Linker) UpdateNearBranches(text []byte, originAddrs []int32) { 35 | labelAddr := int32(len(text)) 36 | for _, originAddr := range originAddrs { 37 | updateAddr8(text, originAddr, labelAddr-originAddr) 38 | } 39 | } 40 | 41 | // UpdateFarBranches modifies 32-bit relocations of JMP and Jcc instructions. 42 | func (Linker) UpdateFarBranches(text []byte, l *link.L) { 43 | labelAddr := l.FinalAddr() 44 | for _, originAddr := range l.Sites { 45 | updateAddr32(text, originAddr, labelAddr-originAddr) 46 | } 47 | } 48 | 49 | // UpdateStackCheck modifies the 32-bit displacement of a LEA instruction. 50 | func (Linker) UpdateStackCheck(text []byte, addr int32, depth int) { 51 | updateAddr32(text, addr, int32(-depth*obj.Word)) 52 | } 53 | 54 | // UpdateCalls modifies CALL instructions. 55 | func (Linker) UpdateCalls(text []byte, l *link.L) { 56 | funcAddr := l.FinalAddr() 57 | for _, retAddr := range l.Sites { 58 | atomic.PutUint32(text[retAddr-4:retAddr], uint32(funcAddr-retAddr)) 59 | } 60 | } 61 | 62 | func updateAddr8(text []byte, addr, value int32) { 63 | if value < -0x80 || value >= 0x80 { 64 | panic(value) 65 | } 66 | text[addr-1] = uint8(value) 67 | } 68 | 69 | func updateAddr32(text []byte, addr, value int32) { 70 | binary.LittleEndian.PutUint32(text[addr-4:addr], uint32(value)) 71 | } 72 | -------------------------------------------------------------------------------- /binding/bind.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package binding contains import and export utilities. 6 | package binding 7 | 8 | import ( 9 | "gate.computer/wag/compile" 10 | "gate.computer/wag/wa" 11 | ) 12 | 13 | // Well-known indexes of the import vector. Import function addresses precede 14 | // VectorIndexMemoryAddr. 15 | const ( 16 | VectorIndexLastImport = -5 17 | VectorIndexMemoryAddr = -4 18 | VectorIndexCurrentMemory = -3 19 | VectorIndexGrowMemory = -2 20 | VectorIndexTrapHandler = -1 21 | ) 22 | 23 | // ImportResolver maps symbols to library function indexes and constant values. 24 | // 25 | // ResolveFunc returns a non-negative library function index. 26 | // 27 | // ResolveGlobal returns a bit pattern the interpretation of which depends on 28 | // the scalar type. 29 | type ImportResolver interface { 30 | ResolveFunc(module, field string, sig wa.FuncType) (funcIndex uint32, err error) 31 | ResolveGlobal(module, field string, t wa.Type) (init uint64, err error) 32 | } 33 | 34 | func BindImports(mod *compile.Module, reso ImportResolver) (err error) { 35 | for i := 0; i < mod.NumImportFuncs(); i++ { 36 | index, err := reso.ResolveFunc(mod.ImportFunc(i)) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | mod.SetImportFunc(i, index) 42 | } 43 | 44 | for i := 0; i < mod.NumImportGlobals(); i++ { 45 | init, err := reso.ResolveGlobal(mod.ImportGlobal(i)) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | mod.SetImportGlobal(i, init) 51 | } 52 | 53 | return nil 54 | } 55 | 56 | // LibraryImportResolver maps symbols to vector indexes. 57 | // 58 | // ResolveFunc returns a negative vector index. The vector is addressed from 59 | // the end. VectorIndexLastImport is the largest valid index which ResolveFunc 60 | // can return. 61 | type LibraryImportResolver interface { 62 | ResolveFunc(module, field string, sig wa.FuncType) (vectorIndex int, err error) 63 | } 64 | 65 | func BindLibraryImports(lib *compile.Library, reso LibraryImportResolver) (err error) { 66 | for i := 0; i < lib.NumImportFuncs(); i++ { 67 | index, err := reso.ResolveFunc(lib.ImportFunc(i)) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | lib.SetImportFunc(i, index) 73 | } 74 | 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /internal/isa/arm64/select.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package arm64 8 | 9 | import ( 10 | "gate.computer/wag/internal/gen" 11 | "gate.computer/wag/internal/gen/condition" 12 | "gate.computer/wag/internal/gen/operand" 13 | "gate.computer/wag/internal/gen/reg" 14 | "gate.computer/wag/internal/gen/storage" 15 | "gate.computer/wag/internal/isa/arm64/in" 16 | "gate.computer/wag/wa" 17 | ) 18 | 19 | func (MacroAssembler) Select(f *gen.Func, a, b, condOperand operand.O) operand.O { 20 | var o outbuf 21 | 22 | cond := condOperand.FlagsCond() // Default case (value unspecified until storage checked). 23 | 24 | switch condOperand.Storage { 25 | case storage.Stack: 26 | o.insn(in.PopReg(RegScratch, wa.I32)) 27 | f.StackValueConsumed() 28 | o.insn(in.SUBSi.RdRnI12S2(RegDiscard, RegScratch, 0, 0, wa.Size32)) 29 | cond = condition.Ne 30 | 31 | case storage.Imm: 32 | o.moveUintImm32(RegScratch, uint32(condOperand.ImmValue())) 33 | condOperand = operand.Reg(wa.I32, RegScratch) 34 | fallthrough 35 | 36 | case storage.Reg: 37 | r := condOperand.Reg() 38 | o.insn(in.SUBSi.RdRnI12S2(RegDiscard, r, 0, 0, wa.Size32)) 39 | f.Regs.Free(wa.I32, r) 40 | cond = condition.Ne 41 | } 42 | 43 | bReg := o.allocResultReg(f, b) 44 | aReg := o.getScratchReg(f, a) 45 | 46 | if a.Type.Category() == wa.Int { 47 | o.selectInt(aReg, bReg, cond, a.Size()) 48 | } else { 49 | o.selectFloat(aReg, bReg, cond, a.Size()) 50 | } 51 | o.copy(f.Text.Extend(o.size)) 52 | 53 | f.Regs.Free(a.Type, aReg) 54 | return operand.Reg(a.Type, bReg) 55 | } 56 | 57 | func (o *outbuf) selectInt(aReg, bReg reg.R, cond condition.C, t wa.Size) { 58 | o.insn(in.CSEL.RdRnCondRm(bReg, aReg, conditions[cond], bReg, t)) 59 | } 60 | 61 | func (o *outbuf) selectFloat(aReg, bReg reg.R, cond condition.C, t wa.Size) { 62 | switch cond { 63 | case condition.OrderedAndEq: 64 | o.insn(in.Bc.CondI19(in.VS, 2)) // Skip FCSEL if unordered. 65 | 66 | case condition.UnorderedOrEq: 67 | // If unordered, copy aReg to bReg so that the second FCSEL will 68 | // select between identical values. 69 | o.insn(in.FCSEL.RdRnCondRm(bReg, aReg, in.VS, bReg, t)) 70 | } 71 | 72 | o.insn(in.FCSEL.RdRnCondRm(bReg, aReg, conditions[cond], bReg, t)) 73 | } 74 | -------------------------------------------------------------------------------- /buffer/static.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package buffer 6 | 7 | import ( 8 | "encoding/binary" 9 | "io" 10 | 11 | "gate.computer/wag/internal/pan" 12 | ) 13 | 14 | // Static is a fixed-capacity buffer, for wrapping a memory-mapped region. The 15 | // default value is a zero-capacity buffer. 16 | type Static struct { 17 | buf []byte 18 | } 19 | 20 | // MakeStatic buffer. 21 | // 22 | // This function can be used in field initializer expressions. The initialized 23 | // field must not be copied. 24 | func MakeStatic(b []byte) Static { 25 | return Static{b} 26 | } 27 | 28 | // NewStatic buffer. 29 | func NewStatic(b []byte) *Static { 30 | s := MakeStatic(b) 31 | return &s 32 | } 33 | 34 | // Capacity of the static buffer. 35 | func (s *Static) Cap() int { 36 | return cap(s.buf) 37 | } 38 | 39 | // Len doesn't panic. 40 | func (s *Static) Len() int { 41 | return len(s.buf) 42 | } 43 | 44 | // Bytes doesn't panic. 45 | func (s *Static) Bytes() []byte { 46 | return s.buf 47 | } 48 | 49 | // Write doesn't panic. 50 | func (s *Static) Write(b []byte) (n int, err error) { 51 | offset := len(s.buf) 52 | size := offset + len(b) 53 | if size <= cap(s.buf) { 54 | s.buf = s.buf[:size] 55 | } else { 56 | s.buf = s.buf[:cap(s.buf)] 57 | err = io.EOF 58 | } 59 | n = copy(s.buf[offset:], b) 60 | return 61 | } 62 | 63 | // PutByte panics if the buffer is already full. 64 | func (s *Static) PutByte(value byte) { 65 | offset := len(s.buf) 66 | if offset >= cap(s.buf) { 67 | pan.Panic(ErrSizeLimit) 68 | } 69 | s.buf = s.buf[:offset+1] 70 | s.buf[offset] = value 71 | } 72 | 73 | // Extend panics if 4 bytes cannot be appended to the buffer. 74 | func (s *Static) PutUint32(i uint32) { 75 | binary.LittleEndian.PutUint32(s.Extend(4), i) 76 | } 77 | 78 | // Extend panics if n bytes cannot be appended to the buffer. 79 | func (s *Static) Extend(n int) []byte { 80 | offset := len(s.buf) 81 | size := offset + n 82 | if size > cap(s.buf) { 83 | pan.Panic(ErrSizeLimit) 84 | } 85 | s.buf = s.buf[:size] 86 | return s.buf[offset:] 87 | } 88 | 89 | // ResizeBytes panics if n is larger than buffer capacity. 90 | func (s *Static) ResizeBytes(n int) []byte { 91 | if n > cap(s.buf) { 92 | pan.Panic(ErrSizeLimit) 93 | } 94 | s.buf = s.buf[:n] 95 | return s.buf 96 | } 97 | -------------------------------------------------------------------------------- /internal/datalayout/datalayout.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package datalayout 6 | 7 | import ( 8 | "encoding/binary" 9 | "io" 10 | "math" 11 | 12 | "gate.computer/wag/internal/data" 13 | "gate.computer/wag/internal/initexpr" 14 | "gate.computer/wag/internal/loader" 15 | "gate.computer/wag/internal/module" 16 | "gate.computer/wag/internal/obj" 17 | "gate.computer/wag/internal/pan" 18 | ) 19 | 20 | const ( 21 | MinAlignment = 16 // for amd64 SSE 22 | ) 23 | 24 | const ( 25 | maxSegments = math.MaxInt32 26 | ) 27 | 28 | func MemoryOffset(m *module.M, alignment int) int { 29 | globalsSize := len(m.Globals) * obj.Word 30 | 31 | mask := alignment - 1 32 | return (globalsSize + mask) &^ mask 33 | } 34 | 35 | func CopyGlobalsAlign(buffer data.Buffer, m *module.M, memoryOffset int) { 36 | globalsSize := len(m.Globals) * obj.Word 37 | globalsOffset := memoryOffset - globalsSize 38 | 39 | b := buffer.ResizeBytes(memoryOffset) 40 | b = b[globalsOffset:] 41 | 42 | for _, g := range m.Globals { 43 | value := m.EvaluateGlobalInitializer(int(g.InitImport), g.InitConst) 44 | binary.LittleEndian.PutUint64(b, value) 45 | b = b[obj.Word:] 46 | } 47 | } 48 | 49 | func ReadMemory(buffer data.Buffer, load *loader.L, m *module.M) { 50 | b := buffer.Bytes() 51 | memoryOffset := len(b) 52 | 53 | for i := range load.Span(maxSegments, "segment") { 54 | offset, size := readSegmentHeader(load, m, i) 55 | 56 | var ( 57 | bufOffset = memoryOffset + int(offset) 58 | bufEnd = bufOffset + int(size) 59 | ) 60 | 61 | if bufEnd > len(b) { 62 | b = buffer.ResizeBytes(bufEnd) 63 | } 64 | 65 | load.Into(b[bufOffset:bufEnd]) 66 | } 67 | } 68 | 69 | func ValidateMemory(load *loader.L, m *module.M) { 70 | for i := range load.Span(maxSegments, "segment") { 71 | _, size := readSegmentHeader(load, m, i) 72 | pan.Must(io.CopyN(io.Discard, load, int64(size))) 73 | } 74 | } 75 | 76 | func readSegmentHeader(load *loader.L, m *module.M, segmentIndex int) (offset, size uint32) { 77 | if index := load.Varuint32(); index >= m.NumMemory() { 78 | pan.Panic(module.Errorf("unknown memory: %d", index)) 79 | } 80 | 81 | offset = initexpr.ReadOffset(m, load) 82 | size = load.Varuint32() 83 | 84 | if uint64(offset)+uint64(size) > uint64(m.MemoryLimit.Init) { 85 | pan.Panic(module.Errorf("memory segment #%d exceeds initial memory size", segmentIndex)) 86 | } 87 | 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /internal/isa/amd64/select.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (amd64 || wagamd64) && !wagarm64 6 | 7 | package amd64 8 | 9 | import ( 10 | "gate.computer/wag/internal/gen" 11 | "gate.computer/wag/internal/gen/condition" 12 | "gate.computer/wag/internal/gen/operand" 13 | "gate.computer/wag/internal/gen/storage" 14 | "gate.computer/wag/internal/isa/amd64/in" 15 | "gate.computer/wag/wa" 16 | ) 17 | 18 | func (MacroAssembler) Select(f *gen.Func, a, b, condOperand operand.O) operand.O { 19 | var cond condition.C 20 | 21 | switch condOperand.Storage { 22 | case storage.Stack: 23 | in.POPo.RegScratch(&f.Text) 24 | f.StackValueConsumed() 25 | in.TEST.RegReg(&f.Text, wa.I32, RegScratch, RegScratch) 26 | cond = condition.Ne 27 | 28 | case storage.Imm: 29 | asm.Move(f, RegScratch, condOperand) 30 | condOperand = operand.Reg(wa.I32, RegScratch) 31 | fallthrough 32 | 33 | case storage.Reg: 34 | r := condOperand.Reg() 35 | in.TEST.RegReg(&f.Text, wa.I32, r, r) 36 | f.Regs.Free(wa.I32, r) 37 | cond = condition.Ne 38 | 39 | case storage.Flags: 40 | cond = condOperand.FlagsCond() 41 | } 42 | 43 | targetReg, _ := allocResultReg(f, b) 44 | 45 | switch a.Type.Category() { 46 | case wa.Int: 47 | move := conditionInsns[cond].CmovccOpcode() 48 | 49 | aReg, _ := getScratchReg(f, a) 50 | move.RegReg(&f.Text, a.Type, targetReg, aReg) 51 | f.Regs.Free(a.Type, aReg) 52 | 53 | case wa.Float: 54 | movJumps := make([]int32, 0, 1) 55 | endJumps := make([]int32, 0, 2) 56 | 57 | condInv := condition.Inverted[cond] 58 | jumpInv := conditionInsns[condInv].JccOpcodeCb() 59 | 60 | switch { 61 | case cond >= condition.MinUnorderedOrCondition: 62 | movJumps = append(movJumps, in.JPcb.Stub8(&f.Text)) // Take a if unordered, else 63 | endJumps = append(endJumps, jumpInv.Stub8(&f.Text)) // keep b if not cond, else 64 | 65 | case cond >= condition.MinOrderedAndCondition: 66 | endJumps = append(endJumps, in.JPcb.Stub8(&f.Text)) // Keep b if unordered, else 67 | endJumps = append(endJumps, jumpInv.Stub8(&f.Text)) // keep b if cond, else 68 | 69 | default: 70 | endJumps = append(endJumps, jumpInv.Stub8(&f.Text)) // Keep b if not cond, else 71 | } 72 | 73 | linker.UpdateNearBranches(f.Text.Bytes(), movJumps) 74 | 75 | asm.Move(f, targetReg, a) // take a. 76 | 77 | linker.UpdateNearBranches(f.Text.Bytes(), endJumps) 78 | } 79 | 80 | return operand.Reg(a.Type, targetReg) 81 | } 82 | -------------------------------------------------------------------------------- /internal/gen/condition/condition.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package condition 6 | 7 | type C int 8 | 9 | const ( 10 | Eq = C(iota) 11 | Ne 12 | GeS 13 | GtS 14 | GeU 15 | GtU 16 | LeS 17 | LtS 18 | LeU 19 | LtU 20 | 21 | OrderedAndEq 22 | OrderedAndNe 23 | OrderedAndGe 24 | OrderedAndGt 25 | OrderedAndLe 26 | OrderedAndLt 27 | 28 | UnorderedOrEq 29 | UnorderedOrNe 30 | UnorderedOrGe 31 | UnorderedOrGt 32 | UnorderedOrLe 33 | UnorderedOrLt 34 | ) 35 | 36 | const ( 37 | MinOrderedAndCondition = OrderedAndEq 38 | MinUnorderedOrCondition = UnorderedOrEq 39 | ) 40 | 41 | var Inverted = [22]C{ 42 | Eq: Ne, 43 | Ne: Eq, 44 | GeS: LtS, 45 | GtS: LeS, 46 | GeU: LtU, 47 | GtU: LeU, 48 | LeS: GtS, 49 | LtS: GeS, 50 | LeU: GtU, 51 | LtU: GeU, 52 | OrderedAndEq: UnorderedOrNe, 53 | OrderedAndNe: UnorderedOrEq, 54 | OrderedAndGe: UnorderedOrLt, 55 | OrderedAndGt: UnorderedOrLe, 56 | OrderedAndLe: UnorderedOrGt, 57 | OrderedAndLt: UnorderedOrGe, 58 | UnorderedOrEq: OrderedAndNe, 59 | UnorderedOrNe: OrderedAndEq, 60 | UnorderedOrGe: OrderedAndLt, 61 | UnorderedOrGt: OrderedAndLe, 62 | UnorderedOrLe: OrderedAndGt, 63 | UnorderedOrLt: OrderedAndGe, 64 | } 65 | 66 | var strings = []string{ 67 | Eq: "equal", 68 | Ne: "not-equal", 69 | GeS: "signed greater-or-equal", 70 | GtS: "signed greater", 71 | GeU: "unsigned greater-or-equal", 72 | GtU: "unsigned greater", 73 | LeS: "signed less-or-equal", 74 | LtS: "signed less", 75 | LeU: "unsigned less-or-equal", 76 | LtU: "unsigned less", 77 | OrderedAndEq: "ordered-and-equal", 78 | OrderedAndNe: "ordered-and-not-equal", 79 | OrderedAndGe: "ordered-and-greater-or-equal", 80 | OrderedAndGt: "ordered-and-greater", 81 | OrderedAndLe: "ordered-and-less-or-equal", 82 | OrderedAndLt: "ordered-and-less", 83 | UnorderedOrEq: "unordered-or-equal", 84 | UnorderedOrNe: "unordered-or-not-equal", 85 | UnorderedOrGe: "unordered-or-greater-or-equal", 86 | UnorderedOrGt: "unordered-or-greater", 87 | UnorderedOrLe: "unordered-or-less-or-equal", 88 | UnorderedOrLt: "unordered-or-less", 89 | } 90 | 91 | func (f C) String() string { 92 | if i := int(f); i < len(strings) { 93 | return strings[i] 94 | } else { 95 | return "" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /section/custom.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package section 6 | 7 | import ( 8 | "io" 9 | 10 | "gate.computer/wag/binary" 11 | "gate.computer/wag/internal/loader" 12 | "gate.computer/wag/internal/section" 13 | ) 14 | 15 | // Unwrapped may be returned by custom section content loaders. 16 | var Unwrapped error = section.Unwrapped 17 | 18 | // Reader is suitable for reading sections. 19 | type Reader = binary.Reader 20 | 21 | // Loader is suitable for use with section loading functions. 22 | type Loader = loader.Loader 23 | 24 | // NewLoader creates a WebAssembly section loader. 25 | func NewLoader(r Reader) Loader { 26 | return loader.New(r, 0) 27 | } 28 | 29 | type CustomContentLoader func(sectionName string, r Reader, payloadSize uint32) error 30 | 31 | type customLoaderMux struct { 32 | loaders map[string]CustomContentLoader 33 | } 34 | 35 | func CustomLoader(loaders map[string]CustomContentLoader) func(Reader, uint32) error { 36 | mux := customLoaderMux{loaders: loaders} 37 | return mux.load 38 | } 39 | 40 | func (mux customLoaderMux) load(r Reader, length uint32) error { 41 | nameLen, n, err := binary.Varuint32(r) 42 | if err != nil { 43 | return err 44 | } 45 | length -= uint32(n) 46 | 47 | nameData := make([]byte, nameLen) 48 | if _, err := io.ReadFull(r, nameData); err != nil { 49 | return err 50 | } 51 | name := loader.String(nameData, "custom section name") 52 | length -= nameLen 53 | 54 | if f := mux.loaders[name]; f != nil { 55 | return f(name, r, length) 56 | } 57 | 58 | _, err = io.CopyN(io.Discard, r, int64(length)) 59 | return err 60 | } 61 | 62 | type CustomMapping ByteRange 63 | 64 | // Loader of arbitrary custom section. Remembers position, discards content. 65 | func (target *CustomMapping) Loader(sectionMap *Map) CustomContentLoader { 66 | return func(_ string, r Reader, length uint32) (err error) { 67 | *target = CustomMapping(sectionMap.Sections[Custom]) // The latest one. 68 | _, err = io.CopyN(io.Discard, r, int64(length)) 69 | return 70 | } 71 | } 72 | 73 | type CustomSections struct { 74 | Sections map[string][]byte 75 | } 76 | 77 | func (cs *CustomSections) Load(name string, r Reader, length uint32) (err error) { 78 | data := make([]byte, length) 79 | 80 | _, err = io.ReadFull(r, data) 81 | if err != nil { 82 | return 83 | } 84 | 85 | if cs.Sections == nil { 86 | cs.Sections = make(map[string][]byte) 87 | } 88 | 89 | cs.Sections[name] = data 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /testsuite/resolver.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "strings" 11 | 12 | "gate.computer/gate/runtime/abi/rt" 13 | "gate.computer/wag/compile" 14 | "gate.computer/wag/wa" 15 | ) 16 | 17 | type libResolver struct{} 18 | 19 | func (libResolver) ResolveFunc(module, field string, sig wa.FuncType) (int, error) { 20 | m, found := rt.ImportFuncs()[module] 21 | if !found { 22 | panic(module) 23 | } 24 | 25 | index, found := m[field] 26 | if !found { 27 | panic(field) 28 | } 29 | 30 | return index, nil 31 | } 32 | 33 | type modResolver struct { 34 | L compile.Library 35 | } 36 | 37 | func (reso modResolver) ResolveFunc(module, field string, expectSig wa.FuncType) (uint32, error) { 38 | symbol := module + "_" + strings.Replace(strings.Replace(field, "->", "_to_", -1), "-", "_", -1) 39 | 40 | index, actualSig, found := reso.L.ExportFunc(symbol) 41 | if !found { 42 | return 0, fmt.Errorf("unknown function imported: %q.%q", module, field) 43 | } 44 | 45 | if !expectSig.Equal(actualSig) { 46 | return 0, fmt.Errorf("function %s.%s%s imported with wrong type: %s", module, field, actualSig, expectSig) 47 | } 48 | 49 | return index, nil 50 | } 51 | 52 | func (reso modResolver) ResolveGlobal(module, field string, t wa.Type) (uint64, error) { 53 | switch module { 54 | case "spectest": 55 | switch field { 56 | case "global_i32": 57 | if t == wa.I32 { 58 | return 666, nil 59 | } 60 | 61 | case "global_i64": 62 | if t == wa.I64 { 63 | return 666, nil 64 | } 65 | 66 | case "global_f32": 67 | if t == wa.F32 { 68 | return uint64(math.Float32bits(666)), nil 69 | } 70 | 71 | case "global_f64": 72 | if t == wa.F64 { 73 | return math.Float64bits(666), nil 74 | } 75 | 76 | default: 77 | return 0, fmt.Errorf("unknown global imported: %q.%q", module, field) 78 | } 79 | 80 | case "test": 81 | switch field { 82 | case "global-i32": 83 | if t == wa.I32 { 84 | return 0, nil 85 | } 86 | 87 | case "global-f32": 88 | if t == wa.F32 { 89 | return 0, nil 90 | } 91 | 92 | default: 93 | return 0, fmt.Errorf("unknown global imported: %q.%q", module, field) 94 | } 95 | 96 | default: 97 | return 0, fmt.Errorf("unknown global imported: %q.%q", module, field) 98 | } 99 | 100 | return 0, fmt.Errorf("global %s.%s imported with wrong type: %s", module, field, t) 101 | } 102 | -------------------------------------------------------------------------------- /internal/isa/arm64/convert.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package arm64 8 | 9 | import ( 10 | "gate.computer/wag/internal/gen" 11 | "gate.computer/wag/internal/gen/operand" 12 | "gate.computer/wag/internal/isa/arm64/in" 13 | "gate.computer/wag/internal/isa/prop" 14 | "gate.computer/wag/trap" 15 | "gate.computer/wag/wa" 16 | ) 17 | 18 | func (MacroAssembler) Convert(f *gen.Func, props uint64, resultType wa.Type, source operand.O) operand.O { 19 | var o outbuf 20 | 21 | switch props & prop.MaskConversion { 22 | case prop.ConversionMote: 23 | r := o.allocResultReg(f, source) 24 | o.insn(in.UnaryFloat(props>>8).Opcode().RdRn(r, r, source.Size())) 25 | o.copy(f.Text.Extend(o.size)) 26 | 27 | return operand.Reg(resultType, r) 28 | 29 | case prop.ConversionFloatToInt: 30 | resultReg := f.Regs.AllocResult(wa.I64) 31 | sourceReg := o.getScratchReg(f, source) 32 | o.insn(in.MSR_FPSR.Rt(RegZero)) 33 | o.insn(in.Conversion(props>>8).Opcode().RdRn(resultReg, sourceReg, source.Size(), resultType.Size())) 34 | o.insn(in.MRS_FPSR.Rt(RegScratch)) 35 | o.insn(in.TBZ.RtI14Bit(RegScratch, 2, 0)) // Skip next instruction if valid operation. 36 | o.trap(f, trap.IntegerOverflow) 37 | o.copy(f.Text.Extend(o.size)) 38 | 39 | f.Regs.Free(wa.F64, sourceReg) 40 | return operand.Reg(resultType, resultReg) 41 | 42 | case prop.ConversionIntToFloat: 43 | resultReg := f.Regs.AllocResult(wa.F64) 44 | sourceReg := o.getScratchReg(f, source) 45 | o.insn(in.Conversion(props>>8).Opcode().RdRn(resultReg, sourceReg, resultType.Size(), source.Size())) 46 | o.copy(f.Text.Extend(o.size)) 47 | 48 | f.Regs.Free(wa.I64, sourceReg) 49 | return operand.Reg(resultType, resultReg) 50 | } 51 | 52 | panic(props) 53 | } 54 | 55 | func (MacroAssembler) TruncSat(f *gen.Func, props uint64, resultType wa.Type, source operand.O) operand.O { 56 | var o outbuf 57 | 58 | resultReg := f.Regs.AllocResult(wa.I64) 59 | sourceReg := o.getScratchReg(f, source) 60 | o.insn(in.Conversion(props>>8).Opcode().RdRn(resultReg, sourceReg, source.Size(), resultType.Size())) 61 | o.copy(f.Text.Extend(o.size)) 62 | 63 | f.Regs.Free(wa.F64, sourceReg) 64 | return operand.Reg(resultType, resultReg) 65 | } 66 | 67 | func (MacroAssembler) Extend(f *gen.Func, props uint32, resultType wa.Type, source operand.O) operand.O { 68 | var o outbuf 69 | 70 | r := o.allocResultReg(f, source) 71 | o.insn(in.RegRegNSf(props).RdRn(r, r, resultType.Size())) 72 | o.copy(f.Text.Extend(o.size)) 73 | 74 | return operand.Reg(resultType, r) 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Wag is a [WebAssembly](https://webassembly.org) compiler implemented as a 2 | [Go](https://golang.org) package. 3 | 4 | - License: [3-clause BSD](LICENSE) 5 | - Author: Timo Savola 6 | 7 | 8 | Features 9 | -------- 10 | 11 | - The input is a wasm binary module. 12 | 13 | - The output is machine code. 14 | 15 | - It is only a compiler. A runtime environment for the compiled program, 16 | including all import functions, needs to be implemented separately. Wag has 17 | been developed for the [Gate](https://gate.computer/gate) runtime. 18 | 19 | - Single-pass, fast ahead-of-time compilation. 20 | 21 | - The generated code requires minimal runtime support; it's designed to be 22 | executed in an isolated environment. Calling standard library ABIs is not 23 | directly supported. 24 | 25 | - Supports snapshot-and-restore across compiler versions and CPU architectures. 26 | 27 | - Supports breakpoint debugging via recompilation. 28 | 29 | - Cross-compilation is supported via Go build tags. If `wagamd64` is 30 | specified, the x86-64 code generator is used regardless of host architecture, 31 | and CPU feature detection is disabled with pessimistic assumptions. Likewise 32 | for `wagarm64` (but feature detection is not currently used for ARM64 in any 33 | case). 34 | 35 | 36 | Status 37 | ------ 38 | 39 | - Supports WebAssembly version 1 (wasm32). 40 | 41 | - Supports non-trapping float-to-int conversions extension, sign-extension 42 | instructions extension, and partially bulk memory operations extension. 43 | 44 | - Supports x86-64 and ARM64 code generation. 45 | 46 | - Generated x86-64 code requires SSE4.1 floating-point instructions (available 47 | since 2007). 48 | 49 | 50 | Security 51 | -------- 52 | 53 | [Spectre](https://spectreattack.com) variant 1: Out-of-bounds linear memory 54 | access detection requires that addressable but unallocated memory is 55 | inaccessible. It naturally prevents conditional branch exploitation. 56 | 57 | Spectre variant 2: On x86-64, [Retpoline](https://support.google.com/faqs/answer/7625886) 58 | is used to protect the runtime environment (although user programs shouldn't be 59 | able to inject arbitrary addresses into the branch target buffer). 60 | 61 | 62 | Testing 63 | ------- 64 | 65 | Requires Linux, Make, Go, Python, [Capstone](https://www.capstone-engine.org), 66 | and a recent version of [WABT](https://github.com/WebAssembly/wabt). 67 | The applicable parts of the WebAssembly spec testsuite are run. Code execution 68 | tests are implemented in a separate Go module in the testsuite subdirectory (to 69 | work around circular dependencies). All tests can be run by checking out Git 70 | submodules and running `make check`. 71 | 72 | -------------------------------------------------------------------------------- /internal/gen/regalloc/regalloc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package regalloc 6 | 7 | import ( 8 | "testing" 9 | 10 | "gate.computer/wag/internal/gen/reg" 11 | "gate.computer/wag/internal/isa/reglayout" 12 | "gate.computer/wag/wa" 13 | ) 14 | 15 | func (a *Allocator) testAllocated(t wa.Type, r reg.R) bool { 16 | return a.categories[t.Category()].testAllocated(r) 17 | } 18 | 19 | func (s *state) testAllocated(r reg.R) bool { 20 | return s.available&(1< r0 { 44 | t.Fatal(r0, "-", r2, "allocated") 45 | } 46 | } else { 47 | if r2 <= r0 { 48 | t.Fatal(r0, "-", r2, "not allocated") 49 | } 50 | } 51 | } 52 | } 53 | 54 | if r := a.AllocResult(wa.I32); r != reg.Result { 55 | t.Fatal("allocation succeeded:", r) 56 | } 57 | 58 | for r0 := first; r0 <= last; r0++ { 59 | a.Free(wa.I32, r0) 60 | if a.testAllocated(wa.I32, r0) { 61 | t.Fatal(r0, "is still allocated") 62 | } 63 | 64 | r1 := a.AllocResult(wa.I32) 65 | if r1 == reg.Result { 66 | t.Fatal("allocation failed") 67 | } 68 | if r1 != first { 69 | t.Fatal(r1, "is not", first) 70 | } 71 | a.Free(wa.I32, r1) 72 | } 73 | 74 | a.CheckNoneAllocated() 75 | 76 | r1 := a.AllocResult(wa.I32) 77 | if r1 == reg.Result { 78 | t.Fatal("#1") 79 | } 80 | if !a.testAllocated(wa.I32, r1) { 81 | t.Fatal("#2") 82 | } 83 | if r1 != reglayout.AllocIntFirst { 84 | t.Fatal("#3") 85 | } 86 | 87 | r2 := a.AllocResult(wa.I32) 88 | if r2 == reg.Result { 89 | t.Fatal("#4") 90 | } 91 | if !a.testAllocated(wa.I32, r2) { 92 | t.Fatal("#5") 93 | } 94 | if r2 == r1 { 95 | t.Fatal("#6") 96 | } 97 | if r2 != reglayout.AllocIntFirst+1 { 98 | t.Fatal("#7") 99 | } 100 | 101 | r1f := a.AllocResult(wa.F32) 102 | if r1f == reg.Result { 103 | t.Fatal("#8") 104 | } 105 | if !a.testAllocated(wa.F32, r1f) { 106 | t.Fatal("#9") 107 | } 108 | if r1f != reglayout.AllocFloatFirst { 109 | t.Fatal("#10") 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /section/copy.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package section 6 | 7 | import ( 8 | "encoding/binary" 9 | "io" 10 | 11 | "gate.computer/wag/internal" 12 | "gate.computer/wag/internal/loader" 13 | "gate.computer/wag/internal/pan" 14 | "gate.computer/wag/internal/section" 15 | ) 16 | 17 | // CopyStandardSection with the given type if one is found. The returned 18 | // length includes the copied section's header and payload (everything that was 19 | // written). Custom sections preceding the standard section are processed by 20 | // customLoader (or discarded if it's nil) - they are not included in the 21 | // returned length. If another standard section type is found, it is left 22 | // untouched (the reader will be backed up before the section id) and zero 23 | // length is returned. If no standard section is encountered, zero length and 24 | // io.EOF are returned. io.EOF is returned only when it occurs between 25 | // sections. 26 | func CopyStandardSection(w io.Writer, l Loader, id ID, customLoader func(r Reader, payloadSize uint32) error) (length int64, err error) { 27 | if internal.DontPanic() { 28 | defer func() { 29 | if x := recover(); x != nil { 30 | err = pan.Error(x) 31 | } 32 | }() 33 | } 34 | 35 | load := loader.Get(l) 36 | 37 | switch _, foundID := section.Find(id, load, nil, customLoader); foundID { 38 | case id: 39 | length, err = copySection(w, id, load) 40 | 41 | case 0: 42 | err = io.EOF 43 | } 44 | return 45 | } 46 | 47 | // SkipCustomSections until the next standard section. The skipped sections 48 | // are processed by customLoader (or discarded if it's nil). If no standard 49 | // section is encountered, io.EOF is returned. io.EOF is returned only when it 50 | // occurs between sections. 51 | func SkipCustomSections(l Loader, customLoader func(Reader, uint32) error) (err error) { 52 | if internal.DontPanic() { 53 | defer func() { 54 | if x := recover(); x != nil { 55 | err = pan.Error(x) 56 | } 57 | }() 58 | } 59 | 60 | load := loader.Get(l) 61 | 62 | if _, id := section.Find(0, load, nil, customLoader); id == 0 { 63 | err = io.EOF 64 | } 65 | return 66 | } 67 | 68 | func copySection(w io.Writer, id ID, load *loader.L) (length int64, err error) { 69 | payloadSize := load.Varuint32() 70 | 71 | head := make([]byte, 1+binary.MaxVarintLen32) 72 | head[0] = byte(id) 73 | n := binary.PutUvarint(head[1:], uint64(payloadSize)) 74 | 75 | n, err = w.Write(head[:1+n]) 76 | length += int64(n) 77 | if err != nil { 78 | return 79 | } 80 | 81 | m, err := io.CopyN(w, load, int64(payloadSize)) 82 | length += m 83 | return 84 | } 85 | -------------------------------------------------------------------------------- /object/stack/stacktrace.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stack 6 | 7 | import ( 8 | "encoding/binary" 9 | "errors" 10 | "fmt" 11 | "math" 12 | 13 | "gate.computer/wag/wa" 14 | ) 15 | 16 | type TextMap interface { 17 | FindFunc(addr uint32) (index int, found bool) 18 | FindCall(retAddr uint32) (init bool, funcIndex, callIndex int, stackOffset int32, retOffset uint32) 19 | } 20 | 21 | type Frame struct { 22 | FuncIndex int 23 | RetOffset int // Zero if information is not available. 24 | Locals []uint64 // If function signatures are available. 25 | } 26 | 27 | func Trace(stack []byte, textAddr uint64, textMap TextMap, funcSigs []wa.FuncType) (stacktrace []Frame, err error) { 28 | if n := len(stack); n == 0 || n&7 != 0 { 29 | err = fmt.Errorf("invalid stack size %d", n) 30 | return 31 | } 32 | 33 | for len(stack) > 0 { 34 | absRetAddr := binary.LittleEndian.Uint64(stack[:8]) 35 | 36 | retAddr := absRetAddr - textAddr 37 | if retAddr > math.MaxUint32 { 38 | err = fmt.Errorf("return address 0x%x is not in text section", absRetAddr) 39 | return 40 | } 41 | 42 | init, funcIndex, callIndex, stackOffset, retOffset := textMap.FindCall(uint32(retAddr)) 43 | if init { 44 | if callIndex < 0 { 45 | err = fmt.Errorf("unknown initial call return address 0x%x", retAddr) 46 | } else if !(stackOffset == 0 || stackOffset == 8) { 47 | err = fmt.Errorf("initial function call site 0x%x has inconsistent stack offset %d", retAddr, stackOffset) 48 | } 49 | return 50 | } 51 | 52 | if stackOffset < 0 { 53 | err = fmt.Errorf("unknown return address 0x%x", retAddr) 54 | return 55 | } 56 | if funcIndex < 0 { 57 | err = fmt.Errorf("function not found for return address 0x%x", retAddr) 58 | return 59 | } 60 | if stackOffset == 0 || stackOffset&7 != 0 { 61 | err = fmt.Errorf("invalid stack offset %d", stackOffset) 62 | return 63 | } 64 | 65 | var locals []uint64 66 | 67 | if funcSigs != nil { 68 | numParams := len(funcSigs[funcIndex].Params) 69 | numOthers := int(stackOffset/8) - 1 70 | numLocals := numParams + numOthers 71 | locals = make([]uint64, numLocals) 72 | 73 | for i := 0; i < numParams; i++ { 74 | locals[i] = binary.LittleEndian.Uint64(stack[(numLocals-i+1)*8:]) 75 | } 76 | 77 | for i := 0; i < numOthers; i++ { 78 | locals[numParams+i] = binary.LittleEndian.Uint64(stack[(numOthers-i)*8:]) 79 | } 80 | } 81 | 82 | stacktrace = append(stacktrace, Frame{ 83 | FuncIndex: funcIndex, 84 | RetOffset: int(retOffset), 85 | Locals: locals, 86 | }) 87 | 88 | stack = stack[stackOffset:] 89 | } 90 | 91 | err = errors.New("ran out of stack before initial call") 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /testsuite/testdata/library/library.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Update ../library.wasm by running 'go generate' in parent directory. 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #define EXPORT __attribute__((visibility("default"))) 13 | 14 | enum op { 15 | NOP, 16 | PRINT_I32, 17 | PRINT_I64, 18 | PRINT_F32, 19 | PRINT_F64, 20 | }; 21 | 22 | static inline uint32_t float_bits(float v) 23 | { 24 | return *(uint32_t *) &v; 25 | } 26 | 27 | static inline uint64_t double_bits(double v) 28 | { 29 | return *(uint64_t *) &v; 30 | } 31 | 32 | static inline void write8(uint64_t v) 33 | { 34 | rt_write8(v); 35 | } 36 | 37 | static inline void write44(uint32_t v1, uint32_t v2) 38 | { 39 | rt_write8(((uint64_t) v1) | (((uint64_t) v2) << 32)); 40 | } 41 | 42 | static inline void write_header(enum op op1, enum op op2, size_t content_length) 43 | { 44 | uint64_t size = 8 + 8 + (uint64_t) content_length; 45 | if (size > 65536) 46 | rt_trap(1); 47 | 48 | rt_write8(size); // Code, domain and index are zero. 49 | rt_write8(((uint64_t) op1) | (((uint64_t) op2) << 8)); 50 | } 51 | 52 | static inline void read_reply(void) 53 | { 54 | uint64_t header = rt_read8(); 55 | uint32_t size = (uint32_t) header; 56 | uint32_t misc = (uint32_t) (header >> 32); 57 | if (misc != 0 || size != 8) 58 | rt_trap(1); 59 | } 60 | 61 | EXPORT void spectest_print(void) {} 62 | 63 | EXPORT void spectest_print_f32(float v) 64 | { 65 | write_header(PRINT_F32, NOP, 4); 66 | write44(float_bits(v), 0); 67 | read_reply(); 68 | } 69 | 70 | EXPORT void spectest_print_f64(double v) 71 | { 72 | write_header(PRINT_F64, NOP, 8); 73 | write8(double_bits(v)); 74 | read_reply(); 75 | } 76 | 77 | EXPORT void spectest_print_f64_f64(double v1, double v2) 78 | { 79 | write_header(PRINT_F64, PRINT_F64, 16); 80 | write8(double_bits(v1)); 81 | write8(double_bits(v2)); 82 | read_reply(); 83 | } 84 | 85 | EXPORT void spectest_print_i32(uint32_t v) 86 | { 87 | write_header(PRINT_I32, NOP, 4); 88 | write44(v, 0); 89 | read_reply(); 90 | } 91 | 92 | EXPORT void spectest_print_i32_f32(uint32_t v1, float v2) 93 | { 94 | write_header(PRINT_I32, PRINT_F32, 8); 95 | write44(v1, float_bits(v2)); 96 | read_reply(); 97 | } 98 | 99 | EXPORT void spectest_print_i64(uint64_t v) 100 | { 101 | write_header(PRINT_I64, NOP, 8); 102 | write8(v); 103 | read_reply(); 104 | } 105 | 106 | EXPORT void test_func(void) {} 107 | EXPORT void test_func_f32(float v) {} 108 | EXPORT void test_func_i32(uint32_t v) {} 109 | EXPORT uint32_t test_func_i32_to_i32(uint32_t v) { return v; } 110 | EXPORT uint64_t test_func_i64_to_i64(uint64_t v) { return v; } 111 | EXPORT float test_func_to_f32(void) { return 0; } 112 | EXPORT uint32_t test_func_to_i32(void) { return 0; } 113 | -------------------------------------------------------------------------------- /internal/section/section.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package section 6 | 7 | import ( 8 | "errors" 9 | "io" 10 | "math" 11 | 12 | "gate.computer/wag/binary" 13 | "gate.computer/wag/internal/loader" 14 | "gate.computer/wag/internal/module" 15 | "gate.computer/wag/internal/pan" 16 | ) 17 | 18 | var Unwrapped = errors.New("section unwrapped") //lint:ignore ST1012 special 19 | 20 | // ModuleMapper gathers information about positions of WebAssembly sections. 21 | type ModuleMapper interface { 22 | // PutSection is invoked just after the payload length has been read. 23 | // Section offset is the position of the section id. Section size covers 24 | // section id byte, encoded payload length, and payload content. 25 | PutSection(sectionID byte, sectionOffset int64, sectionSize, payloadSize uint32) error 26 | } 27 | 28 | func Find( 29 | findID module.SectionID, 30 | load *loader.L, 31 | mapper ModuleMapper, 32 | customLoader func(binary.Reader, uint32) error, 33 | ) (int64, module.SectionID) { 34 | for { 35 | sectionOffset := load.Tell() 36 | 37 | sectionID, err := load.ReadByte() 38 | if err == io.EOF { 39 | return sectionOffset, 0 40 | } 41 | pan.Check(err) 42 | 43 | id := module.SectionID(sectionID) 44 | 45 | switch { 46 | case id == module.SectionCustom: 47 | payloadSize := LoadPayloadSize(sectionOffset, id, load, mapper) 48 | payloadOffset := load.Tell() 49 | partial := false 50 | 51 | if customLoader != nil { 52 | err := customLoader(load, payloadSize) 53 | if err == Unwrapped { 54 | partial = true 55 | } 56 | pan.Check(err) 57 | } else { 58 | pan.Must(io.CopyN(io.Discard, load, int64(payloadSize))) 59 | } 60 | 61 | CheckConsumption(load, payloadOffset, payloadSize, partial) 62 | 63 | case id == findID: 64 | return sectionOffset, id 65 | 66 | default: 67 | load.UnreadByte() 68 | return sectionOffset, id 69 | } 70 | } 71 | } 72 | 73 | func LoadPayloadSize( 74 | sectionOffset int64, 75 | id module.SectionID, 76 | load *loader.L, 77 | mapper ModuleMapper, 78 | ) uint32 { 79 | payloadSize := load.Varuint32() 80 | sectionSize := load.Tell() - sectionOffset + int64(payloadSize) 81 | if sectionSize > math.MaxInt32 { 82 | pan.Panic(module.Error("section end offset out of bounds")) 83 | } 84 | 85 | if mapper != nil { 86 | pan.Check(mapper.PutSection(byte(id), sectionOffset, uint32(sectionSize), payloadSize)) 87 | } 88 | 89 | return payloadSize 90 | } 91 | 92 | func CheckConsumption(load *loader.L, payloadOffset int64, payloadSize uint32, partial bool) { 93 | consumed := load.Tell() - payloadOffset 94 | if consumed == int64(payloadSize) { 95 | return 96 | } 97 | if partial && consumed < int64(payloadSize) { 98 | return 99 | } 100 | pan.Panic(module.Errorf("section size is %d but %d bytes was read", payloadSize, consumed)) 101 | } 102 | -------------------------------------------------------------------------------- /internal/isa/arm64/memory.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package arm64 8 | 9 | import ( 10 | "gate.computer/wag/internal/code" 11 | "gate.computer/wag/internal/gen" 12 | "gate.computer/wag/internal/gen/operand" 13 | "gate.computer/wag/internal/gen/reg" 14 | "gate.computer/wag/internal/gen/storage" 15 | "gate.computer/wag/internal/isa/arm64/in" 16 | "gate.computer/wag/trap" 17 | "gate.computer/wag/wa" 18 | ) 19 | 20 | func (MacroAssembler) Load(f *gen.Func, props uint64, index operand.O, resultType wa.Type, align, offset uint32) operand.O { 21 | var o outbuf 22 | 23 | r := f.Regs.AllocResult(resultType) 24 | o.access(f, in.Memory(props), r, index, offset) 25 | o.copy(f.Text.Extend(o.size)) 26 | 27 | return operand.Reg(resultType, r) 28 | } 29 | 30 | func (MacroAssembler) Store(f *gen.Func, props uint64, index, x operand.O, align, offset uint32) { 31 | var o outbuf 32 | 33 | r := RegZero 34 | if !(x.Storage == storage.Imm && x.ImmValue() == 0) { 35 | r = RegResult 36 | o.move(f, r, x) 37 | } 38 | 39 | o.access(f, in.Memory(props), r, index, offset) 40 | o.copy(f.Text.Extend(o.size)) 41 | } 42 | 43 | func (o *outbuf) access(f *gen.Func, op in.Memory, dataReg reg.R, index operand.O, offset uint32) { 44 | if offset >= 0x80000000 { 45 | f.ValueBecameUnreachable(index) 46 | asm.Trap(f, trap.MemoryAccessOutOfBounds) 47 | return 48 | } 49 | 50 | // Wrapping index to 32 bits avoids speculation beyond memory mapping. 51 | 52 | switch index.Storage { 53 | case storage.Imm: 54 | o.moveUintImm32(RegScratch, uint32(index.ImmValue())) 55 | 56 | case storage.Stack: 57 | o.insn(in.PopReg(RegScratch, wa.I32)) 58 | f.StackValueConsumed() 59 | 60 | case storage.Reg: 61 | r := index.Reg() 62 | o.insn(in.ORRs.RdRnI6RmS2(RegScratch, RegZero, 0, r, 0, wa.Size32)) 63 | f.Regs.Free(index.Type, r) 64 | 65 | case storage.Flags: 66 | o.setBool(RegScratch, index.FlagsCond()) 67 | } 68 | 69 | o.moveUintImm32(RegScratch2, offset) 70 | o.insn(in.ADDs.RdRnI6RmS2(RegScratch, RegScratch, 0, RegScratch2, 0, wa.Size64)) 71 | o.insn(op.OpcodeReg().RtRnSOptionRm(dataReg, RegMemoryBase, in.Unscaled, in.UX, RegScratch)) 72 | 73 | f.MapCallAddr(o.addr(&f.Text)) // Address of instruction pointer during SIGSEGV handling. 74 | } 75 | 76 | func (MacroAssembler) CurrentMemory(f *gen.Func) int32 { 77 | getCurrentMemoryPages(&f.Text) 78 | return f.Text.Addr 79 | } 80 | 81 | func getCurrentMemoryPages(text *code.Buf) { 82 | var o outbuf 83 | 84 | o.insn(in.LDUR.RtRnI9(RegScratch, RegTextBase, in.Int9(gen.VectorOffsetCurrentMemory), wa.I64)) 85 | o.insn(in.BLR.Rn(RegScratch)) 86 | o.copy(text.Extend(o.size)) 87 | } 88 | 89 | func (MacroAssembler) GrowMemory(f *gen.Func) int32 { 90 | var o outbuf 91 | 92 | o.insn(in.LDUR.RtRnI9(RegScratch, RegTextBase, in.Int9(gen.VectorOffsetGrowMemory), wa.I64)) 93 | o.insn(in.BLR.Rn(RegScratch)) 94 | o.copy(f.Text.Extend(o.size)) 95 | 96 | return f.Text.Addr 97 | } 98 | -------------------------------------------------------------------------------- /buffer/dynamic.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package buffer 6 | 7 | import ( 8 | "encoding/binary" 9 | "errors" 10 | ) 11 | 12 | // Dynamic is a variable-capacity buffer. The default value is a valid buffer. 13 | type Dynamic struct { 14 | buf []byte 15 | maxSize int // For limiting allocation; not enforced by this implementation. 16 | } 17 | 18 | // MakeDynamic buffer. 19 | // 20 | // This function can be used in field initializer expressions. The initialized 21 | // field must not be copied. 22 | func MakeDynamic(b []byte) Dynamic { 23 | return MakeDynamicHint(b, 0) 24 | } 25 | 26 | // MakeDynamicHint avoids making excessive allocations if the maximum buffer 27 | // size can be estimated in advance. The slice must be empty. 28 | // 29 | // This function can be used in field initializer expressions. The initialized 30 | // field must not be copied. 31 | func MakeDynamicHint(b []byte, maxSizeHint int) Dynamic { 32 | return Dynamic{b, maxSizeHint} 33 | } 34 | 35 | // NewDynamic buffer. 36 | func NewDynamic(b []byte) *Dynamic { 37 | return NewDynamicHint(b, 0) 38 | } 39 | 40 | // NewDynamicHint avoids making excessive allocations if the maximum buffer 41 | // size can be estimated in advance. The slice must be empty. 42 | func NewDynamicHint(b []byte, maxSizeHint int) *Dynamic { 43 | d := MakeDynamicHint(b, maxSizeHint) 44 | return &d 45 | } 46 | 47 | // Len doesn't panic. 48 | func (d *Dynamic) Len() int { 49 | return len(d.buf) 50 | } 51 | 52 | // Bytes doesn't panic. 53 | func (d *Dynamic) Bytes() []byte { 54 | return d.buf 55 | } 56 | 57 | // PutBytes doesn't panic unless out of memory. 58 | func (d *Dynamic) PutByte(value byte) { 59 | d.Extend(1)[0] = value 60 | } 61 | 62 | // Extend doesn't panic unless out of memory. 63 | func (d *Dynamic) PutUint32(i uint32) { 64 | binary.LittleEndian.PutUint32(d.Extend(4), i) 65 | } 66 | 67 | // Extend doesn't panic unless out of memory. 68 | func (d *Dynamic) Extend(addLen int) []byte { 69 | offset := len(d.buf) 70 | 71 | if size := offset + addLen; size <= cap(d.buf) { 72 | if size < offset { // Check for overflow 73 | panic(errors.New("buffer size out of range")) 74 | } 75 | 76 | d.buf = d.buf[:size] 77 | } else { 78 | d.grow(addLen) 79 | } 80 | 81 | return d.buf[offset:] 82 | } 83 | 84 | // ResizeBytes doesn't panic unless out of memory. 85 | func (d *Dynamic) ResizeBytes(newLen int) []byte { 86 | if newLen <= cap(d.buf) { 87 | d.buf = d.buf[:newLen] 88 | } else { 89 | d.grow(newLen - len(d.buf)) 90 | } 91 | 92 | return d.buf 93 | } 94 | 95 | func (d *Dynamic) grow(addLen int) { 96 | newLen := len(d.buf) + addLen 97 | 98 | newCap := cap(d.buf)*2 + addLen 99 | if newCap < cap(d.buf) { // Handle overflow 100 | newCap = newLen 101 | } 102 | 103 | if newCap > d.maxSize { 104 | if d.maxSize >= newLen { // Ignore it if we went over it 105 | newCap = d.maxSize 106 | } 107 | } 108 | 109 | newBuf := make([]byte, newLen, newCap) 110 | copy(newBuf, d.buf) 111 | d.buf = newBuf 112 | } 113 | -------------------------------------------------------------------------------- /testsuite/runtime.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Module testsuite uses Gate runtime to implement tests which need to execute 6 | // WebAssembly code. Gate's wag dependency is replaced with the version in the 7 | // parent directory. 8 | package main 9 | 10 | import ( 11 | "context" 12 | "os" 13 | "testing" 14 | "time" 15 | 16 | "gate.computer/gate/image" 17 | "gate.computer/gate/runtime" 18 | "gate.computer/gate/runtime/container" 19 | "gate.computer/gate/service" 20 | "gate.computer/gate/snapshot" 21 | "gate.computer/gate/trap" 22 | wagtrap "gate.computer/wag/trap" 23 | ) 24 | 25 | const runTimeout = time.Second * 10 26 | 27 | var ( 28 | theExecutor *runtime.Executor 29 | executorErr error 30 | ) 31 | 32 | // getExecutor shared between tests. 33 | func getExecutor(t *testing.T) *runtime.Executor { 34 | if executorErr != nil { 35 | t.Fatal(executorErr) 36 | } 37 | if theExecutor != nil { 38 | return theExecutor 39 | } 40 | 41 | var namespace container.NamespaceConfig 42 | if os.Getenv("WAG_TEST_CONTAINER_NAMESPACE") == "disabled" { 43 | namespace.Disabled = true 44 | } else { 45 | namespace.SingleUID = true 46 | } 47 | 48 | theExecutor, executorErr = runtime.NewExecutor(&runtime.Config{ 49 | Container: container.Config{ 50 | Namespace: namespace, 51 | }, 52 | }) 53 | if executorErr != nil { 54 | t.Fatal(executorErr) 55 | } 56 | 57 | go func() { 58 | <-theExecutor.Dead() 59 | time.Sleep(time.Second) 60 | panic("gate executor died") 61 | }() 62 | 63 | return theExecutor 64 | } 65 | 66 | func run(t *testing.T, code *image.Program, state *image.Instance, serviceHandler func([]byte) []byte) (int, wagtrap.ID) { 67 | var ( 68 | services = new(service.Registry) 69 | buffers *snapshot.Buffers 70 | ) 71 | if serviceHandler != nil { 72 | services.MustRegister(newServer(serviceHandler)) 73 | buffers = &snapshot.Buffers{ 74 | Services: []snapshot.Service{{ // Prediscovered service. 75 | Name: serviceName, 76 | Buffer: []byte{0}, 77 | }}, 78 | } 79 | } 80 | 81 | ctx, cancel := context.WithTimeout(context.Background(), runTimeout) 82 | defer cancel() 83 | 84 | p, err := getExecutor(t).NewProcess(ctx) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | defer p.Close() 89 | 90 | policy := runtime.ProcessPolicy{ 91 | TimeResolution: time.Nanosecond, 92 | DebugLog: os.Stderr, 93 | } 94 | 95 | if err := p.Start(code, state, policy); err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | result, trapID, err := p.Serve(ctx, services, buffers) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | switch trapID { 105 | case trap.Exit: 106 | return result.Value(), wagtrap.Exit 107 | 108 | case trap.Unreachable, trap.CallStackExhausted, trap.MemoryAccessOutOfBounds, trap.IndirectCallIndexOutOfBounds, trap.IndirectCallSignatureMismatch, trap.IntegerDivideByZero, trap.IntegerOverflow, trap.Breakpoint: 109 | 110 | default: 111 | t.Fatal(trapID) 112 | } 113 | 114 | return -1, wagtrap.ID(trapID) 115 | } 116 | -------------------------------------------------------------------------------- /internal/gen/func.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gen 6 | 7 | import ( 8 | "fmt" 9 | 10 | "gate.computer/wag/internal/gen/debug" 11 | "gate.computer/wag/internal/gen/link" 12 | "gate.computer/wag/internal/gen/operand" 13 | "gate.computer/wag/internal/gen/reg" 14 | "gate.computer/wag/internal/gen/regalloc" 15 | "gate.computer/wag/internal/gen/storage" 16 | "gate.computer/wag/internal/obj" 17 | "gate.computer/wag/internal/pan" 18 | "gate.computer/wag/wa" 19 | ) 20 | 21 | type Block struct { 22 | Suspension bool 23 | Deadend bool 24 | } 25 | 26 | type BranchTarget struct { 27 | Label link.L 28 | StackDepth int 29 | ValueType wa.Type 30 | FuncEnd bool 31 | 32 | Block Block 33 | } 34 | 35 | type BranchTable struct { 36 | Addr int32 37 | Targets []*BranchTarget 38 | StackDepth int // -1 indicates common depth among all targets 39 | } 40 | 41 | type Func struct { 42 | Prog // Initialized by GenProgram, preserved by GenFunction 43 | 44 | Regs regalloc.Allocator 45 | 46 | ResultType wa.Type 47 | LocalTypes []wa.Type 48 | NumParams int 49 | NumLocals int // The non-param ones 50 | NumExtra int // Library function's duplicated arguments etc. 51 | 52 | Operands []operand.O 53 | FrameBase int // Number of (stack) operands belonging to parent blocks 54 | NumStableOperands int 55 | StackDepth int // The dynamic entries after locals 56 | MaxStackDepth int 57 | 58 | BranchTargets []*BranchTarget 59 | BranchTables []BranchTable 60 | 61 | AtomicCallStubs bool 62 | } 63 | 64 | func (f *Func) LocalOffset(index int) int32 { 65 | // Params are in behind function link address slot 66 | n := f.StackDepth + f.NumLocals + f.NumParams - index 67 | if index >= f.NumParams { 68 | // Other locals are on this side of function link address slot 69 | n-- 70 | } 71 | if n < 0 { 72 | pan.Panic(fmt.Errorf("effective stack offset of local variable #%d is negative", index)) 73 | } 74 | return int32(n * obj.Word) 75 | } 76 | 77 | // StackValueConsumed updates the virtual stack pointer on behalf of 78 | // MacroAssembler when it changes the physical stack pointer. 79 | func (f *Func) StackValueConsumed() { 80 | f.StackDepth-- 81 | 82 | if debug.Enabled { 83 | debug.Printf("stack depth: %d (pop 1)", f.StackDepth) 84 | } 85 | } 86 | 87 | // ValueBecameUnreachable keeps the state consistent when an operand will not 88 | // be operated on (because it was popped on an unreachable code path). 89 | func (f *Func) ValueBecameUnreachable(x operand.O) { 90 | switch x.Storage { 91 | case storage.Stack: 92 | f.StackValueConsumed() 93 | 94 | case storage.Reg: 95 | if x.Reg() != reg.Result { 96 | f.Regs.Free(x.Type, x.Reg()) 97 | } 98 | } 99 | } 100 | 101 | func (f *Func) MapCallAddr(retAddr int32) { 102 | f.Map.PutCallSite(uint32(retAddr), f.mapStackUsage()) 103 | f.LastCallAddr = retAddr // Needed only by arm64 backend. 104 | } 105 | 106 | func (f *Func) mapStackUsage() int32 { 107 | // Add one entry for link address. 108 | return int32((f.NumLocals + f.StackDepth + 1) * obj.Word) 109 | } 110 | -------------------------------------------------------------------------------- /internal/isa/amd64/in/imm_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package in 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | // Number formatting varies due to gapstone 12 | 13 | var testImm8 = []int8{ 14 | -128, 15 | -127, 16 | -2, 17 | -1, 18 | 0, 19 | 1, 20 | 2, 21 | 0x7e, 22 | 0x7f, 23 | } 24 | 25 | var testDisp8 = map[int8]string{ 26 | -128: " - 0x80", 27 | -127: " - 0x7f", 28 | -2: " - 2", 29 | -1: " - 1", 30 | 0: "", 31 | +1: " + 1", 32 | +2: " + 2", 33 | +0x7e: " + 0x7e", 34 | +0x7f: " + 0x7f", 35 | } 36 | 37 | var testImm16 = []int16{ 38 | -32768, 39 | -32767, 40 | -128, 41 | -127, 42 | -2, 43 | -1, 44 | 0, 45 | 1, 46 | 2, 47 | 0x7e, 48 | 0x7f, 49 | 32766, 50 | 32767, 51 | } 52 | 53 | var testImm32 = []int32{ 54 | -0x80000000, 55 | -0x7fffffff, 56 | -0x10000, 57 | -0x82, 58 | -0x81, 59 | -128, 60 | -127, 61 | -2, 62 | -1, 63 | 0, 64 | 1, 65 | 2, 66 | 0x7e, 67 | 0x7f, 68 | 0x80, 69 | 0x81, 70 | 0xffff, 71 | 0x10000, 72 | 0x7ffffffe, 73 | 0x7fffffff, 74 | } 75 | 76 | var testDisp32 = map[int32]string{ 77 | -0x80000000: " - 0x80000000", 78 | -0x7fffffff: " - 0x7fffffff", 79 | -0x10000: " - 0x10000", 80 | -0x82: " - 0x82", 81 | -0x81: " - 0x81", 82 | -128: " - 0x80", 83 | -127: " - 0x7f", 84 | -2: " - 2", 85 | -1: " - 1", 86 | 0: "", 87 | +1: " + 1", 88 | +2: " + 2", 89 | +0x7e: " + 0x7e", 90 | +0x7f: " + 0x7f", 91 | 0x80: " + 0x80", 92 | 0x81: " + 0x81", 93 | 0xffff: " + 0xffff", 94 | 0x10000: " + 0x10000", 95 | 0x7ffffffe: " + 0x7ffffffe", 96 | 0x7fffffff: " + 0x7fffffff", 97 | } 98 | 99 | var testImm64 = []int64{ 100 | -0x8000000000000000, 101 | -0x7fffffffffffffff, 102 | -0x80000001, 103 | -0x80000000, 104 | -0x7fffffff, 105 | -0x10000, 106 | -0x82, 107 | -0x81, 108 | -128, 109 | -127, 110 | -2, 111 | -1, 112 | 0, 113 | 1, 114 | 2, 115 | 0x7e, 116 | 0x7f, 117 | 0x80, 118 | 0x81, 119 | 0xffff, 120 | 0x10000, 121 | 0x7ffffffe, 122 | 0x7fffffff, 123 | 0x80000000, 124 | 0x7ffffffffffffffe, 125 | 0x7fffffffffffffff, 126 | } 127 | 128 | func TestImm(t *testing.T) { 129 | for _, pair := range [][2]int{ 130 | {-0x80000000, 4}, 131 | {-0x7fffffff, 4}, 132 | {-0x10000, 4}, 133 | {-0x82, 4}, 134 | {-0x81, 4}, 135 | {-0x80, 1}, 136 | {-0x7f, 1}, 137 | {-2, 1}, 138 | {-1, 1}, 139 | {0, 1}, 140 | {1, 1}, 141 | {2, 1}, 142 | {0x7e, 1}, 143 | {0x7f, 1}, 144 | {0x80, 4}, 145 | {0x81, 4}, 146 | {0xffff, 4}, 147 | {0x10000, 4}, 148 | {0x7ffffffe, 4}, 149 | {0x7fffffff, 4}, 150 | } { 151 | if size := immSize(int32(pair[0])); size != uint32(pair[1]) { 152 | t.Errorf("immSize(%d) = %d", pair[0], size) 153 | } 154 | 155 | op, size := immOpcodeSize(0xed67, int32(pair[0])) 156 | if size == uint32(pair[1]) { 157 | if size == 1 && op == 0x67 { 158 | continue 159 | } 160 | if size == 4 && op == 0xed { 161 | continue 162 | } 163 | } 164 | t.Errorf("immOpcodeSize(0xed67, %d) = %#x, %d", pair[0], op, size) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /internal/isa/arm64/linker.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 6 | 7 | package arm64 8 | 9 | import ( 10 | "encoding/binary" 11 | "fmt" 12 | 13 | "gate.computer/wag/internal/gen/link" 14 | "gate.computer/wag/internal/isa/arm64/in" 15 | "gate.computer/wag/internal/module" 16 | "gate.computer/wag/internal/pan" 17 | "gate.computer/wag/wa" 18 | ) 19 | 20 | const ( 21 | MaxFuncSize = 4 * 1024 * 1024 // Conditional branch distance. 22 | ) 23 | 24 | var linker Linker 25 | 26 | type Linker struct{} 27 | 28 | // UpdateNearLoad overwrites an instruction with an ADR instruction. 29 | func (Linker) UpdateNearLoad(text []byte, addr int32) { 30 | loadAddr := addr - 4 31 | offset := int32(len(text)) - loadAddr 32 | insn := in.ADR.RdI19hiI2lo(RegScratch, uint32(offset)>>2, 0) 33 | binary.LittleEndian.PutUint32(text[loadAddr:], insn) 34 | } 35 | 36 | func (Linker) UpdateNearBranch(text []byte, site int32) { 37 | updateBranchInsn(text, site, int32(len(text))) 38 | } 39 | 40 | func (Linker) UpdateNearBranches(text []byte, sites []int32) { 41 | labelAddr := int32(len(text)) 42 | for _, afterBranchAddr := range sites { 43 | updateBranchInsn(text, afterBranchAddr, labelAddr) 44 | } 45 | } 46 | 47 | func (Linker) UpdateFarBranches(text []byte, l *link.L) { 48 | labelAddr := l.FinalAddr() 49 | for _, afterBranchAddr := range l.Sites { 50 | updateBranchInsn(text, afterBranchAddr, labelAddr) 51 | } 52 | } 53 | 54 | func (Linker) UpdateStackCheck(text []byte, addr int32, depth int) { 55 | if maxFuncOffset := len(text) - int(addr); maxFuncOffset > MaxFuncSize { 56 | pan.Panic(module.Error("text size limit exceeded")) 57 | } 58 | 59 | // codegen.MaxFuncLocals ensures that alloc4 is not out of range. 60 | alloc3 := depth 61 | alloc4 := uint32(alloc3+1) >> 1 // Round up. 62 | 63 | // scratch = limit/16 + alloc/16 64 | // scratch*16 = limit + alloc 65 | insn := in.ADDi.RdRnI12S2(RegScratch, RegStackLimit4, alloc4, 0, wa.Size64) 66 | 67 | binary.LittleEndian.PutUint32(text[addr-4:addr], insn) 68 | } 69 | 70 | func (Linker) UpdateCalls(text []byte, l *link.L) { 71 | funcAddr := l.FinalAddr() 72 | for _, retAddr := range l.Sites { 73 | updateCallInsn(text, retAddr, funcAddr) 74 | } 75 | } 76 | 77 | func updateBranchInsn(text []byte, addr, labelAddr int32) { 78 | branchAddr := addr - 4 79 | offset := (labelAddr - branchAddr) / 4 80 | 81 | insn := binary.LittleEndian.Uint32(text[branchAddr:]) 82 | 83 | // MaxFuncSize ensures that offset is not out of range. 84 | switch { 85 | case insn>>25 == 0x2a: // Conditional branch. 86 | insn = insn&^(0x7ffff<<5) | in.Int19(offset)<<5 87 | 88 | case (insn>>26)&0x1f == 0x05: // Unconditional branch. 89 | insn = insn&^0x3ffffff | in.Int26(offset) 90 | 91 | default: 92 | panic(fmt.Sprintf("unknown branch instruction encoding: %#v", insn)) 93 | } 94 | 95 | binary.LittleEndian.PutUint32(text[branchAddr:], insn) 96 | } 97 | 98 | func updateCallInsn(text []byte, addr, funcAddr int32) { 99 | callAddr := addr - 4 100 | offset := funcAddr - callAddr 101 | 102 | // compile.MaxTextSize ensures that offset is not out of range. 103 | insn := in.BL.I26(in.Int26(offset / 4)) 104 | binary.LittleEndian.PutUint32(text[callAddr:], insn) 105 | } 106 | -------------------------------------------------------------------------------- /testsuite/wa.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "testing" 11 | 12 | "gate.computer/wag/wa" 13 | ) 14 | 15 | type arg struct { 16 | wa.Type 17 | Value uint64 18 | } 19 | 20 | func (a arg) equal(t *testing.T, b arg) bool { 21 | if a.Type != b.Type { 22 | return false 23 | } 24 | 25 | switch a.Type { 26 | case wa.Void: 27 | return true 28 | 29 | case wa.F32: 30 | return equalF32(t, a.Value, b.Value) 31 | 32 | case wa.F64: 33 | return equalF64(t, a.Value, b.Value) 34 | 35 | case wa.I32: 36 | return uint32(a.Value) == uint32(b.Value) 37 | 38 | default: 39 | return a.Value == b.Value 40 | } 41 | } 42 | 43 | func (a arg) String() string { 44 | switch { 45 | case a.Type == wa.Void: 46 | return a.Type.String() 47 | 48 | case a.Type == wa.F32: 49 | if isF32NaN(a.Value) { 50 | return fmt.Sprintf("%s(NaN:%#08x)", a.Type, uint32(a.Value)) 51 | } else { 52 | return fmt.Sprintf("%s(%f)", a.Type, math.Float32frombits(uint32(a.Value))) 53 | } 54 | 55 | case a.Type == wa.F64: 56 | if isF64NaN(a.Value) { 57 | return fmt.Sprintf("%s(NaN:%#016x)", a.Type, a.Value) 58 | } else { 59 | return fmt.Sprintf("%s(%f)", a.Type, math.Float64frombits(a.Value)) 60 | } 61 | 62 | case a.Type.Category() == wa.Int && a.Value < 256: 63 | return fmt.Sprintf("%s(%d)", a.Type, uint32(a.Value)) 64 | 65 | case a.Type.Category() == wa.Int: 66 | return fmt.Sprintf("%s(%#x)", a.Type, a.Value) 67 | 68 | default: 69 | return a.Type.String() 70 | } 71 | } 72 | 73 | func equalF32(t *testing.T, a, b uint64) bool { 74 | if isF32NaN(a) { 75 | if isF32NaN(b) { 76 | return true // equalF32NaN(t, a, b) 77 | } 78 | return false 79 | } 80 | 81 | x := math.Float32frombits(uint32(a)) 82 | y := math.Float32frombits(uint32(b)) 83 | return x == y 84 | } 85 | 86 | func equalF64(t *testing.T, a, b uint64) bool { 87 | if isF64NaN(a) { 88 | if isF64NaN(b) { 89 | return true // equalF64NaN(t, a, b) 90 | } 91 | return false 92 | } 93 | 94 | x := math.Float64frombits(a) 95 | y := math.Float64frombits(b) 96 | return x == y 97 | } 98 | 99 | func isF32NaN(a uint64) bool { 100 | return a&0x7f000000 == 0x7f000000 && a&0xffffff != 0 101 | } 102 | 103 | func isF64NaN(a uint64) bool { 104 | return a&0x7ff0000000000000 == 0x7ff0000000000000 && a&0xfffffffffffff != 0 105 | } 106 | 107 | // func equalF32NaN(t *testing.T, a0, b0 uint64) bool { 108 | // a := a0 & 0x7fffff 109 | // b := b0 & 0x7fffff 110 | // 111 | // // Canonical NaN. 112 | // if a == 0 { 113 | // return b == 0 114 | // } 115 | // if b == 0 { 116 | // return false 117 | // } 118 | // 119 | // t.Logf("non-canonical NaN: %#08x == %#08x", a0, b0) 120 | // 121 | // // Arithmetic NaN. 122 | // if a == 0x400000 && b == 0x400000 { 123 | // return true 124 | // } 125 | // 126 | // // Other NaNs. 127 | // return true 128 | // } 129 | // 130 | // func equalF64NaN(t *testing.T, a0, b0 uint64) bool { 131 | // a := a0 & 0x7ffffffffffff 132 | // b := b0 & 0x7ffffffffffff 133 | // 134 | // // Canonical NaN. 135 | // if a == 0 { 136 | // return b == 0 137 | // } 138 | // if b == 0 { 139 | // return false 140 | // } 141 | // 142 | // t.Logf("non-canonical NaN: %#016x == %#016x", a0, b0) 143 | // 144 | // // Arithmetic NaN. 145 | // if a == 0x4000000000000 && b == 0x4000000000000 { 146 | // return true 147 | // } 148 | // 149 | // // Other NaNs. 150 | // return true 151 | // } 152 | -------------------------------------------------------------------------------- /internal/gen/regalloc/regalloc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package regalloc 6 | 7 | import ( 8 | "math/bits" 9 | 10 | "gate.computer/wag/internal/gen/debug" 11 | "gate.computer/wag/internal/gen/reg" 12 | "gate.computer/wag/internal/isa/reglayout" 13 | "gate.computer/wag/wa" 14 | ) 15 | 16 | type bitmap uint32 17 | 18 | const ( 19 | numInt = reglayout.AllocIntLast - reglayout.AllocIntFirst + 1 20 | numFloat = reglayout.AllocFloatLast - reglayout.AllocFloatFirst + 1 21 | 22 | allocatableInt = bitmap((1<" 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /object/debug/dump/arm64.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (arm64 || wagarm64) && !wagamd64 && cgo 6 | 7 | package dump 8 | 9 | import ( 10 | "fmt" 11 | "regexp" 12 | "strings" 13 | 14 | "gate.computer/wag/trap" 15 | "github.com/knightsc/gapstone" 16 | ) 17 | 18 | const ( 19 | csArch = gapstone.CS_ARCH_ARM64 20 | csMode = gapstone.CS_MODE_LITTLE_ENDIAN 21 | csSyntax = gapstone.CS_OPT_SYNTAX_DEFAULT 22 | padInsn = gapstone.ARM64_INS_BRK 23 | ) 24 | 25 | func rewriteText(insns []gapstone.Instruction, targets map[uint]string, textAddr uintptr, firstFuncAddr uint) { 26 | sequence := 0 27 | skipTrapInsn := false 28 | 29 | var ( 30 | r0 = regexp.MustCompile(`\b([wx])0\b`) 31 | r1 = regexp.MustCompile(`\b([wx])1\b`) 32 | r26 = regexp.MustCompile(`\b([wx])26\b`) 33 | r27 = regexp.MustCompile(`\b([wx])27\b`) 34 | x28 = regexp.MustCompile(`\bx28\b`) 35 | w28 = regexp.MustCompile(`\bw28\b`) 36 | r29 = regexp.MustCompile(`\b([wx])29\b`) 37 | r30 = regexp.MustCompile(`\b([wx])30\b`) 38 | ) 39 | 40 | for i := range insns { 41 | insn := &insns[i] 42 | 43 | insn.OpStr = r0.ReplaceAllString(insn.OpStr, "${1}result") 44 | insn.OpStr = r1.ReplaceAllString(insn.OpStr, "${1}scratch") 45 | insn.OpStr = r26.ReplaceAllString(insn.OpStr, "${1}memory") 46 | insn.OpStr = r27.ReplaceAllString(insn.OpStr, "${1}text") 47 | insn.OpStr = x28.ReplaceAllString(insn.OpStr, "xstacklimit4") 48 | insn.OpStr = w28.ReplaceAllString(insn.OpStr, "wstacklimit4") 49 | insn.OpStr = r29.ReplaceAllString(insn.OpStr, "${1}fakestack") 50 | insn.OpStr = r30.ReplaceAllString(insn.OpStr, "${1}link") 51 | 52 | if insn.Address < firstFuncAddr { 53 | if skipTrapInsn { 54 | skipTrapInsn = false 55 | continue 56 | } 57 | 58 | if insn.Mnemonic == "sub" && strings.HasPrefix(insn.OpStr, "xlink, xlink, #") { 59 | switch insn.OpStr { 60 | case "xlink, xlink, #0x10": 61 | targets[insn.Address] = "trap.call_stack_exhausted" 62 | skipTrapInsn = true 63 | 64 | case "xlink, xlink, #8": 65 | targets[insn.Address] = "trap.suspended.rewind" 66 | skipTrapInsn = true 67 | } 68 | } 69 | 70 | if insn.Mnemonic == "movz" { 71 | var n uint 72 | if _, err := fmt.Sscanf(insn.OpStr, "x2, #0x%x", &n); err == nil { 73 | if id := trap.ID(n); id < trap.NumTraps { 74 | targets[insn.Address] = "trap." + strings.Replace(id.String(), " ", "_", -1) 75 | } 76 | } 77 | } 78 | } 79 | } 80 | 81 | for i := range insns { 82 | insn := &insns[i] 83 | 84 | var prefix string 85 | var input string 86 | 87 | switch { 88 | case insn.Mnemonic == "b" || insn.Mnemonic == "bl" || strings.HasPrefix(insn.Mnemonic, "b."): 89 | input = insn.OpStr 90 | 91 | case strings.HasPrefix(insn.Mnemonic, "tb"): 92 | parts := strings.SplitN(insn.OpStr, ", ", 3) 93 | input = parts[2] 94 | parts[2] = "" 95 | prefix = strings.Join(parts, ", ") 96 | 97 | case insn.Mnemonic == "adr" || strings.HasPrefix(insn.Mnemonic, "cb"): 98 | parts := strings.SplitN(insn.OpStr, ", ", 2) 99 | input = parts[1] 100 | parts[1] = "" 101 | prefix = strings.Join(parts, ", ") 102 | 103 | default: 104 | continue 105 | } 106 | 107 | var addr uint 108 | if n, err := fmt.Sscanf(input, "#0x%x", &addr); err != nil { 109 | panic(err) 110 | } else if n != 1 { 111 | panic(n) 112 | } 113 | 114 | name, found := targets[addr] 115 | if !found { 116 | name = fmt.Sprintf(".%x", sequence%0x10000) 117 | sequence++ 118 | 119 | if addr < insn.Address { 120 | name += "\t\t\t; back" 121 | } 122 | 123 | targets[addr] = name 124 | } 125 | 126 | insn.OpStr = prefix + name 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /object/stack/stacktrace/printstack.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package stacktrace 6 | 7 | import ( 8 | "debug/dwarf" 9 | "fmt" 10 | "io" 11 | 12 | "gate.computer/wag/object/stack" 13 | "gate.computer/wag/section" 14 | "gate.computer/wag/wa" 15 | "github.com/ianlancetaylor/demangle" 16 | ) 17 | 18 | func Fprint(w io.Writer, stacktrace []stack.Frame, funcSigs []wa.FuncType, names *section.NameSection, debug *dwarf.Data) (err error) { 19 | var debugLines map[int]string 20 | 21 | if debug != nil { 22 | debugLines, err = parseDWARF(debug) 23 | if err != nil { 24 | return 25 | } 26 | } 27 | 28 | var ( 29 | depthWidth int 30 | offsetWidth int 31 | ) 32 | 33 | for depth, frame := range stacktrace { 34 | if n := len(fmt.Sprintf("%d", depth)); n > depthWidth { 35 | depthWidth = n 36 | } 37 | if n := len(fmt.Sprintf("%x", frame.RetOffset)); n > offsetWidth { 38 | offsetWidth = n 39 | } 40 | } 41 | 42 | if depthWidth < 2 { 43 | depthWidth = 2 44 | } 45 | if offsetWidth&1 == 1 { 46 | offsetWidth++ 47 | } 48 | 49 | var ( 50 | lineFmt = fmt.Sprintf("#%%-%dd %%s%%s%%s\n", depthWidth) 51 | prefixFmt = fmt.Sprintf("0x%%0%dx in ", offsetWidth) 52 | ) 53 | 54 | for depth, frame := range stacktrace { 55 | var name string 56 | 57 | if names != nil && int(frame.FuncIndex) < len(names.FuncNames) { 58 | name = demangle.Filter(names.FuncNames[frame.FuncIndex].FuncName) 59 | } else { 60 | name = fmt.Sprintf("function %d", frame.FuncIndex) 61 | } 62 | 63 | var ( 64 | prefix string 65 | suffix string 66 | ) 67 | 68 | if frame.RetOffset != 0 { 69 | callOffset := frame.RetOffset - 1 70 | prefix = fmt.Sprintf(prefixFmt, callOffset) 71 | 72 | if debugLines != nil { 73 | if s := getLine(debugLines, frame.RetOffset); s != "" { 74 | suffix = fmt.Sprintf(" at %s", s) 75 | } 76 | } 77 | } 78 | 79 | _, err = fmt.Fprintf(w, lineFmt, depth, prefix, name, suffix) 80 | if err != nil { 81 | return 82 | } 83 | 84 | if frame.Locals != nil { 85 | var values string 86 | 87 | delim := " 0 " 88 | 89 | for i, x := range frame.Locals { 90 | if i&3 == 3 { 91 | values += fmt.Sprintf("%s%016x", delim, x) 92 | delim = fmt.Sprintf("\n%8d ", i+1) 93 | } else { 94 | values += fmt.Sprintf("%s%016x", delim, x) 95 | delim = " " 96 | } 97 | } 98 | 99 | if values != "" { 100 | _, err = fmt.Fprintln(w, values) 101 | if err != nil { 102 | return 103 | } 104 | } 105 | } 106 | } 107 | 108 | return 109 | } 110 | 111 | func parseDWARF(data *dwarf.Data) (map[int]string, error) { 112 | lines := make(map[int]string) 113 | 114 | r := data.Reader() 115 | 116 | for { 117 | e, err := r.Next() 118 | if err != nil { 119 | return nil, err 120 | } 121 | if e == nil { 122 | break 123 | } 124 | 125 | if e.Tag == dwarf.TagCompileUnit { 126 | if e.Children { 127 | lr, err := data.LineReader(e) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | if lr != nil { 133 | var le dwarf.LineEntry 134 | 135 | for { 136 | if err := lr.Next(&le); err != nil { 137 | if err == io.EOF { 138 | break 139 | } 140 | return nil, err 141 | } 142 | 143 | lines[int(le.Address)] = fmt.Sprintf("%s:%d", le.File.Name, le.Line) 144 | } 145 | } 146 | } 147 | } else { 148 | if e.Children { 149 | r.SkipChildren() 150 | } 151 | } 152 | } 153 | 154 | return lines, nil 155 | } 156 | 157 | func getLine(lines map[int]string, offset int) string { 158 | for ; offset > 0; offset-- { 159 | s, found := lines[offset] 160 | if found { 161 | return s 162 | } 163 | } 164 | 165 | return "" 166 | } 167 | -------------------------------------------------------------------------------- /section/names.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package section 6 | 7 | import ( 8 | "errors" 9 | "math" 10 | 11 | "gate.computer/wag/internal" 12 | "gate.computer/wag/internal/loader" 13 | "gate.computer/wag/internal/module" 14 | "gate.computer/wag/internal/pan" 15 | ) 16 | 17 | const ( 18 | maxFuncNames = 1000000 // Industry standard. 19 | maxLocalNames = 50000 // Industry standard. 20 | ) 21 | 22 | const CustomName = "name" 23 | 24 | const ( 25 | nameSubsectionModuleName byte = iota 26 | nameSubsectionFunctionNames 27 | nameSubsectionLocalNames 28 | ) 29 | 30 | type FuncName struct { 31 | FuncName string 32 | LocalNames []string 33 | } 34 | 35 | type NameSection struct { 36 | ModuleName string 37 | FuncNames []FuncName 38 | } 39 | 40 | // Load "name" section. 41 | func (ns *NameSection) Load(_ string, r Reader, length uint32) (err error) { 42 | if internal.DontPanic() { 43 | defer func() { err = pan.Error(recover()) }() 44 | } 45 | 46 | load, ok := r.(*loader.L) 47 | if !ok { 48 | // Use bogus offsets to avoid possible confusion. 49 | load = loader.New(r, math.MaxUint32) 50 | } 51 | 52 | for begin := load.Tell(); ; { 53 | switch pos := load.Tell() - begin; { 54 | case pos == int64(length): 55 | return 56 | 57 | case pos > int64(length): 58 | pan.Panic(errors.New("name section content exceeded payload length")) 59 | 60 | default: 61 | ns.readSubsection(load) 62 | } 63 | } 64 | } 65 | 66 | func (ns *NameSection) readSubsection(load *loader.L) { 67 | id := load.Byte() 68 | contentSize := load.Varuint32() 69 | 70 | switch id { 71 | case nameSubsectionModuleName: 72 | ns.ModuleName = load.String(contentSize, "name section: module name") 73 | 74 | case nameSubsectionFunctionNames, nameSubsectionLocalNames: 75 | for range load.Span(maxFuncNames, "function name count") { 76 | funcIndex := load.Varuint32() 77 | if funcIndex >= uint32(len(ns.FuncNames)) { 78 | if funcIndex >= maxFuncNames { 79 | pan.Panic(module.Errorf("function name index is too large: %d", funcIndex)) 80 | } 81 | 82 | buf := make([]FuncName, funcIndex+1) 83 | copy(buf, ns.FuncNames) 84 | ns.FuncNames = buf 85 | } 86 | 87 | fn := &ns.FuncNames[funcIndex] 88 | 89 | switch id { 90 | case nameSubsectionFunctionNames: 91 | funcNameLen := load.Varuint32() 92 | fn.FuncName = load.String(funcNameLen, "name section: function name") 93 | 94 | case nameSubsectionLocalNames: 95 | count := load.Varuint32() 96 | if count > maxLocalNames { 97 | pan.Panic(module.Errorf("local name count is too large: %d", count)) 98 | } 99 | fn.LocalNames = make([]string, count) 100 | 101 | for range make([]struct{}, count) { 102 | localIndex := load.Varuint32() 103 | if localIndex >= uint32(len(fn.LocalNames)) { 104 | if localIndex >= maxLocalNames { 105 | pan.Panic(module.Errorf("local name index is too large: %d", localIndex)) 106 | } 107 | 108 | buf := make([]string, localIndex+1) 109 | copy(buf, fn.LocalNames) 110 | fn.LocalNames = buf 111 | } 112 | 113 | localNameLen := load.Varuint32() 114 | fn.LocalNames[localIndex] = load.String(localNameLen, "name section: local name") 115 | } 116 | } 117 | } 118 | 119 | default: 120 | load.Discard(contentSize) 121 | } 122 | } 123 | 124 | type MappedNameSection struct { 125 | NameSection 126 | Mapping ByteRange 127 | } 128 | 129 | // Loader of "name" section. Remembers position. 130 | func (ns *MappedNameSection) Loader(sectionMap *Map) func(string, Reader, uint32) error { 131 | return func(sectionName string, r Reader, length uint32) error { 132 | ns.Mapping = sectionMap.Sections[Custom] // The latest one. 133 | return ns.NameSection.Load(sectionName, r, length) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /object/debug/dump/text.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build cgo 6 | 7 | package dump 8 | 9 | import ( 10 | "encoding/binary" 11 | "fmt" 12 | "io" 13 | "strings" 14 | 15 | "gate.computer/wag/object/abi" 16 | "gate.computer/wag/section" 17 | "github.com/knightsc/gapstone" 18 | ) 19 | 20 | func Text(w io.Writer, text []byte, textAddr uintptr, funcAddrs []uint32, ns *section.NameSection) error { 21 | var names []section.FuncName 22 | if ns != nil { 23 | names = ns.FuncNames 24 | } 25 | 26 | engine, err := gapstone.New(csArch, csMode) 27 | if err != nil { 28 | return err 29 | } 30 | defer engine.Close() 31 | 32 | if err := engine.SetOption(gapstone.CS_OPT_SYNTAX, csSyntax); err != nil { 33 | return err 34 | } 35 | 36 | var insns []gapstone.Instruction 37 | 38 | for offset := uint64(0); true; { 39 | is, err := engine.Disasm(text[offset:], uint64(textAddr)+offset, 0) 40 | if err != nil { 41 | fmt.Fprintf(w, "Disassembly error at %x: %v\n", uint64(textAddr)+offset, err) 42 | } 43 | 44 | if len(is) > 0 { 45 | insns = append(insns, is...) 46 | 47 | latest := is[len(is)-1] 48 | offset = uint64(latest.Address+latest.Size) - uint64(textAddr) 49 | } 50 | 51 | skipTo := (offset + 4) &^ 3 // try next 32-bit word 52 | if skipTo >= uint64(len(text)) { 53 | break 54 | } 55 | offset = skipTo 56 | } 57 | 58 | for i := 0; i < len(insns)-1; i++ { 59 | end := insns[i].Address + insns[i].Size 60 | next := insns[i+1].Address 61 | if end < next { 62 | size := next - end 63 | if size > 4 { 64 | size = 4 65 | } 66 | 67 | repr := "?" 68 | if size == 4 { 69 | repr = fmt.Sprintf("%08x", binary.LittleEndian.Uint32(text[uintptr(end)-textAddr:])) 70 | } 71 | 72 | insn := gapstone.Instruction{ 73 | InstructionHeader: gapstone.InstructionHeader{ 74 | Address: end, 75 | Size: size, 76 | Mnemonic: repr, 77 | }, 78 | } 79 | insns = append(insns[:i+1], append([]gapstone.Instruction{insn}, insns[i+1:]...)...) 80 | } 81 | } 82 | 83 | firstFuncAddr := uint(textAddr) + uint(funcAddrs[0]) 84 | 85 | targets := map[uint]string{ 86 | uint(textAddr) + abi.TextAddrNoFunction: "trap.no_function", 87 | uint(textAddr) + abi.TextAddrExit: "trap.exit", 88 | uint(textAddr) + abi.TextAddrResume: "resume", 89 | uint(textAddr) + abi.TextAddrEnter: "enter", 90 | } 91 | 92 | for i := 0; len(funcAddrs) > 0; i++ { 93 | addr := uint(textAddr) + uint(funcAddrs[0]) 94 | funcAddrs = funcAddrs[1:] 95 | 96 | var name string 97 | if i < len(names) { 98 | name = names[i].FuncName 99 | } 100 | if name == "" { 101 | name = fmt.Sprintf("func.%d", i) 102 | } 103 | 104 | targets[addr] = name 105 | } 106 | 107 | rewriteText(insns, targets, textAddr, firstFuncAddr) 108 | 109 | lastAddr := uintptr(insns[len(insns)-1].Address) 110 | addrWidth := (len(fmt.Sprintf("%x", lastAddr)) + 7) &^ 7 111 | 112 | var addrFmt string 113 | if textAddr == 0 { // relative 114 | addrFmt = fmt.Sprintf("%%%dx", addrWidth) 115 | } else { 116 | addrFmt = fmt.Sprintf("%%0%dx", addrWidth) 117 | } 118 | 119 | prevWasPad := false 120 | 121 | for _, insn := range insns { 122 | if insn.Id == padInsn { 123 | prevWasPad = true 124 | continue 125 | } 126 | 127 | addr := uintptr(insn.Address) 128 | 129 | name, found := targets[insn.Address] 130 | if found { 131 | if strings.HasPrefix(name, ".") { 132 | fmt.Fprintf(w, addrFmt+" %s:", addr, strings.TrimSpace(strings.Split(name, ";")[0])) 133 | } else { 134 | fmt.Fprintf(w, "\n%s:\n"+addrFmt, name, addr) 135 | } 136 | } else { 137 | if prevWasPad { 138 | fmt.Fprintln(w) 139 | } 140 | 141 | fmt.Fprintf(w, addrFmt, addr) 142 | } 143 | 144 | fmt.Fprint(w, "\t", strings.TrimSpace(fmt.Sprintf("%s\t%s", insn.Mnemonic, insn.OpStr))) 145 | fmt.Fprintf(w, "\t\t\t; %x", insn.Bytes) 146 | fmt.Fprint(w, "\n") 147 | 148 | prevWasPad = false 149 | } 150 | 151 | fmt.Fprintln(w) 152 | return nil 153 | } 154 | -------------------------------------------------------------------------------- /internal/isa/amd64/unary.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build (amd64 || wagamd64) && !wagarm64 6 | 7 | package amd64 8 | 9 | import ( 10 | "gate.computer/wag/internal/gen" 11 | "gate.computer/wag/internal/gen/condition" 12 | "gate.computer/wag/internal/gen/operand" 13 | "gate.computer/wag/internal/gen/reg" 14 | "gate.computer/wag/internal/gen/rodata" 15 | "gate.computer/wag/internal/isa/amd64/in" 16 | "gate.computer/wag/internal/isa/prop" 17 | "gate.computer/wag/wa" 18 | ) 19 | 20 | func (MacroAssembler) Unary(f *gen.Func, props uint64, x operand.O) operand.O { 21 | switch props & prop.MaskUnary { 22 | case prop.UnaryIntEqz: 23 | r, _ := getScratchReg(f, x) 24 | in.TEST.RegReg(&f.Text, x.Type, r, r) 25 | f.Regs.Free(x.Type, r) 26 | return operand.Flags(condition.Eq) 27 | 28 | case prop.UnaryIntClz: 29 | r, _ := allocResultReg(f, x) 30 | if haveLZCNT { 31 | in.LZCNT.RegReg(&f.Text, x.Type, r, r) 32 | } else { 33 | in.BSR.RegReg(&f.Text, x.Type, RegScratch, r) 34 | in.MOVi.RegImm32(&f.Text, x.Type, r, -1) 35 | in.CMOVE.RegReg(&f.Text, x.Type, RegScratch, r) 36 | in.MOVi.RegImm32(&f.Text, x.Type, r, (int32(x.Size())<<3)-1) 37 | in.SUB.RegReg(&f.Text, x.Type, r, RegScratch) 38 | } 39 | return operand.Reg(x.Type, r) 40 | 41 | case prop.UnaryIntCtz: 42 | r, _ := allocResultReg(f, x) 43 | if haveTZCNT { 44 | in.TZCNT.RegReg(&f.Text, x.Type, r, r) 45 | } else { 46 | in.BSF.RegReg(&f.Text, x.Type, r, r) 47 | in.MOVi.RegImm32(&f.Text, x.Type, RegScratch, int32(x.Size())<<3) 48 | in.CMOVE.RegReg(&f.Text, x.Type, r, RegScratch) 49 | } 50 | return operand.Reg(x.Type, r) 51 | 52 | case prop.UnaryIntPopcnt: 53 | var r reg.R 54 | if havePOPCNT { 55 | r, _ = allocResultReg(f, x) 56 | in.POPCNT.RegReg(&f.Text, x.Type, r, r) 57 | } else { 58 | r = popcnt(f, x) 59 | } 60 | return operand.Reg(x.Type, r) 61 | 62 | case prop.UnaryFloatAbs: 63 | r, _ := allocResultReg(f, x) 64 | absFloatReg(&f.Prog, x.Type, r) 65 | return operand.Reg(x.Type, r) 66 | 67 | case prop.UnaryFloatNeg: 68 | r, _ := allocResultReg(f, x) 69 | negFloatReg(&f.Prog, x.Type, r) 70 | return operand.Reg(x.Type, r) 71 | 72 | case prop.UnaryFloatRound: 73 | r, _ := allocResultReg(f, x) 74 | roundMode := int8(props >> 8) 75 | in.ROUNDSx.RegRegImm8(&f.Text, x.Type, r, r, roundMode) 76 | return operand.Reg(x.Type, r) 77 | 78 | case prop.UnaryFloatSqrt: 79 | r, _ := allocResultReg(f, x) 80 | in.SQRTSx.RegReg(&f.Text, x.Type, r, r) 81 | return operand.Reg(x.Type, r) 82 | } 83 | 84 | panic(props) 85 | } 86 | 87 | // absFloatReg in-place. 88 | func absFloatReg(p *gen.Prog, t wa.Type, r reg.R) { 89 | absMaskAddr := rodata.MaskAddr(rodata.Mask7fBase, t) 90 | in.ANDPx.RegMemDisp(&p.Text, t, r, in.BaseText, absMaskAddr) 91 | } 92 | 93 | // negFloatReg in-place. 94 | func negFloatReg(p *gen.Prog, t wa.Type, r reg.R) { 95 | signMaskAddr := rodata.MaskAddr(rodata.Mask80Base, t) 96 | in.XORPx.RegMemDisp(&p.Text, t, r, in.BaseText, signMaskAddr) 97 | } 98 | 99 | // Population count algorithm: 100 | // 101 | // func popcnt(x uint) (count int) { 102 | // for count = 0; x != 0; count++ { 103 | // x &= x - 1 104 | // } 105 | // return 106 | // } 107 | func popcnt(f *gen.Func, x operand.O) (count reg.R) { 108 | count = f.Regs.AllocResult(x.Type) 109 | pop, _ := getScratchReg(f, x) 110 | temp := RegZero // cleared again at the end 111 | 112 | in.XOR.RegReg(&f.Text, wa.I32, count, count) 113 | 114 | in.TEST.RegReg(&f.Text, x.Type, pop, pop) 115 | skipJump := in.JEcb.Stub8(&f.Text) 116 | 117 | loopAddr := f.Text.Addr 118 | in.INC.Reg(&f.Text, wa.I32, count) 119 | in.MOV.RegReg(&f.Text, x.Type, temp, pop) 120 | in.DEC.Reg(&f.Text, x.Type, temp) 121 | in.AND.RegReg(&f.Text, x.Type, pop, temp) 122 | in.JNEcb.Addr8(&f.Text, loopAddr) 123 | 124 | linker.UpdateNearBranch(f.Text.Bytes(), skipJump) 125 | 126 | in.XOR.RegReg(&f.Text, wa.I32, RegZero, RegZero) // temp reg 127 | f.Regs.Free(x.Type, pop) 128 | return 129 | } 130 | -------------------------------------------------------------------------------- /internal/loader/loader.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package loader 6 | 7 | import ( 8 | "io" 9 | "unicode/utf8" 10 | 11 | "gate.computer/wag/binary" 12 | "gate.computer/wag/internal/module" 13 | "gate.computer/wag/internal/pan" 14 | ) 15 | 16 | type Loader interface { 17 | binary.Reader 18 | loader() 19 | } 20 | 21 | func Get(load Loader) *L { 22 | return load.(*L) 23 | } 24 | 25 | // L provides panicking reading and integer decoding methods. 26 | type L struct { 27 | r binary.Reader 28 | n int64 29 | } 30 | 31 | func New(r binary.Reader, offset int64) *L { 32 | if load, ok := r.(*L); ok && load.n == offset { 33 | return load 34 | } 35 | return &L{r, offset} 36 | } 37 | 38 | func (*L) loader() {} 39 | 40 | // Tell how many bytes have been read. 41 | func (load *L) Tell() int64 { 42 | return load.n 43 | } 44 | 45 | // Read doesn't panic, but returns an error. 46 | func (load *L) Read(buf []byte) (int, error) { 47 | n, err := load.r.Read(buf) 48 | load.n += int64(n) 49 | return n, err 50 | } 51 | 52 | // ReadByte doesn't panic, but returns an error. 53 | func (load *L) ReadByte() (byte, error) { 54 | x, err := load.r.ReadByte() 55 | if err == nil { 56 | load.n++ 57 | } 58 | return x, err 59 | } 60 | 61 | // UnreadByte doesn't panic, but returns an error. 62 | func (load *L) UnreadByte() error { 63 | err := load.r.UnreadByte() 64 | if err == nil { 65 | load.n-- 66 | } 67 | return err 68 | } 69 | 70 | func (load *L) Into(buf []byte) { 71 | n, err := io.ReadFull(load.r, buf) 72 | load.n += int64(n) 73 | pan.Check(err) 74 | } 75 | 76 | func (load *L) String(n uint32, name string) string { 77 | return String(load.Bytes(n), name) 78 | } 79 | 80 | func (load *L) Bytes(n uint32) (data []byte) { 81 | data = make([]byte, n) 82 | load.Into(data) 83 | return 84 | } 85 | 86 | func (load *L) Byte() byte { 87 | return pan.Must(load.ReadByte()) 88 | } 89 | 90 | func (load *L) Uint32() uint32 { 91 | x, n, err := binary.Uint32(load.r) 92 | load.n += int64(n) 93 | pan.Check(err) 94 | return x 95 | } 96 | 97 | func (load *L) Uint64() uint64 { 98 | x, n, err := binary.Uint64(load.r) 99 | load.n += int64(n) 100 | pan.Check(err) 101 | return x 102 | } 103 | 104 | func (load *L) Varint7() int8 { 105 | x, n, err := binary.Varint7(load.r) 106 | load.n += int64(n) 107 | pan.Check(err) 108 | return x 109 | } 110 | 111 | func (load *L) Varint32() int32 { 112 | x, n, err := binary.Varint32(load.r) 113 | load.n += int64(n) 114 | pan.Check(err) 115 | return x 116 | } 117 | 118 | func (load *L) Varint64() int64 { 119 | x, n, err := binary.Varint64(load.r) 120 | load.n += int64(n) 121 | pan.Check(err) 122 | return x 123 | } 124 | 125 | func (load *L) Varuint1() bool { 126 | x, n, err := binary.Varuint1(load.r) 127 | load.n += int64(n) 128 | pan.Check(err) 129 | return x 130 | } 131 | 132 | func (load *L) Varuint32() uint32 { 133 | x, n, err := binary.Varuint32(load.r) 134 | load.n += int64(n) 135 | pan.Check(err) 136 | return x 137 | } 138 | 139 | func (load *L) Varuint64() uint64 { 140 | x, n, err := binary.Varuint64(load.r) 141 | load.n += int64(n) 142 | pan.Check(err) 143 | return x 144 | } 145 | 146 | // Count reads a varuint32. 147 | func (load *L) Count(max int, name string) int { 148 | count, n, err := binary.Varuint32(load.r) 149 | load.n += int64(n) 150 | pan.Check(err) 151 | if uint64(count) > uint64(max) { 152 | pan.Panic(module.Errorf("%s count is too large: 0x%x", name, count)) 153 | } 154 | return int(count) 155 | } 156 | 157 | // Span reads a varuint32 for iteration. 158 | func (load *L) Span(max int, name string) []struct{} { 159 | return make([]struct{}, load.Count(max, name)) 160 | } 161 | 162 | func (load *L) Discard(count uint32) { 163 | n, err := io.CopyN(io.Discard, load.r, int64(count)) 164 | load.n += n 165 | pan.Check(err) 166 | } 167 | 168 | func String(b []byte, name string) string { 169 | if !utf8.Valid(b) { 170 | pan.Panic(module.Errorf("%s is not a valid UTF-8 string", name)) 171 | } 172 | return string(b) 173 | } 174 | -------------------------------------------------------------------------------- /internal/module/module.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Timo Savola. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package module 6 | 7 | import ( 8 | "fmt" 9 | 10 | "gate.computer/wag/wa" 11 | ) 12 | 13 | const ( 14 | MagicNumber = uint32(0x6d736100) 15 | Version = uint32(1) 16 | ) 17 | 18 | type Header struct { 19 | MagicNumber uint32 20 | Version uint32 21 | } 22 | 23 | const ( 24 | MaxFunctions = 32768 25 | MaxFuncParams = 255 26 | MaxTypes = MaxFunctions 27 | MaxImports = MaxFunctions 28 | ) 29 | 30 | type SectionID byte 31 | 32 | const ( 33 | SectionCustom = SectionID(iota) 34 | SectionType 35 | SectionImport 36 | SectionFunction 37 | SectionTable 38 | SectionMemory 39 | SectionGlobal 40 | SectionExport 41 | SectionStart 42 | SectionElement 43 | SectionCode 44 | SectionData 45 | 46 | NumSections 47 | ) 48 | 49 | var sectionNames = []string{ 50 | SectionCustom: "custom", 51 | SectionType: "type", 52 | SectionImport: "import", 53 | SectionFunction: "function", 54 | SectionTable: "table", 55 | SectionMemory: "memory", 56 | SectionGlobal: "global", 57 | SectionExport: "export", 58 | SectionStart: "start", 59 | SectionElement: "element", 60 | SectionCode: "code", 61 | SectionData: "data", 62 | } 63 | 64 | func (id SectionID) String() string { 65 | if int(id) < len(sectionNames) { 66 | return sectionNames[id] 67 | } else { 68 | return fmt.Sprintf("", byte(id)) 69 | } 70 | } 71 | 72 | type ExternalKind byte 73 | 74 | const ( 75 | ExternalKindFunction = ExternalKind(iota) 76 | ExternalKindTable 77 | ExternalKindMemory 78 | ExternalKindGlobal 79 | ) 80 | 81 | var externalKindStrings = []string{ 82 | ExternalKindFunction: "function", 83 | ExternalKindTable: "table", 84 | ExternalKindMemory: "memory", 85 | ExternalKindGlobal: "global", 86 | } 87 | 88 | func (kind ExternalKind) String() (s string) { 89 | if int(kind) < len(externalKindStrings) { 90 | s = externalKindStrings[kind] 91 | } else { 92 | s = fmt.Sprintf("", byte(kind)) 93 | } 94 | return 95 | } 96 | 97 | type Import struct { 98 | Module string 99 | Field string 100 | } 101 | 102 | type ImportFunc struct { 103 | Import 104 | LibraryFunc uint32 105 | } 106 | 107 | type ResizableLimits struct { 108 | Init int 109 | Max int // -1 if unlimited (memory). 110 | } 111 | 112 | type Global struct { 113 | Type wa.Type 114 | Mutable bool 115 | InitImport int8 // -1 if InitConst is defined. 116 | InitConst uint64 117 | } 118 | 119 | type M struct { 120 | Types []wa.FuncType 121 | Funcs []uint32 122 | ImportFuncs []ImportFunc 123 | Table bool 124 | TableLimit ResizableLimits 125 | Memory bool 126 | MemoryLimit ResizableLimits 127 | Globals []Global 128 | ImportGlobals []Import 129 | ExportFuncs map[string]uint32 130 | StartIndex uint32 131 | StartDefined bool 132 | TableFuncs []uint32 133 | CanonicalTypes map[uint32]uint32 // Includes only remapped indexes. 134 | } 135 | 136 | func (m *M) NumTable() uint32 { 137 | if m.Table { 138 | return 1 139 | } 140 | return 0 141 | } 142 | 143 | func (m *M) NumMemory() uint32 { 144 | if m.Memory { 145 | return 1 146 | } 147 | return 0 148 | } 149 | 150 | func (m *M) EvaluateGlobalInitializer(index int, value uint64) uint64 { 151 | if index == -1 { 152 | return value 153 | } 154 | 155 | // If the global value has not been initialized, index will be invalid, and 156 | // this will panic (on purpose). 157 | return m.Globals[index].InitConst 158 | } 159 | 160 | func (m *M) GetCanonicalTypeIndex(index uint32) uint32 { 161 | if i, found := m.CanonicalTypes[index]; found { 162 | return i 163 | } 164 | return index 165 | } 166 | 167 | type ImportIndex struct { 168 | Import 169 | VectorIndex int 170 | } 171 | 172 | type Library struct { 173 | Types []wa.FuncType 174 | Funcs []uint32 175 | ImportFuncs []ImportIndex 176 | Memory bool 177 | ExportFuncs map[string]uint32 178 | CodeFuncs [][]byte 179 | } 180 | --------------------------------------------------------------------------------