Skip to content

Commit b98bdef

Browse files
authored
Merge pull request #249 from bytecodealliance/ydnar/wasm
internal/wasm: prototype package for wasm file manipulation
2 parents be55559 + 82fcacf commit b98bdef

File tree

7 files changed

+350
-0
lines changed

7 files changed

+350
-0
lines changed

internal/wasm/section.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package wasm
2+
3+
// SectionID represents a WebAssembly [section SectionID].
4+
//
5+
// [section SectionID]: https://webassembly.github.io/spec/core/binary/modules.html#sections
6+
type SectionID uint8
7+
8+
const (
9+
SectionCustom SectionID = 0
10+
SectionType SectionID = 1
11+
SectionImport SectionID = 2
12+
SectionFunction SectionID = 3
13+
SectionTable SectionID = 4
14+
SectionMemory SectionID = 5
15+
SectionGlobal SectionID = 6
16+
SectionExport SectionID = 7
17+
SectionStart SectionID = 8
18+
SectionElement SectionID = 9
19+
SectionCode SectionID = 10
20+
SectionData SectionID = 11
21+
SectionDataCount SectionID = 12
22+
)
23+
24+
// Section represents an abstract [WebAssembly section].
25+
//
26+
// [WebAssembly section]: https://webassembly.github.io/spec/core/binary/modules.html#sections
27+
type Section interface {
28+
// SectionID returns the section ID of this section.
29+
SectionID() SectionID
30+
31+
// SectionContents returns the section contents as a byte slice.
32+
SectionContents() ([]byte, error)
33+
}
34+
35+
// CustomSection represents a [WebAssembly custom section].
36+
//
37+
// [WebAssembly custom section]: https://webassembly.github.io/spec/core/binary/modules.html#binary-customsec
38+
type CustomSection struct {
39+
Name string
40+
Contents []byte
41+
}
42+
43+
// SectionID implements the [Section] interface.
44+
func (*CustomSection) SectionID() SectionID {
45+
return SectionCustom
46+
}
47+
48+
// SectionContents implements the [Section] interface.
49+
func (s *CustomSection) SectionContents() ([]byte, error) {
50+
// TODO: encode name correctly
51+
return append([]byte(s.Name), s.Contents...), nil
52+
}

internal/wasm/sleb128/sleb128.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Package sleb128 reads and writes signed LEB128 integers.
2+
package sleb128
3+
4+
import (
5+
"io"
6+
"unsafe"
7+
)
8+
9+
// Read reads a signed [LEB128] value from r.
10+
// Returns the value, number of bytes read, and/or an error.
11+
//
12+
// [LEB128]: https://en.wikipedia.org/wiki/LEB128
13+
func Read(r io.ByteReader) (v int64, n int, err error) {
14+
shift := 0
15+
for {
16+
b, err := r.ReadByte()
17+
if err != nil {
18+
return 0, n, err
19+
}
20+
n++
21+
v |= int64(b&0x7f) << shift
22+
shift += 7
23+
if (b & 0x80) == 0 {
24+
if shift < int(unsafe.Sizeof(v)<<8) && (b&0x40) != 0 {
25+
v |= (^0 << shift)
26+
}
27+
break
28+
}
29+
}
30+
return v, n, nil
31+
}
32+
33+
// Write writes v in signed [LEB128] format to w.
34+
// Returns the number of bytes written and/or an error.
35+
//
36+
// Adapted from the Go standard library with the following copyright:
37+
// Copyright 2018 The Go Authors. All rights reserved.
38+
// Use of this source code is governed by a BSD-style
39+
// license that can be found in the LICENSE file.
40+
//
41+
// [LEB128]: https://en.wikipedia.org/wiki/LEB128
42+
func Write(w io.ByteWriter, v int64) (n int, err error) {
43+
more := true
44+
for more {
45+
c := uint8(v & 0x7f)
46+
s := uint8(v & 0x40)
47+
v >>= 7
48+
more = (v != 0 || s != 0) && (v != -1 || s == 0)
49+
if more {
50+
c |= 0x80
51+
}
52+
err = w.WriteByte(c)
53+
if err != nil {
54+
return n, err
55+
}
56+
n++
57+
}
58+
return n, nil
59+
}

