Skip to content

Commit 0e7fb6b

Browse files
committed
Copy and embed for serialization format
1 parent 7414a8c commit 0e7fb6b

6 files changed

Lines changed: 39 additions & 141 deletions

File tree

docs/serialization.md

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ Every field on the node is then appended to the serialized string. The fields ca
126126
* `node` - A field that is a node. This is structured just as like parent node.
127127
* `node?` - A field that is a node that is optionally present. If the node is not present, then a single `0` byte will be written in its place. If it is present, then it will be structured just as like parent node.
128128
* `node[]` - A field that is an array of nodes. This is structured as a variable-length integer length, followed by the child nodes themselves.
129-
* `string` - A field that is a string. For example, this is used as the name of the method in a call node, since it cannot directly reference the source string (as in `@-` or `foo=`). This is structured as a variable-length integer byte length, followed by the string itself (_without_ a trailing null byte).
129+
* `string` - A field that is a string. For example, this is used as the name of the method in a call node, since it cannot directly reference the source string (as in `@-` or `foo=`). This is structured as a variable-length integer byte length, followed by the string bytes (_without_ a trailing null byte).
130130
* `constant` - A variable-length integer that represents an index in the constant pool.
131131
* `constant?` - An optional variable-length integer that represents an index in the constant pool. If it's not present, then a single `0` byte will be written in its place.
132132
* `integer` - A field that represents an arbitrary-sized integer. The structure is listed above.
@@ -135,23 +135,14 @@ Every field on the node is then appended to the serialized string. The fields ca
135135
* `uint8` - A field that is an 8-bit unsigned integer. This is structured as a single byte.
136136
* `uint32` - A field that is a 32-bit unsigned integer. This is structured as a variable-length integer.
137137

138-
After the syntax tree, the content pool is serialized. This is a list of constants that were referenced from within the tree. The content pool begins at the offset specified in the header. Constants can be either "owned" (in which case their contents are embedded in the serialization) or "shared" (in which case their contents represent a slice of the source string). The most significant bit of the constant indicates whether it is owned or shared.
139-
140-
In the case that it is owned, the constant is structured as follows:
138+
After the syntax tree, the content pool is serialized. This is a list of constants that were referenced from within the tree. The content pool begins at the offset specified in the header. Every constant is embedded in the serialization. Each constant is structured as follows:
141139

142140
| # bytes | field |
143141
| --- | --- |
144142
| `4` | the byte offset in the serialization for the contents of the constant |
145143
| `4` | the byte length in the serialization |
146144

147-
Note that you will need to mask off the most significant bit for the byte offset in the serialization. In the case that it is shared, the constant is structured as follows:
148-
149-
| # bytes | field |
150-
| --- | --- |
151-
| `4` | the byte offset in the source string for the contents of the constant |
152-
| `4` | the byte length in the source string |
153-
154-
After the constant pool, the contents of the owned constants are serialized. This is just a sequence of bytes that represent the contents of the constants. At the end of the serialization, the buffer is null terminated.
145+
After the constant pool, the contents of the constants are serialized. This is just a sequence of bytes that represent the contents of the constants. At the end of the serialization, the buffer is null terminated.
155146

156147
## APIs
157148

