From f18f8f572bc460c09d20c30ec9ef1a600791f683 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Tue, 8 Apr 2025 09:26:54 +0100 Subject: [PATCH 01/37] experimental opsem --- docs/opsem.md | 337 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 docs/opsem.md diff --git a/docs/opsem.md b/docs/opsem.md new file mode 100644 index 000000000..992d42ade --- /dev/null +++ b/docs/opsem.md @@ -0,0 +1,337 @@ +# Operational Semantics + +Still to do: +* Type tests and `typeof` for Ref Type. +* Arrays? Or model them as objects? +* For a reference to a primitive on the stack, build a wrapper object. +* Undecided region. +* Region extraction. +* Region entry points. +* Region deallocation. +* Immutability. +* Behaviors. +* GC or RC cycle detection. + +## Shape + +```rs + +x, y, z ∈ Ident +τ ∈ TypeId +𝕗 ∈ FuncId +ρ ∈ RegionId +Φ ∈ FrameId +ι ∈ ObjectId +ιs ∈ 𝒫(ObjectId) + +T ∈ Type = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | TypeId | Ref TypeId + +𝕥 ∈ TypeDesc = + { + supertypes: 𝒫(TypeId), + fields: Ident ↦ Type, + methods: Ident ↦ FuncId + } + +F ∈ Func = + { + params: {name: Ident, type: Type}*, + result: Type, + body: Stmt* + } + +P ∈ Program = + { + primitives: Type ↦ TypeDesc, + types: TypeId ↦ TypeDesc, + funcs: FuncId ↦ Func + } + +𝕣 ∈ Reference = {object: ObjectId, field: Ident} +p ∈ Primitive = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ +v ∈ Value = ObjectId | Primitive | Reference +// TODO: this doesn't allow embedded object fields +ω ∈ Object = Ident ↦ Value + +ϕ ∈ Frame = { + id = FrameId, + vars: Ident ↦ Value, + ret: Ident, + cont: Statement* + } + +σ ∈ Stack = Frame* + +R ∈ RegionType = RegionRC | RegionGC | RegionArena + Region = { + type: RegionType, + members: ObjectId ↦ ℕ + } + + // An object located in another object is an embedded field. + Metadata = + { + type: TypeId, + location: RegionId | FrameId | ObjectId + } + +χ ∈ Heap = + { + data: ObjectId ↦ Object, + metadata: ObjectId ↦ Metadata + frames: 𝒫(FrameId), + regions: RegionId ↦ Region + } + +Heap, Stack, Statement* ⇝ Heap, Stack, Statement* + +// Frame variables. +x ∈ φ ≝ x ∈ dom(φ.vars) +φ(x) = φ.vars(x) +φ[x↦v] = φ[vars(x)↦v] +φ\x = φ[vars\{x}] +φ\{x} = φ[vars\{x}] + +// Heap objects. +ι ∈ χ ≝ ι ∈ dom(χ.data) +χ(ι) = χ.data(ι) +χ[ι↦(ω, τ, Φ)] = χ[data(ι)↦ω, metadata(ι)↦(τ, Φ)] +χ[ι↦(ω, τ, ρ)] = χ[data(ι)↦ω, metadata(ι)↦(τ, ρ), regions(ρ).members[ι↦1]] + +// Regions. +ρ ∈ χ ≝ ρ ∈ dom(χ.regions) +χ[ρ↦R] = χ[regions(ρ)↦(R, ∅)] + +// Frame management. +χ∪Φ = {χ.data, χ.metadata, χ.frames∪{Φ}, χ.regions} +χ\Φ = {χ.data, χ.metadata, χ.frames\{Φ}, χ.regions} + +// Stack deallocation. +χ\ι = {χ.data\{ι}, χ.metadata\{ι}, χ.frames, χ.regions} +χ\{ιs} = {χ.data\ιs, χ.metadata\ιs, χ.frames, χ.regions} + +// Object in region deallocation. +χ\(ι, ρ) = {χ.data\{ι}, χ.metadata\{ι}, χ.frames, χ.regions[ρ\{ι}]} + +// Dynamic type of a value. +typeof(χ, v) = + P.primitives(Bool) if v ∈ Bool + P.primitives(Signed × ℕ) if v ∈ Signed × ℕ + P.primitives(Unsigned × ℕ) if v ∈ Unsigned × ℕ + P.primitives(Float × ℕ) if v ∈ Float × ℕ + χ.metadata(ι).type if ι = v + // TODO: dynamic type of a reference is not a τ !! + Ref typeof(χ, χ(𝕣.object)(𝕣.field)) if 𝕣 = v + +// Subtype test. +// TODO: what if it's a reference? +typetest(χ, v, T) = T ∈ P.types(typeof(χ, v)).supertypes + +// Transitive closure. +reachable(χ, v) = reachable(χ, v, ∅) +reachable(χ, p, ιs) = ιs +reachable(χ, 𝕣, ιs) = reachable(χ, 𝕣.object, ιs) +reachable(χ, ι, ιs) = + ιs if ι ∈ ιs + ιsₙ otherwise + where + xs = [x | x ∈ dom(χ(ι))] ∧ + n = |xs| ∧ + ιs₀ = ιs ∧ + ∀i ∈ 0 .. n . ιsᵢ₊₁ = reachable(ιsᵢ, χ(ι)(xsᵢ)) + + +// It's safe to return an object from a function if it's: +// * in a region, or +// * in a parent frame on the same stack, or +// * embedded in an object that is safe to return. +returnable(χ, σ, ι) = + χ.metadata(ι).location = ρ ∨ + ∃ϕ ∈ σ . χ.metadata(ι).location = ϕ.id ∨ + ((χ.metadata(ι).location = ι′) ∧ returnable(χ, σ, ι′)) + +// Reference counting. +inc(χ, p) = χ +inc(χ, 𝕣) = dec(χ, 𝕣.object) +inc(χ, ι) = + χ if χ.metadata(ι).location = Φ + inc(χ, ι′) if χ.metadata(ι).location = ι′ + incref(χ, ι) if χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC + +dec(χ, p) = χ +dec(χ, 𝕣) = dec(χ, 𝕣.object) +dec(χ, ι) = + χ if χ.metadata(ι).location = Φ + dec(χ, ι′) if χ.metadata(ι).location = ι′ + decref(χ, ι) if χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC + +incref(χ, ι) = + χ[ρ↦χ(ρ)[members(ι)↦(rc + 1)]] + where + ρ = χ.metadata(ι).location ∧ + rc = χ(ρ).members(ι) + +decref(χ, ι) = + free(χ, ρ, ι) if rc = 1 + χ[ρ↦χ(ρ)[members(ι)↦(rc - 1)]] otherwise + where + ρ = χ.metadata(ι).location ∧ + rc = χ(ρ).members(ι) + +free(χ, ρ, ι) = χₙ[ρ\ι] where + χ₀ = χ ∧ + n = |xs| ∧ + xs = [x | x ∈ dom(χ(ι))] ∧ + ∀i ∈ 0 .. n . χᵢ₊₁ = dec(χᵢ, χ(ι)(xsᵢ)) + +``` + +## New + +```rs + +x ∉ φ +--- [new primitive] +χ, σ;φ, bind x (primitive p);stmt* ⇝ χ, σ;φ[x↦p], stmt* + +newobject(χ, τ, (y, z)*) = + ω where + f = P.types(τ).fields ∧ + ys = {y | y ∈ (y, z)*} = dom(f) ∧ + zs = {z | z ∈ (y, z)*} ∧ + |zs| = |dom(f)| ∧ + ω = {y ↦ φ(z) | y ∈ (y, z)*} ∧ + ∀y ∈ dom(ω) . typetest(χ, f(y).type, ω(y)) + +x ∉ φ +ι ∉ χ +ω = newobject(χ, τ, (y, z)*) +--- [new stack] +χ, σ;φ, bind x (new τ (y, z)*);stmt* ⇝ χ[ι↦(ω, τ, φ.id], σ;φ[x↦ι], stmt* + +x ∉ φ +ι ∉ χ +ρ = χ.metadata(φ(y)).location +ω = newobject(χ, τ, (y, z)*) +--- [new heap] +χ, σ;φ, bind x (new y τ (y, z)*);stmt* ⇝ χ[ι↦(ω, τ, ρ)], σ;φ[x↦ι], stmt* + +x ∉ φ +ι ∉ χ +ρ ∉ χ +ω = newobject(χ, τ, (y, z)*) +--- [new region] +χ, σ;φ, bind x (new R τ (y, z)*);stmt* ⇝ χ[ρ↦R][ι↦(ω, τ, ρ)], σ;φ[x↦ι], stmt* + +``` + +## Drop, Duplicate + +```rs + +--- [drop] +χ, σ;φ, drop x;stmt* ⇝ dec(χ, φ, φ(x)), σ;ϕ\x, stmt* + +x ∉ ϕ +ϕ(y) = v +--- [dup] +χ, σ;φ, bind x (dup y);stmt* ⇝ inc(χ, v), σ;φ[x↦v], stmt* + +``` + +## Fields + +```rs + +// TODO: should this consume y instead of inc? +x ∉ ϕ +ι = ϕ(y) +z ∈ dom(P.types(typeof(χ, ι)).fields) +𝕣 = {object: ι, field: z} +--- [bind ref] +χ, σ;ϕ, bind x (ref y z);stmt* ⇝ inc(χ, ι), σ;ϕ[x↦𝕣], stmt* + +x ∉ ϕ +𝕣 = ϕ(y) +𝕣 = {object: ι, field: z} +z ∈ dom(P.types(typeof(χ, ι)).fields) +v = χ(ι)(z) +--- [bind load] +χ, σ;ϕ, bind x (load y);stmt* ⇝ inc(χ, v), σ;ϕ[x↦v], stmt* + +// TODO: should this consume z instead of inc? +x ∉ ϕ +ϕ(y) = {object: ι, field: z} +z ∈ dom(P.types(typeof(χ, ι)).fields) +v₀ = χ(ι)(z) +v₁ = φ(z) +χ₁ = χ₀[ι↦χ₀(ι)[z↦v₁]] +--- [bind store] +χ₀, σ;ϕ, bind x (store y z);stmt* ⇝ inc(χ₁, v₁), σ;ϕ[x↦v₀], stmt* + +``` + +## Type Test + +```rs + +x ∉ ϕ +v = typetest(χ, φ(y), T) +--- [typetest] +χ, σ;φ, bind x (typetest T y);stmt* ⇝ χ, σ;φ[x↦v], stmt* + +``` + +## Conditional + +```rs + +φ(x) = true +--- [cond true] +χ, σ;φ, cond x stmt₀* stmt₁*;stmt₂* ⇝ χ, σ;φ, stmt₀*;stmt₂* + +Θ(x) = false +--- [cond false] +χ, σ;φ, cond x stmt₀* stmt₁*;stmt₂* ⇝ χ, σ;φ, stmt₁*;stmt₂* + +``` + +## Call + +All arguments are consumed. To keep them, `dup` them first. As such, an identifier can't appear more than once in the argument list. + +```rs + +newframe(χ, ϕ, F, x, y*, stmt*) = + {id: Φ, vars: F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 0 .. |y*|, ret: x, cont: stmt*} + where + Φ ∉ dom(χ.metadata.frames) ∧ + |F.params| = |y*| = |{y*}| ∧ + ∀i ∈ 0 .. |y*| . typetest(χ, φ(yᵢ), F.paramsᵢ.type) + +x ∉ φ₀ +F = P.funcs(𝕗) +φ₁ = newframe(χ, φ₀, F, x, y*, stmt*) +--- [call static] +χ, σ;φ₀, bind x (call 𝕗 y*);stmt* ⇝ χ∪φ₁.id, σ;φ₀;φ₁\{y*}, F.body + +x ∉ φ₀ +τ = typeof(χ, φ(z₀)) +F = P.funcs(P.types(τ).methods(y)) +φ₁ = newframe(χ, φ₀, F, x, z*, stmt*) +--- [call dynamic] +χ, σ;φ₀, bind x (call y z*);stmt* ⇝ χ∪φ₁.id, σ;φ₀;φ₁\{z*}, F.body + +``` + +## Return + +```rs + +|dom(φ₁)| = 1 +ιs = {ι | χ.metadata(ι).location = φ₁} +∀ι ∈ reachable(χ, φ₁(x)) . returnable(χ, σ, ι) +--- [return] +χ, σ;φ₀;φ₁, return x;stmt* ⇝ (χ\ιs)\φ₁.id, σ;φ₀[φ₁.ret↦φ₁(x)], ϕ₁.cont + +``` From 2f43e4cae3c688e7619174d04222a3ecc96980ee Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Thu, 30 Jan 2025 23:48:00 -0600 Subject: [PATCH 02/37] opsem minor edits --- docs/opsem.md | 67 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 992d42ade..970057474 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -2,12 +2,13 @@ Still to do: * Type tests and `typeof` for Ref Type. +* Embedded object fields? * Arrays? Or model them as objects? * For a reference to a primitive on the stack, build a wrapper object. * Undecided region. -* Region extraction. * Region entry points. * Region deallocation. +* Region extraction. * Immutability. * Behaviors. * GC or RC cycle detection. @@ -16,7 +17,9 @@ Still to do: ```rs +n ∈ ℕ x, y, z ∈ Ident +xs, ys, zs ∈ 𝒫(Ident) τ ∈ TypeId 𝕗 ∈ FuncId ρ ∈ RegionId @@ -50,7 +53,6 @@ P ∈ Program = 𝕣 ∈ Reference = {object: ObjectId, field: Ident} p ∈ Primitive = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ v ∈ Value = ObjectId | Primitive | Reference -// TODO: this doesn't allow embedded object fields ω ∈ Object = Ident ↦ Value ϕ ∈ Frame = { @@ -85,12 +87,18 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena Heap, Stack, Statement* ⇝ Heap, Stack, Statement* +``` + +## Helpers + +```rs + // Frame variables. x ∈ φ ≝ x ∈ dom(φ.vars) φ(x) = φ.vars(x) φ[x↦v] = φ[vars(x)↦v] -φ\x = φ[vars\{x}] -φ\{x} = φ[vars\{x}] +φ\x = φ\{x} +φ\xs = φ[vars\xs] // Heap objects. ι ∈ χ ≝ ι ∈ dom(χ.data) @@ -107,12 +115,18 @@ x ∈ φ ≝ x ∈ dom(φ.vars) χ\Φ = {χ.data, χ.metadata, χ.frames\{Φ}, χ.regions} // Stack deallocation. -χ\ι = {χ.data\{ι}, χ.metadata\{ι}, χ.frames, χ.regions} -χ\{ιs} = {χ.data\ιs, χ.metadata\ιs, χ.frames, χ.regions} +χ\ι = χ\{ι} +χ\ιs = {χ.data\ιs, χ.metadata\ιs, χ.frames, χ.regions} // Object in region deallocation. χ\(ι, ρ) = {χ.data\{ι}, χ.metadata\{ι}, χ.frames, χ.regions[ρ\{ι}]} +``` + +## Dynamic Types + +```rs + // Dynamic type of a value. typeof(χ, v) = P.primitives(Bool) if v ∈ Bool @@ -120,12 +134,18 @@ typeof(χ, v) = P.primitives(Unsigned × ℕ) if v ∈ Unsigned × ℕ P.primitives(Float × ℕ) if v ∈ Float × ℕ χ.metadata(ι).type if ι = v - // TODO: dynamic type of a reference is not a τ !! Ref typeof(χ, χ(𝕣.object)(𝕣.field)) if 𝕣 = v // Subtype test. -// TODO: what if it's a reference? -typetest(χ, v, T) = T ∈ P.types(typeof(χ, v)).supertypes +typetest(χ, v, T) = + T = typeof(χ, v) if v ∈ Reference + T ∈ P.types(typeof(χ, v)).supertypes otherwise + +``` + +## Reachability and Safety + +```rs // Transitive closure. reachable(χ, v) = reachable(χ, v, ∅) @@ -140,7 +160,6 @@ reachable(χ, ι, ιs) = ιs₀ = ιs ∧ ∀i ∈ 0 .. n . ιsᵢ₊₁ = reachable(ιsᵢ, χ(ι)(xsᵢ)) - // It's safe to return an object from a function if it's: // * in a region, or // * in a parent frame on the same stack, or @@ -150,7 +169,12 @@ returnable(χ, σ, ι) = ∃ϕ ∈ σ . χ.metadata(ι).location = ϕ.id ∨ ((χ.metadata(ι).location = ι′) ∧ returnable(χ, σ, ι′)) -// Reference counting. +``` + +## Reference counting. + +```rs + inc(χ, p) = χ inc(χ, 𝕣) = dec(χ, 𝕣.object) inc(χ, ι) = @@ -190,10 +214,6 @@ free(χ, ρ, ι) = χₙ[ρ\ι] where ```rs -x ∉ φ ---- [new primitive] -χ, σ;φ, bind x (primitive p);stmt* ⇝ χ, σ;φ[x↦p], stmt* - newobject(χ, τ, (y, z)*) = ω where f = P.types(τ).fields ∧ @@ -203,6 +223,10 @@ newobject(χ, τ, (y, z)*) = ω = {y ↦ φ(z) | y ∈ (y, z)*} ∧ ∀y ∈ dom(ω) . typetest(χ, f(y).type, ω(y)) +x ∉ φ +--- [new primitive] +χ, σ;φ, bind x (primitive p);stmt* ⇝ χ, σ;φ[x↦p], stmt* + x ∉ φ ι ∉ χ ω = newobject(χ, τ, (y, z)*) @@ -227,6 +251,8 @@ x ∉ φ ## Drop, Duplicate +Local variables are consumed on use. To keep them, `dup` them first. + ```rs --- [drop] @@ -241,6 +267,8 @@ x ∉ ϕ ## Fields +> Could add stack references. Instead of an object container, it would be a frame container. + ```rs // TODO: should this consume y instead of inc? @@ -252,8 +280,7 @@ z ∈ dom(P.types(typeof(χ, ι)).fields) χ, σ;ϕ, bind x (ref y z);stmt* ⇝ inc(χ, ι), σ;ϕ[x↦𝕣], stmt* x ∉ ϕ -𝕣 = ϕ(y) -𝕣 = {object: ι, field: z} +ϕ(y) = {object: ι, field: z} z ∈ dom(P.types(typeof(χ, ι)).fields) v = χ(ι)(z) --- [bind load] @@ -290,7 +317,7 @@ v = typetest(χ, φ(y), T) --- [cond true] χ, σ;φ, cond x stmt₀* stmt₁*;stmt₂* ⇝ χ, σ;φ, stmt₀*;stmt₂* -Θ(x) = false +φ(x) = false --- [cond false] χ, σ;φ, cond x stmt₀* stmt₁*;stmt₂* ⇝ χ, σ;φ, stmt₁*;stmt₂* @@ -326,12 +353,14 @@ F = P.funcs(P.types(τ).methods(y)) ## Return +This checks that only the return value remains in the frame, and that the return value and everything it references is safe to return. + ```rs |dom(φ₁)| = 1 ιs = {ι | χ.metadata(ι).location = φ₁} ∀ι ∈ reachable(χ, φ₁(x)) . returnable(χ, σ, ι) --- [return] -χ, σ;φ₀;φ₁, return x;stmt* ⇝ (χ\ιs)\φ₁.id, σ;φ₀[φ₁.ret↦φ₁(x)], ϕ₁.cont +χ, σ;φ₀;φ₁, return x;stmt* ⇝ (χ\ιs)\(φ₁.id), σ;φ₀[φ₁.ret↦φ₁(x)], ϕ₁.cont ``` From 5ba0298714ad9b9ca72185038a72811b3fa75704 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Thu, 30 Jan 2025 23:53:41 -0600 Subject: [PATCH 03/37] opsem minor edits --- docs/opsem.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/opsem.md b/docs/opsem.md index 970057474..8bb29940f 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -4,7 +4,8 @@ Still to do: * Type tests and `typeof` for Ref Type. * Embedded object fields? * Arrays? Or model them as objects? -* For a reference to a primitive on the stack, build a wrapper object. +* Stack references? The container is a frame instead of an object. +* Region safety. * Undecided region. * Region entry points. * Region deallocation. @@ -12,6 +13,7 @@ Still to do: * Immutability. * Behaviors. * GC or RC cycle detection. +* Non-local returns. ## Shape From 5c14a34b49745a700bfbcab420dc97d91c7784b4 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Fri, 31 Jan 2025 18:06:35 -0600 Subject: [PATCH 04/37] opsem stack reference counting --- docs/opsem.md | 112 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 45 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 8bb29940f..40f65003b 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -1,17 +1,16 @@ # Operational Semantics Still to do: -* Type tests and `typeof` for Ref Type. -* Embedded object fields? -* Arrays? Or model them as objects? -* Stack references? The container is a frame instead of an object. * Region safety. * Undecided region. * Region entry points. * Region deallocation. * Region extraction. * Immutability. -* Behaviors. +* Behaviors and cowns. +* Embedded object fields? +* Arrays? Or model them as objects? +* Stack references? The container is a frame instead of an object. * GC or RC cycle detection. * Non-local returns. @@ -57,8 +56,9 @@ p ∈ Primitive = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ v ∈ Value = ObjectId | Primitive | Reference ω ∈ Object = Ident ↦ Value -ϕ ∈ Frame = { - id = FrameId, +ϕ ∈ Frame = + { + id: FrameId, vars: Ident ↦ Value, ret: Ident, cont: Statement* @@ -83,7 +83,7 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena { data: ObjectId ↦ Object, metadata: ObjectId ↦ Metadata - frames: 𝒫(FrameId), + frames: FrameId ↦ {members: ObjectId ↦ ℕ} regions: RegionId ↦ Region } @@ -105,16 +105,16 @@ x ∈ φ ≝ x ∈ dom(φ.vars) // Heap objects. ι ∈ χ ≝ ι ∈ dom(χ.data) χ(ι) = χ.data(ι) -χ[ι↦(ω, τ, Φ)] = χ[data(ι)↦ω, metadata(ι)↦(τ, Φ)] +χ[ι↦(ω, τ, Φ)] = χ[data(ι)↦ω, metadata(ι)↦(τ, Φ), frames(Φ).members[ι↦1]] χ[ι↦(ω, τ, ρ)] = χ[data(ι)↦ω, metadata(ι)↦(τ, ρ), regions(ρ).members[ι↦1]] // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) χ[ρ↦R] = χ[regions(ρ)↦(R, ∅)] -// Frame management. -χ∪Φ = {χ.data, χ.metadata, χ.frames∪{Φ}, χ.regions} -χ\Φ = {χ.data, χ.metadata, χ.frames\{Φ}, χ.regions} +// Frames. +χ∪Φ = {χ.data, χ.metadata, χ.frames[Φ↦∅], χ.regions} +χ\Φ = {χ.data, χ.metadata, χ.frames\Φ, χ.regions} // Stack deallocation. χ\ι = χ\{ι} @@ -162,53 +162,67 @@ reachable(χ, ι, ιs) = ιs₀ = ιs ∧ ∀i ∈ 0 .. n . ιsᵢ₊₁ = reachable(ιsᵢ, χ(ι)(xsᵢ)) -// It's safe to return an object from a function if it's: -// * in a region, or -// * in a parent frame on the same stack, or -// * embedded in an object that is safe to return. -returnable(χ, σ, ι) = - χ.metadata(ι).location = ρ ∨ - ∃ϕ ∈ σ . χ.metadata(ι).location = ϕ.id ∨ - ((χ.metadata(ι).location = ι′) ∧ returnable(χ, σ, ι′)) +// This checks that it's safe to discharge a region, including: +// * deallocate the region, or +// * freeze the region, or +// * send the region to a behavior. +// TODO: this doesn't allow a region to reference another region +// TODO: this doesn't require other regions or stacks not to reference this region +dischargeable(χ, ρ) = + ∀ι ∈ ιs . reachable(χ, ι) ⊆ ιs + where + ιs = χ.regions(ρ).members ``` ## Reference counting. +Reference counting is a no-op on `RegionGC` and `RegionArena`. It's tracked on stack allocations to ensure that no allocations on a frame that is being torn down are returned. + ```rs inc(χ, p) = χ inc(χ, 𝕣) = dec(χ, 𝕣.object) inc(χ, ι) = - χ if χ.metadata(ι).location = Φ inc(χ, ι′) if χ.metadata(ι).location = ι′ - incref(χ, ι) if χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC + incref(χ, ρ, ι) if χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC + incref(χ, Φ, ι) if χ.metadata(ι).location = Φ + χ otherwise dec(χ, p) = χ dec(χ, 𝕣) = dec(χ, 𝕣.object) dec(χ, ι) = - χ if χ.metadata(ι).location = Φ dec(χ, ι′) if χ.metadata(ι).location = ι′ - decref(χ, ι) if χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC + decref(χ, ρ, ι) if χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC + decref(χ, Φ, ι) if χ.metadata(ι).location = Φ + χ otherwise + +incref(χ, Φ, ι) = + χ[frames(Φ)↦χ(Φ)[members(ι)↦(rc + 1)]] + where + rc = χ(Φ).members(ι) -incref(χ, ι) = - χ[ρ↦χ(ρ)[members(ι)↦(rc + 1)]] +incref(χ, ρ, ι) = + χ[regions(ρ)↦χ.regions(ρ)[members(ι)↦(rc + 1)]] where - ρ = χ.metadata(ι).location ∧ rc = χ(ρ).members(ι) -decref(χ, ι) = +decref(χ, Φ, ι) = + χ[frames(Φ)↦χ.frames(Φ)[members(ι)↦(rc - 1)]] + where + rc = χ(Φ).members(ι) + +decref(χ, ρ, ι) = free(χ, ρ, ι) if rc = 1 - χ[ρ↦χ(ρ)[members(ι)↦(rc - 1)]] otherwise + χ[regions(ρ)↦χ.regions(ρ)[members(ι)↦(rc - 1)]] otherwise where - ρ = χ.metadata(ι).location ∧ rc = χ(ρ).members(ι) free(χ, ρ, ι) = χₙ[ρ\ι] where - χ₀ = χ ∧ - n = |xs| ∧ xs = [x | x ∈ dom(χ(ι))] ∧ - ∀i ∈ 0 .. n . χᵢ₊₁ = dec(χᵢ, χ(ι)(xsᵢ)) + n = |xs| ∧ + χ₀ = χ ∧ + ∀i ∈ 0 .. (n - 1) . χᵢ₊₁ = dec(χᵢ, χ(ι)(xsᵢ)) ``` @@ -219,35 +233,36 @@ free(χ, ρ, ι) = χₙ[ρ\ι] where newobject(χ, τ, (y, z)*) = ω where f = P.types(τ).fields ∧ - ys = {y | y ∈ (y, z)*} = dom(f) ∧ - zs = {z | z ∈ (y, z)*} ∧ - |zs| = |dom(f)| ∧ + {y | y ∈ (y, z)*} = dom(f) ∧ ω = {y ↦ φ(z) | y ∈ (y, z)*} ∧ ∀y ∈ dom(ω) . typetest(χ, f(y).type, ω(y)) x ∉ φ --- [new primitive] -χ, σ;φ, bind x (primitive p);stmt* ⇝ χ, σ;φ[x↦p], stmt* +χ, σ;φ, bind x (new p);stmt* ⇝ χ, σ;φ[x↦p], stmt* x ∉ φ ι ∉ χ +zs = {z | z ∈ (y, z)*} ∧ |zs| = |(y, z)*| ω = newobject(χ, τ, (y, z)*) --- [new stack] -χ, σ;φ, bind x (new τ (y, z)*);stmt* ⇝ χ[ι↦(ω, τ, φ.id], σ;φ[x↦ι], stmt* +χ, σ;φ, bind x (new τ (y, z)*);stmt* ⇝ χ[ι↦(ω, τ, φ.id)], σ;φ[x↦ι]\zs, stmt* x ∉ φ ι ∉ χ ρ = χ.metadata(φ(y)).location +zs = {z | z ∈ (y, z)*} ∧ |zs| = |(y, z)*| ω = newobject(χ, τ, (y, z)*) --- [new heap] -χ, σ;φ, bind x (new y τ (y, z)*);stmt* ⇝ χ[ι↦(ω, τ, ρ)], σ;φ[x↦ι], stmt* +χ, σ;φ, bind x (new y τ (y, z)*);stmt* ⇝ χ[ι↦(ω, τ, ρ)], σ;φ[x↦ι]\zs, stmt* x ∉ φ ι ∉ χ ρ ∉ χ +zs = {z | z ∈ (y, z)*} ∧ |zs| = |(y, z)*| ω = newobject(χ, τ, (y, z)*) --- [new region] -χ, σ;φ, bind x (new R τ (y, z)*);stmt* ⇝ χ[ρ↦R][ι↦(ω, τ, ρ)], σ;φ[x↦ι], stmt* +χ, σ;φ, bind x (new R τ (y, z)*);stmt* ⇝ χ[ρ↦R][ι↦(ω, τ, ρ)], σ;φ[x↦ι]\zs, stmt* ``` @@ -269,16 +284,21 @@ x ∉ ϕ ## Fields -> Could add stack references. Instead of an object container, it would be a frame container. - ```rs +// TODO: ref can't be in a frame yet +x ∉ ϕ +y ∈ φ +𝕣 = {object: φ.id, field: z} +--- [bind stack ref] +χ, σ;ϕ, bind x (ref y);stmt* ⇝ inc(χ, ι), σ;ϕ[x↦𝕣], stmt* + // TODO: should this consume y instead of inc? x ∉ ϕ ι = ϕ(y) z ∈ dom(P.types(typeof(χ, ι)).fields) 𝕣 = {object: ι, field: z} ---- [bind ref] +--- [bind field ref] χ, σ;ϕ, bind x (ref y z);stmt* ⇝ inc(χ, ι), σ;ϕ[x↦𝕣], stmt* x ∉ ϕ @@ -355,13 +375,15 @@ F = P.funcs(P.types(τ).methods(y)) ## Return -This checks that only the return value remains in the frame, and that the return value and everything it references is safe to return. +This checks that: +* only the return value remains in the frame, to ensure proper reference counting, and +* that the reference count of everything allocated on this frame has dropped to zero, which ensures that no dangling references are returned. ```rs |dom(φ₁)| = 1 ιs = {ι | χ.metadata(ι).location = φ₁} -∀ι ∈ reachable(χ, φ₁(x)) . returnable(χ, σ, ι) +∀ι ∈ ιs . χ.frames(φ₁.id).members(ι) = 0 --- [return] χ, σ;φ₀;φ₁, return x;stmt* ⇝ (χ\ιs)\(φ₁.id), σ;φ₀[φ₁.ret↦φ₁(x)], ϕ₁.cont From 25c2813108d780c195d919e01929a99abc956a24 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Fri, 31 Jan 2025 23:53:59 -0600 Subject: [PATCH 05/37] opsem region extract --- docs/opsem.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/opsem.md b/docs/opsem.md index 40f65003b..49136b3b2 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -5,7 +5,6 @@ Still to do: * Undecided region. * Region entry points. * Region deallocation. -* Region extraction. * Immutability. * Behaviors and cowns. * Embedded object fields? @@ -388,3 +387,21 @@ This checks that: χ, σ;φ₀;φ₁, return x;stmt* ⇝ (χ\ιs)\(φ₁.id), σ;φ₀[φ₁.ret↦φ₁(x)], ϕ₁.cont ``` + +## Extract + +```rs + +x ∉ φ +ι = φ(y) +ρ₀ = χ₀.metadata(ι).location +ρ₁ ∉ χ₀ +ιs = reachable(χ, ι) +∀ι′ ∈ χ₀.regions(ρ₀).members . (ι′ ∉ ιs ⇒ ∀z ∈ dom(χ₀(ι′)) . χ₀(ι′)(z) ∉ ιs) +χ₁ = χ₀[regions(ρ₀).members\ιs] + [regions(ρ₁)↦{type: χ₀.regions(ρ₀).type, members: ιs}] + [∀ι′ ∈ ιs . χ₀.metadata(ι′).location↦ρ₁] +--- [extract] +χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁, σ;φ[x↦ι]\y, stmt* + +``` From 120f5caa718725915db7b386e17be8ddf7dafc5d Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Sat, 1 Feb 2025 10:34:00 -0600 Subject: [PATCH 06/37] opsem minor edits --- docs/opsem.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/opsem.md b/docs/opsem.md index 49136b3b2..2b1a38af2 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -168,6 +168,7 @@ reachable(χ, ι, ιs) = // TODO: this doesn't allow a region to reference another region // TODO: this doesn't require other regions or stacks not to reference this region dischargeable(χ, ρ) = + ∀ι ∈ χ . ι ∉ ιs ⇒ ∀z ∈ dom(χ(ι)) . χ(ι)(z) ∉ ιs ∧ ∀ι ∈ ιs . reachable(χ, ι) ⊆ ιs where ιs = χ.regions(ρ).members @@ -286,6 +287,7 @@ x ∉ ϕ ```rs // TODO: ref can't be in a frame yet +// tricky: inc/dec, typeof, reachable take a heap but not a stack x ∉ ϕ y ∈ φ 𝕣 = {object: φ.id, field: z} From 8025fb70210fd63d8d86d046281ce09b9bd8b2e6 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Mon, 3 Feb 2025 10:22:33 -0600 Subject: [PATCH 07/37] move rc into metadata --- docs/opsem.md | 77 +++++++++++++++++++++------------------------------ 1 file changed, 32 insertions(+), 45 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 2b1a38af2..dbdbe8b55 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -2,10 +2,11 @@ Still to do: * Region safety. -* Undecided region. * Region entry points. * Region deallocation. * Immutability. + * SCCs? No, keep it abstract. Use same cycle detection algo as RC/GC. +* Undecided region. * Behaviors and cowns. * Embedded object fields? * Arrays? Or model them as objects? @@ -67,8 +68,7 @@ v ∈ Value = ObjectId | Primitive | Reference R ∈ RegionType = RegionRC | RegionGC | RegionArena Region = { - type: RegionType, - members: ObjectId ↦ ℕ + type: RegionType } // An object located in another object is an embedded field. @@ -76,14 +76,15 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena { type: TypeId, location: RegionId | FrameId | ObjectId + rc: ℕ } χ ∈ Heap = { data: ObjectId ↦ Object, - metadata: ObjectId ↦ Metadata - frames: FrameId ↦ {members: ObjectId ↦ ℕ} - regions: RegionId ↦ Region + metadata: ObjectId ↦ Metadata, + regions: RegionId ↦ Region, + frames: 𝒫(FrameId) } Heap, Stack, Statement* ⇝ Heap, Stack, Statement* @@ -104,23 +105,20 @@ x ∈ φ ≝ x ∈ dom(φ.vars) // Heap objects. ι ∈ χ ≝ ι ∈ dom(χ.data) χ(ι) = χ.data(ι) -χ[ι↦(ω, τ, Φ)] = χ[data(ι)↦ω, metadata(ι)↦(τ, Φ), frames(Φ).members[ι↦1]] -χ[ι↦(ω, τ, ρ)] = χ[data(ι)↦ω, metadata(ι)↦(τ, ρ), regions(ρ).members[ι↦1]] +χ[ι↦(ω, τ, Φ)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: Φ, rc: 1}] +χ[ι↦(ω, τ, ρ)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: ρ, rc: 1}] // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) χ[ρ↦R] = χ[regions(ρ)↦(R, ∅)] // Frames. -χ∪Φ = {χ.data, χ.metadata, χ.frames[Φ↦∅], χ.regions} -χ\Φ = {χ.data, χ.metadata, χ.frames\Φ, χ.regions} +χ∪Φ = χ[frames = frames ∪ Φ] +χ\Φ = χ[frames = frames \ Φ] -// Stack deallocation. +// Deallocation. χ\ι = χ\{ι} -χ\ιs = {χ.data\ιs, χ.metadata\ιs, χ.frames, χ.regions} - -// Object in region deallocation. -χ\(ι, ρ) = {χ.data\{ι}, χ.metadata\{ι}, χ.frames, χ.regions[ρ\{ι}]} +χ\ιs = χ[data = data\ιs, metadata = metadata\ιs] ``` @@ -159,19 +157,20 @@ reachable(χ, ι, ιs) = xs = [x | x ∈ dom(χ(ι))] ∧ n = |xs| ∧ ιs₀ = ιs ∧ - ∀i ∈ 0 .. n . ιsᵢ₊₁ = reachable(ιsᵢ, χ(ι)(xsᵢ)) + ∀i ∈ 0 .. (n - 1) . ιsᵢ₊₁ = reachable(ιsᵢ, χ(ι)(xsᵢ)) // This checks that it's safe to discharge a region, including: // * deallocate the region, or // * freeze the region, or // * send the region to a behavior. // TODO: this doesn't allow a region to reference another region -// TODO: this doesn't require other regions or stacks not to reference this region +// needs to allow references in to immutable objects. +// TODO: this doesn't require stacks not to reference this region dischargeable(χ, ρ) = ∀ι ∈ χ . ι ∉ ιs ⇒ ∀z ∈ dom(χ(ι)) . χ(ι)(z) ∉ ιs ∧ ∀ι ∈ ιs . reachable(χ, ι) ⊆ ιs where - ιs = χ.regions(ρ).members + ιs = {ι | χ.metadata(ι).location = ρ} ``` @@ -185,40 +184,24 @@ inc(χ, p) = χ inc(χ, 𝕣) = dec(χ, 𝕣.object) inc(χ, ι) = inc(χ, ι′) if χ.metadata(ι).location = ι′ - incref(χ, ρ, ι) if χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC - incref(χ, Φ, ι) if χ.metadata(ι).location = Φ + χ[metadata(ι)[rc↦metadata(ι).rc + 1]] if + χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC ∨ + χ.metadata(ι).location = Φ χ otherwise dec(χ, p) = χ dec(χ, 𝕣) = dec(χ, 𝕣.object) dec(χ, ι) = dec(χ, ι′) if χ.metadata(ι).location = ι′ - decref(χ, ρ, ι) if χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC - decref(χ, Φ, ι) if χ.metadata(ι).location = Φ + free(χ, ρ, ι) if + χ.metadata(ι).rc = 1 ∧ + χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC + χ[metadata(ι)[rc↦metata(ι).rc - 1]] if + χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC ∨ + χ.metadata(ι).location = Φ χ otherwise -incref(χ, Φ, ι) = - χ[frames(Φ)↦χ(Φ)[members(ι)↦(rc + 1)]] - where - rc = χ(Φ).members(ι) - -incref(χ, ρ, ι) = - χ[regions(ρ)↦χ.regions(ρ)[members(ι)↦(rc + 1)]] - where - rc = χ(ρ).members(ι) - -decref(χ, Φ, ι) = - χ[frames(Φ)↦χ.frames(Φ)[members(ι)↦(rc - 1)]] - where - rc = χ(Φ).members(ι) - -decref(χ, ρ, ι) = - free(χ, ρ, ι) if rc = 1 - χ[regions(ρ)↦χ.regions(ρ)[members(ι)↦(rc - 1)]] otherwise - where - rc = χ(ρ).members(ι) - -free(χ, ρ, ι) = χₙ[ρ\ι] where +free(χ, ρ, ι) = χₙ\ι where xs = [x | x ∈ dom(χ(ι))] ∧ n = |xs| ∧ χ₀ = χ ∧ @@ -288,6 +271,8 @@ x ∉ ϕ // TODO: ref can't be in a frame yet // tricky: inc/dec, typeof, reachable take a heap but not a stack +// all variables in a frame could be contained in an object +// - but then frame variable access requires the heap x ∉ ϕ y ∈ φ 𝕣 = {object: φ.id, field: z} @@ -384,7 +369,7 @@ This checks that: |dom(φ₁)| = 1 ιs = {ι | χ.metadata(ι).location = φ₁} -∀ι ∈ ιs . χ.frames(φ₁.id).members(ι) = 0 +∀ι ∈ ιs . χ.metadata(ι).rc = 0 --- [return] χ, σ;φ₀;φ₁, return x;stmt* ⇝ (χ\ιs)\(φ₁.id), σ;φ₀[φ₁.ret↦φ₁(x)], ϕ₁.cont @@ -392,6 +377,8 @@ This checks that: ## Extract +> Doesn't work. Doesn't allow sub-regions or immutable objects. + ```rs x ∉ φ From abf83d25a52ad6c93ea3258a3aabb2ec26fbd6a5 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Wed, 5 Feb 2025 10:28:07 -0600 Subject: [PATCH 08/37] opsem no rc on stack --- docs/opsem.md | 96 +++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 52 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index dbdbe8b55..23467e021 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -10,7 +10,6 @@ Still to do: * Behaviors and cowns. * Embedded object fields? * Arrays? Or model them as objects? -* Stack references? The container is a frame instead of an object. * GC or RC cycle detection. * Non-local returns. @@ -19,12 +18,12 @@ Still to do: ```rs n ∈ ℕ -x, y, z ∈ Ident +w, x, y, z ∈ Ident xs, ys, zs ∈ 𝒫(Ident) τ ∈ TypeId 𝕗 ∈ FuncId ρ ∈ RegionId -Φ ∈ FrameId +𝔽 ∈ FrameId ι ∈ ObjectId ιs ∈ 𝒫(ObjectId) @@ -75,7 +74,7 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena Metadata = { type: TypeId, - location: RegionId | FrameId | ObjectId + location: RegionId | FrameId | ObjectId, rc: ℕ } @@ -95,27 +94,27 @@ Heap, Stack, Statement* ⇝ Heap, Stack, Statement* ```rs -// Frame variables. +// Frames. x ∈ φ ≝ x ∈ dom(φ.vars) φ(x) = φ.vars(x) φ[x↦v] = φ[vars(x)↦v] φ\x = φ\{x} φ\xs = φ[vars\xs] +𝔽 ∈ χ ≝ φ ∈ dom(χ.frames) +χ∪𝔽 = χ[frames∪𝔽] +χ\𝔽 = χ[frames\𝔽] + // Heap objects. ι ∈ χ ≝ ι ∈ dom(χ.data) χ(ι) = χ.data(ι) -χ[ι↦(ω, τ, Φ)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: Φ, rc: 1}] +χ[ι↦(ω, τ, 𝔽)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: 𝔽, rc: 1}] χ[ι↦(ω, τ, ρ)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: ρ, rc: 1}] // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) χ[ρ↦R] = χ[regions(ρ)↦(R, ∅)] -// Frames. -χ∪Φ = χ[frames = frames ∪ Φ] -χ\Φ = χ[frames = frames \ Φ] - // Deallocation. χ\ι = χ\{ι} χ\ιs = χ[data = data\ιs, metadata = metadata\ιs] @@ -159,6 +158,13 @@ reachable(χ, ι, ιs) = ιs₀ = ιs ∧ ∀i ∈ 0 .. (n - 1) . ιsᵢ₊₁ = reachable(ιsᵢ, χ(ι)(xsᵢ)) +// Tree structured regions. +// TODO: stack references? +regiondom(χ, ρ₀, ρ₁) = + ∀ι₀, ι₁ ∈ χ . + (∃z . χ(ι₀)(z) = ι₁) ∧ (χ.metadata(ι₁).location = ρ₁) ⇒ + χ.metadata(ι₀).location ∈ {ρ₀, ρ₁} + // This checks that it's safe to discharge a region, including: // * deallocate the region, or // * freeze the region, or @@ -176,7 +182,7 @@ dischargeable(χ, ρ) = ## Reference counting. -Reference counting is a no-op on `RegionGC` and `RegionArena`. It's tracked on stack allocations to ensure that no allocations on a frame that is being torn down are returned. +Reference counting is a no-op unless the object is in a `RegionRC`. ```rs @@ -185,8 +191,7 @@ inc(χ, 𝕣) = dec(χ, 𝕣.object) inc(χ, ι) = inc(χ, ι′) if χ.metadata(ι).location = ι′ χ[metadata(ι)[rc↦metadata(ι).rc + 1]] if - χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC ∨ - χ.metadata(ι).location = Φ + χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC χ otherwise dec(χ, p) = χ @@ -197,8 +202,7 @@ dec(χ, ι) = χ.metadata(ι).rc = 1 ∧ χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC χ[metadata(ι)[rc↦metata(ι).rc - 1]] if - χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC ∨ - χ.metadata(ι).location = Φ + χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC χ otherwise free(χ, ρ, ι) = χₙ\ι where @@ -256,7 +260,7 @@ Local variables are consumed on use. To keep them, `dup` them first. ```rs --- [drop] -χ, σ;φ, drop x;stmt* ⇝ dec(χ, φ, φ(x)), σ;ϕ\x, stmt* +χ, σ;φ, drop x;stmt* ⇝ dec(χ, φ(x)), σ;ϕ\x, stmt* x ∉ ϕ ϕ(y) = v @@ -267,42 +271,30 @@ x ∉ ϕ ## Fields -```rs +The `load` statement is the only operation other than `dup` or `drop` that can change the reference count of an object. -// TODO: ref can't be in a frame yet -// tricky: inc/dec, typeof, reachable take a heap but not a stack -// all variables in a frame could be contained in an object -// - but then frame variable access requires the heap -x ∉ ϕ -y ∈ φ -𝕣 = {object: φ.id, field: z} ---- [bind stack ref] -χ, σ;ϕ, bind x (ref y);stmt* ⇝ inc(χ, ι), σ;ϕ[x↦𝕣], stmt* +```rs -// TODO: should this consume y instead of inc? x ∉ ϕ ι = ϕ(y) -z ∈ dom(P.types(typeof(χ, ι)).fields) -𝕣 = {object: ι, field: z} +w ∈ dom(P.types(typeof(χ, ι)).fields) +𝕣 = {object: ι, field: w} --- [bind field ref] -χ, σ;ϕ, bind x (ref y z);stmt* ⇝ inc(χ, ι), σ;ϕ[x↦𝕣], stmt* +χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦𝕣]\y, stmt* x ∉ ϕ -ϕ(y) = {object: ι, field: z} -z ∈ dom(P.types(typeof(χ, ι)).fields) -v = χ(ι)(z) +ϕ(y) = {object: ι, field: w} +w ∈ dom(P.types(typeof(χ, ι)).fields) +v = χ(ι)(w) --- [bind load] χ, σ;ϕ, bind x (load y);stmt* ⇝ inc(χ, v), σ;ϕ[x↦v], stmt* -// TODO: should this consume z instead of inc? x ∉ ϕ -ϕ(y) = {object: ι, field: z} -z ∈ dom(P.types(typeof(χ, ι)).fields) -v₀ = χ(ι)(z) -v₁ = φ(z) -χ₁ = χ₀[ι↦χ₀(ι)[z↦v₁]] +ϕ(y) = {object: ι, field: w} +w ∈ dom(P.types(typeof(χ, ι)).fields) +v = χ(ι)(w) --- [bind store] -χ₀, σ;ϕ, bind x (store y z);stmt* ⇝ inc(χ₁, v₁), σ;ϕ[x↦v₀], stmt* +χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ[ι↦χ(ι)[w↦φ(z)]], σ;ϕ[x↦v]\z, stmt* ``` @@ -338,9 +330,9 @@ All arguments are consumed. To keep them, `dup` them first. As such, an identifi ```rs newframe(χ, ϕ, F, x, y*, stmt*) = - {id: Φ, vars: F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 0 .. |y*|, ret: x, cont: stmt*} + {id: 𝔽, vars: F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 0 .. |y*|, ret: x, cont: stmt*} where - Φ ∉ dom(χ.metadata.frames) ∧ + 𝔽 ∉ dom(χ.frames) ∧ |F.params| = |y*| = |{y*}| ∧ ∀i ∈ 0 .. |y*| . typetest(χ, φ(yᵢ), F.paramsᵢ.type) @@ -348,28 +340,28 @@ x ∉ φ₀ F = P.funcs(𝕗) φ₁ = newframe(χ, φ₀, F, x, y*, stmt*) --- [call static] -χ, σ;φ₀, bind x (call 𝕗 y*);stmt* ⇝ χ∪φ₁.id, σ;φ₀;φ₁\{y*}, F.body +χ, σ;φ₀, bind x (call 𝕗 y*);stmt* ⇝ χ∪(φ₁.id), σ;φ₀\{y*};φ₁, F.body x ∉ φ₀ τ = typeof(χ, φ(z₀)) F = P.funcs(P.types(τ).methods(y)) φ₁ = newframe(χ, φ₀, F, x, z*, stmt*) --- [call dynamic] -χ, σ;φ₀, bind x (call y z*);stmt* ⇝ χ∪φ₁.id, σ;φ₀;φ₁\{z*}, F.body +χ, σ;φ₀, bind x (call y z*);stmt* ⇝ χ∪(φ₁.id), σ;φ₀\{z*};φ₁, F.body ``` ## Return This checks that: -* only the return value remains in the frame, to ensure proper reference counting, and -* that the reference count of everything allocated on this frame has dropped to zero, which ensures that no dangling references are returned. +* Only the return value remains in the frame, to ensure proper reference counting. +* No objects that will survive the frame reference any object allocated on the frame, to prevent dangling references. ```rs -|dom(φ₁)| = 1 -ιs = {ι | χ.metadata(ι).location = φ₁} -∀ι ∈ ιs . χ.metadata(ι).rc = 0 +dom(φ₁.vars) = {x} +ιs = {ι | χ.metadata(ι).location = φ₁.id} +∀ι ∈ χ . ι ∉ ιs ⇒ (∀z ∈ dom(χ(ι)) . χ(ι)(z) ∉ ιs) --- [return] χ, σ;φ₀;φ₁, return x;stmt* ⇝ (χ\ιs)\(φ₁.id), σ;φ₀[φ₁.ret↦φ₁(x)], ϕ₁.cont @@ -381,8 +373,8 @@ This checks that: ```rs -x ∉ φ -ι = φ(y) +x ∉ χ(φ) +ι = χ(φ, y) ρ₀ = χ₀.metadata(ι).location ρ₁ ∉ χ₀ ιs = reachable(χ, ι) @@ -391,6 +383,6 @@ x ∉ φ [regions(ρ₁)↦{type: χ₀.regions(ρ₀).type, members: ιs}] [∀ι′ ∈ ιs . χ₀.metadata(ι′).location↦ρ₁] --- [extract] -χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁, σ;φ[x↦ι]\y, stmt* +χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁[φ\y][φ(x)↦ι], σ;φ, stmt* ``` From 9961a91a1488e9a543a22dc69418ff8ad1dca322 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Wed, 5 Feb 2025 11:21:50 -0600 Subject: [PATCH 09/37] opsem immutability --- docs/opsem.md | 79 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 23467e021..cd268b4e9 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -75,7 +75,8 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena { type: TypeId, location: RegionId | FrameId | ObjectId, - rc: ℕ + rc: ℕ, + mutable: Bool } χ ∈ Heap = @@ -108,8 +109,10 @@ x ∈ φ ≝ x ∈ dom(φ.vars) // Heap objects. ι ∈ χ ≝ ι ∈ dom(χ.data) χ(ι) = χ.data(ι) -χ[ι↦(ω, τ, 𝔽)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: 𝔽, rc: 1}] -χ[ι↦(ω, τ, ρ)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: ρ, rc: 1}] +χ[ι↦(ω, τ, 𝔽)] = + χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: 𝔽, rc: 1, mutable: true}] +χ[ι↦(ω, τ, ρ)] = + χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: ρ, rc: 1, mutable: true}] // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) @@ -146,6 +149,8 @@ typetest(χ, v, T) = ```rs // Transitive closure. +reachable(χ, σ) = ∀φ ∈ σ . ⋃{reachable(χ, φ)} +reachable(χ, φ) = ∀x ∈ dom(φ) . ⋃{reachable(χ, φ(x))} reachable(χ, v) = reachable(χ, v, ∅) reachable(χ, p, ιs) = ιs reachable(χ, 𝕣, ιs) = reachable(χ, 𝕣.object, ιs) @@ -155,32 +160,40 @@ reachable(χ, ι, ιs) = where xs = [x | x ∈ dom(χ(ι))] ∧ n = |xs| ∧ - ιs₀ = ιs ∧ - ∀i ∈ 0 .. (n - 1) . ιsᵢ₊₁ = reachable(ιsᵢ, χ(ι)(xsᵢ)) - -// Tree structured regions. -// TODO: stack references? -regiondom(χ, ρ₀, ρ₁) = - ∀ι₀, ι₁ ∈ χ . - (∃z . χ(ι₀)(z) = ι₁) ∧ (χ.metadata(ι₁).location = ρ₁) ⇒ - χ.metadata(ι₀).location ∈ {ρ₀, ρ₁} - -// This checks that it's safe to discharge a region, including: -// * deallocate the region, or -// * freeze the region, or -// * send the region to a behavior. -// TODO: this doesn't allow a region to reference another region -// needs to allow references in to immutable objects. -// TODO: this doesn't require stacks not to reference this region -dischargeable(χ, ρ) = - ∀ι ∈ χ . ι ∉ ιs ⇒ ∀z ∈ dom(χ(ι)) . χ(ι)(z) ∉ ιs ∧ - ∀ι ∈ ιs . reachable(χ, ι) ⊆ ιs + ιs₀ = (ι ∪ ιs) ∧ + ∀i ∈ 0 .. (n - 1) . ιsᵢ₊₁ = reachable(χ, χ(ι)(xsᵢ), ιsᵢ) + +// Mutability. +mut(χ, ι) = χ.metadata(ι).mutable +mut-reachable(χ, σ) = {ι′ | ι′ ∈ reachable(χ, σ) ∧ mut(χ, ι′)} +mut-reachable(χ, φ) = {ι′ | ι′ ∈ reachable(χ, φ) ∧ mut(χ, ι′)} +mut-reachable(χ, ι) = {ι′ | ι′ ∈ reachable(χ, ι) ∧ mut(χ, ι′)} + +// Safe to send or deallocate. +dischargeable(χ, σ, ι) = + ∀ι′ ∈ χ . ι′ ∉ ιs ⇒ (∀z ∈ dom(χ(ι′)) . χ(ι′)(z) ∉ ιs) ∧ + ∀φ ∈ σ . (∀x ∈ dom(φ.vars) . φ(x) ∉ ιs) where - ιs = {ι | χ.metadata(ι).location = ρ} + ιs = mut-reachable(χ, ι) ``` -## Reference counting. +## Well-Formedness + +```rs + +// Deep immutability. +wf_immutable(χ) = + ∀ι ∈ χ . ¬mut(χ, ι) ⇒ (mut-reachable(χ, ι) = ∅) + +// Data-race freedom. +// TODO: apply this with concurrent semantics to expose multiple stacks. +wf_racefree(χ, σs) = + ∀σ₀, σ₁ ∈ σs . σ₀ ≠ σ₁ ⇒ (mut-reachable(σ₀) ∩ mut-reachable(σ₁) = ∅) + +``` + +## Reference Counting Reference counting is a no-op unless the object is in a `RegionRC`. @@ -292,6 +305,7 @@ v = χ(ι)(w) x ∉ ϕ ϕ(y) = {object: ι, field: w} w ∈ dom(P.types(typeof(χ, ι)).fields) +χ.metadata(ι).mutable = true v = χ(ι)(w) --- [bind store] χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ[ι↦χ(ι)[w↦φ(z)]], σ;ϕ[x↦v]\z, stmt* @@ -367,6 +381,19 @@ dom(φ₁.vars) = {x} ``` +## Freeze + +```rs + +x ∉ φ +ι = φ(y) +ιs = mut-reachable(χ, ι) +χ₁ = χ₀[∀ι′ ∈ ιs . metadata(ι′)[mutable↦false]] +--- [freeze] +χ₀, σ;φ, bind x (freeze y);stmt* ⇝ χ₁, σ;φ[x↦ι]\y, stmt* + +``` + ## Extract > Doesn't work. Doesn't allow sub-regions or immutable objects. @@ -374,7 +401,7 @@ dom(φ₁.vars) = {x} ```rs x ∉ χ(φ) -ι = χ(φ, y) +ι = φ(y) ρ₀ = χ₀.metadata(ι).location ρ₁ ∉ χ₀ ιs = reachable(χ, ι) From 7b91a491010e5d1045d4eb8a8255f0428c798e49 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Wed, 5 Feb 2025 11:26:40 -0600 Subject: [PATCH 10/37] opsem refcounting for immutable --- docs/opsem.md | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index cd268b4e9..468da16aa 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -74,9 +74,8 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena Metadata = { type: TypeId, - location: RegionId | FrameId | ObjectId, - rc: ℕ, - mutable: Bool + location: RegionId | FrameId | ObjectId | Immutable, + rc: ℕ } χ ∈ Heap = @@ -109,10 +108,8 @@ x ∈ φ ≝ x ∈ dom(φ.vars) // Heap objects. ι ∈ χ ≝ ι ∈ dom(χ.data) χ(ι) = χ.data(ι) -χ[ι↦(ω, τ, 𝔽)] = - χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: 𝔽, rc: 1, mutable: true}] -χ[ι↦(ω, τ, ρ)] = - χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: ρ, rc: 1, mutable: true}] +χ[ι↦(ω, τ, 𝔽)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: 𝔽, rc: 1}] +χ[ι↦(ω, τ, ρ)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: ρ, rc: 1}] // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) @@ -164,7 +161,7 @@ reachable(χ, ι, ιs) = ∀i ∈ 0 .. (n - 1) . ιsᵢ₊₁ = reachable(χ, χ(ι)(xsᵢ), ιsᵢ) // Mutability. -mut(χ, ι) = χ.metadata(ι).mutable +mut(χ, ι) = χ.metadata(ι).location ≠ Immutable mut-reachable(χ, σ) = {ι′ | ι′ ∈ reachable(χ, σ) ∧ mut(χ, ι′)} mut-reachable(χ, φ) = {ι′ | ι′ ∈ reachable(χ, φ) ∧ mut(χ, ι′)} mut-reachable(χ, ι) = {ι′ | ι′ ∈ reachable(χ, ι) ∧ mut(χ, ι′)} @@ -195,30 +192,30 @@ wf_racefree(χ, σs) = ## Reference Counting -Reference counting is a no-op unless the object is in a `RegionRC`. +Reference counting is a no-op unless the object is in a `RegionRC` or is `Immutable`. ```rs +enable-rc(χ, ι) = + (χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC) ∨ + χ.metadata(ι).location = Immutable + inc(χ, p) = χ inc(χ, 𝕣) = dec(χ, 𝕣.object) inc(χ, ι) = inc(χ, ι′) if χ.metadata(ι).location = ι′ - χ[metadata(ι)[rc↦metadata(ι).rc + 1]] if - χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC + χ[metadata(ι)[rc↦metadata(ι).rc + 1]] if enable-rc(χ, ι) χ otherwise dec(χ, p) = χ dec(χ, 𝕣) = dec(χ, 𝕣.object) dec(χ, ι) = dec(χ, ι′) if χ.metadata(ι).location = ι′ - free(χ, ρ, ι) if - χ.metadata(ι).rc = 1 ∧ - χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC - χ[metadata(ι)[rc↦metata(ι).rc - 1]] if - χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC + free(χ, ι) if enable-rc(χ, ι) ∧ (χ.metadata(ι).rc = 1) + χ[metadata(ι)[rc↦metata(ι).rc - 1]] if enable-rc(χ, ι) χ otherwise -free(χ, ρ, ι) = χₙ\ι where +free(χ, ι) = χₙ\ι where xs = [x | x ∈ dom(χ(ι))] ∧ n = |xs| ∧ χ₀ = χ ∧ @@ -305,7 +302,7 @@ v = χ(ι)(w) x ∉ ϕ ϕ(y) = {object: ι, field: w} w ∈ dom(P.types(typeof(χ, ι)).fields) -χ.metadata(ι).mutable = true +mut(χ, ι) v = χ(ι)(w) --- [bind store] χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ[ι↦χ(ι)[w↦φ(z)]], σ;ϕ[x↦v]\z, stmt* @@ -388,7 +385,7 @@ dom(φ₁.vars) = {x} x ∉ φ ι = φ(y) ιs = mut-reachable(χ, ι) -χ₁ = χ₀[∀ι′ ∈ ιs . metadata(ι′)[mutable↦false]] +χ₁ = χ₀[∀ι′ ∈ ιs . metadata(ι′)[location↦Immutable]] --- [freeze] χ₀, σ;φ, bind x (freeze y);stmt* ⇝ χ₁, σ;φ[x↦ι]\y, stmt* From ce94e65ea156f854a0598d944403d069cf4a852e Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Wed, 5 Feb 2025 15:04:12 -0600 Subject: [PATCH 11/37] opsem wf stack local --- docs/opsem.md | 54 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 468da16aa..589b89e5c 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -1,12 +1,33 @@ # Operational Semantics Still to do: -* Region safety. * Region entry points. -* Region deallocation. -* Immutability. - * SCCs? No, keep it abstract. Use same cycle detection algo as RC/GC. + * Track a region's parent region? + * Prevent references from other than the stack or parent region? + * Track external (stack or parent region) RC to the region? + * Non-RC parent regions? + * RC inc child region on load. + * On store: + * Old value has RC moved from the parent region to the stack. + * If we allow more than one parent to child reference, how do we know when to clear the parent region? + * Could track stack RC separately from parent RC. + * New value has RC moved from the stack to the parent region. + * Need to `dec` child regions on `free`. +* Region send and free. + * When external RC is 0? + * Could do freeze this way too? Only needed with a static type checker. + * If external region RC is limited to 1, we can check send-ability statically. + * But we can also delay send or free until RC is 0. +* Freeze. + * For a static type checker, how do we know there are no `mut` aliases? + * Could error if external RC isn't 0 or 1? + * Or delay? + * Extract and then freeze for sub-graphs. The extract could fail. * Undecided region. + * Is this a per-stack region, where we extract from it? +* Efficient frame teardown. + * Disallow heap and "earlier frame" objects from referencing frame objects? + * All function arguments and return values are "heap or earlier frame". * Behaviors and cowns. * Embedded object fields? * Arrays? Or model them as objects? @@ -141,11 +162,12 @@ typetest(χ, v, T) = ``` -## Reachability and Safety +## Reachability ```rs // Transitive closure. +reachable(χ, σs) = ∀σ ∈ σs . ⋃{reachable(χ, σ)} reachable(χ, σ) = ∀φ ∈ σ . ⋃{reachable(χ, φ)} reachable(χ, φ) = ∀x ∈ dom(φ) . ⋃{reachable(χ, φ(x))} reachable(χ, v) = reachable(χ, v, ∅) @@ -161,18 +183,13 @@ reachable(χ, ι, ιs) = ∀i ∈ 0 .. (n - 1) . ιsᵢ₊₁ = reachable(χ, χ(ι)(xsᵢ), ιsᵢ) // Mutability. +mut(χ, p) = false +mut(χ, 𝕣) = mut(χ, 𝕣.object) mut(χ, ι) = χ.metadata(ι).location ≠ Immutable mut-reachable(χ, σ) = {ι′ | ι′ ∈ reachable(χ, σ) ∧ mut(χ, ι′)} mut-reachable(χ, φ) = {ι′ | ι′ ∈ reachable(χ, φ) ∧ mut(χ, ι′)} mut-reachable(χ, ι) = {ι′ | ι′ ∈ reachable(χ, ι) ∧ mut(χ, ι′)} -// Safe to send or deallocate. -dischargeable(χ, σ, ι) = - ∀ι′ ∈ χ . ι′ ∉ ιs ⇒ (∀z ∈ dom(χ(ι′)) . χ(ι′)(z) ∉ ιs) ∧ - ∀φ ∈ σ . (∀x ∈ dom(φ.vars) . φ(x) ∉ ιs) - where - ιs = mut-reachable(χ, ι) - ``` ## Well-Formedness @@ -184,10 +201,15 @@ wf_immutable(χ) = ∀ι ∈ χ . ¬mut(χ, ι) ⇒ (mut-reachable(χ, ι) = ∅) // Data-race freedom. -// TODO: apply this with concurrent semantics to expose multiple stacks. wf_racefree(χ, σs) = ∀σ₀, σ₁ ∈ σs . σ₀ ≠ σ₁ ⇒ (mut-reachable(σ₀) ∩ mut-reachable(σ₁) = ∅) +// Stack allocations are reachable only from that stack. +wf_stacklocal(χ, σs) = + ∀σ₀, σ₁ ∈ σs . ∀φ ∈ σ₀ . (reachable(χ, σ₁) ∩ ιs = ∅) + where + ιs = {ι | χ.metadata(ι).location = φ.id} + ``` ## Reference Counting @@ -225,6 +247,8 @@ free(χ, ι) = χₙ\ι where ## New +For an "address-taken" local variable, i.e. a `var` as opposed to a `let`, allocate an object in the frame with a single field to hold the value. + ```rs newobject(χ, τ, (y, z)*) = @@ -368,6 +392,8 @@ This checks that: * Only the return value remains in the frame, to ensure proper reference counting. * No objects that will survive the frame reference any object allocated on the frame, to prevent dangling references. +> TODO: how to make this efficient? + ```rs dom(φ₁.vars) = {x} @@ -393,7 +419,7 @@ x ∉ φ ## Extract -> Doesn't work. Doesn't allow sub-regions or immutable objects. +> TODO: Doesn't work. Doesn't allow sub-regions or immutable objects. ```rs From 626867a2fb0e7c05998476e4d059b36f84980597 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Thu, 6 Feb 2025 09:35:34 -0600 Subject: [PATCH 12/37] opsem minor edits --- docs/opsem.md | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 589b89e5c..3ddb0947a 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -180,7 +180,7 @@ reachable(χ, ι, ιs) = xs = [x | x ∈ dom(χ(ι))] ∧ n = |xs| ∧ ιs₀ = (ι ∪ ιs) ∧ - ∀i ∈ 0 .. (n - 1) . ιsᵢ₊₁ = reachable(χ, χ(ι)(xsᵢ), ιsᵢ) + ∀i ∈ 1 .. n . ιsᵢ = reachable(χ, χ(ι)(xsᵢ), ιsᵢ₋₁) // Mutability. mut(χ, p) = false @@ -241,7 +241,7 @@ free(χ, ι) = χₙ\ι where xs = [x | x ∈ dom(χ(ι))] ∧ n = |xs| ∧ χ₀ = χ ∧ - ∀i ∈ 0 .. (n - 1) . χᵢ₊₁ = dec(χᵢ, χ(ι)(xsᵢ)) + ∀i ∈ 1 .. n . χᵢ₊₁ = dec(χᵢ, χ(ι)(xsᵢ)) ``` @@ -307,6 +307,8 @@ x ∉ ϕ The `load` statement is the only operation other than `dup` or `drop` that can change the reference count of an object. +The containing object in `load` and `store` is not consumed. + ```rs x ∉ ϕ @@ -335,6 +337,8 @@ v = χ(ι)(w) ## Type Test +The local variable being type-tested is not consumed. + ```rs x ∉ ϕ @@ -346,6 +350,8 @@ v = typetest(χ, φ(y), T) ## Conditional +The condition is not consumed. + ```rs φ(x) = true @@ -365,11 +371,11 @@ All arguments are consumed. To keep them, `dup` them first. As such, an identifi ```rs newframe(χ, ϕ, F, x, y*, stmt*) = - {id: 𝔽, vars: F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 0 .. |y*|, ret: x, cont: stmt*} + {id: 𝔽, vars: {F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 1 .. |y*|}, ret: x, cont: stmt*} where 𝔽 ∉ dom(χ.frames) ∧ |F.params| = |y*| = |{y*}| ∧ - ∀i ∈ 0 .. |y*| . typetest(χ, φ(yᵢ), F.paramsᵢ.type) + ∀i ∈ 1 .. |y*| . typetest(χ, φ(yᵢ), F.paramsᵢ.type) x ∉ φ₀ F = P.funcs(𝕗) @@ -378,11 +384,11 @@ F = P.funcs(𝕗) χ, σ;φ₀, bind x (call 𝕗 y*);stmt* ⇝ χ∪(φ₁.id), σ;φ₀\{y*};φ₁, F.body x ∉ φ₀ -τ = typeof(χ, φ(z₀)) -F = P.funcs(P.types(τ).methods(y)) -φ₁ = newframe(χ, φ₀, F, x, z*, stmt*) +τ = typeof(χ, φ(y₀)) +F = P.funcs(P.types(τ).methods(w)) +φ₁ = newframe(χ, φ₀, F, x, y*, stmt*) --- [call dynamic] -χ, σ;φ₀, bind x (call y z*);stmt* ⇝ χ∪(φ₁.id), σ;φ₀\{z*};φ₁, F.body +χ, σ;φ₀, bind x (call w y*);stmt* ⇝ χ∪(φ₁.id), σ;φ₀\{y*};φ₁, F.body ``` @@ -406,13 +412,16 @@ dom(φ₁.vars) = {x} ## Freeze +Dynamic freeze is suitable for a dynamic type checker. A static type checker will have incorrect mutability information if there are mutable aliases. + ```rs x ∉ φ ι = φ(y) ιs = mut-reachable(χ, ι) +∀ι′ ∈ ιs . χ.metadata(ι′).location ∉ FrameId χ₁ = χ₀[∀ι′ ∈ ιs . metadata(ι′)[location↦Immutable]] ---- [freeze] +--- [dynamic freeze] χ₀, σ;φ, bind x (freeze y);stmt* ⇝ χ₁, σ;φ[x↦ι]\y, stmt* ``` @@ -423,7 +432,7 @@ x ∉ φ ```rs -x ∉ χ(φ) +x ∉ φ ι = φ(y) ρ₀ = χ₀.metadata(ι).location ρ₁ ∉ χ₀ From 3568ae8574177e2bb780ab41a620d66c8cf326d4 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Thu, 6 Feb 2025 14:27:52 -0600 Subject: [PATCH 13/37] opsem heap and stack rc for regions --- docs/opsem.md | 126 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 91 insertions(+), 35 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 3ddb0947a..427350754 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -1,18 +1,7 @@ # Operational Semantics Still to do: -* Region entry points. - * Track a region's parent region? - * Prevent references from other than the stack or parent region? - * Track external (stack or parent region) RC to the region? - * Non-RC parent regions? - * RC inc child region on load. - * On store: - * Old value has RC moved from the parent region to the stack. - * If we allow more than one parent to child reference, how do we know when to clear the parent region? - * Could track stack RC separately from parent RC. - * New value has RC moved from the stack to the parent region. - * Need to `dec` child regions on `free`. +* Do we need to prevent cyclic regions? * Region send and free. * When external RC is 0? * Could do freeze this way too? Only needed with a static type checker. @@ -23,11 +12,19 @@ Still to do: * Could error if external RC isn't 0 or 1? * Or delay? * Extract and then freeze for sub-graphs. The extract could fail. +* Merge. + * External RC from the destination is removed. + * Other external RC is added to the destination. + * If tracking on a per-parent basis, this is easy. * Undecided region. * Is this a per-stack region, where we extract from it? + * Could be implemented as "allocate in many regions" and "merge often". + * How to distinguish a merge from a subregion reference? Only need to do this at the language level, the semantics can be explicit. * Efficient frame teardown. * Disallow heap and "earlier frame" objects from referencing frame objects? * All function arguments and return values are "heap or earlier frame". + * Treat each frame as a region, external RC is 0? + * Each frame could be an Arena, disallowing extract, not needing ref counting. * Behaviors and cowns. * Embedded object fields? * Arrays? Or model them as objects? @@ -88,7 +85,9 @@ v ∈ Value = ObjectId | Primitive | Reference R ∈ RegionType = RegionRC | RegionGC | RegionArena Region = { - type: RegionType + type: RegionType, + heap_rc: RegionId ↦ ℕ, + stack_rc: ℕ } // An object located in another object is an embedded field. @@ -182,10 +181,17 @@ reachable(χ, ι, ιs) = ιs₀ = (ι ∪ ιs) ∧ ∀i ∈ 1 .. n . ιsᵢ = reachable(χ, χ(ι)(xsᵢ), ιsᵢ₋₁) +// Region. +loc(χ, p) = Immutable +loc(χ, 𝕣) = loc(χ, 𝕣.object) +loc(χ, ι) = χ.metadata(ι).location + +same_loc(χ, v₀, v₁) = (loc(χ, v₀) = loc(χ, v₁)) + // Mutability. mut(χ, p) = false mut(χ, 𝕣) = mut(χ, 𝕣.object) -mut(χ, ι) = χ.metadata(ι).location ≠ Immutable +mut(χ, ι) = loc(χ, ι) ≠ Immutable mut-reachable(χ, σ) = {ι′ | ι′ ∈ reachable(χ, σ) ∧ mut(χ, ι′)} mut-reachable(χ, φ) = {ι′ | ι′ ∈ reachable(χ, φ) ∧ mut(χ, ι′)} mut-reachable(χ, ι) = {ι′ | ι′ ∈ reachable(χ, ι) ∧ mut(χ, ι′)} @@ -208,7 +214,7 @@ wf_racefree(χ, σs) = wf_stacklocal(χ, σs) = ∀σ₀, σ₁ ∈ σs . ∀φ ∈ σ₀ . (reachable(χ, σ₁) ∩ ιs = ∅) where - ιs = {ι | χ.metadata(ι).location = φ.id} + ιs = {ι | loc(χ, ι) = φ.id} ``` @@ -218,21 +224,58 @@ Reference counting is a no-op unless the object is in a `RegionRC` or is `Immuta ```rs +region_stack_inc(χ, p) = χ +region_stack_inc(χ, 𝕣) = region_stack_inc(χ, 𝕣.object) +region_stack_inc(χ, ι) = + χ if loc(χ, ι) = Immutable + χ[regions(ρ)[stack_rc↦(rc + 1)]] otherwise + where + loc(χ, ι) = ρ ∧ + χ.regions(ρ).stack_rc = rc + +region_stack_dec(χ, p) = χ +region_stack_dec(χ, 𝕣) = region_stack_dec(χ, 𝕣.object) +region_stack_dec(χ, ι) = + χ if loc(χ, ι) = Immutable + χ[regions(ρ)[stack_rc↦(rc - 1)]] otherwise + where + loc(χ, ι) = ρ ∧ + χ.regions(ρ).stack_rc = rc + +region_heap_inc(χ, ι, p) = χ +region_heap_inc(χ, ι, 𝕣) = region_heap_inc(χ, ι, 𝕣.object) +region_heap_inc(χ, ι, ι′) = + χ if loc(χ, ι′) = Immutable + χ if same_loc(χ, ι, ι′) + χ[regions(ρ′)[heap_rc(ρ)↦(rc + 1)]] otherwise + where + (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ + χ.regions(ρ′).heap_rc(ρ) = rc + +region_heap_dec(χ, ι, p) = χ +region_heap_dec(χ, ι, 𝕣) = region_heap_dec(χ, ι, 𝕣.object) +region_heap_dec(χ, ι, ι′) = + χ if loc(χ, ι′) = Immutable + χ if same_loc(χ, ι, ι′) + χ[regions(ρ′)[heap_rc(ρ)↦(rc - 1)]] otherwise + where + (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ + χ.regions(ρ′).heap_rc(ρ) = rc + enable-rc(χ, ι) = - (χ.metadata(ι).location = ρ ∧ ρ.type = RegionRC) ∨ - χ.metadata(ι).location = Immutable + (loc(χ, ι) = ρ ∧ ρ.type = RegionRC) ∨ (loc(χ, ι) = Immutable) inc(χ, p) = χ inc(χ, 𝕣) = dec(χ, 𝕣.object) inc(χ, ι) = - inc(χ, ι′) if χ.metadata(ι).location = ι′ + inc(χ, ι′) if loc(χ, ι) = ι′ χ[metadata(ι)[rc↦metadata(ι).rc + 1]] if enable-rc(χ, ι) χ otherwise dec(χ, p) = χ dec(χ, 𝕣) = dec(χ, 𝕣.object) dec(χ, ι) = - dec(χ, ι′) if χ.metadata(ι).location = ι′ + dec(χ, ι′) if loc(χ, ι) = ι′ free(χ, ι) if enable-rc(χ, ι) ∧ (χ.metadata(ι).rc = 1) χ[metadata(ι)[rc↦metata(ι).rc - 1]] if enable-rc(χ, ι) χ otherwise @@ -241,7 +284,7 @@ free(χ, ι) = χₙ\ι where xs = [x | x ∈ dom(χ(ι))] ∧ n = |xs| ∧ χ₀ = χ ∧ - ∀i ∈ 1 .. n . χᵢ₊₁ = dec(χᵢ, χ(ι)(xsᵢ)) + ∀i ∈ 1 .. n . (ιᵢ = χ(ι)(xsᵢ)) ∧ χᵢ₊₁ = dec(region_heap_dec(χᵢ, ι, ιᵢ), ιᵢ) ``` @@ -271,11 +314,11 @@ zs = {z | z ∈ (y, z)*} ∧ |zs| = |(y, z)*| x ∉ φ ι ∉ χ -ρ = χ.metadata(φ(y)).location +ρ = loc(χ, φ(w)) zs = {z | z ∈ (y, z)*} ∧ |zs| = |(y, z)*| ω = newobject(χ, τ, (y, z)*) --- [new heap] -χ, σ;φ, bind x (new y τ (y, z)*);stmt* ⇝ χ[ι↦(ω, τ, ρ)], σ;φ[x↦ι]\zs, stmt* +χ, σ;φ, bind x (new w τ (y, z)*);stmt* ⇝ χ[ι↦(ω, τ, ρ)], σ;φ[x↦ι]\zs, stmt* x ∉ φ ι ∉ χ @@ -293,13 +336,18 @@ Local variables are consumed on use. To keep them, `dup` them first. ```rs +φ(x) = v +χ₁ = region_stack_dec(χ₀, v) +χ₂ = dec(χ₁, v) --- [drop] -χ, σ;φ, drop x;stmt* ⇝ dec(χ, φ(x)), σ;ϕ\x, stmt* +χ₀, σ;φ, drop x;stmt* ⇝ χ₂, σ;ϕ\x, stmt* x ∉ ϕ ϕ(y) = v +χ₁ = region_stack_inc(χ₀, v) +χ₂ = inc(χ₁, v) --- [dup] -χ, σ;φ, bind x (dup y);stmt* ⇝ inc(χ, v), σ;φ[x↦v], stmt* +χ₀, σ;φ, bind x (dup y);stmt* ⇝ χ₂, σ;φ[x↦v], stmt* ``` @@ -320,18 +368,26 @@ w ∈ dom(P.types(typeof(χ, ι)).fields) x ∉ ϕ ϕ(y) = {object: ι, field: w} -w ∈ dom(P.types(typeof(χ, ι)).fields) -v = χ(ι)(w) +w ∈ dom(P.types(typeof(χ₀, ι)).fields) +v = χ₀(ι)(w) +χ₁ = region_stack_inc(χ₀, v) +χ₂ = inc(χ₁, v) --- [bind load] -χ, σ;ϕ, bind x (load y);stmt* ⇝ inc(χ, v), σ;ϕ[x↦v], stmt* +χ₀, σ;ϕ, bind x (load y);stmt* ⇝ χ₂, σ;ϕ[x↦v], stmt* x ∉ ϕ ϕ(y) = {object: ι, field: w} -w ∈ dom(P.types(typeof(χ, ι)).fields) -mut(χ, ι) -v = χ(ι)(w) +w ∈ dom(P.types(typeof(χ₀, ι)).fields) +mut(χ₀, ι) +v₀ = χ₀(ι)(w) +v₁ = φ(z) +ω = χ₀(ι)[w↦v₁] +χ₁ = region_stack_inc(χ₀, v₀) +χ₂ = region_heap_inc(χ₁, ι, v₁) +χ₃ = region_stack_dec(χ₂, v₁) +χ₄ = region_heap_dec(χ₃, ι, v₀) --- [bind store] -χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ[ι↦χ(ι)[w↦φ(z)]], σ;ϕ[x↦v]\z, stmt* +χ₀, σ;ϕ, bind x (store y z);stmt* ⇝ χ₄[ι↦ω], σ;ϕ[x↦v₀]\z, stmt* ``` @@ -403,7 +459,7 @@ This checks that: ```rs dom(φ₁.vars) = {x} -ιs = {ι | χ.metadata(ι).location = φ₁.id} +ιs = {ι | loc(χ, ι) = φ₁.id} ∀ι ∈ χ . ι ∉ ιs ⇒ (∀z ∈ dom(χ(ι)) . χ(ι)(z) ∉ ιs) --- [return] χ, σ;φ₀;φ₁, return x;stmt* ⇝ (χ\ιs)\(φ₁.id), σ;φ₀[φ₁.ret↦φ₁(x)], ϕ₁.cont @@ -419,7 +475,7 @@ Dynamic freeze is suitable for a dynamic type checker. A static type checker wil x ∉ φ ι = φ(y) ιs = mut-reachable(χ, ι) -∀ι′ ∈ ιs . χ.metadata(ι′).location ∉ FrameId +∀ι′ ∈ ιs . loc(χ, ι′) ∉ FrameId χ₁ = χ₀[∀ι′ ∈ ιs . metadata(ι′)[location↦Immutable]] --- [dynamic freeze] χ₀, σ;φ, bind x (freeze y);stmt* ⇝ χ₁, σ;φ[x↦ι]\y, stmt* @@ -434,13 +490,13 @@ x ∉ φ x ∉ φ ι = φ(y) -ρ₀ = χ₀.metadata(ι).location +ρ₀ = loc(χ₀, ι) ρ₁ ∉ χ₀ ιs = reachable(χ, ι) ∀ι′ ∈ χ₀.regions(ρ₀).members . (ι′ ∉ ιs ⇒ ∀z ∈ dom(χ₀(ι′)) . χ₀(ι′)(z) ∉ ιs) χ₁ = χ₀[regions(ρ₀).members\ιs] [regions(ρ₁)↦{type: χ₀.regions(ρ₀).type, members: ιs}] - [∀ι′ ∈ ιs . χ₀.metadata(ι′).location↦ρ₁] + [∀ι′ ∈ ιs . metadata(ι′).location↦ρ₁] --- [extract] χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁[φ\y][φ(x)↦ι], σ;φ, stmt* From 5d121ce894e2d0c646c33f66130b26df8d205e4d Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Mon, 24 Mar 2025 16:11:58 -0500 Subject: [PATCH 14/37] track parent region rather than heap_rc --- docs/opsem.md | 294 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 215 insertions(+), 79 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 427350754..42049f6e8 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -1,36 +1,47 @@ # Operational Semantics Still to do: -* Do we need to prevent cyclic regions? -* Region send and free. - * When external RC is 0? - * Could do freeze this way too? Only needed with a static type checker. - * If external region RC is limited to 1, we can check send-ability statically. - * But we can also delay send or free until RC is 0. -* Freeze. - * For a static type checker, how do we know there are no `mut` aliases? - * Could error if external RC isn't 0 or 1? - * Or delay? - * Extract and then freeze for sub-graphs. The extract could fail. -* Merge. - * External RC from the destination is removed. - * Other external RC is added to the destination. - * If tracking on a per-parent basis, this is easy. +* Add destructors explicitly in the semantics. +* Extract. +* Discharge (send/free/freeze?) when stack RC for the region and all children (recursively) is 0. + * Could optimize by tracking the count of "busy" child regions. + * Can we still have an Arena per frame? + * Frame Arenas can't be discharged anyway. +* Efficient frame teardown. + * Each frame could be an Arena. + * References from a frame Arena to another region (including another frame Arena) would be tracked as stack RC. + * A frame Arena must reach external RC 0 at `return`. + * Prevent heap or "older frame" regions from referencing a frame Arena? + * Any region other than the current frame Arena can't reference the current frame Arena? +* How are Arenas different from uncounted regions? + * How should they treat changing region type? + * How should they treat merging, freezing, extract? * Undecided region. * Is this a per-stack region, where we extract from it? * Could be implemented as "allocate in many regions" and "merge often". - * How to distinguish a merge from a subregion reference? Only need to do this at the language level, the semantics can be explicit. -* Efficient frame teardown. - * Disallow heap and "earlier frame" objects from referencing frame objects? - * All function arguments and return values are "heap or earlier frame". - * Treat each frame as a region, external RC is 0? - * Each frame could be an Arena, disallowing extract, not needing ref counting. + * How to distinguish a merge from a subregion reference? Only need to do this at the language level, the semantics can be explicit? * Behaviors and cowns. * Embedded object fields? * Arrays? Or model them as objects? * GC or RC cycle detection. * Non-local returns. +Dynamic failures: +* `store`: + * `w` is not a field of the target. + * `store` to an immutable object. + * `store` a region that already has a parent. + * `store` a region that would create a cycle. + * TODO: frame references? +* `call dynamic`: + * `w` is not a method of the target. +* `merge`: + * Trying to merge a value that isn't an object in a region. + * Trying to merge a region that is a child of a region other than the destination region. + * Trying to merge a region that would create a cycle. +* `freeze`: + * Trying to freeze a value that is not an object in a region. + ## Shape ```rs @@ -84,9 +95,11 @@ v ∈ Value = ObjectId | Primitive | Reference σ ∈ Stack = Frame* R ∈ RegionType = RegionRC | RegionGC | RegionArena + + // The size of the parents set will be at most 1. Region = { type: RegionType, - heap_rc: RegionId ↦ ℕ, + parents: 𝒫(RegionId), stack_rc: ℕ } @@ -134,6 +147,7 @@ x ∈ φ ≝ x ∈ dom(φ.vars) // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) χ[ρ↦R] = χ[regions(ρ)↦(R, ∅)] +χ\ρ = χ[regions\ρ] // Deallocation. χ\ι = χ\{ι} @@ -184,7 +198,9 @@ reachable(χ, ι, ιs) = // Region. loc(χ, p) = Immutable loc(χ, 𝕣) = loc(χ, 𝕣.object) -loc(χ, ι) = χ.metadata(ι).location +loc(χ, ι) = + loc(χ, ι′) if χ.metadata(ι).location = ι′ + χ.metadata(ι).location otherwise same_loc(χ, v₀, v₁) = (loc(χ, v₀) = loc(χ, v₁)) @@ -196,6 +212,32 @@ mut-reachable(χ, σ) = {ι′ | ι′ ∈ reachable(χ, σ) ∧ mut(χ, ι′)} mut-reachable(χ, φ) = {ι′ | ι′ ∈ reachable(χ, φ) ∧ mut(χ, ι′)} mut-reachable(χ, ι) = {ι′ | ι′ ∈ reachable(χ, ι) ∧ mut(χ, ι′)} +// Region parents. +parents(χ, ρ) = χ.regions(ρ).parents + +// Check if ρ₀ is an ancestor of ρ₁. +is_ancestor(χ, ρ₀, ρ₁) = + ρ₀ ∈ parents(χ, ρ₁) ∨ + (∀ρ ∈ parents(χ, ρ₁) . is_ancestor(χ, ρ₀, ρ)) + +``` + +## Safety + +This enforces a tree-shaped region graph, with a single reference from parent to child. + +```rs + +safe_store(χ, ι, v) = + false if loc(χ, ι) = Immutable + true if loc(χ, v) = Immutable + // TODO: more precise frame references? + true if loc(χ, ι) = 𝔽 + true if same_loc(χ, ι, v) + true if (ρ₀ = loc(χ, ι)) ∧ (ρ₁ = loc(χ, v)) ∧ + (parents(χ, ρ₁) = ∅) ∧ ¬is_ancestor(χ, ρ₁, ρ₀) + false otherwise + ``` ## Well-Formedness @@ -216,6 +258,13 @@ wf_stacklocal(χ, σs) = where ιs = {ι | loc(χ, ι) = φ.id} +// The region graph is a tree. +// TODO: examine all references +wf_regiontree(χ) = + ∀ρ₀, ρ₁ ∈ χ . + (|parents(χ, ρ₀)| ≤ 1) ∧ + (ρ₀ ∈ parents(χ, ρ₁) ⇒ (ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ, ρ₁, ρ₀)) + ``` ## Reference Counting @@ -227,40 +276,30 @@ Reference counting is a no-op unless the object is in a `RegionRC` or is `Immuta region_stack_inc(χ, p) = χ region_stack_inc(χ, 𝕣) = region_stack_inc(χ, 𝕣.object) region_stack_inc(χ, ι) = - χ if loc(χ, ι) = Immutable - χ[regions(ρ)[stack_rc↦(rc + 1)]] otherwise - where - loc(χ, ι) = ρ ∧ - χ.regions(ρ).stack_rc = rc + χ[regions(ρ)[stack_rc↦(stack_rc + 1)]] if loc(χ, ι) = ρ + χ otherwise region_stack_dec(χ, p) = χ region_stack_dec(χ, 𝕣) = region_stack_dec(χ, 𝕣.object) region_stack_dec(χ, ι) = - χ if loc(χ, ι) = Immutable - χ[regions(ρ)[stack_rc↦(rc - 1)]] otherwise - where - loc(χ, ι) = ρ ∧ - χ.regions(ρ).stack_rc = rc - -region_heap_inc(χ, ι, p) = χ -region_heap_inc(χ, ι, 𝕣) = region_heap_inc(χ, ι, 𝕣.object) -region_heap_inc(χ, ι, ι′) = - χ if loc(χ, ι′) = Immutable - χ if same_loc(χ, ι, ι′) - χ[regions(ρ′)[heap_rc(ρ)↦(rc + 1)]] otherwise - where - (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ - χ.regions(ρ′).heap_rc(ρ) = rc - -region_heap_dec(χ, ι, p) = χ -region_heap_dec(χ, ι, 𝕣) = region_heap_dec(χ, ι, 𝕣.object) -region_heap_dec(χ, ι, ι′) = - χ if loc(χ, ι′) = Immutable - χ if same_loc(χ, ι, ι′) - χ[regions(ρ′)[heap_rc(ρ)↦(rc - 1)]] otherwise - where - (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ - χ.regions(ρ′).heap_rc(ρ) = rc + χ[regions(ρ)[stack_rc↦(stack_rc - 1)]] if loc(χ, ι) = ρ + χ otherwise + +// TODO: what if ι is in a frame? +region_add_parent(χ, ι, p) = χ +region_add_parent(χ, ι, 𝕣) = region_add_parent(χ, ι, 𝕣.object) +region_add_parent(χ, ι, ι′) = + χ[regions(ρ)[parents ∪ {ρ′})]] if + (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) + χ otherwise + +// TODO: what if ι is in a frame? +region_remove_parent(χ, ι, p) = χ +region_remove_parent(χ, ι, 𝕣) = region_remove_parent(χ, ι, 𝕣.object) +region_remove_parent(χ, ι, ι′) = + χ[regions(ρ)[parents \ {ρ′})]] if + (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) + χ otherwise enable-rc(χ, ι) = (loc(χ, ι) = ρ ∧ ρ.type = RegionRC) ∨ (loc(χ, ι) = Immutable) @@ -268,23 +307,85 @@ enable-rc(χ, ι) = inc(χ, p) = χ inc(χ, 𝕣) = dec(χ, 𝕣.object) inc(χ, ι) = - inc(χ, ι′) if loc(χ, ι) = ι′ + inc(χ, ι′) if χ.metadata(ι).location = ι′ χ[metadata(ι)[rc↦metadata(ι).rc + 1]] if enable-rc(χ, ι) χ otherwise dec(χ, p) = χ dec(χ, 𝕣) = dec(χ, 𝕣.object) dec(χ, ι) = - dec(χ, ι′) if loc(χ, ι) = ι′ + dec(χ, ι′) if χ.metadata(ι).location = ι′ free(χ, ι) if enable-rc(χ, ι) ∧ (χ.metadata(ι).rc = 1) χ[metadata(ι)[rc↦metata(ι).rc - 1]] if enable-rc(χ, ι) χ otherwise +// TODO: free entire region? free(χ, ι) = χₙ\ι where xs = [x | x ∈ dom(χ(ι))] ∧ n = |xs| ∧ χ₀ = χ ∧ - ∀i ∈ 1 .. n . (ιᵢ = χ(ι)(xsᵢ)) ∧ χᵢ₊₁ = dec(region_heap_dec(χᵢ, ι, ιᵢ), ιᵢ) + ∀i ∈ 1 .. n . + (ιᵢ = χ(ι)(xsᵢ)) ∧ + (χᵢ₊₁ = dec(region_remove_parent(χᵢ, ι, ιᵢ), ιᵢ)) + +``` + +## Region Type Change + +```rs + +region_type_change(χ, σ, ∅, R) = χ +region_type_change(χ, σ, {ρ} ∪ ρs, R) = + region_type_change(χ′, σ, ρs, R) + where + χ′ = region_type_change(χ, σ, ρ, R) + +region_type_change(χ, σ, ρ, R) = + calc_rc(χ′, σ, ρ) if (R′ ≠ RegionRC) ∧ (R = RegionRC) + χ′ otherwise + where + R′ = χ.regions(ρ).type ∧ + χ′ = χ[regions(ρ)[type↦R]] + +calc_rc(χ, σ, ρ) = + χ[∀ι ∈ ιs . metadata(ι).rc↦calc_rc(χ, σ, ι)] + where + ιs = {ι | loc(χ, ι) = ρ} + +calc_rc(χ, σ, ι) = + χ[metadata(ι)[rc↦calc_stack_rc(χ, σ, ι) + calc_heap_rc(χ, ι)]] + +calc_stack_rc(χ, ∅, ι) = 0 +calc_stack_rc(χ, σ;φ, ι) = + |{x | φ(x) = ι}| + calc_stack_rc(χ, σ, ι) + +// The heap RC for the parent region will be zero or one. +calc_heap_rc(χ, ι) = + calc_heap_rc(χ, ρ, ι) + calc_heap_rc(χ, ρ′, ι) + where + (ρ = loc(χ, ι)) ∧ ({ρ′} = parents(χ, ρ)) + +calc_heap_rc(χ, ρ, ι) = + |{(ι′, w) | (ι′ ∈ ιs) ∧ (w ∈ dom(χ(ι′)) ∧ (χ(ι′)(w) = ι))}| + where + ιs = {ι′ | loc(χ, ι′) = ρ} + +``` + +## Garbage Collection + +```rs + +gc_roots(χ, σ, ρ) = + {ι | ι ∈ ιs ∧ ((calc_stack_rc(χ, σ, ι) > 0) ∨ (calc_heap_rc(χ, ρ′, ι) > 0))} + where + {ρ′} = parents(χ, ρ) ∧ + ιs = {ι | loc(χ, ι) = ρ} + +// TODO: +gc(χ, σ, ρ) = + where + ιs = gc_roots(χ, σ, ρ) ∧ ``` @@ -330,18 +431,12 @@ zs = {z | z ∈ (y, z)*} ∧ |zs| = |(y, z)*| ``` -## Drop, Duplicate +## Duplicate, Drop Local variables are consumed on use. To keep them, `dup` them first. ```rs -φ(x) = v -χ₁ = region_stack_dec(χ₀, v) -χ₂ = dec(χ₁, v) ---- [drop] -χ₀, σ;φ, drop x;stmt* ⇝ χ₂, σ;ϕ\x, stmt* - x ∉ ϕ ϕ(y) = v χ₁ = region_stack_inc(χ₀, v) @@ -349,13 +444,17 @@ x ∉ ϕ --- [dup] χ₀, σ;φ, bind x (dup y);stmt* ⇝ χ₂, σ;φ[x↦v], stmt* +φ(x) = v +χ₁ = region_stack_dec(χ₀, v) +χ₂ = dec(χ₁, v) +--- [drop] +χ₀, σ;φ, drop x;stmt* ⇝ χ₂, σ;ϕ\x, stmt* + ``` ## Fields -The `load` statement is the only operation other than `dup` or `drop` that can change the reference count of an object. - -The containing object in `load` and `store` is not consumed. +The `load` statement is the only operation other than `dup` or `drop` that can change the reference count of an object. The containing object in `load` and `store` is not consumed. ```rs @@ -363,7 +462,7 @@ x ∉ ϕ ι = ϕ(y) w ∈ dom(P.types(typeof(χ, ι)).fields) 𝕣 = {object: ι, field: w} ---- [bind field ref] +--- [field ref] χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦𝕣]\y, stmt* x ∉ ϕ @@ -372,21 +471,22 @@ w ∈ dom(P.types(typeof(χ₀, ι)).fields) v = χ₀(ι)(w) χ₁ = region_stack_inc(χ₀, v) χ₂ = inc(χ₁, v) ---- [bind load] +--- [load] χ₀, σ;ϕ, bind x (load y);stmt* ⇝ χ₂, σ;ϕ[x↦v], stmt* +// TODO: what happens if safe_store is false? x ∉ ϕ ϕ(y) = {object: ι, field: w} w ∈ dom(P.types(typeof(χ₀, ι)).fields) -mut(χ₀, ι) v₀ = χ₀(ι)(w) v₁ = φ(z) +safe_store(χ₀, ι, v₁) ω = χ₀(ι)[w↦v₁] -χ₁ = region_stack_inc(χ₀, v₀) -χ₂ = region_heap_inc(χ₁, ι, v₁) +χ₁ = region_remove_parent(χ₀, ι, v₀) +χ₂ = region_stack_inc(χ₁, v₀) χ₃ = region_stack_dec(χ₂, v₁) -χ₄ = region_heap_dec(χ₃, ι, v₀) ---- [bind store] +χ₄ = region_add_parent(χ₃, ι, v₁) +--- [store] χ₀, σ;ϕ, bind x (store y z);stmt* ⇝ χ₄[ι↦ω], σ;ϕ[x↦v₀]\z, stmt* ``` @@ -466,19 +566,55 @@ dom(φ₁.vars) = {x} ``` +## Merge + +This allows merging two regions. The region being merged must either have no parent, or be a child of the region it's being merged into. If there are other stack references to the region being merged, a static type system may have the wrong region information for them. + +> TODO: disallow merging a region that has a parent? Disallow merging a region that has other stack references? + +```rs + +x ∉ φ +loc(χ₀, φ(w)) = ρ₀ +loc(χ₀, φ(y)) = ρ₁ +(ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ₀, ρ₁, ρ₀) ∧ ({ρ₀} ⊇ parents(χ₀, ρ₁)) +ιs = {ι | loc(χ₀, ι) = ρ₁} +χ₁ = χ₀[∀ι ∈ ιs . metadata(ι)[location↦ρ₀]] + [regions(ρ₀)[stack_rc += regions(ρ₁).stack_rc)]] +--- [merge true] +χ₀, σ;φ, bind x (merge w y);stmt* ⇝ χ₁\ρ₁, σ;φ[x↦true], stmt* + +x ∉ φ +(loc(χ, φ(w)) ≠ ρ₀) ∨ +(loc(χ, φ(y)) ≠ ρ₁) ∨ +(ρ₀ = ρ₁) ∨ is_ancestor(χ₀, ρ₁, ρ₀) ∨ ({ρ₀} ̸⊇ parents(χ, ρ₁)) +--- [merge false] +χ, σ;φ, bind x (merge w y);stmt* ⇝ χ, σ;φ[x↦false], stmt* + +``` + ## Freeze -Dynamic freeze is suitable for a dynamic type checker. A static type checker will have incorrect mutability information if there are mutable aliases. +If the region being frozen has a parent, a static type system may have the wrong type for the incoming reference. If there are other stack references to the region being frozen or any of its children, a static type system may have the wrong type for them. + +> TODO: disallow freezing a region that has a parent? Disallow freezing a region that has other stack references? ```rs x ∉ φ ι = φ(y) -ιs = mut-reachable(χ, ι) -∀ι′ ∈ ιs . loc(χ, ι′) ∉ FrameId -χ₁ = χ₀[∀ι′ ∈ ιs . metadata(ι′)[location↦Immutable]] ---- [dynamic freeze] -χ₀, σ;φ, bind x (freeze y);stmt* ⇝ χ₁, σ;φ[x↦ι]\y, stmt* +ρ = loc(χ₀, ι) +ρs = {ρ} ∪ {ρ′ | (ρ′ ∈ χ.regions) ∧ is_ancestor(χ₀, ρ, ρ′)} +χ₁ = region_type_change(χ₀, σ;φ, ρs, RegionRC) +ιs = {ι′ | loc(χ₀, ι′) ∈ ρs} +χ₂ = χ₁[∀ι′ ∈ ιs . metadata(ι′)[location↦Immutable]] +--- [freeze true] +χ₀, σ;φ, bind x (freeze y);stmt* ⇝ χ₂\ρs, σ;φ[x↦true], stmt* + +x ∉ φ +loc(χ, φ(y)) ≠ ρ +--- [freeze false] +χ, σ;φ, bind x (freeze y);stmt* ⇝ χ, σ;φ[x↦false], stmt* ``` From 93324d1f2784e4fb1544f661868c5f56fbdd1ccc Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Thu, 27 Mar 2025 14:20:21 -0500 Subject: [PATCH 15/37] gc and free --- docs/opsem.md | 268 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 181 insertions(+), 87 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 42049f6e8..c73ec3fe8 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -1,9 +1,11 @@ # Operational Semantics Still to do: -* Add destructors explicitly in the semantics. -* Extract. +* WIP: Extract. +* Add finalizers explicitly in the semantics. + * Set the objects being finalized to `Immutable`? * Discharge (send/free/freeze?) when stack RC for the region and all children (recursively) is 0. + * Can free even if child regions have stack RC. * Could optimize by tracking the count of "busy" child regions. * Can we still have an Arena per frame? * Frame Arenas can't be discharged anyway. @@ -16,10 +18,6 @@ Still to do: * How are Arenas different from uncounted regions? * How should they treat changing region type? * How should they treat merging, freezing, extract? -* Undecided region. - * Is this a per-stack region, where we extract from it? - * Could be implemented as "allocate in many regions" and "merge often". - * How to distinguish a merge from a subregion reference? Only need to do this at the language level, the semantics can be explicit? * Behaviors and cowns. * Embedded object fields? * Arrays? Or model them as objects? @@ -143,16 +141,14 @@ x ∈ φ ≝ x ∈ dom(φ.vars) χ(ι) = χ.data(ι) χ[ι↦(ω, τ, 𝔽)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: 𝔽, rc: 1}] χ[ι↦(ω, τ, ρ)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: ρ, rc: 1}] +χ\ι = χ\{ι} +χ\ιs = χ[data = data\ιs, metadata = metadata\ιs] // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) χ[ρ↦R] = χ[regions(ρ)↦(R, ∅)] χ\ρ = χ[regions\ρ] -// Deallocation. -χ\ι = χ\{ι} -χ\ιs = χ[data = data\ιs, metadata = metadata\ιs] - ``` ## Dynamic Types @@ -183,34 +179,32 @@ typetest(χ, v, T) = reachable(χ, σs) = ∀σ ∈ σs . ⋃{reachable(χ, σ)} reachable(χ, σ) = ∀φ ∈ σ . ⋃{reachable(χ, φ)} reachable(χ, φ) = ∀x ∈ dom(φ) . ⋃{reachable(χ, φ(x))} + +reachable(χ, ∅) = ∅ +reachable(χ, {v} ∪ vs) = reachable(χ, v) ∪ reachable(χ, vs) + reachable(χ, v) = reachable(χ, v, ∅) reachable(χ, p, ιs) = ιs reachable(χ, 𝕣, ιs) = reachable(χ, 𝕣.object, ιs) reachable(χ, ι, ιs) = ιs if ι ∈ ιs - ιsₙ otherwise - where - xs = [x | x ∈ dom(χ(ι))] ∧ - n = |xs| ∧ - ιs₀ = (ι ∪ ιs) ∧ - ∀i ∈ 1 .. n . ιsᵢ = reachable(χ, χ(ι)(xsᵢ), ιsᵢ₋₁) + reachable(χ, ι, {ι} ∪ ιs, dom(χ(ι))) otherwise + +reachable(χ, ι, ιs, ∅) = ιs +reachable(χ, ι, ιs, {w} ∪ ws) = + reachable(χ, ι, ιs, w) ∪ reachable(χ, ι, ιs, ws) +reachable(χ, ι, ιs, w) = reachable(χ, χ(ι)(w), ιs) // Region. loc(χ, p) = Immutable loc(χ, 𝕣) = loc(χ, 𝕣.object) loc(χ, ι) = loc(χ, ι′) if χ.metadata(ι).location = ι′ - χ.metadata(ι).location otherwise + χ.metadata(ι).location if ι ∈ χ + Immutable otherwise same_loc(χ, v₀, v₁) = (loc(χ, v₀) = loc(χ, v₁)) - -// Mutability. -mut(χ, p) = false -mut(χ, 𝕣) = mut(χ, 𝕣.object) -mut(χ, ι) = loc(χ, ι) ≠ Immutable -mut-reachable(χ, σ) = {ι′ | ι′ ∈ reachable(χ, σ) ∧ mut(χ, ι′)} -mut-reachable(χ, φ) = {ι′ | ι′ ∈ reachable(χ, φ) ∧ mut(χ, ι′)} -mut-reachable(χ, ι) = {ι′ | ι′ ∈ reachable(χ, ι) ∧ mut(χ, ι′)} +members(χ, ρ) = {ι | (ι ∈ χ) ∧ (loc(χ, ι) = ρ)} // Region parents. parents(χ, ρ) = χ.regions(ρ).parents @@ -246,13 +240,18 @@ safe_store(χ, ι, v) = // Deep immutability. wf_immutable(χ) = - ∀ι ∈ χ . ¬mut(χ, ι) ⇒ (mut-reachable(χ, ι) = ∅) + ∀ι₀, ι₁ ∈ χ . + (loc(χ, ι₀) = Immutable) ∧ (ι₁ ∈ reachable(χ, ι₀)) ⇒ + (loc(χ, ι₁) = Immutable) // Data-race freedom. wf_racefree(χ, σs) = - ∀σ₀, σ₁ ∈ σs . σ₀ ≠ σ₁ ⇒ (mut-reachable(σ₀) ∩ mut-reachable(σ₁) = ∅) + ∀σ₀, σ₁ ∈ σs . ∀ι ∈ χ . + (ι ∈ reachable(χ, σ₀)) ∧ (ι ∈ reachable(χ, σ₁)) ⇒ + (σ₀ = σ₁) ∨ (loc(χ, ι) = Immutable) // Stack allocations are reachable only from that stack. +// TODO: wf_stacklocal(χ, σs) = ∀σ₀, σ₁ ∈ σs . ∀φ ∈ σ₀ . (reachable(χ, σ₁) ∩ ιs = ∅) where @@ -267,48 +266,96 @@ wf_regiontree(χ) = ``` +## Region Type Change + +```rs + +region_type_change(χ, σ, ∅, R) = χ +region_type_change(χ, σ, {ρ} ∪ ρs, R) = + region_type_change(χ′, σ, ρs, R) + where + χ′ = region_type_change(χ, σ, ρ, R) + +region_type_change(χ, σ, ρ, R) = + calc_rc(χ′, σ, ρ) if (R′ ≠ RegionRC) ∧ (R = RegionRC) + χ′ otherwise + where + R′ = χ.regions(ρ).type ∧ + χ′ = χ[regions(ρ)[type↦R]] + +calc_rc(χ, σ, ρ) = calc_rc(χ, σ, members(χ, ρ)) +calc_rc(χ, σ, ∅) = χ +calc_rc(χ, σ, {ι} ∪ ιs) = + calc_rc(χ′, σ, ιs) + where + χ′ = calc_rc(χ, σ, ι) +calc_rc(χ, σ, ι) = + χ[metadata(ι)[rc = calc_stack_rc(χ, σ, ι) + calc_heap_rc(χ, ι)]] + +calc_stack_rc(χ, ∅, ι) = 0 +calc_stack_rc(χ, σ;φ, ι) = + |{x | φ(x) = ι}| + calc_stack_rc(χ, σ, ι) + +// The heap RC for the parent region will be zero or one. +calc_heap_rc(χ, ι) = + calc_heap_rc(χ, {ρ} ∪ ρs, ι) + where + (ρ = loc(χ, ι)) ∧ (ρs = parents(χ, ρ)) + +calc_heap_rc(χ, ∅, ι) = 0 +calc_heap_rc(χ, {ρ} ∪ ρs, ι) = calc_heap_rc(χ, ρ, ι) + calc_heap_rc(χ, ρs, ι) +calc_heap_rc(χ, ρ, ι) = + |{(ι′, w) | + (ι′ ∈ members(χ, ρ)) ∧ + (w ∈ dom(χ(ι′))) ∧ + ((χ(ι′)(w) = ι)) ∨ ((χ(ι′)(w) = 𝕣) ∧ (𝕣.object = ι))}| + +``` + ## Reference Counting Reference counting is a no-op unless the object is in a `RegionRC` or is `Immutable`. ```rs +enable-rc(χ, ι) = + (loc(χ, ι) = ρ ∧ ρ.type = RegionRC) ∨ (loc(χ, ι) = Immutable) + region_stack_inc(χ, p) = χ region_stack_inc(χ, 𝕣) = region_stack_inc(χ, 𝕣.object) region_stack_inc(χ, ι) = - χ[regions(ρ)[stack_rc↦(stack_rc + 1)]] if loc(χ, ι) = ρ + χ[regions(ρ)[stack_rc = stack_rc + 1]] if loc(χ, ι) = ρ χ otherwise region_stack_dec(χ, p) = χ region_stack_dec(χ, 𝕣) = region_stack_dec(χ, 𝕣.object) region_stack_dec(χ, ι) = - χ[regions(ρ)[stack_rc↦(stack_rc - 1)]] if loc(χ, ι) = ρ + free_region(χ, ρ) if + (loc(χ, ι) = ρ) ∧ + (parents(χ, ρ) = ∅) ∧ + (χ.regions(ρ).stack_rc = 1) + χ[regions(ρ)[stack_rc = stack_rc - 1]] if loc(χ, ι) = ρ χ otherwise -// TODO: what if ι is in a frame? region_add_parent(χ, ι, p) = χ region_add_parent(χ, ι, 𝕣) = region_add_parent(χ, ι, 𝕣.object) region_add_parent(χ, ι, ι′) = - χ[regions(ρ)[parents ∪ {ρ′})]] if + χ[regions(ρ)[parents = parents ∪ {ρ′})]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) χ otherwise -// TODO: what if ι is in a frame? region_remove_parent(χ, ι, p) = χ region_remove_parent(χ, ι, 𝕣) = region_remove_parent(χ, ι, 𝕣.object) region_remove_parent(χ, ι, ι′) = - χ[regions(ρ)[parents \ {ρ′})]] if + χ[regions(ρ)[parents = parents \ {ρ′})]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) χ otherwise -enable-rc(χ, ι) = - (loc(χ, ι) = ρ ∧ ρ.type = RegionRC) ∨ (loc(χ, ι) = Immutable) - inc(χ, p) = χ inc(χ, 𝕣) = dec(χ, 𝕣.object) inc(χ, ι) = inc(χ, ι′) if χ.metadata(ι).location = ι′ - χ[metadata(ι)[rc↦metadata(ι).rc + 1]] if enable-rc(χ, ι) + χ[metadata(ι)[rc = rc + 1]] if enable-rc(χ, ι) χ otherwise dec(χ, p) = χ @@ -316,76 +363,123 @@ dec(χ, 𝕣) = dec(χ, 𝕣.object) dec(χ, ι) = dec(χ, ι′) if χ.metadata(ι).location = ι′ free(χ, ι) if enable-rc(χ, ι) ∧ (χ.metadata(ι).rc = 1) - χ[metadata(ι)[rc↦metata(ι).rc - 1]] if enable-rc(χ, ι) + χ[metadata(ι)[rc = rc - 1]] if enable-rc(χ, ι) χ otherwise -// TODO: free entire region? -free(χ, ι) = χₙ\ι where - xs = [x | x ∈ dom(χ(ι))] ∧ - n = |xs| ∧ - χ₀ = χ ∧ - ∀i ∈ 1 .. n . - (ιᵢ = χ(ι)(xsᵢ)) ∧ - (χᵢ₊₁ = dec(region_remove_parent(χᵢ, ι, ιᵢ), ιᵢ)) - ``` -## Region Type Change +## Garbage Collection ```rs -region_type_change(χ, σ, ∅, R) = χ -region_type_change(χ, σ, {ρ} ∪ ρs, R) = - region_type_change(χ′, σ, ρs, R) +// GC on RegionRC is cycle detection. +enable-gc(χ, ρ) = χ.regions(ρ).type ∈ {RegionGC, RegionRC} + +gc(χ₀, σ, ρ) = + χ₃ \ ιs₀ if enable-gc(χ₃, ρ) + χ₀ otherwise where - χ′ = region_type_change(χ, σ, ρ, R) + ιs = members(χ₀, ρ) ∧ + ιs₀ = ιs \ reachable(χ₀, gc_roots(χ₀, σ, ρ)) ∧ + ιs₁ = ιs \ ιs₀ ∧ + χ₁, ρs = gc_dec(χ₀, ιs₀, ιs₁) ∧ + χ₂ = finalize(χ₁, ιs₀) ∧ + χ₃ = free_regions(χ₂, ρs) -region_type_change(χ, σ, ρ, R) = - calc_rc(χ′, σ, ρ) if (R′ ≠ RegionRC) ∧ (R = RegionRC) - χ′ otherwise +gc_roots(χ, σ, ρ) = + {ι | (ι ∈ ιs) ∧ ((calc_stack_rc(χ, σ, ι) > 0) ∨ (calc_heap_rc(χ, ρs, ι) > 0))} where - R′ = χ.regions(ρ).type ∧ - χ′ = χ[regions(ρ)[type↦R]] + ρs = parents(χ, ρ) ∧ ιs = members(χ, ρ) -calc_rc(χ, σ, ρ) = - χ[∀ι ∈ ιs . metadata(ι).rc↦calc_rc(χ, σ, ι)] +gc_dec(χ, ∅, ιs₁) = χ, ∅ +gc_dec(χ₀, {ι} ∪ ιs₀, ιs₁) = + χ₂, ρ₁ ∪ ρs₂ + where + χ₁, ρs₁ = gc_dec_fields(χ₀, ι, dom(χ₀(ι)), ιs₁) ∧ + χ₂, ρs₂ = gc_dec(χ₁, ιs₀, ιs₁) + +gc_dec_fields(χ, ι, ∅, ιs₁) = χ, ∅ +gc_dec_fields(χ₀, ι, {w} ∪ ws, ιs₁) = + χ₂, ρs₁ ∪ ρs₂ where - ιs = {ι | loc(χ, ι) = ρ} + χ₁, ρs₁ = gc_dec_field(χ₀, ι, χ(ι)(w), ιs₁) ∧ + χ₂, ρs₂ = gc_dec_fields(χ₁, ι, ws, ιs₁) + +gc_dec_field(χ, ι, p, ιs₁) = χ +gc_dec_field(χ, ι, 𝕣, ιs₁) = gc_dec_field(χ, ι, 𝕣.object) +gc_dec_field(χ, ι, ι′, ιs₁) = + dec(χ, ι′), ∅ if (ι′ ∈ ιs₁) ∨ (loc(χ, ι′) = Immutable) + χ, {ρ} if + (loc(χ, ι′) = ρ) ∧ ¬same_loc(χ, ι, ι′) ∧ (χ.regions(ρ).stack_rc = 0) + region_remove_parent(χ, ι, ι′), ∅ if + (loc(χ, ι′) = ρ) ∧ ¬same_loc(χ, ι, ι′) ∧ (χ.regions(ρ).stack_rc > 0) + χ, ∅ otherwise -calc_rc(χ, σ, ι) = - χ[metadata(ι)[rc↦calc_stack_rc(χ, σ, ι) + calc_heap_rc(χ, ι)]] +``` -calc_stack_rc(χ, ∅, ι) = 0 -calc_stack_rc(χ, σ;φ, ι) = - |{x | φ(x) = ι}| + calc_stack_rc(χ, σ, ι) +## Free -// The heap RC for the parent region will be zero or one. -calc_heap_rc(χ, ι) = - calc_heap_rc(χ, ρ, ι) + calc_heap_rc(χ, ρ′, ι) +```rs + +free_regions(χ, ∅) = χ +free_regions(χ, {ρ} ∪ ρs) = + free_regions(χ′, ρs) where - (ρ = loc(χ, ι)) ∧ ({ρ′} = parents(χ, ρ)) + χ′ = free_region(χ, ρ) -calc_heap_rc(χ, ρ, ι) = - |{(ι′, w) | (ι′ ∈ ιs) ∧ (w ∈ dom(χ(ι′)) ∧ (χ(ι′)(w) = ι))}| +free_region(χ₀, ρ) = + χ₂ \ ιs \ ρ + where + ρs = {ρ′ | (ρ′ ∈ χ) ∧ is_ancestor(χ₀, ρ, ρ′)} ∧ + ιs = members(χ₀, ρ) + χ₁ = finalize(χ₀, ιs) ∧ + χ₂ = free_regions(χ₁, ρs) + +free(χ₀, ι) = + χ₃ \ ιs + where + χ₁, ιs, ρs = free_fields(χ₀, {ι}, ι) ∧ + χ₂ = finalize(χ₁, ιs) ∧ + χ₃ = free_regions(χ₂, ρs) + +free_fields(χ, ιs, ι) = free_fields(χ, ιs, ι, dom(χ(ι))) +free_fields(χ, ιs, ι, ∅) = χ, ιs, ∅ +free_fields(χ₀, ιs₀, ι, {w} ∪ ws) = + χ₂, ιs₂, ρs₁ ∪ ρs₂ where - ιs = {ι′ | loc(χ, ι′) = ρ} + χ₁, ιs₁, ρs₁ = free_field(χ₀, ιs₀, ι, w) ∧ + χ₂, ιs₂, ρs₂ = free_fields(χ₁, ιs₁, ι, ws) + +free_field(χ, ιs, ι, p) = χ, ιs, ∅ +free_field(χ, ιs, ι, 𝕣) = free_field(χ, ιs, ι, 𝕣.object) +free_field(χ, ιs, ι, ι′) = + χ, ιs, ∅ if ι′ ∈ ιs + free_fields(χ, {ι′} ∪ ιs, ι′), {ι′} ∪ ιs, ∅ if + (same_loc(χ, ι, ι′) ∨ (loc(χ, ι′) = Immutable)) ∧ + (χ.metadata(ι′).rc = 1) + χ[metadata(ι′)[rc = rc - 1]], ιs, ∅ if + (same_loc(χ, ι, ι′) ∨ (loc(χ, ι′) = Immutable)) ∧ + (χ.metadata(ι′).rc > 1) + free_fields(χ, {ι′} ∪ ιs, ι′), {ι} ∪ ιs, ∅ if χ.metadata(ι′).location = ι + χ, ιs, {ρ} if + (loc(χ, ι′) = ρ) ∧ ¬same_loc(χ, ι, ι′) ∧ (χ.regions(ρ).stack_rc = 0) + region_remove_parent(χ, ι, ι′), ιs, ∅ if + (loc(χ, ι′) = ρ) ∧ ¬same_loc(χ, ι, ι′) ∧ (χ.regions(ρ).stack_rc > 0) + χ, ιs, ∅ otherwise ``` -## Garbage Collection +## Finalization ```rs -gc_roots(χ, σ, ρ) = - {ι | ι ∈ ιs ∧ ((calc_stack_rc(χ, σ, ι) > 0) ∨ (calc_heap_rc(χ, ρ′, ι) > 0))} - where - {ρ′} = parents(χ, ρ) ∧ - ιs = {ι | loc(χ, ι) = ρ} - -// TODO: -gc(χ, σ, ρ) = +finalize(χ, ∅) = χ +finalize(χ₀, {ι} ∪ ιs) = + finalize(χ₁, ιs) where - ιs = gc_roots(χ, σ, ρ) ∧ + χ₁ = finalize(χ₀, ι) +finalize(χ, ι) = + // TODO: make sure this is read-only to be resurrection-free. ``` @@ -482,10 +576,10 @@ v₀ = χ₀(ι)(w) v₁ = φ(z) safe_store(χ₀, ι, v₁) ω = χ₀(ι)[w↦v₁] -χ₁ = region_remove_parent(χ₀, ι, v₀) -χ₂ = region_stack_inc(χ₁, v₀) -χ₃ = region_stack_dec(χ₂, v₁) -χ₄ = region_add_parent(χ₃, ι, v₁) +χ₁ = region_stack_inc(χ₀, v₀) +χ₂ = region_remove_parent(χ₁, ι, v₀) +χ₃ = region_add_parent(χ₂, ι, v₁) +χ₄ = region_stack_dec(χ₃, v₁) --- [store] χ₀, σ;ϕ, bind x (store y z);stmt* ⇝ χ₄[ι↦ω], σ;ϕ[x↦v₀]\z, stmt* @@ -578,7 +672,7 @@ x ∉ φ loc(χ₀, φ(w)) = ρ₀ loc(χ₀, φ(y)) = ρ₁ (ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ₀, ρ₁, ρ₀) ∧ ({ρ₀} ⊇ parents(χ₀, ρ₁)) -ιs = {ι | loc(χ₀, ι) = ρ₁} +ιs = members(χ₀, ρ₁) χ₁ = χ₀[∀ι ∈ ιs . metadata(ι)[location↦ρ₀]] [regions(ρ₀)[stack_rc += regions(ρ₁).stack_rc)]] --- [merge true] From 5082af4566965907756b9a527fd72d5931f68bf3 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Thu, 27 Mar 2025 14:46:59 -0500 Subject: [PATCH 16/37] opsem minor edits --- docs/opsem.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index c73ec3fe8..8ff867acd 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -21,7 +21,6 @@ Still to do: * Behaviors and cowns. * Embedded object fields? * Arrays? Or model them as objects? -* GC or RC cycle detection. * Non-local returns. Dynamic failures: @@ -623,9 +622,9 @@ All arguments are consumed. To keep them, `dup` them first. As such, an identifi newframe(χ, ϕ, F, x, y*, stmt*) = {id: 𝔽, vars: {F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 1 .. |y*|}, ret: x, cont: stmt*} where - 𝔽 ∉ dom(χ.frames) ∧ - |F.params| = |y*| = |{y*}| ∧ - ∀i ∈ 1 .. |y*| . typetest(χ, φ(yᵢ), F.paramsᵢ.type) + 𝔽 ∉ dom(χ.frames) ∧ + |F.params| = |y*| = |{y*}| ∧ + ∀i ∈ 1 .. |y*| . typetest(χ, φ(yᵢ), F.paramsᵢ.type) x ∉ φ₀ F = P.funcs(𝕗) From ed7af13d9c6c98856883e7615050ba8e4f43c17f Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Fri, 28 Mar 2025 17:14:33 -0500 Subject: [PATCH 17/37] finalization --- docs/opsem.md | 203 ++++++++++++++++++++++++++++---------------------- 1 file changed, 116 insertions(+), 87 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 8ff867acd..916219136 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -2,8 +2,6 @@ Still to do: * WIP: Extract. -* Add finalizers explicitly in the semantics. - * Set the objects being finalized to `Immutable`? * Discharge (send/free/freeze?) when stack RC for the region and all children (recursively) is 0. * Can free even if child regions have stack RC. * Could optimize by tracking the count of "busy" child regions. @@ -32,6 +30,13 @@ Dynamic failures: * TODO: frame references? * `call dynamic`: * `w` is not a method of the target. + * Arguments don't type check. +* `call static`: + * Arguments don't type check. +* `return`: + * The return value is not the only thing in the frame. + * The return value can't escape the frame. + * The return value doesn't type check. * `merge`: * Trying to merge a value that isn't an object in a region. * Trying to merge a region that is a child of a region other than the destination region. @@ -113,7 +118,11 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena data: ObjectId ↦ Object, metadata: ObjectId ↦ Metadata, regions: RegionId ↦ Region, - frames: 𝒫(FrameId) + frames: 𝒫(FrameId), + pre_final: 𝒫(ObjectId), + post_final: 𝒫(ObjectId), + pre_final_r: 𝒫(RegionId), + post_final_r: 𝒫(RegionId) } Heap, Stack, Statement* ⇝ Heap, Stack, Statement* @@ -145,7 +154,7 @@ x ∈ φ ≝ x ∈ dom(φ.vars) // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) -χ[ρ↦R] = χ[regions(ρ)↦(R, ∅)] +χ[ρ↦R] = χ[regions(ρ)↦{type: R, parents: ∅, stack_rc: 1}] χ\ρ = χ[regions\ρ] ``` @@ -222,6 +231,7 @@ This enforces a tree-shaped region graph, with a single reference from parent to ```rs safe_store(χ, ι, v) = + false if finalizing(ι) ∨ finalizing(v) false if loc(χ, ι) = Immutable true if loc(χ, v) = Immutable // TODO: more precise frame references? @@ -231,6 +241,10 @@ safe_store(χ, ι, v) = (parents(χ, ρ₁) = ∅) ∧ ¬is_ancestor(χ, ρ₁, ρ₀) false otherwise +finalizing(χ, p) = false +finalizing(χ, 𝕣) = finalizing(χ, 𝕣.object) +finalizing(χ, ι) = (ι ∈ χ.pre_final) ∨ (ι ∈ χ.post_final) + ``` ## Well-Formedness @@ -280,7 +294,7 @@ region_type_change(χ, σ, ρ, R) = χ′ otherwise where R′ = χ.regions(ρ).type ∧ - χ′ = χ[regions(ρ)[type↦R]] + χ′ = χ[regions(ρ)[type = R]] calc_rc(χ, σ, ρ) = calc_rc(χ, σ, members(χ, ρ)) calc_rc(χ, σ, ∅) = χ @@ -323,30 +337,30 @@ enable-rc(χ, ι) = region_stack_inc(χ, p) = χ region_stack_inc(χ, 𝕣) = region_stack_inc(χ, 𝕣.object) region_stack_inc(χ, ι) = - χ[regions(ρ)[stack_rc = stack_rc + 1]] if loc(χ, ι) = ρ + χ[regions(ρ)[stack_rc += 1]] if loc(χ, ι) = ρ χ otherwise region_stack_dec(χ, p) = χ region_stack_dec(χ, 𝕣) = region_stack_dec(χ, 𝕣.object) region_stack_dec(χ, ι) = - free_region(χ, ρ) if + χ[pre_final_r ∪= {ρ}] if (loc(χ, ι) = ρ) ∧ (parents(χ, ρ) = ∅) ∧ (χ.regions(ρ).stack_rc = 1) - χ[regions(ρ)[stack_rc = stack_rc - 1]] if loc(χ, ι) = ρ + χ[regions(ρ)[stack_rc -= 1]] if loc(χ, ι) = ρ χ otherwise region_add_parent(χ, ι, p) = χ region_add_parent(χ, ι, 𝕣) = region_add_parent(χ, ι, 𝕣.object) region_add_parent(χ, ι, ι′) = - χ[regions(ρ)[parents = parents ∪ {ρ′})]] if + χ[regions(ρ)[parents ∪= {ρ′})]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) χ otherwise region_remove_parent(χ, ι, p) = χ region_remove_parent(χ, ι, 𝕣) = region_remove_parent(χ, ι, 𝕣.object) region_remove_parent(χ, ι, ι′) = - χ[regions(ρ)[parents = parents \ {ρ′})]] if + χ[regions(ρ)[parents \= {ρ′})]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) χ otherwise @@ -354,7 +368,7 @@ inc(χ, p) = χ inc(χ, 𝕣) = dec(χ, 𝕣.object) inc(χ, ι) = inc(χ, ι′) if χ.metadata(ι).location = ι′ - χ[metadata(ι)[rc = rc + 1]] if enable-rc(χ, ι) + χ[metadata(ι)[rc += 1]] if enable-rc(χ, ι) χ otherwise dec(χ, p) = χ @@ -362,7 +376,7 @@ dec(χ, 𝕣) = dec(χ, 𝕣.object) dec(χ, ι) = dec(χ, ι′) if χ.metadata(ι).location = ι′ free(χ, ι) if enable-rc(χ, ι) ∧ (χ.metadata(ι).rc = 1) - χ[metadata(ι)[rc = rc - 1]] if enable-rc(χ, ι) + χ[metadata(ι)[rc -= 1]] if enable-rc(χ, ι) χ otherwise ``` @@ -374,45 +388,37 @@ dec(χ, ι) = // GC on RegionRC is cycle detection. enable-gc(χ, ρ) = χ.regions(ρ).type ∈ {RegionGC, RegionRC} -gc(χ₀, σ, ρ) = - χ₃ \ ιs₀ if enable-gc(χ₃, ρ) - χ₀ otherwise +gc(χ, σ, ρ) = + χ′[pre_final ∪= ιs₀] if enable-gc(χ₃, ρ) + χ otherwise where ιs = members(χ₀, ρ) ∧ - ιs₀ = ιs \ reachable(χ₀, gc_roots(χ₀, σ, ρ)) ∧ + ιs₀ ⊆ ιs \ reachable(χ₀, gc_roots(χ₀, σ, ρ)) ∧ ιs₁ = ιs \ ιs₀ ∧ - χ₁, ρs = gc_dec(χ₀, ιs₀, ιs₁) ∧ - χ₂ = finalize(χ₁, ιs₀) ∧ - χ₃ = free_regions(χ₂, ρs) + χ′ = gc_dec(χ, ιs₀, ιs₁) gc_roots(χ, σ, ρ) = {ι | (ι ∈ ιs) ∧ ((calc_stack_rc(χ, σ, ι) > 0) ∨ (calc_heap_rc(χ, ρs, ι) > 0))} where ρs = parents(χ, ρ) ∧ ιs = members(χ, ρ) -gc_dec(χ, ∅, ιs₁) = χ, ∅ -gc_dec(χ₀, {ι} ∪ ιs₀, ιs₁) = - χ₂, ρ₁ ∪ ρs₂ +gc_dec(χ, ∅, ιs₁) = χ +gc_dec(χ, {ι} ∪ ιs₀, ιs₁) = + gc_dec(χ′, ιs₀, ιs₁) where - χ₁, ρs₁ = gc_dec_fields(χ₀, ι, dom(χ₀(ι)), ιs₁) ∧ - χ₂, ρs₂ = gc_dec(χ₁, ιs₀, ιs₁) + χ′ = gc_dec_fields(χ, ι, dom(χ₀(ι)), ιs₁) ∧ -gc_dec_fields(χ, ι, ∅, ιs₁) = χ, ∅ -gc_dec_fields(χ₀, ι, {w} ∪ ws, ιs₁) = - χ₂, ρs₁ ∪ ρs₂ +gc_dec_fields(χ, ι, ∅, ιs₁) = χ +gc_dec_fields(χ, ι, {w} ∪ ws, ιs₁) = + gc_dec_fields(χ′, ι, ws, ιs₁) where - χ₁, ρs₁ = gc_dec_field(χ₀, ι, χ(ι)(w), ιs₁) ∧ - χ₂, ρs₂ = gc_dec_fields(χ₁, ι, ws, ιs₁) + χ′ = gc_dec_field(χ₀, ι, χ(ι)(w), ιs₁) ∧ gc_dec_field(χ, ι, p, ιs₁) = χ gc_dec_field(χ, ι, 𝕣, ιs₁) = gc_dec_field(χ, ι, 𝕣.object) gc_dec_field(χ, ι, ι′, ιs₁) = - dec(χ, ι′), ∅ if (ι′ ∈ ιs₁) ∨ (loc(χ, ι′) = Immutable) - χ, {ρ} if - (loc(χ, ι′) = ρ) ∧ ¬same_loc(χ, ι, ι′) ∧ (χ.regions(ρ).stack_rc = 0) - region_remove_parent(χ, ι, ι′), ∅ if - (loc(χ, ι′) = ρ) ∧ ¬same_loc(χ, ι, ι′) ∧ (χ.regions(ρ).stack_rc > 0) - χ, ∅ otherwise + dec(χ, ι′) if (ι′ ∈ ιs₁) ∨ (loc(χ, ι′) = Immutable) + χ otherwise ``` @@ -420,68 +426,33 @@ gc_dec_field(χ, ι, ι′, ιs₁) = ```rs -free_regions(χ, ∅) = χ -free_regions(χ, {ρ} ∪ ρs) = - free_regions(χ′, ρs) - where - χ′ = free_region(χ, ρ) - -free_region(χ₀, ρ) = - χ₂ \ ιs \ ρ - where - ρs = {ρ′ | (ρ′ ∈ χ) ∧ is_ancestor(χ₀, ρ, ρ′)} ∧ - ιs = members(χ₀, ρ) - χ₁ = finalize(χ₀, ιs) ∧ - χ₂ = free_regions(χ₁, ρs) - -free(χ₀, ι) = - χ₃ \ ιs +free(χ, ι) = + χ′[pre_final ∪= ιs] where - χ₁, ιs, ρs = free_fields(χ₀, {ι}, ι) ∧ - χ₂ = finalize(χ₁, ιs) ∧ - χ₃ = free_regions(χ₂, ρs) + χ′, ιs = free_fields(χ, {ι}, ι) free_fields(χ, ιs, ι) = free_fields(χ, ιs, ι, dom(χ(ι))) -free_fields(χ, ιs, ι, ∅) = χ, ιs, ∅ -free_fields(χ₀, ιs₀, ι, {w} ∪ ws) = - χ₂, ιs₂, ρs₁ ∪ ρs₂ +free_fields(χ, ιs, ι, ∅) = χ, ιs +free_fields(χ, ιs, ι, {w} ∪ ws) = + free_fields(χ′, ιs′, ι, ws) where - χ₁, ιs₁, ρs₁ = free_field(χ₀, ιs₀, ι, w) ∧ - χ₂, ιs₂, ρs₂ = free_fields(χ₁, ιs₁, ι, ws) + χ₁′ ιs′ = free_field(χ, ιs, ι, w) -free_field(χ, ιs, ι, p) = χ, ιs, ∅ +free_field(χ, ιs, ι, p) = χ, ιs free_field(χ, ιs, ι, 𝕣) = free_field(χ, ιs, ι, 𝕣.object) free_field(χ, ιs, ι, ι′) = - χ, ιs, ∅ if ι′ ∈ ιs - free_fields(χ, {ι′} ∪ ιs, ι′), {ι′} ∪ ιs, ∅ if + χ, ιs if ι′ ∈ ιs + free_fields(χ, {ι′} ∪ ιs, ι′), {ι′} ∪ ιs if (same_loc(χ, ι, ι′) ∨ (loc(χ, ι′) = Immutable)) ∧ (χ.metadata(ι′).rc = 1) - χ[metadata(ι′)[rc = rc - 1]], ιs, ∅ if + χ[metadata(ι′)[rc -= 1]], ιs if (same_loc(χ, ι, ι′) ∨ (loc(χ, ι′) = Immutable)) ∧ (χ.metadata(ι′).rc > 1) - free_fields(χ, {ι′} ∪ ιs, ι′), {ι} ∪ ιs, ∅ if χ.metadata(ι′).location = ι - χ, ιs, {ρ} if - (loc(χ, ι′) = ρ) ∧ ¬same_loc(χ, ι, ι′) ∧ (χ.regions(ρ).stack_rc = 0) - region_remove_parent(χ, ι, ι′), ιs, ∅ if - (loc(χ, ι′) = ρ) ∧ ¬same_loc(χ, ι, ι′) ∧ (χ.regions(ρ).stack_rc > 0) + free_fields(χ, {ι′} ∪ ιs, ι′), {ι} ∪ ιs if χ.metadata(ι′).location = ι χ, ιs, ∅ otherwise ``` -## Finalization - -```rs - -finalize(χ, ∅) = χ -finalize(χ₀, {ι} ∪ ιs) = - finalize(χ₁, ιs) - where - χ₁ = finalize(χ₀, ι) -finalize(χ, ι) = - // TODO: make sure this is read-only to be resurrection-free. - -``` - ## New For an "address-taken" local variable, i.e. a `var` as opposed to a `let`, allocate an object in the frame with a single field to hold the value. @@ -601,6 +572,8 @@ v = typetest(χ, φ(y), T) The condition is not consumed. +> TODO: bind a result? + ```rs φ(x) = true @@ -651,7 +624,9 @@ This checks that: ```rs +// TODO: What if the return value doesn't type check? dom(φ₁.vars) = {x} +typetest(χ, φ₁(x), F.result) ιs = {ι | loc(χ, ι) = φ₁.id} ∀ι ∈ χ . ι ∉ ιs ⇒ (∀z ∈ dom(χ(ι)) . χ(ι)(z) ∉ ιs) --- [return] @@ -672,7 +647,7 @@ loc(χ₀, φ(w)) = ρ₀ loc(χ₀, φ(y)) = ρ₁ (ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ₀, ρ₁, ρ₀) ∧ ({ρ₀} ⊇ parents(χ₀, ρ₁)) ιs = members(χ₀, ρ₁) -χ₁ = χ₀[∀ι ∈ ιs . metadata(ι)[location↦ρ₀]] +χ₁ = χ₀[∀ι ∈ ιs . metadata(ι)[location = ρ₀]] [regions(ρ₀)[stack_rc += regions(ρ₁).stack_rc)]] --- [merge true] χ₀, σ;φ, bind x (merge w y);stmt* ⇝ χ₁\ρ₁, σ;φ[x↦true], stmt* @@ -700,7 +675,7 @@ x ∉ φ ρs = {ρ} ∪ {ρ′ | (ρ′ ∈ χ.regions) ∧ is_ancestor(χ₀, ρ, ρ′)} χ₁ = region_type_change(χ₀, σ;φ, ρs, RegionRC) ιs = {ι′ | loc(χ₀, ι′) ∈ ρs} -χ₂ = χ₁[∀ι′ ∈ ιs . metadata(ι′)[location↦Immutable]] +χ₂ = χ₁[∀ι′ ∈ ιs . metadata(ι′)[location = Immutable]] --- [freeze true] χ₀, σ;φ, bind x (freeze y);stmt* ⇝ χ₂\ρs, σ;φ[x↦true], stmt* @@ -717,16 +692,70 @@ loc(χ, φ(y)) ≠ ρ ```rs +// TODO: stack rc x ∉ φ ι = φ(y) ρ₀ = loc(χ₀, ι) +R = χ.regions(ρ₀).type ρ₁ ∉ χ₀ -ιs = reachable(χ, ι) +ιs = {ι′ | ι′ ∈ reachable(χ, ι) ∧ (loc(χ₀, ι′) = ρ₀)} + + ∀ι′ ∈ χ₀.regions(ρ₀).members . (ι′ ∉ ιs ⇒ ∀z ∈ dom(χ₀(ι′)) . χ₀(ι′)(z) ∉ ιs) χ₁ = χ₀[regions(ρ₀).members\ιs] [regions(ρ₁)↦{type: χ₀.regions(ρ₀).type, members: ιs}] - [∀ι′ ∈ ιs . metadata(ι′).location↦ρ₁] + [∀ι′ ∈ ιs . metadata(ι′).location = ρ₁] --- [extract] -χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁[φ\y][φ(x)↦ι], σ;φ, stmt* +χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁[ρ₁↦R], σ;φ, stmt* + +``` + +## Garbage + +```rs + +region_fields(χ, ι) = + χ[∀ρ′ ∈ ρs . regions(ρ′)[parents \= {ρ}], pre_final_r ∪= ρs′] + where + ρ = loc(χ, ι) ∧ + ws = dom(χ(ι)) ∧ + ρs = {ρ′ | w ∈ ws ∧ (χ(ι)(w) = ι′) ∧ (ρ′ = loc(χ, ι′)) ∧ (ρ ≠ ρ′)} ∧ + ρs′ = {ρ′ | ρ′ ∈ ρs ∧ χ.regions(ρ′).stack_rc = 0} + +χ₀.pre_final = {ι} ∪ ιs +τ = typeof(χ, ι) +F = P.funcs(P.types(τ).methods(final)) +|F.params| = 1 +typetest(χ, ι, F.params₀.type) +𝔽 ∉ dom(χ.frames) +φ₁ = {id: 𝔽, vars: {F.paramsᵢ.name ↦ ι}, ret: final, cont: (drop final;stmt*)} +χ₁ = region_fields(χ₀, ι) +χ₂ = χ₁[frames ∪= 𝔽, pre_final = ιs, post_final ∪= {ι}] +--- [finalize true] +χ₀, σ;φ₀, stmt* ⇝ χ₂, σ;φ₀;φ₁, F.body + +χ₀.pre_final = {ι} ∪ ιs +τ = typeof(χ, ι) +final ∉ dom(P.types(τ).methods) +χ₁ = region_fields(χ₀, ι) +χ₂ = χ₁[pre_final = ιs, post_final ∪= {ι}] +--- [finalize false] +χ₀, σ;φ, stmt* ⇝ χ₂, σ;φ, stmt* + +χ.pre_final = ∅ +χ.post_final = {ι} ∪ ιs +--- [collect object] +χ, σ;φ, stmt* ⇝ χ[post_final = ιs]\ι, σ;φ, stmt* + +χ.pre_final = ∅ +χ.pre_final_r = {ρ} ∪ {ρs} +χ′ = χ[pre_final = members(χ, ρ), pre_final_r \= {ρ}, post_final_r ∪= ρ] +--- [finalize region] +χ, σ;φ, stmt* ⇝ χ′, σ;φ, stmt* + +χ.pre_final = ∅ +χ.post_final_r = {ρ} ∪ {ρs} +--- [collect region] +χ, σ;φ, stmt* ⇝ χ[post_final_r = ρs]\ρ, σ;φ, stmt* ``` From 1a97c0a917393dbeca94b1a0dc6cd6f9a340aa08 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Fri, 28 Mar 2025 19:01:54 -0500 Subject: [PATCH 18/37] extract --- docs/opsem.md | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 916219136..0215f3e87 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -1,7 +1,6 @@ # Operational Semantics Still to do: -* WIP: Extract. * Discharge (send/free/freeze?) when stack RC for the region and all children (recursively) is 0. * Can free even if child regions have stack RC. * Could optimize by tracking the count of "busy" child regions. @@ -305,6 +304,10 @@ calc_rc(χ, σ, {ι} ∪ ιs) = calc_rc(χ, σ, ι) = χ[metadata(ι)[rc = calc_stack_rc(χ, σ, ι) + calc_heap_rc(χ, ι)]] +calc_stack_rc(χ, σ, ∅) = 0 +calc_stack_rc(χ, σ, {ι} ∪ ιs) = + calc_stack_rc(χ, σ, ι) + calc_stack_rc(χ, σ, ιs) + calc_stack_rc(χ, ∅, ι) = 0 calc_stack_rc(χ, σ;φ, ι) = |{x | φ(x) = ι}| + calc_stack_rc(χ, σ, ι) @@ -688,29 +691,32 @@ loc(χ, φ(y)) ≠ ρ ## Extract -> TODO: Doesn't work. Doesn't allow sub-regions or immutable objects. - ```rs -// TODO: stack rc x ∉ φ ι = φ(y) ρ₀ = loc(χ₀, ι) -R = χ.regions(ρ₀).type ρ₁ ∉ χ₀ -ιs = {ι′ | ι′ ∈ reachable(χ, ι) ∧ (loc(χ₀, ι′) = ρ₀)} +ιs = reachable(χ, ι) ∩ members(χ₀, ρ₀) +ρs = {ρ | + (ι ∈ ιs) ∧ (w ∈ dom(χ(ι))) ∧ (χ(ι)(w) = ι′) ∧ + (ρ = loc(χ, ι′)) ∧ (ρ ≠ ρ₀)} +rc = calc_stack_rc(χ₀, σ;φ, ιs) +χ₁ = χ₀[regions(ρ₀)[stack_rc -= rc], + regions(ρ₁)↦{type: χ.regions(ρ₀).type, parents: {ρ₀}, stack_rc: rc}, + ∀ι′ ∈ ιs . metadata(ι′)[location = ρ₁], + ∀ρ ∈ ρs . regions(ρ)[parents = {ρ₁}]] +--- [extract true] +χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁, σ;φ[x↦true], stmt* - -∀ι′ ∈ χ₀.regions(ρ₀).members . (ι′ ∉ ιs ⇒ ∀z ∈ dom(χ₀(ι′)) . χ₀(ι′)(z) ∉ ιs) -χ₁ = χ₀[regions(ρ₀).members\ιs] - [regions(ρ₁)↦{type: χ₀.regions(ρ₀).type, members: ιs}] - [∀ι′ ∈ ιs . metadata(ι′).location = ρ₁] ---- [extract] -χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁[ρ₁↦R], σ;φ, stmt* +x ∉ φ +ρ ≠ loc(χ, φ(y)) +--- [extract false] +χ, σ;φ, bind x (extract y);stmt* ⇝ χ, σ;φ[x↦false], stmt* ``` -## Garbage +## Finalization ```rs From 40146d6ab3bbb4ae967f4a717c6beafcb3069b77 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Fri, 28 Mar 2025 19:21:47 -0500 Subject: [PATCH 19/37] ordered frame ids --- docs/opsem.md | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 0215f3e87..bce153ee7 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -6,12 +6,6 @@ Still to do: * Could optimize by tracking the count of "busy" child regions. * Can we still have an Arena per frame? * Frame Arenas can't be discharged anyway. -* Efficient frame teardown. - * Each frame could be an Arena. - * References from a frame Arena to another region (including another frame Arena) would be tracked as stack RC. - * A frame Arena must reach external RC 0 at `return`. - * Prevent heap or "older frame" regions from referencing a frame Arena? - * Any region other than the current frame Arena can't reference the current frame Arena? * How are Arenas different from uncounted regions? * How should they treat changing region type? * How should they treat merging, freezing, extract? @@ -23,10 +17,12 @@ Still to do: Dynamic failures: * `store`: * `w` is not a field of the target. - * `store` to an immutable object. - * `store` a region that already has a parent. - * `store` a region that would create a cycle. - * TODO: frame references? + * Store to a finalizing object. + * Store a finalizing object. + * Store to an immutable object. + * Store a region that already has a parent. + * Store a region that would create a cycle. + * Store a frame value in a region or in a predecessor frame. * `call dynamic`: * `w` is not a method of the target. * Arguments don't type check. @@ -34,7 +30,7 @@ Dynamic failures: * Arguments don't type check. * `return`: * The return value is not the only thing in the frame. - * The return value can't escape the frame. + * The return value is allocated on the frame. * The return value doesn't type check. * `merge`: * Trying to merge a value that isn't an object in a region. @@ -233,8 +229,8 @@ safe_store(χ, ι, v) = false if finalizing(ι) ∨ finalizing(v) false if loc(χ, ι) = Immutable true if loc(χ, v) = Immutable - // TODO: more precise frame references? - true if loc(χ, ι) = 𝔽 + true if loc(χ, ι) = 𝔽 ∧ (loc(χ, v) = ρ) + true if loc(χ, ι) = 𝔽 ∧ (loc(χ, v) = 𝔽′) ∧ (𝔽 >= 𝔽′) true if same_loc(χ, ι, v) true if (ρ₀ = loc(χ, ι)) ∧ (ρ₁ = loc(χ, v)) ∧ (parents(χ, ρ₁) = ∅) ∧ ¬is_ancestor(χ, ρ₁, ρ₀) @@ -356,8 +352,9 @@ region_stack_dec(χ, ι) = region_add_parent(χ, ι, p) = χ region_add_parent(χ, ι, 𝕣) = region_add_parent(χ, ι, 𝕣.object) region_add_parent(χ, ι, ι′) = - χ[regions(ρ)[parents ∪= {ρ′})]] if + χ[regions(ρ′)[parents ∪= {ρ})]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) + χ[regions(ρ′)[stack_rc += 1]] if (loc(χ, ι) = 𝔽) ∧ (loc(χ, ι′) = ρ′) χ otherwise region_remove_parent(χ, ι, p) = χ @@ -365,6 +362,7 @@ region_remove_parent(χ, ι, 𝕣) = region_remove_parent(χ, ι, 𝕣.object) region_remove_parent(χ, ι, ι′) = χ[regions(ρ)[parents \= {ρ′})]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) + χ[regions(ρ′)[stack_rc -= 1]] if (loc(χ, ι) = 𝔽) ∧ (loc(χ, ι′) = ρ′) χ otherwise inc(χ, p) = χ @@ -598,7 +596,8 @@ All arguments are consumed. To keep them, `dup` them first. As such, an identifi newframe(χ, ϕ, F, x, y*, stmt*) = {id: 𝔽, vars: {F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 1 .. |y*|}, ret: x, cont: stmt*} where - 𝔽 ∉ dom(χ.frames) ∧ + (𝔽 ∉ dom(χ.frames)) ∧ + (𝔽 > φ.id) ∧ |F.params| = |y*| = |{y*}| ∧ ∀i ∈ 1 .. |y*| . typetest(χ, φ(yᵢ), F.paramsᵢ.type) @@ -619,21 +618,14 @@ F = P.funcs(P.types(τ).methods(w)) ## Return -This checks that: -* Only the return value remains in the frame, to ensure proper reference counting. -* No objects that will survive the frame reference any object allocated on the frame, to prevent dangling references. - -> TODO: how to make this efficient? - ```rs -// TODO: What if the return value doesn't type check? dom(φ₁.vars) = {x} -typetest(χ, φ₁(x), F.result) -ιs = {ι | loc(χ, ι) = φ₁.id} -∀ι ∈ χ . ι ∉ ιs ⇒ (∀z ∈ dom(χ(ι)) . χ(ι)(z) ∉ ιs) +v = φ₁(x) +loc(χ, v) ≠ φ₁.id +typetest(χ, v, F.result) --- [return] -χ, σ;φ₀;φ₁, return x;stmt* ⇝ (χ\ιs)\(φ₁.id), σ;φ₀[φ₁.ret↦φ₁(x)], ϕ₁.cont +χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ\(φ₁.id), σ;φ₀[φ₁.ret↦v], ϕ₁.cont ``` @@ -734,6 +726,7 @@ F = P.funcs(P.types(τ).methods(final)) |F.params| = 1 typetest(χ, ι, F.params₀.type) 𝔽 ∉ dom(χ.frames) +𝔽 > φ₀.id φ₁ = {id: 𝔽, vars: {F.paramsᵢ.name ↦ ι}, ret: final, cont: (drop final;stmt*)} χ₁ = region_fields(χ₀, ι) χ₂ = χ₁[frames ∪= 𝔽, pre_final = ιs, post_final ∪= {ι}] From f8450b991d5db370b2510cfc003250557151dbbc Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Sat, 29 Mar 2025 08:00:01 -0500 Subject: [PATCH 20/37] improve WF --- docs/opsem.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index bce153ee7..73723528b 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -258,18 +258,25 @@ wf_racefree(χ, σs) = (ι ∈ reachable(χ, σ₀)) ∧ (ι ∈ reachable(χ, σ₁)) ⇒ (σ₀ = σ₁) ∨ (loc(χ, ι) = Immutable) -// Stack allocations are reachable only from that stack. -// TODO: +// Frame allocations are reachable only from that frame or antecedent frames. wf_stacklocal(χ, σs) = - ∀σ₀, σ₁ ∈ σs . ∀φ ∈ σ₀ . (reachable(χ, σ₁) ∩ ιs = ∅) - where - ιs = {ι | loc(χ, ι) = φ.id} + ∀ι ∈ χ . + (loc(χ, ι) = 𝔽) ⇒ ∀ι′ ∈ χ . + ι ∈ reachable(χ, ι′) ⇒ + (loc(χ, ι′) = 𝔽′) ∧ (𝔽 <= 𝔽′) + +// Regions are externally unique. +wf_regionunique(χ) = + ∀ρ ∈ χ . (|ιs₂| ≤ 1) ∧ (|ρs| ≤ 1) ∧ (ρs = parents(χ, ρ)) + where + ιs₀ = members(χ, ρ) ∧ + ιs₁ = {ι | (ι ∈ χ) ∧ (loc(χ, ι) = ρ′) ∧ (ρ ≠ ρ′)} ∧ + ιs₂ = {ι | (ι ∈ ιs₁) ∧ (w ∈ dom(χ(ι))) ∧ (χ(ι)(w) ∈ ιs₀)} ∧ + ρs = {ρ′ | (ι ∈ ιs₂) ∧ (loc(χ, ι) = ρ′)} // The region graph is a tree. -// TODO: examine all references wf_regiontree(χ) = ∀ρ₀, ρ₁ ∈ χ . - (|parents(χ, ρ₀)| ≤ 1) ∧ (ρ₀ ∈ parents(χ, ρ₁) ⇒ (ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ, ρ₁, ρ₀)) ``` @@ -539,7 +546,6 @@ v = χ₀(ι)(w) --- [load] χ₀, σ;ϕ, bind x (load y);stmt* ⇝ χ₂, σ;ϕ[x↦v], stmt* -// TODO: what happens if safe_store is false? x ∉ ϕ ϕ(y) = {object: ι, field: w} w ∈ dom(P.types(typeof(χ₀, ι)).fields) From 7dc2e9fdfcfc2d9eea1f6dfa0ab37794ea48ba5c Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Mon, 31 Mar 2025 00:29:02 -0500 Subject: [PATCH 21/37] non-local return --- docs/opsem.md | 75 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 73723528b..2896ea92d 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -1,28 +1,25 @@ # Operational Semantics Still to do: -* Discharge (send/free/freeze?) when stack RC for the region and all children (recursively) is 0. - * Can free even if child regions have stack RC. +* Discharge (send/freeze?) when stack RC for the region and all children (recursively) is 0. * Could optimize by tracking the count of "busy" child regions. - * Can we still have an Arena per frame? - * Frame Arenas can't be discharged anyway. * How are Arenas different from uncounted regions? * How should they treat changing region type? * How should they treat merging, freezing, extract? * Behaviors and cowns. * Embedded object fields? * Arrays? Or model them as objects? -* Non-local returns. Dynamic failures: * `store`: * `w` is not a field of the target. - * Store to a finalizing object. - * Store a finalizing object. - * Store to an immutable object. - * Store a region that already has a parent. - * Store a region that would create a cycle. - * Store a frame value in a region or in a predecessor frame. + * Unsafe store: + * Store to a finalizing object. + * Store a finalizing object. + * Store to an immutable object. + * Store a region that already has a parent. + * Store a region that would create a cycle. + * Store a frame value in a region or in a predecessor frame. * `call dynamic`: * `w` is not a method of the target. * Arguments don't type check. @@ -81,12 +78,14 @@ p ∈ Primitive = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ v ∈ Value = ObjectId | Primitive | Reference ω ∈ Object = Ident ↦ Value + Condition = Return | Raise | Throw ϕ ∈ Frame = { id: FrameId, vars: Ident ↦ Value, ret: Ident, - cont: Statement* + cont: Statement*, + condition: Condition } σ ∈ Stack = Frame* @@ -579,8 +578,6 @@ v = typetest(χ, φ(y), T) The condition is not consumed. -> TODO: bind a result? - ```rs φ(x) = true @@ -600,7 +597,8 @@ All arguments are consumed. To keep them, `dup` them first. As such, an identifi ```rs newframe(χ, ϕ, F, x, y*, stmt*) = - {id: 𝔽, vars: {F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 1 .. |y*|}, ret: x, cont: stmt*} + { id: 𝔽, vars: {F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 1 .. |y*|}, + ret: x, cont: stmt*, condition: Return} where (𝔽 ∉ dom(χ.frames)) ∧ (𝔽 > φ.id) ∧ @@ -635,6 +633,48 @@ typetest(χ, v, F.result) ``` +## Non-Local Return + +Use `setreturn` before a return for a standard return. Use `setraise` for a non-local return, and `setthrow` for an error. + +Use `checkblock` after a `call` from inside a Smalltalk style block, such as a Verona lambda. If it's true, return the call result to propagate a non-local return out of a collection of blocks to the calling function, i.e. the syntactically enclosing scope. + +Use `checkfunc` after a `call` from inside a function. If it's true, return the call result to turn a non-local return into a local return, and to propagate an error. + +To catch errors, don't check the call condition. + +```rs + +--- [set return] +χ, σ;φ, setreturn;stmt* ⇝ χ, σ;φ[condition = Return], stmt* + +--- [set raise] +χ, σ;φ, setraise;stmt* ⇝ χ, σ;φ[condition = Raise], stmt* + +--- [set throw] +χ, σ;φ, setthrow;stmt* ⇝ χ, σ;φ[condition = Throw], stmt* + +x ∉ φ +--- [check block] +χ, σ;φ, bind x checkblock;stmt* ⇝ χ, σ;φ[x↦condition ≠ Return], stmt* + +x ∉ φ +φ.condition = Return +--- [check function] +χ, σ;φ, bind x checkfunc;stmt* ⇝ χ, σ;φ[x↦false], stmt* + +x ∉ φ +φ.condition = Raise +--- [check function] +χ, σ;φ, bind x checkfunc;stmt* ⇝ χ, σ;φ[x↦true, condition = Return], stmt* + +x ∉ φ +φ.condition = Throw +--- [check function] +χ, σ;φ, bind x checkfunc;stmt* ⇝ χ, σ;φ[x↦true], stmt* + +``` + ## Merge This allows merging two regions. The region being merged must either have no parent, or be a child of the region it's being merged into. If there are other stack references to the region being merged, a static type system may have the wrong region information for them. @@ -716,6 +756,8 @@ x ∉ φ ## Finalization +These steps can be taken regardless of what statement is pending. + ```rs region_fields(χ, ι) = @@ -733,7 +775,8 @@ F = P.funcs(P.types(τ).methods(final)) typetest(χ, ι, F.params₀.type) 𝔽 ∉ dom(χ.frames) 𝔽 > φ₀.id -φ₁ = {id: 𝔽, vars: {F.paramsᵢ.name ↦ ι}, ret: final, cont: (drop final;stmt*)} +φ₁ = { id: 𝔽, vars: {F.paramsᵢ.name ↦ ι}, + ret: final, cont: (drop final;stmt*), condition: Return} χ₁ = region_fields(χ₀, ι) χ₂ = χ₁[frames ∪= 𝔽, pre_final = ιs, post_final ∪= {ι}] --- [finalize true] From 93103c4ffa7c1e3d1e9b0c773b21bea4d9a066ea Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Mon, 31 Mar 2025 08:59:22 -0500 Subject: [PATCH 22/37] extract --- docs/opsem.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/opsem.md b/docs/opsem.md index 2896ea92d..9c0beb3bb 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -35,6 +35,9 @@ Dynamic failures: * Trying to merge a region that would create a cycle. * `freeze`: * Trying to freeze a value that is not an object in a region. +* `extract`: + * Trying to extract a value that is not an object in a region. + * Trying to extract a graph that is reachable from the region. ## Shape @@ -736,12 +739,14 @@ x ∉ φ ρ₀ = loc(χ₀, ι) ρ₁ ∉ χ₀ ιs = reachable(χ, ι) ∩ members(χ₀, ρ₀) +|{ι | (ι ∈ members(χ₀, ρ₀)) ∧ (w ∈ dom(χ₀(ι))) ∧ + (χ₀(ι)(w) = ι′) ∧ (ι′ ∈ \ios)}| = 0 ρs = {ρ | (ι ∈ ιs) ∧ (w ∈ dom(χ(ι))) ∧ (χ(ι)(w) = ι′) ∧ (ρ = loc(χ, ι′)) ∧ (ρ ≠ ρ₀)} rc = calc_stack_rc(χ₀, σ;φ, ιs) χ₁ = χ₀[regions(ρ₀)[stack_rc -= rc], - regions(ρ₁)↦{type: χ.regions(ρ₀).type, parents: {ρ₀}, stack_rc: rc}, + regions(ρ₁)↦{type: χ.regions(ρ₀).type, parents: ∅, stack_rc: rc}, ∀ι′ ∈ ιs . metadata(ι′)[location = ρ₁], ∀ρ ∈ ρs . regions(ρ)[parents = {ρ₁}]] --- [extract true] From 9060d7918905e525efbf9a6decee90a8fdba156d Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Mon, 31 Mar 2025 15:35:28 -0500 Subject: [PATCH 23/37] dynamic failure WIP --- docs/opsem.md | 93 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 19 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 9c0beb3bb..65be79a95 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -12,7 +12,6 @@ Still to do: Dynamic failures: * `store`: - * `w` is not a field of the target. * Unsafe store: * Store to a finalizing object. * Store a finalizing object. @@ -20,15 +19,6 @@ Dynamic failures: * Store a region that already has a parent. * Store a region that would create a cycle. * Store a frame value in a region or in a predecessor frame. -* `call dynamic`: - * `w` is not a method of the target. - * Arguments don't type check. -* `call static`: - * Arguments don't type check. -* `return`: - * The return value is not the only thing in the frame. - * The return value is allocated on the frame. - * The return value doesn't type check. * `merge`: * Trying to merge a value that isn't an object in a region. * Trying to merge a region that is a child of a region other than the destination region. @@ -39,6 +29,14 @@ Dynamic failures: * Trying to extract a value that is not an object in a region. * Trying to extract a graph that is reachable from the region. +Error values: +* Bad target. +* Bad field. +* Bad method. +* Bad argument type. +* Bad return location. +* Bad return type. + ## Shape ```rs @@ -228,7 +226,7 @@ This enforces a tree-shaped region graph, with a single reference from parent to ```rs safe_store(χ, ι, v) = - false if finalizing(ι) ∨ finalizing(v) + false if finalizing(ι) ⊻ finalizing(v) false if loc(χ, ι) = Immutable true if loc(χ, v) = Immutable true if loc(χ, ι) = 𝔽 ∧ (loc(χ, v) = ρ) @@ -536,12 +534,24 @@ x ∉ ϕ ι = ϕ(y) w ∈ dom(P.types(typeof(χ, ι)).fields) 𝕣 = {object: ι, field: w} ---- [field ref] +--- [fieldref] χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦𝕣]\y, stmt* +x ∉ ϕ +ϕ(y) ∉ ObjectId +v = // TODO: bad target error +--- [fieldref bad-target] +χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦v]\y, setthrow;return x + +x ∉ ϕ +ι = ϕ(y) +w ∉ dom(P.types(typeof(χ, ι)).fields) +v = // TODO: bad field error +--- [fieldref bad-field] +χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦v]\y, setthrow;return x + x ∉ ϕ ϕ(y) = {object: ι, field: w} -w ∈ dom(P.types(typeof(χ₀, ι)).fields) v = χ₀(ι)(w) χ₁ = region_stack_inc(χ₀, v) χ₂ = inc(χ₁, v) @@ -550,7 +560,6 @@ v = χ₀(ι)(w) x ∉ ϕ ϕ(y) = {object: ι, field: w} -w ∈ dom(P.types(typeof(χ₀, ι)).fields) v₀ = χ₀(ι)(w) v₁ = φ(z) safe_store(χ₀, ι, v₁) @@ -603,28 +612,55 @@ newframe(χ, ϕ, F, x, y*, stmt*) = { id: 𝔽, vars: {F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 1 .. |y*|}, ret: x, cont: stmt*, condition: Return} where - (𝔽 ∉ dom(χ.frames)) ∧ - (𝔽 > φ.id) ∧ - |F.params| = |y*| = |{y*}| ∧ - ∀i ∈ 1 .. |y*| . typetest(χ, φ(yᵢ), F.paramsᵢ.type) + (𝔽 ∉ dom(χ.frames)) ∧ (𝔽 > φ.id) + +typecheck(χ, φ, F, y*) = + |F.params| = |y*| = |{y*}| ∧ + ∀i ∈ 1 .. |y*| . typetest(χ, φ(yᵢ), F.paramsᵢ.type) x ∉ φ₀ F = P.funcs(𝕗) +typecheck(χ, φ₀, F, y*) φ₁ = newframe(χ, φ₀, F, x, y*, stmt*) --- [call static] χ, σ;φ₀, bind x (call 𝕗 y*);stmt* ⇝ χ∪(φ₁.id), σ;φ₀\{y*};φ₁, F.body +x ∉ φ +F = P.funcs(𝕗) +¬typecheck(χ, φ, F, y*) +v = // TODO: bad args error +--- [call static bad-args] +χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦v], setthrow;return x + x ∉ φ₀ -τ = typeof(χ, φ(y₀)) +τ = typeof(χ, φ₀(y₁)) F = P.funcs(P.types(τ).methods(w)) +typecheck(χ, φ₀, F, y*) φ₁ = newframe(χ, φ₀, F, x, y*, stmt*) --- [call dynamic] χ, σ;φ₀, bind x (call w y*);stmt* ⇝ χ∪(φ₁.id), σ;φ₀\{y*};φ₁, F.body +x ∉ φ +τ = typeof(χ, φ(y₁)) +w ∉ P.types(τ).methods +v = // TODO: bad method error +--- [call dynamic bad-method] +χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦v], setthrow;return x + +x ∉ φ +τ = typeof(χ, φ(y₁)) +F = P.funcs(P.types(τ).methods(w)) +¬typecheck(χ, φ, F, y*) +v = // TODO: bad args error +--- [call dynamic bad-args] +χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦v], setthrow;return x + ``` ## Return +This drops any remaining frame variables other than the return value. + ```rs dom(φ₁.vars) = {x} @@ -634,6 +670,25 @@ typetest(χ, v, F.result) --- [return] χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ\(φ₁.id), σ;φ₀[φ₁.ret↦v], ϕ₁.cont +dom(φ.vars) = {x, y} ∪ zs +--- [return] +χ, σ;φ, return x;stmt* ⇝ χ, σ;φ, drop y;return x + +dom(φ₁.vars) = {x} +v₀ = φ₁(x) +loc(χ, v₀) = φ₁.id +v₁ = // TODO: bad return loc error +--- [return bad-loc] +χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ, σ;φ₀;φ₁[y↦v₁], drop x;setthrow;return y + +dom(φ₁.vars) = {x} +v₀ = φ₁(x) +loc(χ, v₀) ≠ φ₁.id +¬typetest(χ, v₀, F.result) +v₁ = // TODO: bad return type error +--- [return bad-type] +χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ, σ;φ₀;φ₁[y↦v₁], drop x;setthrow;return y + ``` ## Non-Local Return From b441b5ed354267ed3b3af4d740add9f4be19877c Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Tue, 1 Apr 2025 09:05:39 -0500 Subject: [PATCH 24/37] dynamic failures --- docs/opsem.md | 163 ++++++++++++++++++++++++++------------------------ 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 65be79a95..20a387dd7 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -10,32 +10,12 @@ Still to do: * Embedded object fields? * Arrays? Or model them as objects? -Dynamic failures: -* `store`: - * Unsafe store: - * Store to a finalizing object. - * Store a finalizing object. - * Store to an immutable object. - * Store a region that already has a parent. - * Store a region that would create a cycle. - * Store a frame value in a region or in a predecessor frame. -* `merge`: - * Trying to merge a value that isn't an object in a region. - * Trying to merge a region that is a child of a region other than the destination region. - * Trying to merge a region that would create a cycle. -* `freeze`: - * Trying to freeze a value that is not an object in a region. -* `extract`: - * Trying to extract a value that is not an object in a region. - * Trying to extract a graph that is reachable from the region. - -Error values: -* Bad target. -* Bad field. -* Bad method. -* Bad argument type. -* Bad return location. -* Bad return type. +## Type Checker as Optimizer + +Dynamic failures that aren't trivial to eliminate with a type checker: +* `BadStore`. +* `BadReturnLoc`. +* Merge, freeze, extract failures. ## Shape @@ -75,7 +55,9 @@ P ∈ Program = } 𝕣 ∈ Reference = {object: ObjectId, field: Ident} -p ∈ Primitive = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ + Error = BadTarget | BadField | BadStore | BadMethod | BadArgs + | BadReturnLoc | BadReturnType +p ∈ Primitive = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | Error v ∈ Value = ObjectId | Primitive | Reference ω ∈ Object = Ident ↦ Value @@ -164,6 +146,7 @@ typeof(χ, v) = P.primitives(Signed × ℕ) if v ∈ Signed × ℕ P.primitives(Unsigned × ℕ) if v ∈ Unsigned × ℕ P.primitives(Float × ℕ) if v ∈ Float × ℕ + P.primitives(Error) if v ∈ Error χ.metadata(ι).type if ι = v Ref typeof(χ, χ(𝕣.object)(𝕣.field)) if 𝕣 = v @@ -539,16 +522,14 @@ w ∈ dom(P.types(typeof(χ, ι)).fields) x ∉ ϕ ϕ(y) ∉ ObjectId -v = // TODO: bad target error --- [fieldref bad-target] -χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦v]\y, setthrow;return x +χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦BadTarget]\y, throw;return x x ∉ ϕ ι = ϕ(y) w ∉ dom(P.types(typeof(χ, ι)).fields) -v = // TODO: bad field error --- [fieldref bad-field] -χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦v]\y, setthrow;return x +χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦BadField]\y, throw;return x x ∉ ϕ ϕ(y) = {object: ι, field: w} @@ -558,6 +539,11 @@ v = χ₀(ι)(w) --- [load] χ₀, σ;ϕ, bind x (load y);stmt* ⇝ χ₂, σ;ϕ[x↦v], stmt* +x ∉ ϕ +ϕ(y) ∉ Reference +--- [load bad-target] +χ, σ;ϕ, bind x (load y);stmt* ⇝ χ, σ;ϕ[x↦BadTarget], throw;return x + x ∉ ϕ ϕ(y) = {object: ι, field: w} v₀ = χ₀(ι)(w) @@ -571,6 +557,18 @@ safe_store(χ₀, ι, v₁) --- [store] χ₀, σ;ϕ, bind x (store y z);stmt* ⇝ χ₄[ι↦ω], σ;ϕ[x↦v₀]\z, stmt* +x ∉ ϕ +ϕ(y) ∉ Reference +--- [store bad-target] +χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadTarget], throw;return x + +x ∉ ϕ +ϕ(y) = {object: ι, field: w} +v = φ(z) +¬safe_store(χ₀, ι, v₁) +--- [store] +χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadStore], throw;return x + ``` ## Type Test @@ -628,9 +626,8 @@ typecheck(χ, φ₀, F, y*) x ∉ φ F = P.funcs(𝕗) ¬typecheck(χ, φ, F, y*) -v = // TODO: bad args error --- [call static bad-args] -χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦v], setthrow;return x +χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦BadArgs], throw;return x x ∉ φ₀ τ = typeof(χ, φ₀(y₁)) @@ -643,17 +640,15 @@ typecheck(χ, φ₀, F, y*) x ∉ φ τ = typeof(χ, φ(y₁)) w ∉ P.types(τ).methods -v = // TODO: bad method error --- [call dynamic bad-method] -χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦v], setthrow;return x +χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦BadMethod], throw;return x x ∉ φ τ = typeof(χ, φ(y₁)) F = P.funcs(P.types(τ).methods(w)) ¬typecheck(χ, φ, F, y*) -v = // TODO: bad args error --- [call dynamic bad-args] -χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦v], setthrow;return x +χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦BadArgs], throw;return x ``` @@ -667,69 +662,72 @@ dom(φ₁.vars) = {x} v = φ₁(x) loc(χ, v) ≠ φ₁.id typetest(χ, v, F.result) +φ₂ = φ₀[φ₁.ret↦v, condition = φ₁.condition] --- [return] -χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ\(φ₁.id), σ;φ₀[φ₁.ret↦v], ϕ₁.cont +χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ\(φ₁.id), σ;φ₂, ϕ₁.cont dom(φ.vars) = {x, y} ∪ zs --- [return] χ, σ;φ, return x;stmt* ⇝ χ, σ;φ, drop y;return x dom(φ₁.vars) = {x} -v₀ = φ₁(x) -loc(χ, v₀) = φ₁.id -v₁ = // TODO: bad return loc error +v = φ₁(x) +loc(χ, v) = φ₁.id --- [return bad-loc] -χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ, σ;φ₀;φ₁[y↦v₁], drop x;setthrow;return y +χ, σ;φ₀;φ₁, return x;stmt* ⇝ + χ, σ;φ₀;φ₁[y↦BadReturnLoc], drop x;throw;return y dom(φ₁.vars) = {x} -v₀ = φ₁(x) -loc(χ, v₀) ≠ φ₁.id -¬typetest(χ, v₀, F.result) -v₁ = // TODO: bad return type error +v = φ₁(x) +loc(χ, v) ≠ φ₁.id +¬typetest(χ, v, F.result) --- [return bad-type] -χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ, σ;φ₀;φ₁[y↦v₁], drop x;setthrow;return y +χ, σ;φ₀;φ₁, return x;stmt* ⇝ + χ, σ;φ₀;φ₁[y↦BadReturnType], drop x;throw;return y ``` ## Non-Local Return -Use `setreturn` before a return for a standard return. Use `setraise` for a non-local return, and `setthrow` for an error. +Use `raise` before a return for a non-local return, and `throw` for an error. -Use `checkblock` after a `call` from inside a Smalltalk style block, such as a Verona lambda. If it's true, return the call result to propagate a non-local return out of a collection of blocks to the calling function, i.e. the syntactically enclosing scope. - -Use `checkfunc` after a `call` from inside a function. If it's true, return the call result to turn a non-local return into a local return, and to propagate an error. - -To catch errors, don't check the call condition. +Use `reraise` after a `call` from inside a Smalltalk style block, such as a Verona lambda. This propagates both non-local returns and errors. Use `rethrow` after a `call` from inside a function. This returns a non-local return as local, and propagates errors. Use `catch` instead of either to capture a non-local return or error without propagating it. ```rs ---- [set return] -χ, σ;φ, setreturn;stmt* ⇝ χ, σ;φ[condition = Return], stmt* +--- [raise] +χ, σ;φ, raise;stmt* ⇝ χ, σ;φ[condition = Raise], stmt* ---- [set raise] -χ, σ;φ, setraise;stmt* ⇝ χ, σ;φ[condition = Raise], stmt* +--- [throw] +χ, σ;φ, throw;stmt* ⇝ χ, σ;φ[condition = Throw], stmt* ---- [set throw] -χ, σ;φ, setthrow;stmt* ⇝ χ, σ;φ[condition = Throw], stmt* +--- [catch] +χ, σ;φ, catch;stmt* ⇝ χ, σ;φ[condition = Return], stmt* -x ∉ φ ---- [check block] -χ, σ;φ, bind x checkblock;stmt* ⇝ χ, σ;φ[x↦condition ≠ Return], stmt* +x ∈ φ +φ.condition = Return +--- [reraise] +χ, σ;φ, reraise x;stmt* ⇝ χ, σ;φ, stmt* + +x ∈ φ +φ.condition ≠ Return +--- [reraise] +χ, σ;φ, reraise x;stmt* ⇝ χ, σ;φ, return x x ∉ φ φ.condition = Return ---- [check function] -χ, σ;φ, bind x checkfunc;stmt* ⇝ χ, σ;φ[x↦false], stmt* +--- [rethrow] +χ, σ;φ, rethrow x;stmt* ⇝ χ, σ;φ, stmt* x ∉ φ φ.condition = Raise ---- [check function] -χ, σ;φ, bind x checkfunc;stmt* ⇝ χ, σ;φ[x↦true, condition = Return], stmt* +--- [rethrow] +χ, σ;φ, rethrow x;stmt* ⇝ χ, σ;φ[condition = Return], return x x ∉ φ φ.condition = Throw ---- [check function] -χ, σ;φ, bind x checkfunc;stmt* ⇝ χ, σ;φ[x↦true], stmt* +--- [rethrow] +χ, σ;φ, rethrow x;stmt* ⇝ χ, σ;φ, return x ``` @@ -747,16 +745,16 @@ loc(χ₀, φ(y)) = ρ₁ (ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ₀, ρ₁, ρ₀) ∧ ({ρ₀} ⊇ parents(χ₀, ρ₁)) ιs = members(χ₀, ρ₁) χ₁ = χ₀[∀ι ∈ ιs . metadata(ι)[location = ρ₀]] - [regions(ρ₀)[stack_rc += regions(ρ₁).stack_rc)]] ---- [merge true] -χ₀, σ;φ, bind x (merge w y);stmt* ⇝ χ₁\ρ₁, σ;φ[x↦true], stmt* + [regions(ρ₀)[stack_rc += regions(ρ₁).stack_rc]] +--- [merge] +χ₀, σ;φ, bind x (merge w y);stmt* ⇝ χ₁\ρ₁, σ;φ[x↦φ(y)], stmt* x ∉ φ (loc(χ, φ(w)) ≠ ρ₀) ∨ (loc(χ, φ(y)) ≠ ρ₁) ∨ (ρ₀ = ρ₁) ∨ is_ancestor(χ₀, ρ₁, ρ₀) ∨ ({ρ₀} ̸⊇ parents(χ, ρ₁)) ---- [merge false] -χ, σ;φ, bind x (merge w y);stmt* ⇝ χ, σ;φ[x↦false], stmt* +--- [merge bad-target] +χ, σ;φ, bind x (merge w y);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x ``` @@ -795,7 +793,7 @@ x ∉ φ ρ₁ ∉ χ₀ ιs = reachable(χ, ι) ∩ members(χ₀, ρ₀) |{ι | (ι ∈ members(χ₀, ρ₀)) ∧ (w ∈ dom(χ₀(ι))) ∧ - (χ₀(ι)(w) = ι′) ∧ (ι′ ∈ \ios)}| = 0 + (χ₀(ι)(w) = ι′) ∧ (ι′ ∈ ιs)}| = 0 ρs = {ρ | (ι ∈ ιs) ∧ (w ∈ dom(χ(ι))) ∧ (χ(ι)(w) = ι′) ∧ (ρ = loc(χ, ι′)) ∧ (ρ ≠ ρ₀)} @@ -804,13 +802,22 @@ rc = calc_stack_rc(χ₀, σ;φ, ιs) regions(ρ₁)↦{type: χ.regions(ρ₀).type, parents: ∅, stack_rc: rc}, ∀ι′ ∈ ιs . metadata(ι′)[location = ρ₁], ∀ρ ∈ ρs . regions(ρ)[parents = {ρ₁}]] ---- [extract true] -χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁, σ;φ[x↦true], stmt* +--- [extract] +χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁, σ;φ[x↦ι]\y, stmt* x ∉ φ ρ ≠ loc(χ, φ(y)) ---- [extract false] -χ, σ;φ, bind x (extract y);stmt* ⇝ χ, σ;φ[x↦false], stmt* +--- [extract bad-target] +χ, σ;φ, bind x (extract y);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x + +x ∉ φ +ι = φ(y) +ρ₀ = loc(χ₀, ι) +ιs = reachable(χ, ι) ∩ members(χ₀, ρ₀) +|{ι | (ι ∈ members(χ₀, ρ₀)) ∧ (w ∈ dom(χ₀(ι))) ∧ + (χ₀(ι)(w) = ι′) ∧ (ι′ ∈ ιs)}| > 0 +--- [extract bad-target] +χ, σ;φ, bind x (extract y);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x ``` From 8ec1b92e2545180fc376289a4238bf2d21d97806 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Fri, 4 Apr 2025 13:43:51 -0500 Subject: [PATCH 25/37] concurrent semantics WIP --- docs/opsem.md | 325 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 266 insertions(+), 59 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 20a387dd7..674afeed5 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -23,15 +23,19 @@ Dynamic failures that aren't trivial to eliminate with a type checker: n ∈ ℕ w, x, y, z ∈ Ident -xs, ys, zs ∈ 𝒫(Ident) +ws, xs, ys, zs ∈ 𝒫(Ident) τ ∈ TypeId -𝕗 ∈ FuncId +𝕗 ∈ FunctionId ρ ∈ RegionId 𝔽 ∈ FrameId ι ∈ ObjectId ιs ∈ 𝒫(ObjectId) +π ∈ CownId +𝛽 ∈ BehaviorId +θ ∈ ThreadId T ∈ Type = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | TypeId | Ref TypeId + | Cown TypeId 𝕥 ∈ TypeDesc = { @@ -40,7 +44,7 @@ T ∈ Type = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | TypeId | Re methods: Ident ↦ FuncId } -F ∈ Func = +F ∈ Function = { params: {name: Ident, type: Type}*, result: Type, @@ -49,16 +53,16 @@ F ∈ Func = P ∈ Program = { - primitives: Type ↦ TypeDesc, + primitives: Type ↦ TypeId, types: TypeId ↦ TypeDesc, - funcs: FuncId ↦ Func + functions: FunctionId ↦ Function } -𝕣 ∈ Reference = {object: ObjectId, field: Ident} - Error = BadTarget | BadField | BadStore | BadMethod | BadArgs +𝕣 ∈ Reference = {object: ObjectId | CownId, field: Ident} + Error = BadType | BadTarget | BadField | BadStore | BadMethod | BadArgs | BadReturnLoc | BadReturnType p ∈ Primitive = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | Error -v ∈ Value = ObjectId | Primitive | Reference +v ∈ Value = ObjectId | Primitive | Reference | CownId ω ∈ Object = Ident ↦ Value Condition = Return | Raise | Throw @@ -67,6 +71,7 @@ v ∈ Value = ObjectId | Primitive | Reference id: FrameId, vars: Ident ↦ Value, ret: Ident, + type: Type, cont: Statement*, condition: Condition } @@ -95,6 +100,9 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena data: ObjectId ↦ Object, metadata: ObjectId ↦ Metadata, regions: RegionId ↦ Region, + cowns: CownId ↦ Cown, + behaviors: BehaviorId ↦ Behavior, + threads: ThreadId ↦ Thread, frames: 𝒫(FrameId), pre_final: 𝒫(ObjectId), post_final: 𝒫(ObjectId), @@ -102,6 +110,31 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena post_final_r: 𝒫(RegionId) } +Π ∈ Cown = + { + type: Type, + content: Value, + queue: BehaviorId*, + read: ℕ, + rc: ℕ + } + +B ∈ Behavior = + { + read: Ident ↦ CownId, + write: Ident ↦ CownId, + capture: Ident ↦ Value, + body: Statement*, + result: CownId + } + +Θ ∈ Thread = + { + stack: Frame*, + cont: Statement*, + result: CownId + } + Heap, Stack, Statement* ⇝ Heap, Stack, Statement* ``` @@ -124,15 +157,19 @@ x ∈ φ ≝ x ∈ dom(φ.vars) // Heap objects. ι ∈ χ ≝ ι ∈ dom(χ.data) χ(ι) = χ.data(ι) +χ[ι↦ω] = χ[data(ι)↦ω] χ[ι↦(ω, τ, 𝔽)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: 𝔽, rc: 1}] -χ[ι↦(ω, τ, ρ)] = χ[data(ι)↦ω, metadata(ι)↦{type: τ, location: ρ, rc: 1}] +χ[ι↦(ω, τ, ρ)] = χ[data(ι)↦ω, + metadata(ι)↦{type: τ, location: ρ, rc: 1}, + regions(ρ)[stack_rc += 1]] χ\ι = χ\{ι} χ\ιs = χ[data = data\ιs, metadata = metadata\ιs] // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) -χ[ρ↦R] = χ[regions(ρ)↦{type: R, parents: ∅, stack_rc: 1}] -χ\ρ = χ[regions\ρ] +χ[ρ↦R] = χ[regions(ρ)↦{type: R, parents: ∅, stack_rc: 0}] +χ\ρ = χ\{ρ} +χ\ρs = χ[regions\ρs] ``` @@ -148,11 +185,13 @@ typeof(χ, v) = P.primitives(Float × ℕ) if v ∈ Float × ℕ P.primitives(Error) if v ∈ Error χ.metadata(ι).type if ι = v - Ref typeof(χ, χ(𝕣.object)(𝕣.field)) if 𝕣 = v + Ref P.types(typeof(χ, ι).field(𝕣.field).type if (𝕣 = v) ∧ (𝕣.object = ι) + Ref χ(π).type if (𝕣 = v) ∧ (𝕣.object = π) + Cown χ(π).type if π = v // Subtype test. typetest(χ, v, T) = - T = typeof(χ, v) if v ∈ Reference + T = typeof(χ, v) if (v ∈ Reference) ∨ (v ∈ CownId) T ∈ P.types(typeof(χ, v)).supertypes otherwise ``` @@ -171,10 +210,12 @@ reachable(χ, {v} ∪ vs) = reachable(χ, v) ∪ reachable(χ, vs) reachable(χ, v) = reachable(χ, v, ∅) reachable(χ, p, ιs) = ιs +reachable(χ, π, ιs) = ιs reachable(χ, 𝕣, ιs) = reachable(χ, 𝕣.object, ιs) reachable(χ, ι, ιs) = ιs if ι ∈ ιs reachable(χ, ι, {ι} ∪ ιs, dom(χ(ι))) otherwise +reachable(χ, π, ιs) = ιs reachable(χ, ι, ιs, ∅) = ιs reachable(χ, ι, ιs, {w} ∪ ws) = @@ -183,11 +224,13 @@ reachable(χ, ι, ιs, w) = reachable(χ, χ(ι)(w), ιs) // Region. loc(χ, p) = Immutable +loc(χ, π) = Immutable loc(χ, 𝕣) = loc(χ, 𝕣.object) loc(χ, ι) = loc(χ, ι′) if χ.metadata(ι).location = ι′ χ.metadata(ι).location if ι ∈ χ Immutable otherwise +loc(χ, π) = Immutable same_loc(χ, v₀, v₁) = (loc(χ, v₀) = loc(χ, v₁)) members(χ, ρ) = {ι | (ι ∈ χ) ∧ (loc(χ, ι) = ρ)} @@ -208,20 +251,30 @@ This enforces a tree-shaped region graph, with a single reference from parent to ```rs -safe_store(χ, ι, v) = - false if finalizing(ι) ⊻ finalizing(v) - false if loc(χ, ι) = Immutable +// TODO: v = π +safe_store(χ, Immutable, v) = false +safe_store(χ, 𝔽, v) = + true if loc(χ, v) = Immutable + true if loc(χ, v) = ρ + true if (loc(χ, v) = 𝔽′) ∧ (𝔽 >= 𝔽′) + false otherwise +safe_store(χ, ρ, v) = + false if finalizing(χ, v) + true if loc(χ, v) = Immutable + true if loc(χ, v) = ρ + true if (loc(χ, v) = ρ′) ∧ (parents(χ, ρ′) = ∅) ∧ ¬is_ancestor(χ, ρ′, ρ) + false otherwise +safe_store(χ, μ, v) = + false if finalizing(χ, v) true if loc(χ, v) = Immutable - true if loc(χ, ι) = 𝔽 ∧ (loc(χ, v) = ρ) - true if loc(χ, ι) = 𝔽 ∧ (loc(χ, v) = 𝔽′) ∧ (𝔽 >= 𝔽′) - true if same_loc(χ, ι, v) - true if (ρ₀ = loc(χ, ι)) ∧ (ρ₁ = loc(χ, v)) ∧ - (parents(χ, ρ₁) = ∅) ∧ ¬is_ancestor(χ, ρ₁, ρ₀) + true if (loc(χ, v) = ρ) ∧ (parents(χ, ρ) = ∅) false otherwise finalizing(χ, p) = false +finalizing(χ, π) = false finalizing(χ, 𝕣) = finalizing(χ, 𝕣.object) finalizing(χ, ι) = (ι ∈ χ.pre_final) ∨ (ι ∈ χ.post_final) +finalizing(χ, π) = false ``` @@ -262,6 +315,8 @@ wf_regiontree(χ) = ∀ρ₀, ρ₁ ∈ χ . (ρ₀ ∈ parents(χ, ρ₁) ⇒ (ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ, ρ₁, ρ₀)) +// TODO: a cown contains an immutable object or a region with no parents. + ``` ## Region Type Change @@ -324,12 +379,14 @@ enable-rc(χ, ι) = (loc(χ, ι) = ρ ∧ ρ.type = RegionRC) ∨ (loc(χ, ι) = Immutable) region_stack_inc(χ, p) = χ +region_stack_inc(χ, π) = χ region_stack_inc(χ, 𝕣) = region_stack_inc(χ, 𝕣.object) region_stack_inc(χ, ι) = χ[regions(ρ)[stack_rc += 1]] if loc(χ, ι) = ρ χ otherwise region_stack_dec(χ, p) = χ +region_stack_dec(χ, π) = χ region_stack_dec(χ, 𝕣) = region_stack_dec(χ, 𝕣.object) region_stack_dec(χ, ι) = χ[pre_final_r ∪= {ρ}] if @@ -340,6 +397,7 @@ region_stack_dec(χ, ι) = χ otherwise region_add_parent(χ, ι, p) = χ +region_add_parent(χ, ι, π) = χ region_add_parent(χ, ι, 𝕣) = region_add_parent(χ, ι, 𝕣.object) region_add_parent(χ, ι, ι′) = χ[regions(ρ′)[parents ∪= {ρ})]] if @@ -348,6 +406,7 @@ region_add_parent(χ, ι, ι′) = χ otherwise region_remove_parent(χ, ι, p) = χ +region_remove_parent(χ, ι, π) = χ region_remove_parent(χ, ι, 𝕣) = region_remove_parent(χ, ι, 𝕣.object) region_remove_parent(χ, ι, ι′) = χ[regions(ρ)[parents \= {ρ′})]] if @@ -356,6 +415,7 @@ region_remove_parent(χ, ι, ι′) = χ otherwise inc(χ, p) = χ +inc(χ, π) = χ[cowns(π)[rc += 1]] inc(χ, 𝕣) = dec(χ, 𝕣.object) inc(χ, ι) = inc(χ, ι′) if χ.metadata(ι).location = ι′ @@ -363,10 +423,11 @@ inc(χ, ι) = χ otherwise dec(χ, p) = χ +dec(χ, π) = χ[cowns(π)[rc -= 1]] // TODO: free dec(χ, 𝕣) = dec(χ, 𝕣.object) dec(χ, ι) = dec(χ, ι′) if χ.metadata(ι).location = ι′ - free(χ, ι) if enable-rc(χ, ι) ∧ (χ.metadata(ι).rc = 1) + χ[pre_final ∪= {ι}] if enable-rc(χ, ι) ∧ (χ.metadata(ι).rc = 1) χ[metadata(ι)[rc -= 1]] if enable-rc(χ, ι) χ otherwise @@ -384,6 +445,9 @@ gc(χ, σ, ρ) = χ otherwise where ιs = members(χ₀, ρ) ∧ + // TODO: doesn't work with finalization. + // need to keep everything we might look at during finalization alive. + // if A can reach B, and we select B but not A, then we can't finalize A. ιs₀ ⊆ ιs \ reachable(χ₀, gc_roots(χ₀, σ, ρ)) ∧ ιs₁ = ιs \ ιs₀ ∧ χ′ = gc_dec(χ, ιs₀, ιs₁) @@ -406,6 +470,7 @@ gc_dec_fields(χ, ι, {w} ∪ ws, ιs₁) = χ′ = gc_dec_field(χ₀, ι, χ(ι)(w), ιs₁) ∧ gc_dec_field(χ, ι, p, ιs₁) = χ +gc_dec_field(χ, ι, π, ιs₁) = χ gc_dec_field(χ, ι, 𝕣, ιs₁) = gc_dec_field(χ, ι, 𝕣.object) gc_dec_field(χ, ι, ι′, ιs₁) = dec(χ, ι′) if (ι′ ∈ ιs₁) ∨ (loc(χ, ι′) = Immutable) @@ -417,6 +482,8 @@ gc_dec_field(χ, ι, ι′, ιs₁) = ```rs +// TODO: only call free_fields after finalizing the object. +// remove this, put object into pre_final directly from `dec`. free(χ, ι) = χ′[pre_final ∪= ιs] where @@ -430,6 +497,7 @@ free_fields(χ, ιs, ι, {w} ∪ ws) = χ₁′ ιs′ = free_field(χ, ιs, ι, w) free_field(χ, ιs, ι, p) = χ, ιs +free_field(χ, ιs, ι, π) = χ, ιs free_field(χ, ιs, ι, 𝕣) = free_field(χ, ιs, ι, 𝕣.object) free_field(χ, ιs, ι, ι′) = χ, ιs if ι′ ∈ ιs @@ -450,12 +518,14 @@ For an "address-taken" local variable, i.e. a `var` as opposed to a `let`, alloc ```rs -newobject(χ, τ, (y, z)*) = - ω where - f = P.types(τ).fields ∧ - {y | y ∈ (y, z)*} = dom(f) ∧ - ω = {y ↦ φ(z) | y ∈ (y, z)*} ∧ - ∀y ∈ dom(ω) . typetest(χ, f(y).type, ω(y)) +once(x*) = |{x | x ∈ x*}| = |x*| +once((x, y)*) = |{y | y ∈ (x, y)*}| = |(x, y)*| + +newobject(φ, (y, z)*) = {y ↦ φ(z) | y ∈ (y, z)*} + +typecheck(χ, τ, ω) = + (dom(P.types(τ).fields) = dom(ω)) ∧ + ∀w ∈ dom(ω) . typetest(χ, P.types(τ).fields(w), ω(w)) x ∉ φ --- [new primitive] @@ -463,27 +533,72 @@ x ∉ φ x ∉ φ ι ∉ χ -zs = {z | z ∈ (y, z)*} ∧ |zs| = |(y, z)*| -ω = newobject(χ, τ, (y, z)*) +once((y, z)*) +∀z ∈ (y, z)* . safe_store(χ, φ.id, φ(z)) +ω = newobject(φ, (y, z)*) +typecheck(χ, τ, ω) --- [new stack] χ, σ;φ, bind x (new τ (y, z)*);stmt* ⇝ χ[ι↦(ω, τ, φ.id)], σ;φ[x↦ι]\zs, stmt* +x ∉ φ +∃z ∈ (y, z)* . ¬safe_store(χ, φ.id, φ(z)) +--- [new stack bad-store] +χ, σ;φ, bind x (new τ (y, z)*);stmt* ⇝ χ, σ;φ[x↦BadStore], throw;return x + +x ∉ φ +ω = newobject(φ, (y, z)*) +¬once((y, z)*) ∨ ¬typecheck(χ, τ, ω) +--- [new stack bad-type] +χ, σ;φ, bind x (new τ (y, z)*);stmt* ⇝ χ, σ;φ[x↦BadType], throw;return x + x ∉ φ ι ∉ χ ρ = loc(χ, φ(w)) -zs = {z | z ∈ (y, z)*} ∧ |zs| = |(y, z)*| -ω = newobject(χ, τ, (y, z)*) +once((y, z)*) +ω = newobject(φ, (y, z)*) +typecheck(χ, τ, ω) --- [new heap] χ, σ;φ, bind x (new w τ (y, z)*);stmt* ⇝ χ[ι↦(ω, τ, ρ)], σ;φ[x↦ι]\zs, stmt* +x ∉ φ +ρ ≠ loc(χ, φ(w)) +--- [new heap bad-target] +χ, σ;φ, bind x (new w τ (y, z)*);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x + +x ∉ φ +ρ = loc(χ, φ(w)) +∃z ∈ (y, z)* . ¬safe_store(χ, ρ, φ(z)) +--- [new heap bad-store] +χ, σ;φ, bind x (new τ (y, z)*);stmt* ⇝ χ, σ;φ[x↦BadStore], throw;return x + +x ∉ φ +ρ = loc(χ, φ(w)) +ω = newobject(φ, (y, z)*) +¬once((y, z)*) ∨ ¬typecheck(χ, τ, ω) +--- [new heap bad-type] +χ, σ;φ, bind x (new w τ (y, z)*);stmt* ⇝ χ, σ;φ[x↦BadType], throw;return x + x ∉ φ ι ∉ χ ρ ∉ χ -zs = {z | z ∈ (y, z)*} ∧ |zs| = |(y, z)*| -ω = newobject(χ, τ, (y, z)*) +once((y, z)*) +ω = newobject(φ, (y, z)*) +typecheck(χ, τ, ω) --- [new region] χ, σ;φ, bind x (new R τ (y, z)*);stmt* ⇝ χ[ρ↦R][ι↦(ω, τ, ρ)], σ;φ[x↦ι]\zs, stmt* +x ∉ φ +ρ ∉ χ +∃z ∈ (y, z)* . ¬safe_store(χ, ρ, φ(z)) +--- [new heap bad-store] +χ, σ;φ, bind x (new R τ (y, z)*);stmt* ⇝ χ, σ;φ[x↦BadStore], throw;return x + +x ∉ φ +ω = newobject(φ, (y, z)*) +¬once((y, z)*) ∨ ¬typecheck(χ, τ, ω) +--- [new region bad-type] +χ, σ;φ, bind x (new R τ (y, z)*);stmt* ⇝ χ, σ;φ[x↦BadType], throw;return x + ``` ## Duplicate, Drop @@ -532,8 +647,8 @@ w ∉ dom(P.types(typeof(χ, ι)).fields) χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦BadField]\y, throw;return x x ∉ ϕ -ϕ(y) = {object: ι, field: w} -v = χ₀(ι)(w) +v = χ₀(ι)(w) if ϕ(y) = {object: ι, field: w} + χ₀.cowns(π).value if ϕ(y) = {object: π, field: w} χ₁ = region_stack_inc(χ₀, v) χ₂ = inc(χ₁, v) --- [load] @@ -545,10 +660,10 @@ x ∉ ϕ χ, σ;ϕ, bind x (load y);stmt* ⇝ χ, σ;ϕ[x↦BadTarget], throw;return x x ∉ ϕ -ϕ(y) = {object: ι, field: w} -v₀ = χ₀(ι)(w) +v₀ = χ₀(ι)(w) if ϕ(y) = {object: ι, field: w} + χ₀.cowns(π).value if ϕ(y) = {object: π, field: w} v₁ = φ(z) -safe_store(χ₀, ι, v₁) +safe_store(χ₀, loc(χ₀, ι), v₁) ω = χ₀(ι)[w↦v₁] χ₁ = region_stack_inc(χ₀, v₀) χ₂ = region_remove_parent(χ₁, ι, v₀) @@ -565,7 +680,7 @@ x ∉ ϕ x ∉ ϕ ϕ(y) = {object: ι, field: w} v = φ(z) -¬safe_store(χ₀, ι, v₁) +¬safe_store(χ₀, loc(χ, ι), v₁) --- [store] χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadStore], throw;return x @@ -608,44 +723,49 @@ All arguments are consumed. To keep them, `dup` them first. As such, an identifi newframe(χ, ϕ, F, x, y*, stmt*) = { id: 𝔽, vars: {F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 1 .. |y*|}, - ret: x, cont: stmt*, condition: Return} + ret: x, type: F.result, cont: stmt*, condition: Return} where (𝔽 ∉ dom(χ.frames)) ∧ (𝔽 > φ.id) typecheck(χ, φ, F, y*) = - |F.params| = |y*| = |{y*}| ∧ + |F.params| = |y*| ∧ ∀i ∈ 1 .. |y*| . typetest(χ, φ(yᵢ), F.paramsᵢ.type) x ∉ φ₀ -F = P.funcs(𝕗) +once(y*) +F = P.functions(𝕗) typecheck(χ, φ₀, F, y*) φ₁ = newframe(χ, φ₀, F, x, y*, stmt*) --- [call static] χ, σ;φ₀, bind x (call 𝕗 y*);stmt* ⇝ χ∪(φ₁.id), σ;φ₀\{y*};φ₁, F.body x ∉ φ -F = P.funcs(𝕗) +once(y*) +F = P.functions(𝕗) ¬typecheck(χ, φ, F, y*) --- [call static bad-args] χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦BadArgs], throw;return x x ∉ φ₀ +once(y*) τ = typeof(χ, φ₀(y₁)) -F = P.funcs(P.types(τ).methods(w)) +F = P.functions(P.types(τ).methods(w)) typecheck(χ, φ₀, F, y*) φ₁ = newframe(χ, φ₀, F, x, y*, stmt*) --- [call dynamic] χ, σ;φ₀, bind x (call w y*);stmt* ⇝ χ∪(φ₁.id), σ;φ₀\{y*};φ₁, F.body x ∉ φ +once(y*) τ = typeof(χ, φ(y₁)) w ∉ P.types(τ).methods --- [call dynamic bad-method] χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦BadMethod], throw;return x x ∉ φ +once(y*) τ = typeof(χ, φ(y₁)) -F = P.funcs(P.types(τ).methods(w)) +F = P.functions(P.types(τ).methods(w)) ¬typecheck(χ, φ, F, y*) --- [call dynamic bad-args] χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦BadArgs], throw;return x @@ -661,29 +781,36 @@ This drops any remaining frame variables other than the return value. dom(φ₁.vars) = {x} v = φ₁(x) loc(χ, v) ≠ φ₁.id -typetest(χ, v, F.result) +typetest(χ, v, φ.type) φ₂ = φ₀[φ₁.ret↦v, condition = φ₁.condition] --- [return] χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ\(φ₁.id), σ;φ₂, ϕ₁.cont +dom(φ.vars) = {x} +v = φ(x) +loc(χ, v) ≠ φ.id +typetest(χ, v, φ.type) +// TODO: put v in the result cown? +// safe_store to result cown +--- [return] +χ, φ, return x;stmt* ⇝ χ\(φ.id), ∅, ∅ + dom(φ.vars) = {x, y} ∪ zs --- [return] χ, σ;φ, return x;stmt* ⇝ χ, σ;φ, drop y;return x -dom(φ₁.vars) = {x} -v = φ₁(x) -loc(χ, v) = φ₁.id +dom(φ.vars) = {x} +v = φ(x) +loc(χ, v) = φ.id --- [return bad-loc] -χ, σ;φ₀;φ₁, return x;stmt* ⇝ - χ, σ;φ₀;φ₁[y↦BadReturnLoc], drop x;throw;return y +χ, σ;φ, return x;stmt* ⇝ χ, σ;φ[y↦BadReturnLoc], drop x;throw;return y -dom(φ₁.vars) = {x} -v = φ₁(x) -loc(χ, v) ≠ φ₁.id +dom(φ.vars) = {x} +v = φ(x) +loc(χ, v) ≠ φ.id ¬typetest(χ, v, F.result) --- [return bad-type] -χ, σ;φ₀;φ₁, return x;stmt* ⇝ - χ, σ;φ₀;φ₁[y↦BadReturnType], drop x;throw;return y +χ, σ;φ, return x;stmt* ⇝ χ, σ;φ[y↦BadReturnType], drop x;throw;return y ``` @@ -837,13 +964,13 @@ region_fields(χ, ι) = χ₀.pre_final = {ι} ∪ ιs τ = typeof(χ, ι) -F = P.funcs(P.types(τ).methods(final)) +F = P.functions(P.types(τ).methods(final)) |F.params| = 1 typetest(χ, ι, F.params₀.type) 𝔽 ∉ dom(χ.frames) 𝔽 > φ₀.id φ₁ = { id: 𝔽, vars: {F.paramsᵢ.name ↦ ι}, - ret: final, cont: (drop final;stmt*), condition: Return} + ret: final, type: F.result, cont: (drop final;stmt*), condition: Return} χ₁ = region_fields(χ₀, ι) χ₂ = χ₁[frames ∪= 𝔽, pre_final = ιs, post_final ∪= {ι}] --- [finalize true] @@ -874,3 +1001,83 @@ final ∉ dom(P.types(τ).methods) χ, σ;φ, stmt* ⇝ χ[post_final_r = ρs]\ρ, σ;φ, stmt* ``` + +## Behaviors + +A `when` creates a behavior and returns a cown. +The cown that's created has no value until the `when` completes, and is busy in the meantime. It has the behavior ID in its queue. +Put the behavior ID at the end of a behavior queue for each cown. +The frame will start by moving captures and cowns into frame variables with the same names as the object fields, then destroying the object and its region. + +When all of a behavior's cowns have the behavior at the front of their queue, the behavior executes. The behavior is taken out of the pending set. When the behavior finishes executing, the front of each cown's queue is popped. + +```rs + +ready(χ, 𝛽) = + (∀π ∈ πs . χ(π).queue = 𝛽;𝛽*) ∧ + (∀π ∈ χ(𝛽).write . χ(π).read = 0) + where + πs = {π | π ∈ (χ(𝛽).read ∪ χ(𝛽).write ∪ {χ(𝛽).result})} + +// TODO: return some "read-only" view of the value. +// no rc ops for read-only. +read-acquire(χ, π) = + inc(χ′, v), v if loc(χ, v) = Immutable + // TODO: + where + (χ.cowns(π).queue = 𝛽;𝛽*) ∧ + (χ′ = χ[cowns(π)[queue = 𝛽*, read += 1]]) ∧ + (v = χ.cowns(π).value) + +write-acquire(χ, π) = + inc(χ′, π), {object: π, field: final} + where + (χ.cowns(π).queue = 𝛽;𝛽*) ∧ + (χ′ = χ[cowns(π)[queue = 𝛽*]]) + +// TODO: +// regions put in a behavior need to set a parent to prevent them being put anywhere else. +// delay until all captured regions have stack_rc = 0? +// has to check child regions as well. +x ∉ φ +𝛽 ∉ dom(χ.behaviors) +π ∉ dom(χ.cowns) +once(w*;y*;z*) +∀w ∈ w* . φ(w) ∈ CownId +∀y ∈ y* . φ(y) ∈ CownId +∀z ∈ z* . safe_store(χ, 𝛽, φ(z)) +χ′ = χ[∀π′ ∈ {φ(x′) | (x′ ∈ w*;y*)} . cowns(π′)[queue ++ 𝛽]] +Π = { type: T, value: false, queue: 𝛽 } +B = { read: {w ↦ φ(w) | w ∈ w*}, + write: {y ↦ φ(y) | y ∈ y*}, + capture: {z ↦ φ(z) | z ∈ z*}, + body: stmt₀*, + result: π } +--- [when] +χ, σ;φ, bind x (when T (read w*) (write y*) (capture z*) stmt₀*);stmt₁* ⇝ + χ′[cowns(π)↦Π, behaviors(𝛽)↦B]∪𝔽, σ;φ[x↦π]\(w*;y*;z*), stmt₁* + +𝛽 ∈ dom(χ.behaviors) +θ ∉ dom(χ.threads) +𝔽 ∉ χ +ready(χ, 𝛽) +B = χ.behaviors(𝛽) +φ = { id: 𝔽, + vars: {x ↦ B.capture(x) | x ∈ dom(B.capture)}, + ret: final, + cont: ∅, + condition: Return } + [∀w ∈ dom(B.read) . vars(w)↦read-acquire(vars(w))] + [∀y ∈ dom(B.write) . vars(y)↦write-acquire(vars(w))] +Θ = { stack: φ, cont: B.body, result: π } +--- [start behavior] +χ ⇝ χ[behaviors \= {𝛽}, threads(θ)↦Θ] + +// TODO: how do we end a behavior +θ ∈ χ.threads +χ.threads(θ) = {σ, stmt*, π} +χ, σ, stmt* ⇝ χ′, σ′, stmt′* +--- [step behavior] +χ ⇝ χ′[threads(θ)↦{σ′, stmt′*, π}] + +``` From 6511ad0cf6e22e40bf4bc2d39011b0e92e2c6aad Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Fri, 4 Apr 2025 14:55:24 -0500 Subject: [PATCH 26/37] concurrent semantics WIP --- docs/opsem.md | 143 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 103 insertions(+), 40 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 674afeed5..e8eff6b30 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -116,6 +116,7 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena content: Value, queue: BehaviorId*, read: ℕ, + write: ℕ, rc: ℕ } @@ -132,6 +133,8 @@ B ∈ Behavior = { stack: Frame*, cont: Statement*, + read: 𝒫(CownId), + write: 𝒫(CownId), result: CownId } @@ -148,11 +151,11 @@ x ∈ φ ≝ x ∈ dom(φ.vars) φ(x) = φ.vars(x) φ[x↦v] = φ[vars(x)↦v] φ\x = φ\{x} -φ\xs = φ[vars\xs] +φ\xs = φ[vars \= xs] 𝔽 ∈ χ ≝ φ ∈ dom(χ.frames) -χ∪𝔽 = χ[frames∪𝔽] -χ\𝔽 = χ[frames\𝔽] +χ∪𝔽 = χ[frames ∪= 𝔽] +χ\𝔽 = χ[frames \= 𝔽] // Heap objects. ι ∈ χ ≝ ι ∈ dom(χ.data) @@ -163,13 +166,31 @@ x ∈ φ ≝ x ∈ dom(φ.vars) metadata(ι)↦{type: τ, location: ρ, rc: 1}, regions(ρ)[stack_rc += 1]] χ\ι = χ\{ι} -χ\ιs = χ[data = data\ιs, metadata = metadata\ιs] +χ\ιs = χ[data \= ιs, metadata \= ιs] // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) χ[ρ↦R] = χ[regions(ρ)↦{type: R, parents: ∅, stack_rc: 0}] χ\ρ = χ\{ρ} -χ\ρs = χ[regions\ρs] +χ\ρs = χ[regions \= ρs] + +// Cowns. +π ∈ χ ≝ π ∈ dom(χ.cowns) +χ(π) = χ.cowns(π) +χ[π↦P] = χ[cowns(π)↦P] +χ\π = χ[cowns \= {π}] + +// Behaviors. +𝛽 ∈ χ ≝ 𝛽 ∈ dom(χ.behaviors) +χ(𝛽) = χ.behaviors(𝛽) +χ[𝛽↦B] = χ[behaviors(𝛽)↦B] +χ\𝛽 = χ[behaviors \= {𝛽}] + +// Threads. +θ ∈ χ ≝ θ ∈ dom(χ.threads) +χ(θ) = χ.threads(θ) +χ[θ↦Θ] = χ[threads(θ)↦Θ] +χ\θ = χ[threads \= {θ}] ``` @@ -648,7 +669,7 @@ w ∉ dom(P.types(typeof(χ, ι)).fields) x ∉ ϕ v = χ₀(ι)(w) if ϕ(y) = {object: ι, field: w} - χ₀.cowns(π).value if ϕ(y) = {object: π, field: w} + χ₀(π).value if ϕ(y) = {object: π, field: w} χ₁ = region_stack_inc(χ₀, v) χ₂ = inc(χ₁, v) --- [load] @@ -661,7 +682,7 @@ x ∉ ϕ x ∉ ϕ v₀ = χ₀(ι)(w) if ϕ(y) = {object: ι, field: w} - χ₀.cowns(π).value if ϕ(y) = {object: π, field: w} + χ₀(π).value if ϕ(y) = {object: π, field: w} v₁ = φ(z) safe_store(χ₀, loc(χ₀, ι), v₁) ω = χ₀(ι)[w↦v₁] @@ -781,7 +802,7 @@ This drops any remaining frame variables other than the return value. dom(φ₁.vars) = {x} v = φ₁(x) loc(χ, v) ≠ φ₁.id -typetest(χ, v, φ.type) +typetest(χ, v, φ.type) // TODO: typetest depends on condition φ₂ = φ₀[φ₁.ret↦v, condition = φ₁.condition] --- [return] χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ\(φ₁.id), σ;φ₂, ϕ₁.cont @@ -789,11 +810,10 @@ typetest(χ, v, φ.type) dom(φ.vars) = {x} v = φ(x) loc(χ, v) ≠ φ.id -typetest(χ, v, φ.type) -// TODO: put v in the result cown? -// safe_store to result cown +typetest(χ, v, φ.type) // TODO: typetest depends on condition +// TODO: safe_store to result cown --- [return] -χ, φ, return x;stmt* ⇝ χ\(φ.id), ∅, ∅ +χ, φ, return x;stmt* ⇝ χ\(φ.id), φ[final↦v]\x, ∅ dom(φ.vars) = {x, y} ∪ zs --- [return] @@ -1014,39 +1034,69 @@ When all of a behavior's cowns have the behavior at the front of their queue, th ```rs ready(χ, 𝛽) = - (∀π ∈ πs . χ(π).queue = 𝛽;𝛽*) ∧ + (∀π ∈ πs . (χ(π).queue = 𝛽;𝛽*) ∧ χ(π).write = 0) ∧ (∀π ∈ χ(𝛽).write . χ(π).read = 0) where πs = {π | π ∈ (χ(𝛽).read ∪ χ(𝛽).write ∪ {χ(𝛽).result})} +read-inc(χ, ∅) = χ +read-inc(χ, {π} ∪ πs) = + read-inc(χ′, πs) + where + χ′ = read-inc(χ, π) +read-inc(χ, π) = + χ[cowns(π)[queue = 𝛽*, read += 1]] + where + χ(π).queue = 𝛽;𝛽* + +write-inc(χ, ∅) = χ +write-inc(χ, {π} ∪ πs) = + write-inc(χ′, πs) + where + χ′ = write-inc(χ, π) +write-inc(χ, π) = + χ[cowns(π)[queue = 𝛽*, write += 1]] + where + χ(π).queue = 𝛽;𝛽* + +read-dec(χ, ∅) = χ +read-dec(χ, {π} ∪ πs) = + read-dec(χ′, πs) + where + χ′ = read-dec(χ, π) +read-dec(χ, π) = χ[cowns(π)[rc -= 1, read -= 1]] // TODO: free + +write-dec(χ, ∅) = χ +write-dec(χ, {π} ∪ πs) = + write-dec(χ′, πs) + where + χ′ = write-dec(χ, π) +write-dec(χ, π) = χ[cowns(π)[rc -= 1, write -= 1]] // TODO: free + // TODO: return some "read-only" view of the value. // no rc ops for read-only. read-acquire(χ, π) = - inc(χ′, v), v if loc(χ, v) = Immutable + inc(χ, v), v if loc(χ, v) = Immutable // TODO: where - (χ.cowns(π).queue = 𝛽;𝛽*) ∧ - (χ′ = χ[cowns(π)[queue = 𝛽*, read += 1]]) ∧ - (v = χ.cowns(π).value) + (v = χ(π).value) write-acquire(χ, π) = - inc(χ′, π), {object: π, field: final} - where - (χ.cowns(π).queue = 𝛽;𝛽*) ∧ - (χ′ = χ[cowns(π)[queue = 𝛽*]]) + inc(χ, π), {object: π, field: final} // TODO: // regions put in a behavior need to set a parent to prevent them being put anywhere else. // delay until all captured regions have stack_rc = 0? // has to check child regions as well. x ∉ φ -𝛽 ∉ dom(χ.behaviors) -π ∉ dom(χ.cowns) +𝛽 ∉ χ +π ∉ χ once(w*;y*;z*) ∀w ∈ w* . φ(w) ∈ CownId ∀y ∈ y* . φ(y) ∈ CownId ∀z ∈ z* . safe_store(χ, 𝛽, φ(z)) -χ′ = χ[∀π′ ∈ {φ(x′) | (x′ ∈ w*;y*)} . cowns(π′)[queue ++ 𝛽]] +πs = {φ(x′) | (x′ ∈ w*;y*)} ∪ {π} +χ′ = χ[∀π′ ∈ πs . cowns(π′)[queue ++ 𝛽]] Π = { type: T, value: false, queue: 𝛽 } B = { read: {w ↦ φ(w) | w ∈ w*}, write: {y ↦ φ(y) | y ∈ y*}, @@ -1055,29 +1105,42 @@ B = { read: {w ↦ φ(w) | w ∈ w*}, result: π } --- [when] χ, σ;φ, bind x (when T (read w*) (write y*) (capture z*) stmt₀*);stmt₁* ⇝ - χ′[cowns(π)↦Π, behaviors(𝛽)↦B]∪𝔽, σ;φ[x↦π]\(w*;y*;z*), stmt₁* + χ′[π↦Π, 𝛽↦B]∪𝔽, σ;φ[x↦π]\(w*;y*;z*), stmt₁* -𝛽 ∈ dom(χ.behaviors) -θ ∉ dom(χ.threads) +𝛽 ∈ χ +θ ∉ χ 𝔽 ∉ χ ready(χ, 𝛽) -B = χ.behaviors(𝛽) +π = χ(𝛽).result φ = { id: 𝔽, - vars: {x ↦ B.capture(x) | x ∈ dom(B.capture)}, + vars: {x ↦ χ(𝛽).capture(x) | x ∈ dom(χ(𝛽).capture)}, ret: final, + type: χ(π).type, cont: ∅, condition: Return } - [∀w ∈ dom(B.read) . vars(w)↦read-acquire(vars(w))] - [∀y ∈ dom(B.write) . vars(y)↦write-acquire(vars(w))] -Θ = { stack: φ, cont: B.body, result: π } ---- [start behavior] -χ ⇝ χ[behaviors \= {𝛽}, threads(θ)↦Θ] - -// TODO: how do we end a behavior -θ ∈ χ.threads -χ.threads(θ) = {σ, stmt*, π} + [∀w ∈ dom(χ(𝛽).read) . vars(w)↦read-acquire(χ(𝛽).read(w))] + [∀y ∈ dom(χ(𝛽).write) . vars(y)↦write-acquire(χ(𝛽).write(y))] +Θ = { stack: φ, + cont: χ(𝛽).body, + read: {π′ | π′ ∈ χ(𝛽).read} + write: {π′ | π′ ∈ χ(𝛽).write} + result: π } +χ₁ = read-inc(χ, Θ.read) +χ₂ = write-inc(χ₁, Θ.write ∪ {π}) +--- [start thread] +χ ⇝ χ₂[θ↦Θ]\𝛽 + +θ ∈ χ +χ(θ) = {σ, stmt*, π} χ, σ, stmt* ⇝ χ′, σ′, stmt′* ---- [step behavior] -χ ⇝ χ′[threads(θ)↦{σ′, stmt′*, π}] +--- [step thread] +χ ⇝ χ′[θ↦{stack: σ′, cont: stmt′*, result: π}] + +θ ∈ χ +χ(θ) = {stack: φ, cont: ∅, read: πs₀, write: πs₁, result: π} +χ₁ = read-dec(χ, πs₀) +χ₂ = write-dec(χ₁, πs₁ ∪ {π}) +--- [end thread] +χ ⇝ χ₂[cowns(π)[value = φ(final)]]\θ ``` From 5c0454c20607525dd35288d5753858aa70f0dc59 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Fri, 4 Apr 2025 15:12:18 -0500 Subject: [PATCH 27/37] concurrent semantics WIP --- docs/opsem.md | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index e8eff6b30..a27045e40 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -1074,15 +1074,25 @@ write-dec(χ, {π} ∪ πs) = write-dec(χ, π) = χ[cowns(π)[rc -= 1, write -= 1]] // TODO: free // TODO: return some "read-only" view of the value. -// no rc ops for read-only. -read-acquire(χ, π) = - inc(χ, v), v if loc(χ, v) = Immutable - // TODO: +// no rc ops for read-only? any other way to make a read-only view? +read-acquire(χ, φ, ∅) = χ, φ +read-acquire(χ, φ, ω) = + read-acquire(χ′, φ′, ω\x) where - (v = χ(π).value) - -write-acquire(χ, π) = - inc(χ, π), {object: π, field: final} + x ∈ dom(ω) ∧ + π = ω(x) ∧ + v = χ(π).value ∧ + χ′, φ′ = inc(χ, v), φ[x↦v] if loc(χ, v) = Immutable + = /* TODO: */ otherwise + +write-acquire(χ, φ, ∅) = χ, φ +write-acquire(χ, φ, ω) = + write-acquire(χ′, φ′, ω\x) + where + x ∈ dom(ω) ∧ + π = ω(x) ∧ + χ′ = inc(χ, π) ∧ + φ′ = φ[x↦{object: π, field: final}] // TODO: // regions put in a behavior need to set a parent to prevent them being put anywhere else. @@ -1118,15 +1128,15 @@ ready(χ, 𝛽) type: χ(π).type, cont: ∅, condition: Return } - [∀w ∈ dom(χ(𝛽).read) . vars(w)↦read-acquire(χ(𝛽).read(w))] - [∀y ∈ dom(χ(𝛽).write) . vars(y)↦write-acquire(χ(𝛽).write(y))] -Θ = { stack: φ, +χ₁, φ₁ = read-acquire(χ, φ, χ(𝛽).read) +χ₂, φ₂ = write-acquire(χ₁, φ₁, χ(𝛽).write) +χ₃ = read-inc(χ₂, Θ.read) +χ₄ = write-inc(χ₃, Θ.write ∪ {π}) +Θ = { stack: φ₂, cont: χ(𝛽).body, read: {π′ | π′ ∈ χ(𝛽).read} write: {π′ | π′ ∈ χ(𝛽).write} result: π } -χ₁ = read-inc(χ, Θ.read) -χ₂ = write-inc(χ₁, Θ.write ∪ {π}) --- [start thread] χ ⇝ χ₂[θ↦Θ]\𝛽 From cd3d80eed017a49e7cadef07b551513236e41c8f Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Fri, 4 Apr 2025 17:45:38 -0500 Subject: [PATCH 28/37] readonly --- docs/opsem.md | 130 +++++++++++++++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 49 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index a27045e40..d0992587a 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -34,8 +34,10 @@ ws, xs, ys, zs ∈ 𝒫(Ident) 𝛽 ∈ BehaviorId θ ∈ ThreadId -T ∈ Type = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | TypeId | Ref TypeId - | Cown TypeId +T ∈ Type = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | TypeId + | Cown TypeId | Ref TypeId | Ref Cown TypeId + | Readonly TypeId | Readonly Cown TypeId + | Ref Readonly TypeId | | Ref Readonly Cown TypeId 𝕥 ∈ TypeDesc = { @@ -63,6 +65,7 @@ P ∈ Program = | BadReturnLoc | BadReturnType p ∈ Primitive = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | Error v ∈ Value = ObjectId | Primitive | Reference | CownId + | Readonly ObjectId | Readonly CownId ω ∈ Object = Ident ↦ Value Condition = Return | Raise | Throw @@ -206,9 +209,14 @@ typeof(χ, v) = P.primitives(Float × ℕ) if v ∈ Float × ℕ P.primitives(Error) if v ∈ Error χ.metadata(ι).type if ι = v + Readonly χ.metadata(ι).type if Readonly ι = v Ref P.types(typeof(χ, ι).field(𝕣.field).type if (𝕣 = v) ∧ (𝕣.object = ι) - Ref χ(π).type if (𝕣 = v) ∧ (𝕣.object = π) + Readonly Ref P.types(typeof(χ, ι).field(𝕣.field).type if + (𝕣 = v) ∧ (𝕣.object = Readonly ι) Cown χ(π).type if π = v + Readonly Cown χ(π).type if Readonly π = v + Ref Cown χ(π).type if (𝕣 = v) ∧ (𝕣.object = π) + Ref Readonly Cown χ(π).type if (𝕣 = v) ∧ (𝕣.object = Readonly π) // Subtype test. typetest(χ, v, T) = @@ -247,6 +255,7 @@ reachable(χ, ι, ιs, w) = reachable(χ, χ(ι)(w), ιs) loc(χ, p) = Immutable loc(χ, π) = Immutable loc(χ, 𝕣) = loc(χ, 𝕣.object) +loc(χ, Readonly ι) = loc(χ, ι) loc(χ, ι) = loc(χ, ι′) if χ.metadata(ι).location = ι′ χ.metadata(ι).location if ι ∈ χ @@ -272,20 +281,24 @@ This enforces a tree-shaped region graph, with a single reference from parent to ```rs -// TODO: v = π safe_store(χ, Immutable, v) = false safe_store(χ, 𝔽, v) = true if loc(χ, v) = Immutable - true if loc(χ, v) = ρ + true if (loc(χ, v) = ρ) true if (loc(χ, v) = 𝔽′) ∧ (𝔽 >= 𝔽′) false otherwise safe_store(χ, ρ, v) = false if finalizing(χ, v) - true if loc(χ, v) = Immutable + true if loc(χ, v) = Immutable) true if loc(χ, v) = ρ true if (loc(χ, v) = ρ′) ∧ (parents(χ, ρ′) = ∅) ∧ ¬is_ancestor(χ, ρ′, ρ) false otherwise -safe_store(χ, μ, v) = +safe_store(χ, π, v) = + false if finalizing(χ, v) + true if loc(χ, v) = Immutable + true if (loc(χ, v) = ρ) ∧ (parents(χ, ρ) = ∅) + false otherwise +safe_store(χ, 𝛽, v) = false if finalizing(χ, v) true if loc(χ, v) = Immutable true if (loc(χ, v) = ρ) ∧ (parents(χ, ρ) = ∅) @@ -397,29 +410,32 @@ Reference counting is a no-op unless the object is in a `RegionRC` or is `Immuta ```rs enable-rc(χ, ι) = - (loc(χ, ι) = ρ ∧ ρ.type = RegionRC) ∨ (loc(χ, ι) = Immutable) + ((loc(χ, ι)) = ρ ∧ (ρ.type = RegionRC)) ∨ (loc(χ, ι) = Immutable) region_stack_inc(χ, p) = χ region_stack_inc(χ, π) = χ region_stack_inc(χ, 𝕣) = region_stack_inc(χ, 𝕣.object) +region_stack_inc(χ, Readonly ι) = χ region_stack_inc(χ, ι) = - χ[regions(ρ)[stack_rc += 1]] if loc(χ, ι) = ρ + χ[regions(ρ)[stack_rc += 1]] if (loc(χ, ι) = ρ) χ otherwise region_stack_dec(χ, p) = χ region_stack_dec(χ, π) = χ region_stack_dec(χ, 𝕣) = region_stack_dec(χ, 𝕣.object) +region_stack_dec(χ, Readonly ι) = χ region_stack_dec(χ, ι) = χ[pre_final_r ∪= {ρ}] if (loc(χ, ι) = ρ) ∧ (parents(χ, ρ) = ∅) ∧ (χ.regions(ρ).stack_rc = 1) - χ[regions(ρ)[stack_rc -= 1]] if loc(χ, ι) = ρ + χ[regions(ρ)[stack_rc -= 1]] if (loc(χ, ι) = ρ) χ otherwise region_add_parent(χ, ι, p) = χ region_add_parent(χ, ι, π) = χ region_add_parent(χ, ι, 𝕣) = region_add_parent(χ, ι, 𝕣.object) +region_add_parent(χ, ι, Readonly ι′) = χ region_add_parent(χ, ι, ι′) = χ[regions(ρ′)[parents ∪= {ρ})]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) @@ -429,6 +445,7 @@ region_add_parent(χ, ι, ι′) = region_remove_parent(χ, ι, p) = χ region_remove_parent(χ, ι, π) = χ region_remove_parent(χ, ι, 𝕣) = region_remove_parent(χ, ι, 𝕣.object) +region_remove_parent(χ, ι, Readonly ι′) = χ region_remove_parent(χ, ι, ι′) = χ[regions(ρ)[parents \= {ρ′})]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) @@ -438,6 +455,7 @@ region_remove_parent(χ, ι, ι′) = inc(χ, p) = χ inc(χ, π) = χ[cowns(π)[rc += 1]] inc(χ, 𝕣) = dec(χ, 𝕣.object) +inc(χ, Readonly ι) = χ inc(χ, ι) = inc(χ, ι′) if χ.metadata(ι).location = ι′ χ[metadata(ι)[rc += 1]] if enable-rc(χ, ι) @@ -446,6 +464,7 @@ inc(χ, ι) = dec(χ, p) = χ dec(χ, π) = χ[cowns(π)[rc -= 1]] // TODO: free dec(χ, 𝕣) = dec(χ, 𝕣.object) +dec(χ, Readonly ι) = χ dec(χ, ι) = dec(χ, ι′) if χ.metadata(ι).location = ι′ χ[pre_final ∪= {ι}] if enable-rc(χ, ι) ∧ (χ.metadata(ι).rc = 1) @@ -574,7 +593,8 @@ x ∉ φ x ∉ φ ι ∉ χ -ρ = loc(χ, φ(w)) +ι′ = φ(w) +ρ = loc(χ, ι′) once((y, z)*) ω = newobject(φ, (y, z)*) typecheck(χ, τ, ω) @@ -582,18 +602,20 @@ typecheck(χ, τ, ω) χ, σ;φ, bind x (new w τ (y, z)*);stmt* ⇝ χ[ι↦(ω, τ, ρ)], σ;φ[x↦ι]\zs, stmt* x ∉ φ -ρ ≠ loc(χ, φ(w)) +(ι′ ≠ φ(w)) ∨ (ρ ≠ loc(χ, ι′)) --- [new heap bad-target] χ, σ;φ, bind x (new w τ (y, z)*);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x x ∉ φ -ρ = loc(χ, φ(w)) +ι′ = φ(w) +ρ = loc(χ, ι′) ∃z ∈ (y, z)* . ¬safe_store(χ, ρ, φ(z)) --- [new heap bad-store] χ, σ;φ, bind x (new τ (y, z)*);stmt* ⇝ χ, σ;φ[x↦BadStore], throw;return x x ∉ φ -ρ = loc(χ, φ(w)) +ι′ = φ(w) +ρ = loc(χ, ι′) ω = newobject(φ, (y, z)*) ¬once((y, z)*) ∨ ¬typecheck(χ, τ, ω) --- [new heap bad-type] @@ -649,27 +671,38 @@ The `load` statement is the only operation other than `dup` or `drop` that can c ```rs +readonly(χ, p) = p +readonly(χ, {object: ι, field: w}) = {object: readonly(χ, ι), field: w} +readonly(χ, {object: Readonly ι, field: w}) = {object: Readonly ι, field: w} +readonly(χ, {object: π, field: w}) = {object: Readonly π, field: w} +readonly(ι) = Readonly ι +readonly(Readonly ι) = Readonly ι +readonly(π) = π + x ∉ ϕ -ι = ϕ(y) +(ι = ϕ(y)) ∨ (Readonly ι = ϕ(y)) w ∈ dom(P.types(typeof(χ, ι)).fields) -𝕣 = {object: ι, field: w} ---- [fieldref] +𝕣 = {object: ϕ(y), field: w} +--- [ref] χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦𝕣]\y, stmt* x ∉ ϕ -ϕ(y) ∉ ObjectId ---- [fieldref bad-target] +(ϕ(y) ∉ ObjectId) ∧ (ϕ(y) ∉ Readonly ObjectId) +--- [ref bad-target] χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦BadTarget]\y, throw;return x x ∉ ϕ -ι = ϕ(y) +(ι = ϕ(y)) ∨ (Readonly ι = ϕ(y)) w ∉ dom(P.types(typeof(χ, ι)).fields) ---- [fieldref bad-field] +--- [ref bad-field] χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦BadField]\y, throw;return x x ∉ ϕ -v = χ₀(ι)(w) if ϕ(y) = {object: ι, field: w} - χ₀(π).value if ϕ(y) = {object: π, field: w} +𝕣 = φ(y) +v = χ₀(ι)(w) if 𝕣 = {object: ι, field: w} + readonly(χ, χ₀(ι)(w)) if 𝕣 = {object: Readonly ι, field: w} + χ₀(π).value if 𝕣 = {object: π, field: w} + readonly(χ₀(π).value) if 𝕣 = {object: Readonly π, field: w} χ₁ = region_stack_inc(χ₀, v) χ₂ = inc(χ₁, v) --- [load] @@ -681,27 +714,28 @@ x ∉ ϕ χ, σ;ϕ, bind x (load y);stmt* ⇝ χ, σ;ϕ[x↦BadTarget], throw;return x x ∉ ϕ -v₀ = χ₀(ι)(w) if ϕ(y) = {object: ι, field: w} - χ₀(π).value if ϕ(y) = {object: π, field: w} +𝕣 = φ(y) +v₀ = χ₀(ι)(w) if 𝕣 = {object: ι, field: w} + χ₀(π).value if 𝕣 = {object: π, field: w} v₁ = φ(z) -safe_store(χ₀, loc(χ₀, ι), v₁) -ω = χ₀(ι)[w↦v₁] +safe_store(χ₀, loc(χ₀, 𝕣.object), v₁) +ω = χ₀(ι)[w↦v₁] // TODO: what if it's a cown? χ₁ = region_stack_inc(χ₀, v₀) -χ₂ = region_remove_parent(χ₁, ι, v₀) -χ₃ = region_add_parent(χ₂, ι, v₁) +χ₂ = region_remove_parent(χ₁, 𝕣.object, v₀) +χ₃ = region_add_parent(χ₂, 𝕣.object, v₁) χ₄ = region_stack_dec(χ₃, v₁) --- [store] χ₀, σ;ϕ, bind x (store y z);stmt* ⇝ χ₄[ι↦ω], σ;ϕ[x↦v₀]\z, stmt* x ∉ ϕ -ϕ(y) ∉ Reference +(ϕ(y) ∉ Reference) ∨ (φ(y).object = Readonly ι) ∨ (φ(y).object = Readonly π) --- [store bad-target] χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadTarget], throw;return x x ∉ ϕ -ϕ(y) = {object: ι, field: w} +𝕣 = φ(y) v = φ(z) -¬safe_store(χ₀, loc(χ, ι), v₁) +¬safe_store(χ₀, loc(χ, 𝕣.object), v₁) --- [store] χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadStore], throw;return x @@ -887,8 +921,10 @@ This allows merging two regions. The region being merged must either have no par ```rs x ∉ φ -loc(χ₀, φ(w)) = ρ₀ -loc(χ₀, φ(y)) = ρ₁ +ι₀ = φ(w) +ι₁ = φ(y) +loc(χ₀, ι₀) = ρ₀ +loc(χ₀, ι₁) = ρ₁ (ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ₀, ρ₁, ρ₀) ∧ ({ρ₀} ⊇ parents(χ₀, ρ₁)) ιs = members(χ₀, ρ₁) χ₁ = χ₀[∀ι ∈ ιs . metadata(ι)[location = ρ₀]] @@ -897,8 +933,8 @@ loc(χ₀, φ(y)) = ρ₁ χ₀, σ;φ, bind x (merge w y);stmt* ⇝ χ₁\ρ₁, σ;φ[x↦φ(y)], stmt* x ∉ φ -(loc(χ, φ(w)) ≠ ρ₀) ∨ -(loc(χ, φ(y)) ≠ ρ₁) ∨ +(ι₀ ≠ φ(w)) ∨ (ι₁ ≠ φ(y)) ∨ +(loc(χ, φ(w)) ≠ ρ₀) ∨ (loc(χ, φ(y)) ≠ ρ₁) ∨ (ρ₀ = ρ₁) ∨ is_ancestor(χ₀, ρ₁, ρ₀) ∨ ({ρ₀} ̸⊇ parents(χ, ρ₁)) --- [merge bad-target] χ, σ;φ, bind x (merge w y);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x @@ -921,12 +957,12 @@ x ∉ φ ιs = {ι′ | loc(χ₀, ι′) ∈ ρs} χ₂ = χ₁[∀ι′ ∈ ιs . metadata(ι′)[location = Immutable]] --- [freeze true] -χ₀, σ;φ, bind x (freeze y);stmt* ⇝ χ₂\ρs, σ;φ[x↦true], stmt* +χ₀, σ;φ, bind x (freeze y);stmt* ⇝ χ₂\ρs, σ;φ[x↦ι]\y, stmt* x ∉ φ -loc(χ, φ(y)) ≠ ρ +(ι ≠ φ(y)) ∨ (loc(χ, ι) ≠ ρ) --- [freeze false] -χ, σ;φ, bind x (freeze y);stmt* ⇝ χ, σ;φ[x↦false], stmt* +χ, σ;φ, bind x (freeze y);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x ``` @@ -953,16 +989,16 @@ rc = calc_stack_rc(χ₀, σ;φ, ιs) χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁, σ;φ[x↦ι]\y, stmt* x ∉ φ -ρ ≠ loc(χ, φ(y)) +(ι ≠ φ(y)) ∨ (ρ ≠ loc(χ, φ(y))) --- [extract bad-target] χ, σ;φ, bind x (extract y);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x x ∉ φ ι = φ(y) -ρ₀ = loc(χ₀, ι) -ιs = reachable(χ, ι) ∩ members(χ₀, ρ₀) -|{ι | (ι ∈ members(χ₀, ρ₀)) ∧ (w ∈ dom(χ₀(ι))) ∧ - (χ₀(ι)(w) = ι′) ∧ (ι′ ∈ ιs)}| > 0 +ρ = loc(χ, ι) +ιs = reachable(χ, ι) ∩ members(χ, ρ) +|{ι | (ι ∈ members(χ, ρ)) ∧ (w ∈ dom(χ(ι))) ∧ + (χ(ι)(w) = ι′) ∧ (ι′ ∈ ιs)}| > 0 --- [extract bad-target] χ, σ;φ, bind x (extract y);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x @@ -1073,17 +1109,13 @@ write-dec(χ, {π} ∪ πs) = χ′ = write-dec(χ, π) write-dec(χ, π) = χ[cowns(π)[rc -= 1, write -= 1]] // TODO: free -// TODO: return some "read-only" view of the value. -// no rc ops for read-only? any other way to make a read-only view? read-acquire(χ, φ, ∅) = χ, φ read-acquire(χ, φ, ω) = read-acquire(χ′, φ′, ω\x) where x ∈ dom(ω) ∧ π = ω(x) ∧ - v = χ(π).value ∧ - χ′, φ′ = inc(χ, v), φ[x↦v] if loc(χ, v) = Immutable - = /* TODO: */ otherwise + φ′ = φ[x↦readonly(χ(π).value)] write-acquire(χ, φ, ∅) = χ, φ write-acquire(χ, φ, ω) = From 7fd2b16465bd79000d52af07e8a7c025ca02999c Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Sat, 5 Apr 2025 09:53:12 -0500 Subject: [PATCH 29/37] stop using a set for region parent --- docs/opsem.md | 106 ++++++++++++++++++++++++++------------------------ 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index d0992587a..00b90e342 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -63,7 +63,7 @@ P ∈ Program = 𝕣 ∈ Reference = {object: ObjectId | CownId, field: Ident} Error = BadType | BadTarget | BadField | BadStore | BadMethod | BadArgs | BadReturnLoc | BadReturnType -p ∈ Primitive = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | Error +p ∈ Primitive = None | Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | Error v ∈ Value = ObjectId | Primitive | Reference | CownId | Readonly ObjectId | Readonly CownId ω ∈ Object = Ident ↦ Value @@ -83,10 +83,9 @@ v ∈ Value = ObjectId | Primitive | Reference | CownId R ∈ RegionType = RegionRC | RegionGC | RegionArena - // The size of the parents set will be at most 1. Region = { type: RegionType, - parents: 𝒫(RegionId), + parent: RegionId | CownId | BehaviorId | None, stack_rc: ℕ } @@ -173,7 +172,7 @@ x ∈ φ ≝ x ∈ dom(φ.vars) // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) -χ[ρ↦R] = χ[regions(ρ)↦{type: R, parents: ∅, stack_rc: 0}] +χ[ρ↦R] = χ[regions(ρ)↦{type: R, parent: None, stack_rc: 0}] χ\ρ = χ\{ρ} χ\ρs = χ[regions \= ρs] @@ -265,13 +264,13 @@ loc(χ, π) = Immutable same_loc(χ, v₀, v₁) = (loc(χ, v₀) = loc(χ, v₁)) members(χ, ρ) = {ι | (ι ∈ χ) ∧ (loc(χ, ι) = ρ)} -// Region parents. -parents(χ, ρ) = χ.regions(ρ).parents +// Region parent. +parent(χ, ρ) = χ.regions(ρ).parent // Check if ρ₀ is an ancestor of ρ₁. is_ancestor(χ, ρ₀, ρ₁) = - ρ₀ ∈ parents(χ, ρ₁) ∨ - (∀ρ ∈ parents(χ, ρ₁) . is_ancestor(χ, ρ₀, ρ)) + (ρ₀ = parent(χ, ρ₁)) ∨ + ((ρ = parent(χ, ρ₁) ∧ is_ancestor(χ, ρ₀, ρ))) ``` @@ -291,17 +290,17 @@ safe_store(χ, ρ, v) = false if finalizing(χ, v) true if loc(χ, v) = Immutable) true if loc(χ, v) = ρ - true if (loc(χ, v) = ρ′) ∧ (parents(χ, ρ′) = ∅) ∧ ¬is_ancestor(χ, ρ′, ρ) + true if (loc(χ, v) = ρ′) ∧ (parent(χ, ρ′) = None) ∧ ¬is_ancestor(χ, ρ′, ρ) false otherwise safe_store(χ, π, v) = false if finalizing(χ, v) true if loc(χ, v) = Immutable - true if (loc(χ, v) = ρ) ∧ (parents(χ, ρ) = ∅) + true if (loc(χ, v) = ρ) ∧ (parent(χ, ρ) = None) false otherwise safe_store(χ, 𝛽, v) = false if finalizing(χ, v) true if loc(χ, v) = Immutable - true if (loc(χ, v) = ρ) ∧ (parents(χ, ρ) = ∅) + true if (loc(χ, v) = ρ) ∧ (parent(χ, ρ) = None) false otherwise finalizing(χ, p) = false @@ -337,7 +336,10 @@ wf_stacklocal(χ, σs) = // Regions are externally unique. wf_regionunique(χ) = - ∀ρ ∈ χ . (|ιs₂| ≤ 1) ∧ (|ρs| ≤ 1) ∧ (ρs = parents(χ, ρ)) + ∀ρ ∈ χ . + (|ιs₂| ≤ 1) ∧ (|ρs| ≤ 1) ∧ + ((ρs = {ρ′}) ⇒ (ρ′ = parent(χ, ρ))) ∧ + ((ρs = ∅) ⇒ (parent(χ, ρ) ∉ RegionId)) where ιs₀ = members(χ, ρ) ∧ ιs₁ = {ι | (ι ∈ χ) ∧ (loc(χ, ι) = ρ′) ∧ (ρ ≠ ρ′)} ∧ @@ -347,9 +349,9 @@ wf_regionunique(χ) = // The region graph is a tree. wf_regiontree(χ) = ∀ρ₀, ρ₁ ∈ χ . - (ρ₀ ∈ parents(χ, ρ₁) ⇒ (ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ, ρ₁, ρ₀)) + (ρ₀ = parent(χ, ρ₁)) ⇒ (ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ, ρ₁, ρ₀) -// TODO: a cown contains an immutable object or a region with no parents. +// TODO: a cown contains an immutable object or a region with no parent. ``` @@ -387,14 +389,15 @@ calc_stack_rc(χ, ∅, ι) = 0 calc_stack_rc(χ, σ;φ, ι) = |{x | φ(x) = ι}| + calc_stack_rc(χ, σ, ι) -// The heap RC for the parent region will be zero or one. +// The heap RC from the parent region will be zero or one. calc_heap_rc(χ, ι) = - calc_heap_rc(χ, {ρ} ∪ ρs, ι) + calc_heap_rc(χ, ρ, ι) + calc_heap_rc(χ, parent(χ, ρ), ι) where - (ρ = loc(χ, ι)) ∧ (ρs = parents(χ, ρ)) + ρ = loc(χ, ι) -calc_heap_rc(χ, ∅, ι) = 0 -calc_heap_rc(χ, {ρ} ∪ ρs, ι) = calc_heap_rc(χ, ρ, ι) + calc_heap_rc(χ, ρs, ι) +calc_heap_rc(χ, None, ι) = 0 +calc_heap_rc(χ, 𝛽, ι) = 0 +calc_heap_rc(χ, π, ι) = 0 calc_heap_rc(χ, ρ, ι) = |{(ι′, w) | (ι′ ∈ members(χ, ρ)) ∧ @@ -427,31 +430,39 @@ region_stack_dec(χ, Readonly ι) = χ region_stack_dec(χ, ι) = χ[pre_final_r ∪= {ρ}] if (loc(χ, ι) = ρ) ∧ - (parents(χ, ρ) = ∅) ∧ + (parent(χ, ρ) = None) ∧ (χ.regions(ρ).stack_rc = 1) χ[regions(ρ)[stack_rc -= 1]] if (loc(χ, ι) = ρ) χ otherwise region_add_parent(χ, ι, p) = χ -region_add_parent(χ, ι, π) = χ region_add_parent(χ, ι, 𝕣) = region_add_parent(χ, ι, 𝕣.object) -region_add_parent(χ, ι, Readonly ι′) = χ region_add_parent(χ, ι, ι′) = - χ[regions(ρ′)[parents ∪= {ρ})]] if + χ[regions(ρ′)[parent = ρ]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) χ[regions(ρ′)[stack_rc += 1]] if (loc(χ, ι) = 𝔽) ∧ (loc(χ, ι′) = ρ′) χ otherwise +region_add_parent(χ, π, p) = χ +region_add_parent(χ, π, 𝕣) = region_add_parent(χ, ι, 𝕣.object) +region_add_parent(χ, π, ι) = + χ[regions(ρ)[parent = π]] if loc(χ, ι) = ρ + χ otherwise + region_remove_parent(χ, ι, p) = χ -region_remove_parent(χ, ι, π) = χ region_remove_parent(χ, ι, 𝕣) = region_remove_parent(χ, ι, 𝕣.object) -region_remove_parent(χ, ι, Readonly ι′) = χ region_remove_parent(χ, ι, ι′) = - χ[regions(ρ)[parents \= {ρ′})]] if + χ[regions(ρ)[parent = None]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) χ[regions(ρ′)[stack_rc -= 1]] if (loc(χ, ι) = 𝔽) ∧ (loc(χ, ι′) = ρ′) χ otherwise +region_remove_parent(χ, π, p) = χ +region_remove_parent(χ, π, 𝕣) = region_remove_parent(χ, ι, 𝕣.object) +region_remove_parent(χ, π, ι) = + χ[regions(ρ)[parent = None]] if loc(χ, ι) = ρ + χ otherwise + inc(χ, p) = χ inc(χ, π) = χ[cowns(π)[rc += 1]] inc(χ, 𝕣) = dec(χ, 𝕣.object) @@ -493,9 +504,9 @@ gc(χ, σ, ρ) = χ′ = gc_dec(χ, ιs₀, ιs₁) gc_roots(χ, σ, ρ) = - {ι | (ι ∈ ιs) ∧ ((calc_stack_rc(χ, σ, ι) > 0) ∨ (calc_heap_rc(χ, ρs, ι) > 0))} - where - ρs = parents(χ, ρ) ∧ ιs = members(χ, ρ) + {ι | (ι ∈ members(χ, ρ)) ∧ + ((calc_stack_rc(χ, σ, ι) > 0) ∨ + (calc_heap_rc(χ, parent(χ, ρ), ι) > 0))} gc_dec(χ, ∅, ιs₁) = χ gc_dec(χ, {ι} ∪ ιs₀, ιs₁) = @@ -916,7 +927,7 @@ x ∉ φ This allows merging two regions. The region being merged must either have no parent, or be a child of the region it's being merged into. If there are other stack references to the region being merged, a static type system may have the wrong region information for them. -> TODO: disallow merging a region that has a parent? Disallow merging a region that has other stack references? +> TODO: Disallow merging a region that has other stack references? ```rs @@ -925,7 +936,7 @@ x ∉ φ ι₁ = φ(y) loc(χ₀, ι₀) = ρ₀ loc(χ₀, ι₁) = ρ₁ -(ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ₀, ρ₁, ρ₀) ∧ ({ρ₀} ⊇ parents(χ₀, ρ₁)) +(ρ₀ ≠ ρ₁) ∧ (parent(χ₀, ρ₁) = None) ιs = members(χ₀, ρ₁) χ₁ = χ₀[∀ι ∈ ιs . metadata(ι)[location = ρ₀]] [regions(ρ₀)[stack_rc += regions(ρ₁).stack_rc]] @@ -935,7 +946,7 @@ loc(χ₀, ι₁) = ρ₁ x ∉ φ (ι₀ ≠ φ(w)) ∨ (ι₁ ≠ φ(y)) ∨ (loc(χ, φ(w)) ≠ ρ₀) ∨ (loc(χ, φ(y)) ≠ ρ₁) ∨ -(ρ₀ = ρ₁) ∨ is_ancestor(χ₀, ρ₁, ρ₀) ∨ ({ρ₀} ̸⊇ parents(χ, ρ₁)) +(ρ₀ = ρ₁) ∨ (parent(χ, ρ₁) ≠ None) --- [merge bad-target] χ, σ;φ, bind x (merge w y);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x @@ -945,13 +956,14 @@ x ∉ φ If the region being frozen has a parent, a static type system may have the wrong type for the incoming reference. If there are other stack references to the region being frozen or any of its children, a static type system may have the wrong type for them. -> TODO: disallow freezing a region that has a parent? Disallow freezing a region that has other stack references? +> TODO: Disallow freezing a region that has other stack references? ```rs x ∉ φ ι = φ(y) ρ = loc(χ₀, ι) +parent(χ₀, ρ) = None ρs = {ρ} ∪ {ρ′ | (ρ′ ∈ χ.regions) ∧ is_ancestor(χ₀, ρ, ρ′)} χ₁ = region_type_change(χ₀, σ;φ, ρs, RegionRC) ιs = {ι′ | loc(χ₀, ι′) ∈ ρs} @@ -960,7 +972,7 @@ x ∉ φ χ₀, σ;φ, bind x (freeze y);stmt* ⇝ χ₂\ρs, σ;φ[x↦ι]\y, stmt* x ∉ φ -(ι ≠ φ(y)) ∨ (loc(χ, ι) ≠ ρ) +(ι ≠ φ(y)) ∨ (loc(χ, ι) ≠ ρ) ∨ (parent(χ, ρ) ≠ None) --- [freeze false] χ, σ;φ, bind x (freeze y);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x @@ -982,9 +994,9 @@ x ∉ φ (ρ = loc(χ, ι′)) ∧ (ρ ≠ ρ₀)} rc = calc_stack_rc(χ₀, σ;φ, ιs) χ₁ = χ₀[regions(ρ₀)[stack_rc -= rc], - regions(ρ₁)↦{type: χ.regions(ρ₀).type, parents: ∅, stack_rc: rc}, + regions(ρ₁)↦{type: χ.regions(ρ₀).type, parent: None, stack_rc: rc}, ∀ι′ ∈ ιs . metadata(ι′)[location = ρ₁], - ∀ρ ∈ ρs . regions(ρ)[parents = {ρ₁}]] + ∀ρ ∈ ρs . regions(ρ)[parent = ρ₁]] --- [extract] χ₀, σ;φ, bind x (extract y);stmt* ⇝ χ₁, σ;φ[x↦ι]\y, stmt* @@ -1011,7 +1023,7 @@ These steps can be taken regardless of what statement is pending. ```rs region_fields(χ, ι) = - χ[∀ρ′ ∈ ρs . regions(ρ′)[parents \= {ρ}], pre_final_r ∪= ρs′] + χ[∀ρ′ ∈ ρs . regions(ρ′)[parent = None], pre_final_r ∪= ρs′] where ρ = loc(χ, ι) ∧ ws = dom(χ(ι)) ∧ @@ -1060,20 +1072,16 @@ final ∉ dom(P.types(τ).methods) ## Behaviors -A `when` creates a behavior and returns a cown. -The cown that's created has no value until the `when` completes, and is busy in the meantime. It has the behavior ID in its queue. -Put the behavior ID at the end of a behavior queue for each cown. -The frame will start by moving captures and cowns into frame variables with the same names as the object fields, then destroying the object and its region. - -When all of a behavior's cowns have the behavior at the front of their queue, the behavior executes. The behavior is taken out of the pending set. When the behavior finishes executing, the front of each cown's queue is popped. - ```rs ready(χ, 𝛽) = (∀π ∈ πs . (χ(π).queue = 𝛽;𝛽*) ∧ χ(π).write = 0) ∧ - (∀π ∈ χ(𝛽).write . χ(π).read = 0) + (∀π ∈ χ(𝛽).write . χ(π).read = 0) ∧ + (∀ρ ∈ ρs′ . χ(ρ).stack_rc = 0) where - πs = {π | π ∈ (χ(𝛽).read ∪ χ(𝛽).write ∪ {χ(𝛽).result})} + (πs = {π | π ∈ (χ(𝛽).read ∪ χ(𝛽).write ∪ {χ(𝛽).result})}) ∧ + (ρs = {ρ | (ι ∈ χ(𝛽).capture) ∧ (loc(χ, ι) = ρ)}) ∧ + (ρs′ = {ρ′| (ρ ∈ ρs) ∧ (ρ′ ∈ χ) ∧ is_ancestor(χ, ρ, ρ′)}) read-inc(χ, ∅) = χ read-inc(χ, {π} ∪ πs) = @@ -1126,10 +1134,8 @@ write-acquire(χ, φ, ω) = χ′ = inc(χ, π) ∧ φ′ = φ[x↦{object: π, field: final}] -// TODO: -// regions put in a behavior need to set a parent to prevent them being put anywhere else. -// delay until all captured regions have stack_rc = 0? -// has to check child regions as well. +// TODO: regions put in a behavior need to set a parent to prevent them being put anywhere else. +// what if z* contains multiple objects in the same region, and that region has no parent? x ∉ φ 𝛽 ∉ χ π ∉ χ From 243405fb79b822e3c655c0d369a0c8de76a4ea1a Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Sat, 5 Apr 2025 18:06:12 -0500 Subject: [PATCH 30/37] store to a cown value --- docs/opsem.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 00b90e342..b24d29395 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -351,7 +351,11 @@ wf_regiontree(χ) = ∀ρ₀, ρ₁ ∈ χ . (ρ₀ = parent(χ, ρ₁)) ⇒ (ρ₀ ≠ ρ₁) ∧ ¬is_ancestor(χ, ρ₁, ρ₀) -// TODO: a cown contains an immutable object or a region with no parent. +// A cown contains an immutable value or a region bound to that cown. +wf_cownvalue(χ) = + ∀π ∈ χ . + (loc(χ(π).value) = Immutable) ∨ + ((loc(χ(π).value) = ρ) ∧ (parent(χ, ρ) = π)) ``` @@ -452,7 +456,7 @@ region_add_parent(χ, π, ι) = region_remove_parent(χ, ι, p) = χ region_remove_parent(χ, ι, 𝕣) = region_remove_parent(χ, ι, 𝕣.object) region_remove_parent(χ, ι, ι′) = - χ[regions(ρ)[parent = None]] if + χ[regions(ρ′)[parent = None]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) χ[regions(ρ′)[stack_rc -= 1]] if (loc(χ, ι) = 𝔽) ∧ (loc(χ, ι′) = ρ′) χ otherwise @@ -726,17 +730,18 @@ x ∉ ϕ x ∉ ϕ 𝕣 = φ(y) -v₀ = χ₀(ι)(w) if 𝕣 = {object: ι, field: w} - χ₀(π).value if 𝕣 = {object: π, field: w} -v₁ = φ(z) -safe_store(χ₀, loc(χ₀, 𝕣.object), v₁) -ω = χ₀(ι)[w↦v₁] // TODO: what if it's a cown? -χ₁ = region_stack_inc(χ₀, v₀) -χ₂ = region_remove_parent(χ₁, 𝕣.object, v₀) -χ₃ = region_add_parent(χ₂, 𝕣.object, v₁) -χ₄ = region_stack_dec(χ₃, v₁) +v₀ = φ(z) +safe_store(χ₀, loc(χ₀, 𝕣.object), v₀) +v₁, χ₁ = ω(w), χ₀[ι↦ω[w↦v₀]] if + (𝕣 = {object: ι, field: w}) ∧ (ω = χ₀(ι)) + Π.value, χ₀[π↦Π[value↦v₀]] if + (𝕣 = {object: π, field: w}) ∧ (Π = χ₀(π)) +χ₂ = region_stack_inc(χ₁, v₁) +χ₃ = region_remove_parent(χ₃, 𝕣.object, v₁) +χ₄ = region_add_parent(χ₃, 𝕣.object, v₀) +χ₅ = region_stack_dec(χ₄, v₀) --- [store] -χ₀, σ;ϕ, bind x (store y z);stmt* ⇝ χ₄[ι↦ω], σ;ϕ[x↦v₀]\z, stmt* +χ₀, σ;ϕ, bind x (store y z);stmt* ⇝ χ₅, σ;ϕ[x↦v₁]\z, stmt* x ∉ ϕ (ϕ(y) ∉ Reference) ∨ (φ(y).object = Readonly ι) ∨ (φ(y).object = Readonly π) @@ -1135,7 +1140,8 @@ write-acquire(χ, φ, ω) = φ′ = φ[x↦{object: π, field: final}] // TODO: regions put in a behavior need to set a parent to prevent them being put anywhere else. -// what if z* contains multiple objects in the same region, and that region has no parent? +// what if z* contains multiple objects in the same region, and that region has no parent? is that ok? +// stack_rc isn't going to 0 here, as it's being moved to the new thread. x ∉ φ 𝛽 ∉ χ π ∉ χ From c29ba6ba4baba7813658b03928db99279a329d89 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Sun, 6 Apr 2025 09:19:10 -0500 Subject: [PATCH 31/37] type test on store --- docs/opsem.md | 54 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index b24d29395..b41a8f80f 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -733,9 +733,11 @@ x ∉ ϕ v₀ = φ(z) safe_store(χ₀, loc(χ₀, 𝕣.object), v₀) v₁, χ₁ = ω(w), χ₀[ι↦ω[w↦v₀]] if - (𝕣 = {object: ι, field: w}) ∧ (ω = χ₀(ι)) + (𝕣 = {object: ι, field: w}) ∧ (ω = χ₀(ι)) ∧ + typetest(χ₀, v₀, P.types(typeof(χ₀, ι)).fields(w)) Π.value, χ₀[π↦Π[value↦v₀]] if - (𝕣 = {object: π, field: w}) ∧ (Π = χ₀(π)) + (𝕣 = {object: π, field: w}) ∧ (Π = χ₀(π)) ∧ + typetest(χ₀, v₀, Π.type) χ₂ = region_stack_inc(χ₁, v₁) χ₃ = region_remove_parent(χ₃, 𝕣.object, v₁) χ₄ = region_add_parent(χ₃, 𝕣.object, v₀) @@ -752,9 +754,19 @@ x ∉ ϕ 𝕣 = φ(y) v = φ(z) ¬safe_store(χ₀, loc(χ, 𝕣.object), v₁) ---- [store] +--- [store bad-store] χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadStore], throw;return x +x ∉ ϕ +𝕣 = φ(y) +v = φ(z) +((𝕣 = {object: ι, field: w}) ∧ + ¬typetest(χ₀, v, P.types(typeof(χ₀, 𝕣.object)).fields(w))) ∨ +((𝕣 = {object: π, field: w}) ∧ + ¬typetest(χ₀, v₀, Π.type)) +--- [store bad-type] +χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadType], throw;return x + ``` ## Type Test @@ -859,11 +871,15 @@ typetest(χ, v, φ.type) // TODO: typetest depends on condition dom(φ.vars) = {x} v = φ(x) -loc(χ, v) ≠ φ.id -typetest(χ, v, φ.type) // TODO: typetest depends on condition -// TODO: safe_store to result cown +loc(χ₀, v) ≠ φ.id +typetest(χ₀, v, φ.type) // TODO: typetest depends on condition +π = φ(final) +safe_store(χ₀, π, v) +χ₁ = χ₀[cowns(π)[value↦v]] +χ₂ = region_add_parent(χ₁, π, v) +χ₃ = region_stack_dec(χ₂, v) --- [return] -χ, φ, return x;stmt* ⇝ χ\(φ.id), φ[final↦v]\x, ∅ +χ₀, φ, return x;stmt* ⇝ χ₃\φ.id, ∅, ∅ dom(φ.vars) = {x, y} ∪ zs --- [return] @@ -1151,7 +1167,7 @@ once(w*;y*;z*) ∀z ∈ z* . safe_store(χ, 𝛽, φ(z)) πs = {φ(x′) | (x′ ∈ w*;y*)} ∪ {π} χ′ = χ[∀π′ ∈ πs . cowns(π′)[queue ++ 𝛽]] -Π = { type: T, value: false, queue: 𝛽 } +Π = { type: T, value: None, queue: 𝛽 } B = { read: {w ↦ φ(w) | w ∈ w*}, write: {y ↦ φ(y) | y ∈ y*}, capture: {z ↦ φ(z) | z ∈ z*}, @@ -1166,17 +1182,17 @@ B = { read: {w ↦ φ(w) | w ∈ w*}, 𝔽 ∉ χ ready(χ, 𝛽) π = χ(𝛽).result -φ = { id: 𝔽, - vars: {x ↦ χ(𝛽).capture(x) | x ∈ dom(χ(𝛽).capture)}, - ret: final, - type: χ(π).type, - cont: ∅, - condition: Return } -χ₁, φ₁ = read-acquire(χ, φ, χ(𝛽).read) +φ₀ = { id: 𝔽, + vars: {x ↦ χ(𝛽).capture(x) | x ∈ dom(χ(𝛽).capture)}, + ret: final, + type: χ(π).type, + cont: ∅, + condition: Return } +χ₁, φ₁ = read-acquire(χ, φ₀, χ(𝛽).read) χ₂, φ₂ = write-acquire(χ₁, φ₁, χ(𝛽).write) χ₃ = read-inc(χ₂, Θ.read) χ₄ = write-inc(χ₃, Θ.write ∪ {π}) -Θ = { stack: φ₂, +Θ = { stack: φ₂[final↦π], cont: χ(𝛽).body, read: {π′ | π′ ∈ χ(𝛽).read} write: {π′ | π′ ∈ χ(𝛽).write} @@ -1191,10 +1207,10 @@ ready(χ, 𝛽) χ ⇝ χ′[θ↦{stack: σ′, cont: stmt′*, result: π}] θ ∈ χ -χ(θ) = {stack: φ, cont: ∅, read: πs₀, write: πs₁, result: π} -χ₁ = read-dec(χ, πs₀) +χ(θ) = {stack: ∅, cont: ∅, read: πs₀, write: πs₁, result: π} +χ₁ = read-dec(χ₀, πs₀) χ₂ = write-dec(χ₁, πs₁ ∪ {π}) --- [end thread] -χ ⇝ χ₂[cowns(π)[value = φ(final)]]\θ +χ₀ ⇝ χ₂\θ ``` From f0982ff481c7d996859c22de99fb25b01bb3a082 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Mon, 7 Apr 2025 09:49:05 -0500 Subject: [PATCH 32/37] mark regions readonly --- docs/opsem.md | 286 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 173 insertions(+), 113 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index b41a8f80f..244d92ad7 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -1,6 +1,8 @@ # Operational Semantics Still to do: +* Should type checks be entirely removed from the semantics? + * Or make them more powerful, able to check mutability and more? * Discharge (send/freeze?) when stack RC for the region and all children (recursively) is 0. * Could optimize by tracking the count of "busy" child regions. * How are Arenas different from uncounted regions? @@ -35,9 +37,8 @@ ws, xs, ys, zs ∈ 𝒫(Ident) θ ∈ ThreadId T ∈ Type = Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | TypeId - | Cown TypeId | Ref TypeId | Ref Cown TypeId - | Readonly TypeId | Readonly Cown TypeId - | Ref Readonly TypeId | | Ref Readonly Cown TypeId + | Cown Type | Ref Type + | Union Type Type | Raise Type | Throw Type 𝕥 ∈ TypeDesc = { @@ -57,15 +58,15 @@ P ∈ Program = { primitives: Type ↦ TypeId, types: TypeId ↦ TypeDesc, - functions: FunctionId ↦ Function + functions: FunctionId ↦ Function, + globals: Ident ↦ Value } -𝕣 ∈ Reference = {object: ObjectId | CownId, field: Ident} +𝕣 ∈ Reference = {target: ObjectId | CownId, field: Ident} Error = BadType | BadTarget | BadField | BadStore | BadMethod | BadArgs | BadReturnLoc | BadReturnType p ∈ Primitive = None | Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | Error v ∈ Value = ObjectId | Primitive | Reference | CownId - | Readonly ObjectId | Readonly CownId ω ∈ Object = Ident ↦ Value Condition = Return | Raise | Throw @@ -86,7 +87,8 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena Region = { type: RegionType, parent: RegionId | CownId | BehaviorId | None, - stack_rc: ℕ + stack_rc: ℕ, + readonly: Bool } // An object located in another object is an embedded field. @@ -172,7 +174,7 @@ x ∈ φ ≝ x ∈ dom(φ.vars) // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) -χ[ρ↦R] = χ[regions(ρ)↦{type: R, parent: None, stack_rc: 0}] +χ[ρ↦R] = χ[regions(ρ)↦{type: R, parent: None, stack_rc: 0, readonly: false}] χ\ρ = χ\{ρ} χ\ρs = χ[regions \= ρs] @@ -208,19 +210,18 @@ typeof(χ, v) = P.primitives(Float × ℕ) if v ∈ Float × ℕ P.primitives(Error) if v ∈ Error χ.metadata(ι).type if ι = v - Readonly χ.metadata(ι).type if Readonly ι = v - Ref P.types(typeof(χ, ι).field(𝕣.field).type if (𝕣 = v) ∧ (𝕣.object = ι) - Readonly Ref P.types(typeof(χ, ι).field(𝕣.field).type if - (𝕣 = v) ∧ (𝕣.object = Readonly ι) Cown χ(π).type if π = v - Readonly Cown χ(π).type if Readonly π = v - Ref Cown χ(π).type if (𝕣 = v) ∧ (𝕣.object = π) - Ref Readonly Cown χ(π).type if (𝕣 = v) ∧ (𝕣.object = Readonly π) + Ref P.types(typeof(χ, ι).field(𝕣.field).type if (𝕣 = v) ∧ (𝕣.target = ι) + Ref χ(π).type if (𝕣 = v) ∧ (𝕣.target = π) + +typetest(T₀, T₁) = + typetest(T₂, T₁) ∧ typetest(T₃, T₁) if T₀ = Union T₂ T₃ + typetest(T₀, T₂) ∨ typetest(T₀, T₃) if T₁ = Union T₂ T₃ + T₀ = T₁ if (T₀ ∈ Ref T) ∨ (T₀ ∈ CownId T) ∨ (T₁ ∈ Ref T) ∨ (T₁ ∈ CownId T) + T₁ ∈ P.types(τ).supertypes if T₀ = τ + false otherwise -// Subtype test. -typetest(χ, v, T) = - T = typeof(χ, v) if (v ∈ Reference) ∨ (v ∈ CownId) - T ∈ P.types(typeof(χ, v)).supertypes otherwise +typetest(χ, v, T) = typetest(typeof(χ, v), T) ``` @@ -239,7 +240,7 @@ reachable(χ, {v} ∪ vs) = reachable(χ, v) ∪ reachable(χ, vs) reachable(χ, v) = reachable(χ, v, ∅) reachable(χ, p, ιs) = ιs reachable(χ, π, ιs) = ιs -reachable(χ, 𝕣, ιs) = reachable(χ, 𝕣.object, ιs) +reachable(χ, 𝕣, ιs) = reachable(χ, 𝕣.target, ιs) reachable(χ, ι, ιs) = ιs if ι ∈ ιs reachable(χ, ι, {ι} ∪ ιs, dom(χ(ι))) otherwise @@ -250,16 +251,16 @@ reachable(χ, ι, ιs, {w} ∪ ws) = reachable(χ, ι, ιs, w) ∪ reachable(χ, ι, ιs, ws) reachable(χ, ι, ιs, w) = reachable(χ, χ(ι)(w), ιs) -// Region. +// Location of a value. loc(χ, p) = Immutable loc(χ, π) = Immutable -loc(χ, 𝕣) = loc(χ, 𝕣.object) -loc(χ, Readonly ι) = loc(χ, ι) +loc(χ, 𝕣) = + loc(χ, 𝕣.target) if ι = 𝕣.target + π if π = 𝕣.target loc(χ, ι) = loc(χ, ι′) if χ.metadata(ι).location = ι′ χ.metadata(ι).location if ι ∈ χ Immutable otherwise -loc(χ, π) = Immutable same_loc(χ, v₀, v₁) = (loc(χ, v₀) = loc(χ, v₁)) members(χ, ρ) = {ι | (ι ∈ χ) ∧ (loc(χ, ι) = ρ)} @@ -283,29 +284,31 @@ This enforces a tree-shaped region graph, with a single reference from parent to safe_store(χ, Immutable, v) = false safe_store(χ, 𝔽, v) = true if loc(χ, v) = Immutable - true if (loc(χ, v) = ρ) + true if loc(χ, v) = π + true if loc(χ, v) = ρ true if (loc(χ, v) = 𝔽′) ∧ (𝔽 >= 𝔽′) false otherwise safe_store(χ, ρ, v) = + false if χ(ρ).readonly + false if (loc(χ, v) = ρ′) ∧ χ(ρ′).readonly false if finalizing(χ, v) - true if loc(χ, v) = Immutable) - true if loc(χ, v) = ρ + true if loc(χ, v) = Immutable + true if (loc(χ, v) = ρ) true if (loc(χ, v) = ρ′) ∧ (parent(χ, ρ′) = None) ∧ ¬is_ancestor(χ, ρ′, ρ) false otherwise safe_store(χ, π, v) = - false if finalizing(χ, v) true if loc(χ, v) = Immutable - true if (loc(χ, v) = ρ) ∧ (parent(χ, ρ) = None) + true if (loc(χ, v) = ρ) ∧ (parent(χ, ρ) = None) ∧ + ¬finalizing(χ, v) ∧ ¬χ(ρ).readonly false otherwise safe_store(χ, 𝛽, v) = - false if finalizing(χ, v) true if loc(χ, v) = Immutable - true if (loc(χ, v) = ρ) ∧ (parent(χ, ρ) = None) + true if (loc(χ, v) = ρ) ∧ (parent(χ, ρ) = None) ∧ + ¬finalizing(χ, v) ∧ ¬χ(ρ).readonly false otherwise finalizing(χ, p) = false -finalizing(χ, π) = false -finalizing(χ, 𝕣) = finalizing(χ, 𝕣.object) +finalizing(χ, 𝕣) = finalizing(χ, 𝕣.target) finalizing(χ, ι) = (ι ∈ χ.pre_final) ∨ (ι ∈ χ.post_final) finalizing(χ, π) = false @@ -315,6 +318,10 @@ finalizing(χ, π) = false ```rs +// Globals are immutable. +wf_globals(χ) = + ∀w ∈ dom(P.globals) . (loc(χ, P.globals(w)) = Immutable) + // Deep immutability. wf_immutable(χ) = ∀ι₀, ι₁ ∈ χ . @@ -357,6 +364,12 @@ wf_cownvalue(χ) = (loc(χ(π).value) = Immutable) ∨ ((loc(χ(π).value) = ρ) ∧ (parent(χ, ρ) = π)) +// Fields don't have Raise or Throw types. +wf_fieldtypes(P) = + ∀τ ∈ dom(P.types) . + ∀w ∈ dom(P.types(τ).fields) . + P.types(τ).fields(w) ∉ {Raise T, Throw T} + ``` ## Region Type Change @@ -406,7 +419,7 @@ calc_heap_rc(χ, ρ, ι) = |{(ι′, w) | (ι′ ∈ members(χ, ρ)) ∧ (w ∈ dom(χ(ι′))) ∧ - ((χ(ι′)(w) = ι)) ∨ ((χ(ι′)(w) = 𝕣) ∧ (𝕣.object = ι))}| + ((χ(ι′)(w) = ι)) ∨ ((χ(ι′)(w) = 𝕣) ∧ (𝕣.target = ι))}| ``` @@ -417,20 +430,19 @@ Reference counting is a no-op unless the object is in a `RegionRC` or is `Immuta ```rs enable-rc(χ, ι) = - ((loc(χ, ι)) = ρ ∧ (ρ.type = RegionRC)) ∨ (loc(χ, ι) = Immutable) + ((loc(χ, ι)) = ρ ∧ (ρ.readonly = false) ∧ (ρ.type = RegionRC)) ∨ + (loc(χ, ι) = Immutable) region_stack_inc(χ, p) = χ region_stack_inc(χ, π) = χ -region_stack_inc(χ, 𝕣) = region_stack_inc(χ, 𝕣.object) -region_stack_inc(χ, Readonly ι) = χ +region_stack_inc(χ, 𝕣) = region_stack_inc(χ, 𝕣.target) region_stack_inc(χ, ι) = χ[regions(ρ)[stack_rc += 1]] if (loc(χ, ι) = ρ) χ otherwise region_stack_dec(χ, p) = χ region_stack_dec(χ, π) = χ -region_stack_dec(χ, 𝕣) = region_stack_dec(χ, 𝕣.object) -region_stack_dec(χ, Readonly ι) = χ +region_stack_dec(χ, 𝕣) = region_stack_dec(χ, 𝕣.target) region_stack_dec(χ, ι) = χ[pre_final_r ∪= {ρ}] if (loc(χ, ι) = ρ) ∧ @@ -440,7 +452,8 @@ region_stack_dec(χ, ι) = χ otherwise region_add_parent(χ, ι, p) = χ -region_add_parent(χ, ι, 𝕣) = region_add_parent(χ, ι, 𝕣.object) +region_add_parent(χ, ι, π) = χ +region_add_parent(χ, ι, 𝕣) = region_add_parent(χ, ι, 𝕣.target) region_add_parent(χ, ι, ι′) = χ[regions(ρ′)[parent = ρ]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) @@ -448,13 +461,15 @@ region_add_parent(χ, ι, ι′) = χ otherwise region_add_parent(χ, π, p) = χ -region_add_parent(χ, π, 𝕣) = region_add_parent(χ, ι, 𝕣.object) +region_add_parent(χ, π, π′) = χ +region_add_parent(χ, π, 𝕣) = region_add_parent(χ, ι, 𝕣.target) region_add_parent(χ, π, ι) = χ[regions(ρ)[parent = π]] if loc(χ, ι) = ρ χ otherwise region_remove_parent(χ, ι, p) = χ -region_remove_parent(χ, ι, 𝕣) = region_remove_parent(χ, ι, 𝕣.object) +region_remove_parent(χ, ι, π) = χ +region_remove_parent(χ, ι, 𝕣) = region_remove_parent(χ, ι, 𝕣.target) region_remove_parent(χ, ι, ι′) = χ[regions(ρ′)[parent = None]] if (loc(χ, ι) = ρ) ∧ (loc(χ, ι′) = ρ′) ∧ (ρ ≠ ρ′) @@ -462,15 +477,17 @@ region_remove_parent(χ, ι, ι′) = χ otherwise region_remove_parent(χ, π, p) = χ -region_remove_parent(χ, π, 𝕣) = region_remove_parent(χ, ι, 𝕣.object) +region_remove_parent(χ, π, π′) = χ +region_remove_parent(χ, π, 𝕣) = region_remove_parent(χ, ι, 𝕣.target) region_remove_parent(χ, π, ι) = χ[regions(ρ)[parent = None]] if loc(χ, ι) = ρ χ otherwise inc(χ, p) = χ inc(χ, π) = χ[cowns(π)[rc += 1]] -inc(χ, 𝕣) = dec(χ, 𝕣.object) -inc(χ, Readonly ι) = χ +inc(χ, 𝕣) = + inc(χ, 𝕣.target) if ι = 𝕣.target + χ if π = 𝕣.target inc(χ, ι) = inc(χ, ι′) if χ.metadata(ι).location = ι′ χ[metadata(ι)[rc += 1]] if enable-rc(χ, ι) @@ -478,8 +495,9 @@ inc(χ, ι) = dec(χ, p) = χ dec(χ, π) = χ[cowns(π)[rc -= 1]] // TODO: free -dec(χ, 𝕣) = dec(χ, 𝕣.object) -dec(χ, Readonly ι) = χ +dec(χ, 𝕣) = + dec(χ, 𝕣.target) if ι = 𝕣.target + χ if π = 𝕣.target dec(χ, ι) = dec(χ, ι′) if χ.metadata(ι).location = ι′ χ[pre_final ∪= {ι}] if enable-rc(χ, ι) ∧ (χ.metadata(ι).rc = 1) @@ -526,7 +544,7 @@ gc_dec_fields(χ, ι, {w} ∪ ws, ιs₁) = gc_dec_field(χ, ι, p, ιs₁) = χ gc_dec_field(χ, ι, π, ιs₁) = χ -gc_dec_field(χ, ι, 𝕣, ιs₁) = gc_dec_field(χ, ι, 𝕣.object) +gc_dec_field(χ, ι, 𝕣, ιs₁) = gc_dec_field(χ, ι, 𝕣.target) gc_dec_field(χ, ι, ι′, ιs₁) = dec(χ, ι′) if (ι′ ∈ ιs₁) ∨ (loc(χ, ι′) = Immutable) χ otherwise @@ -553,7 +571,7 @@ free_fields(χ, ιs, ι, {w} ∪ ws) = free_field(χ, ιs, ι, p) = χ, ιs free_field(χ, ιs, ι, π) = χ, ιs -free_field(χ, ιs, ι, 𝕣) = free_field(χ, ιs, ι, 𝕣.object) +free_field(χ, ιs, ι, 𝕣) = free_field(χ, ιs, ι, 𝕣.target) free_field(χ, ιs, ι, ι′) = χ, ιs if ι′ ∈ ιs free_fields(χ, {ι′} ∪ ιs, ι′), {ι′} ∪ ιs if @@ -567,6 +585,18 @@ free_field(χ, ιs, ι, ι′) = ``` +## Global Values + +```rs + +x ∉ φ +v = P.globals(y) +χ₁ = inc(χ₀, v) +--- [global] +χ₀, σ;φ, bind x (global y);stmt* ⇝ χ₁, σ;φ[x↦v], stmt* + +``` + ## New For an "address-taken" local variable, i.e. a `var` as opposed to a `let`, allocate an object in the frame with a single field to hold the value. @@ -580,7 +610,7 @@ newobject(φ, (y, z)*) = {y ↦ φ(z) | y ∈ (y, z)*} typecheck(χ, τ, ω) = (dom(P.types(τ).fields) = dom(ω)) ∧ - ∀w ∈ dom(ω) . typetest(χ, P.types(τ).fields(w), ω(w)) + ∀w ∈ dom(ω) . typetest(χ, ω(w), P.types(τ).fields(w)) x ∉ φ --- [new primitive] @@ -686,38 +716,28 @@ The `load` statement is the only operation other than `dup` or `drop` that can c ```rs -readonly(χ, p) = p -readonly(χ, {object: ι, field: w}) = {object: readonly(χ, ι), field: w} -readonly(χ, {object: Readonly ι, field: w}) = {object: Readonly ι, field: w} -readonly(χ, {object: π, field: w}) = {object: Readonly π, field: w} -readonly(ι) = Readonly ι -readonly(Readonly ι) = Readonly ι -readonly(π) = π - x ∉ ϕ -(ι = ϕ(y)) ∨ (Readonly ι = ϕ(y)) +ι = ϕ(y) w ∈ dom(P.types(typeof(χ, ι)).fields) -𝕣 = {object: ϕ(y), field: w} +𝕣 = {target: ϕ(y), field: w} --- [ref] χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦𝕣]\y, stmt* x ∉ ϕ -(ϕ(y) ∉ ObjectId) ∧ (ϕ(y) ∉ Readonly ObjectId) +ϕ(y) ∉ ObjectId --- [ref bad-target] χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦BadTarget]\y, throw;return x x ∉ ϕ -(ι = ϕ(y)) ∨ (Readonly ι = ϕ(y)) +ι = ϕ(y) w ∉ dom(P.types(typeof(χ, ι)).fields) --- [ref bad-field] χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦BadField]\y, throw;return x x ∉ ϕ 𝕣 = φ(y) -v = χ₀(ι)(w) if 𝕣 = {object: ι, field: w} - readonly(χ, χ₀(ι)(w)) if 𝕣 = {object: Readonly ι, field: w} - χ₀(π).value if 𝕣 = {object: π, field: w} - readonly(χ₀(π).value) if 𝕣 = {object: Readonly π, field: w} +v = χ₀(ι)(w) if 𝕣 = {target: ι, field: w} + χ₀(π).value if 𝕣 = {target: π, field: w} χ₁ = region_stack_inc(χ₀, v) χ₂ = inc(χ₁, v) --- [load] @@ -731,38 +751,38 @@ x ∉ ϕ x ∉ ϕ 𝕣 = φ(y) v₀ = φ(z) -safe_store(χ₀, loc(χ₀, 𝕣.object), v₀) +safe_store(χ₀, loc(χ₀, 𝕣.target), v₀) v₁, χ₁ = ω(w), χ₀[ι↦ω[w↦v₀]] if - (𝕣 = {object: ι, field: w}) ∧ (ω = χ₀(ι)) ∧ + (𝕣 = {target: ι, field: w}) ∧ (ω = χ₀(ι)) ∧ typetest(χ₀, v₀, P.types(typeof(χ₀, ι)).fields(w)) Π.value, χ₀[π↦Π[value↦v₀]] if - (𝕣 = {object: π, field: w}) ∧ (Π = χ₀(π)) ∧ + (𝕣 = {target: π, field: w}) ∧ (Π = χ₀(π)) ∧ typetest(χ₀, v₀, Π.type) χ₂ = region_stack_inc(χ₁, v₁) -χ₃ = region_remove_parent(χ₃, 𝕣.object, v₁) -χ₄ = region_add_parent(χ₃, 𝕣.object, v₀) +χ₃ = region_remove_parent(χ₃, 𝕣.target, v₁) +χ₄ = region_add_parent(χ₃, 𝕣.target, v₀) χ₅ = region_stack_dec(χ₄, v₀) --- [store] χ₀, σ;ϕ, bind x (store y z);stmt* ⇝ χ₅, σ;ϕ[x↦v₁]\z, stmt* x ∉ ϕ -(ϕ(y) ∉ Reference) ∨ (φ(y).object = Readonly ι) ∨ (φ(y).object = Readonly π) +ϕ(y) ∉ Reference --- [store bad-target] χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadTarget], throw;return x x ∉ ϕ 𝕣 = φ(y) v = φ(z) -¬safe_store(χ₀, loc(χ, 𝕣.object), v₁) +¬safe_store(χ₀, loc(χ, 𝕣.target), v₁) --- [store bad-store] χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadStore], throw;return x x ∉ ϕ 𝕣 = φ(y) v = φ(z) -((𝕣 = {object: ι, field: w}) ∧ - ¬typetest(χ₀, v, P.types(typeof(χ₀, 𝕣.object)).fields(w))) ∨ -((𝕣 = {object: π, field: w}) ∧ +((𝕣 = {target: ι, field: w}) ∧ + ¬typetest(χ₀, v, P.types(typeof(χ₀, 𝕣.target)).fields(w))) ∨ +((𝕣 = {target: π, field: w}) ∧ ¬typetest(χ₀, v₀, Π.type)) --- [store bad-type] χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadType], throw;return x @@ -838,6 +858,12 @@ typecheck(χ, φ₀, F, y*) --- [call dynamic] χ, σ;φ₀, bind x (call w y*);stmt* ⇝ χ∪(φ₁.id), σ;φ₀\{y*};φ₁, F.body +x ∉ φ +once(y*) +τ ≠ typeof(χ, φ(y₁)) +--- [call dynamic bad-target] +χ, σ;φ, bind x (call w y*);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x + x ∉ φ once(y*) τ = typeof(χ, φ(y₁)) @@ -864,7 +890,10 @@ This drops any remaining frame variables other than the return value. dom(φ₁.vars) = {x} v = φ₁(x) loc(χ, v) ≠ φ₁.id -typetest(χ, v, φ.type) // TODO: typetest depends on condition +T = typeof(χ, v) if φ₁.condition = Return + Raise typeof(χ, v) if φ₁.condition = Raise + Throw typeof(χ, v) if φ₁.condition = Throw +typetest(T, φ.type) φ₂ = φ₀[φ₁.ret↦v, condition = φ₁.condition] --- [return] χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ\(φ₁.id), σ;φ₂, ϕ₁.cont @@ -872,7 +901,10 @@ typetest(χ, v, φ.type) // TODO: typetest depends on condition dom(φ.vars) = {x} v = φ(x) loc(χ₀, v) ≠ φ.id -typetest(χ₀, v, φ.type) // TODO: typetest depends on condition +T = typeof(χ, v) if φ₁.condition = Return + Raise typeof(χ, v) if φ₁.condition = Raise + Throw typeof(χ, v) if φ₁.condition = Throw +typetest(T, φ.type) π = φ(final) safe_store(χ₀, π, v) χ₁ = χ₀[cowns(π)[value↦v]] @@ -887,16 +919,19 @@ dom(φ.vars) = {x, y} ∪ zs dom(φ.vars) = {x} v = φ(x) -loc(χ, v) = φ.id +(loc(χ, v) = φ.id) ∨ ((π = φ(final)) ∧ ¬safe_store(χ, π, v)) --- [return bad-loc] -χ, σ;φ, return x;stmt* ⇝ χ, σ;φ[y↦BadReturnLoc], drop x;throw;return y +χ, σ;φ, return x;stmt* ⇝ χ, σ;φ[y↦BadReturnLoc], throw;return y dom(φ.vars) = {x} v = φ(x) loc(χ, v) ≠ φ.id -¬typetest(χ, v, F.result) +T = typeof(χ, v) if φ₁.condition = Return + Raise typeof(χ, v) if φ₁.condition = Raise + Throw typeof(χ, v) if φ₁.condition = Throw +¬typetest(T, φ.type) --- [return bad-type] -χ, σ;φ, return x;stmt* ⇝ χ, σ;φ[y↦BadReturnType], drop x;throw;return y +χ, σ;φ, return x;stmt* ⇝ χ, σ;φ[y↦BadReturnType], throw;return y ``` @@ -1015,7 +1050,8 @@ x ∉ φ (ρ = loc(χ, ι′)) ∧ (ρ ≠ ρ₀)} rc = calc_stack_rc(χ₀, σ;φ, ιs) χ₁ = χ₀[regions(ρ₀)[stack_rc -= rc], - regions(ρ₁)↦{type: χ.regions(ρ₀).type, parent: None, stack_rc: rc}, + regions(ρ₁)↦{ type: χ.regions(ρ₀).type, parent: None, + stack_rc: rc, readonly: false }, ∀ι′ ∈ ιs . metadata(ι′)[location = ρ₁], ∀ρ ∈ ρs . regions(ρ)[parent = ρ₁]] --- [extract] @@ -1104,15 +1140,36 @@ ready(χ, 𝛽) = (ρs = {ρ | (ι ∈ χ(𝛽).capture) ∧ (loc(χ, ι) = ρ)}) ∧ (ρs′ = {ρ′| (ρ ∈ ρs) ∧ (ρ′ ∈ χ) ∧ is_ancestor(χ, ρ, ρ′)}) +mark-readonly(χ, π) = + mark-readonly(χ, {ρ} ∪ {ρ′ | ρ′ ∈ χ ∧ is_ancestor(ρ, ρ′)}) if + ρ = loc(χ, χ(π).value) + χ otherwise +mark-readonly(χ, {ρ} ∪ ρs) = + mark-readonly(χ′, ρs) + where + χ′ = mark-readonly(χ, ρ) +mark-readonly(χ, ρ) = χ[regions(ρ)[readonly = true]] + +unmark-readonly(χ, π) = + unmark-readonly(χ, {ρ} ∪ {ρ′ | ρ′ ∈ χ ∧ is_ancestor(ρ, ρ′)}) if + ρ = loc(χ, χ(π).value) + χ otherwise +unmark-readonly(χ, {ρ} ∪ ρs) = + unmark-readonly(χ′, ρs) + where + χ′ = unmark-readonly(χ, ρ) +unmark-readonly(χ, ρ) = χ[regions(ρ)[readonly = false]] + read-inc(χ, ∅) = χ read-inc(χ, {π} ∪ πs) = read-inc(χ′, πs) where χ′ = read-inc(χ, π) read-inc(χ, π) = - χ[cowns(π)[queue = 𝛽*, read += 1]] + χ′[cowns(π)[queue = 𝛽*, read += 1]] where - χ(π).queue = 𝛽;𝛽* + χ(π).queue = 𝛽;𝛽 ∧ + χ′ = mark-readonly(χ, π) write-inc(χ, ∅) = χ write-inc(χ, {π} ∪ πs) = @@ -1129,7 +1186,10 @@ read-dec(χ, {π} ∪ πs) = read-dec(χ′, πs) where χ′ = read-dec(χ, π) -read-dec(χ, π) = χ[cowns(π)[rc -= 1, read -= 1]] // TODO: free +read-dec(χ, π) = + χ′[cowns(π)[rc -= 1, read -= 1]] // TODO: free + where + χ′ = unmark-readonly(χ, π) write-dec(χ, ∅) = χ write-dec(χ, {π} ∪ πs) = @@ -1138,22 +1198,21 @@ write-dec(χ, {π} ∪ πs) = χ′ = write-dec(χ, π) write-dec(χ, π) = χ[cowns(π)[rc -= 1, write -= 1]] // TODO: free -read-acquire(χ, φ, ∅) = χ, φ -read-acquire(χ, φ, ω) = - read-acquire(χ′, φ′, ω\x) +read-acquire(φ, ∅) = φ +read-acquire(φ, ω) = + read-acquire(φ′, ω\x) where x ∈ dom(ω) ∧ π = ω(x) ∧ - φ′ = φ[x↦readonly(χ(π).value)] + φ′ = φ[x↦χ(π).value] -write-acquire(χ, φ, ∅) = χ, φ -write-acquire(χ, φ, ω) = - write-acquire(χ′, φ′, ω\x) +write-acquire(φ, ∅) = φ +write-acquire(φ, ω) = + write-acquire(φ′, ω\x) where x ∈ dom(ω) ∧ π = ω(x) ∧ - χ′ = inc(χ, π) ∧ - φ′ = φ[x↦{object: π, field: final}] + φ′ = φ[x↦{target: π, field: final}] // TODO: regions put in a behavior need to set a parent to prevent them being put anywhere else. // what if z* contains multiple objects in the same region, and that region has no parent? is that ok? @@ -1177,34 +1236,35 @@ B = { read: {w ↦ φ(w) | w ∈ w*}, χ, σ;φ, bind x (when T (read w*) (write y*) (capture z*) stmt₀*);stmt₁* ⇝ χ′[π↦Π, 𝛽↦B]∪𝔽, σ;φ[x↦π]\(w*;y*;z*), stmt₁* -𝛽 ∈ χ -θ ∉ χ -𝔽 ∉ χ -ready(χ, 𝛽) -π = χ(𝛽).result +𝛽 ∈ χ₀ +θ ∉ χ₀ +𝔽 ∉ χ₀ +ready(χ₀, 𝛽) +πs₀ = {π′ | π′ ∈ χ₀(𝛽).read} \ πs₁ +πs₁ = {π′ | π′ ∈ χ₀(𝛽).write} +π = χ₀(𝛽).result φ₀ = { id: 𝔽, - vars: {x ↦ χ(𝛽).capture(x) | x ∈ dom(χ(𝛽).capture)}, + vars: {x ↦ χ₀(𝛽).capture(x) | x ∈ dom(χ₀(𝛽).capture)}, ret: final, - type: χ(π).type, + type: χ₀(π).type, cont: ∅, condition: Return } -χ₁, φ₁ = read-acquire(χ, φ₀, χ(𝛽).read) -χ₂, φ₂ = write-acquire(χ₁, φ₁, χ(𝛽).write) -χ₃ = read-inc(χ₂, Θ.read) -χ₄ = write-inc(χ₃, Θ.write ∪ {π}) Θ = { stack: φ₂[final↦π], - cont: χ(𝛽).body, - read: {π′ | π′ ∈ χ(𝛽).read} - write: {π′ | π′ ∈ χ(𝛽).write} + cont: χ₀(𝛽).body, + read: πs₀ + write: πs₁ result: π } +φ₁ = read-acquire(φ₀, χ₀(𝛽).read) +φ₂ = write-acquire(φ₁, χ₀(𝛽).write) +χ₁ = read-inc(χ₀, Θ.read) +χ₂ = write-inc(χ₁, Θ.write ∪ {π}) --- [start thread] -χ ⇝ χ₂[θ↦Θ]\𝛽 +χ₀ ⇝ χ₂[θ↦Θ]\𝛽 θ ∈ χ -χ(θ) = {σ, stmt*, π} -χ, σ, stmt* ⇝ χ′, σ′, stmt′* +χ, χ(θ).stack, χ(θ).cont ⇝ χ′, σ′, stmt′* --- [step thread] -χ ⇝ χ′[θ↦{stack: σ′, cont: stmt′*, result: π}] +χ ⇝ χ′[threads(θ)[stack = σ′, cont = stmt′*]] θ ∈ χ χ(θ) = {stack: ∅, cont: ∅, read: πs₀, write: πs₁, result: π} From 58a5c86afb017f379ba6d434d261d5ba8cc5b0f8 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Mon, 7 Apr 2025 09:58:53 -0500 Subject: [PATCH 33/37] minor edit --- docs/opsem.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/opsem.md b/docs/opsem.md index 244d92ad7..ce1895e49 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -8,7 +8,6 @@ Still to do: * How are Arenas different from uncounted regions? * How should they treat changing region type? * How should they treat merging, freezing, extract? -* Behaviors and cowns. * Embedded object fields? * Arrays? Or model them as objects? From 0e216d58f1011309594dd409330230accf71b2de Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Tue, 8 Apr 2025 13:56:22 +0100 Subject: [PATCH 34/37] Some comments and small changes from reading through. --- docs/opsem.md | 63 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index ce1895e49..b5a736c5d 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -64,7 +64,15 @@ P ∈ Program = 𝕣 ∈ Reference = {target: ObjectId | CownId, field: Ident} Error = BadType | BadTarget | BadField | BadStore | BadMethod | BadArgs | BadReturnLoc | BadReturnType + +// mjp: Is this primitive values, or the type names? +// There is a confusion here. Does `Bool` mean the type or the set of +// values of the type? Earlier is was the type, but here I think it is the values? +// Perhaps use NoneV, BoolV, ... instead of None, Bool, ...? +// mjp: Signed × ℕ is this a product type or the inhabitants of the type. +// /Not sure what a good syntax here is p ∈ Primitive = None | Bool | Signed × ℕ | Unsigned × ℕ | Float × ℕ | Error + v ∈ Value = ObjectId | Primitive | Reference | CownId ω ∈ Object = Ident ↦ Value @@ -90,11 +98,15 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena readonly: Bool } + // mjp: Factored this out as a lot of the helpers use this. + // mjp: Should this have CownId? + Location = RegionId | FrameId | ObjectId | Immutable + // An object located in another object is an embedded field. Metadata = { type: TypeId, - location: RegionId | FrameId | ObjectId | Immutable, + location: Location, rc: ℕ } @@ -146,19 +158,44 @@ Heap, Stack, Statement* ⇝ Heap, Stack, Statement* ``` ## Helpers +We use `.` notation for accessing fields of a record: +```rs +{label0↦value0, ... labeln↦valuen}.labeli = valuei +``` + +We use `[.. ↦ ..]` notation for updating a record: +```rs +r[labeli ↦ value] = {label0 ↦ r(label0), ..., labeli ↦ value, ..., labeln ↦ r(labeln)} + where r = {label0↦value0, ... labeln↦valuen} +``` + +We use `[..(..) ↦ ..]` notation for updating an element of a function component of a record: +```rs +r[label(idx) ↦ v] = r[label ↦ r(label)[idx ↦ v]] +``` +We use `[.. op= ..]` notation for updating a component of a record with a specific operation: ```rs +r[labeli op= value] = r[labeli ↦ r(labeli) op value] +``` +We compose updates with `[.., ..]`: +```rs +r[upd1, upd2] = r[upd1][upd2] +``` + +mjp: You use ϕ for frames, but here I think you are using φ for frames. Should we just use one? +```rs // Frames. x ∈ φ ≝ x ∈ dom(φ.vars) φ(x) = φ.vars(x) φ[x↦v] = φ[vars(x)↦v] -φ\x = φ\{x} φ\xs = φ[vars \= xs] +φ\x = φ\{x} 𝔽 ∈ χ ≝ φ ∈ dom(χ.frames) -χ∪𝔽 = χ[frames ∪= 𝔽] -χ\𝔽 = χ[frames \= 𝔽] +χ∪𝔽 = χ[frames ∪= {𝔽}] +χ\𝔽 = χ[frames \= {𝔽}] // Heap objects. ι ∈ χ ≝ ι ∈ dom(χ.data) @@ -174,8 +211,8 @@ x ∈ φ ≝ x ∈ dom(φ.vars) // Regions. ρ ∈ χ ≝ ρ ∈ dom(χ.regions) χ[ρ↦R] = χ[regions(ρ)↦{type: R, parent: None, stack_rc: 0, readonly: false}] -χ\ρ = χ\{ρ} χ\ρs = χ[regions \= ρs] +χ\ρ = χ\{ρ} // Cowns. π ∈ χ ≝ π ∈ dom(χ.cowns) @@ -198,25 +235,31 @@ x ∈ φ ≝ x ∈ dom(φ.vars) ``` ## Dynamic Types +The following definition are all implicitly passed the current program, `P`. ```rs // Dynamic type of a value. typeof(χ, v) = + // mjp: Perhaps BoolV based on comment above of dual use of Bool here. P.primitives(Bool) if v ∈ Bool + // mjp: Signed × ℕ shouldn't this be an element of the type? (Signed,n) rather than the product itself? P.primitives(Signed × ℕ) if v ∈ Signed × ℕ P.primitives(Unsigned × ℕ) if v ∈ Unsigned × ℕ P.primitives(Float × ℕ) if v ∈ Float × ℕ P.primitives(Error) if v ∈ Error χ.metadata(ι).type if ι = v Cown χ(π).type if π = v - Ref P.types(typeof(χ, ι).field(𝕣.field).type if (𝕣 = v) ∧ (𝕣.target = ι) + // mjp: Is the recursion well-founded here? The argument is not clear to me, the semantics allows references in fields? + // This definition might need to be coinductive if that is the case. + Ref P.types(typeof(χ, ι).field(𝕣.field).type) if (𝕣 = v) ∧ (𝕣.target = ι) Ref χ(π).type if (𝕣 = v) ∧ (𝕣.target = π) typetest(T₀, T₁) = typetest(T₂, T₁) ∧ typetest(T₃, T₁) if T₀ = Union T₂ T₃ typetest(T₀, T₂) ∨ typetest(T₀, T₃) if T₁ = Union T₂ T₃ T₀ = T₁ if (T₀ ∈ Ref T) ∨ (T₀ ∈ CownId T) ∨ (T₁ ∈ Ref T) ∨ (T₁ ∈ CownId T) + // mjp: Is supertypes transitive? Does it need to be here? T₁ ∈ P.types(τ).supertypes if T₀ = τ false otherwise @@ -229,9 +272,12 @@ typetest(χ, v, T) = typetest(typeof(χ, v), T) ```rs // Transitive closure. +// mjp: Do you really mean forall here? Is this a predicate? Or is this really: +// ⋃_(σ ∈ σs) {reachable(χ, σ)} +// ... reachable(χ, σs) = ∀σ ∈ σs . ⋃{reachable(χ, σ)} reachable(χ, σ) = ∀φ ∈ σ . ⋃{reachable(χ, φ)} -reachable(χ, φ) = ∀x ∈ dom(φ) . ⋃{reachable(χ, φ(x))} +reachable(χ, φ) = ∀x ∈ φ . ⋃{reachable(χ, φ(x))} reachable(χ, ∅) = ∅ reachable(χ, {v} ∪ vs) = reachable(χ, v) ∪ reachable(χ, vs) @@ -279,13 +325,12 @@ is_ancestor(χ, ρ₀, ρ₁) = This enforces a tree-shaped region graph, with a single reference from parent to child. ```rs - safe_store(χ, Immutable, v) = false safe_store(χ, 𝔽, v) = true if loc(χ, v) = Immutable true if loc(χ, v) = π true if loc(χ, v) = ρ - true if (loc(χ, v) = 𝔽′) ∧ (𝔽 >= 𝔽′) + true if (loc(χ, v) = 𝔽′) ∧ (𝔽 >= 𝔽′) // MJP: What is the order on FrameIds? Is this coming from the stack in χ? false otherwise safe_store(χ, ρ, v) = false if χ(ρ).readonly From f63ae3d5b00ca550fd30c47ccd6002d8b1ea47f2 Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Tue, 8 Apr 2025 14:53:58 +0100 Subject: [PATCH 35/37] Some more comments and small changes from reading through. --- docs/opsem.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index b5a736c5d..c50cfaee8 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -61,6 +61,7 @@ P ∈ Program = globals: Ident ↦ Value } +// mjp: What is a reference to a Cown usage? 𝕣 ∈ Reference = {target: ObjectId | CownId, field: Ident} Error = BadType | BadTarget | BadField | BadStore | BadMethod | BadArgs | BadReturnLoc | BadReturnType @@ -277,27 +278,25 @@ typetest(χ, v, T) = typetest(typeof(χ, v), T) // ... reachable(χ, σs) = ∀σ ∈ σs . ⋃{reachable(χ, σ)} reachable(χ, σ) = ∀φ ∈ σ . ⋃{reachable(χ, φ)} -reachable(χ, φ) = ∀x ∈ φ . ⋃{reachable(χ, φ(x))} +reachable(χ, φ) = ∀x ∈ φ . ⋃{reachable(χ, φ(x), ∅)} -reachable(χ, ∅) = ∅ -reachable(χ, {v} ∪ vs) = reachable(χ, v) ∪ reachable(χ, vs) +reachable(χ, ∅, ιs) = ιs +// mjp: Technically need to show this is a function as the choice of v is arbitrary. Would need to show order is irrelevant. +reachable(χ, {v} ⊎ vs, ιs) = reachable(χ, vs, reachable(χ, v, ιs)) -reachable(χ, v) = reachable(χ, v, ∅) reachable(χ, p, ιs) = ιs reachable(χ, π, ιs) = ιs reachable(χ, 𝕣, ιs) = reachable(χ, 𝕣.target, ιs) reachable(χ, ι, ιs) = ιs if ι ∈ ιs - reachable(χ, ι, {ι} ∪ ιs, dom(χ(ι))) otherwise + reachable(χ, ⋃_(w ∈ dom(χ(ι))) χ(ι)(w), {ι} ∪ ιs) otherwise reachable(χ, π, ιs) = ιs -reachable(χ, ι, ιs, ∅) = ιs -reachable(χ, ι, ιs, {w} ∪ ws) = - reachable(χ, ι, ιs, w) ∪ reachable(χ, ι, ιs, ws) -reachable(χ, ι, ιs, w) = reachable(χ, χ(ι)(w), ιs) - +// mjp: I wonder if location is the right name here? Location has a lot of connotations of address to me, which is not what you mean. +// I wonder if `owner` is a better term: "the owner of primitive values is the Immutable region", "the owner of a nested object is the containing object", etc. // Location of a value. loc(χ, p) = Immutable +// mjp: Cown being Immutable, makes me think we should call this Sharable. loc(χ, π) = Immutable loc(χ, 𝕣) = loc(χ, 𝕣.target) if ι = 𝕣.target @@ -325,6 +324,7 @@ is_ancestor(χ, ρ₀, ρ₁) = This enforces a tree-shaped region graph, with a single reference from parent to child. ```rs +// mjp: Does this need to account for nested/embedded objects? safe_store(χ, Immutable, v) = false safe_store(χ, 𝔽, v) = true if loc(χ, v) = Immutable @@ -554,6 +554,9 @@ dec(χ, ι) = ```rs +// mjp: Should we consider behaviours as roots too? +// mjp: Should we consider cowns as roots, or should we track there reachability? + // GC on RegionRC is cycle detection. enable-gc(χ, ρ) = χ.regions(ρ).type ∈ {RegionGC, RegionRC} From a2464da493cf54a728cc5bc0fe465a4c1fae749a Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Wed, 9 Apr 2025 08:43:35 -0500 Subject: [PATCH 36/37] address some mjp comments --- docs/opsem.md | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index c50cfaee8..1a9aa7931 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -61,7 +61,8 @@ P ∈ Program = globals: Ident ↦ Value } -// mjp: What is a reference to a Cown usage? +// When a Cown is write-acquired, the behavior gets a reference to the value of +// the Cown, allowing it to be replaced entirely. 𝕣 ∈ Reference = {target: ObjectId | CownId, field: Ident} Error = BadType | BadTarget | BadField | BadStore | BadMethod | BadArgs | BadReturnLoc | BadReturnType @@ -99,8 +100,11 @@ R ∈ RegionType = RegionRC | RegionGC | RegionArena readonly: Bool } - // mjp: Factored this out as a lot of the helpers use this. - // mjp: Should this have CownId? + // This is the location of an object. An obect on the heap is either in a + // region or immutable. An object in a frame is on the stack. An object in + // another object is an mebedded field. Objects are not in Cowns, even + // though Cowns have a value - that value is either in a region or + // immutable. Location = RegionId | FrameId | ObjectId | Immutable // An object located in another object is an embedded field. @@ -154,11 +158,16 @@ B ∈ Behavior = result: CownId } +// Sequential semantics. Heap, Stack, Statement* ⇝ Heap, Stack, Statement* +// Concurrent semantics. +Heap ⇝ Heap + ``` ## Helpers + We use `.` notation for accessing fields of a record: ```rs {label0↦value0, ... labeln↦valuen}.labeli = valuei @@ -236,7 +245,8 @@ x ∈ φ ≝ x ∈ dom(φ.vars) ``` ## Dynamic Types -The following definition are all implicitly passed the current program, `P`. + +The following definitions are all implicitly passed the current program, `P`. Super-types are abstract, and are not recursively followed. Reference types depend on field types, and as such are not co-inductive. ```rs @@ -251,8 +261,6 @@ typeof(χ, v) = P.primitives(Error) if v ∈ Error χ.metadata(ι).type if ι = v Cown χ(π).type if π = v - // mjp: Is the recursion well-founded here? The argument is not clear to me, the semantics allows references in fields? - // This definition might need to be coinductive if that is the case. Ref P.types(typeof(χ, ι).field(𝕣.field).type) if (𝕣 = v) ∧ (𝕣.target = ι) Ref χ(π).type if (𝕣 = v) ∧ (𝕣.target = π) @@ -260,7 +268,6 @@ typetest(T₀, T₁) = typetest(T₂, T₁) ∧ typetest(T₃, T₁) if T₀ = Union T₂ T₃ typetest(T₀, T₂) ∨ typetest(T₀, T₃) if T₁ = Union T₂ T₃ T₀ = T₁ if (T₀ ∈ Ref T) ∨ (T₀ ∈ CownId T) ∨ (T₁ ∈ Ref T) ∨ (T₁ ∈ CownId T) - // mjp: Is supertypes transitive? Does it need to be here? T₁ ∈ P.types(τ).supertypes if T₀ = τ false otherwise @@ -321,7 +328,7 @@ is_ancestor(χ, ρ₀, ρ₁) = ## Safety -This enforces a tree-shaped region graph, with a single reference from parent to child. +This enforces a tree-shaped region graph, with a single reference from parent to child. Frame ID order is enforced, such that successor frames have higher IDs than their predecessors. ```rs // mjp: Does this need to account for nested/embedded objects? @@ -330,7 +337,7 @@ safe_store(χ, 𝔽, v) = true if loc(χ, v) = Immutable true if loc(χ, v) = π true if loc(χ, v) = ρ - true if (loc(χ, v) = 𝔽′) ∧ (𝔽 >= 𝔽′) // MJP: What is the order on FrameIds? Is this coming from the stack in χ? + true if (loc(χ, v) = 𝔽′) ∧ (𝔽 >= 𝔽′) false otherwise safe_store(χ, ρ, v) = false if χ(ρ).readonly @@ -378,6 +385,12 @@ wf_racefree(χ, σs) = (ι ∈ reachable(χ, σ₀)) ∧ (ι ∈ reachable(χ, σ₁)) ⇒ (σ₀ = σ₁) ∨ (loc(χ, ι) = Immutable) +// Frame IDs are ordered. +wf_frameorder(χ, σs) = + ∀σ ∈ σs . + ∀i ∈ 1 .. |σ| . + (σᵢ.id = 𝔽) ⇒ (∀j ∈ (i + 1) .. |σ| . σⱼ.id > 𝔽) + // Frame allocations are reachable only from that frame or antecedent frames. wf_stacklocal(χ, σs) = ∀ι ∈ χ . From 83d14bb06120a26f594967ac1f205fe320489db6 Mon Sep 17 00:00:00 2001 From: Sylvan Clebsch Date: Wed, 9 Apr 2025 10:27:03 -0500 Subject: [PATCH 37/37] try to use varphi everywhere --- docs/opsem.md | 65 +++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/docs/opsem.md b/docs/opsem.md index 1a9aa7931..30e6a4c10 100644 --- a/docs/opsem.md +++ b/docs/opsem.md @@ -79,7 +79,7 @@ v ∈ Value = ObjectId | Primitive | Reference | CownId ω ∈ Object = Ident ↦ Value Condition = Return | Raise | Throw -ϕ ∈ Frame = +φ ∈ Frame = { id: FrameId, vars: Ident ↦ Value, @@ -194,7 +194,6 @@ We compose updates with `[.., ..]`: r[upd1, upd2] = r[upd1][upd2] ``` -mjp: You use ϕ for frames, but here I think you are using φ for frames. Should we just use one? ```rs // Frames. x ∈ φ ≝ x ∈ dom(φ.vars) @@ -755,8 +754,8 @@ Local variables are consumed on use. To keep them, `dup` them first. ```rs -x ∉ ϕ -ϕ(y) = v +x ∉ φ +φ(y) = v χ₁ = region_stack_inc(χ₀, v) χ₂ = inc(χ₁, v) --- [dup] @@ -766,7 +765,7 @@ x ∉ ϕ χ₁ = region_stack_dec(χ₀, v) χ₂ = dec(χ₁, v) --- [drop] -χ₀, σ;φ, drop x;stmt* ⇝ χ₂, σ;ϕ\x, stmt* +χ₀, σ;φ, drop x;stmt* ⇝ χ₂, σ;φ\x, stmt* ``` @@ -776,39 +775,39 @@ The `load` statement is the only operation other than `dup` or `drop` that can c ```rs -x ∉ ϕ -ι = ϕ(y) +x ∉ φ +ι = φ(y) w ∈ dom(P.types(typeof(χ, ι)).fields) -𝕣 = {target: ϕ(y), field: w} +𝕣 = {target: φ(y), field: w} --- [ref] -χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦𝕣]\y, stmt* +χ, σ;φ, bind x (ref y w);stmt* ⇝ χ, σ;φ[x↦𝕣]\y, stmt* -x ∉ ϕ -ϕ(y) ∉ ObjectId +x ∉ φ +φ(y) ∉ ObjectId --- [ref bad-target] -χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦BadTarget]\y, throw;return x +χ, σ;φ, bind x (ref y w);stmt* ⇝ χ, σ;φ[x↦BadTarget]\y, throw;return x -x ∉ ϕ -ι = ϕ(y) +x ∉ φ +ι = φ(y) w ∉ dom(P.types(typeof(χ, ι)).fields) --- [ref bad-field] -χ, σ;ϕ, bind x (ref y w);stmt* ⇝ χ, σ;ϕ[x↦BadField]\y, throw;return x +χ, σ;φ, bind x (ref y w);stmt* ⇝ χ, σ;φ[x↦BadField]\y, throw;return x -x ∉ ϕ +x ∉ φ 𝕣 = φ(y) v = χ₀(ι)(w) if 𝕣 = {target: ι, field: w} χ₀(π).value if 𝕣 = {target: π, field: w} χ₁ = region_stack_inc(χ₀, v) χ₂ = inc(χ₁, v) --- [load] -χ₀, σ;ϕ, bind x (load y);stmt* ⇝ χ₂, σ;ϕ[x↦v], stmt* +χ₀, σ;φ, bind x (load y);stmt* ⇝ χ₂, σ;φ[x↦v], stmt* -x ∉ ϕ -ϕ(y) ∉ Reference +x ∉ φ +φ(y) ∉ Reference --- [load bad-target] -χ, σ;ϕ, bind x (load y);stmt* ⇝ χ, σ;ϕ[x↦BadTarget], throw;return x +χ, σ;φ, bind x (load y);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x -x ∉ ϕ +x ∉ φ 𝕣 = φ(y) v₀ = φ(z) safe_store(χ₀, loc(χ₀, 𝕣.target), v₀) @@ -823,21 +822,21 @@ v₁, χ₁ = ω(w), χ₀[ι↦ω[w↦v₀]] if χ₄ = region_add_parent(χ₃, 𝕣.target, v₀) χ₅ = region_stack_dec(χ₄, v₀) --- [store] -χ₀, σ;ϕ, bind x (store y z);stmt* ⇝ χ₅, σ;ϕ[x↦v₁]\z, stmt* +χ₀, σ;φ, bind x (store y z);stmt* ⇝ χ₅, σ;φ[x↦v₁]\z, stmt* -x ∉ ϕ -ϕ(y) ∉ Reference +x ∉ φ +φ(y) ∉ Reference --- [store bad-target] -χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadTarget], throw;return x +χ, σ;φ, bind x (store y z);stmt* ⇝ χ, σ;φ[x↦BadTarget], throw;return x -x ∉ ϕ +x ∉ φ 𝕣 = φ(y) v = φ(z) ¬safe_store(χ₀, loc(χ, 𝕣.target), v₁) --- [store bad-store] -χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadStore], throw;return x +χ, σ;φ, bind x (store y z);stmt* ⇝ χ, σ;φ[x↦BadStore], throw;return x -x ∉ ϕ +x ∉ φ 𝕣 = φ(y) v = φ(z) ((𝕣 = {target: ι, field: w}) ∧ @@ -845,7 +844,7 @@ v = φ(z) ((𝕣 = {target: π, field: w}) ∧ ¬typetest(χ₀, v₀, Π.type)) --- [store bad-type] -χ, σ;ϕ, bind x (store y z);stmt* ⇝ χ, σ;ϕ[x↦BadType], throw;return x +χ, σ;φ, bind x (store y z);stmt* ⇝ χ, σ;φ[x↦BadType], throw;return x ``` @@ -855,7 +854,7 @@ The local variable being type-tested is not consumed. ```rs -x ∉ ϕ +x ∉ φ v = typetest(χ, φ(y), T) --- [typetest] χ, σ;φ, bind x (typetest T y);stmt* ⇝ χ, σ;φ[x↦v], stmt* @@ -884,8 +883,8 @@ All arguments are consumed. To keep them, `dup` them first. As such, an identifi ```rs -newframe(χ, ϕ, F, x, y*, stmt*) = - { id: 𝔽, vars: {F.paramsᵢ.name ↦ ϕ(yᵢ) | i ∈ 1 .. |y*|}, +newframe(χ, φ, F, x, y*, stmt*) = + { id: 𝔽, vars: {F.paramsᵢ.name ↦ φ(yᵢ) | i ∈ 1 .. |y*|}, ret: x, type: F.result, cont: stmt*, condition: Return} where (𝔽 ∉ dom(χ.frames)) ∧ (𝔽 > φ.id) @@ -956,7 +955,7 @@ T = typeof(χ, v) if φ₁.condition = Return typetest(T, φ.type) φ₂ = φ₀[φ₁.ret↦v, condition = φ₁.condition] --- [return] -χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ\(φ₁.id), σ;φ₂, ϕ₁.cont +χ, σ;φ₀;φ₁, return x;stmt* ⇝ χ\(φ₁.id), σ;φ₂, φ₁.cont dom(φ.vars) = {x} v = φ(x)