Compare commits
No commits in common. "24937c7cf5f6d2d6fe8403e6de0b4efa474f62d7" and "3d8bfe88638034eb32611a28ba4984dc94486745" have entirely different histories.
24937c7cf5
...
3d8bfe8863
@ -72,14 +72,6 @@ For module-level APIs, the default workflow is:
|
|||||||
1. declare globals in Lyng using `extern fun` / `extern val` / `extern var`;
|
1. declare globals in Lyng using `extern fun` / `extern val` / `extern var`;
|
||||||
2. bind Kotlin implementation via `ModuleScope.globalBinder()`.
|
2. bind Kotlin implementation via `ModuleScope.globalBinder()`.
|
||||||
|
|
||||||
This is also the recommended way to expose a Kotlin-backed value that should behave like a true
|
|
||||||
Lyng global variable/property. If you need `x` to read/write through Kotlin on every access, use
|
|
||||||
`extern var` / `extern val` plus `bindGlobalVar(...)`.
|
|
||||||
|
|
||||||
Do not use `addConst(...)` for this case: `addConst(...)` installs a value, not a Kotlin-backed
|
|
||||||
property accessor. It is appropriate for fixed values and objects, but not for a global that should
|
|
||||||
delegate reads/writes back into Kotlin state.
|
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
import net.sergeych.lyng.bridge.*
|
import net.sergeych.lyng.bridge.*
|
||||||
import net.sergeych.lyng.obj.ObjInt
|
import net.sergeych.lyng.obj.ObjInt
|
||||||
@ -125,12 +117,6 @@ assertEquals("changed", globalProp)
|
|||||||
assertEquals("1.0.0", globalVersion)
|
assertEquals("1.0.0", globalVersion)
|
||||||
```
|
```
|
||||||
|
|
||||||
Minimal rule of thumb:
|
|
||||||
|
|
||||||
- use `bindGlobalFun(...)` for global functions
|
|
||||||
- use `bindGlobalVar(...)` for Kotlin-backed global variables/properties
|
|
||||||
- use `addConst(...)` only for fixed values/objects that do not need getter/setter behavior
|
|
||||||
|
|
||||||
For custom argument handling and full runtime access:
|
For custom argument handling and full runtime access:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
@ -476,21 +462,14 @@ im.addPackage("my.tools") { module: ModuleScope ->
|
|||||||
module.eval(
|
module.eval(
|
||||||
"""
|
"""
|
||||||
extern val version: String
|
extern val version: String
|
||||||
extern var status: String
|
|
||||||
extern fun triple(x: Int): Int
|
extern fun triple(x: Int): Int
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
val binder = module.globalBinder()
|
val binder = module.globalBinder()
|
||||||
var status = "ready"
|
|
||||||
binder.bindGlobalVar(
|
binder.bindGlobalVar(
|
||||||
name = "version",
|
name = "version",
|
||||||
get = { "1.0" }
|
get = { "1.0" }
|
||||||
)
|
)
|
||||||
binder.bindGlobalVar(
|
|
||||||
name = "status",
|
|
||||||
get = { status },
|
|
||||||
set = { status = it }
|
|
||||||
)
|
|
||||||
binder.bindGlobalFun1<Int>("triple") { x ->
|
binder.bindGlobalFun1<Int>("triple") { x ->
|
||||||
ObjInt.of((x * 3).toLong())
|
ObjInt.of((x * 3).toLong())
|
||||||
}
|
}
|
||||||
@ -500,7 +479,6 @@ im.addPackage("my.tools") { module: ModuleScope ->
|
|||||||
scope.eval("""
|
scope.eval("""
|
||||||
import my.tools.*
|
import my.tools.*
|
||||||
val v = triple(14)
|
val v = triple(14)
|
||||||
status = "busy"
|
|
||||||
""")
|
""")
|
||||||
val v = scope.eval("v").toKotlin(scope) // -> 42
|
val v = scope.eval("v").toKotlin(scope) // -> 42
|
||||||
```
|
```
|
||||||
|
|||||||
@ -46,28 +46,6 @@ class Script(
|
|||||||
// private val catchReturn: Boolean = false,
|
// private val catchReturn: Boolean = false,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
fun statements(): List<Statement> = statements
|
fun statements(): List<Statement> = statements
|
||||||
|
|
||||||
/**
|
|
||||||
* Explicitly apply this script's import/module bindings to [scope] without executing the script.
|
|
||||||
* This is intended for embedding scenarios where the host owns scope lifecycle and wants
|
|
||||||
* script-specific imports to be a separate, opt-in preparation step.
|
|
||||||
*/
|
|
||||||
suspend fun importInto(scope: Scope, seedScope: Scope = scope): Scope {
|
|
||||||
prepareScriptScope(scope, seedScope)
|
|
||||||
return scope
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a fresh raw module scope and apply this script's import/module bindings to it.
|
|
||||||
* If [seedScope] is provided, its import provider is reused and seed-bound imports resolve from it.
|
|
||||||
*/
|
|
||||||
suspend fun instantiateModule(seedScope: Scope? = null, pos: Pos = this.pos): ModuleScope {
|
|
||||||
val provider = seedScope?.currentImportProvider ?: defaultImportManager
|
|
||||||
val module = provider.newModuleAt(pos)
|
|
||||||
prepareScriptScope(module, seedScope ?: module)
|
|
||||||
return module
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
scope.pos = pos
|
scope.pos = pos
|
||||||
val execScope = resolveModuleScope(scope) ?: scope
|
val execScope = resolveModuleScope(scope) ?: scope
|
||||||
@ -97,19 +75,6 @@ class Script(
|
|||||||
return ObjVoid
|
return ObjVoid
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun prepareScriptScope(scope: Scope, seedScope: Scope) {
|
|
||||||
if (importBindings.isNotEmpty() || importedModules.isNotEmpty()) {
|
|
||||||
seedImportBindings(scope, seedScope)
|
|
||||||
}
|
|
||||||
if (moduleSlotPlan.isNotEmpty()) {
|
|
||||||
scope.applySlotPlan(moduleSlotPlan)
|
|
||||||
for (name in moduleSlotPlan.keys) {
|
|
||||||
val record = scope.objects[name] ?: scope.localBindings[name] ?: continue
|
|
||||||
scope.updateSlotFor(name, record)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun seedModuleSlots(scope: Scope, seedScope: Scope) {
|
private suspend fun seedModuleSlots(scope: Scope, seedScope: Scope) {
|
||||||
if (importBindings.isEmpty() && importedModules.isEmpty()) return
|
if (importBindings.isEmpty() && importedModules.isEmpty()) return
|
||||||
seedImportBindings(scope, seedScope)
|
seedImportBindings(scope, seedScope)
|
||||||
|
|||||||
@ -36,7 +36,7 @@ internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
|||||||
} else {
|
} else {
|
||||||
record.value
|
record.value
|
||||||
}
|
}
|
||||||
if (value is net.sergeych.lyng.FrameSlotRef && value.refersTo(frame.frame, i)) {
|
if (value is net.sergeych.lyng.FrameSlotRef && value.refersTo(frame.frame, base + i)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
frame.setObjUnchecked(base + i, value)
|
frame.setObjUnchecked(base + i, value)
|
||||||
|
|||||||
@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import net.sergeych.lyng.Compiler
|
|
||||||
import net.sergeych.lyng.Script
|
|
||||||
import net.sergeych.lyng.Source
|
|
||||||
import net.sergeych.lyng.obj.toInt
|
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertNotNull
|
|
||||||
import kotlin.test.assertNull
|
|
||||||
|
|
||||||
class ScriptImportPreparationTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun scriptImportIntoExplicitlyPreparesExistingScope() = runTest {
|
|
||||||
val manager = ImportManager()
|
|
||||||
manager.addTextPackages(
|
|
||||||
"""
|
|
||||||
package foo
|
|
||||||
|
|
||||||
val answer = 42
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val script = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<prepare-scope>",
|
|
||||||
"""
|
|
||||||
import foo
|
|
||||||
answer
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
manager
|
|
||||||
)
|
|
||||||
val scope = manager.newModule()
|
|
||||||
|
|
||||||
assertNull(scope["answer"])
|
|
||||||
|
|
||||||
script.importInto(scope)
|
|
||||||
|
|
||||||
val record = assertNotNull(scope["answer"])
|
|
||||||
assertEquals(42, scope.resolve(record, "answer").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun scriptInstantiateModuleUsesSeedScopeImportProvider() = runTest {
|
|
||||||
val manager = ImportManager()
|
|
||||||
manager.addTextPackages(
|
|
||||||
"""
|
|
||||||
package foo
|
|
||||||
|
|
||||||
val answer = 42
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val script = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<instantiate-module>",
|
|
||||||
"""
|
|
||||||
import foo
|
|
||||||
answer
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
manager
|
|
||||||
)
|
|
||||||
val seedScope = manager.newModule()
|
|
||||||
|
|
||||||
val module = script.instantiateModule(seedScope)
|
|
||||||
|
|
||||||
val record = assertNotNull(module["answer"])
|
|
||||||
assertEquals(42, module.resolve(record, "answer").toInt())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import net.sergeych.lyng.FrameSlotRef
|
|
||||||
import net.sergeych.lyng.Pos
|
|
||||||
import net.sergeych.lyng.Scope
|
|
||||||
import net.sergeych.lyng.bytecode.BytecodeConst
|
|
||||||
import net.sergeych.lyng.bytecode.CmdFrame
|
|
||||||
import net.sergeych.lyng.bytecode.CmdFunction
|
|
||||||
import net.sergeych.lyng.bytecode.CmdVm
|
|
||||||
import net.sergeych.lyng.bytecode.seedFrameLocalsFromScope
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertNull
|
|
||||||
|
|
||||||
class SeedLocalsRegressionTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun seedFrameLocalsSkipsSelfReferentialFrameSlotRef() = runTest {
|
|
||||||
val fn = CmdFunction(
|
|
||||||
name = "seed-self-ref",
|
|
||||||
localCount = 1,
|
|
||||||
addrCount = 0,
|
|
||||||
returnLabels = emptySet(),
|
|
||||||
scopeSlotCount = 1,
|
|
||||||
scopeSlotIndices = intArrayOf(0),
|
|
||||||
scopeSlotNames = arrayOf(null),
|
|
||||||
scopeSlotIsModule = booleanArrayOf(false),
|
|
||||||
localSlotNames = arrayOf("x"),
|
|
||||||
localSlotMutables = booleanArrayOf(true),
|
|
||||||
localSlotDelegated = booleanArrayOf(false),
|
|
||||||
localSlotCaptures = booleanArrayOf(false),
|
|
||||||
constants = listOf(BytecodeConst.IntVal(0)),
|
|
||||||
cmds = emptyArray(),
|
|
||||||
posByIp = emptyArray()
|
|
||||||
)
|
|
||||||
val scope = Scope(null, pos = Pos.builtIn)
|
|
||||||
val record = scope.addConst("x", net.sergeych.lyng.obj.ObjInt.of(1))
|
|
||||||
val frame = CmdFrame(CmdVm(), fn, scope, emptyList())
|
|
||||||
record.value = FrameSlotRef(frame.frame, 0)
|
|
||||||
scope.updateSlotFor("x", record)
|
|
||||||
|
|
||||||
seedFrameLocalsFromScope(frame, scope)
|
|
||||||
|
|
||||||
assertNull(frame.frame.getRawObj(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
# :lynglib Compiler and VM Review (2026-03-26)
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
- Reviewed `:lynglib` compiler and bytecode VM paths, focusing on compile/execution correctness.
|
|
||||||
- Read core files around `Compiler`, `Script`, `BytecodeCompiler`, `CmdRuntime`, `Scope`, `ModuleScope`, and capture/slot plumbing.
|
|
||||||
- Ran `./gradlew :lynglib:jvmTest` on 2026-03-26: PASS.
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
### 1. High: local seeding uses the wrong slot index when checking for self-referential `FrameSlotRef`
|
|
||||||
- Status: fixed in worktree on 2026-03-26; covered by `SeedLocalsRegressionTest`.
|
|
||||||
- File: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/SeedLocals.kt:39`
|
|
||||||
- The self-reference guard compares `FrameSlotRef` against `base + i`, but `FrameSlotRef` stores the underlying `BytecodeFrame` local slot index, not the VM absolute slot id.
|
|
||||||
- The same slot is then written using `setObjUnchecked(base + i, value)` at `SeedLocals.kt:42`, which means a self-reference is not filtered out before being reinserted.
|
|
||||||
- Impact: this can seed a local with a reference to itself, creating recursive `FrameSlotRef` chains. Reads then recurse through `BytecodeFrame.getObj()` / `FrameSlotRef.read()` instead of resolving to a value, which is a realistic path to stack overflow or non-terminating execution in bytecode-only helper execution (`executeBytecodeWithSeed`).
|
|
||||||
- Why this looks real: the equivalent check in `CmdFrame.applyCaptureRecords()` uses the local index directly (`CmdRuntime.kt:3867`), and `Script.seedModuleLocals()` also compares with the local index, not `scopeSlotCount + i` (`Script.kt:109`).
|
|
||||||
- Suggested fix: compare with `i`, not `base + i`, and add a regression test around `executeBytecodeWithSeed` seeding a scope that already contains a `FrameSlotRef` to the same local.
|
|
||||||
|
|
||||||
### 2. Design note: script-specific import/module preparation should stay explicit, not hidden in `execute(scope)`
|
|
||||||
- Status: resolved by API addition, without changing `Script.execute(scope)` semantics.
|
|
||||||
- File: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt:51-57`
|
|
||||||
- `shouldSeedModule` is only true when `execScope` is a `ModuleScope` or `thisObj === ObjVoid`.
|
|
||||||
- If a compiled script is executed against a non-module scope with a real receiver object, `seedModuleSlots()` is skipped entirely, so script imports and explicit module bindings are never installed into `moduleTarget`.
|
|
||||||
- Original concern: top-level bytecode that expects imported names or module globals can see different behavior depending on whether the host prepared the target scope explicitly.
|
|
||||||
- Resolution taken: do **not** change `Script.execute(scope)`, because in this codebase it is expected to run on exactly the provided scope and many embedding flows already rely on explicit scope setup via `Script.newScope()`, `Scope.eval(...)`, `addFn(...)`, `addConst(...)`, and direct import-manager configuration.
|
|
||||||
- New explicit APIs added instead:
|
|
||||||
- `Script.importInto(scope, seedScope = scope)`
|
|
||||||
- `Script.instantiateModule(seedScope = null, pos = script.pos)`
|
|
||||||
- This keeps existing execution behavior stable while giving hosts an opt-in way to apply a script's import/module bindings when they actually want that preparation step.
|
|
||||||
- Coverage added: `ScriptImportPreparationTest`.
|
|
||||||
|
|
||||||
### 3. Medium: missing module captures are silently converted into fresh `Unset` slots
|
|
||||||
- File: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt:3950-3970`
|
|
||||||
- In `CmdFrame.buildCaptureRecords()`, the module-capture path first tries several lookups. If the requested `slotId` is missing but the capture has a `name`, it calls `target.applySlotPlan(mapOf(name to slotId))` and immediately returns `target.getSlotRecord(slotId)`.
|
|
||||||
- That record is a newly created placeholder from `Scope.applySlotPlan()` (`Scope.kt:460-471`) and defaults to `ObjUnset`.
|
|
||||||
- Impact: a compiler/runtime disagreement in capture resolution is masked as a normal capture of `Unset`, so the failure moves far away from closure creation and becomes data corruption or an unrelated later exception. This will be difficult to debug when it happens.
|
|
||||||
- Suggested fix: if the named module capture cannot be resolved to an existing record, fail immediately with a Lyng error instead of manufacturing a placeholder slot. Add a regression test around missing imported/module captures.
|
|
||||||
|
|
||||||
### 4. Medium: subject-less `when { ... }` still crashes through a raw Kotlin `TODO`
|
|
||||||
- File: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt:6447-6449`
|
|
||||||
- The unsupported branch uses `TODO("when without object is not yet implemented")`.
|
|
||||||
- Current docs explicitly say subject-less `when` is not implemented, so the language limitation itself is documented. The problem is the failure mode: the compiler throws a raw Kotlin `NotImplementedError` instead of a normal `ScriptError` or a feature diagnostic.
|
|
||||||
- Impact: IDE/embedding callers get an implementation exception rather than a source-positioned language error, which is especially bad across non-JVM targets and for editor tooling.
|
|
||||||
- Suggested fix: replace the `TODO(...)` with a `ScriptError` at the current source position, or gate it earlier in parsing with a normal diagnostic.
|
|
||||||
|
|
||||||
## Risks Worth Checking Next
|
|
||||||
|
|
||||||
### 5. Medium risk: module frame growth only retargets records stored in `objects`/`localBindings`
|
|
||||||
- File: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/ModuleScope.kt:39-67`
|
|
||||||
- When `ensureModuleFrame()` replaces the old `BytecodeFrame` with a larger one, it retargets `FrameSlotRef`s only in `objects` and `localBindings`.
|
|
||||||
- Existing closures/capture records that already hold `FrameSlotRef(oldFrame, slot)` are not retargeted here.
|
|
||||||
- Impact: if a module scope survives across recompilation/re-execution with a larger local frame, previously exported closures can keep reading stale values from the old frame instance.
|
|
||||||
- Confidence is lower because this depends on module reuse across differing compiled shapes, but the reference-retargeting logic is clearly incomplete for that scenario.
|
|
||||||
- Suggested check: add a regression covering module re-execution with increased local count and an exported closure captured before the resize.
|
|
||||||
|
|
||||||
## Test Status
|
|
||||||
- `./gradlew :lynglib:jvmTest` passed during this review.
|
|
||||||
- `./gradlew :lynglib:jvmTest --tests ScriptImportPreparationTest --tests SeedLocalsRegressionTest` passed after the fixes/API additions.
|
|
||||||
- Finding 1 is covered directly; finding 2 is covered by explicit preparation API tests.
|
|
||||||
|
|
||||||
## Suggested Fix Order
|
|
||||||
1. Fix finding 1 first: it is a concrete slot-index bug with likely recursive failure modes. Done.
|
|
||||||
2. Keep `Script.execute(scope)` semantics stable and use explicit preparation APIs where script-owned import/module setup is needed. Done.
|
|
||||||
3. Tighten finding 3 next: fail fast on capture mismatches.
|
|
||||||
4. Replace the raw `TODO` in finding 4 so unsupported syntax produces normal diagnostics.
|
|
||||||
5. Decide whether finding 5 matters for current module-reload workflows; add a regression before changing behavior.
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
WASM использует стековую модель памяти. Lyng стеки не использует. В чем разница?
|
|
||||||
|
|
||||||
Стек это очень древняя структура, была придумана в 1955 (магазинная память Бауэра и Самельсона), и приняла современную форму в 1960, благодаря Барбаре Лисокв, вполседствии лауреата премии Тьюринга, которого, в свою очередь, за выдающийся вклад в развитие IT кастрировали, формально, за гомосексуализм, но в британии, где на трех джентльменов приходилось тогда, и приходится и теперь, четыре гомосексуала, все понимают, что это был просто предлог.
|
|
||||||
|
|
||||||
Стек был прекрасен в 60е годы, когда IBM планировала продавать по компьютеру в год в лучшем случае, а за идеи о микропроцессорах или ядрах можно было устроиться только пациентом психушки. Стек это заранее жестко выделенная область памяти, которая обычно вообще не используется, и по ней в одну сторону растет используемая область, которая хранит локальные переменные, адреса возврата и иногда состояние процессора.
|
|
||||||
|
|
||||||
Она крайне плохо подходит для многозадачности. Представь себе что у тебя есть wasm процесс, со своим стеком, и ему надо переключиться, остановив текущий поток исполнения (например, он ждет ответа от сети). Тогда тебе придется создать _новый стек_ — пока еще пустой, переключиться на него и исполнять там другую задачу. Сколько потоков - столько и стеков. И все они по большей части не используются — там просто запас памяти "на вырост", если исполнение потребует. А оценить заранее адекватно почти невозможно, дают с запасом.
|
|
||||||
|
|
||||||
Далее, васм использует фиксированную память выделенную на процесс. Ее тоже приходится брать "с запасом", так как если по ходу не хватит, процесс вылетит. И она тоже в изрядной части пустая. Ну и назасладочку, васм был задуман однозадачным, и все попытки туда хотя бы треды засунуть, получаются очень кривыми.
|
|
||||||
|
|
||||||
Линг использует фреймы и сопрограммы. Линг вообще треды не использует. Основная команда исполнения программы на линге - сопрограмма (coroutine), ей не нужен стейк, она им не пользуется. Вместо этого каждый вызов в линге, грубо говоря, это вызов сопрограммы. Для него формируется фрейм (не на стеке, а в динамической памяти!), которого гарантировано достаточно для исполнения собственно кода. Он заменяет собой "стековый фрейм", но его размер известен заранее, и его можно распределять и освобождать проще, как обычную динамическую память.
|
|
||||||
|
|
||||||
Далее, линг использует ту же динамическую память, что и его родительская платформа. Если он работает на Java, то использует память со сборкой мусора JVM, на Javascript - память машины JS, в нативных машинах - специальнй менеджер памяти со сборкой мусора, от Kotlin Native (довольно хороший и очень быстро развивающийся). В результате, запустить одновременно десять, или сто тысяч программ на лигне, которые будут исполняться конкурентно, вполне реально. Просто попытка запустить 10к васм-машин на v8 или другом движке скорее всего убьет систему, да и 10 тысяч тредов редко какой сервер приложению даст. Огромный перерасход ресурсов и тормоза.
|
|
||||||
|
|
||||||
Далее. Васм СТЕКОВАЯ машина со стековыми командами, он без стека вообще не может. Посмотрим как он считает примитивное выражение:
|
|
||||||
|
|
||||||
```wasm
|
|
||||||
(module
|
|
||||||
;; Экспортируем функцию, чтобы её можно было вызвать из JS
|
|
||||||
(func (export "calc") (param $a i32) (param $b i32) (param $c i32) (param $d i32) (result i32)
|
|
||||||
;; --- вычисление (a + b) / c * d ---
|
|
||||||
local.get $a ;; помещаем a на стек
|
|
||||||
local.get $b ;; помещаем b на стек
|
|
||||||
i32.add ;; складываем: стек содержит (a+b)
|
|
||||||
|
|
||||||
local.get $c ;; помещаем c
|
|
||||||
i32.div_s ;; знаковое деление: стек содержит ((a+b) / c)
|
|
||||||
|
|
||||||
local.get $d ;; помещаем d
|
|
||||||
i32.mul ;; умножение: стек содержит (((a+b)/c) * d)
|
|
||||||
|
|
||||||
;; результат остаётся на стеке — это возвращаемое значение функции
|
|
||||||
)
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
На ВМ Линга нет стека, он трехадресный универсальный ассемблер, если можно так сказать:
|
|
||||||
|
|
||||||
```
|
|
||||||
add_int s1, s2 -> s5 // a + b -> s5
|
|
||||||
div_int s5, s3 -> s5 // s5 -> s5 / c
|
|
||||||
mul_int s5, s4 -> s5 // s5 -> s5 * d
|
|
||||||
return s5
|
|
||||||
```
|
|
||||||
|
|
||||||
Этот код крайне эффективно реализуется на любом реальном процессоре, и хорошо оптимизируется. Исполняется он без всякого стека.
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
Почему не WASM?
|
|
||||||
|
|
||||||
- wasm очень громоздкий и неудобный для разработки, он проектировался очень давно и с другими целями, так что сейчас его безуспешно пытаются "перезаточить" в современные области, но он поддается с трудом. Компилировать в wasm была бы задача по сложности превосходящая весь проект, а результат был бы уныл: мы и так имеем все достоинсва wasm, полезные для Lyng, но не имеем многих его недостатков.
|
|
||||||
|
|
||||||
Виртуалка lyng легкая, и доступна на всех платформах уже сейчас, а вот прикрутить скажем wasm машину к котлину на сервере, чтобы она работала как сопрограммы первого класса вместе с котлинскими, задача сейчас неразрешимая.
|
|
||||||
|
|
||||||
То есть, преимущества wasm что он доступен на многих платформах, для нас неважно - мы и так на тех же платформах имеем полную поддержку.
|
|
||||||
|
|
||||||
- Lyng VM изначально заточена на исполнение именно сопрограмм со сборкой мусора. Эти два ключевых механизма линга практически отсутствуют в wasm, их мучительно прикручивают как расширения, и они довольно слабо поддерживаются.
|
|
||||||
|
|
||||||
- LyngVM заточен на исполнение программ с гибридной ООП-ФП моделью, она хорошо поддерживает множественное наследование, делегацию, работу со сложными списками аргументов (ФП фичи, подстановки, деструктурирование), на васме это очень непросто написать, будет медленно и громоздко.
|
|
||||||
|
|
||||||
- одна из целей Lyng была получить быстрое безопасное и очень мощное скриптовое решение для вклучения в проекты на Java/Kotlin multiplatform. На wasm это в принципе невозможно - его включение это кошмар, он громоздкий. К тому же требует компиляции, а Lyng работает с JIT (исполняет прямо исходник, компилируя его на лету, как JS), и может использоваться как скрипт (wasm не может)
|
|
||||||
|
|
||||||
- типы данных которые мы используем в Lyng в WASM отсутстсвтуют, их бы пришлось добавлять
|
|
||||||
|
|
||||||
В результате если бы мы компилировали Lyng->WASM мы бы получили огромный и медленный код. Для которого потребовалась бы огромная wasm VM. Практического смысла в этом нет.
|
|
||||||
|
|
||||||
Если же ты рассматриваешь идею в единой платформе использовать разные языки, а wasm как промежуточный язых совместимости, то это собственно противоречит идее использовать общую кодовую базу и интерфейсы. Разные языки имеют несовпадающие, несовместимые интерфейсы вызовов, так что приходится писать руками биндинги, которые не добавляют скорости и добавляют ошибки, к тому же, разные языки предоставляют часто вообще несовместимые модели программирования (например прототипы в JS, множественное наследование С++ или Линга и кошмар на улице вязов от руста, в владением ссылками).
|
|
||||||
|
|
||||||
Платформ на васме полно, и там и ловить нечего, и неинтересно. Я же предлагаю вылезти в другой класс. Навеяно реальным опоытом переносов контрактов из старого золота в мольтпей, которое получается на удивление хорошо
|
|
||||||
Loading…
x
Reference in New Issue
Block a user