javascript/src/parsePrism.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function parsePrism(prism, source, options = {}) {
4343

4444
prism.pm_serialize_parse(bufferPointer, sourcePointer, sourceArray.length, optionsPointer);
4545
const serializedView = new Uint8Array(prism.memory.buffer, prism.pm_buffer_value(bufferPointer), prism.pm_buffer_length(bufferPointer));
46-
const result = deserialize(sourceArray, serializedView);
46+
const result = deserialize(serializedView);
4747

4848
prism.pm_buffer_free(bufferPointer);
4949
prism.free(sourcePointer);

templates/java/org/ruby_lang/prism/Loader.java.erb

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,11 @@ public class Loader {
4040
private static final class ConstantPool {
4141

4242
private final Loader loader;
43-
private final byte[] source;
4443
private final int bufferOffset;
4544
private final <%= string_type %>[] cache;
4645

47-
ConstantPool(Loader loader, byte[] source, int bufferOffset, int length) {
46+
ConstantPool(Loader loader, int bufferOffset, int length) {
4847
this.loader = loader;
49-
this.source = source;
5048
this.bufferOffset = bufferOffset;
5149
cache = new <%= string_type %>[length];
5250
}
@@ -61,15 +59,10 @@ public class Loader {
6159
int length = buffer.getInt(offset + 4);
6260

6361
byte[] bytes = new byte[length];
64-
65-
if (Integer.compareUnsigned(start, 0x7FFFFFFF) <= 0) {
66-
System.arraycopy(source, start, bytes, 0, length);
67-
} else {
68-
int position = buffer.position();
69-
buffer.position(start & 0x7FFFFFFF);
70-
buffer.get(bytes, 0, length);
71-
buffer.position(position);
72-
}
62+
int position = buffer.position();
63+
buffer.position(start);
64+
buffer.get(bytes, 0, length);
65+
buffer.position(position);
7366

7467
constant = loader.bytesToName(bytes);
7568
cache[index] = constant;
@@ -125,7 +118,7 @@ public class Loader {
125118

126119
int constantPoolBufferOffset = buffer.getInt();
127120
int constantPoolLength = loadVarUInt();
128-
this.constantPool = new ConstantPool(this, source.bytes, constantPoolBufferOffset, constantPoolLength);
121+
this.constantPool = new ConstantPool(this, constantPoolBufferOffset, constantPoolLength);
129122

130123
Nodes.Node node;
131124
if (errors.length == 0) {
@@ -146,28 +139,13 @@ public class Loader {
146139
return new ParseResult(node, magicComments, dataLocation, errors, warnings, source);
147140
}
148141

149-
private byte[] loadEmbeddedString() {
142+
private byte[] loadString() {
150143
int length = loadVarUInt();
151144
byte[] bytes = new byte[length];
152145
buffer.get(bytes);
153146
return bytes;
154147
}
155148

156-
private byte[] loadString() {
157-
switch (buffer.get()) {
158-
case 1:
159-
int start = loadVarUInt();
160-
int length = loadVarUInt();
161-
byte[] bytes = new byte[length];
162-
System.arraycopy(source.bytes, start, bytes, 0, length);
163-
return bytes;
164-
case 2:
165-
return loadEmbeddedString();
166-
default:
167-
throw new Error("Expected 0 or 1 but was " + buffer.get());
168-
}
169-
}
170-
171149
private int[] loadLineOffsets() {
172150
int count = loadVarUInt();
173151
int[] lineOffsets = new int[count];
@@ -199,7 +177,7 @@ public class Loader {
199177
// error messages only contain ASCII characters
200178
for (int i = 0; i < count; i++) {
201179
Nodes.ErrorType type = Nodes.ERROR_TYPES[loadVarUInt()];
202-
byte[] bytes = loadEmbeddedString();
180+
byte[] bytes = loadString();
203181
String message = new String(bytes, StandardCharsets.US_ASCII);
204182
Nodes.Location location = loadLocation();
205183
ParseResult.ErrorLevel level = ParseResult.ERROR_LEVELS[buffer.get()];
@@ -218,7 +196,7 @@ public class Loader {
218196
// warning messages only contain ASCII characters
219197
for (int i = 0; i < count; i++) {
220198
Nodes.WarningType type = Nodes.WARNING_TYPES[loadVarUInt() - <%= errors.length %>];
221-
byte[] bytes = loadEmbeddedString();
199+
byte[] bytes = loadString();
222200
String message = new String(bytes, StandardCharsets.US_ASCII);
223201
Nodes.Location location = loadLocation();
224202
ParseResult.WarningLevel level = ParseResult.WARNING_LEVELS[buffer.get()];

templates/javascript/src/deserialize.js.erb

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ class SerializationBuffer {
2929
["ascii-8bit", "ascii"]
3030
]);
3131

32-
constructor(source, array) {
33-
this.source = source;
32+
constructor(array) {
3433
this.array = array;
3534
this.index = 0;
3635
this.fileEncoding = "utf-8";
@@ -96,32 +95,15 @@ class SerializationBuffer {
9695

9796
readStringField(flags) {
9897
if (flags === undefined) flags = 0;
99-
const type = this.readByte();
100-
101-
switch (type) {
102-
case 1: {
103-
const startOffset = this.readVarInt();
104-
const length = this.readVarInt();
105-
return this.decodeString(this.source.slice(startOffset, startOffset + length), flags);
106-
}
107-
case 2:
108-
return this.decodeString(this.readBytes(this.readVarInt()), flags);
109-
default:
110-
throw new Error(`Unknown serialized string type: ${type}`);
111-
}
98+
return this.decodeString(this.readBytes(this.readVarInt()), flags);
11299
}
113100

114101
scanConstant(constantPoolOffset, constantIndex) {
115102
const offset = constantPoolOffset + constantIndex * 8;
116-
let startOffset = this.scanUint32(offset);
103+
const startOffset = this.scanUint32(offset);
117104
const length = this.scanUint32(offset + 4);
118105

119-
if (startOffset & (1 << 31)) {
120-
startOffset &= (1 << 31) - 1;
121-
return new TextDecoder().decode(this.array.slice(startOffset, startOffset + length));
122-
} else {
123-
return new TextDecoder().decode(this.source.slice(startOffset, startOffset + length));
124-
}
106+
return this.getDecoder(this.fileEncoding).decode(this.array.slice(startOffset, startOffset + length));
125107
}
126108

127109
readDouble() {
@@ -293,13 +275,12 @@ const warningTypes = [
293275
* Accept two Uint8Arrays, one for the source and one for the serialized format.
294276
* Return the AST corresponding to the serialized form.
295277
*
296-
* @param {Uint8Array} source
297278
* @param {Uint8Array} array
298279
* @returns {ParseResult}
299280
* @throws {Error}
300281
*/
301-
export function deserialize(source, array) {
302-
const buffer = new SerializationBuffer(source, array);
282+
export function deserialize(array) {
283+
const buffer = new SerializationBuffer(array);
303284

304285
if (buffer.readString(5) !== "PRISM") {
305286
throw new Error("Invalid serialization");

templates/lib/prism/serialize.rb.erb

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ module Prism
4646
cpool_base = loader.load_uint32
4747
cpool_size = loader.load_varuint
4848

49-
constant_pool = ConstantPool.new(input, serialized, cpool_base, cpool_size)
49+
constant_pool = ConstantPool.new(serialized, cpool_base, cpool_size)
5050

5151
node = loader.load_node(constant_pool, encoding, freeze) #: ProgramNode
5252
loader.load_constant_pool(constant_pool)
@@ -171,7 +171,7 @@ module Prism
171171
cpool_base = loader.load_uint32
172172
cpool_size = loader.load_varuint
173173

174-
constant_pool = ConstantPool.new(input, serialized, cpool_base, cpool_size)
174+
constant_pool = ConstantPool.new(serialized, cpool_base, cpool_size)
175175

176176
node = loader.load_node(constant_pool, encoding, freeze) #: ProgramNode
177177
loader.load_constant_pool(constant_pool)
@@ -202,14 +202,12 @@ module Prism
202202
class ConstantPool # :nodoc:
203203
attr_reader :size #: Integer
204204

205-
# @rbs @input: String
206205
# @rbs @serialized: String
207206
# @rbs @base: Integer
208207
# @rbs @pool: Array[Symbol?]
209208

210-
#: (String input, String serialized, Integer base, Integer size) -> void
211-
def initialize(input, serialized, base, size)
212-
@input = input
209+
#: (String serialized, Integer base, Integer size) -> void
210+
def initialize(serialized, base, size)
213211
@serialized = serialized
214212
@base = base
215213
@size = size
@@ -224,11 +222,7 @@ module Prism
224222
start = @serialized.unpack1("L", offset: offset) #: Integer
225223
length = @serialized.unpack1("L", offset: offset + 4) #: Integer
226224

227-
if start.nobits?(1 << 31)
228-
(@input.byteslice(start, length) or raise).force_encoding(encoding).to_sym
229-
else
230-
(@serialized.byteslice(start & ((1 << 31) - 1), length) or raise).force_encoding(encoding).to_sym
231-
end
225+
(@serialized.byteslice(start, length) or raise).force_encoding(encoding).to_sym
232226
end
233227
end
234228
end
@@ -289,8 +283,8 @@ module Prism
289283
trailer = 0
290284

291285
constant_pool.size.times do |index|
292-
start, length = (io.read(8) or raise).unpack("L2") #: [Integer, Integer]
293-
trailer += length if start.anybits?(1 << 31)
286+
length = (io.read(8) or raise).unpack1("L", offset: 4) #: Integer
287+
trailer += length
294288
end
295289

296290
io.read(trailer)
@@ -388,7 +382,7 @@ module Prism
388382
error =
389383
ParseError.new(
390384
DIAGNOSTIC_TYPES.fetch(load_varuint),
391-
load_embedded_string(encoding),
385+
load_string(encoding),
392386
load_location_object(freeze),
393387
load_error_level
394388
)
@@ -422,7 +416,7 @@ module Prism
422416
warning =
423417
ParseWarning.new(
424418
DIAGNOSTIC_TYPES.fetch(load_varuint),
425-
load_embedded_string(encoding),
419+
load_string(encoding),
426420
load_location_object(freeze),
427421
load_warning_level
428422
)
@@ -507,21 +501,9 @@ module Prism
507501
end
508502
end
509503

510-
#: (Encoding encoding) -> String
511-
def load_embedded_string(encoding)
512-
(io.read(load_varuint) or raise).force_encoding(encoding).freeze
513-
end
514-
515504
#: (Encoding encoding) -> String
516505
def load_string(encoding)
517-
case (type = io.getbyte)
518-
when 1
519-
(input.byteslice(load_varuint, load_varuint) or raise).force_encoding(encoding).freeze
520-
when 2
521-
load_embedded_string(encoding)
522-
else
523-
raise "Unknown serialized string type: #{type}"
524-
end
506+
(io.read(load_varuint) or raise).force_encoding(encoding).freeze
525507
end
526508

527509
#: (bool freeze) -> Location

templates/src/serialize.c.erb

Lines changed: 10 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,10 @@ pm_serialize_location(const pm_location_t *location, pm_buffer_t *buffer) {
2626
}
2727

2828
static void
29-
pm_serialize_string(const pm_parser_t *parser, const pm_string_t *string, pm_buffer_t *buffer) {
30-
switch (string->type) {
31-
case PM_STRING_SHARED: {
32-
pm_buffer_append_byte(buffer, 1);
33-
pm_buffer_append_varuint(buffer, pm_ptrdifft_to_u32(pm_string_source(string) - parser->start));
34-
pm_buffer_append_varuint(buffer, pm_sizet_to_u32(pm_string_length(string)));
35-
break;
36-
}
37-
case PM_STRING_OWNED:
38-
case PM_STRING_CONSTANT: {
39-
uint32_t length = pm_sizet_to_u32(pm_string_length(string));
40-
pm_buffer_append_byte(buffer, 2);
41-
pm_buffer_append_varuint(buffer, length);
42-
pm_buffer_append_bytes(buffer, pm_string_source(string), length);
43-
break;
44-
}
45-
#ifdef PRISM_HAS_MMAP
46-
case PM_STRING_MAPPED:
47-
assert(false && "Cannot serialize mapped strings.");
48-
break;
49-
#endif
50-
}
29+
pm_serialize_string(const pm_string_t *string, pm_buffer_t *buffer) {
30+
uint32_t length = pm_sizet_to_u32(pm_string_length(string));
31+
pm_buffer_append_varuint(buffer, length);
32+
pm_buffer_append_bytes(buffer, pm_string_source(string), length);
5133
}
5234

5335
static void
@@ -102,7 +84,7 @@ pm_serialize_node(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer) {
10284
pm_serialize_node(parser, (pm_node_t *)((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer);
10385
}
10486
<%- when Prism::Template::StringField -%>
105-
pm_serialize_string(parser, &((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer);
87+
pm_serialize_string(&((pm_<%= node.human %>_t *)node)-><%= field.name %>, buffer);
10688
<%- when Prism::Template::NodeListField -%>
10789
uint32_t <%= field.name %>_size = pm_sizet_to_u32(((pm_<%= node.human %>_t *)node)-><%= field.name %>.size);
10890
pm_buffer_append_varuint(buffer, <%= field.name %>_size);
@@ -304,28 +286,12 @@ pm_serialize_content(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer)
304286
pm_constant_t *constant = &parser->constant_pool.constants[bucket->id - 1];
305287
size_t buffer_offset = offset + ((((size_t)bucket->id) - 1) * 8);
306288

307-
if (bucket->type == PM_CONSTANT_POOL_BUCKET_OWNED || bucket->type == PM_CONSTANT_POOL_BUCKET_CONSTANT) {
308-
// Since this is an owned or constant constant, we are going to
309-
// write its contents into the buffer after the constant pool.
310-
// So effectively in place of the source offset, we have a
311-
// buffer offset. We will add a leading 1 to indicate that this
312-
// is a buffer offset.
313-
uint32_t content_offset = pm_sizet_to_u32(buffer->length);
314-
uint32_t owned_mask = 1U << 31;
315-
316-
assert(content_offset < owned_mask);
317-
content_offset |= owned_mask;
318-
319-
memcpy(buffer->value + buffer_offset, &content_offset, 4);
320-
pm_buffer_append_bytes(buffer, constant->start, constant->length);
321-
} else {
322-
// Since this is a shared constant, we are going to write its
323-
// source offset directly into the buffer.
324-
uint32_t source_offset = pm_ptrdifft_to_u32(constant->start - parser->start);
325-
memcpy(buffer->value + buffer_offset, &source_offset, 4);
326-
}
289+
// Write the constant contents into the buffer after the constant
290+
// pool. In place of the source offset, we store a buffer offset.
291+
uint32_t content_offset = pm_sizet_to_u32(buffer->length);
292+
memcpy(buffer->value + buffer_offset, &content_offset, 4);
293+
pm_buffer_append_bytes(buffer, constant->start, constant->length);
327294

328-
// Now we can write the length of the constant into the buffer.
329295
uint32_t constant_length = pm_sizet_to_u32(constant->length);
330296
memcpy(buffer->value + buffer_offset + 4, &constant_length, 4);
331297
}

0 commit comments

Comments
 (0)