plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


acaaac5 — Peter John 2026-11-02T16:16:50+05:30
Add Mem linear-memory primitive and GameCmd command-buffer game API
plum-std/GameCmd.plum ADDED
@@ -0,0 +1,316 @@
1
+ module std
2
+ import std/Int
3
+ import std/Float
4
+ import std/Str
5
+ import std/Vec2
6
+ import std/Color
7
+ import std/Mem
8
+
9
+ # `std/Game` with the draw calls recorded into a command buffer in linear
10
+ # memory instead of being imported from the host: `import std/GameCmd` replaces
11
+ # `import std/Game` unchanged. Input, assets, sound and screen queries stay host
12
+ # externs; `clear`, `drawRect`, `drawCircle`, `drawLine`, `drawText`,
13
+ # `drawTexture`, `drawTextureEx`, `setCamera` and `resetCamera` append commands
14
+ # that the host reads from the exported `memory` after `draw` (`cmdPtr`,
15
+ # `cmdLen`), and `cmdReset` empties the buffer.
16
+ #
17
+ # Layout: byte 0 holds the i32 count of command bytes written since the last
18
+ # `cmdReset` (zero in fresh memory); commands start at byte 16, and the buffer
19
+ # grows the memory as needed. Everything is little-endian, every field is 4
20
+ # bytes (f32 for Float, i32 for Int and Bool), and a command is a u32 opcode
21
+ # followed by its fields:
22
+ # 1 clear r g b a
23
+ # 2 rect x y w h r g b a
24
+ # 3 line x1 y1 x2 y2 thickness r g b a
25
+ # 4 circle x y radius r g b a
26
+ # 5 text x y size r g b a byteLen, then the UTF-8 bytes zero-padded to a multiple of 4
27
+ # 6 texture tex x y r g b a
28
+ # 7 textureEx tex sx sy sw sh x y w h rotation flipX r g b a
29
+ # 8 setCamera x y scale
30
+ # 9 resetCamera
31
+ # Texture and sound handles are -1 on failure. File, texture and sound paths are
32
+ # relative to the asset root. Coordinates are world units once `setCamera` is set.
33
+
34
+ extern fun screenWidth() -> Float
35
+ extern fun screenHeight() -> Float
36
+ extern fun frameTime() -> Float
37
+
38
+ # Empties the command buffer; the host calls it before `draw`.
39
+ export fun cmdReset() =
40
+ Mem.storeI32(0, 0)
41
+
42
+ # Byte offset of this frame's first command.
43
+ export fun cmdPtr() -> Int =
44
+ 16
45
+
46
+ # Length in bytes of this frame's commands.
47
+ export fun cmdLen() -> Int =
48
+ Mem.loadI32(0)
49
+
50
+ # Reserves a command of `n` field bytes, writes its opcode and returns the
51
+ # address of its first field.
52
+ fun cmdBegin(op: Int, n: Int) -> Int =
53
+ used := Mem.loadI32(0)
54
+ at := 16 + used
55
+ end := at + 4 + n
56
+ size := Mem.size()
57
+ if end > size
58
+ need := {end - size + 65535} / 65536
59
+ _ := Mem.grow(if need < 16 then 16 else need)
60
+ Mem.storeI32(0, used + 4 + n)
61
+ Mem.storeI32(at, op)
62
+ at + 4
63
+
64
+ fun cmdColor(at: Int, c: Color) =
65
+ Mem.storeF32(at, c.r)
66
+ Mem.storeF32(at + 4, c.g)
67
+ Mem.storeF32(at + 8, c.b)
68
+ Mem.storeF32(at + 12, c.a)
69
+
70
+ fun clear(c: Color) =
71
+ a := cmdBegin(1, 16)
72
+ cmdColor(a, c)
73
+
74
+ fun drawRect(x: Float, y: Float, w: Float, h: Float, c: Color) =
75
+ a := cmdBegin(2, 32)
76
+ Mem.storeF32(a, x)
77
+ Mem.storeF32(a + 4, y)
78
+ Mem.storeF32(a + 8, w)
79
+ Mem.storeF32(a + 12, h)
80
+ cmdColor(a + 16, c)
81
+
82
+ fun drawCircle(x: Float, y: Float, r: Float, c: Color) =
83
+ a := cmdBegin(4, 28)
84
+ Mem.storeF32(a, x)
85
+ Mem.storeF32(a + 4, y)
86
+ Mem.storeF32(a + 8, r)
87
+ cmdColor(a + 12, c)
88
+
89
+ fun drawLine(x1: Float, y1: Float, x2: Float, y2: Float, thickness: Float, c: Color) =
90
+ a := cmdBegin(3, 36)
91
+ Mem.storeF32(a, x1)
92
+ Mem.storeF32(a + 4, y1)
93
+ Mem.storeF32(a + 8, x2)
94
+ Mem.storeF32(a + 12, y2)
95
+ Mem.storeF32(a + 16, thickness)
96
+ cmdColor(a + 20, c)
97
+
98
+ fun drawText(s: Str, x: Float, y: Float, size: Float, c: Color) =
99
+ n := s.length()
100
+ padded := {n + 3} / 4 * 4
101
+ a := cmdBegin(5, 32 + padded)
102
+ Mem.storeF32(a, x)
103
+ Mem.storeF32(a + 4, y)
104
+ Mem.storeF32(a + 8, size)
105
+ cmdColor(a + 12, c)
106
+ Mem.storeI32(a + 28, n)
107
+ i := 0
108
+ while i < padded
109
+ Mem.storeU8(a + 32 + i, if i < n then s.byteAt(i) else 0)
110
+ i = i + 1
111
+
112
+ fun setCamera(x: Float, y: Float, scale: Float) =
113
+ a := cmdBegin(8, 12)
114
+ Mem.storeF32(a, x)
115
+ Mem.storeF32(a + 4, y)
116
+ Mem.storeF32(a + 8, scale)
117
+
118
+ fun resetCamera() =
119
+ _ := cmdBegin(9, 0)
120
+
121
+ # The texture at its natural size, top-left at (x, y).
122
+ fun drawTexture(t: Int, x: Float, y: Float, tint: Color) =
123
+ a := cmdBegin(6, 28)
124
+ Mem.storeI32(a, t)
125
+ Mem.storeF32(a + 4, x)
126
+ Mem.storeF32(a + 8, y)
127
+ cmdColor(a + 12, tint)
128
+
129
+ # The source rect (sx, sy, sw, sh) in texture pixels drawn into the dest rect
130
+ # (x, y, w, h), rotated `rotation` radians clockwise about the dest centre.
131
+ fun drawTextureEx(t: Int, sx: Float, sy: Float, sw: Float, sh: Float, x: Float, y: Float, w: Float, h: Float, rotation: Float, flipX: Bool, tint: Color) =
132
+ a := cmdBegin(7, 60)
133
+ Mem.storeI32(a, t)
134
+ Mem.storeF32(a + 4, sx)
135
+ Mem.storeF32(a + 8, sy)
136
+ Mem.storeF32(a + 12, sw)
137
+ Mem.storeF32(a + 16, sh)
138
+ Mem.storeF32(a + 20, x)
139
+ Mem.storeF32(a + 24, y)
140
+ Mem.storeF32(a + 28, w)
141
+ Mem.storeF32(a + 32, h)
142
+ Mem.storeF32(a + 36, rotation)
143
+ Mem.storeI32(a + 40, if flipX then 1 else 0)
144
+ cmdColor(a + 44, tint)
145
+
146
+ # Loads any image the host decodes (PNG at least); -1 on failure.
147
+ extern fun textureLoad(path: Str) -> Int
148
+ # Size in pixels; 0 for a bad handle.
149
+ extern fun textureWidth(t: Int) -> Int
150
+ extern fun textureHeight(t: Int) -> Int
151
+ # Linear (true) or nearest (false, the default) filtering.
152
+ extern fun textureSetSmooth(t: Int, smooth: Bool)
153
+ extern fun fileExists(path: Str) -> Bool
154
+ # The whole file; "" if it is missing.
155
+ extern fun fileLoad(path: Str) -> Str
156
+
157
+ # Loads a WAV or OGG file relative to the asset root; -1 on failure. The host
158
+ # decodes every sound file in the asset root before `init` runs.
159
+ extern fun soundLoad(path: Str) -> Int
160
+ # A one-shot play; plays of the same sound may overlap. Volume is 0..1.
161
+ extern fun soundPlay(s: Int, volume: Float)
162
+ # Starts the sound looping. Call `soundStop` first to restart it cleanly.
163
+ extern fun soundLoop(s: Int, volume: Float)
164
+ # Stops every play and loop of the sound.
165
+ extern fun soundStop(s: Int)
166
+ # Changes the volume of a sound that is playing or looping.
167
+ extern fun soundSetVolume(s: Int, volume: Float)
168
+
169
+ extern fun isKeyDown(k: Int) -> Bool
170
+ extern fun isKeyPressed(k: Int) -> Bool
171
+ extern fun mousePos() -> Vec2
172
+ extern fun isMouseDown(b: Int) -> Bool
173
+ extern fun isMousePressed(b: Int) -> Bool
174
+ extern fun mouseWheel() -> Float
175
+
176
+ # Key codes (GLFW numbering) for `isKeyDown` / `isKeyPressed`.
177
+ # Letter keys are `KeyA`..`KeyZ` (a constant name needs two characters).
178
+ primitive Key =
179
+ Space = 32
180
+ KeyA = 65
181
+ KeyB = 66
182
+ KeyC = 67
183
+ KeyD = 68
184
+ KeyE = 69
185
+ KeyF = 70
186
+ KeyG = 71
187
+ KeyH = 72
188
+ KeyI = 73
189
+ KeyJ = 74
190
+ KeyK = 75
191
+ KeyL = 76
192
+ KeyM = 77
193
+ KeyN = 78
194
+ KeyO = 79
195
+ KeyP = 80
196
+ KeyQ = 81
197
+ KeyR = 82
198
+ KeyS = 83
199
+ KeyT = 84
200
+ KeyU = 85
201
+ KeyV = 86
202
+ KeyW = 87
203
+ KeyX = 88
204
+ KeyY = 89
205
+ KeyZ = 90
206
+ Num0 = 48
207
+ Num1 = 49
208
+ Num2 = 50
209
+ Num3 = 51
210
+ Num4 = 52
211
+ Num5 = 53
212
+ Num6 = 54
213
+ Num7 = 55
214
+ Num8 = 56
215
+ Num9 = 57
216
+ Escape = 256
217
+ Enter = 257
218
+ Tab = 258
219
+ Backspace = 259
220
+ Right = 262
221
+ Left = 263
222
+ Down = 264
223
+ Up = 265
224
+ LeftShift = 340
225
+ LeftControl = 341
226
+
227
+ # Mouse button numbers for `isMouseDown` / `isMousePressed`.
228
+ primitive Mouse =
229
+ Left = 0
230
+ Right = 1
231
+ Middle = 2
232
+
233
+ test "key and mouse constants use GLFW numbering"
234
+ assert Key.KeyW == 87
235
+ assert Key.Left == 263
236
+ assert Mouse.Right == 1
237
+
238
+ test "draw calls encode one command each"
239
+ cmdReset()
240
+ assert cmdPtr() == 16
241
+ assert cmdLen() == 0
242
+ clear(Color(r: 0.25, g: 0.5, b: 0.75, a: 1.0))
243
+ drawRect(1.0, 2.0, 3.0, 4.0, Color.Red)
244
+ assert cmdLen() == 20 + 36
245
+ assert Mem.loadI32(16) == 1
246
+ assert Mem.loadF32(20) == 0.25
247
+ assert Mem.loadF32(32) == 1.0
248
+ assert Mem.loadI32(36) == 2
249
+ assert Mem.loadF32(40) == 1.0
250
+ assert Mem.loadF32(52) == 4.0
251
+ assert Mem.loadF32(56) == 1.0
252
+ assert Mem.loadF32(60) == 0.0
253
+ cmdReset()
254
+ drawLine(1.0, 2.0, 3.0, 4.0, 5.0, Color.Green)
255
+ drawCircle(6.0, 7.0, 8.0, Color.Blue)
256
+ assert Mem.loadI32(16) == 3
257
+ assert Mem.loadF32(36) == 5.0
258
+ assert Mem.loadF32(40) == 0.0
259
+ assert Mem.loadF32(44) == 1.0
260
+ assert Mem.loadI32(56) == 4
261
+ assert Mem.loadF32(60) == 6.0
262
+ assert Mem.loadF32(68) == 8.0
263
+ assert Mem.loadF32(80) == 1.0
264
+ assert cmdLen() == 40 + 32
265
+
266
+ test "texture, camera and reset commands"
267
+ cmdReset()
268
+ drawTexture(9, 1.5, 2.5, Color.White)
269
+ assert Mem.loadI32(16) == 6
270
+ assert Mem.loadI32(20) == 9
271
+ assert Mem.loadF32(28) == 2.5
272
+ assert cmdLen() == 32
273
+ cmdReset()
274
+ drawTextureEx(3, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 0.5, true, Color.Black)
275
+ assert Mem.loadI32(16) == 7
276
+ assert Mem.loadI32(20) == 3
277
+ assert Mem.loadF32(56) == 0.5
278
+ assert Mem.loadI32(60) == 1
279
+ assert Mem.loadF32(76) == 1.0
280
+ assert cmdLen() == 64
281
+ cmdReset()
282
+ setCamera(1.0, 2.0, 3.0)
283
+ resetCamera()
284
+ assert Mem.loadI32(16) == 8
285
+ assert Mem.loadF32(28) == 3.0
286
+ assert Mem.loadI32(32) == 9
287
+ assert cmdLen() == 20
288
+
289
+ test "text is length-prefixed and zero-padded to 4 bytes"
290
+ cmdReset()
291
+ drawText("hello", 1.0, 2.0, 12.0, Color.White)
292
+ assert Mem.loadI32(16) == 5
293
+ assert Mem.loadF32(28) == 12.0
294
+ assert Mem.loadI32(48) == 5
295
+ assert Mem.loadU8(52) == 104
296
+ assert Mem.loadU8(56) == 111
297
+ assert Mem.loadU8(57) == 0
298
+ assert Mem.loadU8(59) == 0
299
+ assert cmdLen() == 4 + 32 + 8
300
+ drawText("abcd", 0.0, 0.0, 1.0, Color.White)
301
+ assert cmdLen() == 44 + 4 + 32 + 4
302
+ drawText("", 0.0, 0.0, 1.0, Color.White)
303
+ assert cmdLen() == 84 + 36
304
+
305
+ test "the buffer grows memory past the initial pages"
306
+ cmdReset()
307
+ i := 0
308
+ while i < 40000
309
+ drawRect(1.0, 2.0, 3.0, 4.0, Color.Red)
310
+ i = i + 1
311
+ assert cmdLen() == 40000 * 36
312
+ assert Mem.size() > 16 * 65536
313
+ assert Mem.loadI32(16 + 39999 * 36) == 2
314
+ assert Mem.loadF32(16 + 39999 * 36 + 8) == 2.0
315
+ cmdReset()
316
+ assert cmdLen() == 0
plum-std/Mem.plum ADDED
@@ -0,0 +1,48 @@
1
+ module std
2
+ import std/Int
3
+ import std/Float
4
+
5
+ # The program's linear memory: one growable wasm memory (16 pages of 64 KiB to
6
+ # start), exported to the host as "memory". Present only in programs that use
7
+ # `Mem`. Addresses are byte offsets; every access is little-endian and traps
8
+ # when out of bounds. Integers are stored as 32 bits (`storeI32` truncates,
9
+ # `loadI32` sign-extends) and floats as f32.
10
+ primitive Mem =
11
+ # Current memory size in bytes.
12
+ fun size() -> Int =
13
+ todo
14
+
15
+ # Adds `pages` 64 KiB pages; returns the old page count, or -1 on failure.
16
+ fun grow(pages: Int) -> Int =
17
+ todo
18
+
19
+ fun storeF32(addr: Int, v: Float) =
20
+ todo
21
+
22
+ fun storeI32(addr: Int, v: Int) =
23
+ todo
24
+
25
+ # Stores the low 8 bits of `v`.
26
+ fun storeU8(addr: Int, v: Int) =
27
+ todo
28
+
29
+ fun loadF32(addr: Int) -> Float =
30
+ todo
31
+
32
+ fun loadI32(addr: Int) -> Int =
33
+ todo
34
+
35
+ # The byte at `addr`, 0..255.
36
+ fun loadU8(addr: Int) -> Int =
37
+ todo
38
+
39
+ test "Mem round trips"
40
+ Mem.storeI32(16, -5)
41
+ assert Mem.loadI32(16) == -5
42
+ Mem.storeF32(32, 1.5f)
43
+ assert Mem.loadF32(32) == 1.5f
44
+ Mem.storeU8(40, 300)
45
+ assert Mem.loadU8(40) == 44
46
+ assert Mem.size() == 1048576
47
+ assert Mem.grow(1) == 16
48
+ assert Mem.size() == 1114112
plum-wasm-codegen/src/lib.rs CHANGED
@@ -49,6 +49,8 @@ pub struct WasmModule {
49
49
  global_count: u32,
50
50
  start_function: Option<u32>,
51
51
  func_names: Vec<(u32, String)>,
52
+ /// Initial page count of the module's one linear memory, when it has one.
53
+ memory_pages: Option<u64>,
52
54
  }
