diff --git a/api/wasm.go b/api/wasm.go index bcaf1b1f..a0b43c13 100644 --- a/api/wasm.go +++ b/api/wasm.go @@ -459,6 +459,9 @@ type Memory interface { // Write writes the slice to the underlying buffer at the offset or returns false if out of range. Write(ctx context.Context, offset uint32, v []byte) bool + + // WriteString writes the string to the underlying buffer at the offset or returns false if out of range. + WriteString(ctx context.Context, offset uint32, v string) bool } // EncodeExternref encodes the input as a ValueTypeExternref. diff --git a/internal/wasm/memory.go b/internal/wasm/memory.go index 36034efc..a03d8f0e 100644 --- a/internal/wasm/memory.go +++ b/internal/wasm/memory.go @@ -162,6 +162,15 @@ func (m *MemoryInstance) Write(_ context.Context, offset uint32, val []byte) boo return true } +// WriteString implements the same method as documented on api.Memory. +func (m *MemoryInstance) WriteString(_ context.Context, offset uint32, val string) bool { + if !m.hasSize(offset, uint32(len(val))) { + return false + } + copy(m.Buffer[offset:], val) + return true +} + // MemoryPagesToBytesNum converts the given pages into the number of bytes contained in these pages. func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) { return uint64(pages) << MemoryPageSizeInBits diff --git a/internal/wasm/memory_test.go b/internal/wasm/memory_test.go index c40bff78..584a0c6a 100644 --- a/internal/wasm/memory_test.go +++ b/internal/wasm/memory_test.go @@ -3,8 +3,10 @@ package wasm import ( "context" "math" + "strings" "testing" + "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/internal/testing/require" ) @@ -763,3 +765,49 @@ func TestMemoryInstance_Write(t *testing.T) { require.False(t, ok) } } + +func TestMemoryInstance_WriteString(t *testing.T) { + for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! + var mem = &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1} + + s := "bear" + require.True(t, mem.WriteString(ctx, 4, s)) + require.Equal(t, []byte{0, 0, 0, 0, 'b', 'e', 'a', 'r'}, mem.Buffer) + + ok := mem.WriteString(ctx, 5, s) + require.False(t, ok) + + ok = mem.WriteString(ctx, 9, s) + require.False(t, ok) + } +} + +func BenchmarkWriteString(b *testing.B) { + tests := []string{ + "", + "bear", + "hello world", + strings.Repeat("hello ", 10), + } + // nolint intentionally testing interface access + var mem api.Memory + mem = &MemoryInstance{Buffer: make([]byte, 1000), Min: 1} + for _, tt := range tests { + b.Run("", func(b *testing.B) { + b.Run("Write", func(b *testing.B) { + for i := 0; i < b.N; i++ { + if !mem.Write(testCtx, 0, []byte(tt)) { + b.Fail() + } + } + }) + b.Run("WriteString", func(b *testing.B) { + for i := 0; i < b.N; i++ { + if !mem.WriteString(testCtx, 0, tt) { + b.Fail() + } + } + }) + }) + } +}