Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix entry alignment issue with AssemblyScript maps #461

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ In previous releases, the name "Hypermode" was used for all three._
- Improve `.env` file handling [#458](https://github.com/hypermodeinc/modus/pull/458)
- Update command-line args and env variables [#459](https://github.com/hypermodeinc/modus/pull/459)
- Update Sentry telemetry collection rules [#460](https://github.com/hypermodeinc/modus/pull/460)
- Fix entry alignment issue with AssemblyScript maps [#461](https://github.com/hypermodeinc/modus/pull/461)

## 2024-10-02 - Version 0.12.7

Expand Down
18 changes: 11 additions & 7 deletions runtime/languages/assemblyscript/handler_maps.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func (h *mapHandler) Read(ctx context.Context, wa langsupport.WasmAdapter, offse
entrySize := byteLength / entriesCapacity
keySize := h.keyHandler.TypeInfo().Size()
valueOffset := max(keySize, 4)
valueAlign := h.valueHandler.TypeInfo().Alignment()

if !h.usePseudoMap {
// return a map
Expand All @@ -144,7 +145,8 @@ func (h *mapHandler) Read(ctx context.Context, wa langsupport.WasmAdapter, offse
return nil, err
}

v, err := h.valueHandler.Read(ctx, wa, p+valueOffset)
p += langsupport.AlignOffset(valueOffset, valueAlign)
v, err := h.valueHandler.Read(ctx, wa, p)
if err != nil {
return nil, err
}
Expand All @@ -164,7 +166,8 @@ func (h *mapHandler) Read(ctx context.Context, wa langsupport.WasmAdapter, offse
return nil, err
}

v, err := h.valueHandler.Read(ctx, wa, p+keySize)
p += langsupport.AlignOffset(valueOffset, valueAlign)
v, err := h.valueHandler.Read(ctx, wa, p)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -211,13 +214,14 @@ func (h *mapHandler) Write(ctx context.Context, wa langsupport.WasmAdapter, offs
// note: unlike arrays, an empty map DOES have array buffers
keySize := h.keyHandler.TypeInfo().Size()
valueSize := h.valueHandler.TypeInfo().Size()
valueOffset := max(keySize, 4)
valueAlign := h.valueHandler.TypeInfo().Alignment()
valueOffset := langsupport.AlignOffset(max(keySize, 4), valueAlign)

const taggedNextSize = 4
taggedNextOffset := valueSize + valueOffset
taggedNextOffset := valueOffset + langsupport.AlignOffset(valueSize, valueAlign)

entryAlign := max(keySize, valueSize, 4) - 1
entrySize := (keySize + valueSize + taggedNextSize + entryAlign) & ^entryAlign
entryAlign := max(keySize, valueSize, taggedNextSize)
entrySize := langsupport.AlignOffset(taggedNextOffset+taggedNextSize, entryAlign)
entriesBufferSize := entrySize * entriesCapacity
entriesBufferOffset, c, err := wa.AllocateMemory(ctx, entriesBufferSize)
cln.AddCleaner(c)
Expand Down Expand Up @@ -259,7 +263,7 @@ func (h *mapHandler) Write(ctx context.Context, wa langsupport.WasmAdapter, offs

// write entry value
value := vals[i]
entryValueOffset := entryOffset + valueOffset
entryValueOffset := entryOffset + langsupport.AlignOffset(valueOffset, valueAlign)
c, err := h.valueHandler.Write(ctx, wa, entryValueOffset, value)
cln.AddCleaner(c)
if err != nil {
Expand Down
29 changes: 29 additions & 0 deletions runtime/languages/assemblyscript/testdata/assembly/maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,32 @@ export function testClassContainingMapOutput_string_string(): TestClassWithMap {
c.m.set("c", "3");
return c;
}

export function testMapInput_string_f64(m: Map<string, f64>): void {
assert(m.size == 4, "expected size 4");

let n: f64 = 0.5;
assert(m.has("a"), "expected key 'a' to be present");
assert(m.get("a") == n, `expected value for key 'a' to be ${n}`);

n = f64.EPSILON;
assert(m.has("b"), "expected key 'b' to be present");
assert(m.get("b") == n, `expected value for key 'b' to be ${n}`);

n = f64.MIN_VALUE;
assert(m.has("c"), "expected key 'c' to be present");
assert(m.get("c") == n, `expected value for key 'c' to be ${n}`);

n = f64.MAX_VALUE;
assert(m.has("d"), "expected key 'd' to be present");
assert(m.get("d") == n, `expected value for key 'd' to be ${n}`);
}

export function testMapOutput_string_f64(): Map<string, f64> {
const m = new Map<string, f64>();
m.set("a", 0.5);
m.set("b", f64.EPSILON);
m.set("c", f64.MIN_VALUE);
m.set("d", f64.MAX_VALUE);
return m;
}
Binary file modified runtime/languages/assemblyscript/testdata/build/testdata.wasm
Binary file not shown.
45 changes: 45 additions & 0 deletions runtime/languages/assemblyscript/tests/maps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package assemblyscript_test
import (
"fmt"
"maps"
"math"
"reflect"
"testing"

Expand Down Expand Up @@ -232,3 +233,47 @@ func makeTestMap(size int) map[string]string {
}
return m
}

var epsilon = math.Nextafter(1, 2) - 1

func TestMapInput_string_f64(t *testing.T) {
fnName := "testMapInput_string_f64"
m := map[string]float64{
"a": 0.5,
"b": epsilon,
"c": math.SmallestNonzeroFloat64,
"d": math.MaxFloat64,
}

if _, err := fixture.CallFunction(t, fnName, m); err != nil {
t.Error(err)
}
if m, err := utils.ConvertToMap(m); err != nil {
t.Error(fmt.Errorf("failed conversion to interface map: %w", err))
} else if _, err := fixture.CallFunction(t, fnName, m); err != nil {
t.Error(err)
}
}

func TestMapOutput_string_f64(t *testing.T) {
fnName := "testMapOutput_string_f64"
result, err := fixture.CallFunction(t, fnName)
if err != nil {
t.Fatal(err)
}

expected := map[string]float64{
"a": 0.5,
"b": epsilon,
"c": math.SmallestNonzeroFloat64,
"d": math.MaxFloat64,
}

if result == nil {
t.Error("expected a result")
} else if r, ok := result.(map[string]float64); !ok {
t.Errorf("expected %T, got %T", expected, result)
} else if !maps.Equal(expected, r) {
t.Errorf("expected %v, got %v", expected, r)
}
}
Loading