// Copyright 2023 CUE Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package wasm import ( "encoding/binary" "fmt" "math" "cuelang.org/go/cue" "cuelang.org/go/internal/core/adt" ) // typ is the type (or kind) of an external type. type typ int8 const ( typErr typ = iota typBool typUint8 typUint16 typUint32 typUint64 typInt8 typInt16 typInt32 typInt64 typFloat32 typFloat64 typStruct ) // field represents a name struct field. type field struct { typ from string // the field name } // positionedField represents a struct field with a known location. type positionedField struct { field offset int // memory offset in the parent struct. inner *structLayout // IFF typ==typStruct } // structLayout describes the memory layout of a struct. type structLayout struct { fields []positionedField size int align int } func sizeof(t typ) int { switch t { case typBool, typUint8, typInt8: return 1 case typUint16, typInt16: return 2 case typUint32, typInt32, typFloat32: return 4 case typUint64, typInt64, typFloat64: return 8 } panic("unreachable") } func encodeStruct(i *instance, v cue.Value, l *structLayout) []*memory { buf := make([]byte, l.size) ms := make([]*memory, 1, 2) // cap is 2 for strings and bytes. buf, ms = encode(i, v, l, buf, ms) ms[0] = encBytes(i, buf) return ms } // encodeStruct serializes v into buf according to the layout. func encode(i *instance, v cue.Value, l *structLayout, buf []byte, ms []*memory) ([]byte, []*memory) { for _, f := range l.fields { arg := v.LookupPath(cue.ParsePath(f.from)) switch f.typ { case typBool: b, _ := arg.Bool() if b { buf[f.offset] = 1 } else { buf[f.offset] = 0 } case typUint8: u, _ := arg.Uint64() buf[f.offset] = byte(u) case typUint16: u, _ := arg.Uint64() binary.LittleEndian.PutUint16(buf[f.offset:], uint16(u)) case typUint32: u, _ := arg.Uint64() binary.LittleEndian.PutUint32(buf[f.offset:], uint32(u)) case typUint64: u, _ := arg.Uint64() binary.LittleEndian.PutUint64(buf[f.offset:], u) case typInt8: u, _ := arg.Int64() buf[f.offset] = byte(u) case typInt16: u, _ := arg.Int64() binary.LittleEndian.PutUint16(buf[f.offset:], uint16(u)) case typInt32: u, _ := arg.Int64() binary.LittleEndian.PutUint32(buf[f.offset:], uint32(u)) case typInt64: u, _ := arg.Int64() binary.LittleEndian.PutUint64(buf[f.offset:], uint64(u)) case typFloat32: x, _ := arg.Float64() binary.LittleEndian.PutUint32(buf[f.offset:], math.Float32bits(float32(x))) case typFloat64: x, _ := arg.Float64() binary.LittleEndian.PutUint64(buf[f.offset:], math.Float64bits(x)) case typStruct: encode(i, arg, f.inner, buf[f.offset:], ms) default: panic(fmt.Sprintf("unsupported argument %v (kind %v)", v, v.IncompleteKind())) } } return buf, ms } // decodeStruct takes the binary representation of a struct described // by the layout and returns its Go representation as a map. func decodeStruct(buf []byte, l *structLayout) map[string]any { m := make(map[string]any) for _, f := range l.fields { switch f.typ { case typBool: u := buf[f.offset] if u == 1 { m[f.from] = true } else { m[f.from] = false } case typUint8: u := buf[f.offset] m[f.from] = u case typUint16: u := binary.LittleEndian.Uint16(buf[f.offset:]) m[f.from] = u case typUint32: u := binary.LittleEndian.Uint32(buf[f.offset:]) m[f.from] = u case typUint64: u := binary.LittleEndian.Uint64(buf[f.offset:]) m[f.from] = u case typInt8: u := buf[f.offset] m[f.from] = int8(u) case typInt16: u := binary.LittleEndian.Uint16(buf[f.offset:]) m[f.from] = int16(u) case typInt32: u := binary.LittleEndian.Uint32(buf[f.offset:]) m[f.from] = int32(u) case typInt64: u := binary.LittleEndian.Uint64(buf[f.offset:]) m[f.from] = int64(u) case typFloat32: u := binary.LittleEndian.Uint32(buf[f.offset:]) m[f.from] = math.Float32frombits(u) case typFloat64: u := binary.LittleEndian.Uint64(buf[f.offset:]) m[f.from] = math.Float64frombits(u) case typStruct: to := f.offset + f.inner.size m[f.from] = decodeStruct(buf[f.offset:to], f.inner) default: panic(fmt.Sprintf("unsupported argument type: %v", f.typ)) } } return m } func align(x, n int) int { return (x + n - 1) & ^(n - 1) } // structLayoutVal returns the System V (C ABI) memory layout of the // struct expressed by t. func structLayoutVal(t cue.Value) *structLayout { if t.IncompleteKind() != adt.StructKind { panic("expected CUE struct") } var sl structLayout off, size := 0, 0 for i, _ := t.Fields(cue.Attributes(true)); i.Next(); { f := i.Value() path := i.Selector().String() switch f.IncompleteKind() { case adt.StructKind: inner := structLayoutVal(f) off = align(off, inner.align) lval := positionedField{ field: field{ typ: typStruct, from: path, }, offset: off, inner: inner, } sl.fields = append(sl.fields, lval) off += inner.size case cue.BoolKind, cue.IntKind, cue.FloatKind, cue.NumberKind: typ := typVal(f) size = sizeof(typ) off = align(off, size) lval := positionedField{ field: field{ typ: typ, from: path, }, offset: off, } sl.fields = append(sl.fields, lval) off += size default: panic(fmt.Sprintf("unsupported argument type %v (kind %v)", f, f.IncompleteKind())) } } // The alignment of a struct is the maximum alignment of its // constituent fields. maxalign := 0 for _, f := range sl.fields { if f.typ == typStruct { if f.inner.align > maxalign { maxalign = f.inner.align } continue } if sizeof(f.typ) > maxalign { maxalign = sizeof(f.typ) } } sl.size = align(off, maxalign) sl.align = maxalign return &sl } func typVal(v cue.Value) typ { switch v.IncompleteKind() { case cue.BoolKind: return typBool case cue.IntKind, cue.FloatKind, cue.NumberKind: return typNum(v) default: panic(fmt.Sprintf("unsupported argument type %v (kind %v)", v, v.IncompleteKind())) } } func typNum(t cue.Value) typ { ctx := t.Context() _int8 := ctx.CompileString("int8") if _int8.Subsume(t) == nil { return typInt8 } _uint8 := ctx.CompileString("uint8") if _uint8.Subsume(t) == nil { return typUint8 } _int16 := ctx.CompileString("int16") if _int16.Subsume(t) == nil { return typInt16 } _uint16 := ctx.CompileString("uint16") if _uint16.Subsume(t) == nil { return typUint16 } _int32 := ctx.CompileString("int32") if _int32.Subsume(t) == nil { return typInt32 } _uint32 := ctx.CompileString("uint32") if _uint32.Subsume(t) == nil { return typUint32 } _int64 := ctx.CompileString("int64") if _int64.Subsume(t) == nil { return typInt64 } _uint64 := ctx.CompileString("uint64") if _uint64.Subsume(t) == nil { return typUint64 } _float32 := ctx.CompileString("float32") if _float32.Subsume(t) == nil { return typFloat32 } _float64 := ctx.CompileString("float64") if _float64.Subsume(t) == nil { return typFloat64 } panic("unreachable") }