internal/wasm/sleb128/sleb128_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package sleb128
2+
3+
import (
4+
"bytes"
5+
"math"
6+
"testing"
7+
)
8+
9+
func TestReadWrite(t *testing.T) {
10+
tests := []int64{
11+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
12+
-1, -2, -3, -4, -5, -6, -7, -8, -9, -10,
13+
1 << 7, 1 << 8, 1 << 9,
14+
-1 << 7, -1 << 8, -1 << 9,
15+
math.MinInt64, math.MaxInt64,
16+
}
17+
for _, want := range tests {
18+
got, b, err := roundTrip(want)
19+
if err != nil {
20+
t.Errorf("roundTrip(%d): error: %v", want, err)
21+
continue
22+
}
23+
if got != want {
24+
t.Errorf("roundTrip(%d): got %d (%x)", want, got, b)
25+
}
26+
}
27+
}
28+
29+
func roundTrip(v int64) (int64, []byte, error) {
30+
var buf bytes.Buffer
31+
_, err := Write(&buf, v)
32+
b := buf.Bytes()
33+
if err != nil {
34+
return 0, b, err
35+
}
36+
v, _, err = Read(bytes.NewReader(b))
37+
return v, b, err
38+
}

internal/wasm/uleb128/uleb128.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Package uleb128 reads and writes unsigned LEB128 integers.
2+
package uleb128
3+
4+
import "io"
5+
6+
// Read reads an unsigned [LEB128] value from r.
7+
// Returns the value, number of bytes read, and/or an error.
8+
//
9+
// [LEB128]: https://en.wikipedia.org/wiki/LEB128
10+
func Read(r io.ByteReader) (v uint64, n int, err error) {
11+
shift := 0
12+
for {
13+
b, err := r.ReadByte()
14+
if err != nil {
15+
return 0, n, err
16+
}
17+
n++
18+
v |= uint64(b&0x7f) << shift
19+
if (b & 0x80) == 0 {
20+
break
21+
}
22+
shift += 7
23+
}
24+
return v, n, nil
25+
}
26+
27+
// Write writes v in unsigned [LEB128] format to w.
28+
// Returns the number of bytes written and/or an error.
29+
//
30+
// Adapted from the Go standard library with the following copyright:
31+
// Copyright 2018 The Go Authors. All rights reserved.
32+
// Use of this source code is governed by a BSD-style
33+
// license that can be found in the LICENSE file.
34+
//
35+
// [LEB128]: https://en.wikipedia.org/wiki/LEB128
36+
func Write(w io.ByteWriter, v uint64) (n int, err error) {
37+
more := true
38+
for more {
39+
c := uint8(v & 0x7f)
40+
v >>= 7
41+
more = v != 0
42+
if more {
43+
c |= 0x80
44+
}
45+
err = w.WriteByte(c)
46+
if err != nil {
47+
return n, err
48+
}
49+
n++
50+
}
51+
return n, nil
52+
}

internal/wasm/uleb128/uleb128_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package uleb128
2+
3+
import (
4+
"bytes"
5+
"math"
6+
"testing"
7+
)
8+
9+
func TestReadWrite(t *testing.T) {
10+
tests := []uint64{
11+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
12+
1 << 7, 1 << 8, 1 << 9,
13+
math.MaxUint64,
14+
}
15+
for _, want := range tests {
16+
got, b, err := roundTrip(want)
17+
if err != nil {
18+
t.Errorf("roundTrip(%d): error: %v", want, err)
19+
continue
20+
}
21+
if got != want {
22+
t.Errorf("roundTrip(%d): got %d (%x)", want, got, b)
23+
}
24+
}
25+
}
26+
27+
func roundTrip(v uint64) (uint64, []byte, error) {
28+
var buf bytes.Buffer
29+
_, err := Write(&buf, v)
30+
b := buf.Bytes()
31+
if err != nil {
32+
return 0, b, err
33+
}
34+
v, _, err = Read(bytes.NewReader(b))
35+
return v, b, err
36+
}

