// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. define(function() { // Memory ------------------------------------------------------------------- function store8(memory, pointer, val) { memory[pointer] = val; } function store16(memory, pointer, val) { memory[pointer + 0] = val >> 0; memory[pointer + 1] = val >> 8; } function store32(memory, pointer, val) { memory[pointer + 0] = val >> 0; memory[pointer + 1] = val >> 8; memory[pointer + 2] = val >> 16; memory[pointer + 3] = val >> 24; } function store64(memory, pointer, val) { store32(memory, pointer, val); var high = (val / 0x10000) | 0; store32(memory, pointer + 4, high); } function load8(memory, pointer) { return memory[pointer]; } function load16(memory, pointer) { return (memory[pointer + 0] << 0) + (memory[pointer + 1] << 8); } function load32(memory, pointer) { return (memory[pointer + 0] << 0) + (memory[pointer + 1] << 8) + (memory[pointer + 2] << 16) + (memory[pointer + 3] << 24); } function load64(memory, pointer) { var low = load32(memory, pointer); var high = load32(memory, pointer + 4); return low + high * 0x10000; } var kAlignment = 8; function align(size) { return size + (kAlignment - (size % kAlignment)) % kAlignment; } // Buffer ------------------------------------------------------------------- function Buffer(size) { this.memory = new Uint8Array(size); this.next = 0; } Buffer.prototype.alloc = function(size) { var pointer = this.next; this.next += size; if (this.next > this.memory.length) { var newSize = (1.5 * (this.memory.length + size)) | 0; this.grow(newSize); } return pointer; }; Buffer.prototype.grow = function(size) { var newMemory = new Uint8Array(size); var oldMemory = this.memory; for (var i = 0; i < oldMemory.length; ++i) newMemory[i] = oldMemory[i]; this.memory = newMemory; }; Buffer.prototype.createViewOfAllocatedMemory = function() { return new Uint8Array(this.memory.buffer, 0, this.next); }; // Constants ---------------------------------------------------------------- var kArrayHeaderSize = 8; var kStructHeaderSize = 8; var kMessageHeaderSize = 8; // Decoder ------------------------------------------------------------------ function Decoder(memory, handles, base) { this.memory = memory; this.handles = handles; this.base = base; this.next = base; } Decoder.prototype.skip = function(offset) { this.next += offset; }; Decoder.prototype.read8 = function() { var result = load8(this.memory, this.next); this.next += 1; return result; }; Decoder.prototype.read32 = function() { var result = load32(this.memory, this.next); this.next += 4; return result; }; Decoder.prototype.read64 = function() { var result = load64(this.memory, this.next); this.next += 8; return result; }; Decoder.prototype.decodePointer = function() { // TODO(abarth): To correctly decode a pointer, we need to know the real // base address of the array buffer. var offsetPointer = this.next; var offset = this.read64(); if (!offset) return 0; return offsetPointer + offset; }; Decoder.prototype.decodeAndCreateDecoder = function() { return new Decoder(this.memory, this.handles, this.decodePointer()); }; Decoder.prototype.decodeHandle = function() { return this.handles[this.read32()]; }; Decoder.prototype.decodeString = function() { // TODO(abarth): We should really support UTF-8. We might want to // jump out of the VM to decode the string directly from the array // buffer using v8::String::NewFromUtf8. var numberOfBytes = this.read32(); var numberOfElements = this.read32(); var val = new Array(numberOfElements); var memory = this.memory; var base = this.next; for (var i = 0; i < numberOfElements; ++i) { val[i] = String.fromCharCode(memory[base + i] & 0x7F); } this.next += numberOfElements; return val.join(''); }; Decoder.prototype.decodeArray = function(cls) { var numberOfBytes = this.read32(); var numberOfElements = this.read32(); var val = new Array(numberOfElements); for (var i = 0; i < numberOfElements; ++i) { val[i] = cls.decode(this); } return val; }; Decoder.prototype.decodeStructPointer = function(cls) { return cls.decode(this.decodeAndCreateDecoder()); }; Decoder.prototype.decodeArrayPointer = function(cls) { return this.decodeAndCreateDecoder().decodeArray(cls); }; Decoder.prototype.decodeStringPointer = function() { return this.decodeAndCreateDecoder().decodeString(); }; // Encoder ------------------------------------------------------------------ function Encoder(buffer, handles, base) { this.buffer = buffer; this.handles = handles; this.base = base; this.next = base; } Encoder.prototype.skip = function(offset) { this.next += offset; }; Encoder.prototype.write8 = function(val) { store8(this.buffer.memory, this.next, val); this.next += 1; }; Encoder.prototype.write32 = function(val) { store32(this.buffer.memory, this.next, val); this.next += 4; }; Encoder.prototype.write64 = function(val) { store64(this.buffer.memory, this.next, val); this.next += 8; }; Encoder.prototype.encodePointer = function(pointer) { if (!pointer) return this.write64(0); // TODO(abarth): To correctly encode a pointer, we need to know the real // base address of the array buffer. var offset = pointer - this.next; this.write64(offset); }; Encoder.prototype.createAndEncodeEncoder = function(size) { var pointer = this.buffer.alloc(align(size)); this.encodePointer(pointer); return new Encoder(this.buffer, this.handles, pointer); }; Encoder.prototype.encodeHandle = function(handle) { this.handles.push(handle); this.write32(this.handles.length - 1); }; Encoder.prototype.encodeString = function(val) { var numberOfElements = val.length; var numberOfBytes = kArrayHeaderSize + numberOfElements; this.write32(numberOfBytes); this.write32(numberOfElements); // TODO(abarth): We should really support UTF-8. We might want to // jump out of the VM to encode the string directly from the array // buffer using v8::String::WriteUtf8. var memory = this.buffer.memory; var base = this.next; var len = val.length; for (var i = 0; i < len; ++i) { memory[base + i] = val.charCodeAt(i) & 0x7F; } this.next += len; }; Encoder.prototype.encodeArray = function(cls, val) { var numberOfElements = val.length; var numberOfBytes = kArrayHeaderSize + cls.encodedSize * numberOfElements; this.write32(numberOfBytes); this.write32(numberOfElements); for (var i = 0; i < numberOfElements; ++i) { cls.encode(this, val[i]); } }; Encoder.prototype.encodeStructPointer = function(cls, val) { var encoder = this.createAndEncodeEncoder(cls.encodedSize); cls.encode(encoder, val); }; Encoder.prototype.encodeArrayPointer = function(cls, val) { var encodedSize = kArrayHeaderSize + cls.encodedSize * val.length; var encoder = this.createAndEncodeEncoder(encodedSize); encoder.encodeArray(cls, val); }; Encoder.prototype.encodeStringPointer = function(val) { // TODO(abarth): This won't be right once we support UTF-8. var encodedSize = kArrayHeaderSize + val.length; var encoder = this.createAndEncodeEncoder(encodedSize); encoder.encodeString(val); }; // Message ------------------------------------------------------------------ function Message(memory, handles) { this.memory = memory; this.handles = handles; } // MessageBuilder ----------------------------------------------------------- function MessageBuilder(messageName, payloadSize) { // Currently, we don't compute the payload size correctly ahead of time. // Instead, we overwrite this field at the end. var numberOfBytes = kMessageHeaderSize + payloadSize; this.buffer = new Buffer(numberOfBytes); this.handles = []; var encoder = this.createEncoder(kMessageHeaderSize); encoder.write32(numberOfBytes); encoder.write32(messageName); } MessageBuilder.prototype.createEncoder = function(size) { var pointer = this.buffer.alloc(size); return new Encoder(this.buffer, this.handles, pointer); } MessageBuilder.prototype.encodeStruct = function(cls, val) { cls.encode(this.createEncoder(cls.encodedSize), val); }; MessageBuilder.prototype.finish = function() { // TODO(abarth): Rather than resizing the buffer at the end, we could // compute the size we need ahead of time, like we do in C++. var memory = this.buffer.createViewOfAllocatedMemory(); store32(memory, 0, memory.length); var message = new Message(memory, this.handles); this.buffer = null; this.handles = null; this.encoder = null; return message; }; // MessageReader ------------------------------------------------------------ function MessageReader(message) { this.decoder = new Decoder(message.memory, message.handles, 0); this.payloadSize = this.decoder.read32() - kMessageHeaderSize; this.messageName = this.decoder.read32(); } MessageReader.prototype.decodeStruct = function(cls) { return cls.decode(this.decoder); }; // Built-in types ----------------------------------------------------------- function Uint8() { } Uint8.encodedSize = 1; Uint8.decode = function(decoder) { return decoder.read8(); }; Uint8.encode = function(encoder, val) { encoder.write8(val); }; function Uint16() { } Uint16.encodedSize = 2; Uint16.decode = function(decoder) { return decoder.read16(); }; Uint16.encode = function(encoder, val) { encoder.write16(val); }; function Uint32() { } Uint32.encodedSize = 4; Uint32.decode = function(decoder) { return decoder.read32(); }; Uint32.encode = function(encoder, val) { encoder.write32(val); }; function Uint64() { }; Uint64.encodedSize = 8; Uint64.decode = function(decoder) { return decoder.read64(); }; Uint64.encode = function(encoder, val) { encoder.write64(val); }; function PointerTo(cls) { this.cls = cls; }; // TODO(abarth): Add missing types: // * String // * Float // * Double // * Signed integers PointerTo.prototype.encodedSize = 8; PointerTo.prototype.decode = function(decoder) { return this.cls.decode(decoder.decodeAndCreateDecoder()); }; PointerTo.prototype.encode = function(encoder, val) { var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize); this.cls.encode(objectEncoder, val); }; function ArrayOf(cls) { this.cls = cls; }; ArrayOf.prototype.encodedSize = 8; ArrayOf.prototype.decode = function(decoder) { return decoder.decodeArrayPointer(self.cls); }; ArrayOf.prototype.encode = function(encoder, val) { encoder.encodeArrayPointer(self.cls, val); }; function Handle() { } Handle.encodedSize = 4; Handle.decode = function(decoder) { return decoder.decodeHandle(); }; Handle.encode = function(encoder, val) { encoder.encodeHandle(val); }; var exports = {}; exports.align = align; exports.Message = Message; exports.MessageBuilder = MessageBuilder; exports.MessageReader = MessageReader; exports.kArrayHeaderSize = kArrayHeaderSize; exports.kStructHeaderSize = kStructHeaderSize; exports.kMessageHeaderSize = kMessageHeaderSize; exports.Uint8 = Uint8; exports.Uint16 = Uint16; exports.Uint32 = Uint32; exports.Uint64 = Uint64; exports.PointerTo = PointerTo; exports.ArrayOf = ArrayOf; exports.Handle = Handle; return exports; });