53
55
 
54
56
  impl WasmModule {
@@ -67,9 +69,16 @@ impl WasmModule {
67
69
  global_count: 0,
68
70
  start_function: None,
69
71
  func_names: Vec::new(),
72
+ memory_pages: None,
70
73
  }
71
74
  }
72
75
 
76
+ /// Declares the module's single growable linear memory (`min_pages` initial
77
+ /// pages) and exports it as "memory".
78
+ pub fn enableMemory(&mut self, min_pages: u64) {
79
+ self.memory_pages = Some(min_pages);
80
+ }
81
+
73
82
  pub fn addType(&mut self, params: &[ValType], results: &[ValType]) -> u32 {
74
83
  let idx = self.next_type_idx;
75
84
  self.types.push(TypeEntry::Func(FuncType::new(
@@ -218,6 +227,19 @@ impl WasmModule {
218
227
  module.section(&tables);
219
228
  }
220
229
 
230
+ // Memory section
231
+ if let Some(min) = self.memory_pages {
232
+ let mut mems = wasm_encoder::MemorySection::new();
233
+ mems.memory(wasm_encoder::MemoryType {
234
+ minimum: min,
235
+ maximum: None,
236
+ memory64: false,
237
+ shared: false,
238
+ page_size_log2: None,
239
+ });
240
+ module.section(&mems);
241
+ }
242
+
221
243
  // Global section
222
244
  if !self.globals.is_empty() {
223
245
  let mut globals = GlobalSection::new();
@@ -236,8 +258,11 @@ impl WasmModule {
236
258
  }
237
259
 
238
260
  // Export section
239
- if !self.exports.is_empty() {
261
+ if !self.exports.is_empty() || self.memory_pages.is_some() {
240
262
  let mut exports = ExportSection::new();
263
+ if self.memory_pages.is_some() {
264
+ exports.export("memory", ExportKind::Memory, 0);
265
+ }
241
266
  for (name, kind, idx) in &self.exports {
242
267
  exports.export(name, *kind, *idx);
243
268
  }
@@ -2758,6 +2783,9 @@ fn compileSourceInner(
2758
2783
  };
2759
2784
  compiled_bodies.push((idx, body));
2760
2785
  }
2786
+ if units.iter().any(|u| u.f.type_param.as_deref() == Some("Mem")) {
2787
+ module.enableMemory(16);
2788
+ }
2761
2789
  for (idx, body) in &compiled_bodies {
2762
2790
  let slot = (*idx - module.func_import_count) as usize;
2763
2791
  module.functions[slot].1 = body.clone();
@@ -3014,6 +3042,9 @@ fn getClosureCallType(
3014
3042
  /// Returns `None` for any other function, meaning "compile it normally."
3015
3043
  fn compileIntrinsicFnBody(f: &ast::Fn, subst: &Subst) -> Option<Vec<u8>> {
3016
3044
  if let Some(recv) = f.type_param.as_deref() {
3045
+ if recv == "Mem" {
3046
+ return compileMemIntrinsicFnBody(f);
3047
+ }
3017
3048
  if isArraySpecialization(recv) {
3018
3049
  return compileArrayIntrinsicFnBody(f, subst);
3019
3050
  }
@@ -3021,6 +3052,69 @@ fn compileIntrinsicFnBody(f: &ast::Fn, subst: &Subst) -> Option<Vec<u8>> {
3021
3052
  None
3022
3053
  }
3023
3054
 
3055
+ /// `Mem`'s linear-memory primitives. Local 0 is the unused static `self` slot,
3056
+ /// so the declared params start at local 1. Addresses and values are `Int`
3057
+ /// (i64) wrapped to i32; `Float` is demoted to / promoted from f32.
3058
+ fn compileMemIntrinsicFnBody(f: &ast::Fn) -> Option<Vec<u8>> {
3059
+ let mut body = vec![0u8];
3060
+ let arg = |b: &mut Vec<u8>, local: u32| {
3061
+ Instruction::LocalGet(local).encode(b);
3062
+ Instruction::I32WrapI64.encode(b);
3063
+ };
3064
+ let mem = |align| wasm_encoder::MemArg {
3065
+ offset: 0,
3066
+ align,
3067
+ memory_index: 0,
3068
+ };
3069
+ match f.name.as_str() {
3070
+ "size" => {
3071
+ Instruction::MemorySize(0).encode(&mut body);
3072
+ Instruction::I64ExtendI32U.encode(&mut body);
3073
+ Instruction::I64Const(65536).encode(&mut body);
3074
+ Instruction::I64Mul.encode(&mut body);
3075
+ }
3076
+ "grow" => {
3077
+ arg(&mut body, 1);
3078
+ Instruction::MemoryGrow(0).encode(&mut body);
3079
+ Instruction::I64ExtendI32S.encode(&mut body);
3080
+ }
3081
+ "storeF32" => {
3082
+ arg(&mut body, 1);
3083
+ Instruction::LocalGet(2).encode(&mut body);
3084
+ Instruction::F32DemoteF64.encode(&mut body);
3085
+ Instruction::F32Store(mem(0)).encode(&mut body);
3086
+ }
3087
+ "storeI32" => {
3088
+ arg(&mut body, 1);
3089
+ arg(&mut body, 2);
3090
+ Instruction::I32Store(mem(0)).encode(&mut body);
3091
+ }
3092
+ "storeU8" => {
3093
+ arg(&mut body, 1);
3094
+ arg(&mut body, 2);
3095
+ Instruction::I32Store8(mem(0)).encode(&mut body);
3096
+ }
3097
+ "loadF32" => {
3098
+ arg(&mut body, 1);
3099
+ Instruction::F32Load(mem(0)).encode(&mut body);
3100
+ Instruction::F64PromoteF32.encode(&mut body);
3101
+ }
3102
+ "loadI32" => {
3103
+ arg(&mut body, 1);
3104
+ Instruction::I32Load(mem(0)).encode(&mut body);
3105
+ Instruction::I64ExtendI32S.encode(&mut body);
3106
+ }
3107
+ "loadU8" => {
3108
+ arg(&mut body, 1);
3109
+ Instruction::I32Load8U(mem(0)).encode(&mut body);
3110
+ Instruction::I64ExtendI32U.encode(&mut body);
3111
+ }
3112
+ _ => return None,
3113
+ }
3114
+ Instruction::End.encode(&mut body);
3115
+ Some(body)
3116
+ }
3117
+
3024
3118
  /// Wraps a just-pushed raw `array<i8>` (top of the value stack, whose length
3025
3119
  /// IS the string's length) into `Str`'s `(data, start: 0, len)` representation via
3026
3120
  /// `mk_str_fn`. Every codegen site that builds a `Str` from raw bytes goes
plum-wasm-codegen/tests/codegen_tests.rs CHANGED
@@ -3115,3 +3115,89 @@ fun main() -> Int =
3115
3115
  ";
3116
3116
  assert_eq!(runInt(src), 3);
3117
3117
  }
3118
+
3119
+ // ---- Mem: linear memory primitive ----
3120
+
3121
+ #[test]
3122
+ fn memRoundTripsAndGrows() {
3123
+ let src = "\
3124
+ import std/Int
3125
+ import std/Float
3126
+ import std/Mem
3127
+
3128
+ fun main() -> Int =
3129
+ Mem.storeI32(8, -7)
3130
+ Mem.storeF32(12, 2.5f)
3131
+ Mem.storeU8(16, 513)
3132
+ ok := Mem.loadI32(8) == -7 && Mem.loadF32(12) == 2.5f && Mem.loadU8(16) == 1
3133
+ before := Mem.size()
3134
+ old := Mem.grow(2)
3135
+ if ok && before == 16 * 65536 && old == 16 && Mem.size() == 18 * 65536 && Mem.grow(100000) == -1
3136
+ return 1
3137
+ 0
3138
+ ";
3139
+ assert_eq!(runMerged::<i64>("mem_roundtrip", src), 1);
3140
+ }
3141
+
3142
+ #[test]
3143
+ fn memOutOfBoundsTraps() {
3144
+ let src = "\
3145
+ import std/Int
3146
+ import std/Mem
3147
+
3148
+ fun main() -> Int =
3149
+ Mem.loadI32(16 * 65536)
3150
+ ";
3151
+ let dir = std::env::temp_dir().join(format!("plum_mem_oob_{}", std::process::id()));
3152
+ std::fs::create_dir_all(&dir).unwrap();
3153
+ let path = dir.join("main.plum");
3154
+ std::fs::write(&path, src).unwrap();
3155
+ let source = plum_core::loadAndMerge(&path, &repoRoot()).expect("load");
3156
+ let bytes = compileSource(&source).expect("compile");
3157
+ let engine = gcEngine();
3158
+ let module = wasmtime::Module::new(&engine, &bytes).unwrap();
3159
+ let mut store = wasmtime::Store::new(&engine, ());
3160
+ let mut linker = wasmtime::Linker::new(&engine);
3161
+ linker
3162
+ .define_unknown_imports_as_default_values(&mut store, &module)
3163
+ .unwrap();
3164
+ let inst = linker.instantiate(&mut store, &module).unwrap();
3165
+ let main = inst.get_typed_func::<(), i64>(&mut store, "main").unwrap();
3166
+ assert!(main.call(&mut store, ()).is_err());
3167
+ let _ = std::fs::remove_dir_all(&dir);
3168
+ }
3169
+
3170
+ #[test]
3171
+ fn memoryExportedOnlyWhenMemUsed() {
3172
+ let with = "\
3173
+ import std/Int
3174
+ import std/Mem
3175
+
3176
+ fun main() -> Int =
3177
+ Mem.size()
3178
+ ";
3179
+ let without = "import std/Int\n\nfun main() -> Int =\n 1\n";
3180
+ let exportsMemory = |name: &str, src: &str| {
3181
+ let (bytes, _) = compiledKeys(name, src);
3182
+ let mut mem = false;
3183
+ let mut exports_memory = false;
3184
+ for payload in wasmparser::Parser::new(0).parse_all(&bytes) {
3185
+ match payload.unwrap() {
3186
+ wasmparser::Payload::MemorySection(_) => mem = true,
3187
+ wasmparser::Payload::ExportSection(r) => {
3188
+ for e in r {
3189
+ let e = e.unwrap();
3190
+ if e.name == "memory" && e.kind == wasmparser::ExternalKind::Memory {
3191
+ exports_memory = true;
3192
+ }
3193
+ }
3194
+ }
3195
+ _ => {}
3196
+ }
3197
+ }
3198
+ assert_eq!(mem, exports_memory);
3199
+ mem
3200
+ };
3201
+ assert!(exportsMemory("mem_with", with));
3202
+ assert!(!exportsMemory("mem_without", without));
3203
+ }