internal/wasm/wasm.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package wasm
2+
3+
import (
4+
"bufio"
5+
"io"
6+
7+
"go.bytecodealliance.org/internal/wasm/uleb128"
8+
)
9+
10+
const (
11+
Magic = "\x00asm"
12+
Version1 = "\x01\x00\x00\x00"
13+
)
14+
15+
// Write writes a binary [WebAssembly module] to w.
16+
//
17+
// [WebAssembly module]: https://webassembly.github.io/spec/core/binary/modules.html#binary-module
18+
func Write(w io.Writer, sections []Section) error {
19+
err := WriteModuleHeader(w)
20+
if err != nil {
21+
return err
22+
}
23+
for _, s := range sections {
24+
contents, err := s.SectionContents()
25+
if err != nil {
26+
return err
27+
}
28+
_, err = WriteSectionHeader(w, s.SectionID(), uint64(len(contents)))
29+
if err != nil {
30+
return err
31+
}
32+
_, err = w.Write(contents)
33+
if err != nil {
34+
return err
35+
}
36+
}
37+
return nil
38+
}
39+
40+
// WriteModuleHeader writes a binary [WebAssembly module header] (version 1) to w.
41+
//
42+
// [WebAssembly module header]: https://webassembly.github.io/spec/core/binary/modules.html#binary-module
43+
func WriteModuleHeader(w io.Writer) error {
44+
_, err := w.Write([]byte(Magic))
45+
if err != nil {
46+
return err
47+
}
48+
_, err = w.Write([]byte(Version1))
49+
return err
50+
}
51+
52+
// WriteSectionHeader writes a binary [WebAssembly section header] to w.
53+
// It returns the number of bytes written and/or an error.
54+
//
55+
// [WebAssembly section header]: https://webassembly.github.io/spec/core/binary/modules.html#sections
56+
func WriteSectionHeader(w io.Writer, id SectionID, size uint64) (n int, err error) {
57+
bw := bufio.NewWriter(w)
58+
err = bw.WriteByte(byte(id))
59+
if err != nil {
60+
return 0, err
61+
}
62+
n, err = uleb128.Write(bw, size)
63+
if err != nil {
64+
return n + 1, err
65+
}
66+
return n + 1, bw.Flush()
67+
}
68+
69+
// WriteString writes a string to w as a [LEB128] encoded length followed by the string bytes.
70+
// Returns the number of bytes written and/or an error.
71+
//
72+
// [LEB128]: https://en.wikipedia.org/wiki/LEB128
73+
func WriteString(w io.Writer, s string) (n int, err error) {
74+
bw := bufio.NewWriter(w)
75+
n, err = uleb128.Write(bw, uint64(len(s)))
76+
if err != nil {
77+
return n, err
78+
}
79+
ns, err := bw.WriteString(s)
80+
n += ns
81+
if err != nil {
82+
return n, err
83+
}
84+
return n, bw.Flush()
85+
}

wit/bindgen/generator.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,6 +2217,16 @@ func (g *generator) wasmFileFor(owner wit.TypeOwner) *gen.File {
22172217
return file
22182218
}
22192219

2220+
func (g *generator) cgoFileFor(owner wit.TypeOwner) *gen.File {
2221+
pkg := g.packageFor(owner)
2222+
file := pkg.File(pkg.Name + ".cgo.go")
2223+
file.GeneratedBy = g.opts.generatedBy
2224+
if file.GoBuild == "" {
2225+
file.GoBuild = "tinygo.wasm"
2226+
}
2227+
return file
2228+
}
2229+
22202230
func (g *generator) packageFor(owner wit.TypeOwner) *gen.Package {
22212231
return g.witPackages[owner]
22222232
}
@@ -2287,5 +2297,23 @@ func (g *generator) newPackage(w *wit.World, i *wit.Interface, name string) (*ge
22872297
g.exportScopes[owner] = gen.NewScope(nil)
22882298
pkg.DeclareName("Exports")
22892299

2300+
// Write a Cgo file that adds a library to the linker arguments.
2301+
// The library is a WebAssembly file that includes a custom section
2302+
// with a name prefixed with "component-type:". The contents are the
2303+
// Component Model definition for a world that encapsulates the
2304+
// Component Model types and functions imported into and/or exported
2305+
// from this Go package.
2306+
if false {
2307+
cgoFile := g.cgoFileFor(owner)
2308+
lib := id.String()
2309+
if name != id.Extension {
2310+
lib += "-" + name
2311+
}
2312+
lib = strings.ReplaceAll(lib, "/", "-")
2313+
lib = strings.ReplaceAll(lib, ":", "-")
2314+
stringio.Write(cgoFile, "// #cgo LDFLAGS: -L. -l", lib, "\n")
2315+
stringio.Write(cgoFile, "import \"C\"\n")
2316+
}
2317+
22902318
return pkg, nil
22912319
}

0 commit comments

Comments
 (0)