From 622bcbdf9d8b0daf2e1e2d537f80203cee6b3461 Mon Sep 17 00:00:00 2001 From: Torin Sandall Date: Fri, 5 Oct 2018 10:51:53 -0700 Subject: [PATCH] Add WASM compiler backend and required types These changes implement a basic WASM compiler backend for the IR added in the previous commit. These changes also include a binary-encoding package that can roundtrip simple WASM modules. Signed-off-by: Torin Sandall --- internal/compiler/wasm/externs.go | 64 ++ internal/compiler/wasm/functypes.go | 31 + internal/compiler/wasm/wasm.go | 270 ++++++++ internal/compiler/wasm/wasm_test.go | 29 + internal/leb128/leb128.go | 170 +++++ internal/leb128/leb128_test.go | 207 +++++++ internal/wasm/constant/constant.go | 67 ++ internal/wasm/encoding/doc.go | 6 + internal/wasm/encoding/encoding_test.go | 59 ++ internal/wasm/encoding/reader.go | 684 +++++++++++++++++++++ internal/wasm/encoding/testdata/test1.wasm | Bin 0 -> 409 bytes internal/wasm/encoding/writer.go | 473 ++++++++++++++ internal/wasm/instruction/control.go | 124 ++++ internal/wasm/instruction/instruction.go | 33 + internal/wasm/instruction/numeric.go | 49 ++ internal/wasm/instruction/variable.go | 38 ++ internal/wasm/module/module.go | 280 +++++++++ internal/wasm/module/pretty.go | 84 +++ internal/wasm/opcode/opcode.go | 218 +++++++ internal/wasm/types/types.go | 36 ++ 20 files changed, 2922 insertions(+) create mode 100644 internal/compiler/wasm/externs.go create mode 100644 internal/compiler/wasm/functypes.go create mode 100644 internal/compiler/wasm/wasm.go create mode 100644 internal/compiler/wasm/wasm_test.go create mode 100644 internal/leb128/leb128.go create mode 100644 internal/leb128/leb128_test.go create mode 100644 internal/wasm/constant/constant.go create mode 100644 internal/wasm/encoding/doc.go create mode 100644 internal/wasm/encoding/encoding_test.go create mode 100644 internal/wasm/encoding/reader.go create mode 100644 internal/wasm/encoding/testdata/test1.wasm create mode 100644 internal/wasm/encoding/writer.go create mode 100644 internal/wasm/instruction/control.go create mode 100644 internal/wasm/instruction/instruction.go create mode 100644 internal/wasm/instruction/numeric.go create mode 100644 internal/wasm/instruction/variable.go create mode 100644 internal/wasm/module/module.go create mode 100644 internal/wasm/module/pretty.go create mode 100644 internal/wasm/opcode/opcode.go create mode 100644 internal/wasm/types/types.go 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 0000000000000000000000000000000000000000..b66da80e17f4a317d5644c344a99a99be61d3c08 GIT binary patch literal 409 zcmYL_zfQw25XL{-{YRsM5n@1MNZzDIsu)3{h%t5BqfM3AC3cFK(v^vg_u`Fkno1e& zr2E}>-<=KZ3I>2lvEX&h>IG{P_#Yb!yVsW?(w(y=-Rsg3(7aOygcG z8^d|`7pOaYqo{7R$6e$+c1BW_}qPv3p)~@k5o1=2!=A~WY`RQej b(3a7?tCl-jc?Z1?9|pbhnKk$ (" + 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 +)