-
Notifications
You must be signed in to change notification settings - Fork 473
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
On-demand module compile #2739
Open
magicxyyz
wants to merge
16
commits into
master
Choose a base branch
from
ondemand-module-compile
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+176
−74
Open
On-demand module compile #2739
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
1bb6665
compile module only when activating or in targets list
magicxyyz 2043058
Merge branch 'master' into ondemand-module-compile
magicxyyz 795651b
Merge branch 'master' into ondemand-module-compile
magicxyyz d8468bf
Merge branch 'master' into ondemand-module-compile
magicxyyz 08f2b2e
Merge branch 'master' into ondemand-module-compile
magicxyyz 81155d8
system_tests: fix checkWasmStoreContent helper
magicxyyz 8914c57
run some tests with single wasm target
magicxyyz 1abc820
system_tests: check for unexpected extra asm in checkWasmStoreContent
magicxyyz a4eccc2
Merge branch 'master' into ondemand-module-compile
magicxyyz 0c4ffe2
Merge branch 'master' into ondemand-module-compile
magicxyyz bf5cc81
Merge branch 'master' into ondemand-module-compile
magicxyyz f792536
Merge branch 'master' into ondemand-module-compile
magicxyyz 452ecb3
Merge branch 'master' into ondemand-module-compile
magicxyyz 233c324
always recompile module when recompiling native targets to validate m…
magicxyyz 94aeade
Merge branch 'master' into ondemand-module-compile
magicxyyz 4c63c91
Merge branch 'master' into ondemand-module-compile
amsanghi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,16 +72,17 @@ func activateProgram( | |
debug bool, | ||
burner burn.Burner, | ||
) (*activationInfo, error) { | ||
info, asmMap, err := activateProgramInternal(db, program, codehash, wasm, page_limit, stylusVersion, arbosVersionForGas, debug, burner.GasLeft()) | ||
targets := db.Database().WasmTargets() | ||
moduleActivationMandatory := true | ||
info, asmMap, err := activateProgramInternal(program, codehash, wasm, page_limit, stylusVersion, arbosVersionForGas, debug, burner.GasLeft(), targets, moduleActivationMandatory) | ||
if err != nil { | ||
return nil, err | ||
} | ||
db.ActivateWasm(info.moduleHash, asmMap) | ||
return info, nil | ||
} | ||
|
||
func activateProgramInternal( | ||
db vm.StateDB, | ||
func activateModule( | ||
addressForLogging common.Address, | ||
codehash common.Hash, | ||
wasm []byte, | ||
|
@@ -90,7 +91,7 @@ func activateProgramInternal( | |
arbosVersionForGas uint64, | ||
debug bool, | ||
gasLeft *uint64, | ||
) (*activationInfo, map[ethdb.WasmTarget][]byte, error) { | ||
) (*activationInfo, []byte, error) { | ||
output := &rustBytes{} | ||
moduleHash := &bytes32{} | ||
stylusData := &C.StylusData{} | ||
|
@@ -108,80 +109,150 @@ func activateProgramInternal( | |
stylusData, | ||
(*u64)(gasLeft), | ||
)) | ||
|
||
module, msg, err := status_mod.toResult(output.intoBytes(), debug) | ||
if err != nil { | ||
if debug { | ||
log.Warn("activation failed", "err", err, "msg", msg, "program", addressForLogging) | ||
} | ||
if errors.Is(err, vm.ErrExecutionReverted) { | ||
return nil, nil, fmt.Errorf("%w: %s", ErrProgramActivation, msg) | ||
} else { | ||
return nil, nil, err | ||
} | ||
} | ||
info := &activationInfo{ | ||
moduleHash: moduleHash.toHash(), | ||
initGas: uint16(stylusData.init_cost), | ||
cachedInitGas: uint16(stylusData.cached_init_cost), | ||
asmEstimate: uint32(stylusData.asm_estimate), | ||
footprint: uint16(stylusData.footprint), | ||
} | ||
return info, module, nil | ||
} | ||
|
||
func compileNative( | ||
wasm []byte, | ||
stylusVersion uint16, | ||
debug bool, | ||
target ethdb.WasmTarget, | ||
) ([]byte, error) { | ||
output := &rustBytes{} | ||
status_asm := C.stylus_compile( | ||
goSlice(wasm), | ||
u16(stylusVersion), | ||
cbool(debug), | ||
goSlice([]byte(target)), | ||
output, | ||
) | ||
asm := output.intoBytes() | ||
if status_asm != 0 { | ||
return nil, fmt.Errorf("%w: %s", ErrProgramActivation, string(asm)) | ||
} | ||
return asm, nil | ||
} | ||
|
||
func activateProgramInternal( | ||
addressForLogging common.Address, | ||
codehash common.Hash, | ||
wasm []byte, | ||
page_limit uint16, | ||
stylusVersion uint16, | ||
arbosVersionForGas uint64, | ||
debug bool, | ||
gasLeft *uint64, | ||
targets []ethdb.WasmTarget, | ||
moduleActivationMandatory bool, | ||
) (*activationInfo, map[ethdb.WasmTarget][]byte, error) { | ||
var wavmFound bool | ||
for _, target := range targets { | ||
if target == rawdb.TargetWavm { | ||
wavmFound = true | ||
break | ||
} | ||
return nil, nil, err | ||
} | ||
hash := moduleHash.toHash() | ||
targets := db.Database().WasmTargets() | ||
type result struct { | ||
target ethdb.WasmTarget | ||
asm []byte | ||
err error | ||
} | ||
asmMap := make(map[ethdb.WasmTarget][]byte, len(targets)) | ||
|
||
// info can be set in separate thread, make sure to wait before reading | ||
var info *activationInfo | ||
var moduleActivationStarted bool | ||
if moduleActivationMandatory { | ||
moduleActivationStarted = true | ||
var err error | ||
var module []byte | ||
info, module, err = activateModule(addressForLogging, codehash, wasm, page_limit, stylusVersion, arbosVersionForGas, debug, gasLeft) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
if wavmFound { | ||
asmMap[rawdb.TargetWavm] = module | ||
} | ||
} | ||
|
||
results := make(chan result, len(targets)) | ||
for _, target := range targets { | ||
target := target | ||
if target == rawdb.TargetWavm { | ||
results <- result{target, module, nil} | ||
if moduleActivationStarted { | ||
// skip if already started or activated because of moduleActivationMandatory | ||
results <- result{target, nil, nil} | ||
continue | ||
} | ||
go func() { | ||
var err error | ||
var module []byte | ||
info, module, err = activateModule(addressForLogging, codehash, wasm, page_limit, stylusVersion, arbosVersionForGas, debug, gasLeft) | ||
results <- result{target, module, err} | ||
}() | ||
moduleActivationStarted = true | ||
} else { | ||
go func() { | ||
output := &rustBytes{} | ||
status_asm := C.stylus_compile( | ||
goSlice(wasm), | ||
u16(stylusVersion), | ||
cbool(debug), | ||
goSlice([]byte(target)), | ||
output, | ||
) | ||
asm := output.intoBytes() | ||
if status_asm != 0 { | ||
results <- result{target, nil, fmt.Errorf("%w: %s", ErrProgramActivation, string(asm))} | ||
return | ||
} | ||
results <- result{target, asm, nil} | ||
asm, err := compileNative(wasm, stylusVersion, debug, target) | ||
results <- result{target, asm, err} | ||
}() | ||
} | ||
} | ||
asmMap := make(map[ethdb.WasmTarget][]byte, len(targets)) | ||
var err error | ||
for range targets { | ||
res := <-results | ||
if res.err != nil { | ||
err = errors.Join(res.err, err) | ||
if res.asm == nil { | ||
continue | ||
} else if res.err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when will res.err != nil but asm == nil? |
||
err = errors.Join(res.err, fmt.Errorf("%s:%w", res.target, err)) | ||
} else { | ||
asmMap[res.target] = res.asm | ||
} | ||
} | ||
if err != nil { | ||
log.Error( | ||
"Compilation failed for one or more targets despite activation succeeding", | ||
"address", addressForLogging, | ||
"codeHash", codeHash, | ||
"moduleHash", hash, | ||
"targets", targets, | ||
"err", err, | ||
) | ||
if err != nil && moduleActivationMandatory { | ||
if info != nil { | ||
log.Error( | ||
"Compilation failed for one or more targets despite activation succeeding", | ||
"address", addressForLogging, | ||
"codehash", codehash, | ||
"moduleHash", info.moduleHash, | ||
"targets", targets, | ||
"err", err, | ||
) | ||
} else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how can info be nil if activationMandatory in this stage? |
||
log.Error( | ||
"Compilation failed for one or more targets despite activation succeeding", | ||
"address", addressForLogging, | ||
"codehash", codehash, | ||
"targets", targets, | ||
"err", err, | ||
) | ||
} | ||
panic(fmt.Sprintf("Compilation of %v failed for one or more targets despite activation succeeding: %v", addressForLogging, err)) | ||
} | ||
|
||
info := &activationInfo{ | ||
moduleHash: hash, | ||
initGas: uint16(stylusData.init_cost), | ||
cachedInitGas: uint16(stylusData.cached_init_cost), | ||
asmEstimate: uint32(stylusData.asm_estimate), | ||
footprint: uint16(stylusData.footprint), | ||
} | ||
return info, asmMap, err | ||
} | ||
|
||
func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codeHash common.Hash, pagelimit uint16, time uint64, debugMode bool, program Program) ([]byte, error) { | ||
func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codehash common.Hash, pagelimit uint16, time uint64, debugMode bool, program Program) ([]byte, error) { | ||
localTarget := rawdb.LocalTarget() | ||
localAsm, err := statedb.TryGetActivatedAsm(localTarget, moduleHash) | ||
if err == nil && len(localAsm) > 0 { | ||
|
@@ -199,8 +270,10 @@ func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging c | |
zeroArbosVersion := uint64(0) | ||
zeroGas := uint64(0) | ||
|
||
targets := statedb.Database().WasmTargets() | ||
// we know program is activated, so it must be in correct version and not use too much memory | ||
info, asmMap, err := activateProgramInternal(statedb, addressForLogging, codeHash, wasm, pagelimit, program.version, zeroArbosVersion, debugMode, &zeroGas) | ||
moduleActivationMandatory := true // TODO: refactor the parameter, always set to true | ||
info, asmMap, err := activateProgramInternal(addressForLogging, codehash, wasm, pagelimit, program.version, zeroArbosVersion, debugMode, &zeroGas, targets, moduleActivationMandatory) | ||
if err != nil { | ||
log.Error("failed to reactivate program", "address", addressForLogging, "expected moduleHash", moduleHash, "err", err) | ||
return nil, fmt.Errorf("failed to reactivate program address: %v err: %w", addressForLogging, err) | ||
|
@@ -302,10 +375,10 @@ func handleReqImpl(apiId usize, req_type u32, data *rustSlice, costPtr *u64, out | |
|
||
// Caches a program in Rust. We write a record so that we can undo on revert. | ||
// For gas estimation and eth_call, we ignore permanent updates and rely on Rust's LRU. | ||
func cacheProgram(db vm.StateDB, module common.Hash, program Program, addressForLogging common.Address, code []byte, codeHash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) { | ||
func cacheProgram(db vm.StateDB, module common.Hash, program Program, addressForLogging common.Address, code []byte, codehash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) { | ||
if runMode == core.MessageCommitMode { | ||
// address is only used for logging | ||
asm, err := getLocalAsm(db, module, addressForLogging, code, codeHash, params.PageLimit, time, debug, program) | ||
asm, err := getLocalAsm(db, module, addressForLogging, code, codehash, params.PageLimit, time, debug, program) | ||
if err != nil { | ||
panic("unable to recreate wasm") | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like calling activateModule twice.
How about:
always call activateModule in a separate gothread (if moduleActivationMandatory || wasmFound)
Then, in case moduleActivationMandatory, wait for the first result before calling the others in a loop?