diff --git a/internal/compiler/wasm/externs.go b/internal/compiler/wasm/externs.go new file mode 100644 index 0000000000..1ac60b8a4c --- /dev/null +++ b/internal/compiler/wasm/externs.go @@ -0,0 +1,64 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package wasm + +import "github.com/open-policy-agent/opa/internal/wasm/module" + +type extern struct { + Name string + Module string + Index uint32 + TypeIndex uint32 +} + +const opaModuleName = "opa" + +const ( + opaParseJSON uint32 = iota + opaBoolean + opaStringTerminated + opaNumberInt + opaValueNotEqual + opaValueGet +) + +var externs = [...]module.Import{ + { + Name: "opa_json_parse", + Descriptor: module.FunctionImport{ + Func: funcInt32Int32retInt32, + }, + }, + { + Name: "opa_boolean", + Descriptor: module.FunctionImport{ + Func: funcInt32retInt32, + }, + }, + { + Name: "opa_string_terminated", + Descriptor: module.FunctionImport{ + Func: funcInt32retInt32, + }, + }, + { + Name: "opa_number_int", + Descriptor: module.FunctionImport{ + Func: funcInt64retInt32, + }, + }, + { + Name: "opa_value_not_equal", + Descriptor: module.FunctionImport{ + Func: funcInt32Int32retInt32, + }, + }, + { + Name: "opa_value_get", + Descriptor: module.FunctionImport{ + Func: funcInt32Int32retInt32, + }, + }, +} diff --git a/internal/compiler/wasm/functypes.go b/internal/compiler/wasm/functypes.go new file mode 100644 index 0000000000..a49cc902bf --- /dev/null +++ b/internal/compiler/wasm/functypes.go @@ -0,0 +1,31 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package wasm + +import ( + "github.com/open-policy-agent/opa/internal/wasm/module" + "github.com/open-policy-agent/opa/internal/wasm/types" +) + +const ( + funcInt32Int32retInt32 uint32 = iota + funcInt32retInt32 = iota + funcInt64retInt32 = iota +) + +var functypes = [...]module.FunctionType{ + { + Params: []types.ValueType{types.I32, types.I32}, + Results: []types.ValueType{types.I32}, + }, + { + Params: []types.ValueType{types.I32}, + Results: []types.ValueType{types.I32}, + }, + { + Params: []types.ValueType{types.I64}, + Results: []types.ValueType{types.I32}, + }, +} diff --git a/internal/compiler/wasm/wasm.go b/internal/compiler/wasm/wasm.go new file mode 100644 index 0000000000..5c9c510b95 --- /dev/null +++ b/internal/compiler/wasm/wasm.go @@ -0,0 +1,270 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// Package wasm contains an IR->WASM compiler backend. +package wasm + +import ( + "bytes" + "fmt" + + "github.com/open-policy-agent/opa/internal/ir" + "github.com/open-policy-agent/opa/internal/wasm/encoding" + "github.com/open-policy-agent/opa/internal/wasm/instruction" + "github.com/open-policy-agent/opa/internal/wasm/module" + "github.com/open-policy-agent/opa/internal/wasm/types" +) + +// Compiler implements an IR->WASM compiler backend. +type Compiler struct { + stages []func() error // compiler stages to execute + errors []error // compilation errors encountered + + policy *ir.Policy // input policy to compile + module *module.Module // output WASM module + code *module.CodeEntry // output WASM code + + stringOffset int32 // null-terminated string data base offset + stringAddrs []uint32 // null-terminated string constant addresses + stringData []byte // null-terminated strings to write into data section + + localMax uint32 +} + +// New returns a new compiler object. +func New() *Compiler { + c := &Compiler{ + module: &module.Module{}, + code: &module.CodeEntry{}, + stringOffset: 1024, + localMax: 2, // assume that locals start at 0..2 then increment monotonically + } + c.stages = []func() error{ + c.compileStrings, + c.emitEntry, + c.compilePlan, + c.emitLocals, + c.emitImportSection, + c.emitTypeSection, + c.emitFunctionSection, + c.emitExportSection, + c.emitCodeSection, + c.emitDataSection, + } + return c +} + +// WithPolicy sets the policy to compile. +func (c *Compiler) WithPolicy(p *ir.Policy) *Compiler { + c.policy = p + return c +} + +// Compile returns a compiled WASM module. +func (c *Compiler) Compile() (*module.Module, error) { + + for _, stage := range c.stages { + if err := stage(); err != nil { + return nil, err + } + } + + return c.module, nil +} + +func (c *Compiler) compileStrings() error { + + c.stringAddrs = make([]uint32, len(c.policy.Static.Strings)) + var buf bytes.Buffer + + for i, s := range c.policy.Static.Strings { + addr := uint32(buf.Len()) + uint32(c.stringOffset) + buf.WriteString(s.Value) + buf.WriteByte(0) + c.stringAddrs[i] = addr + } + + c.stringData = buf.Bytes() + return nil +} + +func (c *Compiler) emitEntry() error { + c.appendInstr(instruction.GetLocal{Index: c.local(ir.InputRaw)}) + c.appendInstr(instruction.GetLocal{Index: c.local(ir.InputLen)}) + c.appendInstr(instruction.Call{Index: opaParseJSON}) + c.appendInstr(instruction.SetLocal{Index: c.local(ir.Input)}) + return nil +} + +func (c *Compiler) compilePlan() error { + + for i := range c.policy.Plan.Blocks { + + instrs, err := c.compileBlock(c.policy.Plan.Blocks[i]) + if err != nil { + return err + } + + if i < len(c.policy.Plan.Blocks)-1 { + c.appendInstr(instruction.Block{Instrs: instrs}) + } else { + c.appendInstrs(instrs) + } + } + + return nil +} + +func (c *Compiler) compileBlock(block ir.Block) ([]instruction.Instruction, error) { + + var instrs []instruction.Instruction + + for _, stmt := range block.Stmts { + switch stmt := stmt.(type) { + case ir.ReturnStmt: + instrs = append(instrs, instruction.I32Const{Value: int32(stmt.Code)}) + instrs = append(instrs, instruction.Return{}) + case ir.DotStmt: + instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.Source)}) + instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.Key)}) + instrs = append(instrs, instruction.Call{Index: opaValueGet}) + instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)}) + case ir.EqualStmt: + instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.A)}) + instrs = append(instrs, instruction.GetLocal{Index: c.local(stmt.B)}) + instrs = append(instrs, instruction.Call{Index: opaValueNotEqual}) + instrs = append(instrs, instruction.BrIf{Index: 0}) + case ir.MakeBooleanStmt: + instr := instruction.I32Const{} + if stmt.Value { + instr.Value = 1 + } else { + instr.Value = 0 + } + instrs = append(instrs, instr) + instrs = append(instrs, instruction.Call{Index: opaBoolean}) + instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)}) + case ir.MakeNumberIntStmt: + instrs = append(instrs, instruction.I64Const{Value: stmt.Value}) + instrs = append(instrs, instruction.Call{Index: opaNumberInt}) + instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)}) + case ir.MakeStringStmt: + instrs = append(instrs, instruction.I32Const{Value: c.stringAddr(stmt.Index)}) + instrs = append(instrs, instruction.Call{Index: opaStringTerminated}) + instrs = append(instrs, instruction.SetLocal{Index: c.local(stmt.Target)}) + default: + return instrs, fmt.Errorf("unsupported IR statement %v", stmt) + } + + } + + return instrs, nil +} + +func (c *Compiler) emitLocals() error { + c.code.Func.Locals = []module.LocalDeclaration{ + { + Count: c.localMax + 1, + Type: types.I32, + }, + } + return nil +} + +func (c *Compiler) emitTypeSection() error { + c.module.Type.Functions = functypes[:] + return nil +} + +func (c *Compiler) emitImportSection() error { + + imps := make([]module.Import, len(externs)+1) + + for i, ext := range externs { + imps[i] = ext + if imps[i].Module == "" { + imps[i].Module = "opa" + } + } + + imps[len(imps)-1] = module.Import{ + Module: "env", + Name: "memory", + Descriptor: module.MemoryImport{ + Mem: module.MemType{ + Lim: module.Limit{ + Min: 5, + }, + }, + }, + } + + c.module.Import.Imports = imps + + return nil +} + +func (c *Compiler) emitFunctionSection() error { + c.module.Function.TypeIndices = make([]uint32, 1) + c.module.Function.TypeIndices[0] = funcInt32Int32retInt32 + return nil +} + +func (c *Compiler) emitExportSection() error { + c.module.Export.Exports = make([]module.Export, 1) + c.module.Export.Exports[0].Name = "eval" + c.module.Export.Exports[0].Descriptor = module.ExportDescriptor{ + Type: module.FunctionExportType, + Index: uint32(len(externs)), + } + return nil +} + +func (c *Compiler) emitCodeSection() error { + var buf bytes.Buffer + if err := encoding.WriteCodeEntry(&buf, c.code); err != nil { + return err + } + c.module.Code.Segments = append(c.module.Code.Segments, module.RawCodeSegment{ + Code: buf.Bytes(), + }) + return nil +} + +func (c *Compiler) emitDataSection() error { + c.module.Data.Segments = append(c.module.Data.Segments, module.DataSegment{ + Index: 0, + Offset: module.Expr{ + Instrs: []instruction.Instruction{ + instruction.I32Const{ + Value: c.stringOffset, + }, + }, + }, + Init: c.stringData, + }) + return nil +} + +func (c *Compiler) stringAddr(index int) int32 { + return int32(c.stringAddrs[index]) +} + +func (c *Compiler) local(l ir.Local) uint32 { + u32 := uint32(l) + if u32 > c.localMax { + c.localMax = u32 + } + return u32 +} + +func (c *Compiler) appendInstr(instr instruction.Instruction) { + c.code.Func.Expr.Instrs = append(c.code.Func.Expr.Instrs, instr) +} + +func (c *Compiler) appendInstrs(instrs []instruction.Instruction) { + for _, instr := range instrs { + c.appendInstr(instr) + } +} diff --git a/internal/compiler/wasm/wasm_test.go b/internal/compiler/wasm/wasm_test.go new file mode 100644 index 0000000000..a225d7ceff --- /dev/null +++ b/internal/compiler/wasm/wasm_test.go @@ -0,0 +1,29 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package wasm + +import ( + "testing" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/internal/planner" +) + +func TestCompilerHelloWorld(t *testing.T) { + + policy, err := planner.New(). + WithQueries([]ast.Body{ast.MustParseBody(`input.foo = 1`)}). + Plan() + + if err != nil { + t.Fatal(err) + } + + c := New().WithPolicy(policy) + _, err = c.Compile() + if err != nil { + t.Fatal(err) + } +} diff --git a/internal/leb128/leb128.go b/internal/leb128/leb128.go new file mode 100644 index 0000000000..24ddc90951 --- /dev/null +++ b/internal/leb128/leb128.go @@ -0,0 +1,170 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// Package leb128 implements LEB128 integer encoding. +package leb128 + +import ( + "io" +) + +// MustReadVarInt32 returns an int32 from r or panics. +func MustReadVarInt32(r io.Reader) int32 { + i32, err := ReadVarInt32(r) + if err != nil { + panic(err) + } + return i32 +} + +// MustReadVarInt64 returns an int64 from r or panics. +func MustReadVarInt64(r io.Reader) int64 { + i64, err := ReadVarInt64(r) + if err != nil { + panic(err) + } + return i64 +} + +// MustReadVarUint32 returns an uint32 from r or panics. +func MustReadVarUint32(r io.Reader) uint32 { + u32, err := ReadVarUint32(r) + if err != nil { + panic(err) + } + return u32 +} + +// MustReadVarUint64 returns an uint64 from r or panics. +func MustReadVarUint64(r io.Reader) uint64 { + u64, err := ReadVarUint64(r) + if err != nil { + panic(err) + } + return u64 +} + +// Copied rom http://dwarfstd.org/doc/Dwarf3.pdf. + +// ReadVarUint32 tries to read a uint32 from r. +func ReadVarUint32(r io.Reader) (uint32, error) { + u64, err := ReadVarUint64(r) + if err != nil { + return 0, err + } + return uint32(u64), nil +} + +// ReadVarUint64 tries to read a uint64 from r. +func ReadVarUint64(r io.Reader) (uint64, error) { + var result uint64 + var shift uint64 + buf := make([]byte, 1) + for { + if _, err := r.Read(buf); err != nil { + return 0, err + } + v := uint64(buf[0]) + result |= (v & 0x7F) << shift + if v&0x80 == 0 { + return result, nil + } + shift += 7 + } + +} + +// ReadVarInt32 tries to read a int32 from r. +func ReadVarInt32(r io.Reader) (int32, error) { + i64, err := ReadVarInt64(r) + if err != nil { + return 0, err + } + return int32(i64), nil +} + +// ReadVarInt64 tries to read a int64 from r. +func ReadVarInt64(r io.Reader) (int64, error) { + var result int64 + var shift uint64 + size := uint64(32) + buf := make([]byte, 1) + for { + if _, err := r.Read(buf); err != nil { + return 0, err + } + v := int64(buf[0]) + result |= (v & 0x7F) << shift + shift += 7 + if v&0x80 == 0 { + if (shift < size) && (v&0x40 != 0) { + result |= (^0 << shift) + } + return result, nil + } + } +} + +// WriteVarUint32 writes u to w. +func WriteVarUint32(w io.Writer, u uint32) error { + var b []byte + _, err := w.Write(appendUleb128(b, uint64(u))) + return err +} + +// WriteVarUint64 writes u to w. +func WriteVarUint64(w io.Writer, u uint64) error { + var b []byte + _, err := w.Write(appendUleb128(b, u)) + return err +} + +// WriteVarInt32 writes u to w. +func WriteVarInt32(w io.Writer, i int32) error { + var b []byte + _, err := w.Write(appendSleb128(b, int64(i))) + return err +} + +// WriteVarInt64 writes u to w. +func WriteVarInt64(w io.Writer, i int64) error { + var b []byte + _, err := w.Write(appendSleb128(b, i)) + return err +} + +// Copied from https://github.com/golang/go/blob/master/src/cmd/internal/dwarf/dwarf.go. + +// appendUleb128 appends v to b using DWARF's unsigned LEB128 encoding. +func appendUleb128(b []byte, v uint64) []byte { + for { + c := uint8(v & 0x7f) + v >>= 7 + if v != 0 { + c |= 0x80 + } + b = append(b, c) + if c&0x80 == 0 { + break + } + } + return b +} + +// appendSleb128 appends v to b using DWARF's signed LEB128 encoding. +func appendSleb128(b []byte, v int64) []byte { + for { + c := uint8(v & 0x7f) + s := uint8(v & 0x40) + v >>= 7 + if (v != -1 || s == 0) && (v != 0 || s != 0) { + c |= 0x80 + } + b = append(b, c) + if c&0x80 == 0 { + break + } + } + return b +} diff --git a/internal/leb128/leb128_test.go b/internal/leb128/leb128_test.go new file mode 100644 index 0000000000..34b496e3ea --- /dev/null +++ b/internal/leb128/leb128_test.go @@ -0,0 +1,207 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package leb128 + +import ( + "bytes" + "testing" +) + +// Test cases copied from http://dwarfstd.org/doc/Dwarf3.pdf. + +func TestReadVarUint64(t *testing.T) { + + tests := []struct { + bs []byte + exp uint64 + }{ + { + bs: []byte("\x02"), + exp: 2, + }, + { + bs: []byte("\x7F"), + exp: 127, + }, + { + bs: []byte("\x80\x01"), + exp: 128, + }, + { + bs: []byte("\x81\x01"), + exp: 129, + }, + { + bs: []byte("\x82\x01"), + exp: 130, + }, + { + bs: []byte("\xB9\x64"), + exp: 12857, + }, + } + + for i, tc := range tests { + r := bytes.NewReader(tc.bs) + result, err := ReadVarUint64(r) + if err != nil { + t.Fatalf("Case %d, err: %v", i, err) + } else if result != tc.exp { + t.Fatalf("Case %d, expected %v, but got %v", i, tc.exp, result) + } + } + +} + +func TestReadVarInt64(t *testing.T) { + + tests := []struct { + bs []byte + exp int64 + }{ + { + bs: []byte("\x02"), + exp: 2, + }, + { + bs: []byte("\x7E"), + exp: -2, + }, + { + bs: []byte("\xFF\x00"), + exp: 127, + }, + { + bs: []byte("\x81\x7F"), + exp: -127, + }, + { + bs: []byte("\x80\x01"), + exp: 128, + }, + { + bs: []byte("\x80\x7F"), + exp: -128, + }, + { + bs: []byte("\x81\x01"), + exp: 129, + }, + { + bs: []byte("\xFF\x7E"), + exp: -129, + }, + } + + for i, tc := range tests { + r := bytes.NewReader(tc.bs) + result, err := ReadVarInt64(r) + if err != nil { + t.Fatalf("Case %d, err: %v", i, err) + } else if result != tc.exp { + t.Fatalf("Case %d, expected %v, but got %v", i, tc.exp, result) + } + } +} + +func TestWriteVarUint64(t *testing.T) { + + tests := []struct { + bs []byte + input uint64 + }{ + { + bs: []byte("\x02"), + input: 2, + }, + { + bs: []byte("\x7F"), + input: 127, + }, + { + bs: []byte("\x80\x01"), + input: 128, + }, + { + bs: []byte("\x81\x01"), + input: 129, + }, + { + bs: []byte("\x82\x01"), + input: 130, + }, + { + bs: []byte("\xB9\x64"), + input: 12857, + }, + } + + for i, tc := range tests { + var buf bytes.Buffer + + if err := WriteVarUint64(&buf, tc.input); err != nil { + t.Fatalf("Case %d, err: %v", i, err) + } + + if !bytes.Equal(buf.Bytes(), tc.bs) { + t.Fatalf("Case %d, expected %v, but got %v", i, tc.bs, buf.Bytes()) + } + } + +} + +func TestWriteVarInt64(t *testing.T) { + + tests := []struct { + bs []byte + input int64 + }{ + { + bs: []byte("\x02"), + input: 2, + }, + { + bs: []byte("\x7E"), + input: -2, + }, + { + bs: []byte("\xFF\x00"), + input: 127, + }, + { + bs: []byte("\x81\x7F"), + input: -127, + }, + { + bs: []byte("\x80\x01"), + input: 128, + }, + { + bs: []byte("\x80\x7F"), + input: -128, + }, + { + bs: []byte("\x81\x01"), + input: 129, + }, + { + bs: []byte("\xFF\x7E"), + input: -129, + }, + } + + for i, tc := range tests { + + var buf bytes.Buffer + + if err := WriteVarInt64(&buf, tc.input); err != nil { + t.Fatalf("Case %d, err: %v", i, err) + } + + if !bytes.Equal(buf.Bytes(), tc.bs) { + t.Fatalf("Case %d, expected %v, but got %v", i, tc.bs, buf.Bytes()) + } + } +} diff --git a/internal/wasm/constant/constant.go b/internal/wasm/constant/constant.go new file mode 100644 index 0000000000..84e4d4746c --- /dev/null +++ b/internal/wasm/constant/constant.go @@ -0,0 +1,67 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// Package constant contains WASM constant definitions. +package constant + +// Magic bytes at the beginning of every WASM file ("\0asm"). +const Magic = uint32(0x6D736100) + +// Version defines the WASM version. +const Version = uint32(1) + +// WASM module section IDs. +const ( + CustomSectionID uint8 = iota + TypeSectionID + ImportSectionID + FunctionSectionID + TableSectionID + MemorySectionID + GlobalSectionID + ExportSectionID + StartSectionID + ElementSectionID + CodeSectionID + DataSectionID +) + +// FunctionTypeID indicates the start of a function type definition. +const FunctionTypeID = byte(0x60) + +// ValueType represents an intrinsic value type in WASM. +const ( + ValueTypeF64 byte = iota + 0x7C + ValueTypeF32 + ValueTypeI64 + ValueTypeI32 +) + +// WASM import descriptor types. +const ( + ImportDescType byte = iota + ImportDescTable + ImportDescMem + ImportDescGlobal +) + +// WASM export descriptor types. +const ( + ExportDescType byte = iota + ExportDescTable + ExportDescMem + ExportDescGlobal +) + +// ElementTypeAnyFunc indicates the type of a table import. +const ElementTypeAnyFunc byte = 0x70 + +// BlockTypeEmpty represents a block type. +const BlockTypeEmpty byte = 0x40 + +// WASM global varialbe mutability flag. +const ( + Const byte = iota + Mutable +) diff --git a/internal/wasm/encoding/doc.go b/internal/wasm/encoding/doc.go new file mode 100644 index 0000000000..b252369685 --- /dev/null +++ b/internal/wasm/encoding/doc.go @@ -0,0 +1,6 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// Package encoding implements WASM module reading and writing. +package encoding diff --git a/internal/wasm/encoding/encoding_test.go b/internal/wasm/encoding/encoding_test.go new file mode 100644 index 0000000000..233288bc90 --- /dev/null +++ b/internal/wasm/encoding/encoding_test.go @@ -0,0 +1,59 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package encoding + +import ( + "bytes" + "io/ioutil" + "path/filepath" + "reflect" + "testing" +) + +func TestRoundtrip(t *testing.T) { + + bs, err := ioutil.ReadFile(filepath.Join("testdata", "test1.wasm")) + if err != nil { + t.Fatal(err) + } + + module, err := ReadModule(bytes.NewBuffer(bs)) + if err != nil { + t.Fatal(err) + } + + entries, err := CodeEntries(module) + if err != nil { + t.Fatal(err) + } + + for i, e := range entries { + + var buf3 bytes.Buffer + + if err := WriteCodeEntry(&buf3, e); err != nil { + t.Fatal(err) + } + + module.Code.Segments[i].Code = buf3.Bytes() + } + + var buf2 bytes.Buffer + + if err := WriteModule(&buf2, module); err != nil { + t.Fatal(err) + } + + module2, err := ReadModule(&buf2) + if err != nil { + t.Fatal(err) + } + + // TODO(tsandall): how to make this more debuggable + if !reflect.DeepEqual(module, module2) { + t.Fatal("modules are not equal") + } + +} diff --git a/internal/wasm/encoding/reader.go b/internal/wasm/encoding/reader.go new file mode 100644 index 0000000000..5cdceda172 --- /dev/null +++ b/internal/wasm/encoding/reader.go @@ -0,0 +1,684 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package encoding + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + + "github.com/open-policy-agent/opa/internal/leb128" + "github.com/open-policy-agent/opa/internal/wasm/constant" + "github.com/open-policy-agent/opa/internal/wasm/instruction" + "github.com/open-policy-agent/opa/internal/wasm/module" + "github.com/open-policy-agent/opa/internal/wasm/opcode" + "github.com/open-policy-agent/opa/internal/wasm/types" + "github.com/pkg/errors" +) + +// ReadModule reads a binary-encoded WASM module from r. +func ReadModule(r io.Reader) (*module.Module, error) { + + wr := &reader{r: r, n: 0} + module, err := readModule(wr) + if err != nil { + return nil, errors.Wrapf(err, "offset 0x%x", wr.n) + } + + return module, nil +} + +// ReadCodeEntry reads a binary-encoded WASM code entry from r. +func ReadCodeEntry(r io.Reader) (*module.CodeEntry, error) { + + wr := &reader{r: r, n: 0} + entry, err := readCodeEntry(wr) + if err != nil { + return nil, errors.Wrapf(err, "offset 0x%x", wr.n) + } + + return entry, nil +} + +// CodeEntries returns the WASM code entries contained in r. +func CodeEntries(m *module.Module) ([]*module.CodeEntry, error) { + + entries := make([]*module.CodeEntry, len(m.Code.Segments)) + + for i, s := range m.Code.Segments { + buf := bytes.NewBuffer(s.Code) + entry, err := ReadCodeEntry(buf) + if err != nil { + return nil, err + } + entries[i] = entry + } + + return entries, nil +} + +type reader struct { + r io.Reader + n int +} + +func (r *reader) Read(bs []byte) (int, error) { + n, err := r.r.Read(bs) + r.n += n + return n, err +} + +func readModule(r io.Reader) (*module.Module, error) { + + if err := readMagic(r); err != nil { + return nil, err + } + + if err := readVersion(r); err != nil { + return nil, err + } + + var m module.Module + + if err := readSections(r, &m); err != nil && err != io.EOF { + return nil, err + } + + return &m, nil +} + +func readCodeEntry(r io.Reader) (*module.CodeEntry, error) { + + var entry module.CodeEntry + + if err := readLocals(r, &entry.Func.Locals); err != nil { + return nil, errors.Wrapf(err, "local declarations") + } + + return &entry, readExpr(r, &entry.Func.Expr) +} + +func readMagic(r io.Reader) error { + var v uint32 + if err := binary.Read(r, binary.LittleEndian, &v); err != nil { + return err + } else if v != constant.Magic { + return fmt.Errorf("illegal magic value") + } + return nil +} + +func readVersion(r io.Reader) error { + var v uint32 + if err := binary.Read(r, binary.LittleEndian, &v); err != nil { + return err + } else if v != constant.Version { + return fmt.Errorf("illegal wasm version") + } + return nil +} + +func readSections(r io.Reader, m *module.Module) error { + for { + id, err := readByte(r) + if err != nil { + return err + } + + size, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + buf := make([]byte, size) + if _, err := io.ReadFull(r, buf); err != nil { + return err + } + + bufr := bytes.NewReader(buf) + + switch id { + case constant.CustomSectionID, constant.GlobalSectionID, constant.TableSectionID, constant.ElementSectionID, constant.StartSectionID, constant.MemorySectionID: + break + case constant.TypeSectionID: + if err := readTypeSection(bufr, &m.Type); err != nil { + return errors.Wrap(err, "type section") + } + case constant.ImportSectionID: + if err := readImportSection(bufr, &m.Import); err != nil { + return errors.Wrap(err, "import section") + } + case constant.FunctionSectionID: + if err := readFunctionSection(bufr, &m.Function); err != nil { + return errors.Wrap(err, "function section") + } + case constant.ExportSectionID: + if err := readExportSection(bufr, &m.Export); err != nil { + return errors.Wrap(err, "export section") + } + case constant.DataSectionID: + if err := readDataSection(bufr, &m.Data); err != nil { + return errors.Wrap(err, "data section") + } + case constant.CodeSectionID: + if err := readRawCodeSection(bufr, &m.Code); err != nil { + return errors.Wrap(err, "code section") + } + default: + return fmt.Errorf("illegal section id") + } + } +} + +func readTypeSection(r io.Reader, s *module.TypeSection) error { + + n, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + for i := uint32(0); i < n; i++ { + + var ftype module.FunctionType + if err := readFunctionType(r, &ftype); err != nil { + return err + } + + s.Functions = append(s.Functions, ftype) + } + + return nil +} + +func readImportSection(r io.Reader, s *module.ImportSection) error { + + n, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + for i := uint32(0); i < n; i++ { + + var imp module.Import + + if err := readImport(r, &imp); err != nil { + return err + } + + s.Imports = append(s.Imports, imp) + } + + return nil +} + +func readFunctionSection(r io.Reader, s *module.FunctionSection) error { + return readVarUint32Vector(r, &s.TypeIndices) +} + +func readExportSection(r io.Reader, s *module.ExportSection) error { + + n, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + for i := uint32(0); i < n; i++ { + + var exp module.Export + + if err := readExport(r, &exp); err != nil { + return err + } + + s.Exports = append(s.Exports, exp) + } + + return nil +} + +func readDataSection(r io.Reader, s *module.DataSection) error { + + n, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + for i := uint32(0); i < n; i++ { + + var seg module.DataSegment + + if err := readDataSegment(r, &seg); err != nil { + return err + } + + s.Segments = append(s.Segments, seg) + } + + return nil +} + +func readRawCodeSection(r io.Reader, s *module.RawCodeSection) error { + + n, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + for i := uint32(0); i < n; i++ { + var seg module.RawCodeSegment + + if err := readRawCodeSegment(r, &seg); err != nil { + return err + } + + s.Segments = append(s.Segments, seg) + } + + return nil +} + +func readFunctionType(r io.Reader, ftype *module.FunctionType) error { + + if b, err := readByte(r); err != nil { + return err + } else if b != constant.FunctionTypeID { + return fmt.Errorf("illegal function type id 0x%x", b) + } + + if err := readValueTypeVector(r, &ftype.Params); err != nil { + return err + } + + return readValueTypeVector(r, &ftype.Results) +} + +func readImport(r io.Reader, imp *module.Import) error { + + if err := readByteVectorString(r, &imp.Module); err != nil { + return err + } + + if err := readByteVectorString(r, &imp.Name); err != nil { + return err + } + + b, err := readByte(r) + if err != nil { + return err + + } + + if b == constant.ImportDescType { + index, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + imp.Descriptor = module.FunctionImport{ + Func: index, + } + return nil + } + + if b == constant.ImportDescTable { + if elem, err := readByte(r); err != nil { + return err + } else if elem != constant.ElementTypeAnyFunc { + return fmt.Errorf("illegal element type") + } + desc := module.TableImport{ + Type: types.Anyfunc, + } + if err := readLimits(r, &desc.Lim); err != nil { + return err + } + imp.Descriptor = desc + return nil + } + + if b == constant.ImportDescMem { + desc := module.MemoryImport{} + if err := readLimits(r, &desc.Mem.Lim); err != nil { + return err + } + imp.Descriptor = desc + return nil + } + + if b == constant.ImportDescGlobal { + desc := module.GlobalImport{} + if err := readValueType(r, &desc.Type); err != nil { + return err + } + b, err := readByte(r) + if err != nil { + return err + } + if b == 1 { + desc.Mutable = true + } else if b != 0 { + return fmt.Errorf("illegal mutability flag") + } + return nil + } + + return fmt.Errorf("illegal import descriptor type") +} + +func readExport(r io.Reader, exp *module.Export) error { + + if err := readByteVectorString(r, &exp.Name); err != nil { + return err + } + + b, err := readByte(r) + if err != nil { + return err + } + + switch b { + case constant.ExportDescType: + exp.Descriptor.Type = module.FunctionExportType + case constant.ExportDescTable: + exp.Descriptor.Type = module.TableExportType + case constant.ExportDescMem: + exp.Descriptor.Type = module.MemoryExportType + case constant.ExportDescGlobal: + exp.Descriptor.Type = module.GlobalExportType + default: + return fmt.Errorf("illegal export descriptor type") + } + + exp.Descriptor.Index, err = leb128.ReadVarUint32(r) + if err != nil { + return err + } + + return nil +} + +func readDataSegment(r io.Reader, seg *module.DataSegment) error { + + if err := readVarUint32(r, &seg.Index); err != nil { + return err + } + + if err := readConstantExpr(r, &seg.Offset); err != nil { + return err + } + + if err := readByteVector(r, &seg.Init); err != nil { + return err + } + + return nil +} + +func readRawCodeSegment(r io.Reader, seg *module.RawCodeSegment) error { + return readByteVector(r, &seg.Code) +} + +func readConstantExpr(r io.Reader, expr *module.Expr) error { + + instrs := make([]instruction.Instruction, 0) + + for { + b, err := readByte(r) + if err != nil { + return err + } + + switch opcode.Opcode(b) { + case opcode.I32Const: + i32, err := leb128.ReadVarInt32(r) + if err != nil { + return err + } + instrs = append(instrs, instruction.I32Const{Value: i32}) + case opcode.I64Const: + i64, err := leb128.ReadVarInt64(r) + if err != nil { + return err + } + instrs = append(instrs, instruction.I64Const{Value: i64}) + case opcode.End: + expr.Instrs = instrs + return nil + default: + return fmt.Errorf("illegal constant expr opcode 0x%x", b) + } + } +} + +func readExpr(r io.Reader, expr *module.Expr) (err error) { + + defer func() { + if r := recover(); r != nil { + switch r := r.(type) { + case error: + err = r + default: + err = fmt.Errorf("unknown panic") + } + } + }() + + return readInstructions(r, &expr.Instrs) +} + +func readInstructions(r io.Reader, instrs *[]instruction.Instruction) error { + + ret := make([]instruction.Instruction, 0) + + for { + b, err := readByte(r) + if err != nil { + return err + } + + switch opcode.Opcode(b) { + case opcode.I32Const: + ret = append(ret, instruction.I32Const{Value: leb128.MustReadVarInt32(r)}) + case opcode.I64Const: + ret = append(ret, instruction.I64Const{Value: leb128.MustReadVarInt64(r)}) + case opcode.I32Eqz: + ret = append(ret, instruction.I32Eqz{}) + case opcode.GetLocal: + ret = append(ret, instruction.GetLocal{Index: leb128.MustReadVarUint32(r)}) + case opcode.SetLocal: + ret = append(ret, instruction.SetLocal{Index: leb128.MustReadVarUint32(r)}) + case opcode.Call: + ret = append(ret, instruction.Call{Index: leb128.MustReadVarUint32(r)}) + case opcode.BrIf: + ret = append(ret, instruction.BrIf{Index: leb128.MustReadVarUint32(r)}) + case opcode.Return: + ret = append(ret, instruction.Return{}) + case opcode.Block: + block := instruction.Block{} + if err := readBlockValueType(r, block.Type); err != nil { + return err + } + if err := readInstructions(r, &block.Instrs); err != nil { + return err + } + ret = append(ret, block) + case opcode.Loop: + loop := instruction.Loop{} + if err := readBlockValueType(r, loop.Type); err != nil { + return err + } + if err := readInstructions(r, &loop.Instrs); err != nil { + return err + } + ret = append(ret, loop) + case opcode.End: + *instrs = ret + return nil + default: + return fmt.Errorf("illegal opcode 0x%x", b) + } + } +} + +func readLimits(r io.Reader, l *module.Limit) error { + + b, err := readByte(r) + if err != nil { + return err + } + + min, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + l.Min = min + + if b == 1 { + max, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + l.Max = &max + } else if b != 0 { + return fmt.Errorf("illegal limit flag") + } + + return nil +} + +func readLocals(r io.Reader, locals *[]module.LocalDeclaration) error { + + n, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + ret := make([]module.LocalDeclaration, n) + + for i := uint32(0); i < n; i++ { + if err := readVarUint32(r, &ret[i].Count); err != nil { + return err + } + if err := readValueType(r, &ret[i].Type); err != nil { + return err + } + } + + *locals = ret + return nil +} + +func readByteVector(r io.Reader, v *[]byte) error { + + n, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + buf := make([]byte, n) + if _, err := io.ReadFull(r, buf); err != nil { + return err + } + + *v = buf + return nil +} + +func readByteVectorString(r io.Reader, v *string) error { + + var buf []byte + + if err := readByteVector(r, &buf); err != nil { + return err + } + + *v = string(buf) + return nil +} + +func readVarUint32Vector(r io.Reader, v *[]uint32) error { + + n, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + ret := make([]uint32, n) + + for i := uint32(0); i < n; i++ { + if err := readVarUint32(r, &ret[i]); err != nil { + return err + } + } + + *v = ret + return nil +} + +func readValueTypeVector(r io.Reader, v *[]types.ValueType) error { + + n, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + ret := make([]types.ValueType, n) + + for i := uint32(0); i < n; i++ { + if err := readValueType(r, &ret[i]); err != nil { + return err + } + } + + *v = ret + return nil +} + +func readVarUint32(r io.Reader, v *uint32) error { + var err error + *v, err = leb128.ReadVarUint32(r) + return err +} + +func readValueType(r io.Reader, v *types.ValueType) error { + if b, err := readByte(r); err != nil { + return err + } else if b == constant.ValueTypeI32 { + *v = types.I32 + } else if b == constant.ValueTypeI64 { + *v = types.I64 + } else if b == constant.ValueTypeF32 { + *v = types.F32 + } else if b == constant.ValueTypeF64 { + *v = types.F64 + } else { + return fmt.Errorf("illegal value type: 0x%x", b) + } + return nil +} + +func readBlockValueType(r io.Reader, v *types.ValueType) error { + if b, err := readByte(r); err != nil { + return err + } else if b == constant.ValueTypeI32 { + *v = types.I32 + } else if b == constant.ValueTypeI64 { + *v = types.I64 + } else if b == constant.ValueTypeF32 { + *v = types.F32 + } else if b == constant.ValueTypeF64 { + *v = types.F64 + } else if b != constant.BlockTypeEmpty { + return fmt.Errorf("illegal value type: 0x%x", b) + } + return nil +} + +func readByte(r io.Reader) (byte, error) { + buf := make([]byte, 1) + _, err := r.Read(buf) + return buf[0], err +} diff --git a/internal/wasm/encoding/testdata/test1.wasm b/internal/wasm/encoding/testdata/test1.wasm new file mode 100644 index 0000000000..b66da80e17 Binary files /dev/null and b/internal/wasm/encoding/testdata/test1.wasm differ diff --git a/internal/wasm/encoding/writer.go b/internal/wasm/encoding/writer.go new file mode 100644 index 0000000000..26e61bc53a --- /dev/null +++ b/internal/wasm/encoding/writer.go @@ -0,0 +1,473 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package encoding + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "math" + + "github.com/open-policy-agent/opa/internal/leb128" + "github.com/open-policy-agent/opa/internal/wasm/constant" + "github.com/open-policy-agent/opa/internal/wasm/instruction" + "github.com/open-policy-agent/opa/internal/wasm/module" + "github.com/open-policy-agent/opa/internal/wasm/opcode" + "github.com/open-policy-agent/opa/internal/wasm/types" +) + +// WriteModule writes a binary-encoded representation of module to w. +func WriteModule(w io.Writer, module *module.Module) error { + + if err := writeMagic(w); err != nil { + return err + } + + if err := writeVersion(w); err != nil { + return err + } + + if module == nil { + return nil + } + + if err := writeTypeSection(w, module.Type); err != nil { + return err + } + + if err := writeImportSection(w, module.Import); err != nil { + return err + } + + if err := writeFunctionSection(w, module.Function); err != nil { + return err + } + + if err := writeExportSection(w, module.Export); err != nil { + return err + } + + if err := writeRawCodeSection(w, module.Code); err != nil { + return err + } + + if err := writeDataSection(w, module.Data); err != nil { + return err + } + + return nil +} + +// WriteCodeEntry writes a binary encoded representation of entry to w. +func WriteCodeEntry(w io.Writer, entry *module.CodeEntry) error { + + if err := leb128.WriteVarUint32(w, uint32(len(entry.Func.Locals))); err != nil { + return err + } + + for _, local := range entry.Func.Locals { + + if err := leb128.WriteVarUint32(w, local.Count); err != nil { + return err + } + + if err := writeValueType(w, local.Type); err != nil { + return err + } + } + + return writeInstructions(w, entry.Func.Expr.Instrs) +} + +func writeMagic(w io.Writer) error { + return binary.Write(w, binary.LittleEndian, constant.Magic) +} + +func writeVersion(w io.Writer) error { + return binary.Write(w, binary.LittleEndian, constant.Version) +} + +func writeTypeSection(w io.Writer, s module.TypeSection) error { + + if len(s.Functions) == 0 { + return nil + } + + if err := writeByte(w, constant.TypeSectionID); err != nil { + return err + } + + var buf bytes.Buffer + + if err := leb128.WriteVarUint32(&buf, uint32(len(s.Functions))); err != nil { + return err + } + + for _, fsig := range s.Functions { + if err := writeFunctionType(&buf, fsig); err != nil { + return err + } + } + + return writeRawSection(w, &buf) +} + +func writeImportSection(w io.Writer, s module.ImportSection) error { + + if len(s.Imports) == 0 { + return nil + } + + if err := writeByte(w, constant.ImportSectionID); err != nil { + return err + } + + var buf bytes.Buffer + + if err := leb128.WriteVarUint32(&buf, uint32(len(s.Imports))); err != nil { + return err + } + + for _, imp := range s.Imports { + if err := writeImport(&buf, imp); err != nil { + return err + } + } + + return writeRawSection(w, &buf) +} + +func writeFunctionSection(w io.Writer, s module.FunctionSection) error { + + if len(s.TypeIndices) == 0 { + return nil + } + + if err := writeByte(w, constant.FunctionSectionID); err != nil { + return err + } + + var buf bytes.Buffer + + if err := leb128.WriteVarUint32(&buf, uint32(len(s.TypeIndices))); err != nil { + return err + } + + for _, idx := range s.TypeIndices { + if err := leb128.WriteVarUint32(&buf, uint32(idx)); err != nil { + return err + } + } + + return writeRawSection(w, &buf) +} + +func writeExportSection(w io.Writer, s module.ExportSection) error { + + if len(s.Exports) == 0 { + return nil + } + + if err := writeByte(w, constant.ExportSectionID); err != nil { + return err + } + + var buf bytes.Buffer + + if err := leb128.WriteVarUint32(&buf, uint32(len(s.Exports))); err != nil { + return err + } + + for _, exp := range s.Exports { + if err := writeByteVector(&buf, []byte(exp.Name)); err != nil { + return err + } + var tpe byte + switch exp.Descriptor.Type { + case module.FunctionExportType: + tpe = constant.ExportDescType + case module.TableExportType: + tpe = constant.ExportDescTable + case module.MemoryExportType: + tpe = constant.ExportDescMem + case module.GlobalExportType: + tpe = constant.ExportDescGlobal + default: + return fmt.Errorf("illegal export descriptor type 0x%x", exp.Descriptor.Type) + } + if err := writeByte(&buf, tpe); err != nil { + return err + } + if err := leb128.WriteVarUint32(&buf, exp.Descriptor.Index); err != nil { + return err + } + } + + return writeRawSection(w, &buf) +} + +func writeRawCodeSection(w io.Writer, s module.RawCodeSection) error { + + if len(s.Segments) == 0 { + return nil + } + + if err := writeByte(w, constant.CodeSectionID); err != nil { + return err + } + + var buf bytes.Buffer + + if err := leb128.WriteVarUint32(&buf, uint32(len(s.Segments))); err != nil { + return err + } + + for _, seg := range s.Segments { + if err := leb128.WriteVarUint32(&buf, uint32(len(seg.Code))); err != nil { + return err + } + if _, err := buf.Write(seg.Code); err != nil { + return err + } + } + + return writeRawSection(w, &buf) +} + +func writeDataSection(w io.Writer, s module.DataSection) error { + + if len(s.Segments) == 0 { + return nil + } + + if err := writeByte(w, constant.DataSectionID); err != nil { + return err + } + + var buf bytes.Buffer + + if err := leb128.WriteVarUint32(&buf, uint32(len(s.Segments))); err != nil { + return err + } + + for _, seg := range s.Segments { + if err := leb128.WriteVarUint32(&buf, seg.Index); err != nil { + return err + } + if err := writeInstructions(&buf, seg.Offset.Instrs); err != nil { + return err + } + if err := writeByteVector(&buf, seg.Init); err != nil { + return err + } + } + + return writeRawSection(w, &buf) +} + +func writeFunctionType(w io.Writer, fsig module.FunctionType) error { + + if err := writeByte(w, constant.FunctionTypeID); err != nil { + return err + } + + if err := writeValueTypeVector(w, fsig.Params); err != nil { + return err + } + + return writeValueTypeVector(w, fsig.Results) +} + +func writeImport(w io.Writer, imp module.Import) error { + + if err := writeByteVector(w, []byte(imp.Module)); err != nil { + return err + } + + if err := writeByteVector(w, []byte(imp.Name)); err != nil { + return err + } + + switch desc := imp.Descriptor.(type) { + case module.FunctionImport: + if err := writeByte(w, constant.ImportDescType); err != nil { + return err + } + return leb128.WriteVarUint32(w, desc.Func) + case module.TableImport: + if err := writeByte(w, constant.ImportDescTable); err != nil { + return err + } + if err := writeByte(w, constant.ElementTypeAnyFunc); err != nil { + return err + } + return writeLimits(w, desc.Lim) + case module.MemoryImport: + if err := writeByte(w, constant.ImportDescMem); err != nil { + return err + } + return writeLimits(w, desc.Mem.Lim) + case module.GlobalImport: + if err := writeByte(w, constant.ImportDescGlobal); err != nil { + return err + } + if err := writeValueType(w, desc.Type); err != nil { + return err + } + if desc.Mutable { + return writeByte(w, constant.Mutable) + } + return writeByte(w, constant.Const) + default: + return fmt.Errorf("illegal import descriptor type") + } +} + +func writeInstructions(w io.Writer, instrs []instruction.Instruction) error { + + for i, instr := range instrs { + + _, err := w.Write([]byte{byte(instr.Op())}) + if err != nil { + return err + } + + for _, arg := range instr.ImmediateArgs() { + var err error + switch arg := arg.(type) { + case int32: + err = leb128.WriteVarInt32(w, arg) + case int64: + err = leb128.WriteVarInt64(w, arg) + case uint32: + err = leb128.WriteVarUint32(w, arg) + case uint64: + err = leb128.WriteVarUint64(w, arg) + case float32: + u32 := math.Float32bits(arg) + err = binary.Write(w, binary.LittleEndian, u32) + case float64: + u64 := math.Float64bits(arg) + err = binary.Write(w, binary.LittleEndian, u64) + default: + return fmt.Errorf("illegal immediate argument type on instruction %d", i) + } + if err != nil { + return err + } + } + + if si, ok := instr.(instruction.StructuredInstruction); ok { + if err := writeBlockValueType(w, si.BlockType()); err != nil { + return err + } + if err := writeInstructions(w, si.Instructions()); err != nil { + return err + } + } + + } + + _, err := w.Write([]byte{byte(opcode.End)}) + return err +} + +func writeLimits(w io.Writer, lim module.Limit) error { + if lim.Max == nil { + if err := writeByte(w, 0); err != nil { + return err + } + } else { + if err := writeByte(w, 1); err != nil { + return err + } + } + if err := leb128.WriteVarUint32(w, lim.Min); err != nil { + return err + } + if lim.Max != nil { + return leb128.WriteVarUint32(w, *lim.Max) + } + return nil +} + +func writeValueTypeVector(w io.Writer, v []types.ValueType) error { + + if err := leb128.WriteVarUint32(w, uint32(len(v))); err != nil { + return err + } + + for i := range v { + if err := writeValueType(w, v[i]); err != nil { + return err + } + } + + return nil +} + +func writeBlockValueType(w io.Writer, v *types.ValueType) error { + var b byte + if v != nil { + switch *v { + case types.I32: + b = constant.ValueTypeI32 + case types.I64: + b = constant.ValueTypeI64 + case types.F32: + b = constant.ValueTypeF32 + case types.F64: + b = constant.ValueTypeF64 + } + } else { + b = constant.BlockTypeEmpty + } + return writeByte(w, b) +} + +func writeValueType(w io.Writer, v types.ValueType) error { + var b byte + switch v { + case types.I32: + b = constant.ValueTypeI32 + case types.I64: + b = constant.ValueTypeI64 + case types.F32: + b = constant.ValueTypeF32 + case types.F64: + b = constant.ValueTypeF64 + } + return writeByte(w, b) +} + +func writeRawSection(w io.Writer, buf *bytes.Buffer) error { + + size := buf.Len() + + if err := leb128.WriteVarUint32(w, uint32(size)); err != nil { + return err + } + + _, err := io.Copy(w, buf) + return err +} + +func writeByteVector(w io.Writer, bs []byte) error { + + if err := leb128.WriteVarUint32(w, uint32(len(bs))); err != nil { + return err + } + + _, err := w.Write(bs) + return err +} + +func writeByte(w io.Writer, b byte) error { + buf := make([]byte, 1) + buf[0] = b + _, err := w.Write(buf) + return err +} diff --git a/internal/wasm/instruction/control.go b/internal/wasm/instruction/control.go new file mode 100644 index 0000000000..6a024212ef --- /dev/null +++ b/internal/wasm/instruction/control.go @@ -0,0 +1,124 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package instruction + +import ( + "github.com/open-policy-agent/opa/internal/wasm/opcode" + "github.com/open-policy-agent/opa/internal/wasm/types" +) + +// Unreachable reprsents an unreachable opcode. +type Unreachable struct { + NoImmediateArgs +} + +// Op returns the opcode of the instruction. +func (Unreachable) Op() opcode.Opcode { + return opcode.Unreachable +} + +// Nop represents a WASM no-op instruction. +type Nop struct { + NoImmediateArgs +} + +// Op returns the opcode of the instruction. +func (Nop) Op() opcode.Opcode { + return opcode.Nop +} + +// Block represents a WASM block instruction. +type Block struct { + NoImmediateArgs + Type *types.ValueType + Instrs []Instruction +} + +// Op returns the opcode of the instruction +func (Block) Op() opcode.Opcode { + return opcode.Block +} + +// BlockType returns the type of the block's return value. +func (i Block) BlockType() *types.ValueType { + return i.Type +} + +// Instructions returns the instructions contained in the block. +func (i Block) Instructions() []Instruction { + return i.Instrs +} + +// Loop represents a WASM loop instruction. +type Loop struct { + NoImmediateArgs + Type *types.ValueType + Instrs []Instruction +} + +// Op returns the opcode of the instruction. +func (Loop) Op() opcode.Opcode { + return opcode.Loop +} + +// BlockType returns the type of the loop's return value. +func (i Loop) BlockType() *types.ValueType { + return i.Type +} + +// Instructions represents the instructions contained in the loop. +func (i Loop) Instructions() []Instruction { + return i.Instrs +} + +// BrIf represents a WASM br_if instruction. +type BrIf struct { + Index uint32 +} + +// Op returns the opcode of the instruction. +func (BrIf) Op() opcode.Opcode { + return opcode.BrIf +} + +// ImmediateArgs returns the block index to break to. +func (i BrIf) ImmediateArgs() []interface{} { + return []interface{}{i.Index} +} + +// Call represents a WASM call instruction. +type Call struct { + Index uint32 +} + +// Op returns the opcode of the instruction. +func (Call) Op() opcode.Opcode { + return opcode.Call +} + +// ImmediateArgs returns the function index. +func (i Call) ImmediateArgs() []interface{} { + return []interface{}{i.Index} +} + +// Return represents a WASM return instruction. +type Return struct { + NoImmediateArgs +} + +// Op returns the opcode of the instruction. +func (Return) Op() opcode.Opcode { + return opcode.Return +} + +// End represents the special WASM end instruction. +type End struct { + NoImmediateArgs +} + +// Op returns the opcode of the instruction. +func (End) Op() opcode.Opcode { + return opcode.End +} diff --git a/internal/wasm/instruction/instruction.go b/internal/wasm/instruction/instruction.go new file mode 100644 index 0000000000..066be77c44 --- /dev/null +++ b/internal/wasm/instruction/instruction.go @@ -0,0 +1,33 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// Package instruction defines WASM instruction types. +package instruction + +import ( + "github.com/open-policy-agent/opa/internal/wasm/opcode" + "github.com/open-policy-agent/opa/internal/wasm/types" +) + +// NoImmediateArgs indicates the instruction has no immediate arguments. +type NoImmediateArgs struct { +} + +// ImmediateArgs returns the immedate arguments of an instruction. +func (NoImmediateArgs) ImmediateArgs() []interface{} { + return nil +} + +// Instruction represents a single WASM instruction. +type Instruction interface { + Op() opcode.Opcode + ImmediateArgs() []interface{} +} + +// StructuredInstruction represents a structured control instruction like br_if. +type StructuredInstruction interface { + Instruction + BlockType() *types.ValueType + Instructions() []Instruction +} diff --git a/internal/wasm/instruction/numeric.go b/internal/wasm/instruction/numeric.go new file mode 100644 index 0000000000..3bad8dd9a2 --- /dev/null +++ b/internal/wasm/instruction/numeric.go @@ -0,0 +1,49 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package instruction + +import ( + "github.com/open-policy-agent/opa/internal/wasm/opcode" +) + +// I32Const represents the WASM i32.const instruction. +type I32Const struct { + Value int32 +} + +// Op returns the opcode of the instruction. +func (I32Const) Op() opcode.Opcode { + return opcode.I32Const +} + +// ImmediateArgs returns the i32 value to push onto the stack. +func (i I32Const) ImmediateArgs() []interface{} { + return []interface{}{i.Value} +} + +// I64Const represents the WASM i64.const instruction. +type I64Const struct { + Value int64 +} + +// Op returns the opcode of the instruction. +func (I64Const) Op() opcode.Opcode { + return opcode.I64Const +} + +// ImmediateArgs returns the i64 value to push onto the stack. +func (i I64Const) ImmediateArgs() []interface{} { + return []interface{}{i.Value} +} + +// I32Eqz represents the WASM i32.eqz instruction. +type I32Eqz struct { + NoImmediateArgs +} + +// Op returns the opcode of the instruction. +func (I32Eqz) Op() opcode.Opcode { + return opcode.I32Eqz +} diff --git a/internal/wasm/instruction/variable.go b/internal/wasm/instruction/variable.go new file mode 100644 index 0000000000..ac57e5048f --- /dev/null +++ b/internal/wasm/instruction/variable.go @@ -0,0 +1,38 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package instruction + +import "github.com/open-policy-agent/opa/internal/wasm/opcode" + +// GetLocal represents the WASM get_local instruction. +type GetLocal struct { + Index uint32 +} + +// Op returns the opcode of the instruction. +func (GetLocal) Op() opcode.Opcode { + return opcode.GetLocal +} + +// ImmediateArgs returns the index of the local variable to push onto the stack. +func (i GetLocal) ImmediateArgs() []interface{} { + return []interface{}{i.Index} +} + +// SetLocal represents the WASM set_local instruction. +type SetLocal struct { + Index uint32 +} + +// Op returns the opcode of the instruction. +func (SetLocal) Op() opcode.Opcode { + return opcode.SetLocal +} + +// ImmediateArgs returns the index of the local variable to set with the top of +// the stack. +func (i SetLocal) ImmediateArgs() []interface{} { + return []interface{}{i.Index} +} diff --git a/internal/wasm/module/module.go b/internal/wasm/module/module.go new file mode 100644 index 0000000000..45f8bf5f3d --- /dev/null +++ b/internal/wasm/module/module.go @@ -0,0 +1,280 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package module + +import ( + "fmt" + "strings" + + "github.com/open-policy-agent/opa/internal/wasm/instruction" + "github.com/open-policy-agent/opa/internal/wasm/types" +) + +type ( + // Module represents a WASM module. + Module struct { + Version uint32 + Type TypeSection + Import ImportSection + Function FunctionSection + Export ExportSection + Code RawCodeSection + Data DataSection + } + + // TypeSection represents a WASM type section. + TypeSection struct { + Functions []FunctionType + } + + // ImportSection represents a WASM import section. + ImportSection struct { + Imports []Import + } + + // FunctionSection represents a WASM function section. + FunctionSection struct { + TypeIndices []uint32 + } + + // ExportSection represents a WASM export section. + ExportSection struct { + Exports []Export + } + + // RawCodeSection represents a WASM code section. The code section is left as a + // raw byte sequence. See CodeSection for the decoded version. + RawCodeSection struct { + Segments []RawCodeSegment + } + + // DataSection represents a WASM data section. + DataSection struct { + Segments []DataSegment + } + + // FunctionType represents a WASM function type definition. + FunctionType struct { + Params []types.ValueType + Results []types.ValueType + } + + // Import represents a WASM import statement. + Import struct { + Module string + Name string + Descriptor ImportDescriptor + } + + // ImportDescriptor represents a WASM import descriptor. + ImportDescriptor interface { + fmt.Stringer + Kind() ImportDescriptorType + } + + // ImportDescriptorType defines allowed kinds of import descriptors. + ImportDescriptorType int + + // FunctionImport represents a WASM function import statement. + FunctionImport struct { + Func uint32 + } + + // MemoryImport represents a WASM memory import statement. + MemoryImport struct { + Mem MemType + } + + // MemType defines the attributes of a memory import. + MemType struct { + Lim Limit + } + + // TableImport represents a WASM table import statement. + TableImport struct { + Type types.ElementType + Lim Limit + } + + // GlobalImport represents a WASM global variable import statement. + GlobalImport struct { + Type types.ValueType + Mutable bool + } + + // Limit represents a WASM limit. + Limit struct { + Min uint32 + Max *uint32 + } + + // Export represents a WASM export statement. + Export struct { + Name string + Descriptor ExportDescriptor + } + + // ExportDescriptor represents a WASM export descriptor. + ExportDescriptor struct { + Type ExportDescriptorType + Index uint32 + } + + // ExportDescriptorType defines the allowed kinds of export descriptors. + ExportDescriptorType int + + // RawCodeSegment represents a binary-encoded WASM code segment. + RawCodeSegment struct { + Code []byte + } + + // DataSegment represents a WASM data segment. + DataSegment struct { + Index uint32 + Offset Expr + Init []byte + } + + // Expr represents a WASM expression. + Expr struct { + Instrs []instruction.Instruction + } + + // CodeEntry represents a code segment entry. + CodeEntry struct { + Func Function + } + + // Function represents a function in a code segment. + Function struct { + Locals []LocalDeclaration + Expr Expr + } + + // LocalDeclaration represents a local variable declaration. + LocalDeclaration struct { + Count uint32 + Type types.ValueType + } +) + +// Defines the allowed kinds of imports. +const ( + FunctionImportType ImportDescriptorType = iota + TableImportType + MemoryImportType + GlobalImportType +) + +func (x ImportDescriptorType) String() string { + switch x { + case FunctionImportType: + return "func" + case TableImportType: + return "table" + case MemoryImportType: + return "memory" + case GlobalImportType: + return "global" + } + panic("illegal value") +} + +// Defines the allowed kinds of exports. +const ( + FunctionExportType ExportDescriptorType = iota + TableExportType + MemoryExportType + GlobalExportType +) + +func (x ExportDescriptorType) String() string { + switch x { + case FunctionExportType: + return "func" + case TableExportType: + return "table" + case MemoryExportType: + return "memory" + case GlobalExportType: + return "global" + } + panic("illegal value") +} + +// Kind returns the function import type kind. +func (i FunctionImport) Kind() ImportDescriptorType { + return FunctionImportType +} + +func (i FunctionImport) String() string { + return fmt.Sprintf("%v[type=%v]", i.Kind(), i.Func) +} + +// Kind returns the memory import type kind. +func (i MemoryImport) Kind() ImportDescriptorType { + return MemoryImportType +} + +func (i MemoryImport) String() string { + return fmt.Sprintf("%v[%v]", i.Kind(), i.Mem.Lim) +} + +// Kind returns the table import type kind. +func (i TableImport) Kind() ImportDescriptorType { + return TableImportType +} + +func (i TableImport) String() string { + return fmt.Sprintf("%v[%v, %v]", i.Kind(), i.Type, i.Lim) +} + +// Kind returns the global import type kind. +func (i GlobalImport) Kind() ImportDescriptorType { + return GlobalImportType +} + +func (i GlobalImport) String() string { + return fmt.Sprintf("%v[%v, mut=%v]", i.Kind(), i.Type, i.Mutable) +} + +func (tpe FunctionType) String() string { + params := make([]string, len(tpe.Params)) + results := make([]string, len(tpe.Results)) + for i := range tpe.Params { + params[i] = tpe.Params[i].String() + } + for i := range tpe.Results { + results[i] = tpe.Results[i].String() + } + return "(" + strings.Join(params, ", ") + ") -> (" + strings.Join(results, ", ") + ")" +} + +func (imp Import) String() string { + return fmt.Sprintf("%v %v.%v", imp.Descriptor.String(), imp.Module, imp.Name) +} + +func (exp Export) String() string { + return fmt.Sprintf("%v[%v] %v", exp.Descriptor.Type, exp.Descriptor.Index, exp.Name) +} + +func (seg RawCodeSegment) String() string { + return fmt.Sprintf("", len(seg.Code)) +} + +func (seg DataSegment) String() string { + return fmt.Sprintf("", seg.Index, seg.Offset, len(seg.Init)) +} + +func (e Expr) String() string { + return fmt.Sprintf("%d instr(s)", len(e.Instrs)) +} + +func (lim Limit) String() string { + if lim.Max == nil { + return fmt.Sprintf("min=%v", lim.Min) + } + return fmt.Sprintf("min=%v max=%v", lim.Min, lim.Max) +} diff --git a/internal/wasm/module/pretty.go b/internal/wasm/module/pretty.go new file mode 100644 index 0000000000..3b566a27b0 --- /dev/null +++ b/internal/wasm/module/pretty.go @@ -0,0 +1,84 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +package module + +import ( + "encoding/hex" + "fmt" + "io" +) + +// PrettyOption defines options for controlling pretty printing. +type PrettyOption struct { + Contents bool // show raw byte content of data+code sections. +} + +// Pretty writes a human-readable representation of m to w. +func Pretty(w io.Writer, m *Module, opts ...PrettyOption) { + fmt.Println("version:", m.Version) + fmt.Println("types:") + for _, fn := range m.Type.Functions { + fmt.Println(" -", fn) + } + fmt.Println("imports:") + for i, imp := range m.Import.Imports { + if imp.Descriptor.Kind() == FunctionImportType { + fmt.Printf(" - [%d] %v\n", i, imp) + } else { + fmt.Println(" -", imp) + } + } + fmt.Println("functions:") + for _, fn := range m.Function.TypeIndices { + if fn >= uint32(len(m.Type.Functions)) { + fmt.Println(" -", "???") + } else { + fmt.Println(" -", m.Type.Functions[fn]) + } + } + fmt.Println("exports:") + for _, exp := range m.Export.Exports { + fmt.Println(" -", exp) + } + fmt.Println("code:") + for _, seg := range m.Code.Segments { + fmt.Println(" -", seg) + } + fmt.Println("data:") + for _, seg := range m.Data.Segments { + fmt.Println(" -", seg) + } + if len(opts) == 0 { + return + } + fmt.Println() + for _, opt := range opts { + if opt.Contents { + newline := false + if len(m.Data.Segments) > 0 { + fmt.Println("data section:") + for _, seg := range m.Data.Segments { + if newline { + fmt.Println() + } + fmt.Println(hex.Dump(seg.Init)) + newline = true + } + newline = false + } + if len(m.Code.Segments) > 0 { + fmt.Println("code section:") + for _, seg := range m.Code.Segments { + if newline { + fmt.Println() + } + fmt.Println(hex.Dump(seg.Code)) + newline = true + } + newline = false + } + } + } +} diff --git a/internal/wasm/opcode/opcode.go b/internal/wasm/opcode/opcode.go new file mode 100644 index 0000000000..7d35a3012c --- /dev/null +++ b/internal/wasm/opcode/opcode.go @@ -0,0 +1,218 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// Package opcode contains constants and utilities for working with WASM opcodes. +package opcode + +// Opcode represents a WASM instruction opcode. +type Opcode byte + +// Control instructions. +const ( + Unreachable Opcode = iota + Nop + Block + Loop + If + Else +) + +const ( + // End defines the special end WASM opcode. + End Opcode = 0x0B +) + +// Extended control instructions. +const ( + Br Opcode = iota + 0x0C + BrIf + BrTable + Return + Call + CallIndirect +) + +// Parameter instructions. +const ( + Drop Opcode = iota + 0x1A + Select +) + +// Variable instructions. +const ( + GetLocal Opcode = iota + 0x20 + SetLocal + TeeLocal + GetGlobal + SetGlobal +) + +// Memory instructions. +const ( + I32Load Opcode = iota + 0x28 + I64Load + F32Load + F64Load + I32Load8S + I32Load8U + I32Load16S + I32Load16U + I64Load8S + I64Load8U + I64Load16S + I64Load16U + I64Load32S + I64Load32U + I32Store + I64Store + F32Store + F64Store + I32Store8 + I32Store16 + I64Store8 + I64Store16 + I64Store32 + MemorySize + MemoryGrow +) + +// Numeric instructions. +const ( + I32Const Opcode = iota + 0x41 + I64Const + F32Const + F64Const + + I32Eqz + I32Eq + I32Ne + I32LtS + I32LtU + I32GtS + I32GtU + I32LeS + I32LeU + I32GeS + I32GeU + + I64Eqz + I64Eq + I64Ne + I64LtS + I64LtU + I64GtS + I64GtU + I64LeS + I64LeU + I64GeS + I64GeU + + F32Eq + F32Ne + F32Lt + F32Gt + F32Le + F32Ge + + F64Eq + F64Ne + F64Lt + F64Gt + F64Le + F64Ge + + I32Clz + I32Ctz + I32Popcnt + I32Add + I32Sub + I32Mul + I32DivS + I32DivU + I32RemS + I32RemU + I32And + I32Or + I32Xor + I32Shl + I32ShrS + I32ShrU + I32Rotl + I32Rotr + + I64Clz + I64Ctz + I64Popcnt + I64Add + I64Sub + I64Mul + I64DivS + I64DivU + I64RemS + I64RemU + I64And + I64Or + I64Xor + I64Shl + I64ShrS + I64ShrU + I64Rotl + I64Rotr + + F32Abs + F32Neg + F32Ceil + F32Floor + F32Trunc + F32Nearest + F32Sqrt + F32Add + F32Sub + F32Mul + F32Div + F32Min + F32Max + F32Copysign + + F64Abs + F64Neg + F64Ceil + F64Floor + F64Trunc + F64Nearest + F64Sqrt + F64Add + F64Sub + F64Mul + F64Div + F64Min + F64Max + F64Copysign + + I32WrapI64 + I32TruncSF32 + I32TruncUF32 + I32TruncSF64 + I32TruncUF64 + I64ExtendSI32 + I64ExtendUI32 + I64TruncSF32 + I64TruncUF32 + I64TruncSF64 + I64TruncUF64 + F32ConvertSI32 + F32ConvertUI32 + F32ConvertSI64 + F32ConvertUI64 + F32DemoteF64 + F64ConvertSI32 + F64ConvertUI32 + F64ConvertSI64 + F64ConvertUI64 + F64PromoteF32 + I32ReinterpretF32 + I64ReinterpretF64 + F32ReinterpretI32 + F64ReinterpretI64 +) diff --git a/internal/wasm/types/types.go b/internal/wasm/types/types.go new file mode 100644 index 0000000000..4e2b776220 --- /dev/null +++ b/internal/wasm/types/types.go @@ -0,0 +1,36 @@ +// Copyright 2018 The OPA Authors. All rights reserved. +// Use of this source code is governed by an Apache2 +// license that can be found in the LICENSE file. + +// Package types defines the WASM value type constants. +package types + +// ValueType represents an intrinsic value in WASM. +type ValueType int + +// Defines the intrinsic value types. +const ( + I32 ValueType = iota + I64 + F32 + F64 +) + +func (tpe ValueType) String() string { + if tpe == I32 { + return "i32" + } else if tpe == I64 { + return "i64" + } else if tpe == F32 { + return "f32" + } + return "f64" +} + +// ElementType defines the type of table elements. +type ElementType int + +const ( + // Anyfunc is the union of all table types. + Anyfunc ElementType = iota +)