Skip to content
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
12 changes: 12 additions & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ jobs:
run: |
make test

- name: Cleanup
if: always()
run: |
go clean -testcache
git clean -ffdx

test-macos-arm64:
runs-on: [self-hosted, macOS, ARM64]
steps:
Expand All @@ -35,3 +41,9 @@ jobs:
- name: Test MacOS ARM64
run: |
make test-w2

- name: Cleanup
if: always()
run: |
go clean -testcache
git clean -ffdx
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ test/contracts/**/wasm/target
**/Cargo.lock
vmhost/**/__debug_bin
.gocache/

# Temporary LLM artifacts
docs
1 change: 1 addition & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ ignore:
- "**test/**"
- "**testcommon/**"
- "**testgen/**"
- "vmhost/vmhooks/generate/**"
2 changes: 2 additions & 0 deletions config/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,9 @@
MemoryAtomicWait32 = 1
MemoryAtomicWait64 = 1
MemoryCopy = 1
MemoryCopyPerByte = 1
MemoryFill = 1
MemoryFillPerByte = 1
MemoryGrow = 1
MemoryInit = 1
MemorySize = 1
Expand Down
2 changes: 2 additions & 0 deletions config/gasSchedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,9 @@ func FillGasMapWASMOpcodeValues(value uint64) map[string]uint64 {
gasMap["MemoryAtomicWait32"] = value
gasMap["MemoryAtomicWait64"] = value
gasMap["MemoryCopy"] = value
gasMap["MemoryCopyPerByte"] = value
gasMap["MemoryFill"] = value
gasMap["MemoryFillPerByte"] = value
gasMap["MemoryGrow"] = value
gasMap["MemoryInit"] = value
gasMap["MemorySize"] = value
Expand Down
4 changes: 2 additions & 2 deletions executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ type CompilationOptions struct {
type Executor interface {
check.NilInterfaceChecker

// SetOpcodeCosts sets gas costs globally inside an executor.
SetOpcodeCosts(opcodeCosts *WASMOpcodeCost)
// SetOpcodeConfig sets gas costs globally inside an executor.
SetOpcodeConfig(opcodeVersion OpcodeVersion, wasmOps *WASMOpcodeCost) error

// FunctionNames return the low-level function names provided to contracts.
FunctionNames() vmcommon.FunctionNames
Expand Down
7 changes: 7 additions & 0 deletions executor/executorError.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ var ErrFunctionNonvoidSignature = fmt.Errorf("%w (nonvoid signature)", ErrInvali
// ErrFuncNotFound signals that the the function does not exist
var ErrFuncNotFound = fmt.Errorf("%w (not found)", ErrInvalidFunction)

// ErrNilOpcodeCosts signals that an executor was configured without a WASM opcode cost table.
//
// Rejected rather than defaulted, because an absent table is indistinguishable from one in
// which every opcode costs zero, and that is a contract running for free. OpcodeVersion needs
// no such check: its zero value is OpcodeVersionV1, a real choice.
var ErrNilOpcodeCosts = errors.New("nil WASM opcode costs")

// ErrMemoryBadBounds signals that a certain variable is out of bounds
var ErrMemoryBadBounds = errors.New("bad bounds")

Expand Down
1 change: 1 addition & 0 deletions executor/executorFactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "github.com/multiversx/mx-chain-core-go/core/check"
// ExecutorFactoryArgs define the Executor configurations that come from the VM, especially the hooks and the gas costs.
type ExecutorFactoryArgs struct {
VMHooks VMHooks
OpcodeVersion OpcodeVersion
Comment thread
andrei-marinica marked this conversation as resolved.
Resolved
OpcodeCosts *WASMOpcodeCost
RkyvSerializationEnabled bool
WasmerSIGSEGVPassthrough bool
Expand Down
38 changes: 38 additions & 0 deletions executor/executorOpcodeVersion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package executor

// OpcodeVersion specifies which set of opcodes should be used by the VM.
//
// [OpcodeVersionV1] is the legacy opcode set that does not include support
// for bulk memory operations. [OpcodeVersionV2] extends it by adding bulk
// memory instructions, such as MemoryCopy and MemoryFill, while dropping
// these categories from the whitelist, since no contract built with the
// current SDK toolchain can emit any of them:
//
// - legacy exception handling: Catch, CatchAll, Delegate, Rethrow, Throw, Try
// - reference types: RefFunc, RefIsNull, RefNull, TypedSelect
type OpcodeVersion uint32

const (
// OpcodeVersionV1 is the legacy opcode set without bulk memory support.
//
// Use this for modules or environments that were compiled or designed
// before bulk memory operations were introduced, or when compatibility
// with older tooling is required.
OpcodeVersionV1 OpcodeVersion = iota

// OpcodeVersionV2 is the opcode set with bulk memory support.
//
// Use this for modules that rely on bulk memory operations like
// MemoryCopy and MemoryFill, or when targeting newer runtimes that
// support these instructions.
//
// Relative to OpcodeVersionV1, OpcodeVersionV2 also drops these
// categories from the whitelist, since no contract built with today's
// tooling can produce them:
//
// - legacy exception handling: Catch, CatchAll, Delegate, Rethrow, Throw, Try
// - reference types: RefFunc, RefIsNull, RefNull, TypedSelect
//
// Note: OpcodeVersionV2 does not add support for memory.init and data.drop.
OpcodeVersionV2
)
2 changes: 2 additions & 0 deletions executor/gasCostWASM.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions executor/wrapper/wrapperExecutor.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ type WrapperExecutor struct {
WrappedInstances map[string][]executor.Instance
}

// SetOpcodeCosts wraps the call to the underlying executor.
func (wexec *WrapperExecutor) SetOpcodeCosts(opcodeCosts *executor.WASMOpcodeCost) {
wexec.wrappedExecutor.SetOpcodeCosts(opcodeCosts)
// SetOpcodeConfig wraps the call to the underlying executor.
func (wexec *WrapperExecutor) SetOpcodeConfig(opcodeVersion executor.OpcodeVersion, wasmOps *executor.WASMOpcodeCost) error {
return wexec.wrappedExecutor.SetOpcodeConfig(opcodeVersion, wasmOps)
}

// FunctionNames wraps the call to the underlying executor.
Expand Down
22 changes: 22 additions & 0 deletions executor/wrapper/wrapperExecutorFactory.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package executorwrapper

import (
"errors"

"github.com/multiversx/mx-chain-core-go/core/check"
"github.com/multiversx/mx-chain-vm-go/executor"
)

// ErrNilWrappedExecutorFactory signals that the wrapper was built around no factory at all.
var ErrNilWrappedExecutorFactory = errors.New("nil wrapped executor factory")

// ErrNilExecutorLogger signals that the wrapper was built without a logger. SimpleWrappedExecutorFactory
// supplies NoLogger for callers that want no logging; nil is not that.
var ErrNilExecutorLogger = errors.New("nil executor logger")

// WrapperExecutorFactory is the factory for the WrapperExecutor.
type WrapperExecutorFactory struct {
logger ExecutorLogger
Expand All @@ -29,12 +39,24 @@ func SimpleWrappedExecutorFactory(wrappedFactory executor.ExecutorAbstractFactor
}

// CreateExecutor creates a new Executor instance.
//
// The dependencies are checked here rather than in NewWrappedExecutorFactory, which has no
// error to return: the factory struct is inert, and this is the first point at which a
// missing one would be used.
func (factory *WrapperExecutorFactory) CreateExecutor(args executor.ExecutorFactoryArgs) (executor.Executor, error) {
if check.IfNil(factory.wrappedFactory) {
return nil, ErrNilWrappedExecutorFactory
}
if factory.logger == nil {
return nil, ErrNilExecutorLogger
}

wrappedExecutor, err := factory.wrappedFactory.CreateExecutor(executor.ExecutorFactoryArgs{
VMHooks: &WrapperVMHooks{
logger: factory.logger,
wrappedVMHooks: args.VMHooks,
},
OpcodeVersion: args.OpcodeVersion,
OpcodeCosts: args.OpcodeCosts,
RkyvSerializationEnabled: args.RkyvSerializationEnabled,
WasmerSIGSEGVPassthrough: args.WasmerSIGSEGVPassthrough,
Expand Down
26 changes: 26 additions & 0 deletions executor/wrapper/wrapperExecutorFactory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package executorwrapper

import (
"testing"

"github.com/multiversx/mx-chain-vm-go/executor"
"github.com/stretchr/testify/require"
)

func TestWrapperExecutorFactory_CreateExecutor_MissingDependencies(t *testing.T) {
t.Run("nil wrapped factory", func(t *testing.T) {
factory := SimpleWrappedExecutorFactory(nil)

exec, err := factory.CreateExecutor(executor.ExecutorFactoryArgs{})
require.ErrorIs(t, err, ErrNilWrappedExecutorFactory)
require.Nil(t, exec)
})

t.Run("nil logger", func(t *testing.T) {
factory := NewWrappedExecutorFactory(nil, SimpleWrappedExecutorFactory(nil))

exec, err := factory.CreateExecutor(executor.ExecutorFactoryArgs{})
require.ErrorIs(t, err, ErrNilExecutorLogger)
require.Nil(t, exec)
})
}
5 changes: 3 additions & 2 deletions mock/context/executorMock.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ func NewExecutorMock(world *worldmock.MockWorld) *ExecutorMock {
}
}

// SetOpcodeCosts should set gas costs, but it does nothing in the case of this mock.
func (executorMock *ExecutorMock) SetOpcodeCosts(_ *executor.WASMOpcodeCost) {
// SetOpcodeConfig should set gas costs, but it does nothing in the case of this mock.
func (executorMock *ExecutorMock) SetOpcodeConfig(_ executor.OpcodeVersion, _ *executor.WASMOpcodeCost) error {
return nil
}

// FunctionNames mocked method
Expand Down
20 changes: 12 additions & 8 deletions scenario/gasSchedules/gasScheduleEmbedGenerated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions scenario/gasSchedules/gasScheduleV3.toml
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,9 @@
MaxMemoryGrow = 8
MaxMemoryGrowDelta = 10
MemoryCopy = 5
MemoryCopyPerByte = 5
MemoryFill = 5
MemoryFillPerByte = 5
MemoryGrow = 5
MemoryInit = 5
MemorySize = 5
Expand Down
2 changes: 2 additions & 0 deletions scenario/gasSchedules/gasScheduleV4.toml
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,9 @@
MaxMemoryGrow = 100
MaxMemoryGrowDelta = 1
MemoryCopy = 5
MemoryCopyPerByte = 5
MemoryFill = 5
MemoryFillPerByte = 5
MemoryGrow = 5
MemoryInit = 5
MemorySize = 5
Expand Down
14 changes: 14 additions & 0 deletions test/contracts/bulk-memory/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# bulk-memory

Contract used to measure the gas cost of the bulk memory opcodes, `memory.copy` and `memory.fill`.

Every endpoint takes the number of bytes to copy/fill as argument 0. Since the rest of the
code path is identical no matter the argument, the gas difference between two calls is exactly
`(bytes1 - bytes2) * <opcode>PerByte`, which is what the tests in
`vmhost/hosttest/bulkMemoryGas_test.go` assert.

Built directly from WAT, without `wasm-opt`, so that the opcodes stay exactly as written:

```
./build.sh
```
9 changes: 9 additions & 0 deletions test/contracts/bulk-memory/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

# Built directly from WAT, no wasm-opt, so that the opcodes stay exactly as written.

set -e

cd "$(dirname "$0")"
mkdir -p output
wat2wasm bulk-memory.wat -o output/bulk-memory.wasm
Loading
Loading