mirror of https://github.com/lukechilds/node.git
763 changed files with 265280 additions and 22 deletions
@ -0,0 +1,5 @@ |
|||
*.o |
|||
examples/hello_world |
|||
test_request_parser |
|||
ebb_request_parser.c |
|||
tags |
@ -0,0 +1,21 @@ |
|||
Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining |
|||
a copy of this software and associated documentation files (the |
|||
"Software"), to deal in the Software without restriction, including |
|||
without limitation the rights to use, copy, modify, merge, publish, |
|||
distribute, sublicense, and/or sell copies of the Software, and to |
|||
permit persons to whom the Software is furnished to do so, subject to |
|||
the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be |
|||
included in all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
|
@ -0,0 +1,7 @@ |
|||
see doc/index.html and examples/hello_world.c for explanation |
|||
|
|||
webpage: http://tinyclouds.org/libebb/ |
|||
git repository: http://github.com/ry/libebb/tree/master |
|||
|
|||
To build libebb please edit config.mk to reflect your system's parameters. |
|||
|
@ -0,0 +1,38 @@ |
|||
PREFIX = $(HOME)/local/libebb |
|||
|
|||
# libev
|
|||
EVINC = $(HOME)/local/libev/include |
|||
EVLIB = $(HOME)/local/libev/lib |
|||
EVLIBS = -L${EVLIB} -lev |
|||
|
|||
# GnuTLS, comment if you don't want it (necessary for HTTPS)
|
|||
GNUTLSLIB = /usr/lib |
|||
GNUTLSINC = /usr/include |
|||
GNUTLSLIBS = -L${GNUTLSLIB} -lgnutls |
|||
GNUTLSFLAGS = -DHAVE_GNUTLS |
|||
|
|||
# includes and libs
|
|||
INCS = -I${EVINC} -I${GNUTLSINC} |
|||
LIBS = ${EVLIBS} ${GNUTLSLIBS} -lefence |
|||
|
|||
# flags
|
|||
CPPFLAGS = -DVERSION=\"$(VERSION)\" ${GNUTLSFLAGS} |
|||
CFLAGS = -O2 -g -Wall ${INCS} ${CPPFLAGS} -fPIC |
|||
LDFLAGS = -s ${LIBS} |
|||
LDOPT = -shared |
|||
SUFFIX = so |
|||
SONAME = -Wl,-soname,$(OUTPUT_LIB) |
|||
|
|||
# Solaris
|
|||
#CFLAGS = -fast ${INCS} -DVERSION=\"$(VERSION)\" -fPIC
|
|||
#LDFLAGS = ${LIBS}
|
|||
#SONAME =
|
|||
|
|||
# Darwin
|
|||
# LDOPT = -dynamiclib
|
|||
# SUFFIX = dylib
|
|||
# SONAME = -current_version $(VERSION) -compatibility_version $(VERSION)
|
|||
|
|||
# compiler and linker
|
|||
CC = cc |
|||
RANLIB = ranlib |
After Width: | Height: | Size: 9.2 KiB |
@ -0,0 +1,240 @@ |
|||
|
|||
<html> |
|||
<style> |
|||
body { |
|||
background: #fff; |
|||
color: #2e3436; |
|||
font-size: 12pt; |
|||
line-height: 16pt; |
|||
/* font-family: Palatino; */ |
|||
margin: 3em 0 3em 3em; |
|||
} |
|||
|
|||
code, pre { |
|||
} |
|||
|
|||
#contents { |
|||
max-width: 40em; |
|||
} |
|||
|
|||
ul { |
|||
padding-left: 0; |
|||
} |
|||
|
|||
li { |
|||
margin-top: 0.5em; |
|||
margin-bottom: 0.5em; |
|||
} |
|||
|
|||
p { |
|||
text-align: left; |
|||
} |
|||
|
|||
|
|||
img { |
|||
float: left; |
|||
margin: 0 1em 1em 0; |
|||
} |
|||
|
|||
p { clear: both; } |
|||
</style> |
|||
<body> <div id="contents"> |
|||
<img src="icon.png"/> |
|||
<h1>libebb</h1> |
|||
|
|||
<p> |
|||
libebb is a lightweight HTTP server library for C. It lays the |
|||
foundation for writing a web server by providing the socket juggling |
|||
and request parsing. By implementing the HTTP/1.1 grammar provided in |
|||
RFC2612, libebb understands most most valid HTTP/1.1 connections |
|||
(persistent, pipelined, and chunked requests included) and rejects |
|||
invalid or malicious requests. libebb supports SSL over HTTP. |
|||
</p> |
|||
|
|||
<p> |
|||
The library embraces a minimalistic single-threaded evented design. |
|||
No control is removed from the user. For example, all allocations are |
|||
done through callbacks so that the user might implement in optimal |
|||
ways for their specific application. By design libebb is not |
|||
thread-safe and all provided callbacks must not block. libebb uses |
|||
the <a href="http://libev.schmorp.de/bench.html">high-performance</a> |
|||
libev event loop, but does not control it. The user of the library may |
|||
start and stop the loop at will, they may attach thier own watchers. |
|||
</p> |
|||
|
|||
<p> |
|||
libebb depends on POSIX sockets, libev, and optionally GnuTLS. |
|||
</p> |
|||
|
|||
<p> |
|||
libebb is in the early stages of development and probably contains |
|||
many bugs. The API is subject to radical changes. If you're |
|||
interested <a href="http://github.com/ry/libebb/tree/master">checkout |
|||
the source code</a> and <a |
|||
href="http://groups.google.com/group/ebbebb">join the mailing |
|||
list</a>. A release will be made when it proves stable. |
|||
</p> |
|||
|
|||
<p>libebb is released under <a |
|||
href="http://www.gnu.org/licenses/license-list.html#X11License">the |
|||
X11 license</a>.</p> |
|||
|
|||
<h2>Usage</h2> |
|||
|
|||
<p> |
|||
libebb is a simple API, mostly it is providing callbacks. There are |
|||
two types of callbacks that one will work with: |
|||
</p> |
|||
|
|||
<ul> |
|||
<li>callbacks to allocate and initialize data for libebb. These are |
|||
named <code>new_*</code> like <code>new_connection</code> and |
|||
<code>new_request</code></li> |
|||
<li>callbacks which happen on an event and might provide a pointer to |
|||
a chunk of data. These are named <code>on_*</code> like |
|||
<code>on_body</code> and <code>on_close</code>.</li> |
|||
</ul> |
|||
|
|||
<p> |
|||
In libebb there are three important classes: <code>ebb_server</code>, |
|||
<code>ebb_connection</code>, and <code>ebb_request</code>. |
|||
Each server has many peer connections. Each peer connection may have many |
|||
requests. |
|||
There are two additional classes <code>ebb_buf</code> and <code>ebb_request_parser</code> |
|||
which may or may not be useful. |
|||
</p> |
|||
|
|||
<h3><code>ebb_server</code></h3> |
|||
<p> |
|||
<code>ebb_server</code> represents a single web server listening on a |
|||
single port. The user must allocate the structure themselves, then |
|||
call <code>ebb_server_init()</code> and provide a libev event loop. |
|||
<code>ebb_server_set_secure()</code> will make the server understand |
|||
HTTPS connections. |
|||
</p> |
|||
|
|||
<p> |
|||
After initialized the <code>ebb_server_listen_on_port()</code> can be |
|||
called to open the server up to new connections. libebb does not |
|||
control the event loop, it is the user's responsibility to start the |
|||
event loop (using <code>ev_loop()</code>) after |
|||
<code>ebb_server_listen_on_port()</code> is called. |
|||
</p> |
|||
|
|||
<p> |
|||
To accept connections you must provide the new server with a callback |
|||
called <code>new_connection</code>. This callback must return an allocated |
|||
and initialized <code>ebb_connection</code> structure. |
|||
To set this callback do |
|||
</p> |
|||
|
|||
<pre>my_server->new_connection = my_new_connection_callback;</pre> |
|||
|
|||
<p> |
|||
Additional documentation can be found in <code>ebb.h</code> |
|||
</p> |
|||
|
|||
<h3><code>ebb_connection</code></h3> |
|||
|
|||
<p> |
|||
This structure contains information and callbacks for a single client |
|||
connection. It is allocated and initialized through the |
|||
<code>new_connection</code> callback in <code>ebb_server</code>. |
|||
To initialize a newly allocated <code>ebb_connection</code> use |
|||
<code>ebb_connection_init()</code>. |
|||
</p> |
|||
|
|||
<p> |
|||
After <code>ebb_connection_init()</code> is called a number of |
|||
callbacks can be set: <code>new_request</code>, <code>new_buf</code>, |
|||
<code>on_timeout</code>, and <code>on_close</code>. |
|||
</p> |
|||
|
|||
<p> |
|||
When an <code>ebb_connection</code> is returned to an |
|||
<code>ebb_server</code>, data is immediately data is read from the |
|||
socket. This data must be stored somewhere. Because libebb is |
|||
agnostic about allocation decisions, it passes this off to the user in |
|||
the form of a callback: <code>connection->new_buf</code>. This |
|||
callback returns a newly allocated and initialized |
|||
<code>ebb_buf</code> structure. How much libebb attempts to read from |
|||
the socket is determined by how large the returned |
|||
<code>ebb_buf</code> structure is. Using <code>new_buf</code> is |
|||
optional. By default libebb reads data into a static buffer |
|||
(allocated at compile time), writing over it on each read. In many |
|||
web server using the static buffer will be sufficent because callbacks |
|||
made during the parsing will buffer the data elsewhere. Providing a |
|||
<code>new_buf</code> callback is necessary only if you want to save |
|||
the raw data coming from the socket. |
|||
</p> |
|||
|
|||
<p> |
|||
The <code>new_request</code> callback is called at the beginning of a |
|||
request. It must return a newly allocated and initialized |
|||
<code>ebb_request</code> structure. Because HTTP/1.1 supports <a |
|||
href="http://en.wikipedia.org/wiki/HTTP_persistent_connection">peristant</a> |
|||
connections, there may be many requests per connection. |
|||
</p> |
|||
|
|||
<p> |
|||
You may access the file descriptor for the client socket inside the |
|||
<code>ebb_connection</code> structure. Writing the response, in valid |
|||
HTTP, is the user's responsibility. Remember, requests must be |
|||
returned to client in the same order that they were received. |
|||
</p> |
|||
|
|||
<p> |
|||
A convience function, <coe>ebb_connection_write</code>, is provided |
|||
which will write a single string to the peer. You may use |
|||
this function or you may write to the file descriptor directly. |
|||
</p> |
|||
|
|||
<p> |
|||
To close a peer connection use |
|||
<code>ebb_connnection_schedule_close()</code>. Because SSL may require |
|||
some additional communication to close the connection properly, the |
|||
file descriptor cannot be closed immediately. The |
|||
<code>on_close</code> callback will be made when the peer socket is |
|||
finally closed. |
|||
<em>Only once <code>on_close</code> is called may the |
|||
user free the <code>ebb_connection</code> structure.</em> |
|||
</p> |
|||
|
|||
|
|||
<h3><code>ebb_request</code></h3> |
|||
|
|||
<p> |
|||
This structure provides information about a request. For example, |
|||
<code>request->method == EBB_POST</code> would mean the method of |
|||
the request is <code>POST</code>. There are also many callbacks |
|||
which can be set to handle data from a request as it is parsed. |
|||
</p> |
|||
|
|||
<p> |
|||
The <code>on_uri</code> callback and all other |
|||
<code>ebb_element_cb</code> callbacks provide pointers to request |
|||
data. The annoying thing is they do not necessarily provide a |
|||
complete string. This is because the reads from the socket may not |
|||
contain an entire request and for efficency libebb does not attempt to |
|||
buffer the data. Theoretically, one might receive an |
|||
<code>on_uri</code> callback 10 times, each providing just a single |
|||
character of the request's URI. See <code>ebb_request_parser.h</code> for |
|||
a full list of callbacks that you may provide. (If you don't set them, |
|||
they are ignored.) |
|||
</p> |
|||
|
|||
<p> |
|||
The <code>on_complete</code> callback is called at the end of |
|||
each request. |
|||
<em>Only once <code>on_complete</code> is called may the |
|||
user free the <code>ebb_request</code> structure.</em> |
|||
</p> |
|||
|
|||
<h2>Example</h2> |
|||
|
|||
<p> |
|||
A simple example is provided in <code>examples/hello_world.c</code>. |
|||
</p> |
|||
|
|||
</div></body> |
|||
</html> |
@ -0,0 +1,798 @@ |
|||
/* This file is part of libebb.
|
|||
* |
|||
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) |
|||
* All rights reserved. |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining |
|||
* a copy of this software and associated documentation files (the |
|||
* "Software"), to deal in the Software without restriction, including |
|||
* without limitation the rights to use, copy, modify, merge, publish, |
|||
* distribute, sublicense, and/or sell copies of the Software, and to |
|||
* permit persons to whom the Software is furnished to do so, subject to |
|||
* the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be |
|||
* included in all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
*/ |
|||
#include <assert.h> |
|||
#include <string.h> |
|||
#include <fcntl.h> |
|||
#include <sys/types.h> |
|||
#include <sys/socket.h> |
|||
#include <netinet/tcp.h> /* TCP_NODELAY */ |
|||
#include <netinet/in.h> /* inet_ntoa */ |
|||
#include <arpa/inet.h> /* inet_ntoa */ |
|||
#include <unistd.h> |
|||
#include <stdio.h> /* perror */ |
|||
#include <errno.h> /* perror */ |
|||
#include <stdlib.h> /* for the default methods */ |
|||
#include <ev.h> |
|||
|
|||
#include "ebb.h" |
|||
#include "ebb_request_parser.h" |
|||
#ifdef HAVE_GNUTLS |
|||
# include <gnutls/gnutls.h> |
|||
# include "rbtree.h" /* for session_cache */ |
|||
#endif |
|||
|
|||
#ifndef TRUE |
|||
# define TRUE 1 |
|||
#endif |
|||
#ifndef FALSE |
|||
# define FALSE 0 |
|||
#endif |
|||
#ifndef MIN |
|||
# define MIN(a,b) (a < b ? a : b) |
|||
#endif |
|||
|
|||
#define error(FORMAT, ...) fprintf(stderr, "error: " FORMAT "\n", ##__VA_ARGS__) |
|||
|
|||
#define CONNECTION_HAS_SOMETHING_TO_WRITE (connection->to_write != NULL) |
|||
|
|||
static void |
|||
set_nonblock (int fd) |
|||
{ |
|||
int flags = fcntl(fd, F_GETFL, 0); |
|||
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
|||
assert(0 <= r && "Setting socket non-block failed!"); |
|||
} |
|||
|
|||
static ssize_t |
|||
nosigpipe_push(void *data, const void *buf, size_t len) |
|||
{ |
|||
int fd = (int)data; |
|||
int flags = 0; |
|||
#ifdef MSG_NOSIGNAL |
|||
flags = MSG_NOSIGNAL; |
|||
#endif |
|||
return send(fd, buf, len, flags); |
|||
} |
|||
|
|||
static void |
|||
close_connection(ebb_connection *connection) |
|||
{ |
|||
#ifdef HAVE_GNUTLS |
|||
if(connection->server->secure) |
|||
ev_io_stop(connection->server->loop, &connection->handshake_watcher); |
|||
#endif |
|||
ev_io_stop(connection->server->loop, &connection->read_watcher); |
|||
ev_io_stop(connection->server->loop, &connection->write_watcher); |
|||
ev_timer_stop(connection->server->loop, &connection->timeout_watcher); |
|||
|
|||
if(0 > close(connection->fd)) |
|||
error("problem closing connection fd"); |
|||
|
|||
connection->open = FALSE; |
|||
|
|||
if(connection->on_close) |
|||
connection->on_close(connection); |
|||
/* No access to the connection past this point!
|
|||
* The user is allowed to free in the callback |
|||
*/ |
|||
} |
|||
|
|||
#ifdef HAVE_GNUTLS |
|||
#define GNUTLS_NEED_WRITE (gnutls_record_get_direction(connection->session) == 1) |
|||
#define GNUTLS_NEED_READ (gnutls_record_get_direction(connection->session) == 0) |
|||
|
|||
#define EBB_MAX_SESSION_KEY 32 |
|||
#define EBB_MAX_SESSION_VALUE 512 |
|||
|
|||
struct session_cache { |
|||
struct rbtree_node_t node; |
|||
|
|||
gnutls_datum_t key; |
|||
gnutls_datum_t value; |
|||
|
|||
char key_storage[EBB_MAX_SESSION_KEY]; |
|||
char value_storage[EBB_MAX_SESSION_VALUE]; |
|||
}; |
|||
|
|||
static int |
|||
session_cache_compare (void *left, void *right) |
|||
{ |
|||
gnutls_datum_t *left_key = left; |
|||
gnutls_datum_t *right_key = right; |
|||
if(left_key->size < right_key->size) |
|||
return -1; |
|||
else if(left_key->size > right_key->size) |
|||
return 1; |
|||
else |
|||
return memcmp( left_key->data |
|||
, right_key->data |
|||
, MIN(left_key->size, right_key->size) |
|||
); |
|||
} |
|||
|
|||
static int |
|||
session_cache_store(void *data, gnutls_datum_t key, gnutls_datum_t value) |
|||
{ |
|||
rbtree tree = data; |
|||
|
|||
if( tree == NULL |
|||
|| key.size > EBB_MAX_SESSION_KEY |
|||
|| value.size > EBB_MAX_SESSION_VALUE |
|||
) return -1; |
|||
|
|||
struct session_cache *cache = gnutls_malloc(sizeof(struct session_cache)); |
|||
|
|||
memcpy (cache->key_storage, key.data, key.size); |
|||
cache->key.size = key.size; |
|||
cache->key.data = (void*)cache->key_storage; |
|||
|
|||
memcpy (cache->value_storage, value.data, value.size); |
|||
cache->value.size = value.size; |
|||
cache->value.data = (void*)cache->value_storage; |
|||
|
|||
cache->node.key = &cache->key; |
|||
cache->node.value = &cache; |
|||
|
|||
rbtree_insert(tree, (rbtree_node)cache); |
|||
|
|||
//printf("session_cache_store\n");
|
|||
|
|||
return 0; |
|||
} |
|||
|
|||
static gnutls_datum_t |
|||
session_cache_retrieve (void *data, gnutls_datum_t key) |
|||
{ |
|||
rbtree tree = data; |
|||
gnutls_datum_t res = { NULL, 0 }; |
|||
struct session_cache *cache = rbtree_lookup(tree, &key); |
|||
|
|||
if(cache == NULL) |
|||
return res; |
|||
|
|||
res.size = cache->value.size; |
|||
res.data = gnutls_malloc (res.size); |
|||
if(res.data == NULL) |
|||
return res; |
|||
|
|||
memcpy(res.data, cache->value.data, res.size); |
|||
|
|||
//printf("session_cache_retrieve\n");
|
|||
|
|||
return res; |
|||
} |
|||
|
|||
static int |
|||
session_cache_remove (void *data, gnutls_datum_t key) |
|||
{ |
|||
rbtree tree = data; |
|||
|
|||
if(tree == NULL) |
|||
return -1; |
|||
|
|||
struct session_cache *cache = (struct session_cache *)rbtree_delete(tree, &key); |
|||
if(cache == NULL) |
|||
return -1; |
|||
|
|||
gnutls_free(cache); |
|||
|
|||
//printf("session_cache_remove\n");
|
|||
|
|||
return 0; |
|||
} |
|||
|
|||
static void |
|||
on_handshake(struct ev_loop *loop ,ev_io *watcher, int revents) |
|||
{ |
|||
ebb_connection *connection = watcher->data; |
|||
|
|||
//printf("on_handshake\n");
|
|||
|
|||
assert(ev_is_active(&connection->timeout_watcher)); |
|||
assert(!ev_is_active(&connection->read_watcher)); |
|||
assert(!ev_is_active(&connection->write_watcher)); |
|||
|
|||
if(EV_ERROR & revents) { |
|||
error("on_handshake() got error event, closing connection.n"); |
|||
goto error; |
|||
} |
|||
|
|||
int r = gnutls_handshake(connection->session); |
|||
if(r < 0) { |
|||
if(gnutls_error_is_fatal(r)) goto error; |
|||
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) |
|||
ev_io_set( watcher |
|||
, connection->fd |
|||
, (GNUTLS_NEED_WRITE ? EV_WRITE : EV_READ) |
|||
); |
|||
return; |
|||
} |
|||
|
|||
ebb_connection_reset_timeout(connection); |
|||
ev_io_stop(loop, watcher); |
|||
|
|||
ev_io_start(loop, &connection->read_watcher); |
|||
if(CONNECTION_HAS_SOMETHING_TO_WRITE) |
|||
ev_io_start(loop, &connection->write_watcher); |
|||
|
|||
return; |
|||
error: |
|||
close_connection(connection); |
|||
} |
|||
|
|||
#endif /* HAVE_GNUTLS */ |
|||
|
|||
|
|||
/* Internal callback
|
|||
* called by connection->timeout_watcher |
|||
*/ |
|||
static void |
|||
on_timeout(struct ev_loop *loop, ev_timer *watcher, int revents) |
|||
{ |
|||
ebb_connection *connection = watcher->data; |
|||
|
|||
assert(watcher == &connection->timeout_watcher); |
|||
|
|||
//printf("on_timeout\n");
|
|||
|
|||
/* if on_timeout returns true, we don't time out */ |
|||
if(connection->on_timeout) { |
|||
int r = connection->on_timeout(connection); |
|||
|
|||
if(r == EBB_AGAIN) { |
|||
ebb_connection_reset_timeout(connection); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
ebb_connection_schedule_close(connection); |
|||
} |
|||
|
|||
/* Internal callback
|
|||
* called by connection->read_watcher |
|||
*/ |
|||
static void |
|||
on_readable(struct ev_loop *loop, ev_io *watcher, int revents) |
|||
{ |
|||
ebb_connection *connection = watcher->data; |
|||
char recv_buffer[TCP_MAXWIN]; |
|||
ssize_t recved; |
|||
|
|||
//printf("on_readable\n");
|
|||
// TODO -- why is this broken?
|
|||
//assert(ev_is_active(&connection->timeout_watcher));
|
|||
assert(watcher == &connection->read_watcher); |
|||
|
|||
if(EV_ERROR & revents) { |
|||
error("on_readable() got error event, closing connection."); |
|||
goto error; |
|||
} |
|||
|
|||
#ifdef HAVE_GNUTLS |
|||
assert(!ev_is_active(&connection->handshake_watcher)); |
|||
|
|||
if(connection->server->secure) { |
|||
recved = gnutls_record_recv( connection->session |
|||
, recv_buffer |
|||
, TCP_MAXWIN |
|||
); |
|||
if(recved <= 0) { |
|||
if(gnutls_error_is_fatal(recved)) goto error; |
|||
if( (recved == GNUTLS_E_INTERRUPTED || recved == GNUTLS_E_AGAIN) |
|||
&& GNUTLS_NEED_WRITE |
|||
) ev_io_start(loop, &connection->write_watcher); |
|||
return; |
|||
} |
|||
} else { |
|||
#endif /* HAVE_GNUTLS */ |
|||
|
|||
recved = recv(connection->fd, recv_buffer, TCP_MAXWIN, 0); |
|||
if(recved <= 0) goto error; |
|||
|
|||
#ifdef HAVE_GNUTLS |
|||
} |
|||
#endif /* HAVE_GNUTLS */ |
|||
|
|||
ebb_connection_reset_timeout(connection); |
|||
|
|||
ebb_request_parser_execute(&connection->parser, recv_buffer, recved); |
|||
|
|||
/* parse error? just drop the client. screw the 400 response */ |
|||
if(ebb_request_parser_has_error(&connection->parser)) goto error; |
|||
return; |
|||
error: |
|||
ebb_connection_schedule_close(connection); |
|||
} |
|||
|
|||
/* Internal callback
|
|||
* called by connection->write_watcher |
|||
*/ |
|||
static void |
|||
on_writable(struct ev_loop *loop, ev_io *watcher, int revents) |
|||
{ |
|||
ebb_connection *connection = watcher->data; |
|||
ssize_t sent; |
|||
|
|||
//printf("on_writable\n");
|
|||
|
|||
assert(CONNECTION_HAS_SOMETHING_TO_WRITE); |
|||
assert(connection->written <= connection->to_write_len); |
|||
// TODO -- why is this broken?
|
|||
//assert(ev_is_active(&connection->timeout_watcher));
|
|||
assert(watcher == &connection->write_watcher); |
|||
|
|||
if(connection->to_write == 0) |
|||
goto stop_writing; |
|||
|
|||
#ifdef HAVE_GNUTLS |
|||
assert(!ev_is_active(&connection->handshake_watcher)); |
|||
|
|||
if(connection->server->secure) { |
|||
sent = gnutls_record_send( connection->session |
|||
, connection->to_write + connection->written |
|||
, connection->to_write_len - connection->written |
|||
); |
|||
if(sent < 0) { |
|||
if(gnutls_error_is_fatal(sent)) goto error; |
|||
if( (sent == GNUTLS_E_INTERRUPTED || sent == GNUTLS_E_AGAIN) |
|||
&& GNUTLS_NEED_READ |
|||
) ev_io_stop(loop, watcher); |
|||
return; |
|||
} |
|||
} else { |
|||
#endif /* HAVE_GNUTLS */ |
|||
|
|||
sent = nosigpipe_push( (void*)connection->fd |
|||
, connection->to_write + connection->written |
|||
, connection->to_write_len - connection->written |
|||
); |
|||
if(sent < 0) goto error; |
|||
if(sent == 0) return; |
|||
|
|||
#ifdef HAVE_GNUTLS |
|||
} |
|||
#endif /* HAVE_GNUTLS */ |
|||
|
|||
ebb_connection_reset_timeout(connection); |
|||
|
|||
connection->written += sent; |
|||
|
|||
if(connection->written == connection->to_write_len) { |
|||
goto stop_writing; |
|||
} |
|||
return; |
|||
stop_writing: |
|||
ev_io_stop(loop, watcher); |
|||
connection->to_write = NULL; |
|||
|
|||
if(connection->after_write_cb) |
|||
connection->after_write_cb(connection); |
|||
return; |
|||
error: |
|||
error("close connection on write."); |
|||
ebb_connection_schedule_close(connection); |
|||
} |
|||
|
|||
#ifdef HAVE_GNUTLS |
|||
|
|||
static void |
|||
on_goodbye_tls(struct ev_loop *loop, ev_io *watcher, int revents) |
|||
{ |
|||
ebb_connection *connection = watcher->data; |
|||
assert(watcher == &connection->goodbye_tls_watcher); |
|||
|
|||
if(EV_ERROR & revents) { |
|||
error("on_goodbye() got error event, closing connection."); |
|||
goto die; |
|||
} |
|||
|
|||
int r = gnutls_bye(connection->session, GNUTLS_SHUT_RDWR); |
|||
if(r < 0) { |
|||
if(gnutls_error_is_fatal(r)) goto die; |
|||
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) |
|||
ev_io_set( watcher |
|||
, connection->fd |
|||
, (GNUTLS_NEED_WRITE ? EV_WRITE : EV_READ) |
|||
); |
|||
return; |
|||
} |
|||
|
|||
die: |
|||
ev_io_stop(loop, watcher); |
|||
if(connection->session) |
|||
gnutls_deinit(connection->session); |
|||
close_connection(connection); |
|||
} |
|||
#endif /* HAVE_GNUTLS*/ |
|||
|
|||
static void |
|||
on_goodbye(struct ev_loop *loop, ev_timer *watcher, int revents) |
|||
{ |
|||
ebb_connection *connection = watcher->data; |
|||
assert(watcher == &connection->goodbye_watcher); |
|||
|
|||
close_connection(connection); |
|||
} |
|||
|
|||
|
|||
static ebb_request* |
|||
new_request_wrapper(void *data) |
|||
{ |
|||
ebb_connection *connection = data; |
|||
if(connection->new_request) |
|||
return connection->new_request(connection); |
|||
return NULL; |
|||
} |
|||
|
|||
/* Internal callback
|
|||
* Called by server->connection_watcher. |
|||
*/ |
|||
static void |
|||
on_connection(struct ev_loop *loop, ev_io *watcher, int revents) |
|||
{ |
|||
ebb_server *server = watcher->data; |
|||
|
|||
//printf("on connection!\n");
|
|||
|
|||
assert(server->listening); |
|||
assert(server->loop == loop); |
|||
assert(&server->connection_watcher == watcher); |
|||
|
|||
if(EV_ERROR & revents) { |
|||
error("on_connection() got error event, closing server."); |
|||
ebb_server_unlisten(server); |
|||
return; |
|||
} |
|||
|
|||
struct sockaddr_in addr; // connector's address information
|
|||
socklen_t addr_len = sizeof(addr); |
|||
int fd = accept( server->fd |
|||
, (struct sockaddr*) & addr |
|||
, & addr_len |
|||
); |
|||
if(fd < 0) { |
|||
perror("accept()"); |
|||
return; |
|||
} |
|||
|
|||
ebb_connection *connection = NULL; |
|||
if(server->new_connection) |
|||
connection = server->new_connection(server, &addr); |
|||
if(connection == NULL) { |
|||
close(fd); |
|||
return; |
|||
} |
|||
|
|||
set_nonblock(fd); |
|||
connection->fd = fd; |
|||
connection->open = TRUE; |
|||
connection->server = server; |
|||
memcpy(&connection->sockaddr, &addr, addr_len); |
|||
if(server->port[0] != '\0') |
|||
connection->ip = inet_ntoa(connection->sockaddr.sin_addr); |
|||
|
|||
#ifdef SO_NOSIGPIPE |
|||
int arg = 1; |
|||
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &arg, sizeof(int)); |
|||
#endif |
|||
|
|||
#ifdef HAVE_GNUTLS |
|||
if(server->secure) { |
|||
gnutls_init(&connection->session, GNUTLS_SERVER); |
|||
gnutls_transport_set_lowat(connection->session, 0); |
|||
gnutls_set_default_priority(connection->session); |
|||
gnutls_credentials_set(connection->session, GNUTLS_CRD_CERTIFICATE, connection->server->credentials); |
|||
|
|||
gnutls_transport_set_ptr(connection->session, (gnutls_transport_ptr) fd); |
|||
gnutls_transport_set_push_function(connection->session, nosigpipe_push); |
|||
|
|||
gnutls_db_set_ptr (connection->session, &server->session_cache); |
|||
gnutls_db_set_store_function (connection->session, session_cache_store); |
|||
gnutls_db_set_retrieve_function (connection->session, session_cache_retrieve); |
|||
gnutls_db_set_remove_function (connection->session, session_cache_remove); |
|||
} |
|||
|
|||
ev_io_set(&connection->handshake_watcher, connection->fd, EV_READ | EV_WRITE); |
|||
#endif /* HAVE_GNUTLS */ |
|||
|
|||
/* Note: not starting the write watcher until there is data to be written */ |
|||
ev_io_set(&connection->write_watcher, connection->fd, EV_WRITE); |
|||
ev_io_set(&connection->read_watcher, connection->fd, EV_READ); |
|||
/* XXX: seperate error watcher? */ |
|||
|
|||
ev_timer_again(loop, &connection->timeout_watcher); |
|||
|
|||
#ifdef HAVE_GNUTLS |
|||
if(server->secure) { |
|||
ev_io_start(loop, &connection->handshake_watcher); |
|||
return; |
|||
} |
|||
#endif |
|||
|
|||
ev_io_start(loop, &connection->read_watcher); |
|||
} |
|||
|
|||
/**
|
|||
* Begin the server listening on a file descriptor. This DOES NOT start the |
|||
* event loop. Start the event loop after making this call. |
|||
*/ |
|||
int |
|||
ebb_server_listen_on_fd(ebb_server *server, const int fd) |
|||
{ |
|||
assert(server->listening == FALSE); |
|||
|
|||
if (listen(fd, EBB_MAX_CONNECTIONS) < 0) { |
|||
perror("listen()"); |
|||
return -1; |
|||
} |
|||
|
|||
set_nonblock(fd); /* XXX superfluous? */ |
|||
|
|||
server->fd = fd; |
|||
server->listening = TRUE; |
|||
|
|||
ev_io_set (&server->connection_watcher, server->fd, EV_READ); |
|||
ev_io_start (server->loop, &server->connection_watcher); |
|||
|
|||
return server->fd; |
|||
} |
|||
|
|||
|
|||
/**
|
|||
* Begin the server listening on a file descriptor This DOES NOT start the |
|||
* event loop. Start the event loop after making this call. |
|||
*/ |
|||
int |
|||
ebb_server_listen_on_port(ebb_server *server, const int port) |
|||
{ |
|||
int fd = -1; |
|||
struct linger ling = {0, 0}; |
|||
struct sockaddr_in addr; |
|||
int flags = 1; |
|||
|
|||
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { |
|||
perror("socket()"); |
|||
goto error; |
|||
} |
|||
|
|||
flags = 1; |
|||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags)); |
|||
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags)); |
|||
setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling)); |
|||
|
|||
/* XXX: Sending single byte chunks in a response body? Perhaps there is a
|
|||
* need to enable the Nagel algorithm dynamically. For now disabling. |
|||
*/ |
|||
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags)); |
|||
|
|||
/* the memset call clears nonstandard fields in some impementations that
|
|||
* otherwise mess things up. |
|||
*/ |
|||
memset(&addr, 0, sizeof(addr)); |
|||
|
|||
addr.sin_family = AF_INET; |
|||
addr.sin_port = htons(port); |
|||
addr.sin_addr.s_addr = htonl(INADDR_ANY); |
|||
|
|||
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { |
|||
perror("bind()"); |
|||
goto error; |
|||
} |
|||
|
|||
int ret = ebb_server_listen_on_fd(server, fd); |
|||
if (ret >= 0) { |
|||
sprintf(server->port, "%d", port); |
|||
} |
|||
return ret; |
|||
error: |
|||
if(fd > 0) close(fd); |
|||
return -1; |
|||
} |
|||
|
|||
/**
|
|||
* Stops the server. Will not accept new connections. Does not drop |
|||
* existing connections. |
|||
*/ |
|||
void |
|||
ebb_server_unlisten(ebb_server *server) |
|||
{ |
|||
if(server->listening) { |
|||
ev_io_stop(server->loop, &server->connection_watcher); |
|||
close(server->fd); |
|||
server->port[0] = '\0'; |
|||
server->listening = FALSE; |
|||
} |
|||
} |
|||
|
|||
/**
|
|||
* Initialize an ebb_server structure. After calling ebb_server_init set |
|||
* the callback server->new_connection and, optionally, callback data |
|||
* server->data. The new connection MUST be initialized with |
|||
* ebb_connection_init before returning it to the server. |
|||
* |
|||
* @param server the server to initialize |
|||
* @param loop a libev loop |
|||
*/ |
|||
void |
|||
ebb_server_init(ebb_server *server, struct ev_loop *loop) |
|||
{ |
|||
server->loop = loop; |
|||
server->listening = FALSE; |
|||
server->port[0] = '\0'; |
|||
server->fd = -1; |
|||
server->connection_watcher.data = server; |
|||
ev_init (&server->connection_watcher, on_connection); |
|||
server->secure = FALSE; |
|||
|
|||
#ifdef HAVE_GNUTLS |
|||
rbtree_init(&server->session_cache, session_cache_compare); |
|||
server->credentials = NULL; |
|||
#endif |
|||
|
|||
server->new_connection = NULL; |
|||
server->data = NULL; |
|||
} |
|||
|
|||
|
|||
#ifdef HAVE_GNUTLS |
|||
/* similar to server_init.
|
|||
* |
|||
* the user of secure server might want to set additional callbacks from |
|||
* GNUTLS. In particular |
|||
* gnutls_global_set_mem_functions() |
|||
* gnutls_global_set_log_function() |
|||
* Also see the note above ebb_connection_init() about setting gnutls cache |
|||
* access functions |
|||
* |
|||
* cert_file: the filename of a PEM certificate file |
|||
* |
|||
* key_file: the filename of a private key. Currently only PKCS-1 encoded |
|||
* RSA and DSA private keys are accepted. |
|||
*/ |
|||
int |
|||
ebb_server_set_secure (ebb_server *server, const char *cert_file, const char *key_file) |
|||
{ |
|||
server->secure = TRUE; |
|||
gnutls_global_init(); |
|||
gnutls_certificate_allocate_credentials(&server->credentials); |
|||
/* todo gnutls_certificate_free_credentials */ |
|||
int r = gnutls_certificate_set_x509_key_file( server->credentials |
|||
, cert_file |
|||
, key_file |
|||
, GNUTLS_X509_FMT_PEM |
|||
); |
|||
if(r < 0) { |
|||
error("loading certificates"); |
|||
return -1; |
|||
} |
|||
return 1; |
|||
} |
|||
#endif /* HAVE_GNUTLS */ |
|||
|
|||
/**
|
|||
* Initialize an ebb_connection structure. After calling this function you |
|||
* must setup callbacks for the different actions the server can take. See |
|||
* server.h for which callbacks are availible. |
|||
* |
|||
* This should be called immediately after allocating space for a new |
|||
* ebb_connection structure. Most likely, this will only be called within |
|||
* the ebb_server->new_connection callback which you supply. |
|||
* |
|||
* If using SSL do consider setting |
|||
* gnutls_db_set_retrieve_function (connection->session, _); |
|||
* gnutls_db_set_remove_function (connection->session, _); |
|||
* gnutls_db_set_store_function (connection->session, _); |
|||
* gnutls_db_set_ptr (connection->session, _); |
|||
* To provide a better means of storing SSL session caches. libebb provides |
|||
* only a simple default implementation. |
|||
* |
|||
* @param connection the connection to initialize |
|||
* @param timeout the timeout in seconds |
|||
*/ |
|||
void |
|||
ebb_connection_init(ebb_connection *connection) |
|||
{ |
|||
connection->fd = -1; |
|||
connection->server = NULL; |
|||
connection->ip = NULL; |
|||
connection->open = FALSE; |
|||
|
|||
ebb_request_parser_init( &connection->parser ); |
|||
connection->parser.data = connection; |
|||
connection->parser.new_request = new_request_wrapper; |
|||
|
|||
ev_init (&connection->write_watcher, on_writable); |
|||
connection->write_watcher.data = connection; |
|||
connection->to_write = NULL; |
|||
|
|||
ev_init(&connection->read_watcher, on_readable); |
|||
connection->read_watcher.data = connection; |
|||
|
|||
#ifdef HAVE_GNUTLS |
|||
connection->handshake_watcher.data = connection; |
|||
ev_init(&connection->handshake_watcher, on_handshake); |
|||
|
|||
ev_init(&connection->goodbye_tls_watcher, on_goodbye_tls); |
|||
connection->goodbye_tls_watcher.data = connection; |
|||
|
|||
connection->session = NULL; |
|||
#endif /* HAVE_GNUTLS */ |
|||
|
|||
ev_timer_init(&connection->goodbye_watcher, on_goodbye, 0., 0.); |
|||
connection->goodbye_watcher.data = connection; |
|||
|
|||
ev_timer_init(&connection->timeout_watcher, on_timeout, 0., EBB_DEFAULT_TIMEOUT); |
|||
connection->timeout_watcher.data = connection; |
|||
|
|||
connection->new_request = NULL; |
|||
connection->on_timeout = NULL; |
|||
connection->on_close = NULL; |
|||
connection->data = NULL; |
|||
} |
|||
|
|||
void |
|||
ebb_connection_schedule_close (ebb_connection *connection) |
|||
{ |
|||
#ifdef HAVE_GNUTLS |
|||
if(connection->server->secure) { |
|||
ev_io_set(&connection->goodbye_tls_watcher, connection->fd, EV_READ | EV_WRITE); |
|||
ev_io_start(connection->server->loop, &connection->goodbye_tls_watcher); |
|||
return; |
|||
} |
|||
#endif |
|||
ev_timer_start(connection->server->loop, &connection->goodbye_watcher); |
|||
} |
|||
|
|||
/*
|
|||
* Resets the timeout to stay alive for another connection->timeout seconds |
|||
*/ |
|||
void |
|||
ebb_connection_reset_timeout(ebb_connection *connection) |
|||
{ |
|||
ev_timer_again(connection->server->loop, &connection->timeout_watcher); |
|||
} |
|||
|
|||
/**
|
|||
* Writes a string to the socket. This is actually sets a watcher |
|||
* which may take multiple iterations to write the entire string. |
|||
* |
|||
* This can only be called once at a time. If you call it again |
|||
* while the connection is writing another buffer the ebb_connection_write |
|||
* will return FALSE and ignore the request. |
|||
*/ |
|||
int |
|||
ebb_connection_write (ebb_connection *connection, const char *buf, size_t len, ebb_after_write_cb cb) |
|||
{ |
|||
if(ev_is_active(&connection->write_watcher)) |
|||
return FALSE; |
|||
assert(!CONNECTION_HAS_SOMETHING_TO_WRITE); |
|||
connection->to_write = buf; |
|||
connection->to_write_len = len; |
|||
connection->written = 0; |
|||
connection->after_write_cb = cb; |
|||
ev_io_start(connection->server->loop, &connection->write_watcher); |
|||
return TRUE; |
|||
} |
|||
|
@ -0,0 +1,120 @@ |
|||
/* This file is part of libebb.
|
|||
* |
|||
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) |
|||
* All rights reserved. |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining |
|||
* a copy of this software and associated documentation files (the |
|||
* "Software"), to deal in the Software without restriction, including |
|||
* without limitation the rights to use, copy, modify, merge, publish, |
|||
* distribute, sublicense, and/or sell copies of the Software, and to |
|||
* permit persons to whom the Software is furnished to do so, subject to |
|||
* the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be |
|||
* included in all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
*/ |
|||
#ifndef EBB_H |
|||
#define EBB_H |
|||
|
|||
#include <sys/socket.h> |
|||
#include <netinet/in.h> |
|||
#include <ev.h> |
|||
#ifdef HAVE_GNUTLS |
|||
# include <gnutls/gnutls.h> |
|||
# include "rbtree.h" /* for ebb_server.session_cache */ |
|||
#endif |
|||
#include "ebb_request_parser.h" |
|||
|
|||
#define EBB_MAX_CONNECTIONS 1024 |
|||
#define EBB_DEFAULT_TIMEOUT 30.0 |
|||
|
|||
#define EBB_AGAIN 0 |
|||
#define EBB_STOP 1 |
|||
|
|||
typedef struct ebb_server ebb_server; |
|||
typedef struct ebb_connection ebb_connection; |
|||
typedef void (*ebb_after_write_cb) (ebb_connection *connection); |
|||
typedef void (*ebb_connection_cb)(ebb_connection *connection, void *data); |
|||
|
|||
struct ebb_server { |
|||
int fd; /* ro */ |
|||
struct sockaddr_in sockaddr; /* ro */ |
|||
socklen_t socklen; /* ro */ |
|||
char port[6]; /* ro */ |
|||
struct ev_loop *loop; /* ro */ |
|||
unsigned listening:1; /* ro */ |
|||
unsigned secure:1; /* ro */ |
|||
#ifdef HAVE_GNUTLS |
|||
gnutls_certificate_credentials_t credentials; /* private */ |
|||
struct rbtree_t session_cache; /* private */ |
|||
#endif |
|||
ev_io connection_watcher; /* private */ |
|||
|
|||
/* Public */ |
|||
|
|||
/* Allocates and initializes an ebb_connection. NULL by default. */ |
|||
ebb_connection* (*new_connection) (ebb_server*, struct sockaddr_in*); |
|||
|
|||
void *data; |
|||
}; |
|||
|
|||
struct ebb_connection { |
|||
int fd; /* ro */ |
|||
struct sockaddr_in sockaddr; /* ro */ |
|||
socklen_t socklen; /* ro */ |
|||
ebb_server *server; /* ro */ |
|||
char *ip; /* ro */ |
|||
unsigned open:1; /* ro */ |
|||
|
|||
const char *to_write; /* ro */ |
|||
size_t to_write_len; /* ro */ |
|||
size_t written; /* ro */ |
|||
ebb_after_write_cb after_write_cb; /* ro */ |
|||
|
|||
ebb_request_parser parser; /* private */ |
|||
ev_io write_watcher; /* private */ |
|||
ev_io read_watcher; /* private */ |
|||
ev_timer timeout_watcher; /* private */ |
|||
ev_timer goodbye_watcher; /* private */ |
|||
#ifdef HAVE_GNUTLS |
|||
ev_io handshake_watcher; /* private */ |
|||
gnutls_session_t session; /* private */ |
|||
ev_io goodbye_tls_watcher; /* private */ |
|||
#endif |
|||
|
|||
/* Public */ |
|||
|
|||
ebb_request* (*new_request) (ebb_connection*); |
|||
|
|||
/* Returns EBB_STOP or EBB_AGAIN. NULL by default. */ |
|||
int (*on_timeout) (ebb_connection*); |
|||
|
|||
void (*on_close) (ebb_connection*); |
|||
|
|||
void *data; |
|||
}; |
|||
|
|||
void ebb_server_init (ebb_server *server, struct ev_loop *loop); |
|||
#ifdef HAVE_GNUTLS |
|||
int ebb_server_set_secure (ebb_server *server, const char *cert_file, |
|||
const char *key_file); |
|||
#endif |
|||
int ebb_server_listen_on_port (ebb_server *server, const int port); |
|||
int ebb_server_listen_on_fd (ebb_server *server, const int sfd); |
|||
void ebb_server_unlisten (ebb_server *server); |
|||
|
|||
void ebb_connection_init (ebb_connection *); |
|||
void ebb_connection_schedule_close (ebb_connection *); |
|||
void ebb_connection_reset_timeout (ebb_connection *); |
|||
int ebb_connection_write (ebb_connection *, const char *buf, size_t len, ebb_after_write_cb); |
|||
|
|||
#endif |
@ -0,0 +1,117 @@ |
|||
/* This file is part of the libebb web server library
|
|||
* |
|||
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) |
|||
* All rights reserved. |
|||
* |
|||
* This parser is based on code from Zed Shaw's Mongrel. |
|||
* Copyright (c) 2005 Zed A. Shaw |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining |
|||
* a copy of this software and associated documentation files (the |
|||
* "Software"), to deal in the Software without restriction, including |
|||
* without limitation the rights to use, copy, modify, merge, publish, |
|||
* distribute, sublicense, and/or sell copies of the Software, and to |
|||
* permit persons to whom the Software is furnished to do so, subject to |
|||
* the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be |
|||
* included in all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
*/ |
|||
#ifndef ebb_request_parser_h |
|||
#define ebb_request_parser_h |
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
|
|||
#include <sys/types.h> |
|||
|
|||
typedef struct ebb_request ebb_request; |
|||
typedef struct ebb_request_parser ebb_request_parser; |
|||
typedef void (*ebb_header_cb)(ebb_request*, const char *at, size_t length, int header_index); |
|||
typedef void (*ebb_element_cb)(ebb_request*, const char *at, size_t length); |
|||
|
|||
#define EBB_MAX_MULTIPART_BOUNDARY_LEN 20 |
|||
|
|||
/* HTTP Methods */ |
|||
#define EBB_COPY 0x00000001 |
|||
#define EBB_DELETE 0x00000002 |
|||
#define EBB_GET 0x00000004 |
|||
#define EBB_HEAD 0x00000008 |
|||
#define EBB_LOCK 0x00000010 |
|||
#define EBB_MKCOL 0x00000020 |
|||
#define EBB_MOVE 0x00000040 |
|||
#define EBB_OPTIONS 0x00000080 |
|||
#define EBB_POST 0x00000100 |
|||
#define EBB_PROPFIND 0x00000200 |
|||
#define EBB_PROPPATCH 0x00000400 |
|||
#define EBB_PUT 0x00000800 |
|||
#define EBB_TRACE 0x00001000 |
|||
#define EBB_UNLOCK 0x00002000 |
|||
|
|||
/* Transfer Encodings */ |
|||
#define EBB_IDENTITY 0x00000001 |
|||
#define EBB_CHUNKED 0x00000002 |
|||
|
|||
struct ebb_request { |
|||
int method; |
|||
int transfer_encoding; /* ro */ |
|||
int expect_continue; /* ro */ |
|||
unsigned int version_major; /* ro */ |
|||
unsigned int version_minor; /* ro */ |
|||
int number_of_headers; /* ro */ |
|||
int keep_alive; /* private - use ebb_request_should_keep_alive */ |
|||
size_t content_length; /* ro - 0 if unknown */ |
|||
size_t body_read; /* ro */ |
|||
|
|||
/* Public - ordered list of callbacks */ |
|||
ebb_element_cb on_path; |
|||
ebb_element_cb on_query_string; |
|||
ebb_element_cb on_uri; |
|||
ebb_element_cb on_fragment; |
|||
ebb_header_cb on_header_field; |
|||
ebb_header_cb on_header_value; |
|||
void (*on_headers_complete)(ebb_request *); |
|||
ebb_element_cb on_body; |
|||
void (*on_complete)(ebb_request *); |
|||
void *data; |
|||
}; |
|||
|
|||
struct ebb_request_parser { |
|||
int cs; /* private */ |
|||
size_t chunk_size; /* private */ |
|||
unsigned eating:1; /* private */ |
|||
ebb_request *current_request; /* ro */ |
|||
const char *header_field_mark; |
|||
const char *header_value_mark; |
|||
const char *query_string_mark; |
|||
const char *path_mark; |
|||
const char *uri_mark; |
|||
const char *fragment_mark; |
|||
|
|||
/* Public */ |
|||
ebb_request* (*new_request)(void*); |
|||
void *data; |
|||
}; |
|||
|
|||
void ebb_request_parser_init(ebb_request_parser *parser); |
|||
size_t ebb_request_parser_execute(ebb_request_parser *parser, const char *data, size_t len); |
|||
int ebb_request_parser_has_error(ebb_request_parser *parser); |
|||
int ebb_request_parser_is_finished(ebb_request_parser *parser); |
|||
void ebb_request_init(ebb_request *); |
|||
int ebb_request_should_keep_alive(ebb_request *request); |
|||
#define ebb_request_has_body(request) \ |
|||
(request->transfer_encoding == EBB_CHUNKED || request->content_length > 0 ) |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
|||
#endif |
@ -0,0 +1,413 @@ |
|||
/* This file is part of the libebb web server library |
|||
* |
|||
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) |
|||
* All rights reserved. |
|||
* |
|||
* This parser is based on code from Zed Shaw's Mongrel. |
|||
* Copyright (c) 2005 Zed A. Shaw |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining |
|||
* a copy of this software and associated documentation files (the |
|||
* "Software"), to deal in the Software without restriction, including |
|||
* without limitation the rights to use, copy, modify, merge, publish, |
|||
* distribute, sublicense, and/or sell copies of the Software, and to |
|||
* permit persons to whom the Software is furnished to do so, subject to |
|||
* the following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be |
|||
* included in all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
*/ |
|||
#include "ebb_request_parser.h" |
|||
|
|||
#include <stdio.h> |
|||
#include <assert.h> |
|||
|
|||
static int unhex[] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 |
|||
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 |
|||
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 |
|||
, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 |
|||
,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 |
|||
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 |
|||
,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 |
|||
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 |
|||
}; |
|||
#define TRUE 1 |
|||
#define FALSE 0 |
|||
#define MIN(a,b) (a < b ? a : b) |
|||
|
|||
#define REMAINING (pe - p) |
|||
#define CURRENT (parser->current_request) |
|||
#define CONTENT_LENGTH (parser->current_request->content_length) |
|||
#define CALLBACK(FOR) \ |
|||
if(parser->FOR##_mark && CURRENT->on_##FOR) { \ |
|||
CURRENT->on_##FOR( CURRENT \ |
|||
, parser->FOR##_mark \ |
|||
, p - parser->FOR##_mark \ |
|||
); \ |
|||
} |
|||
#define HEADER_CALLBACK(FOR) \ |
|||
if(parser->FOR##_mark && CURRENT->on_##FOR) { \ |
|||
CURRENT->on_##FOR( CURRENT \ |
|||
, parser->FOR##_mark \ |
|||
, p - parser->FOR##_mark \ |
|||
, CURRENT->number_of_headers \ |
|||
); \ |
|||
} |
|||
#define END_REQUEST \ |
|||
if(CURRENT->on_complete) \ |
|||
CURRENT->on_complete(CURRENT); \ |
|||
CURRENT = NULL; |
|||
|
|||
|
|||
%%{ |
|||
machine ebb_request_parser; |
|||
|
|||
action mark_header_field { parser->header_field_mark = p; } |
|||
action mark_header_value { parser->header_value_mark = p; } |
|||
action mark_fragment { parser->fragment_mark = p; } |
|||
action mark_query_string { parser->query_string_mark = p; } |
|||
action mark_request_path { parser->path_mark = p; } |
|||
action mark_request_uri { parser->uri_mark = p; } |
|||
|
|||
action write_field { |
|||
HEADER_CALLBACK(header_field); |
|||
parser->header_field_mark = NULL; |
|||
} |
|||
|
|||
action write_value { |
|||
HEADER_CALLBACK(header_value); |
|||
parser->header_value_mark = NULL; |
|||
} |
|||
|
|||
action request_uri { |
|||
CALLBACK(uri); |
|||
parser->uri_mark = NULL; |
|||
} |
|||
|
|||
action fragment { |
|||
CALLBACK(fragment); |
|||
parser->fragment_mark = NULL; |
|||
} |
|||
|
|||
action query_string { |
|||
CALLBACK(query_string); |
|||
parser->query_string_mark = NULL; |
|||
} |
|||
|
|||
action request_path { |
|||
CALLBACK(path); |
|||
parser->path_mark = NULL; |
|||
} |
|||
|
|||
action content_length { |
|||
CURRENT->content_length *= 10; |
|||
CURRENT->content_length += *p - '0'; |
|||
} |
|||
|
|||
action use_identity_encoding { CURRENT->transfer_encoding = EBB_IDENTITY; } |
|||
action use_chunked_encoding { CURRENT->transfer_encoding = EBB_CHUNKED; } |
|||
|
|||
action set_keep_alive { CURRENT->keep_alive = TRUE; } |
|||
action set_not_keep_alive { CURRENT->keep_alive = FALSE; } |
|||
|
|||
action expect_continue { |
|||
CURRENT->expect_continue = TRUE; |
|||
} |
|||
|
|||
action trailer { |
|||
/* not implemenetd yet. (do requests even have trailing headers?) */ |
|||
} |
|||
|
|||
action version_major { |
|||
CURRENT->version_major *= 10; |
|||
CURRENT->version_major += *p - '0'; |
|||
} |
|||
|
|||
action version_minor { |
|||
CURRENT->version_minor *= 10; |
|||
CURRENT->version_minor += *p - '0'; |
|||
} |
|||
|
|||
action end_header_line { |
|||
CURRENT->number_of_headers++; |
|||
} |
|||
|
|||
action end_headers { |
|||
if(CURRENT->on_headers_complete) |
|||
CURRENT->on_headers_complete(CURRENT); |
|||
} |
|||
|
|||
action add_to_chunk_size { |
|||
parser->chunk_size *= 16; |
|||
parser->chunk_size += unhex[(int)*p]; |
|||
} |
|||
|
|||
action skip_chunk_data { |
|||
skip_body(&p, parser, MIN(parser->chunk_size, REMAINING)); |
|||
fhold; |
|||
if(parser->chunk_size > REMAINING) { |
|||
fbreak; |
|||
} else { |
|||
fgoto chunk_end; |
|||
} |
|||
} |
|||
|
|||
action end_chunked_body { |
|||
END_REQUEST; |
|||
fnext main; |
|||
} |
|||
|
|||
action start_req { |
|||
assert(CURRENT == NULL); |
|||
CURRENT = parser->new_request(parser->data); |
|||
} |
|||
|
|||
action body_logic { |
|||
if(CURRENT->transfer_encoding == EBB_CHUNKED) { |
|||
fnext ChunkedBody; |
|||
} else { |
|||
/* this is pretty stupid. i'd prefer to combine this with skip_chunk_data */ |
|||
parser->chunk_size = CURRENT->content_length; |
|||
p += 1; |
|||
skip_body(&p, parser, MIN(REMAINING, CURRENT->content_length)); |
|||
fhold; |
|||
if(parser->chunk_size > REMAINING) { |
|||
fbreak; |
|||
} |
|||
} |
|||
} |
|||
|
|||
# |
|||
## |
|||
### |
|||
#### HTTP/1.1 STATE MACHINE |
|||
### |
|||
## RequestHeaders and character types are from |
|||
# Zed Shaw's beautiful Mongrel parser. |
|||
|
|||
CRLF = "\r\n"; |
|||
|
|||
# character types |
|||
CTL = (cntrl | 127); |
|||
safe = ("$" | "-" | "_" | "."); |
|||
extra = ("!" | "*" | "'" | "(" | ")" | ","); |
|||
reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"); |
|||
unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">"); |
|||
national = any -- (alpha | digit | reserved | extra | safe | unsafe); |
|||
unreserved = (alpha | digit | safe | extra | national); |
|||
escape = ("%" xdigit xdigit); |
|||
uchar = (unreserved | escape); |
|||
pchar = (uchar | ":" | "@" | "&" | "=" | "+"); |
|||
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t"); |
|||
|
|||
# elements |
|||
token = (ascii -- (CTL | tspecials)); |
|||
quote = "\""; |
|||
# qdtext = token -- "\""; |
|||
# quoted_pair = "\" ascii; |
|||
# quoted_string = "\"" (qdtext | quoted_pair )* "\""; |
|||
|
|||
# headers |
|||
|
|||
Method = ( "COPY" %{ CURRENT->method = EBB_COPY; } |
|||
| "DELETE" %{ CURRENT->method = EBB_DELETE; } |
|||
| "GET" %{ CURRENT->method = EBB_GET; } |
|||
| "HEAD" %{ CURRENT->method = EBB_HEAD; } |
|||
| "LOCK" %{ CURRENT->method = EBB_LOCK; } |
|||
| "MKCOL" %{ CURRENT->method = EBB_MKCOL; } |
|||
| "MOVE" %{ CURRENT->method = EBB_MOVE; } |
|||
| "OPTIONS" %{ CURRENT->method = EBB_OPTIONS; } |
|||
| "POST" %{ CURRENT->method = EBB_POST; } |
|||
| "PROPFIND" %{ CURRENT->method = EBB_PROPFIND; } |
|||
| "PROPPATCH" %{ CURRENT->method = EBB_PROPPATCH; } |
|||
| "PUT" %{ CURRENT->method = EBB_PUT; } |
|||
| "TRACE" %{ CURRENT->method = EBB_TRACE; } |
|||
| "UNLOCK" %{ CURRENT->method = EBB_UNLOCK; } |
|||
); # Not allowing extension methods |
|||
|
|||
HTTP_Version = "HTTP/" digit+ $version_major "." digit+ $version_minor; |
|||
|
|||
scheme = ( alpha | digit | "+" | "-" | "." )* ; |
|||
absolute_uri = (scheme ":" (uchar | reserved )*); |
|||
path = ( pchar+ ( "/" pchar* )* ) ; |
|||
query = ( uchar | reserved )* >mark_query_string %query_string ; |
|||
param = ( pchar | "/" )* ; |
|||
params = ( param ( ";" param )* ) ; |
|||
rel_path = ( path? (";" params)? ) ; |
|||
absolute_path = ( "/"+ rel_path ) >mark_request_path %request_path ("?" query)?; |
|||
Request_URI = ( "*" | absolute_uri | absolute_path ) >mark_request_uri %request_uri; |
|||
Fragment = ( uchar | reserved )* >mark_fragment %fragment; |
|||
|
|||
field_name = ( token -- ":" )+; |
|||
Field_Name = field_name >mark_header_field %write_field; |
|||
|
|||
field_value = ((any - " ") any*)?; |
|||
Field_Value = field_value >mark_header_value %write_value; |
|||
|
|||
hsep = ":" " "*; |
|||
header = (field_name hsep field_value) :> CRLF; |
|||
Header = ( ("Content-Length"i hsep digit+ $content_length) |
|||
| ("Connection"i hsep |
|||
( "Keep-Alive"i %set_keep_alive |
|||
| "close"i %set_not_keep_alive |
|||
) |
|||
) |
|||
| ("Transfer-Encoding"i %use_chunked_encoding hsep "identity" %use_identity_encoding) |
|||
# | ("Expect"i hsep "100-continue"i %expect_continue) |
|||
# | ("Trailer"i hsep field_value %trailer) |
|||
| (Field_Name hsep Field_Value) |
|||
) :> CRLF; |
|||
|
|||
Request_Line = ( Method " " Request_URI ("#" Fragment)? " " HTTP_Version CRLF ) ; |
|||
RequestHeader = Request_Line (Header %end_header_line)* :> CRLF @end_headers; |
|||
|
|||
# chunked message |
|||
trailing_headers = header*; |
|||
#chunk_ext_val = token | quoted_string; |
|||
chunk_ext_val = token*; |
|||
chunk_ext_name = token*; |
|||
chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*; |
|||
last_chunk = "0"+ chunk_extension CRLF; |
|||
chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size; |
|||
chunk_end = CRLF; |
|||
chunk_body = any >skip_chunk_data; |
|||
chunk_begin = chunk_size chunk_extension CRLF; |
|||
chunk = chunk_begin chunk_body chunk_end; |
|||
ChunkedBody := chunk* last_chunk trailing_headers CRLF @end_chunked_body; |
|||
|
|||
Request = RequestHeader >start_req @body_logic; |
|||
|
|||
main := Request*; # sequence of requests (for keep-alive) |
|||
}%% |
|||
|
|||
%% write data; |
|||
|
|||
static void |
|||
skip_body(const char **p, ebb_request_parser *parser, size_t nskip) { |
|||
if(CURRENT->on_body && nskip > 0) { |
|||
CURRENT->on_body(CURRENT, *p, nskip); |
|||
} |
|||
CURRENT->body_read += nskip; |
|||
parser->chunk_size -= nskip; |
|||
*p += nskip; |
|||
if(0 == parser->chunk_size) { |
|||
parser->eating = FALSE; |
|||
if(CURRENT->transfer_encoding == EBB_IDENTITY) { |
|||
END_REQUEST; |
|||
} |
|||
} else { |
|||
parser->eating = TRUE; |
|||
} |
|||
} |
|||
|
|||
void ebb_request_parser_init(ebb_request_parser *parser) |
|||
{ |
|||
int cs = 0; |
|||
%% write init; |
|||
parser->cs = cs; |
|||
|
|||
parser->chunk_size = 0; |
|||
parser->eating = 0; |
|||
|
|||
parser->current_request = NULL; |
|||
|
|||
parser->header_field_mark = parser->header_value_mark = |
|||
parser->query_string_mark = parser->path_mark = |
|||
parser->uri_mark = parser->fragment_mark = NULL; |
|||
|
|||
parser->new_request = NULL; |
|||
} |
|||
|
|||
|
|||
/** exec **/ |
|||
size_t ebb_request_parser_execute(ebb_request_parser *parser, const char *buffer, size_t len) |
|||
{ |
|||
const char *p, *pe; |
|||
int cs = parser->cs; |
|||
|
|||
assert(parser->new_request && "undefined callback"); |
|||
|
|||
p = buffer; |
|||
pe = buffer+len; |
|||
|
|||
if(0 < parser->chunk_size && parser->eating) { |
|||
/* eat body */ |
|||
size_t eat = MIN(len, parser->chunk_size); |
|||
skip_body(&p, parser, eat); |
|||
} |
|||
|
|||
if(parser->header_field_mark) parser->header_field_mark = buffer; |
|||
if(parser->header_value_mark) parser->header_value_mark = buffer; |
|||
if(parser->fragment_mark) parser->fragment_mark = buffer; |
|||
if(parser->query_string_mark) parser->query_string_mark = buffer; |
|||
if(parser->path_mark) parser->path_mark = buffer; |
|||
if(parser->uri_mark) parser->uri_mark = buffer; |
|||
|
|||
%% write exec; |
|||
|
|||
parser->cs = cs; |
|||
|
|||
HEADER_CALLBACK(header_field); |
|||
HEADER_CALLBACK(header_value); |
|||
CALLBACK(fragment); |
|||
CALLBACK(query_string); |
|||
CALLBACK(path); |
|||
CALLBACK(uri); |
|||
|
|||
assert(p <= pe && "buffer overflow after parsing execute"); |
|||
|
|||
return(p - buffer); |
|||
} |
|||
|
|||
int ebb_request_parser_has_error(ebb_request_parser *parser) |
|||
{ |
|||
return parser->cs == ebb_request_parser_error; |
|||
} |
|||
|
|||
int ebb_request_parser_is_finished(ebb_request_parser *parser) |
|||
{ |
|||
return parser->cs == ebb_request_parser_first_final; |
|||
} |
|||
|
|||
void ebb_request_init(ebb_request *request) |
|||
{ |
|||
request->expect_continue = FALSE; |
|||
request->body_read = 0; |
|||
request->content_length = 0; |
|||
request->version_major = 0; |
|||
request->version_minor = 0; |
|||
request->number_of_headers = 0; |
|||
request->transfer_encoding = EBB_IDENTITY; |
|||
request->keep_alive = -1; |
|||
|
|||
request->on_complete = NULL; |
|||
request->on_headers_complete = NULL; |
|||
request->on_body = NULL; |
|||
request->on_header_field = NULL; |
|||
request->on_header_value = NULL; |
|||
request->on_uri = NULL; |
|||
request->on_fragment = NULL; |
|||
request->on_path = NULL; |
|||
request->on_query_string = NULL; |
|||
} |
|||
|
|||
int ebb_request_should_keep_alive(ebb_request *request) |
|||
{ |
|||
if(request->keep_alive == -1) |
|||
if(request->version_major == 1) |
|||
return (request->version_minor != 0); |
|||
else if(request->version_major == 0) |
|||
return FALSE; |
|||
else |
|||
return TRUE; |
|||
else |
|||
return request->keep_alive; |
|||
} |
@ -0,0 +1,12 @@ |
|||
-----BEGIN CERTIFICATE----- |
|||
MIIBzDCCATegAwIBAgIESIuNVTALBgkqhkiG9w0BAQUwADAeFw0wODA3MjYyMDQ3 |
|||
MTlaFw0xMTA0MjIyMDQ3MjVaMAAwgZwwCwYJKoZIhvcNAQEBA4GMADCBiAKBgOm9 |
|||
l/FoXbTIcEusk/QlS5YrlR04+oWIbSdZIf3GJBEWEUPljDxAX96qHsTcaVnGK+EP |
|||
keU4cIZvdY+hzbqa5cc1j2/9IeJNejL8gpQ/ocyMM69yq5Ib2F8K4mGWm1xr30hU |
|||
bYpY5D0MrZ1b0HtYFVc8KVAr0ADGG+pye0P9c3B/AgMBAAGjWjBYMAwGA1UdEwEB |
|||
/wQCMAAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MBMGA1UdJQQMMAoGCCsGAQUFBwMB |
|||
MB0GA1UdDgQWBBTYgVB7kJnnm+jgX9DgrapzGfUmxjALBgkqhkiG9w0BAQUDgYEA |
|||
GkadA2H8CAzU3w4oCGZu9Ry9Tj/9Agw1XMFKvoJuG7VLPk7+B25JvNFVsmpROLxO |
|||
0TJ6mIU2hz5/rLvEfTBGQ+DYtbsjIxCz1fD7R5c1kKBtA0d0u8mY8pTlPNlxFPSW |
|||
3ymx5DB2zyDa/HuX6m6/VmzMYmA0vp7Dp1cl+pA9Nhs= |
|||
-----END CERTIFICATE----- |
@ -0,0 +1,15 @@ |
|||
-----BEGIN RSA PRIVATE KEY----- |
|||
MIICXQIBAAKBgQDpvZfxaF20yHBLrJP0JUuWK5UdOPqFiG0nWSH9xiQRFhFD5Yw8 |
|||
QF/eqh7E3GlZxivhD5HlOHCGb3WPoc26muXHNY9v/SHiTXoy/IKUP6HMjDOvcquS |
|||
G9hfCuJhlptca99IVG2KWOQ9DK2dW9B7WBVXPClQK9AAxhvqcntD/XNwfwIDAQAB |
|||
AoGAJqo3LTbfcV1KvinhG5zjwQaalwfq4RXtQHoNFmalZrIozvt01C6t7S5lApmX |
|||
T8NpVMR3lNxeOM7NOqJAXuLqqVVqk81YEYuMx6E4gB/Ifl7jVZk1jstmLILhh59D |
|||
pXrlpzvvm5X2hVsI7lp/YGAvtdLS1iVy37bGgmQWfCeeZiECQQDtZLfcJb4oE1yR |
|||
ZfLOcPDlBCw02wGMNFpAjwbspf/du3Yn3ONWHVfhSCCcCe262h9PLblL97LoB+gF |
|||
OHOlM9JvAkEA/A+U3/p9pwL4L742pxZP62/rmk6p5mZFIykk2jIUTpdilXBWBlcT |
|||
OjgjnpquZpwnaClmvgFpkzdhIPF7Nq4K8QJBAITVKagOmnOUOeTF1fI78h9DkXTV |
|||
4uzP0nxzS52ZWS16Gqg9ihuCecz97flB+Prn2EMWw6tFY58/5U0ehF85OxMCQQDF |
|||
08TYdVSg+6emcPeb89sNwW18UjjuZ13j1qrhxWRCunXZK62YlEa27tCl7mjqh6w2 |
|||
CChm/9zIejJ1FJHLvJVBAkBj63ZbwggMYkxuj60jIBbNrEtDx9y7zM0sXkiJqcKp |
|||
5uGtJNafG+yZrLAHE6/b4aqUOtGsCGsiZpT9ms7CoaVr |
|||
-----END RSA PRIVATE KEY----- |
@ -0,0 +1,101 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <unistd.h> |
|||
#include <assert.h> |
|||
|
|||
#include <ev.h> |
|||
#include "ebb.h" |
|||
|
|||
#define MSG ("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nhello world\n") |
|||
static int c = 0; |
|||
|
|||
struct hello_connection { |
|||
unsigned int responses_to_write; |
|||
}; |
|||
|
|||
void on_close(ebb_connection *connection) |
|||
{ |
|||
free(connection->data); |
|||
free(connection); |
|||
} |
|||
|
|||
static void continue_responding(ebb_connection *connection) |
|||
{ |
|||
int r; |
|||
struct hello_connection *connection_data = connection->data; |
|||
//printf("response complete \n");
|
|||
if(--connection_data->responses_to_write > 0) { |
|||
/* write another response */ |
|||
r = ebb_connection_write(connection, MSG, sizeof MSG, continue_responding); |
|||
assert(r); |
|||
} else { |
|||
ebb_connection_schedule_close(connection); |
|||
} |
|||
} |
|||
|
|||
static void request_complete(ebb_request *request) |
|||
{ |
|||
//printf("request complete \n");
|
|||
ebb_connection *connection = request->data; |
|||
struct hello_connection *connection_data = connection->data; |
|||
|
|||
if(ebb_request_should_keep_alive(request)) |
|||
connection_data->responses_to_write++; |
|||
else |
|||
connection_data->responses_to_write = 1; |
|||
|
|||
ebb_connection_write(connection, MSG, sizeof MSG, continue_responding); |
|||
free(request); |
|||
} |
|||
|
|||
static ebb_request* new_request(ebb_connection *connection) |
|||
{ |
|||
//printf("request %d\n", ++c);
|
|||
ebb_request *request = malloc(sizeof(ebb_request)); |
|||
ebb_request_init(request); |
|||
request->data = connection; |
|||
request->on_complete = request_complete; |
|||
return request; |
|||
} |
|||
|
|||
ebb_connection* new_connection(ebb_server *server, struct sockaddr_in *addr) |
|||
{ |
|||
struct hello_connection *connection_data = malloc(sizeof(struct hello_connection)); |
|||
if(connection_data == NULL) |
|||
return NULL; |
|||
connection_data->responses_to_write = 0; |
|||
|
|||
ebb_connection *connection = malloc(sizeof(ebb_connection)); |
|||
if(connection == NULL) { |
|||
free(connection_data); |
|||
return NULL; |
|||
} |
|||
|
|||
ebb_connection_init(connection); |
|||
connection->data = connection_data; |
|||
connection->new_request = new_request; |
|||
connection->on_close = on_close; |
|||
|
|||
printf("connection: %d\n", c++); |
|||
return connection; |
|||
} |
|||
|
|||
int main(int argc, char **_) |
|||
{ |
|||
struct ev_loop *loop = ev_default_loop(0); |
|||
ebb_server server; |
|||
|
|||
ebb_server_init(&server, loop); |
|||
if(argc > 1) { |
|||
printf("using SSL\n"); |
|||
ebb_server_set_secure(&server, "ca-cert.pem", "ca-key.pem"); |
|||
} |
|||
server.new_connection = new_connection; |
|||
|
|||
printf("hello_world listening on port 5000\n"); |
|||
ebb_server_listen_on_port(&server, 5000); |
|||
ev_loop(loop, 0); |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,412 @@ |
|||
/* Copyright (c) 2008 Derrick Coetzee
|
|||
* http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982
|
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining a |
|||
* copy of this software and associated documentation files (the |
|||
* "Software"), to deal in the Software without restriction, including |
|||
* without limitation the rights to use, copy, modify, merge, publish, |
|||
* distribute, sublicense, and/or sell copies of the Software, and to permit |
|||
* persons to whom the Software is furnished to do so, subject to the |
|||
* following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be included |
|||
* in all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT |
|||
* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR |
|||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
*/ |
|||
|
|||
#include "rbtree.h" |
|||
#include <assert.h> |
|||
|
|||
#ifndef NULL |
|||
# define NULL ((void*)0) |
|||
#endif |
|||
|
|||
|
|||
typedef rbtree_node node; |
|||
typedef enum rbtree_node_color color; |
|||
|
|||
|
|||
static node grandparent(node n); |
|||
static node sibling(node n); |
|||
static node uncle(node n); |
|||
static void verify_properties(rbtree t); |
|||
static void verify_property_1(node root); |
|||
/* static void verify_property_2(node root); */ |
|||
static color node_color(node n); |
|||
static void verify_property_4(node root); |
|||
/* static void verify_property_5(node root); */ |
|||
static void verify_property_5_helper(node n, int black_count, int* black_count_path); |
|||
|
|||
static node lookup_node(rbtree t, void* key); |
|||
static void rotate_left(rbtree t, node n); |
|||
static void rotate_right(rbtree t, node n); |
|||
|
|||
static void replace_node(rbtree t, node oldn, node newn); |
|||
static void insert_case1(rbtree t, node n); |
|||
static void insert_case2(rbtree t, node n); |
|||
static void insert_case3(rbtree t, node n); |
|||
static void insert_case4(rbtree t, node n); |
|||
static void insert_case5(rbtree t, node n); |
|||
static node maximum_node(node root); |
|||
static void delete_case1(rbtree t, node n); |
|||
static void delete_case2(rbtree t, node n); |
|||
static void delete_case3(rbtree t, node n); |
|||
static void delete_case4(rbtree t, node n); |
|||
static void delete_case5(rbtree t, node n); |
|||
static void delete_case6(rbtree t, node n); |
|||
|
|||
node grandparent(node n) { |
|||
assert (n != NULL); |
|||
assert (n->parent != NULL); /* Not the root node */ |
|||
assert (n->parent->parent != NULL); /* Not child of root */ |
|||
return n->parent->parent; |
|||
} |
|||
|
|||
node sibling(node n) { |
|||
assert (n != NULL); |
|||
assert (n->parent != NULL); /* Root node has no sibling */ |
|||
if (n == n->parent->left) |
|||
return n->parent->right; |
|||
else |
|||
return n->parent->left; |
|||
} |
|||
|
|||
node uncle(node n) { |
|||
assert (n != NULL); |
|||
assert (n->parent != NULL); /* Root node has no uncle */ |
|||
assert (n->parent->parent != NULL); /* Children of root have no uncle */ |
|||
return sibling(n->parent); |
|||
} |
|||
|
|||
void verify_properties(rbtree t) { |
|||
#ifdef VERIFY_RBTREE |
|||
verify_property_1(t->root); |
|||
verify_property_2(t->root); |
|||
/* Property 3 is implicit */ |
|||
verify_property_4(t->root); |
|||
verify_property_5(t->root); |
|||
#endif |
|||
} |
|||
|
|||
void verify_property_1(node n) { |
|||
assert(node_color(n) == RED || node_color(n) == BLACK); |
|||
if (n == NULL) return; |
|||
verify_property_1(n->left); |
|||
verify_property_1(n->right); |
|||
} |
|||
|
|||
/*
|
|||
void verify_property_2(node root) { |
|||
assert(node_color(root) == BLACK); |
|||
} |
|||
*/ |
|||
|
|||
color node_color(node n) { |
|||
return n == NULL ? BLACK : n->color; |
|||
} |
|||
|
|||
void verify_property_4(node n) { |
|||
if (node_color(n) == RED) { |
|||
assert (node_color(n->left) == BLACK); |
|||
assert (node_color(n->right) == BLACK); |
|||
assert (node_color(n->parent) == BLACK); |
|||
} |
|||
if (n == NULL) return; |
|||
verify_property_4(n->left); |
|||
verify_property_4(n->right); |
|||
} |
|||
|
|||
/*
|
|||
void verify_property_5(node root) { |
|||
int black_count_path = -1; |
|||
verify_property_5_helper(root, 0, &black_count_path); |
|||
} |
|||
*/ |
|||
|
|||
void verify_property_5_helper(node n, int black_count, int* path_black_count) { |
|||
if (node_color(n) == BLACK) { |
|||
black_count++; |
|||
} |
|||
if (n == NULL) { |
|||
if (*path_black_count == -1) { |
|||
*path_black_count = black_count; |
|||
} else { |
|||
assert (black_count == *path_black_count); |
|||
} |
|||
return; |
|||
} |
|||
verify_property_5_helper(n->left, black_count, path_black_count); |
|||
verify_property_5_helper(n->right, black_count, path_black_count); |
|||
} |
|||
|
|||
void rbtree_init(rbtree t, rbtree_compare_func compare) { |
|||
t->root = NULL; |
|||
t->compare = compare; |
|||
verify_properties(t); |
|||
} |
|||
|
|||
node lookup_node(rbtree t, void* key) { |
|||
node n = t->root; |
|||
while (n != NULL) { |
|||
int comp_result = t->compare(key, n->key); |
|||
if (comp_result == 0) { |
|||
return n; |
|||
} else if (comp_result < 0) { |
|||
n = n->left; |
|||
} else { |
|||
assert(comp_result > 0); |
|||
n = n->right; |
|||
} |
|||
} |
|||
return n; |
|||
} |
|||
|
|||
void* rbtree_lookup(rbtree t, void* key) { |
|||
node n = lookup_node(t, key); |
|||
return n == NULL ? NULL : n->value; |
|||
} |
|||
|
|||
void rotate_left(rbtree t, node n) { |
|||
node r = n->right; |
|||
replace_node(t, n, r); |
|||
n->right = r->left; |
|||
if (r->left != NULL) { |
|||
r->left->parent = n; |
|||
} |
|||
r->left = n; |
|||
n->parent = r; |
|||
} |
|||
|
|||
void rotate_right(rbtree t, node n) { |
|||
node L = n->left; |
|||
replace_node(t, n, L); |
|||
n->left = L->right; |
|||
if (L->right != NULL) { |
|||
L->right->parent = n; |
|||
} |
|||
L->right = n; |
|||
n->parent = L; |
|||
} |
|||
|
|||
void replace_node(rbtree t, node oldn, node newn) { |
|||
if (oldn->parent == NULL) { |
|||
t->root = newn; |
|||
} else { |
|||
if (oldn == oldn->parent->left) |
|||
oldn->parent->left = newn; |
|||
else |
|||
oldn->parent->right = newn; |
|||
} |
|||
if (newn != NULL) { |
|||
newn->parent = oldn->parent; |
|||
} |
|||
} |
|||
|
|||
void rbtree_insert(rbtree t, rbtree_node inserted_node) { |
|||
inserted_node->color = RED; |
|||
inserted_node->left = NULL; |
|||
inserted_node->right = NULL; |
|||
inserted_node->parent = NULL; |
|||
|
|||
if (t->root == NULL) { |
|||
t->root = inserted_node; |
|||
} else { |
|||
node n = t->root; |
|||
while (1) { |
|||
int comp_result = t->compare(inserted_node->key, n->key); |
|||
if (comp_result == 0) { |
|||
n->value = inserted_node->value; |
|||
return; |
|||
} else if (comp_result < 0) { |
|||
if (n->left == NULL) { |
|||
n->left = inserted_node; |
|||
break; |
|||
} else { |
|||
n = n->left; |
|||
} |
|||
} else { |
|||
assert (comp_result > 0); |
|||
if (n->right == NULL) { |
|||
n->right = inserted_node; |
|||
break; |
|||
} else { |
|||
n = n->right; |
|||
} |
|||
} |
|||
} |
|||
inserted_node->parent = n; |
|||
} |
|||
insert_case1(t, inserted_node); |
|||
verify_properties(t); |
|||
} |
|||
|
|||
void insert_case1(rbtree t, node n) { |
|||
if (n->parent == NULL) |
|||
n->color = BLACK; |
|||
else |
|||
insert_case2(t, n); |
|||
} |
|||
|
|||
void insert_case2(rbtree t, node n) { |
|||
if (node_color(n->parent) == BLACK) |
|||
return; /* Tree is still valid */ |
|||
else |
|||
insert_case3(t, n); |
|||
} |
|||
|
|||
void insert_case3(rbtree t, node n) { |
|||
if (node_color(uncle(n)) == RED) { |
|||
n->parent->color = BLACK; |
|||
uncle(n)->color = BLACK; |
|||
grandparent(n)->color = RED; |
|||
insert_case1(t, grandparent(n)); |
|||
} else { |
|||
insert_case4(t, n); |
|||
} |
|||
} |
|||
|
|||
void insert_case4(rbtree t, node n) { |
|||
if (n == n->parent->right && n->parent == grandparent(n)->left) { |
|||
rotate_left(t, n->parent); |
|||
n = n->left; |
|||
} else if (n == n->parent->left && n->parent == grandparent(n)->right) { |
|||
rotate_right(t, n->parent); |
|||
n = n->right; |
|||
} |
|||
insert_case5(t, n); |
|||
} |
|||
|
|||
void insert_case5(rbtree t, node n) { |
|||
n->parent->color = BLACK; |
|||
grandparent(n)->color = RED; |
|||
if (n == n->parent->left && n->parent == grandparent(n)->left) { |
|||
rotate_right(t, grandparent(n)); |
|||
} else { |
|||
assert (n == n->parent->right && n->parent == grandparent(n)->right); |
|||
rotate_left(t, grandparent(n)); |
|||
} |
|||
} |
|||
|
|||
rbtree_node rbtree_delete(rbtree t, void* key) { |
|||
node child; |
|||
node n = lookup_node(t, key); |
|||
if (n == NULL) return NULL; /* Key not found, do nothing */ |
|||
if (n->left != NULL && n->right != NULL) { |
|||
/* Copy key/value from predecessor and then delete it instead */ |
|||
node pred = maximum_node(n->left); |
|||
n->key = pred->key; |
|||
n->value = pred->value; |
|||
n = pred; |
|||
} |
|||
|
|||
assert(n->left == NULL || n->right == NULL); |
|||
child = n->right == NULL ? n->left : n->right; |
|||
if (node_color(n) == BLACK) { |
|||
n->color = node_color(child); |
|||
delete_case1(t, n); |
|||
} |
|||
replace_node(t, n, child); |
|||
|
|||
verify_properties(t); |
|||
return n; |
|||
} |
|||
|
|||
static node maximum_node(node n) { |
|||
assert (n != NULL); |
|||
while (n->right != NULL) { |
|||
n = n->right; |
|||
} |
|||
return n; |
|||
} |
|||
|
|||
void delete_case1(rbtree t, node n) { |
|||
if (n->parent == NULL) |
|||
return; |
|||
else |
|||
delete_case2(t, n); |
|||
} |
|||
|
|||
void delete_case2(rbtree t, node n) { |
|||
if (node_color(sibling(n)) == RED) { |
|||
n->parent->color = RED; |
|||
sibling(n)->color = BLACK; |
|||
if (n == n->parent->left) |
|||
rotate_left(t, n->parent); |
|||
else |
|||
rotate_right(t, n->parent); |
|||
} |
|||
delete_case3(t, n); |
|||
} |
|||
|
|||
void delete_case3(rbtree t, node n) { |
|||
if (node_color(n->parent) == BLACK && |
|||
node_color(sibling(n)) == BLACK && |
|||
node_color(sibling(n)->left) == BLACK && |
|||
node_color(sibling(n)->right) == BLACK) |
|||
{ |
|||
sibling(n)->color = RED; |
|||
delete_case1(t, n->parent); |
|||
} |
|||
else |
|||
delete_case4(t, n); |
|||
} |
|||
|
|||
void delete_case4(rbtree t, node n) { |
|||
if (node_color(n->parent) == RED && |
|||
node_color(sibling(n)) == BLACK && |
|||
node_color(sibling(n)->left) == BLACK && |
|||
node_color(sibling(n)->right) == BLACK) |
|||
{ |
|||
sibling(n)->color = RED; |
|||
n->parent->color = BLACK; |
|||
} |
|||
else |
|||
delete_case5(t, n); |
|||
} |
|||
|
|||
void delete_case5(rbtree t, node n) { |
|||
if (n == n->parent->left && |
|||
node_color(sibling(n)) == BLACK && |
|||
node_color(sibling(n)->left) == RED && |
|||
node_color(sibling(n)->right) == BLACK) |
|||
{ |
|||
sibling(n)->color = RED; |
|||
sibling(n)->left->color = BLACK; |
|||
rotate_right(t, sibling(n)); |
|||
} |
|||
else if (n == n->parent->right && |
|||
node_color(sibling(n)) == BLACK && |
|||
node_color(sibling(n)->right) == RED && |
|||
node_color(sibling(n)->left) == BLACK) |
|||
{ |
|||
sibling(n)->color = RED; |
|||
sibling(n)->right->color = BLACK; |
|||
rotate_left(t, sibling(n)); |
|||
} |
|||
delete_case6(t, n); |
|||
} |
|||
|
|||
void delete_case6(rbtree t, node n) { |
|||
sibling(n)->color = node_color(n->parent); |
|||
n->parent->color = BLACK; |
|||
if (n == n->parent->left) { |
|||
assert (node_color(sibling(n)->right) == RED); |
|||
sibling(n)->right->color = BLACK; |
|||
rotate_left(t, n->parent); |
|||
} |
|||
else |
|||
{ |
|||
assert (node_color(sibling(n)->left) == RED); |
|||
sibling(n)->left->color = BLACK; |
|||
rotate_right(t, n->parent); |
|||
} |
|||
} |
|||
|
|||
|
@ -0,0 +1,54 @@ |
|||
/* Copyright (c) 2008 Derrick Coetzee
|
|||
* http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982
|
|||
* Small changes by Ryah Dahl |
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining a |
|||
* copy of this software and associated documentation files (the |
|||
* "Software"), to deal in the Software without restriction, including |
|||
* without limitation the rights to use, copy, modify, merge, publish, |
|||
* distribute, sublicense, and/or sell copies of the Software, and to permit |
|||
* persons to whom the Software is furnished to do so, subject to the |
|||
* following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be included |
|||
* in all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT |
|||
* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR |
|||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
*/ |
|||
|
|||
#ifndef _RBTREE_H_ |
|||
#define _RBTREE_H_ |
|||
|
|||
enum rbtree_node_color { RED, BLACK }; |
|||
|
|||
typedef int (*rbtree_compare_func)(void* left_key, void* right_key); |
|||
|
|||
typedef struct rbtree_node_t { |
|||
struct rbtree_node_t* left; /* private */ |
|||
struct rbtree_node_t* right; /* private */ |
|||
struct rbtree_node_t* parent; /* private */ |
|||
enum rbtree_node_color color; /* private */ |
|||
void* key; /* public */ |
|||
void* value; /* public */ |
|||
} *rbtree_node; |
|||
|
|||
typedef struct rbtree_t { |
|||
rbtree_node root; /* private */ |
|||
rbtree_compare_func compare; /* private */ |
|||
} *rbtree; |
|||
|
|||
|
|||
void rbtree_init(rbtree t, rbtree_compare_func); |
|||
void* rbtree_lookup(rbtree t, void* key); |
|||
void rbtree_insert(rbtree t, rbtree_node); |
|||
/* you must free the returned node */ |
|||
rbtree_node rbtree_delete(rbtree t, void* key); |
|||
|
|||
#endif |
|||
|
@ -0,0 +1,60 @@ |
|||
require 'test/unit' |
|||
require 'socket' |
|||
|
|||
REQ = "GET /hello/%d HTTP/1.1\r\n\r\n" |
|||
HOST = '0.0.0.0' |
|||
PORT = 5000 |
|||
|
|||
class TCPSocket |
|||
def full_send(string) |
|||
written = 0 |
|||
while(written < string.length) |
|||
sent = write(string) |
|||
string = string.slice(sent, 10000) |
|||
end |
|||
written |
|||
end |
|||
|
|||
def full_read |
|||
response = "" |
|||
while chunk = read(10000) |
|||
response += chunk |
|||
end |
|||
response |
|||
end |
|||
|
|||
end |
|||
|
|||
class EbbTest < Test::Unit::TestCase |
|||
|
|||
def setup |
|||
@socket = TCPSocket.new(HOST, PORT) |
|||
end |
|||
|
|||
def test_bad_req |
|||
@socket.full_send("hello") |
|||
assert_equal "", @socket.full_read |
|||
#assert @socket.closed? |
|||
end |
|||
|
|||
def test_single |
|||
written = 0 |
|||
req = REQ % 1 |
|||
@socket.full_send(req) |
|||
response = @socket.full_read() |
|||
count = 0 |
|||
response.scan("hello world") { count += 1 } |
|||
|
|||
assert_equal 1, count |
|||
end |
|||
|
|||
def test_pipeline |
|||
written = 0 |
|||
req = (REQ % 1) + (REQ % 2) + (REQ % 3) + (REQ % 4) |
|||
@socket.full_send(req) |
|||
response = @socket.full_read() |
|||
count = 0 |
|||
response.scan("hello world") { count += 1 } |
|||
assert_equal 4, count |
|||
end |
|||
end |
@ -0,0 +1,108 @@ |
|||
/* Copyright (c) 2008 Derrick Coetzee
|
|||
* http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982
|
|||
* |
|||
* Permission is hereby granted, free of charge, to any person obtaining a |
|||
* copy of this software and associated documentation files (the |
|||
* "Software"), to deal in the Software without restriction, including |
|||
* without limitation the rights to use, copy, modify, merge, publish, |
|||
* distribute, sublicense, and/or sell copies of the Software, and to permit |
|||
* persons to whom the Software is furnished to do so, subject to the |
|||
* following conditions: |
|||
* |
|||
* The above copyright notice and this permission notice shall be included |
|||
* in all copies or substantial portions of the Software. |
|||
* |
|||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
|||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT |
|||
* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR |
|||
* THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
*/ |
|||
|
|||
#include "rbtree.h" |
|||
#include <stdio.h> |
|||
#include <assert.h> |
|||
#include <stdlib.h> /* rand(), malloc(), free() */ |
|||
|
|||
static int compare_int(void* left, void* right); |
|||
static void print_tree(rbtree t); |
|||
static void print_tree_helper(rbtree_node n, int indent); |
|||
|
|||
int compare_int(void* leftp, void* rightp) { |
|||
int left = (int)leftp; |
|||
int right = (int)rightp; |
|||
if (left < right) |
|||
return -1; |
|||
else if (left > right) |
|||
return 1; |
|||
else { |
|||
assert (left == right); |
|||
return 0; |
|||
} |
|||
} |
|||
|
|||
#define INDENT_STEP 4 |
|||
|
|||
void print_tree_helper(rbtree_node n, int indent); |
|||
|
|||
void print_tree(rbtree t) { |
|||
print_tree_helper(t->root, 0); |
|||
puts(""); |
|||
} |
|||
|
|||
void print_tree_helper(rbtree_node n, int indent) { |
|||
int i; |
|||
if (n == NULL) { |
|||
fputs("<empty tree>", stdout); |
|||
return; |
|||
} |
|||
if (n->right != NULL) { |
|||
print_tree_helper(n->right, indent + INDENT_STEP); |
|||
} |
|||
for(i=0; i<indent; i++) |
|||
fputs(" ", stdout); |
|||
if (n->color == BLACK) |
|||
printf("%d\n", (int)n->key); |
|||
else |
|||
printf("<%d>\n", (int)n->key); |
|||
if (n->left != NULL) { |
|||
print_tree_helper(n->left, indent + INDENT_STEP); |
|||
} |
|||
} |
|||
|
|||
int main() { |
|||
int i; |
|||
struct rbtree_t t; |
|||
rbtree_init(&t, compare_int); |
|||
print_tree(&t); |
|||
|
|||
for(i=0; i<5000; i++) { |
|||
int x = rand() % 10000; |
|||
int y = rand() % 10000; |
|||
#ifdef TRACE |
|||
print_tree(&t); |
|||
printf("Inserting %d -> %d\n\n", x, y); |
|||
#endif |
|||
rbtree_node node = (rbtree_node) malloc(sizeof(struct rbtree_node_t)); |
|||
node->key = (void*)x; |
|||
node->value = (void*)y; |
|||
rbtree_insert(&t, node); |
|||
assert(rbtree_lookup(&t, (void*)x) == (void*)y); |
|||
} |
|||
for(i=0; i<60000; i++) { |
|||
int x = rand() % 10000; |
|||
#ifdef TRACE |
|||
print_tree(&t); |
|||
printf("Deleting key %d\n\n", x); |
|||
#endif |
|||
rbtree_node n = rbtree_delete(&t, (void*)x); |
|||
if(n != NULL) { |
|||
free(n); |
|||
} |
|||
} |
|||
printf("Okay\n"); |
|||
return 0; |
|||
} |
|||
|
@ -0,0 +1,746 @@ |
|||
/* unit tests for request parser
|
|||
* Copyright 2008 ryah dahl, ry@ndahl.us |
|||
* |
|||
* This software may be distributed under the "MIT" license included in the |
|||
* README |
|||
*/ |
|||
#include "ebb_request_parser.h" |
|||
#include <stdlib.h> |
|||
#include <assert.h> |
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
|
|||
#define TRUE 1 |
|||
#define FALSE 0 |
|||
|
|||
#define MAX_HEADERS 500 |
|||
#define MAX_ELEMENT_SIZE 500 |
|||
|
|||
static ebb_request_parser parser; |
|||
struct request_data { |
|||
const char *raw; |
|||
int request_method; |
|||
char request_path[MAX_ELEMENT_SIZE]; |
|||
char request_uri[MAX_ELEMENT_SIZE]; |
|||
char fragment[MAX_ELEMENT_SIZE]; |
|||
char query_string[MAX_ELEMENT_SIZE]; |
|||
char body[MAX_ELEMENT_SIZE]; |
|||
int num_headers; |
|||
char header_fields[MAX_HEADERS][MAX_ELEMENT_SIZE]; |
|||
char header_values[MAX_HEADERS][MAX_ELEMENT_SIZE]; |
|||
int should_keep_alive; |
|||
ebb_request request; |
|||
}; |
|||
static struct request_data requests[5]; |
|||
static int num_requests; |
|||
|
|||
const struct request_data curl_get = |
|||
{ raw: "GET /test HTTP/1.1\r\n" |
|||
"User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n" |
|||
"Host: 0.0.0.0:5000\r\n" |
|||
"Accept: */*\r\n" |
|||
"\r\n" |
|||
, should_keep_alive: TRUE |
|||
, request_method: EBB_GET |
|||
, query_string: "" |
|||
, fragment: "" |
|||
, request_path: "/test" |
|||
, request_uri: "/test" |
|||
, num_headers: 3 |
|||
, header_fields: { "User-Agent", "Host", "Accept" } |
|||
, header_values: { "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1", "0.0.0.0:5000", "*/*" } |
|||
, body: "" |
|||
}; |
|||
|
|||
const struct request_data firefox_get = |
|||
{ raw: "GET /favicon.ico HTTP/1.1\r\n" |
|||
"Host: 0.0.0.0:5000\r\n" |
|||
"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n" |
|||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" |
|||
"Accept-Language: en-us,en;q=0.5\r\n" |
|||
"Accept-Encoding: gzip,deflate\r\n" |
|||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" |
|||
"Keep-Alive: 300\r\n" |
|||
"Connection: keep-alive\r\n" |
|||
"\r\n" |
|||
, should_keep_alive: TRUE |
|||
, request_method: EBB_GET |
|||
, query_string: "" |
|||
, fragment: "" |
|||
, request_path: "/favicon.ico" |
|||
, request_uri: "/favicon.ico" |
|||
, num_headers: 8 |
|||
, header_fields: |
|||
{ "Host" |
|||
, "User-Agent" |
|||
, "Accept" |
|||
, "Accept-Language" |
|||
, "Accept-Encoding" |
|||
, "Accept-Charset" |
|||
, "Keep-Alive" |
|||
, "Connection" |
|||
} |
|||
, header_values: |
|||
{ "0.0.0.0:5000" |
|||
, "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" |
|||
, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" |
|||
, "en-us,en;q=0.5" |
|||
, "gzip,deflate" |
|||
, "ISO-8859-1,utf-8;q=0.7,*;q=0.7" |
|||
, "300" |
|||
, "keep-alive" |
|||
} |
|||
, body: "" |
|||
}; |
|||
|
|||
const struct request_data dumbfuck = |
|||
{ raw: "GET /dumbfuck HTTP/1.1\r\n" |
|||
"aaaaaaaaaaaaa:++++++++++\r\n" |
|||
"\r\n" |
|||
, should_keep_alive: TRUE |
|||
, request_method: EBB_GET |
|||
, query_string: "" |
|||
, fragment: "" |
|||
, request_path: "/dumbfuck" |
|||
, request_uri: "/dumbfuck" |
|||
, num_headers: 1 |
|||
, header_fields: { "aaaaaaaaaaaaa" } |
|||
, header_values: { "++++++++++" } |
|||
, body: "" |
|||
}; |
|||
|
|||
const struct request_data fragment_in_uri = |
|||
{ raw: "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n" |
|||
"\r\n" |
|||
, should_keep_alive: TRUE |
|||
, request_method: EBB_GET |
|||
, query_string: "page=1" |
|||
, fragment: "posts-17408" |
|||
, request_path: "/forums/1/topics/2375" |
|||
/* XXX request uri does not include fragment? */ |
|||
, request_uri: "/forums/1/topics/2375?page=1" |
|||
, num_headers: 0 |
|||
, body: "" |
|||
}; |
|||
|
|||
|
|||
// get - no headers - no body
|
|||
const struct request_data get_no_headers_no_body = |
|||
{ raw: "GET /get_no_headers_no_body/world HTTP/1.1\r\n" |
|||
"\r\n" |
|||
, should_keep_alive: TRUE |
|||
, request_method: EBB_GET |
|||
, query_string: "" |
|||
, fragment: "" |
|||
, request_path: "/get_no_headers_no_body/world" |
|||
, request_uri: "/get_no_headers_no_body/world" |
|||
, num_headers: 0 |
|||
, body: "" |
|||
}; |
|||
|
|||
// get - one header - no body
|
|||
const struct request_data get_one_header_no_body = |
|||
{ raw: "GET /get_one_header_no_body HTTP/1.1\r\n" |
|||
"Accept: */*\r\n" |
|||
"\r\n" |
|||
, should_keep_alive: TRUE |
|||
, request_method: EBB_GET |
|||
, query_string: "" |
|||
, fragment: "" |
|||
, request_path: "/get_one_header_no_body" |
|||
, request_uri: "/get_one_header_no_body" |
|||
, num_headers: 1 |
|||
, header_fields: { "Accept" } |
|||
, header_values: { "*/*" } |
|||
, body: "" |
|||
}; |
|||
|
|||
// get - no headers - body "HELLO"
|
|||
const struct request_data get_funky_content_length_body_hello = |
|||
{ raw: "GET /get_funky_content_length_body_hello HTTP/1.0\r\n" |
|||
"conTENT-Length: 5\r\n" |
|||
"\r\n" |
|||
"HELLO" |
|||
, should_keep_alive: FALSE |
|||
, request_method: EBB_GET |
|||
, query_string: "" |
|||
, fragment: "" |
|||
, request_path: "/get_funky_content_length_body_hello" |
|||
, request_uri: "/get_funky_content_length_body_hello" |
|||
, num_headers: 1 |
|||
, header_fields: { "conTENT-Length" } |
|||
, header_values: { "5" } |
|||
, body: "HELLO" |
|||
}; |
|||
|
|||
// post - one header - body "World"
|
|||
const struct request_data post_identity_body_world = |
|||
{ raw: "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" |
|||
"Accept: */*\r\n" |
|||
"Transfer-Encoding: identity\r\n" |
|||
"Content-Length: 5\r\n" |
|||
"\r\n" |
|||
"World" |
|||
, should_keep_alive: TRUE |
|||
, request_method: EBB_POST |
|||
, query_string: "q=search" |
|||
, fragment: "hey" |
|||
, request_path: "/post_identity_body_world" |
|||
, request_uri: "/post_identity_body_world?q=search" |
|||
, num_headers: 3 |
|||
, header_fields: { "Accept", "Transfer-Encoding", "Content-Length" } |
|||
, header_values: { "*/*", "identity", "5" } |
|||
, body: "World" |
|||
}; |
|||
|
|||
// post - no headers - chunked body "all your base are belong to us"
|
|||
const struct request_data post_chunked_all_your_base = |
|||
{ raw: "POST /post_chunked_all_your_base HTTP/1.1\r\n" |
|||
"Transfer-Encoding: chunked\r\n" |
|||
"\r\n" |
|||
"1e\r\nall your base are belong to us\r\n" |
|||
"0\r\n" |
|||
"\r\n" |
|||
, should_keep_alive: TRUE |
|||
, request_method: EBB_POST |
|||
, query_string: "" |
|||
, fragment: "" |
|||
, request_path: "/post_chunked_all_your_base" |
|||
, request_uri: "/post_chunked_all_your_base" |
|||
, num_headers: 1 |
|||
, header_fields: { "Transfer-Encoding" } |
|||
, header_values: { "chunked" } |
|||
, body: "all your base are belong to us" |
|||
}; |
|||
|
|||
// two chunks ; triple zero ending
|
|||
const struct request_data two_chunks_mult_zero_end = |
|||
{ raw: "POST /two_chunks_mult_zero_end HTTP/1.1\r\n" |
|||
"Transfer-Encoding: chunked\r\n" |
|||
"\r\n" |
|||
"5\r\nhello\r\n" |
|||
"6\r\n world\r\n" |
|||
"000\r\n" |
|||
"\r\n" |
|||
, should_keep_alive: TRUE |
|||
, request_method: EBB_POST |
|||
, query_string: "" |
|||
, fragment: "" |
|||
, request_path: "/two_chunks_mult_zero_end" |
|||
, request_uri: "/two_chunks_mult_zero_end" |
|||
, num_headers: 1 |
|||
, header_fields: { "Transfer-Encoding" } |
|||
, header_values: { "chunked" } |
|||
, body: "hello world" |
|||
}; |
|||
|
|||
|
|||
// chunked with trailing headers. blech.
|
|||
const struct request_data chunked_w_trailing_headers = |
|||
{ raw: "POST /chunked_w_trailing_headers HTTP/1.1\r\n" |
|||
"Transfer-Encoding: chunked\r\n" |
|||
"\r\n" |
|||
"5\r\nhello\r\n" |
|||
"6\r\n world\r\n" |
|||
"0\r\n" |
|||
"Vary: *\r\n" |
|||
"Content-Type: text/plain\r\n" |
|||
"\r\n" |
|||
, should_keep_alive: TRUE |
|||
, request_method: EBB_POST |
|||
, query_string: "" |
|||
, fragment: "" |
|||
, request_path: "/chunked_w_trailing_headers" |
|||
, request_uri: "/chunked_w_trailing_headers" |
|||
, num_headers: 1 |
|||
, header_fields: { "Transfer-Encoding" } |
|||
, header_values: { "chunked" } |
|||
, body: "hello world" |
|||
}; |
|||
|
|||
// with bullshit after the length
|
|||
const struct request_data chunked_w_bullshit_after_length = |
|||
{ raw: "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n" |
|||
"Transfer-Encoding: chunked\r\n" |
|||
"\r\n" |
|||
"5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n" |
|||
"6; blahblah; blah\r\n world\r\n" |
|||
"0\r\n" |
|||
"\r\n" |
|||
, should_keep_alive: TRUE |
|||
, request_method: EBB_POST |
|||
, query_string: "" |
|||
, fragment: "" |
|||
, request_path: "/chunked_w_bullshit_after_length" |
|||
, request_uri: "/chunked_w_bullshit_after_length" |
|||
, num_headers: 1 |
|||
, header_fields: { "Transfer-Encoding" } |
|||
, header_values: { "chunked" } |
|||
, body: "hello world" |
|||
}; |
|||
|
|||
const struct request_data *fixtures[] = |
|||
{ &curl_get |
|||
, &firefox_get |
|||
, &dumbfuck |
|||
, &fragment_in_uri |
|||
, &get_no_headers_no_body |
|||
, &get_one_header_no_body |
|||
, &get_funky_content_length_body_hello |
|||
, &post_identity_body_world |
|||
, &post_chunked_all_your_base |
|||
, &two_chunks_mult_zero_end |
|||
, &chunked_w_trailing_headers |
|||
, &chunked_w_bullshit_after_length |
|||
, NULL |
|||
}; |
|||
|
|||
|
|||
int request_data_eq |
|||
( struct request_data *r1 |
|||
, const struct request_data *r2 |
|||
) |
|||
{ |
|||
if(ebb_request_should_keep_alive(&r1->request) != r2->should_keep_alive) { |
|||
printf("requests disagree on keep-alive"); |
|||
return FALSE; |
|||
} |
|||
|
|||
if(0 != strcmp(r1->body, r2->body)) { |
|||
printf("body '%s' != '%s'\n", r1->body, r2->body); |
|||
return FALSE; |
|||
} |
|||
if(0 != strcmp(r1->fragment, r2->fragment)) { |
|||
printf("fragment '%s' != '%s'\n", r1->fragment, r2->fragment); |
|||
return FALSE; |
|||
} |
|||
if(0 != strcmp(r1->query_string, r2->query_string)) { |
|||
printf("query_string '%s' != '%s'\n", r1->query_string, r2->query_string); |
|||
return FALSE; |
|||
} |
|||
if(r1->request.method != r2->request_method) { |
|||
printf("request_method '%d' != '%d'\n", r1->request.method, r2->request_method); |
|||
return FALSE; |
|||
} |
|||
if(0 != strcmp(r1->request_path, r2->request_path)) { |
|||
printf("request_path '%s' != '%s'\n", r1->request_path, r2->request_path); |
|||
return FALSE; |
|||
} |
|||
if(0 != strcmp(r1->request_uri, r2->request_uri)) { |
|||
printf("request_uri '%s' != '%s'\n", r1->request_uri, r2->request_uri); |
|||
return FALSE; |
|||
} |
|||
if(r1->num_headers != r2->num_headers) { |
|||
printf("num_headers '%d' != '%d'\n", r1->num_headers, r2->num_headers); |
|||
return FALSE; |
|||
} |
|||
int i; |
|||
for(i = 0; i < r1->num_headers; i++) { |
|||
if(0 != strcmp(r1->header_fields[i], r2->header_fields[i])) { |
|||
printf("header field '%s' != '%s'\n", r1->header_fields[i], r2->header_fields[i]); |
|||
return FALSE; |
|||
} |
|||
if(0 != strcmp(r1->header_values[i], r2->header_values[i])) { |
|||
printf("header field '%s' != '%s'\n", r1->header_values[i], r2->header_values[i]); |
|||
return FALSE; |
|||
} |
|||
} |
|||
return TRUE; |
|||
} |
|||
|
|||
int request_eq |
|||
( int index |
|||
, const struct request_data *expected |
|||
) |
|||
{ |
|||
return request_data_eq(&requests[index], expected); |
|||
} |
|||
|
|||
void request_complete(ebb_request *info) |
|||
{ |
|||
num_requests++; |
|||
} |
|||
|
|||
void request_path_cb(ebb_request *request, const char *p, size_t len) |
|||
{ |
|||
strncat(requests[num_requests].request_path, p, len); |
|||
} |
|||
|
|||
void request_uri_cb(ebb_request *request, const char *p, size_t len) |
|||
{ |
|||
strncat(requests[num_requests].request_uri, p, len); |
|||
} |
|||
|
|||
void query_string_cb(ebb_request *request, const char *p, size_t len) |
|||
{ |
|||
strncat(requests[num_requests].query_string, p, len); |
|||
} |
|||
|
|||
void fragment_cb(ebb_request *request, const char *p, size_t len) |
|||
{ |
|||
strncat(requests[num_requests].fragment, p, len); |
|||
} |
|||
|
|||
void header_field_cb(ebb_request *request, const char *p, size_t len, int header_index) |
|||
{ |
|||
strncat(requests[num_requests].header_fields[header_index], p, len); |
|||
} |
|||
|
|||
void header_value_cb(ebb_request *request, const char *p, size_t len, int header_index) |
|||
{ |
|||
strncat(requests[num_requests].header_values[header_index], p, len); |
|||
requests[num_requests].num_headers = header_index + 1; |
|||
} |
|||
|
|||
void body_handler(ebb_request *request, const char *p, size_t len) |
|||
{ |
|||
strncat(requests[num_requests].body, p, len); |
|||
// printf("body_handler: '%s'\n", requests[num_requests].body);
|
|||
} |
|||
|
|||
ebb_request* new_request () |
|||
{ |
|||
requests[num_requests].num_headers = 0; |
|||
requests[num_requests].request_method = -1; |
|||
requests[num_requests].request_path[0] = 0; |
|||
requests[num_requests].request_uri[0] = 0; |
|||
requests[num_requests].fragment[0] = 0; |
|||
requests[num_requests].query_string[0] = 0; |
|||
requests[num_requests].body[0] = 0; |
|||
int i; |
|||
for(i = 0; i < MAX_HEADERS; i++) { |
|||
requests[num_requests].header_fields[i][0] = 0; |
|||
requests[num_requests].header_values[i][0] = 0; |
|||
} |
|||
|
|||
ebb_request *r = &requests[num_requests].request; |
|||
ebb_request_init(r); |
|||
|
|||
r->on_complete = request_complete; |
|||
r->on_header_field = header_field_cb; |
|||
r->on_header_value = header_value_cb; |
|||
r->on_path = request_path_cb; |
|||
r->on_uri = request_uri_cb; |
|||
r->on_fragment = fragment_cb; |
|||
r->on_query_string = query_string_cb; |
|||
r->on_body = body_handler; |
|||
r->on_headers_complete = NULL; |
|||
|
|||
r->data = &requests[num_requests]; |
|||
// printf("new request %d\n", num_requests);
|
|||
return r; |
|||
} |
|||
|
|||
void parser_init() |
|||
{ |
|||
num_requests = 0; |
|||
|
|||
ebb_request_parser_init(&parser); |
|||
|
|||
parser.new_request = new_request; |
|||
} |
|||
|
|||
int test_request |
|||
( const struct request_data *request_data |
|||
) |
|||
{ |
|||
size_t traversed = 0; |
|||
parser_init(); |
|||
|
|||
traversed = ebb_request_parser_execute( &parser |
|||
, request_data->raw |
|||
, strlen(request_data->raw) |
|||
); |
|||
if( ebb_request_parser_has_error(&parser) ) |
|||
return FALSE; |
|||
if(! ebb_request_parser_is_finished(&parser) ) |
|||
return FALSE; |
|||
if(num_requests != 1) |
|||
return FALSE; |
|||
|
|||
return request_eq(0, request_data); |
|||
} |
|||
|
|||
int test_error |
|||
( const char *buf |
|||
) |
|||
{ |
|||
size_t traversed = 0; |
|||
parser_init(); |
|||
|
|||
traversed = ebb_request_parser_execute(&parser, buf, strlen(buf)); |
|||
|
|||
return ebb_request_parser_has_error(&parser); |
|||
} |
|||
|
|||
|
|||
int test_multiple3 |
|||
( const struct request_data *r1 |
|||
, const struct request_data *r2 |
|||
, const struct request_data *r3 |
|||
) |
|||
{ |
|||
char total[80*1024] = "\0"; |
|||
|
|||
strcat(total, r1->raw); |
|||
strcat(total, r2->raw); |
|||
strcat(total, r3->raw); |
|||
|
|||
size_t traversed = 0; |
|||
parser_init(); |
|||
|
|||
traversed = ebb_request_parser_execute(&parser, total, strlen(total)); |
|||
|
|||
|
|||
if( ebb_request_parser_has_error(&parser) ) |
|||
return FALSE; |
|||
if(! ebb_request_parser_is_finished(&parser) ) |
|||
return FALSE; |
|||
if(num_requests != 3) |
|||
return FALSE; |
|||
|
|||
if(!request_eq(0, r1)){ |
|||
printf("request 1 error.\n"); |
|||
return FALSE; |
|||
} |
|||
if(!request_eq(1, r2)){ |
|||
printf("request 2 error.\n"); |
|||
return FALSE; |
|||
} |
|||
if(!request_eq(2, r3)){ |
|||
printf("request 3 error.\n"); |
|||
return FALSE; |
|||
} |
|||
|
|||
return TRUE; |
|||
} |
|||
|
|||
/**
|
|||
* SCAN through every possible breaking to make sure the |
|||
* parser can handle getting the content in any chunks that |
|||
* might come from the socket |
|||
*/ |
|||
int test_scan2 |
|||
( const struct request_data *r1 |
|||
, const struct request_data *r2 |
|||
, const struct request_data *r3 |
|||
) |
|||
{ |
|||
char total[80*1024] = "\0"; |
|||
char buf1[80*1024] = "\0"; |
|||
char buf2[80*1024] = "\0"; |
|||
|
|||
strcat(total, r1->raw); |
|||
strcat(total, r2->raw); |
|||
strcat(total, r3->raw); |
|||
|
|||
int total_len = strlen(total); |
|||
|
|||
//printf("total_len = %d\n", total_len);
|
|||
int i; |
|||
for(i = 1; i < total_len - 1; i ++ ) { |
|||
|
|||
parser_init(); |
|||
|
|||
int buf1_len = i; |
|||
strncpy(buf1, total, buf1_len); |
|||
buf1[buf1_len] = 0; |
|||
|
|||
int buf2_len = total_len - i; |
|||
strncpy(buf2, total+i, buf2_len); |
|||
buf2[buf2_len] = 0; |
|||
|
|||
ebb_request_parser_execute(&parser, buf1, buf1_len); |
|||
|
|||
if( ebb_request_parser_has_error(&parser) ) { |
|||
return FALSE; |
|||
} |
|||
/*
|
|||
if(ebb_request_parser_is_finished(&parser)) |
|||
return FALSE; |
|||
*/ |
|||
|
|||
ebb_request_parser_execute(&parser, buf2, buf2_len); |
|||
|
|||
if( ebb_request_parser_has_error(&parser)) |
|||
return FALSE; |
|||
if(!ebb_request_parser_is_finished(&parser)) |
|||
return FALSE; |
|||
|
|||
if(3 != num_requests) { |
|||
printf("scan error: got %d requests in iteration %d\n", num_requests, i); |
|||
return FALSE; |
|||
} |
|||
|
|||
if(!request_eq(0, r1)) { |
|||
printf("not maching r1\n"); |
|||
return FALSE; |
|||
} |
|||
if(!request_eq(1, r2)) { |
|||
printf("not maching r2\n"); |
|||
return FALSE; |
|||
} |
|||
if(!request_eq(2, r3)) { |
|||
printf("not maching r3\n"); |
|||
return FALSE; |
|||
} |
|||
} |
|||
return TRUE; |
|||
} |
|||
|
|||
int test_scan3 |
|||
( const struct request_data *r1 |
|||
, const struct request_data *r2 |
|||
, const struct request_data *r3 |
|||
) |
|||
{ |
|||
char total[80*1024] = "\0"; |
|||
char buf1[80*1024] = "\0"; |
|||
char buf2[80*1024] = "\0"; |
|||
char buf3[80*1024] = "\0"; |
|||
|
|||
strcat(total, r1->raw); |
|||
strcat(total, r2->raw); |
|||
strcat(total, r3->raw); |
|||
|
|||
int total_len = strlen(total); |
|||
|
|||
//printf("total_len = %d\n", total_len);
|
|||
int i,j; |
|||
for(j = 2; j < total_len - 1; j ++ ) { |
|||
for(i = 1; i < j; i ++ ) { |
|||
|
|||
parser_init(); |
|||
|
|||
|
|||
|
|||
|
|||
int buf1_len = i; |
|||
strncpy(buf1, total, buf1_len); |
|||
buf1[buf1_len] = 0; |
|||
|
|||
int buf2_len = j - i; |
|||
strncpy(buf2, total+i, buf2_len); |
|||
buf2[buf2_len] = 0; |
|||
|
|||
int buf3_len = total_len - j; |
|||
strncpy(buf3, total+j, buf3_len); |
|||
buf3[buf3_len] = 0; |
|||
|
|||
/*
|
|||
printf("buf1: %s - %d\n", buf1, buf1_len); |
|||
printf("buf2: %s - %d \n", buf2, buf2_len ); |
|||
printf("buf3: %s - %d\n\n", buf3, buf3_len); |
|||
*/ |
|||
|
|||
ebb_request_parser_execute(&parser, buf1, buf1_len); |
|||
|
|||
if( ebb_request_parser_has_error(&parser) ) { |
|||
return FALSE; |
|||
} |
|||
|
|||
ebb_request_parser_execute(&parser, buf2, buf2_len); |
|||
|
|||
if( ebb_request_parser_has_error(&parser) ) { |
|||
return FALSE; |
|||
} |
|||
|
|||
ebb_request_parser_execute(&parser, buf3, buf3_len); |
|||
|
|||
if( ebb_request_parser_has_error(&parser)) |
|||
return FALSE; |
|||
if(!ebb_request_parser_is_finished(&parser)) |
|||
return FALSE; |
|||
|
|||
if(3 != num_requests) { |
|||
printf("scan error: only got %d requests in iteration %d\n", num_requests, i); |
|||
return FALSE; |
|||
} |
|||
|
|||
if(!request_eq(0, r1)) { |
|||
printf("not maching r1\n"); |
|||
return FALSE; |
|||
} |
|||
if(!request_eq(1, r2)) { |
|||
printf("not maching r2\n"); |
|||
return FALSE; |
|||
} |
|||
if(!request_eq(2, r3)) { |
|||
printf("not maching r3\n"); |
|||
return FALSE; |
|||
} |
|||
} |
|||
} |
|||
return TRUE; |
|||
} |
|||
|
|||
int main() |
|||
{ |
|||
|
|||
assert(test_error("hello world")); |
|||
assert(test_error("GET / HTP/1.1\r\n\r\n")); |
|||
|
|||
assert(test_request(&curl_get)); |
|||
assert(test_request(&firefox_get)); |
|||
|
|||
// Zed's header tests
|
|||
|
|||
assert(test_request(&dumbfuck)); |
|||
|
|||
const char *dumbfuck2 = "GET / HTTP/1.1\r\nX-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"; |
|||
assert(test_error(dumbfuck2)); |
|||
|
|||
assert(test_request(&fragment_in_uri)); |
|||
|
|||
/* TODO sending junk and large headers gets rejected */ |
|||
|
|||
|
|||
/* check to make sure our predefined requests are okay */ |
|||
|
|||
assert(test_request(&get_no_headers_no_body)); |
|||
assert(test_request(&get_one_header_no_body)); |
|||
assert(test_request(&get_no_headers_no_body)); |
|||
|
|||
// no content-length
|
|||
const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\nAccept: */*\r\nHELLO\r\n"; |
|||
assert(test_error(bad_get_no_headers_no_body)); // error if there is a body without content length
|
|||
|
|||
assert(test_request(&get_funky_content_length_body_hello)); |
|||
assert(test_request(&post_identity_body_world)); |
|||
assert(test_request(&post_chunked_all_your_base)); |
|||
assert(test_request(&two_chunks_mult_zero_end)); |
|||
assert(test_request(&chunked_w_trailing_headers)); |
|||
|
|||
assert(test_request(&chunked_w_bullshit_after_length)); |
|||
assert(1 == requests[0].request.version_major); |
|||
assert(1 == requests[0].request.version_minor); |
|||
|
|||
// three requests - no bodies
|
|||
assert( test_multiple3( &get_no_headers_no_body |
|||
, &get_one_header_no_body |
|||
, &get_no_headers_no_body |
|||
)); |
|||
|
|||
// three requests - one body
|
|||
assert( test_multiple3(&get_no_headers_no_body, &get_funky_content_length_body_hello, &get_no_headers_no_body)); |
|||
|
|||
// three requests with bodies -- last is chunked
|
|||
assert( test_multiple3(&get_funky_content_length_body_hello, &post_identity_body_world, &post_chunked_all_your_base)); |
|||
|
|||
// three chunked requests
|
|||
assert( test_multiple3(&two_chunks_mult_zero_end, &post_chunked_all_your_base, &chunked_w_trailing_headers)); |
|||
|
|||
|
|||
assert(test_scan2(&get_no_headers_no_body, &get_one_header_no_body, &get_no_headers_no_body)); |
|||
assert(test_scan2(&get_funky_content_length_body_hello, &post_identity_body_world, &post_chunked_all_your_base)); |
|||
assert(test_scan2(&two_chunks_mult_zero_end, &chunked_w_trailing_headers, &chunked_w_bullshit_after_length)); |
|||
|
|||
assert(test_scan3(&get_no_headers_no_body, &get_one_header_no_body, &get_no_headers_no_body)); |
|||
assert(test_scan3(&get_funky_content_length_body_hello, &post_identity_body_world, &post_chunked_all_your_base)); |
|||
assert(test_scan3(&two_chunks_mult_zero_end, &chunked_w_trailing_headers, &chunked_w_bullshit_after_length)); |
|||
|
|||
|
|||
printf("okay\n"); |
|||
return 0; |
|||
} |
|||
|
@ -0,0 +1,23 @@ |
|||
liboi is Copyright (C) 2009 Ryan Dahl. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are met: |
|||
|
|||
* Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
|
|||
* Redistributions in binary form must reproduce the above |
|||
copyright notice, this list of conditions and the following disclaimer in |
|||
the documentation and/or other materials provided with the distribution. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
|||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
|||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
|||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
|||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
|||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
|||
POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,36 @@ |
|||
liboi is a C library for doing evented I/O. It is intended for building |
|||
efficent internet programs. |
|||
|
|||
liboi is released under the X11 license. |
|||
|
|||
= Feature Summary |
|||
|
|||
* The library has a minimalist design |
|||
- Does not make internal allocations |
|||
- Does not wrap functionality of GnuTLS or libev. The user must use those |
|||
libraries in conjuction with liboi. |
|||
* Supports both server and client sockets. |
|||
* Supports evented file I/O emulation through a thread pool. |
|||
* SSL support |
|||
* Sendfile (file to socket) with emulation on platforms that do not support |
|||
it. |
|||
|
|||
= Building |
|||
|
|||
1 Edit config.mk. You almost certainly will need to set the EVDIR and |
|||
GNUTLSDIR variables. |
|||
2 Run "make" |
|||
|
|||
= Documentation |
|||
|
|||
1 make doc |
|||
2 man ./oi.3 |
|||
|
|||
= Website |
|||
|
|||
http://github.com/ry/liboi |
|||
|
|||
= Author |
|||
|
|||
Ryan Dahl (ry@tinyclouds.org) |
|||
|
@ -0,0 +1,33 @@ |
|||
# Define EVDIR=/foo/bar if your libev header and library files are in
|
|||
# /foo/bar/include and /foo/bar/lib directories.
|
|||
EVDIR=$(HOME)/local/libev |
|||
|
|||
# Define GNUTLSDIR=/foo/bar if your gnutls header and library files are in
|
|||
# /foo/bar/include and /foo/bar/lib directories.
|
|||
#GNUTLSDIR=/usr
|
|||
#
|
|||
#
|
|||
# Define NO_PREAD if you have a problem with pread() system call (e.g.
|
|||
# cygwin.dll before v1.5.22).
|
|||
#
|
|||
#
|
|||
# Define NO_SENDFILE if you have a problem with the sendfile() system call
|
|||
#
|
|||
#
|
|||
|
|||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') |
|||
uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') |
|||
uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') |
|||
uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') |
|||
uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') |
|||
|
|||
# CFLAGS and LDFLAGS are for the users to override from the command line.
|
|||
CFLAGS = -g |
|||
LDFLAGS = |
|||
|
|||
PREFIX = $(HOME)/local/liboi |
|||
|
|||
CC = gcc |
|||
AR = ar |
|||
RM = rm -f |
|||
RANLIB = ranlib |
@ -0,0 +1,8 @@ |
|||
#ifndef oi_h |
|||
#define oi_h |
|||
|
|||
#include <oi_socket.h> |
|||
#include <oi_async.h> |
|||
#include <oi_file.h> |
|||
|
|||
#endif |
@ -0,0 +1,278 @@ |
|||
=head1 NAME |
|||
|
|||
liboi - a C library for doing evented I/O. |
|||
|
|||
=head1 SYNOPSIS |
|||
|
|||
#include <oi.h> |
|||
|
|||
=head1 DESCRIPTION |
|||
|
|||
liboi is an object oriented library for doing evented socket and file I/O. |
|||
The API is mostly about registering callbacks to be executed on certain |
|||
events. |
|||
|
|||
Because most systems do not support asynchornous file I/O, the behavior is |
|||
emulated with an internal thread pool. The thread pool is accessed with the |
|||
C<oi_async> and C<oi_task> objects. Typically one will not need to use these |
|||
directly as C<oi_file> wraps that functionality. |
|||
|
|||
=head2 CONVENTIONS |
|||
|
|||
liboi's goal is to be very simple layer above the POSIX API. To that end it |
|||
avoids internal allocations as much as possible. Unless otherwise noted you |
|||
should assume all pointers passed into liboi will remain your responsibility |
|||
to maintain. That means you should not free the data passed into liboi |
|||
until the object in question has completed. |
|||
|
|||
C<oi_socket> and C<oi_file> objects must be attached to an event loop. This |
|||
is completed with the C<*_attach> and C<*_detach> methods. When an object |
|||
is detached, other methods can be called - just the loop will not churn out |
|||
callbacks. |
|||
|
|||
Both C<oi_socket> and C<oi_file> contain a number of callback pointers. |
|||
These are to be set manually after calling their initalization functions. |
|||
All classes include a C<void *data> member which is left for you to use. |
|||
|
|||
=head1 ERROR HANDLING |
|||
|
|||
|
|||
=head1 Sockets |
|||
|
|||
The C<oi_socket> structure represents a socket. |
|||
The callbacks inside C<oi_socket> are |
|||
void (*on_connect) (oi_socket *); |
|||
void (*on_read) (oi_socket *, const void *buf, size_t count); |
|||
void (*on_drain) (oi_socket *); |
|||
void (*on_error) (oi_socket *, struct oi_error e); |
|||
void (*on_close) (oi_socket *); |
|||
void (*on_timeout) (oi_socket *); |
|||
|
|||
A the memory for a socket is released when the C<on_close()> callback is |
|||
made. That is, the user may free the memory for the socket with-in the |
|||
C<on_close()> callback. |
|||
|
|||
=over 4 |
|||
|
|||
=item void oi_socket_init (oi_socket *, float timeout); |
|||
|
|||
Initialize a socket. C<timeout> is the number of seconds of inactivity that |
|||
is allowed before the socket closes. The timeout only starts once the |
|||
socket is attached to a loop and open. |
|||
|
|||
A C<timeout> argument of 0.0 signals that no timeout should be used. |
|||
|
|||
After calling this function, register your callbacks manually. Thus your |
|||
code will probably look like this |
|||
oi_socket socket; |
|||
oi_socket_init(&socket, 60.0); |
|||
socket.on_connect = my_on_connect; |
|||
socket.on_read = my_on_read; |
|||
/* etc */ |
|||
|
|||
|
|||
=item int oi_socket_connect (oi_socket *, struct addrinfo *addrinfo); |
|||
|
|||
Open a client connect to the specified address. When the connection is made |
|||
C<socket.on_connect> will be called. |
|||
|
|||
Here is an example of filling in C<addrinfo> for a local TCP connection on |
|||
port 5555: |
|||
struct addrinfo *servinfo; |
|||
struct addrinfo hints; |
|||
memset(&hints, 0, sizeof hints); |
|||
hints.ai_family = AF_UNSPEC; |
|||
hints.ai_socktype = SOCK_STREAM; |
|||
hints.ai_flags = AI_PASSIVE; |
|||
r = getaddrinfo(NULL, "5555", &hints, &servinfo); |
|||
assert(r == 0); |
|||
oi_socket_connect(socket, servinfo); |
|||
|
|||
=item void oi_socket_attach (oi_socket *, struct ev_loop *loop); |
|||
|
|||
A socket must be attached to a loop in order before any callbacks will be |
|||
made. |
|||
|
|||
=item void oi_socket_detach (oi_socket *); |
|||
|
|||
Detaching a socket will not close the connection. |
|||
|
|||
=item void oi_socket_read_start (oi_socket *); |
|||
|
|||
This will make the socket start receiving data. When data is received the |
|||
C<socket.on_read> callback is made. The maximum amount of data that can be |
|||
receive at a time is controlled by C<socket.chunksize>. |
|||
|
|||
The buffer returned by C<socket.on_read> is statically allocated exists only |
|||
for the length of the callback. That means if you need to save any of the |
|||
data coming down the line, you must copy it to a new buffer. |
|||
|
|||
Ideally you will have a parser attached to the C<on_read> callback which can |
|||
be interrupted at any time. |
|||
|
|||
C<socket.chunksize> can be changed at any time. |
|||
|
|||
=item void oi_socket_read_stop (oi_socket *); |
|||
|
|||
Stops receiving data. You may receive spurious C<on_read> attempts even |
|||
though the socket reading is stopped - be prepared to handle them. |
|||
|
|||
=item void oi_socket_reset_timeout (oi_socket *); |
|||
|
|||
Reset the timeout to allow the socket to exist for another few seconds |
|||
(however long you specified in the initialization function). |
|||
|
|||
=item void oi_socket_write (oi_socket *socket, oi_buf *buf); |
|||
|
|||
Write the I<buf> to the socket. Each socket has a queue of C<oi_buf> objects |
|||
to be written - this appends the specified buffer to the end of that queue. |
|||
You will be notified when the queue is empty with the C<socket.on_drain()> |
|||
callback. When the socket has written the buffer C<buf.release()> will be |
|||
called. The release callback does not imply that the buffer was successfully |
|||
written. |
|||
|
|||
=item void oi_socket_write_simple (oi_socket *, const char *str, size_t len); |
|||
|
|||
Sometimes you are just hacking around and need to quickly write a string to |
|||
the socket. This convenience function allocates an C<oi_buf> object, and |
|||
C<strdup>s the given string. The allocated buffer will be freed by liboi |
|||
internally. |
|||
|
|||
Most production most applications will use their own memory pool and will |
|||
not need this function. |
|||
|
|||
=item void oi_socket_write_eof (oi_socket *); |
|||
|
|||
This closes the write end of the socket. Further writes are not allowed |
|||
after this. |
|||
|
|||
=item void oi_socket_close (oi_socket *); |
|||
|
|||
Attempts to close the socket. |
|||
|
|||
If the socket is secure, an SSL bye message will be sent. |
|||
SSL recommends that you wait for a bye response from the peer however this |
|||
tends to be overkill for most people. By default liboi will not wait for |
|||
peer to send a matching bye message. If you require this then set |
|||
C<socket.wait_for_secure_hangup> to 1. |
|||
|
|||
When the close is complete C<on_close()> is made. The C<on_close()> |
|||
callback is not made until the program returns to the event loop. This is |
|||
because C<on_close()> may free the socket memory and if C<on_close()> was |
|||
called from C<oi_socket_close()>, then the socket object might unexpectedly |
|||
be gone. To summarize: C<oi_socket_close()> does not call C<on_close()> and |
|||
the socket memory is still accessable immediately after making calling |
|||
C<oi_socket_close()>. |
|||
|
|||
=item void oi_socket_set_secure_session (oi_socket *, gnutls_session_t); |
|||
|
|||
This make a socket use SSL. You must create the GnuTLS session yourself and |
|||
assign its credentials. |
|||
|
|||
=back |
|||
|
|||
=head1 Servers |
|||
|
|||
A server simply listens on an address for new connections. The connections |
|||
come in the form of C<oi_socket> objects. The key is to give a |
|||
C<server.on_connection()> callback which returns an initialized C<oi_socket>. |
|||
The callback looks like this |
|||
|
|||
oi_socket* (*on_connection) (oi_server *, struct sockaddr *remote_addr, socklen_t remove_addr_len); |
|||
|
|||
Returning NULL from C<on_connection()> will reject the connection. |
|||
|
|||
=over 4 |
|||
|
|||
=item void oi_server_init (oi_server *, int backlog); |
|||
|
|||
Initializes a server object. C<backlog> is the argument given |
|||
internally to C<listen()>. Set the C<server.on_connection()> callback |
|||
after calling this. |
|||
|
|||
=item int oi_server_listen (oi_server *, struct addrinfo *addrinfo); |
|||
|
|||
Listens on the specified address. The server will not accept connections |
|||
until it is attached to a loop, however. |
|||
|
|||
=item void oi_server_attach (oi_server *, struct ev_loop *loop); |
|||
|
|||
Attaches a server to a loop. |
|||
|
|||
=item void oi_server_detach (oi_server *); |
|||
|
|||
Detaches a server to a loop. Does not close the server. |
|||
|
|||
=item void oi_server_close (oi_server *); |
|||
|
|||
Stops the server from listening. |
|||
|
|||
=back |
|||
|
|||
=head1 Files |
|||
|
|||
Files internally use a thread pool to operate without blocking. |
|||
The thread pool is started once a file is attached and it continues until |
|||
program termination. |
|||
|
|||
The following callbacks are used inside of the file object |
|||
void (*on_open) (oi_file *); |
|||
void (*on_read) (oi_file *, size_t count); |
|||
void (*on_drain) (oi_file *); |
|||
void (*on_error) (oi_file *, struct oi_error); |
|||
void (*on_close) (oi_file *); |
|||
|
|||
=over 4 |
|||
|
|||
=item int oi_file_init (oi_file *); |
|||
|
|||
Initializes a file object. |
|||
|
|||
=item void oi_file_attach (oi_file *, struct ev_loop *); |
|||
|
|||
Attaches a file object to a loop. If the thread pool has not been started, |
|||
then it is started at this call. |
|||
|
|||
=item void oi_file_detach (oi_file *); |
|||
|
|||
Detaches a file object from the loop. |
|||
|
|||
=item int oi_file_open_path (oi_file *, const char *path, int flags, mode_t mode); |
|||
|
|||
Opens a file specified by the path. The C<flag> and C<mode> arguments are |
|||
the same as used by L<open.2>. The C<file.on_open> callback is triggered |
|||
when the file is opened. Returns 0 on success. Returns -1 if the given file |
|||
is already open. |
|||
|
|||
WARNING: path argument must be valid until C<oi_file> object is closed and |
|||
the C<file.on_close()> callback is made. I.E., liboi does not strdup the path |
|||
pointer. |
|||
|
|||
=item int oi_file_open_stdin (oi_file *); |
|||
|
|||
=item int oi_file_open_stdout (oi_file *); |
|||
|
|||
=item int oi_file_open_stderr (oi_file *); |
|||
|
|||
=item void oi_file_read_start (oi_file *, void *buffer, size_t bufsize); |
|||
|
|||
|
|||
|
|||
=item void oi_file_read_stop (oi_file *); |
|||
|
|||
=item int oi_file_write (oi_file *, oi_buf *to_write); |
|||
|
|||
=item int oi_file_write_simple (oi_file *, const char *, size_t); |
|||
|
|||
=item int oi_file_send (oi_file *source, oi_socket *destination, off_t offset, size_t length); |
|||
|
|||
=item void oi_file_close (oi_file *); |
|||
|
|||
=over 4 |
|||
|
|||
=back |
|||
|
|||
=head1 AUTHOR |
|||
|
|||
Ryan Dahl <ry@tinyclouds.org> |
|||
|
@ -0,0 +1,486 @@ |
|||
#include <stdlib.h> /* malloc() */ |
|||
#include <stdio.h> /* perror() */ |
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <unistd.h> /* read(), write() */ |
|||
#include <fcntl.h> |
|||
#include <errno.h> |
|||
#include <assert.h> |
|||
#include <pthread.h> |
|||
|
|||
#if HAVE_SENDFILE |
|||
# if __linux |
|||
# include <sys/sendfile.h> |
|||
# elif __freebsd |
|||
# include <sys/socket.h> |
|||
# include <sys/uio.h> |
|||
# elif __hpux |
|||
# include <sys/socket.h> |
|||
# elif __solaris /* not yet */ |
|||
# include <sys/sendfile.h> |
|||
# else |
|||
# error sendfile support requested but not available |
|||
# endif |
|||
#endif |
|||
|
|||
#include <ev.h> |
|||
#include <oi.h> |
|||
|
|||
#define NWORKERS 4 |
|||
/* TODO make adjustable
|
|||
* once it is fix sleeping_tasks |
|||
*/ |
|||
|
|||
static int active_watchers = 0; |
|||
static int active_workers = 0; |
|||
static int readiness_pipe[2] = {-1, -1}; |
|||
static oi_queue waiting_tasks; |
|||
static pthread_mutex_t queue_lock = PTHREAD_MUTEX_INITIALIZER; |
|||
static pthread_mutex_t finished_lock = PTHREAD_MUTEX_INITIALIZER; |
|||
|
|||
struct worker { |
|||
oi_task *task; |
|||
pthread_t thread; |
|||
pthread_attr_t thread_attr; |
|||
}; |
|||
|
|||
/* Sendfile and pread emulation come from Marc Lehmann's libeio and are
|
|||
* Copyright (C)2007,2008 Marc Alexander Lehmann. |
|||
* Many ideas of oi_async.* are taken from libeio and in fact, I plan to |
|||
* use libeio once it becomes usable for me. (The problem is issuing tasks |
|||
* from multiple threads.) |
|||
*/ |
|||
|
|||
#if !HAVE_PREADWRITE |
|||
/*
|
|||
* make our pread/pwrite emulation safe against themselves, but not against |
|||
* normal read/write by using a mutex. slows down execution a lot, |
|||
* but that's your problem, not mine. |
|||
*/ |
|||
static pthread_mutex_t preadwritelock = PTHREAD_MUTEX_INITIALIZER; |
|||
#endif |
|||
|
|||
#if !HAVE_PREADWRITE |
|||
# undef pread |
|||
# undef pwrite |
|||
# define pread eio__pread |
|||
# define pwrite eio__pwrite |
|||
|
|||
static ssize_t |
|||
eio__pread (int fd, void *buf, size_t count, off_t offset) |
|||
{ |
|||
ssize_t res; |
|||
off_t ooffset; |
|||
|
|||
pthread_mutex_lock(&preadwritelock); |
|||
ooffset = lseek (fd, 0, SEEK_CUR); |
|||
lseek (fd, offset, SEEK_SET); |
|||
res = read (fd, buf, count); |
|||
lseek (fd, ooffset, SEEK_SET); |
|||
pthread_mutex_unlock(&preadwritelock); |
|||
|
|||
return res; |
|||
} |
|||
|
|||
static ssize_t |
|||
eio__pwrite (int fd, void *buf, size_t count, off_t offset) |
|||
{ |
|||
ssize_t res; |
|||
off_t ooffset; |
|||
|
|||
pthread_mutex_lock(&preadwritelock); |
|||
ooffset = lseek (fd, 0, SEEK_CUR); |
|||
lseek (fd, offset, SEEK_SET); |
|||
res = write (fd, buf, count); |
|||
lseek (fd, offset, SEEK_SET); |
|||
pthread_mutex_unlock(&preadwritelock); |
|||
|
|||
return res; |
|||
} |
|||
#endif |
|||
|
|||
|
|||
/* sendfile always needs emulation */ |
|||
static ssize_t |
|||
eio__sendfile (int ofd, int ifd, off_t offset, size_t count) |
|||
{ |
|||
ssize_t res; |
|||
|
|||
if (!count) |
|||
return 0; |
|||
|
|||
#if HAVE_SENDFILE |
|||
# if __linux |
|||
res = sendfile (ofd, ifd, &offset, count); |
|||
|
|||
# elif __freebsd |
|||
/*
|
|||
* Of course, the freebsd sendfile is a dire hack with no thoughts |
|||
* wasted on making it similar to other I/O functions. |
|||
*/ |
|||
{ |
|||
off_t sbytes; |
|||
res = sendfile (ifd, ofd, offset, count, 0, &sbytes, 0); |
|||
|
|||
if (res < 0 && sbytes) |
|||
/* maybe only on EAGAIN: as usual, the manpage leaves you guessing */ |
|||
res = sbytes; |
|||
} |
|||
|
|||
# elif __hpux |
|||
res = sendfile (ofd, ifd, offset, count, 0, 0); |
|||
|
|||
# elif __solaris |
|||
{ |
|||
struct sendfilevec vec; |
|||
size_t sbytes; |
|||
|
|||
vec.sfv_fd = ifd; |
|||
vec.sfv_flag = 0; |
|||
vec.sfv_off = offset; |
|||
vec.sfv_len = count; |
|||
|
|||
res = sendfilev (ofd, &vec, 1, &sbytes); |
|||
|
|||
if (res < 0 && sbytes) |
|||
res = sbytes; |
|||
} |
|||
|
|||
# endif |
|||
#else |
|||
res = -1; |
|||
errno = ENOSYS; |
|||
#endif |
|||
|
|||
if (res < 0 |
|||
&& (errno == ENOSYS || errno == EINVAL || errno == ENOTSOCK |
|||
#if __solaris |
|||
|| errno == EAFNOSUPPORT || errno == EPROTOTYPE |
|||
#endif |
|||
) |
|||
) |
|||
{ |
|||
/* emulate sendfile. this is a major pain in the ass */ |
|||
/* buffer size for various temporary buffers */ |
|||
#define EIO_BUFSIZE 65536 |
|||
char *eio_buf = malloc (EIO_BUFSIZE); |
|||
errno = ENOMEM; |
|||
if (!eio_buf) |
|||
return -1; |
|||
|
|||
res = 0; |
|||
|
|||
while (count) { |
|||
ssize_t cnt; |
|||
|
|||
cnt = pread (ifd, eio_buf, count > EIO_BUFSIZE ? EIO_BUFSIZE : count, offset); |
|||
|
|||
if (cnt <= 0) { |
|||
if (cnt && !res) res = -1; |
|||
break; |
|||
} |
|||
|
|||
cnt = write (ofd, eio_buf, cnt); |
|||
|
|||
if (cnt <= 0) { |
|||
if (cnt && !res) res = -1; |
|||
break; |
|||
} |
|||
|
|||
offset += cnt; |
|||
res += cnt; |
|||
count -= cnt; |
|||
} |
|||
|
|||
free(eio_buf); |
|||
} |
|||
|
|||
return res; |
|||
} |
|||
|
|||
static oi_task* |
|||
queue_shift(pthread_mutex_t *lock, oi_queue *queue) |
|||
{ |
|||
oi_queue *last = NULL; |
|||
pthread_mutex_lock(lock); |
|||
if(!oi_queue_empty(queue)) { |
|||
last = oi_queue_last(queue); |
|||
oi_queue_remove(last); |
|||
} |
|||
pthread_mutex_unlock(lock); |
|||
|
|||
if(last == NULL) |
|||
return NULL; |
|||
|
|||
return oi_queue_data(last, oi_task, queue); |
|||
} |
|||
|
|||
#define P1(name,a) { \ |
|||
t->params.name.result = name( t->params.name.a ); \ |
|||
break; \ |
|||
} |
|||
|
|||
#define P2(name,a,b) { \ |
|||
t->params.name.result = name( t->params.name.a \ |
|||
, t->params.name.b \ |
|||
); \ |
|||
break; \ |
|||
} |
|||
|
|||
#define P3(name,a,b,c) { \ |
|||
t->params.name.result = name( t->params.name.a \ |
|||
, t->params.name.b \ |
|||
, t->params.name.c \ |
|||
); \ |
|||
break; \ |
|||
} |
|||
|
|||
#define P4(name,a,b,c,d) { \ |
|||
t->params.name.result = name( t->params.name.a \ |
|||
, t->params.name.b \ |
|||
, t->params.name.c \ |
|||
, t->params.name.d \ |
|||
); \ |
|||
break; \ |
|||
} |
|||
|
|||
static void |
|||
execute_task(oi_task *t) |
|||
{ |
|||
errno = 0; |
|||
switch(t->type) { |
|||
case OI_TASK_OPEN: P3(open, pathname, flags, mode); |
|||
case OI_TASK_READ: P3(read, fd, buf, count); |
|||
case OI_TASK_WRITE: P3(write, fd, buf, count); |
|||
case OI_TASK_CLOSE: P1(close, fd); |
|||
case OI_TASK_SLEEP: P1(sleep, seconds); |
|||
case OI_TASK_SENDFILE: P4(eio__sendfile, out_fd, in_fd, offset, count); |
|||
case OI_TASK_GETADDRINFO: P4(getaddrinfo, nodename, servname, hints, res); |
|||
case OI_TASK_LSTAT: P2(lstat, path, buf); |
|||
default: |
|||
assert(0 && "unknown task type"); |
|||
break; |
|||
} |
|||
t->errorno = errno; |
|||
} |
|||
|
|||
static void |
|||
attempt_to_get_a_task(struct worker *worker) |
|||
{ |
|||
char dummy; |
|||
assert(readiness_pipe[0] > 0); |
|||
int r = read(readiness_pipe[0], &dummy, 1); |
|||
if(r == -1 && (errno != EAGAIN || errno != EINTR)) { |
|||
perror("read(readiness_pipe[0])"); |
|||
return; |
|||
} |
|||
|
|||
// 1 pop task from queue
|
|||
assert(worker->task == NULL); |
|||
oi_task *task = queue_shift(&queue_lock, &waiting_tasks); |
|||
if(task == NULL) return; |
|||
worker->task = task; |
|||
|
|||
// 2 run task
|
|||
execute_task(task); |
|||
|
|||
// 3 notify complition
|
|||
oi_async *async = task->async; |
|||
assert(async != NULL); |
|||
pthread_mutex_lock(&finished_lock); |
|||
oi_queue_insert_head(&async->finished_tasks, &task->queue); |
|||
pthread_mutex_unlock(&finished_lock); |
|||
ev_async_send(async->loop, &async->watcher); |
|||
worker->task = NULL; |
|||
|
|||
/* attempt to pull another task */ |
|||
return attempt_to_get_a_task(worker); |
|||
} |
|||
|
|||
void * |
|||
worker_loop(void *data) |
|||
{ |
|||
int r; |
|||
struct worker *worker = data; |
|||
fd_set readfds; |
|||
FD_ZERO(&readfds); |
|||
FD_SET(readiness_pipe[0], &readfds); |
|||
|
|||
active_workers++; |
|||
assert(active_workers <= NWORKERS); |
|||
|
|||
while(1) { |
|||
r = select(1+readiness_pipe[0], &readfds, 0, 0, 0); |
|||
if(r == -1) break; |
|||
attempt_to_get_a_task(worker); |
|||
} |
|||
active_workers--; |
|||
|
|||
return NULL; |
|||
} |
|||
|
|||
static struct worker* |
|||
worker_new() |
|||
{ |
|||
int r; |
|||
struct worker *worker = calloc(sizeof(struct worker), 1); |
|||
if(worker == NULL ) { return NULL; } |
|||
|
|||
worker->task = NULL; |
|||
pthread_attr_setdetachstate(&worker->thread_attr, PTHREAD_CREATE_DETACHED); |
|||
|
|||
r = pthread_create( &worker->thread |
|||
, NULL // &worker->thread_attr
|
|||
, worker_loop |
|||
, worker |
|||
); |
|||
if(r != 0) { |
|||
/* TODO: error checking */ |
|||
perror("pthread_create"); |
|||
goto error; |
|||
} |
|||
|
|||
return worker; |
|||
error: |
|||
free(worker); |
|||
return NULL; |
|||
} |
|||
|
|||
static void |
|||
start_workers() |
|||
{ |
|||
assert(readiness_pipe[0] == -1); |
|||
assert(readiness_pipe[1] == -1); |
|||
assert(active_workers == 0); |
|||
|
|||
int r = pipe(readiness_pipe); |
|||
if(r < 0) { |
|||
perror("pipe()"); |
|||
assert(0 && "TODO HANDLE ME"); |
|||
} |
|||
|
|||
/* set the write end non-blocking */ |
|||
int flags = fcntl(readiness_pipe[1], F_GETFL, 0); |
|||
r = fcntl(readiness_pipe[1], F_SETFL, flags | O_NONBLOCK); |
|||
if(r < 0) { |
|||
assert(0 && "error setting pipe to non-blocking?"); |
|||
/* TODO error report */ |
|||
} |
|||
|
|||
oi_queue_init(&waiting_tasks); |
|||
|
|||
int i; |
|||
for(i = 0; i < NWORKERS; i++) |
|||
worker_new(); |
|||
} |
|||
|
|||
/*
|
|||
static void |
|||
stop_workers() |
|||
{ |
|||
assert(0 && "TODO implement me"); |
|||
} |
|||
*/ |
|||
|
|||
static void |
|||
on_completion(struct ev_loop *loop, ev_async *watcher, int revents) |
|||
{ |
|||
oi_async *async = watcher->data; |
|||
oi_task *task; |
|||
|
|||
while((task = queue_shift(&finished_lock, &async->finished_tasks))) { |
|||
assert(task->active); |
|||
task->active = 0; |
|||
errno = task->errorno; |
|||
# define done_cb(kind) { \ |
|||
assert(task->params.kind.cb); \ |
|||
task->params.kind.cb(task, task->params.kind.result); \ |
|||
break; \ |
|||
} |
|||
switch(task->type) { |
|||
case OI_TASK_OPEN: done_cb(open); |
|||
case OI_TASK_READ: done_cb(read); |
|||
case OI_TASK_WRITE: done_cb(write); |
|||
case OI_TASK_CLOSE: done_cb(close); |
|||
case OI_TASK_SLEEP: done_cb(sleep); |
|||
case OI_TASK_SENDFILE: done_cb(eio__sendfile); |
|||
case OI_TASK_GETADDRINFO: done_cb(getaddrinfo); |
|||
case OI_TASK_LSTAT: done_cb(lstat); |
|||
} |
|||
/* the task is possibly freed by callback. do not access it again. */ |
|||
} |
|||
} |
|||
|
|||
void |
|||
oi_async_init (oi_async *async) |
|||
{ |
|||
ev_async_init(&async->watcher, on_completion); |
|||
|
|||
oi_queue_init(&async->finished_tasks); |
|||
oi_queue_init(&async->new_tasks); |
|||
|
|||
async->watcher.data = async; |
|||
} |
|||
|
|||
static void |
|||
dispatch_tasks(oi_async *async) |
|||
{ |
|||
while(!oi_queue_empty(&async->new_tasks)) { |
|||
oi_queue *last = oi_queue_last(&async->new_tasks); |
|||
oi_queue_remove(last); |
|||
oi_task *task = oi_queue_data(last, oi_task, queue); |
|||
|
|||
// 1. add task to task queue.
|
|||
pthread_mutex_lock(&queue_lock); |
|||
oi_queue_insert_head(&waiting_tasks, &task->queue); |
|||
pthread_mutex_unlock(&queue_lock); |
|||
|
|||
// 2. write byte to pipe
|
|||
char dummy; |
|||
int written = write(readiness_pipe[1], &dummy, 1); |
|||
|
|||
// 3. TODO make sure byte is written
|
|||
assert(written == 1); |
|||
} |
|||
} |
|||
|
|||
void |
|||
oi_async_attach (struct ev_loop *loop, oi_async *async) |
|||
{ |
|||
if(active_watchers == 0 && active_workers == 0) |
|||
start_workers(); |
|||
active_watchers++; |
|||
|
|||
ev_async_start(loop, &async->watcher); |
|||
async->loop = loop; |
|||
|
|||
dispatch_tasks(async); |
|||
} |
|||
|
|||
void |
|||
oi_async_detach (oi_async *async) |
|||
{ |
|||
if(async->loop == NULL) |
|||
return; |
|||
ev_async_stop(async->loop, &async->watcher); |
|||
async->loop = NULL; |
|||
active_watchers--; |
|||
if(active_watchers == 0) { |
|||
//stop_workers();
|
|||
} |
|||
} |
|||
|
|||
void |
|||
oi_async_submit (oi_async *async, oi_task *task) |
|||
{ |
|||
assert(!task->active); |
|||
assert(task->async == NULL); |
|||
task->async = async; |
|||
task->active = 1; |
|||
|
|||
oi_queue_insert_head(&async->new_tasks, &task->queue); |
|||
if(ev_is_active(&async->watcher)) { |
|||
dispatch_tasks(async); |
|||
} |
|||
} |
|||
|
@ -0,0 +1,218 @@ |
|||
#include <ev.h> |
|||
#include <pthread.h> |
|||
#include <netdb.h> |
|||
#include <oi.h> |
|||
|
|||
#ifndef oi_async_h |
|||
#define oi_async_h |
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
typedef struct oi_async oi_async; |
|||
typedef struct oi_task oi_task; |
|||
|
|||
struct oi_async { |
|||
/* private */ |
|||
ev_async watcher; |
|||
struct ev_loop *loop; |
|||
|
|||
oi_queue finished_tasks; |
|||
oi_queue new_tasks; |
|||
|
|||
/* public */ |
|||
void *data; |
|||
}; |
|||
|
|||
typedef void (*oi_task_int_cb)(oi_task *, int result); |
|||
typedef void (*oi_task_uint_cb)(oi_task *, unsigned int result); |
|||
typedef void (*oi_task_ssize_cb)(oi_task *, ssize_t result); |
|||
|
|||
struct oi_task { |
|||
/* private */ |
|||
oi_async *async; |
|||
oi_queue queue; |
|||
int type; |
|||
union { |
|||
|
|||
struct { |
|||
const char *pathname; |
|||
int flags; |
|||
mode_t mode; |
|||
oi_task_int_cb cb; |
|||
int result; |
|||
} open; |
|||
|
|||
struct { |
|||
int fd; |
|||
void *buf; |
|||
size_t count; |
|||
oi_task_ssize_cb cb; |
|||
ssize_t result; |
|||
} read; |
|||
|
|||
struct { |
|||
int fd; |
|||
const void *buf; |
|||
size_t count; |
|||
oi_task_ssize_cb cb; |
|||
ssize_t result; |
|||
} write; |
|||
|
|||
struct { |
|||
int fd; |
|||
oi_task_int_cb cb; |
|||
int result; |
|||
} close; |
|||
|
|||
struct { |
|||
unsigned int seconds; |
|||
oi_task_uint_cb cb; |
|||
unsigned int result; |
|||
} sleep; |
|||
|
|||
struct { |
|||
int out_fd; |
|||
int in_fd; |
|||
off_t offset; |
|||
size_t count; |
|||
oi_task_ssize_cb cb; |
|||
ssize_t result; |
|||
} eio__sendfile; |
|||
|
|||
struct { |
|||
const char *nodename; /* restrict ? */ |
|||
const char *servname; /* restrict ? */ |
|||
struct addrinfo *hints; |
|||
struct addrinfo **res; /* restrict ? */ |
|||
oi_task_int_cb cb; |
|||
int result; |
|||
} getaddrinfo; |
|||
|
|||
struct { |
|||
const char *path; |
|||
struct stat *buf; |
|||
oi_task_int_cb cb; |
|||
int result; |
|||
} lstat; |
|||
|
|||
} params; |
|||
|
|||
/* read-only */ |
|||
volatile unsigned active:1; |
|||
int errorno; |
|||
|
|||
/* public */ |
|||
void *data; |
|||
}; |
|||
|
|||
void oi_async_init (oi_async *); |
|||
void oi_async_attach (struct ev_loop *loop, oi_async *); |
|||
void oi_async_detach (oi_async *); |
|||
void oi_async_submit (oi_async *, oi_task *); |
|||
|
|||
/* To submit a task for async processing
|
|||
* (0) allocate memory for your task |
|||
* (1) initialize the task with one of the functions below |
|||
* (2) optionally set the task->data pointer |
|||
* (3) oi_async_submit() the task |
|||
*/ |
|||
|
|||
enum { OI_TASK_OPEN |
|||
, OI_TASK_READ |
|||
, OI_TASK_WRITE |
|||
, OI_TASK_CLOSE |
|||
, OI_TASK_SLEEP |
|||
, OI_TASK_SENDFILE |
|||
, OI_TASK_GETADDRINFO |
|||
, OI_TASK_LSTAT |
|||
}; |
|||
|
|||
#define oi_task_init_common(task, _type) do {\ |
|||
(task)->active = 0;\ |
|||
(task)->async = NULL;\ |
|||
(task)->type = _type;\ |
|||
} while(0) |
|||
|
|||
static inline void |
|||
oi_task_init_open(oi_task *t, oi_task_int_cb cb, const char *pathname, int flags, mode_t mode) |
|||
{ |
|||
oi_task_init_common(t, OI_TASK_OPEN); |
|||
t->params.open.cb = cb; |
|||
t->params.open.pathname = pathname; |
|||
t->params.open.flags = flags; |
|||
t->params.open.mode = mode; |
|||
} |
|||
|
|||
static inline void |
|||
oi_task_init_read(oi_task *t, oi_task_ssize_cb cb, int fd, void *buf, size_t count) |
|||
{ |
|||
oi_task_init_common(t, OI_TASK_READ); |
|||
t->params.read.cb = cb; |
|||
t->params.read.fd = fd; |
|||
t->params.read.buf = buf; |
|||
t->params.read.count = count; |
|||
} |
|||
|
|||
static inline void |
|||
oi_task_init_write(oi_task *t, oi_task_ssize_cb cb, int fd, const void *buf, size_t count) |
|||
{ |
|||
oi_task_init_common(t, OI_TASK_WRITE); |
|||
t->params.write.cb = cb; |
|||
t->params.write.fd = fd; |
|||
t->params.write.buf = buf; |
|||
t->params.write.count = count; |
|||
} |
|||
|
|||
static inline void |
|||
oi_task_init_close(oi_task *t, oi_task_int_cb cb, int fd) |
|||
{ |
|||
oi_task_init_common(t, OI_TASK_CLOSE); |
|||
t->params.close.cb = cb; |
|||
t->params.close.fd = fd; |
|||
} |
|||
|
|||
static inline void |
|||
oi_task_init_sleep(oi_task *t, oi_task_uint_cb cb, unsigned int seconds) |
|||
{ |
|||
oi_task_init_common(t, OI_TASK_SLEEP); |
|||
t->params.sleep.cb = cb; |
|||
t->params.sleep.seconds = seconds; |
|||
} |
|||
|
|||
static inline void |
|||
oi_task_init_sendfile(oi_task *t, oi_task_ssize_cb cb, int out_fd, int in_fd, off_t offset, size_t count) |
|||
{ |
|||
oi_task_init_common(t, OI_TASK_SENDFILE); |
|||
t->params.eio__sendfile.cb = cb; |
|||
t->params.eio__sendfile.out_fd = out_fd; |
|||
t->params.eio__sendfile.in_fd = in_fd; |
|||
t->params.eio__sendfile.offset = offset; |
|||
t->params.eio__sendfile.count = count; |
|||
} |
|||
|
|||
static inline void |
|||
oi_task_init_getaddrinfo(oi_task *t, oi_task_int_cb cb, const char *node, |
|||
const char *service, struct addrinfo *hints, struct addrinfo **res) |
|||
{ |
|||
oi_task_init_common(t, OI_TASK_GETADDRINFO); |
|||
t->params.getaddrinfo.cb = cb; |
|||
t->params.getaddrinfo.nodename = node; |
|||
t->params.getaddrinfo.servname = service; |
|||
t->params.getaddrinfo.hints = hints; |
|||
t->params.getaddrinfo.res = res; |
|||
} |
|||
|
|||
static inline void |
|||
oi_task_init_lstat(oi_task *t, oi_task_int_cb cb, const char *path, struct stat *buf) |
|||
{ |
|||
oi_task_init_common(t, OI_TASK_LSTAT); |
|||
t->params.lstat.cb = cb; |
|||
t->params.lstat.path = path; |
|||
t->params.lstat.buf = buf; |
|||
} |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
|||
#endif /* oi_async_h */ |
@ -0,0 +1,41 @@ |
|||
#include <oi_buf.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
|
|||
void oi_buf_destroy |
|||
( oi_buf *buf |
|||
) |
|||
{ |
|||
free(buf->base); |
|||
free(buf); |
|||
} |
|||
|
|||
oi_buf * oi_buf_new2 |
|||
( size_t len |
|||
) |
|||
{ |
|||
oi_buf *buf = malloc(sizeof(oi_buf)); |
|||
if(!buf) |
|||
return NULL; |
|||
buf->base = malloc(len); |
|||
if(!buf->base) { |
|||
free(buf); |
|||
return NULL; |
|||
} |
|||
buf->len = len; |
|||
buf->release = oi_buf_destroy; |
|||
return buf; |
|||
} |
|||
|
|||
oi_buf * oi_buf_new |
|||
( const char *base |
|||
, size_t len |
|||
) |
|||
{ |
|||
oi_buf *buf = oi_buf_new2(len); |
|||
if(!buf) |
|||
return NULL; |
|||
memcpy(buf->base, base, len); |
|||
return buf; |
|||
} |
|||
|
@ -0,0 +1,30 @@ |
|||
#include <oi_queue.h> |
|||
|
|||
#ifndef oi_buf_h |
|||
#define oi_buf_h |
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
typedef struct oi_buf oi_buf; |
|||
|
|||
struct oi_buf { |
|||
/* public */ |
|||
char *base; |
|||
size_t len; |
|||
void (*release) (oi_buf *); /* called when oi is done with the object */ |
|||
void *data; |
|||
|
|||
/* private */ |
|||
size_t written; |
|||
oi_queue queue; |
|||
}; |
|||
|
|||
oi_buf * oi_buf_new (const char* base, size_t len); |
|||
oi_buf * oi_buf_new2 (size_t len); |
|||
void oi_buf_destroy (oi_buf *); |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
|||
#endif // oi_buf_h
|
@ -0,0 +1,25 @@ |
|||
#ifndef oi_error_h |
|||
#define oi_error_h |
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
struct oi_error { |
|||
enum { OI_ERROR_GNUTLS |
|||
, OI_ERROR_EV |
|||
, OI_ERROR_CLOSE |
|||
, OI_ERROR_SHUTDOWN |
|||
, OI_ERROR_OPEN |
|||
, OI_ERROR_SEND |
|||
, OI_ERROR_RECV |
|||
, OI_ERROR_WRITE |
|||
, OI_ERROR_READ |
|||
, OI_ERROR_SENDFILE |
|||
} domain; |
|||
int code; /* errno */ |
|||
}; |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
|||
#endif // oi_error_h
|
@ -0,0 +1,391 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <unistd.h> |
|||
#include <assert.h> |
|||
#include <errno.h> |
|||
|
|||
#include <ev.h> |
|||
#include <oi.h> |
|||
|
|||
#define RELEASE_BUF(buf) if(buf->release) { buf->release(buf); } |
|||
#define DRAIN_CB(file) if(file->on_drain) { file->on_drain(file); } |
|||
#define RAISE_ERROR(s, _domain, _code) do { \ |
|||
if(s->on_error) { \ |
|||
struct oi_error __oi_error; \ |
|||
__oi_error.domain = _domain; \ |
|||
__oi_error.code = _code; \ |
|||
s->on_error(s, __oi_error); \ |
|||
} \ |
|||
} while(0) \ |
|||
|
|||
/* forwards */ |
|||
static void dispatch_write_buf (oi_file *file); |
|||
static void maybe_do_read (oi_file *file); |
|||
|
|||
static void |
|||
after_read(oi_task *task, ssize_t recved) |
|||
{ |
|||
oi_file *file = task->data; |
|||
|
|||
if(recved == -1) { |
|||
RAISE_ERROR(file, OI_ERROR_READ, errno); |
|||
return; |
|||
} |
|||
|
|||
if(recved == 0) |
|||
oi_file_read_stop(file); |
|||
|
|||
if(file->on_read) |
|||
file->on_read(file, recved); |
|||
|
|||
maybe_do_read(file); |
|||
} |
|||
|
|||
static void |
|||
maybe_do_read(oi_file *file) |
|||
{ |
|||
if ( file->read_buffer == NULL |
|||
|| file->write_buf != NULL |
|||
|| file->write_socket != NULL |
|||
|| !oi_queue_empty(&file->write_queue) |
|||
|| file->io_task.active |
|||
) return; |
|||
|
|||
assert(file->fd > 0); |
|||
|
|||
oi_task_init_read ( &file->io_task |
|||
, after_read |
|||
, file->fd |
|||
, file->read_buffer |
|||
, file->read_buffer_size |
|||
); |
|||
file->io_task.data = file; |
|||
oi_async_submit(&file->async, &file->io_task); |
|||
} |
|||
|
|||
static void |
|||
submit_read (oi_file *file) |
|||
{ |
|||
} |
|||
|
|||
int |
|||
oi_file_init (oi_file *file) |
|||
{ |
|||
oi_async_init(&file->async); |
|||
file->async.data = file; |
|||
|
|||
oi_queue_init(&file->write_queue); |
|||
|
|||
file->fd = -1; |
|||
file->loop = NULL; |
|||
file->write_buf = NULL; |
|||
file->read_buffer = NULL; |
|||
|
|||
file->on_open = NULL; |
|||
file->on_read = NULL; |
|||
file->on_drain = NULL; |
|||
file->on_error = NULL; |
|||
file->on_close = NULL; |
|||
return 0; |
|||
} |
|||
|
|||
void |
|||
oi_file_read_start (oi_file *file, void *buffer, size_t bufsize) |
|||
{ |
|||
file->read_buffer = buffer; |
|||
file->read_buffer_size = bufsize; |
|||
maybe_do_read(file); |
|||
} |
|||
|
|||
void |
|||
oi_file_read_stop (oi_file *file) |
|||
{ |
|||
file->read_buffer = NULL; |
|||
} |
|||
|
|||
void |
|||
oi_api_free_buf_with_heap_base(oi_buf *buf) |
|||
{ |
|||
free(buf->base); |
|||
free(buf); |
|||
} |
|||
|
|||
static void |
|||
after_open(oi_task *task, int result) |
|||
{ |
|||
oi_file *file = task->data; |
|||
|
|||
if(result == -1) { |
|||
RAISE_ERROR(file, OI_ERROR_OPEN, errno); |
|||
return; |
|||
} |
|||
|
|||
file->fd = result; |
|||
|
|||
if(file->on_open) { |
|||
file->on_open(file); |
|||
} |
|||
|
|||
maybe_do_read(file); |
|||
} |
|||
|
|||
int |
|||
oi_file_open_path (oi_file *file, const char *path, int flags, mode_t mode) |
|||
{ |
|||
if(file->fd >= 0) |
|||
return -1; |
|||
oi_task_init_open( &file->io_task |
|||
, after_open |
|||
, path |
|||
, flags |
|||
, mode |
|||
); |
|||
file->io_task.data = file; |
|||
oi_async_submit(&file->async, &file->io_task); |
|||
return 0; |
|||
} |
|||
|
|||
int |
|||
oi_file_open_stdin (oi_file *file) |
|||
{ |
|||
if(file->fd >= 0) |
|||
return -1; |
|||
file->fd = STDIN_FILENO; |
|||
if(file->on_open) |
|||
file->on_open(file); |
|||
return 0; |
|||
} |
|||
|
|||
int |
|||
oi_file_open_stdout (oi_file *file) |
|||
{ |
|||
if(file->fd >= 0) |
|||
return -1; |
|||
file->fd = STDOUT_FILENO; |
|||
if(file->on_open) |
|||
file->on_open(file); |
|||
return 0; |
|||
} |
|||
|
|||
int |
|||
oi_file_open_stderr (oi_file *file) |
|||
{ |
|||
if(file->fd >= 0) |
|||
return -1; |
|||
file->fd = STDERR_FILENO; |
|||
if(file->on_open) |
|||
file->on_open(file); |
|||
return 0; |
|||
} |
|||
|
|||
void |
|||
oi_file_attach (oi_file *file, struct ev_loop *loop) |
|||
{ |
|||
oi_async_attach (loop, &file->async); |
|||
file->loop = loop; |
|||
} |
|||
|
|||
void |
|||
oi_file_detach (oi_file *file) |
|||
{ |
|||
oi_async_detach (&file->async); |
|||
file->loop = NULL; |
|||
} |
|||
|
|||
static void |
|||
after_write(oi_task *task, ssize_t result) |
|||
{ |
|||
oi_file *file = task->data; |
|||
|
|||
if(result == -1) { |
|||
RAISE_ERROR(file, OI_ERROR_WRITE, errno); |
|||
return; |
|||
} |
|||
|
|||
assert(file->write_buf != NULL); |
|||
oi_buf *buf = file->write_buf; |
|||
|
|||
buf->written += result; |
|||
if(buf->written < buf->len) { |
|||
oi_task_init_write ( &file->io_task |
|||
, after_write |
|||
, file->fd |
|||
, buf->base + buf->written |
|||
, buf->len - buf->written |
|||
); |
|||
file->io_task.data = file; |
|||
oi_async_submit(&file->async, &file->io_task); |
|||
return; |
|||
} |
|||
|
|||
assert(buf->written == buf->len); |
|||
|
|||
RELEASE_BUF(file->write_buf); |
|||
file->write_buf = NULL; |
|||
|
|||
if(oi_queue_empty(&file->write_queue)) { |
|||
DRAIN_CB(file); |
|||
maybe_do_read(file); |
|||
} else { |
|||
dispatch_write_buf(file); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
static void |
|||
dispatch_write_buf(oi_file *file) |
|||
{ |
|||
if(file->write_buf != NULL) |
|||
return; |
|||
if(oi_queue_empty(&file->write_queue)) |
|||
return; |
|||
|
|||
oi_queue *q = oi_queue_last(&file->write_queue); |
|||
oi_queue_remove(q); |
|||
oi_buf *buf = file->write_buf = oi_queue_data(q, oi_buf, queue); |
|||
|
|||
assert(!file->io_task.active); |
|||
oi_task_init_write ( &file->io_task |
|||
, after_write |
|||
, file->fd |
|||
, buf->base + buf->written |
|||
, buf->len - buf->written |
|||
); |
|||
file->io_task.data = file; |
|||
oi_async_submit(&file->async, &file->io_task); |
|||
} |
|||
|
|||
int |
|||
oi_file_write (oi_file *file, oi_buf *buf) |
|||
{ |
|||
if(file->fd < 0) |
|||
return -1; |
|||
if(file->read_buffer) |
|||
return -2; |
|||
/* TODO better business check*/ |
|||
|
|||
buf->written = 0; |
|||
oi_queue_insert_head(&file->write_queue, &buf->queue); |
|||
dispatch_write_buf(file); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
// Writes a string to the file.
|
|||
// NOTE: Allocates memory. Avoid for performance applications.
|
|||
int |
|||
oi_file_write_simple (oi_file *file, const char *str, size_t len) |
|||
{ |
|||
if(file->fd < 0) |
|||
return -1; |
|||
if(file->read_buffer) |
|||
return -2; |
|||
/* TODO better business check*/ |
|||
|
|||
oi_buf *buf = malloc(sizeof(oi_buf)); |
|||
buf->base = malloc(len); |
|||
memcpy(buf->base, str, len); |
|||
buf->len = len; |
|||
buf->release = oi_api_free_buf_with_heap_base; |
|||
|
|||
oi_file_write(file, buf); |
|||
return 0; |
|||
} |
|||
|
|||
static void |
|||
clear_write_queue(oi_file *file) |
|||
{ |
|||
while(!oi_queue_empty(&file->write_queue)) { |
|||
oi_queue *q = oi_queue_last(&file->write_queue); |
|||
oi_queue_remove(q); |
|||
oi_buf *buf = oi_queue_data(q, oi_buf, queue); |
|||
RELEASE_BUF(buf); |
|||
} |
|||
} |
|||
|
|||
static void |
|||
after_close(oi_task *task, int result) |
|||
{ |
|||
oi_file *file = task->data; |
|||
|
|||
assert(oi_queue_empty(&file->write_queue)); |
|||
|
|||
if(result == -1) { |
|||
RAISE_ERROR(file, OI_ERROR_CLOSE, errno); |
|||
return; |
|||
// TODO try to close again?
|
|||
} |
|||
|
|||
file->fd = -1; |
|||
// TODO deinit task_queue, detach thread_pool_result_watcher
|
|||
|
|||
if(file->on_close) { |
|||
file->on_close(file); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
void |
|||
oi_file_close (oi_file *file) |
|||
{ |
|||
assert(file->fd >= 0 && "file not open!"); |
|||
clear_write_queue(file); |
|||
oi_task_init_close ( &file->io_task |
|||
, after_close |
|||
, file->fd |
|||
); |
|||
file->io_task.data = file; |
|||
oi_async_submit(&file->async, &file->io_task); |
|||
} |
|||
|
|||
static void |
|||
after_sendfile(oi_task *task, ssize_t sent) |
|||
{ |
|||
oi_file *file = task->data; |
|||
oi_socket *socket = file->write_socket; |
|||
assert(socket != NULL); |
|||
file->write_socket = NULL; |
|||
|
|||
if(sent == -1) { |
|||
RAISE_ERROR(file, OI_ERROR_SENDFILE, errno); |
|||
return; |
|||
} |
|||
|
|||
if(socket->on_drain) { |
|||
socket->on_drain(socket); |
|||
} |
|||
|
|||
maybe_do_read(file); |
|||
} |
|||
|
|||
int |
|||
oi_file_send (oi_file *file, oi_socket *destination, off_t offset, size_t count) |
|||
{ |
|||
if(file->fd < 0) |
|||
return -1; |
|||
if(file->read_buffer) |
|||
return -2; |
|||
/* TODO better business check*/ |
|||
|
|||
assert(file->write_socket == NULL); |
|||
// (1) make sure the write queue on the socket is cleared.
|
|||
//
|
|||
// (2)
|
|||
//
|
|||
file->write_socket = destination; |
|||
oi_task_init_sendfile ( &file->io_task |
|||
, after_sendfile |
|||
, destination->fd |
|||
, file->fd |
|||
, offset |
|||
, count |
|||
); |
|||
file->io_task.data = file; |
|||
oi_async_submit(&file->async, &file->io_task); |
|||
|
|||
return 0; |
|||
} |
|||
|
@ -0,0 +1,58 @@ |
|||
#include <oi.h> |
|||
#include <ev.h> |
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
#ifndef oi_file_h |
|||
#define oi_file_h |
|||
|
|||
typedef struct oi_file oi_file; |
|||
|
|||
int oi_file_init (oi_file *); |
|||
|
|||
void oi_file_attach (oi_file *, struct ev_loop *); |
|||
void oi_file_detach (oi_file *); |
|||
|
|||
/* WARNING oi_file_open_path: path argument must be valid until oi_file
|
|||
* object is closed and the on_close() callback is made. oi does not strdup |
|||
* the path pointer. */ |
|||
int oi_file_open_path (oi_file *, const char *path, int flags, mode_t mode); |
|||
int oi_file_open_stdin (oi_file *); |
|||
int oi_file_open_stdout (oi_file *); |
|||
int oi_file_open_stderr (oi_file *); |
|||
|
|||
void oi_file_read_start (oi_file *, void *buffer, size_t bufsize); |
|||
void oi_file_read_stop (oi_file *); |
|||
int oi_file_write (oi_file *, oi_buf *to_write); |
|||
int oi_file_write_simple (oi_file *, const char *, size_t); |
|||
int oi_file_send (oi_file *source, oi_socket *destination, off_t offset, size_t length); |
|||
void oi_file_close (oi_file *); |
|||
|
|||
struct oi_file { |
|||
/* private */ |
|||
oi_async async; |
|||
oi_task io_task; |
|||
struct ev_loop *loop; |
|||
oi_queue write_queue; |
|||
oi_buf *write_buf; /* TODO this pointer is unnecessary - remove and just look at first element of the queue */ |
|||
oi_socket *write_socket; |
|||
void *read_buffer; |
|||
size_t read_buffer_size; |
|||
|
|||
/* read-only */ |
|||
int fd; |
|||
|
|||
/* public */ |
|||
void (*on_open) (oi_file *); |
|||
void (*on_read) (oi_file *, size_t count); |
|||
void (*on_drain) (oi_file *); |
|||
void (*on_error) (oi_file *, struct oi_error); |
|||
void (*on_close) (oi_file *); |
|||
void *data; |
|||
}; |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
|||
#endif /* oi_file_h */ |
@ -0,0 +1,69 @@ |
|||
/* Copyright (C) 2002-2009 Igor Sysoev
|
|||
* |
|||
* Redistribution and use in source and binary forms, with or without |
|||
* modification, are permitted provided that the following conditions |
|||
* are met: |
|||
* 1. Redistributions of source code must retain the above copyright |
|||
* notice, this list of conditions and the following disclaimer. |
|||
* 2. Redistributions in binary form must reproduce the above copyright |
|||
* notice, this list of conditions and the following disclaimer in the |
|||
* documentation and/or other materials provided with the distribution. |
|||
* |
|||
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
|||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|||
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE |
|||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|||
* SUCH DAMAGE. |
|||
*/ |
|||
#ifndef oi_queue_h |
|||
#define oi_queue_h |
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
#include <stddef.h> /* offsetof() */ |
|||
|
|||
typedef struct oi_queue oi_queue; |
|||
struct oi_queue { |
|||
oi_queue *prev; |
|||
oi_queue *next; |
|||
}; |
|||
|
|||
#define oi_queue_init(q) \ |
|||
(q)->prev = q; \ |
|||
(q)->next = q |
|||
|
|||
#define oi_queue_empty(h) \ |
|||
(h == (h)->prev) |
|||
|
|||
#define oi_queue_insert_head(h, x) \ |
|||
(x)->next = (h)->next; \ |
|||
(x)->next->prev = x; \ |
|||
(x)->prev = h; \ |
|||
(h)->next = x |
|||
|
|||
#define oi_queue_head(h) \ |
|||
(h)->next |
|||
|
|||
#define oi_queue_last(h) \ |
|||
(h)->prev |
|||
|
|||
#define oi_queue_remove(x) \ |
|||
(x)->next->prev = (x)->prev; \ |
|||
(x)->prev->next = (x)->next; \ |
|||
(x)->prev = NULL; \ |
|||
(x)->next = NULL |
|||
|
|||
#define oi_queue_data(q, type, link) \ |
|||
(type *) ((unsigned char *) q - offsetof(type, link)) |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
|||
#endif // oi_queue_h
|
@ -0,0 +1,941 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <assert.h> |
|||
#include <unistd.h> /* close() */ |
|||
#include <fcntl.h> /* fcntl() */ |
|||
#include <errno.h> /* for the default methods */ |
|||
#include <string.h> /* memset */ |
|||
|
|||
#include <netinet/tcp.h> /* TCP_NODELAY */ |
|||
|
|||
#include <ev.h> |
|||
#include <oi_socket.h> |
|||
|
|||
#if HAVE_GNUTLS |
|||
# include <gnutls/gnutls.h> |
|||
# define GNUTLS_NEED_WRITE (gnutls_record_get_direction(socket->session) == 1) |
|||
# define GNUTLS_NEED_READ (gnutls_record_get_direction(socket->session) == 0) |
|||
#endif |
|||
|
|||
#undef TRUE |
|||
#define TRUE 1 |
|||
#undef FALSE |
|||
#define FALSE 0 |
|||
#undef MIN |
|||
#define MIN(a,b) ((a) < (b) ? (a) : (b)) |
|||
|
|||
#define OKAY 0 |
|||
#define AGAIN 1 |
|||
#define ERROR 2 |
|||
|
|||
#define RAISE_ERROR(s, _domain, _code) do { \ |
|||
if(s->on_error) { \ |
|||
struct oi_error __oi_error; \ |
|||
__oi_error.domain = _domain; \ |
|||
__oi_error.code = _code; \ |
|||
s->on_error(s, __oi_error); \ |
|||
} \ |
|||
} while(0) \ |
|||
|
|||
static int |
|||
full_close(oi_socket *socket) |
|||
{ |
|||
if(-1 == close(socket->fd) && errno == EINTR) { |
|||
/* TODO fd still open. next loop call close again? */ |
|||
assert(0 && "implement me"); |
|||
return ERROR; |
|||
} |
|||
|
|||
socket->read_action = NULL; |
|||
socket->write_action = NULL; |
|||
|
|||
if(socket->loop) { |
|||
ev_feed_event(socket->loop, &socket->read_watcher, EV_READ); |
|||
} |
|||
return OKAY; |
|||
} |
|||
|
|||
static int |
|||
half_close(oi_socket *socket) |
|||
{ |
|||
int r = shutdown(socket->fd, SHUT_WR); |
|||
|
|||
if(r == -1) { |
|||
RAISE_ERROR(socket, OI_ERROR_SHUTDOWN, errno); |
|||
return ERROR; |
|||
} |
|||
|
|||
socket->write_action = NULL; |
|||
|
|||
/* TODO set timer to zero so we get a callback soon */ |
|||
return OKAY; |
|||
} |
|||
|
|||
static void |
|||
update_write_buffer_after_send(oi_socket *socket, ssize_t sent) |
|||
{ |
|||
oi_queue *q = oi_queue_last(&socket->out_stream); |
|||
oi_buf *to_write = oi_queue_data(q, oi_buf, queue); |
|||
to_write->written += sent; |
|||
socket->written += sent; |
|||
|
|||
if(to_write->written == to_write->len) { |
|||
|
|||
oi_queue_remove(q); |
|||
|
|||
if(to_write->release) { |
|||
to_write->release(to_write); |
|||
} |
|||
|
|||
if(oi_queue_empty(&socket->out_stream)) { |
|||
ev_io_stop(socket->loop, &socket->write_watcher); |
|||
if(socket->on_drain) |
|||
socket->on_drain(socket); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
#if HAVE_GNUTLS |
|||
static int secure_socket_send(oi_socket *socket); |
|||
static int secure_socket_recv(oi_socket *socket); |
|||
|
|||
/* TODO can this be done without ignoring SIGPIPE? */ |
|||
static ssize_t |
|||
nosigpipe_push(gnutls_transport_ptr_t data, const void *buf, size_t len) |
|||
{ |
|||
oi_socket *socket = (oi_socket*)data; |
|||
assert(socket->secure); |
|||
int flags = 0; |
|||
#ifdef MSG_NOSIGNAL |
|||
flags |= MSG_NOSIGNAL; |
|||
#endif |
|||
#ifdef MSG_DONTWAIT |
|||
flags |= MSG_DONTWAIT; |
|||
#endif |
|||
int r = send(socket->fd, buf, len, flags); |
|||
|
|||
if(r == -1) { |
|||
gnutls_transport_set_errno(socket->session, errno); /* necessary ? */ |
|||
} |
|||
|
|||
return r; |
|||
} |
|||
|
|||
static int |
|||
secure_handshake(oi_socket *socket) |
|||
{ |
|||
assert(socket->secure); |
|||
|
|||
int r = gnutls_handshake(socket->session); |
|||
|
|||
if(gnutls_error_is_fatal(r)) { |
|||
RAISE_ERROR(socket, OI_ERROR_GNUTLS, r); |
|||
return ERROR; |
|||
} |
|||
|
|||
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) |
|||
return AGAIN; |
|||
|
|||
oi_socket_reset_timeout(socket); |
|||
|
|||
if(!socket->connected) { |
|||
socket->connected = TRUE; |
|||
if(socket->on_connect) |
|||
socket->on_connect(socket); |
|||
} |
|||
|
|||
if(socket->read_action) |
|||
socket->read_action = secure_socket_recv; |
|||
|
|||
if(socket->write_action) |
|||
socket->write_action = secure_socket_send; |
|||
|
|||
return OKAY; |
|||
} |
|||
|
|||
static int |
|||
secure_socket_send(oi_socket *socket) |
|||
{ |
|||
ssize_t sent; |
|||
|
|||
if(oi_queue_empty(&socket->out_stream)) { |
|||
ev_io_stop(socket->loop, &socket->write_watcher); |
|||
return AGAIN; |
|||
} |
|||
|
|||
oi_queue *q = oi_queue_last(&socket->out_stream); |
|||
oi_buf *to_write = oi_queue_data(q, oi_buf, queue); |
|||
|
|||
assert(socket->secure); |
|||
|
|||
sent = gnutls_record_send( socket->session |
|||
, to_write->base + to_write->written |
|||
, to_write->len - to_write->written |
|||
); |
|||
|
|||
if(gnutls_error_is_fatal(sent)) { |
|||
RAISE_ERROR(socket, OI_ERROR_GNUTLS, sent); |
|||
return ERROR; |
|||
} |
|||
|
|||
if(sent == 0) |
|||
return AGAIN; |
|||
|
|||
oi_socket_reset_timeout(socket); |
|||
|
|||
if(sent == GNUTLS_E_INTERRUPTED || sent == GNUTLS_E_AGAIN) { |
|||
if(GNUTLS_NEED_READ) { |
|||
if(socket->read_action) { |
|||
socket->read_action = secure_socket_send; |
|||
} else { |
|||
/* TODO GnuTLS needs read but already got EOF */ |
|||
assert(0 && "needs read but already got EOF"); |
|||
return ERROR; |
|||
} |
|||
} |
|||
return AGAIN; |
|||
} |
|||
|
|||
if(sent > 0) { |
|||
/* make sure the callbacks are correct */ |
|||
if(socket->read_action) |
|||
socket->read_action = secure_socket_recv; |
|||
update_write_buffer_after_send(socket, sent); |
|||
return OKAY; |
|||
} |
|||
|
|||
assert(0 && "Unhandled return code from gnutls_record_send()!"); |
|||
return ERROR; |
|||
} |
|||
|
|||
static int |
|||
secure_socket_recv(oi_socket *socket) |
|||
{ |
|||
char recv_buffer[TCP_MAXWIN]; |
|||
size_t recv_buffer_size = MIN(TCP_MAXWIN, socket->chunksize); |
|||
ssize_t recved; |
|||
|
|||
assert(socket->secure); |
|||
|
|||
recved = gnutls_record_recv(socket->session, recv_buffer, recv_buffer_size); |
|||
|
|||
//printf("secure socket recv %d %p\n", recved, socket->on_connect);
|
|||
|
|||
if(gnutls_error_is_fatal(recved)) { |
|||
RAISE_ERROR(socket, OI_ERROR_GNUTLS, recved); |
|||
return ERROR; |
|||
} |
|||
|
|||
if(recved == GNUTLS_E_INTERRUPTED || recved == GNUTLS_E_AGAIN) { |
|||
if(GNUTLS_NEED_WRITE) { |
|||
if(socket->write_action) { |
|||
printf("need write\n"); |
|||
socket->write_action = secure_socket_recv; |
|||
} else { |
|||
/* TODO GnuTLS needs send but already closed write end */ |
|||
assert(0 && "needs read but cannot"); |
|||
return ERROR; |
|||
} |
|||
} |
|||
return AGAIN; |
|||
} |
|||
|
|||
oi_socket_reset_timeout(socket); |
|||
|
|||
/* A server may also receive GNUTLS_E_REHANDSHAKE when a client has
|
|||
* initiated a handshake. In that case the server can only initiate a |
|||
* handshake or terminate the connection. */ |
|||
if(recved == GNUTLS_E_REHANDSHAKE) { |
|||
if(socket->write_action) { |
|||
socket->read_action = secure_handshake; |
|||
socket->write_action = secure_handshake; |
|||
return OKAY; |
|||
} else { |
|||
/* TODO */ |
|||
assert(0 && "needs read but cannot"); |
|||
return ERROR; |
|||
} |
|||
} |
|||
|
|||
if(recved >= 0) { |
|||
/* Got EOF */ |
|||
if(recved == 0) |
|||
socket->read_action = NULL; |
|||
|
|||
if(socket->write_action) |
|||
socket->write_action = secure_socket_send; |
|||
|
|||
if(socket->on_read) { socket->on_read(socket, recv_buffer, recved); } |
|||
|
|||
return OKAY; |
|||
} |
|||
|
|||
assert(0 && "Unhandled return code from gnutls_record_send()!"); |
|||
return ERROR; |
|||
} |
|||
|
|||
static int |
|||
secure_goodbye(oi_socket *socket, gnutls_close_request_t how) |
|||
{ |
|||
assert(socket->secure); |
|||
|
|||
int r = gnutls_bye(socket->session, how); |
|||
|
|||
if(gnutls_error_is_fatal(r)) { |
|||
RAISE_ERROR(socket, OI_ERROR_GNUTLS, r); |
|||
return ERROR; |
|||
} |
|||
|
|||
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) |
|||
return AGAIN; |
|||
|
|||
return OKAY; |
|||
} |
|||
|
|||
static int |
|||
secure_full_goodbye(oi_socket *socket) |
|||
{ |
|||
int r = secure_goodbye(socket, GNUTLS_SHUT_RDWR); |
|||
if(OKAY == r) { |
|||
return full_close(socket); |
|||
} |
|||
return r; |
|||
} |
|||
|
|||
static int |
|||
secure_half_goodbye(oi_socket *socket) |
|||
{ |
|||
int r = secure_goodbye(socket, GNUTLS_SHUT_WR); |
|||
if(OKAY == r) { |
|||
return half_close(socket); |
|||
} |
|||
return r; |
|||
} |
|||
|
|||
/* Tells the socket to use transport layer security (SSL). liboi does not
|
|||
* want to make any decisions about security requirements, so the |
|||
* majoirty of GnuTLS configuration is left to the user. Only the transport |
|||
* layer of GnuTLS is controlled by liboi. |
|||
* |
|||
* That is, do not use gnutls_transport_* functions. |
|||
* Do use the rest of GnuTLS's API. |
|||
*/ |
|||
void |
|||
oi_socket_set_secure_session (oi_socket *socket, gnutls_session_t session) |
|||
{ |
|||
socket->session = session; |
|||
socket->secure = TRUE; |
|||
} |
|||
#endif /* HAVE GNUTLS */ |
|||
|
|||
static int |
|||
socket_send(oi_socket *socket) |
|||
{ |
|||
ssize_t sent; |
|||
|
|||
assert(socket->secure == FALSE); |
|||
|
|||
if(oi_queue_empty(&socket->out_stream)) { |
|||
ev_io_stop(socket->loop, &socket->write_watcher); |
|||
return AGAIN; |
|||
} |
|||
|
|||
oi_queue *q = oi_queue_last(&socket->out_stream); |
|||
oi_buf *to_write = oi_queue_data(q, oi_buf, queue); |
|||
|
|||
int flags = 0; |
|||
#ifdef MSG_NOSIGNAL |
|||
flags |= MSG_NOSIGNAL; |
|||
#endif |
|||
#ifdef MSG_DONTWAIT |
|||
flags |= MSG_DONTWAIT; |
|||
#endif |
|||
|
|||
/* TODO use writev() here */ |
|||
|
|||
sent = send( socket->fd |
|||
, to_write->base + to_write->written |
|||
, to_write->len - to_write->written |
|||
, flags |
|||
); |
|||
|
|||
if(sent < 0) { |
|||
switch(errno) { |
|||
case EAGAIN: |
|||
return AGAIN; |
|||
|
|||
case ECONNREFUSED: |
|||
case ECONNRESET: |
|||
socket->write_action = NULL; |
|||
/* TODO maybe just clear write buffer instead of error?
|
|||
* They should be able to read still from the socket. |
|||
*/ |
|||
RAISE_ERROR(socket, OI_ERROR_SEND, errno); |
|||
return ERROR; |
|||
|
|||
default: |
|||
perror("send()"); |
|||
assert(0 && "oi shouldn't let this happen."); |
|||
return ERROR; |
|||
} |
|||
} |
|||
|
|||
oi_socket_reset_timeout(socket); |
|||
|
|||
if(!socket->connected) { |
|||
socket->connected = TRUE; |
|||
if(socket->on_connect) { socket->on_connect(socket); } |
|||
} |
|||
|
|||
update_write_buffer_after_send(socket, sent); |
|||
|
|||
return OKAY; |
|||
} |
|||
|
|||
static int |
|||
socket_recv(oi_socket *socket) |
|||
{ |
|||
char buf[TCP_MAXWIN]; |
|||
size_t buf_size = TCP_MAXWIN; |
|||
ssize_t recved; |
|||
|
|||
assert(socket->secure == FALSE); |
|||
|
|||
if(!socket->connected) { |
|||
socket->connected = TRUE; |
|||
if(socket->on_connect) { socket->on_connect(socket); } |
|||
return OKAY; |
|||
} |
|||
|
|||
recved = recv(socket->fd, buf, buf_size, 0); |
|||
|
|||
if(recved < 0) { |
|||
switch(errno) { |
|||
case EAGAIN: |
|||
case EINTR: |
|||
return AGAIN; |
|||
|
|||
/* A remote host refused to allow the network connection (typically
|
|||
* because it is not running the requested service). */ |
|||
case ECONNREFUSED: |
|||
RAISE_ERROR(socket, OI_ERROR_RECV, errno); |
|||
return ERROR; |
|||
|
|||
case ECONNRESET: |
|||
RAISE_ERROR(socket, OI_ERROR_RECV, errno); |
|||
return ERROR; |
|||
|
|||
default: |
|||
perror("recv()"); |
|||
printf("unmatched errno %d\n", errno); |
|||
assert(0 && "recv returned error that oi should have caught before."); |
|||
return ERROR; |
|||
} |
|||
} |
|||
|
|||
oi_socket_reset_timeout(socket); |
|||
|
|||
if(recved == 0) { |
|||
oi_socket_read_stop(socket); |
|||
socket->read_action = NULL; |
|||
} |
|||
|
|||
/* NOTE: EOF is signaled with recved == 0 on callback */ |
|||
if(socket->on_read) { socket->on_read(socket, buf, recved); } |
|||
|
|||
return OKAY; |
|||
} |
|||
|
|||
static void |
|||
assign_file_descriptor(oi_socket *socket, int fd) |
|||
{ |
|||
socket->fd = fd; |
|||
|
|||
ev_io_set (&socket->read_watcher, fd, EV_READ); |
|||
ev_io_set (&socket->write_watcher, fd, EV_WRITE); |
|||
|
|||
socket->read_action = socket_recv; |
|||
socket->write_action = socket_send; |
|||
|
|||
#if HAVE_GNUTLS |
|||
if(socket->secure) { |
|||
gnutls_transport_set_lowat(socket->session, 0); |
|||
gnutls_transport_set_push_function(socket->session, nosigpipe_push); |
|||
gnutls_transport_set_ptr2 ( socket->session |
|||
/* recv */ , (gnutls_transport_ptr_t)fd |
|||
/* send */ , socket |
|||
); |
|||
socket->read_action = secure_handshake; |
|||
socket->write_action = secure_handshake; |
|||
} |
|||
#endif |
|||
} |
|||
|
|||
|
|||
/* Internal callback
|
|||
* Called by server->connection_watcher. |
|||
*/ |
|||
static void |
|||
on_connection(struct ev_loop *loop, ev_io *watcher, int revents) |
|||
{ |
|||
oi_server *server = watcher->data; |
|||
|
|||
// printf("on connection!\n");
|
|||
|
|||
assert(server->listening); |
|||
assert(server->loop == loop); |
|||
assert(&server->connection_watcher == watcher); |
|||
|
|||
if(EV_ERROR & revents) { |
|||
oi_server_close(server); |
|||
return; |
|||
} |
|||
|
|||
struct sockaddr address; /* connector's address information */ |
|||
socklen_t addr_len = sizeof(address); |
|||
|
|||
/* TODO accept all possible connections? currently: just one */ |
|||
int fd = accept(server->fd, (struct sockaddr*)&address, &addr_len); |
|||
if(fd < 0) { |
|||
perror("accept()"); |
|||
return; |
|||
} |
|||
|
|||
oi_socket *socket = NULL; |
|||
if(server->on_connection) |
|||
socket = server->on_connection(server, (struct sockaddr*)&address, addr_len); |
|||
|
|||
if(socket == NULL) { |
|||
close(fd); |
|||
return; |
|||
} |
|||
|
|||
int flags = fcntl(fd, F_GETFL, 0); |
|||
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
|||
if(r < 0) { |
|||
/* TODO error report */ |
|||
} |
|||
|
|||
#ifdef SO_NOSIGPIPE |
|||
flags = 1; |
|||
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &flags, sizeof(flags)); |
|||
#endif |
|||
|
|||
socket->server = server; |
|||
assign_file_descriptor(socket, fd); |
|||
oi_socket_attach(socket, loop); |
|||
} |
|||
|
|||
int |
|||
oi_server_listen(oi_server *server, struct addrinfo *addrinfo) |
|||
{ |
|||
int fd = -1; |
|||
struct linger ling = {0, 0}; |
|||
assert(server->listening == FALSE); |
|||
|
|||
fd = socket( addrinfo->ai_family |
|||
, addrinfo->ai_socktype |
|||
, addrinfo->ai_protocol |
|||
); |
|||
if(fd < 0) { |
|||
perror("socket()"); |
|||
return -1; |
|||
} |
|||
|
|||
int flags = fcntl(fd, F_GETFL, 0); |
|||
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
|||
if(r < 0) { |
|||
perror("fcntl()"); |
|||
return -1; |
|||
} |
|||
|
|||
flags = 1; |
|||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags)); |
|||
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags)); |
|||
setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling)); |
|||
|
|||
/* XXX: Sending single byte chunks in a response body? Perhaps there is a
|
|||
* need to enable the Nagel algorithm dynamically. For now disabling. |
|||
*/ |
|||
//setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags));
|
|||
|
|||
if (bind(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) < 0) { |
|||
perror("bind()"); |
|||
close(fd); |
|||
return -1; |
|||
} |
|||
|
|||
if (listen(fd, server->backlog) < 0) { |
|||
perror("listen()"); |
|||
close(fd); |
|||
return -1; |
|||
} |
|||
|
|||
server->fd = fd; |
|||
server->listening = TRUE; |
|||
ev_io_set (&server->connection_watcher, server->fd, EV_READ); |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/**
|
|||
* Stops the server. Will not accept new connections. Does not drop |
|||
* existing connections. |
|||
*/ |
|||
void |
|||
oi_server_close(oi_server *server) |
|||
{ |
|||
if(server->listening) { |
|||
oi_server_detach(server); |
|||
close(server->fd); |
|||
/* TODO do this on the loop? check return value? */ |
|||
server->listening = FALSE; |
|||
} |
|||
} |
|||
|
|||
void |
|||
oi_server_attach (oi_server *server, struct ev_loop *loop) |
|||
{ |
|||
ev_io_start (loop, &server->connection_watcher); |
|||
server->loop = loop; |
|||
} |
|||
|
|||
void |
|||
oi_server_detach (oi_server *server) |
|||
{ |
|||
ev_io_stop (server->loop, &server->connection_watcher); |
|||
server->loop = NULL; |
|||
} |
|||
|
|||
void |
|||
oi_server_init(oi_server *server, int backlog) |
|||
{ |
|||
server->backlog = backlog; |
|||
server->listening = FALSE; |
|||
server->fd = -1; |
|||
server->connection_watcher.data = server; |
|||
ev_init (&server->connection_watcher, on_connection); |
|||
|
|||
server->on_connection = NULL; |
|||
server->on_error = NULL; |
|||
server->data = NULL; |
|||
} |
|||
|
|||
/* Internal callback. called by socket->timeout_watcher */ |
|||
static void |
|||
on_timeout(struct ev_loop *loop, ev_timer *watcher, int revents) |
|||
{ |
|||
oi_socket *socket = watcher->data; |
|||
|
|||
assert(watcher == &socket->timeout_watcher); |
|||
|
|||
// printf("on_timeout\n");
|
|||
|
|||
if(socket->on_timeout) { socket->on_timeout(socket); } |
|||
|
|||
|
|||
/* TODD set timer to zero */ |
|||
full_close(socket); |
|||
} |
|||
|
|||
static void |
|||
release_write_buffer(oi_socket *socket) |
|||
{ |
|||
while(!oi_queue_empty(&socket->out_stream)) { |
|||
oi_queue *q = oi_queue_last(&socket->out_stream); |
|||
oi_buf *buf = oi_queue_data(q, oi_buf, queue); |
|||
oi_queue_remove(q); |
|||
if(buf->release) { buf->release(buf); } |
|||
} |
|||
} |
|||
|
|||
/* Internal callback. called by socket->read_watcher */ |
|||
static void |
|||
on_io_event(struct ev_loop *loop, ev_io *watcher, int revents) |
|||
{ |
|||
oi_socket *socket = watcher->data; |
|||
|
|||
if(revents & EV_ERROR) { |
|||
RAISE_ERROR(socket, OI_ERROR_EV, 0); |
|||
goto close; |
|||
} |
|||
|
|||
int r; |
|||
int have_read_event = TRUE; |
|||
int have_write_event = TRUE; |
|||
|
|||
while(have_read_event || have_write_event) { |
|||
|
|||
if(socket->read_action) { |
|||
r = socket->read_action(socket); |
|||
if(r == ERROR) goto close; |
|||
if(r == AGAIN) have_read_event = FALSE; |
|||
} else { |
|||
have_read_event = FALSE; |
|||
} |
|||
|
|||
if(socket->write_action) { |
|||
r = socket->write_action(socket); |
|||
if(r == ERROR) goto close; |
|||
if(r == AGAIN) have_write_event = FALSE; |
|||
} else { |
|||
have_write_event = FALSE; |
|||
} |
|||
|
|||
|
|||
if(socket->read_watcher.active == FALSE) |
|||
have_read_event = FALSE; |
|||
if(socket->write_watcher.active == FALSE) |
|||
have_write_event = FALSE; |
|||
} |
|||
|
|||
if(socket->write_action == NULL && socket->read_action == NULL) |
|||
goto close; |
|||
|
|||
return; |
|||
|
|||
close: |
|||
release_write_buffer(socket); |
|||
|
|||
ev_clear_pending (socket->loop, &socket->write_watcher); |
|||
ev_clear_pending (socket->loop, &socket->read_watcher); |
|||
ev_clear_pending (socket->loop, &socket->timeout_watcher); |
|||
|
|||
oi_socket_detach(socket); |
|||
|
|||
if(socket->on_close) { socket->on_close(socket); } |
|||
/* WARNING: user can free socket in on_close so no more
|
|||
* access beyond this point. */ |
|||
} |
|||
|
|||
/**
|
|||
* If using SSL do consider setting |
|||
* gnutls_db_set_retrieve_function (socket->session, _); |
|||
* gnutls_db_set_remove_function (socket->session, _); |
|||
* gnutls_db_set_store_function (socket->session, _); |
|||
* gnutls_db_set_ptr (socket->session, _); |
|||
*/ |
|||
void |
|||
oi_socket_init(oi_socket *socket, float timeout) |
|||
{ |
|||
socket->fd = -1; |
|||
socket->server = NULL; |
|||
socket->loop = NULL; |
|||
socket->connected = FALSE; |
|||
|
|||
oi_queue_init(&socket->out_stream); |
|||
|
|||
ev_init (&socket->write_watcher, on_io_event); |
|||
socket->write_watcher.data = socket; |
|||
|
|||
ev_init(&socket->read_watcher, on_io_event); |
|||
socket->read_watcher.data = socket; |
|||
|
|||
socket->secure = FALSE; |
|||
socket->wait_for_secure_hangup = FALSE; |
|||
#if HAVE_GNUTLS |
|||
socket->session = NULL; |
|||
#endif |
|||
|
|||
/* TODO higher resolution timer */ |
|||
ev_timer_init(&socket->timeout_watcher, on_timeout, 0., timeout); |
|||
socket->timeout_watcher.data = socket; |
|||
|
|||
socket->read_action = NULL; |
|||
socket->write_action = NULL; |
|||
|
|||
socket->chunksize = TCP_MAXWIN; |
|||
socket->on_connect = NULL; |
|||
socket->on_read = NULL; |
|||
socket->on_drain = NULL; |
|||
socket->on_error = NULL; |
|||
socket->on_timeout = NULL; |
|||
} |
|||
|
|||
void |
|||
oi_socket_write_eof (oi_socket *socket) |
|||
{ |
|||
#if HAVE_GNUTLS |
|||
/* try to hang up properly for secure connections */ |
|||
if(socket->secure) |
|||
{ |
|||
if( socket->connected /* completed handshake */ |
|||
&& socket->write_action /* write end is open */ |
|||
) |
|||
{ |
|||
socket->write_action = secure_half_goodbye; |
|||
if(socket->loop) |
|||
ev_io_start(socket->loop, &socket->write_watcher); |
|||
return; |
|||
} |
|||
/* secure servers cannot handle half-closed connections? */ |
|||
full_close(socket); |
|||
return; |
|||
} |
|||
#endif // HAVE_GNUTLS
|
|||
|
|||
if(socket->write_action) |
|||
half_close(socket); |
|||
else |
|||
full_close(socket); |
|||
} |
|||
|
|||
void |
|||
oi_socket_close (oi_socket *socket) |
|||
{ |
|||
#if HAVE_GNUTLS |
|||
/* try to hang up properly for secure connections */ |
|||
if( socket->secure |
|||
&& socket->connected /* completed handshake */ |
|||
&& socket->write_action /* write end is open */ |
|||
) |
|||
{ |
|||
if(socket->wait_for_secure_hangup && socket->read_action) { |
|||
socket->write_action = secure_full_goodbye; |
|||
socket->read_action = secure_full_goodbye; |
|||
} else { |
|||
socket->write_action = secure_half_goodbye; |
|||
socket->read_action = NULL; |
|||
} |
|||
|
|||
if(socket->loop) |
|||
ev_io_start(socket->loop, &socket->write_watcher); |
|||
|
|||
return; |
|||
} |
|||
#endif // HAVE_GNUTLS
|
|||
|
|||
full_close(socket); |
|||
} |
|||
|
|||
/*
|
|||
* Resets the timeout to stay alive for another socket->timeout seconds |
|||
*/ |
|||
void |
|||
oi_socket_reset_timeout(oi_socket *socket) |
|||
{ |
|||
ev_timer_again(socket->loop, &socket->timeout_watcher); |
|||
} |
|||
|
|||
/**
|
|||
* Writes a string to the socket. This is actually sets a watcher which may |
|||
* take multiple iterations to write the entire string. |
|||
*/ |
|||
void |
|||
oi_socket_write(oi_socket *socket, oi_buf *buf) |
|||
{ |
|||
if(socket->write_action == NULL) |
|||
return; |
|||
|
|||
oi_queue_insert_head(&socket->out_stream, &buf->queue); |
|||
|
|||
buf->written = 0; |
|||
ev_io_start(socket->loop, &socket->write_watcher); |
|||
} |
|||
|
|||
static void |
|||
free_simple_buf ( oi_buf *buf ) |
|||
{ |
|||
free(buf->base); |
|||
free(buf); |
|||
} |
|||
|
|||
/* Writes a string to the socket.
|
|||
* NOTE: Allocates memory. Avoid for performance applications. |
|||
*/ |
|||
void |
|||
oi_socket_write_simple(oi_socket *socket, const char *str, size_t len) |
|||
{ |
|||
oi_buf *buf = malloc(sizeof(oi_buf)); |
|||
buf->release = free_simple_buf; |
|||
buf->base = strdup(str); |
|||
buf->len = len; |
|||
|
|||
oi_socket_write(socket, buf); |
|||
} |
|||
|
|||
void |
|||
oi_socket_attach(oi_socket *socket, struct ev_loop *loop) |
|||
{ |
|||
socket->loop = loop; |
|||
|
|||
ev_timer_again(loop, &socket->timeout_watcher); |
|||
|
|||
if(socket->read_action) |
|||
ev_io_start(loop, &socket->read_watcher); |
|||
|
|||
if(socket->write_action) |
|||
ev_io_start(loop, &socket->write_watcher); |
|||
|
|||
/* make sure the io_event happens soon in the case we're being reattached */ |
|||
ev_feed_event(loop, &socket->read_watcher, EV_READ); |
|||
} |
|||
|
|||
void |
|||
oi_socket_detach(oi_socket *socket) |
|||
{ |
|||
if(socket->loop) { |
|||
ev_io_stop(socket->loop, &socket->write_watcher); |
|||
ev_io_stop(socket->loop, &socket->read_watcher); |
|||
ev_timer_stop(socket->loop, &socket->timeout_watcher); |
|||
socket->loop = NULL; |
|||
} |
|||
} |
|||
|
|||
void |
|||
oi_socket_read_stop (oi_socket *socket) |
|||
{ |
|||
ev_io_stop(socket->loop, &socket->read_watcher); |
|||
ev_clear_pending (socket->loop, &socket->read_watcher); |
|||
} |
|||
|
|||
void |
|||
oi_socket_read_start (oi_socket *socket) |
|||
{ |
|||
if(socket->read_action) { |
|||
ev_io_start(socket->loop, &socket->read_watcher); |
|||
/* XXX feed event? */ |
|||
} |
|||
} |
|||
|
|||
int |
|||
oi_socket_connect(oi_socket *s, struct addrinfo *addrinfo) |
|||
{ |
|||
int fd = socket( addrinfo->ai_family |
|||
, addrinfo->ai_socktype |
|||
, addrinfo->ai_protocol |
|||
); |
|||
if(fd < 0) { |
|||
perror("socket()"); |
|||
return -1; |
|||
} |
|||
|
|||
int flags = fcntl(fd, F_GETFL, 0); |
|||
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
|||
if(r < 0) { |
|||
perror("fcntl()"); |
|||
return -1; |
|||
} |
|||
|
|||
#ifdef SO_NOSIGPIPE |
|||
flags = 1; |
|||
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &flags, sizeof(flags)); |
|||
#endif |
|||
|
|||
r = connect( fd |
|||
, addrinfo->ai_addr |
|||
, addrinfo->ai_addrlen |
|||
); |
|||
|
|||
if(r < 0 && errno != EINPROGRESS) { |
|||
perror("connect"); |
|||
close(fd); |
|||
return -1; |
|||
} |
|||
|
|||
assign_file_descriptor(s, fd); |
|||
|
|||
return 0; |
|||
} |
|||
|
@ -0,0 +1,98 @@ |
|||
#include <netdb.h> |
|||
#include <ev.h> |
|||
#include <oi_queue.h> |
|||
#include <oi_error.h> |
|||
#include <oi_buf.h> |
|||
|
|||
#ifndef oi_socket_h |
|||
#define oi_socket_h |
|||
#ifdef __cplusplus |
|||
extern "C" { |
|||
#endif |
|||
|
|||
#ifndef HAVE_GNUTLS |
|||
# define HAVE_GNUTLS 0 |
|||
#endif |
|||
#if HAVE_GNUTLS |
|||
# include <gnutls/gnutls.h> |
|||
#endif |
|||
|
|||
typedef struct oi_server oi_server; |
|||
typedef struct oi_socket oi_socket; |
|||
|
|||
void oi_server_init (oi_server *, int backlog); |
|||
int oi_server_listen (oi_server *, struct addrinfo *addrinfo); |
|||
void oi_server_attach (oi_server *, struct ev_loop *loop); |
|||
void oi_server_detach (oi_server *); |
|||
void oi_server_close (oi_server *); |
|||
|
|||
void oi_socket_init (oi_socket *, float timeout); |
|||
int oi_socket_pair (oi_socket *a, oi_socket *b); /* TODO */ |
|||
int oi_socket_connect (oi_socket *, struct addrinfo *addrinfo); |
|||
void oi_socket_attach (oi_socket *, struct ev_loop *loop); |
|||
void oi_socket_detach (oi_socket *); |
|||
void oi_socket_read_start (oi_socket *); |
|||
void oi_socket_read_stop (oi_socket *); |
|||
void oi_socket_reset_timeout (oi_socket *); |
|||
void oi_socket_write (oi_socket *, oi_buf *); |
|||
void oi_socket_write_simple (oi_socket *, const char *str, size_t len); |
|||
void oi_socket_write_eof (oi_socket *); |
|||
void oi_socket_close (oi_socket *); |
|||
#if HAVE_GNUTLS |
|||
void oi_socket_set_secure_session (oi_socket *, gnutls_session_t); |
|||
#endif |
|||
|
|||
struct oi_server { |
|||
/* read only */ |
|||
int fd; |
|||
int backlog; |
|||
struct ev_loop *loop; |
|||
unsigned listening:1; |
|||
|
|||
/* private */ |
|||
ev_io connection_watcher; |
|||
|
|||
/* public */ |
|||
oi_socket* (*on_connection) (oi_server *, struct sockaddr *remote_addr, socklen_t remove_addr_len); |
|||
void (*on_error) (oi_server *, struct oi_error e); |
|||
void *data; |
|||
}; |
|||
|
|||
struct oi_socket { |
|||
/* read only */ |
|||
int fd; |
|||
struct ev_loop *loop; |
|||
oi_server *server; |
|||
oi_queue out_stream; |
|||
size_t written; |
|||
unsigned connected:1; |
|||
unsigned secure:1; |
|||
unsigned wait_for_secure_hangup:1; |
|||
|
|||
/* if these are NULL then it means that end of the socket is closed. */ |
|||
int (*read_action) (oi_socket *); |
|||
int (*write_action) (oi_socket *); |
|||
|
|||
/* private */ |
|||
ev_io write_watcher; |
|||
ev_io read_watcher; |
|||
ev_timer timeout_watcher; |
|||
#if HAVE_GNUTLS |
|||
gnutls_session_t session; |
|||
#endif |
|||
|
|||
/* public */ |
|||
size_t chunksize; /* the maximum chunk that on_read() will return */ |
|||
void (*on_connect) (oi_socket *); |
|||
void (*on_read) (oi_socket *, const void *buf, size_t count); |
|||
void (*on_drain) (oi_socket *); |
|||
void (*on_error) (oi_socket *, struct oi_error e); |
|||
void (*on_close) (oi_socket *); |
|||
void (*on_timeout) (oi_socket *); |
|||
void *data; |
|||
}; |
|||
|
|||
#ifdef __cplusplus |
|||
} |
|||
#endif |
|||
#endif /* oi_socket_h */ |
@ -0,0 +1,110 @@ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <unistd.h> |
|||
#include <assert.h> |
|||
|
|||
#include <sys/types.h> |
|||
#include <sys/socket.h> |
|||
#include <sys/un.h> |
|||
#include <netinet/in.h> |
|||
|
|||
|
|||
#include <ev.h> |
|||
#include <oi.h> |
|||
#include <gnutls/gnutls.h> |
|||
|
|||
#define HOST "127.0.0.1" |
|||
#define SOCKFILE "/tmp/oi.sock" |
|||
#define PORT "5000" |
|||
|
|||
int nconnections; |
|||
|
|||
static void |
|||
on_peer_close(oi_socket *socket) |
|||
{ |
|||
//printf("server connection closed\n");
|
|||
#if HAVE_GNUTLS |
|||
#if SECURE |
|||
gnutls_deinit(socket->session); |
|||
#endif |
|||
#endif |
|||
free(socket); |
|||
} |
|||
|
|||
static void |
|||
on_client_timeout(oi_socket *socket) |
|||
{ |
|||
printf("client connection timeout\n"); |
|||
assert(0); |
|||
} |
|||
|
|||
static void |
|||
on_peer_timeout(oi_socket *socket) |
|||
{ |
|||
fprintf(stderr, "peer connection timeout\n"); |
|||
assert(0); |
|||
} |
|||
|
|||
static void |
|||
on_client_error(oi_socket *socket, struct oi_error e) |
|||
{ |
|||
assert(0); |
|||
} |
|||
|
|||
|
|||
#if HAVE_GNUTLS |
|||
#if SECURE |
|||
#define DH_BITS 768 |
|||
gnutls_anon_server_credentials_t server_credentials; |
|||
const int kx_prio[] = { GNUTLS_KX_ANON_DH, 0 }; |
|||
static gnutls_dh_params_t dh_params; |
|||
|
|||
void anon_tls_init() |
|||
{ |
|||
gnutls_global_init(); |
|||
|
|||
gnutls_dh_params_init (&dh_params); |
|||
|
|||
fprintf(stderr, ".."); |
|||
fsync((int)stderr); |
|||
gnutls_dh_params_generate2 (dh_params, DH_BITS); |
|||
fprintf(stderr, "."); |
|||
|
|||
gnutls_anon_allocate_server_credentials (&server_credentials); |
|||
gnutls_anon_set_server_dh_params (server_credentials, dh_params); |
|||
} |
|||
|
|||
void anon_tls_server(oi_socket *socket) |
|||
{ |
|||
gnutls_session_t session; |
|||
socket->data = session; |
|||
|
|||
int r = gnutls_init(&session, GNUTLS_SERVER); |
|||
assert(r == 0); |
|||
gnutls_set_default_priority(session); |
|||
gnutls_kx_set_priority (session, kx_prio); |
|||
gnutls_credentials_set(session, GNUTLS_CRD_ANON, server_credentials); |
|||
gnutls_dh_set_prime_bits(session, DH_BITS); |
|||
|
|||
oi_socket_set_secure_session(socket, session); |
|||
} |
|||
|
|||
void anon_tls_client(oi_socket *socket) |
|||
{ |
|||
gnutls_session_t client_session; |
|||
gnutls_anon_client_credentials_t client_credentials; |
|||
|
|||
gnutls_anon_allocate_client_credentials (&client_credentials); |
|||
gnutls_init (&client_session, GNUTLS_CLIENT); |
|||
gnutls_set_default_priority(client_session); |
|||
gnutls_kx_set_priority(client_session, kx_prio); |
|||
/* Need to enable anonymous KX specifically. */ |
|||
gnutls_credentials_set (client_session, GNUTLS_CRD_ANON, client_credentials); |
|||
|
|||
oi_socket_set_secure_session(socket, client_session); |
|||
assert(socket->secure); |
|||
} |
|||
|
|||
#endif // SECURE
|
|||
#endif // HAVE_GNUTLS
|
@ -0,0 +1,159 @@ |
|||
#include "test/common.c" |
|||
#define NCONN 100 |
|||
#define TIMEOUT 1000.0 |
|||
|
|||
static oi_server server; |
|||
|
|||
static void |
|||
on_peer_read(oi_socket *socket, const void *base, size_t len) |
|||
{ |
|||
assert(len == 0); |
|||
oi_socket_write_simple(socket, "BYE", 3); |
|||
//printf("server wrote bye\n");
|
|||
} |
|||
|
|||
static void |
|||
on_peer_drain(oi_socket *socket) |
|||
{ |
|||
oi_socket_close(socket); |
|||
} |
|||
|
|||
static void |
|||
on_peer_error2(oi_socket *socket, struct oi_error e) |
|||
{ |
|||
if(e.domain == OI_ERROR_GNUTLS) return; |
|||
assert(0); |
|||
} |
|||
|
|||
static oi_socket* |
|||
on_server_connection(oi_server *server, struct sockaddr *addr, socklen_t len) |
|||
{ |
|||
oi_socket *socket = malloc(sizeof(oi_socket)); |
|||
oi_socket_init(socket, TIMEOUT); |
|||
socket->on_read = on_peer_read; |
|||
socket->on_drain = on_peer_drain; |
|||
socket->on_error = on_peer_error2; |
|||
socket->on_close = on_peer_close; |
|||
socket->on_timeout = on_peer_timeout; |
|||
|
|||
#if HAVE_GNUTLS |
|||
# if SECURE |
|||
anon_tls_server(socket); |
|||
# endif |
|||
#endif |
|||
|
|||
//printf("on server connection\n");
|
|||
|
|||
return socket; |
|||
} |
|||
|
|||
static void |
|||
on_client_connect(oi_socket *socket) |
|||
{ |
|||
//printf("on client connection\n");
|
|||
oi_socket_write_eof(socket); |
|||
} |
|||
|
|||
static void |
|||
on_client_close(oi_socket *socket) |
|||
{ |
|||
oi_socket_close(socket); // already closed, but it shouldn't crash if we try to do it again
|
|||
|
|||
//printf("client connection closed\n");
|
|||
if(++nconnections == NCONN) { |
|||
oi_server_detach(&server); |
|||
//printf("detaching server\n");
|
|||
} |
|||
} |
|||
|
|||
static void |
|||
on_client_read(oi_socket *socket, const void *base, size_t len) |
|||
{ |
|||
char buf[200000]; |
|||
strncpy(buf, base, len); |
|||
buf[len] = 0; |
|||
|
|||
//printf("client got message: %s\n", buf);
|
|||
|
|||
if(strcmp(buf, "BYE") == 0) { |
|||
oi_socket_close(socket); |
|||
} else { |
|||
assert(0); |
|||
} |
|||
} |
|||
|
|||
int |
|||
main(int argc, const char *argv[]) |
|||
{ |
|||
int r; |
|||
struct ev_loop *loop = ev_default_loop(0); |
|||
|
|||
oi_server_init(&server, 1000); |
|||
server.on_connection = on_server_connection; |
|||
#if HAVE_GNUTLS |
|||
# if SECURE |
|||
anon_tls_init(); |
|||
# endif |
|||
#endif |
|||
|
|||
struct addrinfo *servinfo; |
|||
struct addrinfo hints; |
|||
memset(&hints, 0, sizeof hints); |
|||
#if TCP |
|||
hints.ai_family = AF_UNSPEC; |
|||
hints.ai_socktype = SOCK_STREAM; |
|||
hints.ai_flags = AI_PASSIVE; |
|||
r = getaddrinfo(NULL, PORT, &hints, &servinfo); |
|||
assert(r == 0); |
|||
#else |
|||
struct stat tstat; |
|||
if (lstat(SOCKFILE, &tstat) == 0) { |
|||
if (S_ISSOCK(tstat.st_mode)) |
|||
unlink(SOCKFILE); |
|||
} |
|||
|
|||
servinfo = malloc(sizeof(struct addrinfo)); |
|||
servinfo->ai_family = AF_UNIX; |
|||
servinfo->ai_socktype = SOCK_STREAM; |
|||
servinfo->ai_protocol = 0; |
|||
|
|||
struct sockaddr_un *sockaddr = calloc(sizeof(struct sockaddr_un), 1); |
|||
sockaddr->sun_family = AF_UNIX; |
|||
strcpy(sockaddr->sun_path, SOCKFILE); |
|||
|
|||
servinfo->ai_addr = (struct sockaddr*)sockaddr; |
|||
servinfo->ai_addrlen = sizeof(struct sockaddr_un); |
|||
#endif |
|||
|
|||
oi_server_listen(&server, servinfo); |
|||
oi_server_attach(&server, loop); |
|||
|
|||
int i; |
|||
for(i = 0; i < NCONN; i++) { |
|||
oi_socket *client = malloc(sizeof(oi_socket)); |
|||
oi_socket_init(client, TIMEOUT); |
|||
client->on_read = on_client_read; |
|||
client->on_error = on_client_error; |
|||
client->on_connect = on_client_connect; |
|||
client->on_close = on_client_close; |
|||
client->on_timeout = on_client_timeout; |
|||
#if HAVE_GNUTLS |
|||
#if SECURE |
|||
anon_tls_client(client); |
|||
#endif |
|||
#endif |
|||
r = oi_socket_connect(client, servinfo); |
|||
assert(r == 0 && "problem connecting"); |
|||
oi_socket_attach(client, loop); |
|||
} |
|||
|
|||
ev_loop(loop, 0); |
|||
|
|||
assert(nconnections == NCONN); |
|||
|
|||
#if TCP |
|||
freeaddrinfo(servinfo); |
|||
#endif |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,102 @@ |
|||
#include "test/common.c" |
|||
|
|||
int successful_ping_count; |
|||
|
|||
static void |
|||
on_peer_read(oi_socket *socket, const void *base, size_t len) |
|||
{ |
|||
if(len == 0) |
|||
return; |
|||
|
|||
oi_socket_write_simple(socket, base, len); |
|||
} |
|||
|
|||
static void |
|||
on_peer_error(oi_socket *socket, struct oi_error e) |
|||
{ |
|||
assert(0); |
|||
} |
|||
|
|||
static oi_socket* |
|||
on_server_connection(oi_server *server, struct sockaddr *addr, socklen_t len) |
|||
{ |
|||
oi_socket *socket = malloc(sizeof(oi_socket)); |
|||
oi_socket_init(socket, 5.0); |
|||
socket->on_read = on_peer_read; |
|||
socket->on_error = on_peer_error; |
|||
socket->on_close = on_peer_close; |
|||
socket->on_timeout = on_peer_timeout; |
|||
|
|||
nconnections++; |
|||
|
|||
#if HAVE_GNUTLS |
|||
# if SECURE |
|||
anon_tls_server(socket); |
|||
# endif |
|||
#endif |
|||
|
|||
//printf("on server connection\n");
|
|||
|
|||
return socket; |
|||
} |
|||
|
|||
int |
|||
main(int argc, const char *argv[]) |
|||
{ |
|||
int r; |
|||
struct ev_loop *loop = ev_default_loop(0); |
|||
oi_server server; |
|||
oi_socket client; |
|||
|
|||
//printf("sizeof(oi_server): %d\n", sizeof(oi_server));
|
|||
//printf("sizeof(oi_socket): %d\n", sizeof(oi_socket));
|
|||
|
|||
oi_server_init(&server, 10); |
|||
server.on_connection = on_server_connection; |
|||
|
|||
#if HAVE_GNUTLS |
|||
# if SECURE |
|||
anon_tls_init(); |
|||
# endif |
|||
#endif |
|||
|
|||
struct addrinfo *servinfo; |
|||
struct addrinfo hints; |
|||
memset(&hints, 0, sizeof hints); |
|||
#if TCP |
|||
hints.ai_family = AF_UNSPEC; |
|||
hints.ai_socktype = SOCK_STREAM; |
|||
hints.ai_flags = AI_PASSIVE; |
|||
r = getaddrinfo(NULL, PORT, &hints, &servinfo); |
|||
assert(r == 0); |
|||
#else |
|||
struct stat tstat; |
|||
if (lstat(SOCKFILE, &tstat) == 0) { |
|||
if (S_ISSOCK(tstat.st_mode)) |
|||
unlink(SOCKFILE); |
|||
} |
|||
|
|||
servinfo = malloc(sizeof(struct addrinfo)); |
|||
servinfo->ai_family = AF_UNIX; |
|||
servinfo->ai_socktype = SOCK_STREAM; |
|||
servinfo->ai_protocol = 0; |
|||
|
|||
struct sockaddr_un *sockaddr = calloc(sizeof(struct sockaddr_un), 1); |
|||
sockaddr->sun_family = AF_UNIX; |
|||
strcpy(sockaddr->sun_path, SOCKFILE); |
|||
|
|||
servinfo->ai_addr = (struct sockaddr*)sockaddr; |
|||
servinfo->ai_addrlen = sizeof(struct sockaddr_un); |
|||
#endif |
|||
r = oi_server_listen(&server, servinfo); |
|||
assert(r == 0); |
|||
oi_server_attach(&server, loop); |
|||
|
|||
ev_loop(loop, 0); |
|||
|
|||
#if TCP |
|||
freeaddrinfo(servinfo); |
|||
#endif |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,223 @@ |
|||
/* This test uses most of oi's facilities. It starts by opening a new file
|
|||
* and writing N characters to it. Once the first chunk is written, it opens |
|||
* that file with another handle |
|||
* |
|||
* /tmp/oi_test_src == /tmp/oi_test_dst |
|||
* | ^ |
|||
* | | |
|||
* stream | | write |
|||
* | | |
|||
* V | |
|||
* client ----------> server/connection |
|||
*/ |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <unistd.h> |
|||
#include <assert.h> |
|||
|
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <fcntl.h> |
|||
#include <stdint.h> |
|||
|
|||
#ifndef TRUE |
|||
# define TRUE 1 |
|||
#endif |
|||
#ifndef FALSE |
|||
# define FALSE 0 |
|||
#endif |
|||
|
|||
#include <oi.h> |
|||
#include <oi_file.h> |
|||
|
|||
#define PORT "5555" |
|||
|
|||
static struct ev_loop *loop; |
|||
static oi_file file_src; |
|||
static oi_file file_dst; |
|||
static oi_socket client; |
|||
static oi_server server; |
|||
static oi_socket connection; |
|||
static int got_connection = 0; |
|||
|
|||
static char *src_filename; |
|||
static char *dst_filename; |
|||
|
|||
static void |
|||
file_error(oi_file *_) |
|||
{ |
|||
assert(0); |
|||
} |
|||
|
|||
static void |
|||
on_file_dst_open (oi_file *_) |
|||
{ |
|||
assert(connection.fd > 0); |
|||
oi_socket_read_start(&connection); |
|||
//printf("file_dst is open\n");
|
|||
} |
|||
|
|||
static void |
|||
on_file_dst_close (oi_file *_) |
|||
{ |
|||
//printf("file dst closed\n");
|
|||
oi_file_detach(&file_dst); |
|||
} |
|||
|
|||
static void |
|||
on_connection_read (oi_socket *_, const void *buf, size_t count) |
|||
{ |
|||
assert(file_dst.fd > 0); |
|||
if(count == 0) { |
|||
file_dst.on_drain = oi_file_close; |
|||
oi_socket_close(&connection); |
|||
} else { |
|||
oi_file_write_simple(&file_dst, buf, count); |
|||
} |
|||
} |
|||
|
|||
static void |
|||
on_connection_connect (oi_socket *_) |
|||
{ |
|||
oi_file_init(&file_dst); |
|||
file_dst.on_open = on_file_dst_open; |
|||
file_dst.on_close = on_file_dst_close; |
|||
oi_file_open_path(&file_dst, dst_filename, O_WRONLY | O_CREAT, 0644); |
|||
oi_file_attach(&file_dst, loop); |
|||
|
|||
oi_socket_read_stop(&connection); |
|||
} |
|||
|
|||
static void |
|||
on_connection_timeout (oi_socket *_) |
|||
{ |
|||
assert(0); |
|||
} |
|||
|
|||
static void |
|||
on_connection_error (oi_socket *_, struct oi_error e) |
|||
{ |
|||
assert(0); |
|||
} |
|||
|
|||
static void |
|||
on_connection_close (oi_socket *_) |
|||
{ |
|||
oi_server_close(&server); |
|||
oi_server_detach(&server); |
|||
} |
|||
|
|||
static oi_socket* |
|||
on_server_connection(oi_server *_, struct sockaddr *addr, socklen_t len) |
|||
{ |
|||
assert(got_connection == FALSE); |
|||
oi_socket_init(&connection, 5.0); |
|||
connection.on_connect = on_connection_connect; |
|||
connection.on_read = on_connection_read; |
|||
connection.on_error = on_connection_error; |
|||
connection.on_close = on_connection_close; |
|||
connection.on_timeout = on_connection_timeout; |
|||
connection.on_drain = oi_socket_close; |
|||
got_connection = TRUE; |
|||
//printf("on server connection\n");
|
|||
return &connection; |
|||
} |
|||
|
|||
static void |
|||
on_client_read (oi_socket *_, const void *buf, size_t count) |
|||
{ |
|||
assert(0); |
|||
} |
|||
|
|||
static void |
|||
on_client_error (oi_socket *_, struct oi_error e) |
|||
{ |
|||
assert(0); |
|||
} |
|||
|
|||
static void |
|||
on_client_connect (oi_socket *_) |
|||
{ |
|||
if(file_src.fd > 0) { |
|||
oi_file_send(&file_src, &client, 0, 50*1024); |
|||
} |
|||
} |
|||
|
|||
static void |
|||
on_client_drain (oi_socket *_) |
|||
{ |
|||
oi_socket_close(&client); |
|||
oi_file_close(&file_src); |
|||
} |
|||
|
|||
static void |
|||
on_file_src_open (oi_file *_) |
|||
{ |
|||
if(client.fd > 0) { |
|||
oi_file_send(&file_src, &client, 0, 50*1024); |
|||
} |
|||
} |
|||
|
|||
static void |
|||
on_client_timeout (oi_socket *_) |
|||
{ |
|||
assert(0); |
|||
} |
|||
|
|||
int |
|||
main(int argc, char *argv[]) |
|||
{ |
|||
int r; |
|||
loop = ev_default_loop(0); |
|||
|
|||
assert(argc == 3); |
|||
src_filename = argv[1]; |
|||
dst_filename = argv[2]; |
|||
|
|||
assert(strlen(src_filename) > 0); |
|||
assert(strlen(dst_filename) > 0); |
|||
|
|||
oi_server_init(&server, 10); |
|||
server.on_connection = on_server_connection; |
|||
|
|||
struct addrinfo *servinfo; |
|||
struct addrinfo hints; |
|||
memset(&hints, 0, sizeof hints); |
|||
|
|||
hints.ai_family = AF_UNSPEC; |
|||
hints.ai_socktype = SOCK_STREAM; |
|||
hints.ai_flags = AI_PASSIVE; |
|||
r = getaddrinfo(NULL, PORT, &hints, &servinfo); |
|||
assert(r == 0); |
|||
|
|||
r = oi_server_listen(&server, servinfo); |
|||
assert(r >= 0 && "problem listening"); |
|||
oi_server_attach(&server, loop); |
|||
|
|||
oi_socket_init(&client, 5.0); |
|||
client.on_read = on_client_read; |
|||
client.on_error = on_client_error; |
|||
client.on_connect = on_client_connect; |
|||
client.on_timeout = on_client_timeout; |
|||
//client.on_close = oi_socket_detach;
|
|||
client.on_drain = on_client_drain; |
|||
r = oi_socket_connect(&client, servinfo); |
|||
assert(r == 0 && "problem connecting"); |
|||
oi_socket_attach(&client, loop); |
|||
|
|||
oi_file_init(&file_src); |
|||
file_src.on_open = on_file_src_open; |
|||
file_src.on_drain = file_error; |
|||
file_src.on_close = oi_file_detach; |
|||
oi_file_open_path(&file_src, src_filename, O_RDONLY, 0700); |
|||
oi_file_attach(&file_src, loop); |
|||
|
|||
ev_loop(loop, 0); |
|||
|
|||
printf("\noi_file: %d bytes\n", sizeof(oi_file)); |
|||
printf("oi_socket: %d bytes\n", sizeof(oi_socket)); |
|||
|
|||
return 0; |
|||
} |
|||
|
@ -0,0 +1,165 @@ |
|||
#include "test/common.c" |
|||
|
|||
#define PING "PING" |
|||
#define PONG "PONG" |
|||
#define EXCHANGES 100 |
|||
|
|||
int successful_ping_count; |
|||
|
|||
static void |
|||
on_peer_read(oi_socket *socket, const void *base, size_t len) |
|||
{ |
|||
if(len == 0) |
|||
return; |
|||
|
|||
char buf[2000]; |
|||
strncpy(buf, base, len); |
|||
buf[len] = 0; |
|||
//printf("server got message: %s\n", buf);
|
|||
|
|||
oi_socket_write_simple(socket, PONG, sizeof PONG); |
|||
} |
|||
|
|||
static void |
|||
on_peer_error(oi_socket *socket, struct oi_error e) |
|||
{ |
|||
assert(0); |
|||
} |
|||
|
|||
static void |
|||
on_client_close(oi_socket *socket) |
|||
{ |
|||
//printf("client connection closed\n");
|
|||
ev_unloop(socket->loop, EVUNLOOP_ALL); |
|||
} |
|||
|
|||
static oi_socket* |
|||
on_server_connection(oi_server *server, struct sockaddr *addr, socklen_t len) |
|||
{ |
|||
oi_socket *socket = malloc(sizeof(oi_socket)); |
|||
oi_socket_init(socket, 5.0); |
|||
socket->on_read = on_peer_read; |
|||
socket->on_error = on_peer_error; |
|||
socket->on_close = on_peer_close; |
|||
socket->on_timeout = on_peer_timeout; |
|||
|
|||
nconnections++; |
|||
|
|||
#if HAVE_GNUTLS |
|||
# if SECURE |
|||
anon_tls_server(socket); |
|||
# endif |
|||
#endif |
|||
|
|||
//printf("on server connection\n");
|
|||
|
|||
return socket; |
|||
} |
|||
|
|||
static void |
|||
on_client_connect(oi_socket *socket) |
|||
{ |
|||
//printf("client connected. sending ping\n");
|
|||
oi_socket_write_simple(socket, PING, sizeof PING); |
|||
} |
|||
|
|||
static void |
|||
on_client_read(oi_socket *socket, const void *base, size_t len) |
|||
{ |
|||
char buf[200000]; |
|||
strncpy(buf, base, len); |
|||
buf[len] = 0; |
|||
//printf("client got message: %s\n", buf);
|
|||
|
|||
if(strcmp(buf, PONG) == 0) { |
|||
|
|||
if(++successful_ping_count > EXCHANGES) { |
|||
oi_socket_close(socket); |
|||
return; |
|||
} |
|||
oi_socket_write_simple(socket, PING, sizeof PING); |
|||
} else { |
|||
assert(0); |
|||
} |
|||
} |
|||
|
|||
int |
|||
main(int argc, const char *argv[]) |
|||
{ |
|||
int r; |
|||
struct ev_loop *loop = ev_default_loop(0); |
|||
oi_server server; |
|||
oi_socket client; |
|||
|
|||
//printf("sizeof(oi_server): %d\n", sizeof(oi_server));
|
|||
//printf("sizeof(oi_socket): %d\n", sizeof(oi_socket));
|
|||
|
|||
oi_server_init(&server, 10); |
|||
server.on_connection = on_server_connection; |
|||
|
|||
#if HAVE_GNUTLS |
|||
# if SECURE |
|||
anon_tls_init(); |
|||
# endif |
|||
#endif |
|||
|
|||
struct addrinfo *servinfo; |
|||
struct addrinfo hints; |
|||
memset(&hints, 0, sizeof hints); |
|||
#if TCP |
|||
hints.ai_family = AF_UNSPEC; |
|||
hints.ai_socktype = SOCK_STREAM; |
|||
hints.ai_flags = AI_PASSIVE; |
|||
r = getaddrinfo(NULL, PORT, &hints, &servinfo); |
|||
assert(r == 0); |
|||
#else |
|||
struct stat tstat; |
|||
if (lstat(SOCKFILE, &tstat) == 0) { |
|||
if (S_ISSOCK(tstat.st_mode)) |
|||
unlink(SOCKFILE); |
|||
} |
|||
|
|||
servinfo = malloc(sizeof(struct addrinfo)); |
|||
servinfo->ai_family = AF_UNIX; |
|||
servinfo->ai_socktype = SOCK_STREAM; |
|||
servinfo->ai_protocol = 0; |
|||
|
|||
struct sockaddr_un *sockaddr = calloc(sizeof(struct sockaddr_un), 1); |
|||
sockaddr->sun_family = AF_UNIX; |
|||
strcpy(sockaddr->sun_path, SOCKFILE); |
|||
|
|||
servinfo->ai_addr = (struct sockaddr*)sockaddr; |
|||
servinfo->ai_addrlen = sizeof(struct sockaddr_un); |
|||
#endif |
|||
r = oi_server_listen(&server, servinfo); |
|||
assert(r == 0); |
|||
oi_server_attach(&server, loop); |
|||
|
|||
oi_socket_init(&client, 5.0); |
|||
client.on_read = on_client_read; |
|||
client.on_error = on_client_error; |
|||
client.on_connect = on_client_connect; |
|||
client.on_close = on_client_close; |
|||
client.on_timeout = on_client_timeout; |
|||
|
|||
#if HAVE_GNUTLS |
|||
# if SECURE |
|||
anon_tls_client(&client); |
|||
# endif |
|||
#endif |
|||
|
|||
r = oi_socket_connect(&client, servinfo); |
|||
assert(r == 0 && "problem connecting"); |
|||
oi_socket_attach(&client, loop); |
|||
|
|||
ev_loop(loop, 0); |
|||
|
|||
assert(successful_ping_count == EXCHANGES + 1); |
|||
assert(nconnections == 1); |
|||
|
|||
#if TCP |
|||
freeaddrinfo(servinfo); |
|||
#endif |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,54 @@ |
|||
#include <unistd.h> /* sleep() */ |
|||
#include <stdlib.h> /* malloc(), free() */ |
|||
#include <assert.h> |
|||
#include <ev.h> |
|||
#include <oi_async.h> |
|||
|
|||
#define SLEEPS 4 |
|||
static int runs = 0; |
|||
|
|||
static void |
|||
done (oi_task *task, unsigned int result) |
|||
{ |
|||
assert(result == 0); |
|||
if(++runs == SLEEPS) { |
|||
ev_timer *timer = task->data; |
|||
ev_timer_stop(task->async->loop, timer); |
|||
oi_async_detach(task->async); |
|||
} |
|||
free(task); |
|||
} |
|||
|
|||
static void |
|||
on_timeout(struct ev_loop *loop, ev_timer *w, int events) |
|||
{ |
|||
assert(0 && "timeout before all sleeping tasks were complete!"); |
|||
} |
|||
|
|||
int |
|||
main() |
|||
{ |
|||
struct ev_loop *loop = ev_default_loop(0); |
|||
oi_async async; |
|||
ev_timer timer; |
|||
int i; |
|||
|
|||
oi_async_init(&async); |
|||
for(i = 0; i < SLEEPS; i++) { |
|||
oi_task *task = malloc(sizeof(oi_task)); |
|||
oi_task_init_sleep(task, done, 1); |
|||
task->data = &timer; |
|||
oi_async_submit(&async, task); |
|||
} |
|||
oi_async_attach(loop, &async); |
|||
|
|||
|
|||
ev_timer_init (&timer, on_timeout, 1.2, 0.); |
|||
ev_timer_start (loop, &timer); |
|||
|
|||
ev_loop(loop, 0); |
|||
|
|||
assert(runs == SLEEPS); |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,64 @@ |
|||
#include <unistd.h> /* sleep() */ |
|||
#include <stdlib.h> /* malloc(), free() */ |
|||
#include <stdio.h> |
|||
#include <errno.h> |
|||
#include <assert.h> |
|||
|
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <fcntl.h> |
|||
|
|||
#include <ev.h> |
|||
#include <oi_file.h> |
|||
|
|||
static oi_file file; |
|||
static oi_file out; |
|||
|
|||
#define READ_BUFSIZE (5) |
|||
static char read_buf[READ_BUFSIZE]; |
|||
# define SEP "~~~~~~~~~~~~~~~~~~~~~~\n" |
|||
|
|||
static void |
|||
on_open(oi_file *f) |
|||
{ |
|||
oi_file_write_simple(&out, "\n", 1); |
|||
oi_file_write_simple(&out, SEP, sizeof(SEP)); |
|||
oi_file_read_start(f, read_buf, READ_BUFSIZE); |
|||
} |
|||
|
|||
static void |
|||
on_close(oi_file *f) |
|||
{ |
|||
oi_file_write_simple(&out, SEP, sizeof(SEP)); |
|||
oi_file_detach(f); |
|||
out.on_drain = oi_file_detach; |
|||
} |
|||
|
|||
static void |
|||
on_read(oi_file *f, size_t recved) |
|||
{ |
|||
if(recved == 0) /* EOF */ |
|||
oi_file_close(f); |
|||
else |
|||
oi_file_write_simple(&out, read_buf, recved); |
|||
} |
|||
|
|||
int |
|||
main() |
|||
{ |
|||
struct ev_loop *loop = ev_default_loop(0); |
|||
|
|||
oi_file_init(&file); |
|||
file.on_open = on_open; |
|||
file.on_read = on_read; |
|||
file.on_close = on_close; |
|||
oi_file_open_path(&file, "LICENSE", O_RDONLY, 0); |
|||
oi_file_attach(&file, loop); |
|||
|
|||
oi_file_init(&out); |
|||
oi_file_open_stdout(&out); |
|||
oi_file_attach(&out, loop); |
|||
|
|||
ev_loop(loop, 0); |
|||
return 0; |
|||
} |
@ -0,0 +1,96 @@ |
|||
#!/usr/bin/env ruby |
|||
|
|||
def test(description) |
|||
pid = fork do |
|||
exec(File.dirname(__FILE__) + "/echo") |
|||
end |
|||
|
|||
begin |
|||
sleep 0.5 # give time for the server to start |
|||
yield(pid) |
|||
rescue |
|||
puts "\033[1;31mFAIL\033[m: #{description}" |
|||
raise $! |
|||
ensure |
|||
`kill -9 #{pid}` |
|||
end |
|||
puts "\033[1;32mPASS\033[m: #{description}" |
|||
end |
|||
|
|||
test("make sure echo server works") do |
|||
socket = TCPSocket.open("localhost", 5000) |
|||
w = socket.write("hello"); |
|||
raise "error" unless w == 5 |
|||
|
|||
got = socket.recv(5); |
|||
raise "error" unless got == "hello" |
|||
|
|||
socket.close |
|||
end |
|||
|
|||
test("doing nothing should not timeout the server") do |pid| |
|||
10.times do |
|||
print "." |
|||
STDOUT.flush |
|||
if Process.waitpid(pid, Process::WNOHANG) |
|||
raise "server died when it shouldn't have" |
|||
end |
|||
sleep 1 |
|||
end |
|||
puts "" |
|||
end |
|||
|
|||
test("connecting and doing nothing to should timeout in 5 seconds") do |pid| |
|||
socket = TCPSocket.open("localhost", 5000) |
|||
i = 0 |
|||
10.times do |
|||
print "." |
|||
STDOUT.flush |
|||
break if Process.waitpid(pid, Process::WNOHANG) |
|||
sleep 1 |
|||
i+=1 |
|||
end |
|||
puts "" |
|||
raise "died too soon (after #{i} seconds)" if i < 5 |
|||
raise "died too late (after #{i} seconds)" if i > 6 |
|||
end |
|||
|
|||
|
|||
test("connecting and writing once to should timeout in 5 seconds") do |pid| |
|||
socket = TCPSocket.open("localhost", 5000) |
|||
w = socket.write("hello"); |
|||
raise "error" unless w == 5 |
|||
|
|||
i = 0 |
|||
10.times do |
|||
print "." |
|||
STDOUT.flush |
|||
break if Process.waitpid(pid, Process::WNOHANG) |
|||
sleep 1 |
|||
i+=1 |
|||
end |
|||
puts "" |
|||
raise "died too soon (after #{i} seconds)" if i < 5 |
|||
raise "died too late (after #{i} seconds)" if i > 6 |
|||
end |
|||
|
|||
test("connecting waiting 3, writing once to should timeout in 8 seconds") do |pid| |
|||
socket = TCPSocket.open("localhost", 5000) |
|||
|
|||
sleep 3 |
|||
|
|||
w = socket.write("hello"); |
|||
raise "error" unless w == 5 |
|||
|
|||
i = 0 |
|||
10.times do |
|||
print "." |
|||
STDOUT.flush |
|||
break if Process.waitpid(pid, Process::WNOHANG) |
|||
sleep 1 |
|||
i+=1 |
|||
end |
|||
puts "" |
|||
raise "died too soon (after #{i} seconds)" if i < 5 |
|||
raise "died too late (after #{i} seconds)" if i > 6 |
|||
end |
@ -0,0 +1,15 @@ |
|||
# Below is a list of people and organizations that have contributed |
|||
# to the V8 project. Names should be added to the list like so: |
|||
# |
|||
# Name/Organization <email address> |
|||
|
|||
Google Inc. |
|||
|
|||
Rene Rebe <rene@exactcode.de> |
|||
Rafal Krypa <rafal@krypa.net> |
|||
Jay Freeman <saurik@saurik.com> |
|||
Daniel James <dnljms@gmail.com> |
|||
Paolo Giarrusso <p.giarrusso@gmail.com> |
|||
Daniel Andersson <kodandersson@gmail.com> |
|||
Alexander Botero-Lowry <alexbl@FreeBSD.org> |
|||
Matt Hanselman <mjhanselman@gmail.com> |
@ -0,0 +1,816 @@ |
|||
2009-04-15: Version 1.1.10 |
|||
|
|||
Fixed crash bug that occurred when loading a const variable in the |
|||
presence of eval. |
|||
|
|||
Allowed using with and eval in registered extensions in debug mode |
|||
by fixing bogus assert. |
|||
|
|||
Fixed the source position for function returns to enable the |
|||
debugger to break there. |
|||
|
|||
|
|||
2009-04-14: Version 1.1.9 |
|||
|
|||
Made the stack traversal code in the profiler robust by avoiding |
|||
to look into the heap. |
|||
|
|||
Added name inferencing for anonymous functions to facilitate |
|||
debugging and profiling. |
|||
|
|||
Re-enabled stats timers in the developer shell (d8). |
|||
|
|||
Fixed issue 303 by avoiding to shortcut cons-symbols. |
|||
|
|||
|
|||
2009-04-11: Version 1.1.8 |
|||
|
|||
Changed test-debug/ThreadedDebugging to be non-flaky (issue 96). |
|||
|
|||
Fixed step-in handling for Function.prototype.apply and call in |
|||
the debugger (issue 269). |
|||
|
|||
Fixed v8::Object::DeleteHiddenValue to not bail out when there |
|||
are no hidden properties. |
|||
|
|||
Added workaround for crash bug, where external symbol table |
|||
entries with deleted resources would lead to NPEs when looking |
|||
up in the symbol table. |
|||
|
|||
|
|||
2009-04-07: Version 1.1.7 |
|||
|
|||
Added support for easily importing additional environment |
|||
variables into the SCons build. |
|||
|
|||
Optimized strict equality checks. |
|||
|
|||
Fixed crash in indexed setters on objects without a corresponding |
|||
getter (issue 298). |
|||
|
|||
Re-enabled script compilation cache. |
|||
|
|||
|
|||
2009-04-01: Version 1.1.6 |
|||
|
|||
Reverted an unsafe code generator change. |
|||
|
|||
|
|||
2009-04-01: Version 1.1.5 |
|||
|
|||
Fixed bug that caused function literals to not be optimized as |
|||
much as other functions. |
|||
|
|||
Improved profiler support. |
|||
|
|||
Fixed a crash bug in connection with debugger unloading. |
|||
|
|||
Fixed a crash bug in the code generator caused by losing the |
|||
information that a frame element was copied. |
|||
|
|||
Fixed an exception propagation bug that could cause non-null |
|||
return values when exceptions were thrown. |
|||
|
|||
|
|||
2009-03-30: Version 1.1.4 |
|||
|
|||
Optimized String.prototype.match. |
|||
|
|||
Improved the stack information in profiles. |
|||
|
|||
Fixed bug in ARM port making it possible to compile the runtime |
|||
system for thumb mode again. |
|||
|
|||
Implemented a number of optimizations in the code generator. |
|||
|
|||
Fixed a number of memory leaks in tests. |
|||
|
|||
Fixed crash bug in connection with script source code and external |
|||
strings. |
|||
|
|||
|
|||
2009-03-24: Version 1.1.3 |
|||
|
|||
Fixed assertion failures in compilation of loop conditions. |
|||
|
|||
Removed STL dependency from developer shell (d8). |
|||
|
|||
Added infrastructure for protecting the V8 heap from corruption |
|||
caused by memory modifications from the outside. |
|||
|
|||
|
|||
2009-03-24: Version 1.1.2 |
|||
|
|||
Improved frame merge code generated by the code generator. |
|||
|
|||
Optimized String.prototype.replace. |
|||
|
|||
Implemented __defineGetter__ and __defineSetter__ for properties |
|||
with integer keys on non-array objects. |
|||
|
|||
Improved debugger and profiler support. |
|||
|
|||
Fixed a number of portability issues to allow compilation for |
|||
smaller ARM devices. |
|||
|
|||
Exposed object cloning through the API. |
|||
|
|||
Implemented hidden properties. This is used to expose an identity |
|||
hash for objects through the API. |
|||
|
|||
Implemented restarting of regular expressions if their input |
|||
string changes representation during preemption. |
|||
|
|||
Fixed a code generator bug that could cause assignments in loops |
|||
to be ignored if using continue to break out of the loop (issue |
|||
284). |
|||
|
|||
|
|||
2009-03-12: Version 1.1.1 |
|||
|
|||
Fixed an assertion in the new compiler to take stack overflow |
|||
exceptions into account. |
|||
|
|||
Removed exception propagation code that could cause crashes. |
|||
|
|||
Fixed minor bug in debugger line number computations. |
|||
|
|||
8-byte align the C stack on Linux and Windows to speed up floating |
|||
point computations. |
|||
|
|||
|
|||
2009-03-12: Version 1.1.0 |
|||
|
|||
Improved code generation infrastructure by doing simple register |
|||
allocation and constant folding and propagation. |
|||
|
|||
Optimized regular expression matching by avoiding to create |
|||
intermediate string arrays and by flattening nested array |
|||
representations of RegExp data. |
|||
|
|||
Traverse a few stack frames when recording profiler samples to |
|||
include partial call graphs in the profiling output. |
|||
|
|||
Added support for using OProfile to profile generated code. |
|||
|
|||
Added remote debugging support to the D8 developer shell. |
|||
|
|||
Optimized creation of nested literals like JSON objects. |
|||
|
|||
Fixed a bug in garbage collecting unused maps and turned it on by |
|||
default (--collect-maps). |
|||
|
|||
Added support for running tests under Valgrind. |
|||
|
|||
|
|||
2009-02-27: Version 1.0.3 |
|||
|
|||
Optimized double-to-integer conversions in bit operations by using |
|||
SSE3 instructions if available. |
|||
|
|||
Optimized initialization sequences that store to multiple |
|||
properties of the same object. |
|||
|
|||
Changed the D8 debugger frontend to use JSON messages. |
|||
|
|||
Force garbage collections when disposing contexts. |
|||
|
|||
Align code objects at 32-byte boundaries. |
|||
|
|||
|
|||
2009-02-25: Version 1.0.2 |
|||
|
|||
Improved profiling support by performing simple call stack |
|||
sampling for ticks and by fixing a bug in the logging of code |
|||
addresses. |
|||
|
|||
Fixed a number of debugger issues. |
|||
|
|||
Optimized code that uses eval. |
|||
|
|||
Fixed a couple of bugs in the regular expression engine. |
|||
|
|||
Reduced the size of generated code for certain regular expressions. |
|||
|
|||
Removed JSCRE completely. |
|||
|
|||
Fixed issue where test could not be run if there was a dot in the |
|||
checkout path. |
|||
|
|||
|
|||
2009-02-13: Version 1.0.1 |
|||
|
|||
Fixed two crash-bugs in irregexp (issue 231 and 233). |
|||
|
|||
Fixed a number of minor bugs (issue 87, 227 and 228). |
|||
|
|||
Added support for morphing strings to external strings on demand |
|||
to avoid having to create copies in the embedding code. |
|||
|
|||
Removed experimental support for external symbol callbacks. |
|||
|
|||
|
|||
2009-02-09: Version 1.0.0 |
|||
|
|||
Fixed crash-bug in the code generation for case independent 16 bit |
|||
backreferences. |
|||
|
|||
Made shells more robust in the presence of string conversion |
|||
failures (issue 224). |
|||
|
|||
Fixed a potential infinite loop when attempting to resolve |
|||
eval (issue 221). |
|||
|
|||
Miscellaneous fixes to the new regular expression engine. |
|||
|
|||
Reduced binary by stripping unneeded text from JavaScript library and |
|||
minifying some JavaScript files. |
|||
|
|||
|
|||
2009-01-27: Version 0.4.9 |
|||
|
|||
Enabled new regular expression engine. |
|||
|
|||
Made a number of changes to the debugger protocol. |
|||
|
|||
Fixed a number of bugs in the preemption support. |
|||
|
|||
Added -p option to the developer shell to run files in parallel |
|||
using preemption. |
|||
|
|||
Fixed a number of minor bugs (including issues 176, 187, 189, 192, |
|||
193, 198 and 201). |
|||
|
|||
Fixed a number of bugs in the serialization/deserialization |
|||
support for the ARM platform. |
|||
|
|||
|
|||
2009-01-19: Version 0.4.8.1 |
|||
|
|||
Minor patch to debugger support. |
|||
|
|||
|
|||
2009-01-16: Version 0.4.8 |
|||
|
|||
Fixed string length bug on ARM (issue 171). |
|||
|
|||
Made most methods in the API const. |
|||
|
|||
Optimized object literals by improving data locality. |
|||
|
|||
Fixed bug that caused incomplete functions to be cached in case of |
|||
stack overflow exceptions. |
|||
|
|||
Fixed bugs that caused catch variables and variables introduced by |
|||
eval to behave incorrectly when using accessors (issues 186, 190 |
|||
and 191). |
|||
|
|||
|
|||
2009-01-06: Version 0.4.7 |
|||
|
|||
Minor bugfixes and optimizations. |
|||
|
|||
Added command line debugger to D8 shell. |
|||
|
|||
Fixed subtle bug that caused the wrong 'this' to be used when |
|||
calling a caught function in a catch clause. |
|||
|
|||
Inline array loads within loops directly in the code instead of |
|||
always calling a stub. |
|||
|
|||
|
|||
2008-12-11: Version 0.4.6 |
|||
|
|||
Fixed exception reporting bug where certain exceptions were |
|||
incorrectly reported as uncaught. |
|||
|
|||
Improved the memory allocation strategy used during compilation to |
|||
make running out of memory when compiling huge scripts less |
|||
likely. |
|||
|
|||
Optimized String.replace by avoiding the construction of certain |
|||
sub strings. |
|||
|
|||
Fixed bug in code generation for large switch statements on ARM. |
|||
|
|||
Fixed bug that caused V8 to change the global object template |
|||
passed in by the user. |
|||
|
|||
Changed the API for creating object groups used during garbage |
|||
collection. Entire object groups are now passed to V8 instead of |
|||
individual members of the groups. |
|||
|
|||
|
|||
2008-12-03: Version 0.4.5 |
|||
|
|||
Added experimental API support for allocating V8 symbols as |
|||
external strings. |
|||
|
|||
Fixed bugs in debugging support on ARM. |
|||
|
|||
Changed eval implementation to correctly detect whether or not a |
|||
call to eval is aliased. |
|||
|
|||
Fixed bug caused by a combination of the compilation cache and |
|||
dictionary probing in native code. The bug caused us to sometimes |
|||
call functions that had not yet been compiled. |
|||
|
|||
Added platform support for FreeBSD. |
|||
|
|||
Added support for building V8 on Windows with either the shared or |
|||
static version of MSVCRT |
|||
|
|||
Added the v8::jscre namespace around the jscre functions to avoid |
|||
link errors (duplicate symbols) when building Google Chrome. |
|||
|
|||
Added support for calling a JavaScript function with the current |
|||
debugger execution context as its argument to the debugger |
|||
interface. |
|||
|
|||
Changed the type of names of counters from wchar_t to char. |
|||
|
|||
Changed the Windows system call used to compute daylight savings |
|||
time. The system call that we used to use became four times |
|||
slower on WinXP SP3. |
|||
|
|||
Added support in the d8 developer shell for memory-mapped counters |
|||
and added a stats-viewer tool. |
|||
|
|||
Fixed bug in upper/lower case mappings (issue 149). |
|||
|
|||
|
|||
2008-11-17: Version 0.4.4 |
|||
|
|||
Reduced code size by using shorter instruction encoding when |
|||
possible. |
|||
|
|||
Added a --help option to the shell sample and to the d8 shell. |
|||
|
|||
Added visual studio project files for building the ARM simulator. |
|||
|
|||
Fixed a number of ARM simulator issues. |
|||
|
|||
Fixed bug in out-of-memory handling on ARM. |
|||
|
|||
Implemented shell support for passing arguments to a script from |
|||
the command line. |
|||
|
|||
Fixed bug in date code that made certain date functions return -0 |
|||
instead of 0 for dates before the epoch. |
|||
|
|||
Restricted applications of eval so it can only be used in the |
|||
context of the associated global object. |
|||
|
|||
Treat byte-order marks as whitespace characters. |
|||
|
|||
|
|||
2008-11-04: Version 0.4.3 |
|||
|
|||
Added support for API accessors that prohibit overwriting by |
|||
accessors defined in JavaScript code by using __defineGetter__ and |
|||
__defineSetter__. |
|||
|
|||
Improved handling of conditionals in test status files. |
|||
|
|||
Introduced access control in propertyIsEnumerable. |
|||
|
|||
Improved performance of some string operations by caching |
|||
information about the type of the string between operations. |
|||
|
|||
Fixed bug in fast-case code for switch statements that only have |
|||
integer labels. |
|||
|
|||
|
|||
2008-10-30: Version 0.4.2 |
|||
|
|||
Improved performance of Array.prototype.concat by moving the |
|||
implementation to C++ (issue 123). |
|||
|
|||
Fixed heap growth policy to avoid growing old space to its maximum |
|||
capacity before doing a garbage collection and fixed issue that |
|||
would lead to artificial out of memory situations (issue 129). |
|||
|
|||
Fixed Date.prototype.toLocaleDateString to return the date in the |
|||
same format as WebKit. |
|||
|
|||
Added missing initialization checks to debugger API. |
|||
|
|||
Added removing of unused maps during GC. |
|||
|
|||
|
|||
2008-10-28: Version 0.4.1 |
|||
|
|||
Added caching of RegExp data in compilation cache. |
|||
|
|||
Added Visual Studio project file for d8 shell. |
|||
|
|||
Fixed function call performance regression introduced in version |
|||
0.4.0 when splitting the global object in two parts (issue 120). |
|||
|
|||
Fixed issue 131 by checking for empty handles before throwing and |
|||
reporting exceptions. |
|||
|
|||
|
|||
2008-10-23: Version 0.4.0 |
|||
|
|||
Split the global object into two parts: The state holding global |
|||
object and the global object proxy. |
|||
|
|||
Fixed bug that affected the value of an assignment to an element |
|||
in certain cases (issue 116). |
|||
|
|||
Added GetPropertyNames functionality (issue 33) and extra Date |
|||
functions (issue 77) to the API. |
|||
|
|||
Changed WeakReferenceCallback to take a Persistent<Value> instead |
|||
of a Persistent<Object> (issue 101). |
|||
|
|||
Fixed issues with message reporting for exceptions in try-finally |
|||
blocks (issues 73 and 75). |
|||
|
|||
Optimized flattening of strings and string equality checking. |
|||
|
|||
Improved Boyer-Moore implementation for faster indexOf operations. |
|||
|
|||
Added development shell (d8) which includes counters and |
|||
completion support. |
|||
|
|||
Fixed problem with the receiver passed to functions called from |
|||
eval (issue 124). |
|||
|
|||
|
|||
2008-10-16: Version 0.3.5 |
|||
|
|||
Improved string hash-code distribution by excluding bit-field bits |
|||
from the hash-code. |
|||
|
|||
Changed string search algorithm used in indexOf from KMP to |
|||
Boyer-Moore. |
|||
|
|||
Improved the generated code for the instanceof operator. |
|||
|
|||
Improved performance of slow-case string equality checks by |
|||
specializing the code based on the string representation. |
|||
|
|||
Improve the handling of out-of-memory situations (issue 70). |
|||
|
|||
Improved performance of strict equality checks. |
|||
|
|||
Improved profiler output to make it easier to see anonymous |
|||
functions. |
|||
|
|||
Improved performance of slow-case keyed loads. |
|||
|
|||
Improved property access performance by allocating a number of |
|||
properties in the front object. |
|||
|
|||
Changed the toString behavior on the built-in object constructors |
|||
to print [native code] instead of the actual source. Some web |
|||
applications do not like constructors with complex toString |
|||
results. |
|||
|
|||
|
|||
2008-10-06: Version 0.3.4 |
|||
|
|||
Changed Array.prototype.sort to use quick sort. |
|||
|
|||
Fixed code generation issue where leaving a finally block with |
|||
break or continue would accumulate elements on the expression |
|||
stack (issue 86). |
|||
|
|||
Made sure that the name accessor on functions returns the expected |
|||
names for builtin JavaScript functions and C++ callback functions. |
|||
|
|||
Added fast case code for extending the property storage array of |
|||
JavaScript objects. |
|||
|
|||
Ported switch statement optimizations introduced in version 0.3.3 |
|||
to the ARM code generator. |
|||
|
|||
Allowed GCC to use strict-aliasing rules when compiling. |
|||
|
|||
Improved performance of arguments object allocation by taking care |
|||
of arguments adaptor frames in the generated code. |
|||
|
|||
Updated the V8 benchmark suite to version 2. |
|||
|
|||
|
|||
2008-09-25: Version 0.3.3 |
|||
|
|||
Improved handling of relocation information to enable more |
|||
peep-hole optimizations. |
|||
|
|||
Optimized switch statements where all labels are constant small |
|||
integers. |
|||
|
|||
Optimized String.prototype.indexOf for common cases. |
|||
|
|||
Fixed more build issues (issue 80). |
|||
|
|||
Fixed a couple of profiler issues. |
|||
|
|||
Fixed bug where the body of a function created using the Function |
|||
constructor was not allowed to end with a single-line comment |
|||
(issue 85). |
|||
|
|||
Improved handling of object literals by canonicalizing object |
|||
literal maps. This will allow JSON objects with the same set of |
|||
properties to share the same map making inline caching work better |
|||
for JSON objects. |
|||
|
|||
|
|||
2008-09-17: Version 0.3.2 |
|||
|
|||
Generalized the EvalCache into a CompilationCache and enabled it |
|||
for scripts too. The current strategy is to retire all entries |
|||
whenever a mark-sweep collection is started. |
|||
|
|||
Fixed bug where switch statements containing only a default case |
|||
would lead to an unbalanced stack (issue 69). |
|||
|
|||
Fixed bug that made access to the function in a named function |
|||
expression impossible in certain situations (issue 24). |
|||
|
|||
Fixed even more build issues. |
|||
|
|||
Optimized calling conventions on ARM. The conventions on ARM and |
|||
IA-32 now match. |
|||
|
|||
Removed static initializers for flags and counters. |
|||
|
|||
Improved inline caching behavior for uncommon cases where lazily |
|||
loading Date and RegExp code could force certain code paths go |
|||
megamorphic. |
|||
|
|||
Removed arguments adaption for builtins written in C++. This |
|||
makes Array.prototype.push and Array.prototype.pop slightly |
|||
faster. |
|||
|
|||
|
|||
2008-09-11: Version 0.3.1 |
|||
|
|||
Fixed a number of build issues. |
|||
|
|||
Fixed problem with missing I-cache flusing on ARM. |
|||
|
|||
Changed space layout in memory management by splitting up |
|||
code space into old data space and code space. |
|||
|
|||
Added utf-8 conversion support to the API (issue 57). |
|||
|
|||
Optimized repeated calls to eval with the same strings. These |
|||
repeated calls are common in web applications. |
|||
|
|||
Added Xcode project file. |
|||
|
|||
Optimized a couple of Array operation. |
|||
|
|||
Fixed parser bug by checking for end-of-string when parsing break |
|||
and continue (issue 35). |
|||
|
|||
Fixed problem where asian characters were not categorized as |
|||
letters. |
|||
|
|||
Fixed bug that disallowed calling functions fetched from an array |
|||
using a string as an array index (issue 32). |
|||
|
|||
Fixed bug where the internal field count on object templates were |
|||
sometimes ignored (issue 54). |
|||
|
|||
Added -f option to the shell sample for compatibility with other |
|||
engines (issue 18). |
|||
|
|||
Added source info to TryCatches in the API. |
|||
|
|||
Fixed problem where the seed for the random number generator was |
|||
clipped in a double to unsigned int conversion. |
|||
|
|||
Fixed bug where cons string symbols were sometimes converted to |
|||
non-symbol flat strings during GC. |
|||
|
|||
Fixed bug in error reporting when attempting to convert null to an |
|||
object. |
|||
|
|||
|
|||
2008-09-04: Version 0.3.0 |
|||
|
|||
Added support for running tests on the ARM simulator. |
|||
|
|||
Fixed bug in the 'in' operator where negative indices were not |
|||
treated correctly. |
|||
|
|||
Fixed build issues on gcc-4.3.1. |
|||
|
|||
Changed Date.prototype.toLocaleTimeString to not print the |
|||
timezone part of the time. |
|||
|
|||
Renamed debug.h to v8-debug.h to reduce the risk of name conflicts |
|||
with user code. |
|||
|
|||
|
|||
2008-09-02: Version 0.2.5 |
|||
|
|||
Renamed the top level directory 'public' to 'include'. |
|||
|
|||
Added 'env' option to the SCons build scripts to support |
|||
overriding the ENV part of the build environment. This is mostly |
|||
to support Windows builds in cases where SCons cannot find the |
|||
correct paths to the Windows SDK, as these paths cannot be passed |
|||
through shell environment variables. |
|||
|
|||
Enabled "Buffer Security Check" on for the Windows SCons build and |
|||
added the linker option /OPT:ICF as an optimization. |
|||
|
|||
Added the V8 benchmark suite to the repository. |
|||
|
|||
|
|||
2008-09-01: Version 0.2.4 |
|||
|
|||
Included mjsunit JavaScript test suite and C++ unit tests. |
|||
|
|||
Changed the shell sample to not print the result of executing a |
|||
script provided on the command line. |
|||
|
|||
Fixed issue when building samples on Windows using a shared V8 |
|||
library. Added visibility option on Linux build which makes the |
|||
generated library 18% smaller. |
|||
|
|||
Changed build system to accept multiple build modes in one build |
|||
and generate separate objects, libraries and executables for each |
|||
mode. |
|||
|
|||
Removed deferred negation optimization (a * -b => -(a * b)) since |
|||
this visibly changes operand conversion order. |
|||
|
|||
Improved parsing performance by introducing stack guard in |
|||
preparsing. Without a stack guard preparsing always bails out |
|||
with stack overflow. |
|||
|
|||
Changed shell sample to take flags directly from the command-line. |
|||
Added API call that implements this. |
|||
|
|||
Added load, quit and version functions to the shell sample so it's |
|||
easier to run benchmarks and tests. |
|||
|
|||
Fixed issue with building samples and cctests on 64-bit machines. |
|||
|
|||
Fixed bug in the runtime system where the prototype chain was not |
|||
always searched for a setter when setting a property that does not |
|||
exist locally. |
|||
|
|||
|
|||
2008-08-14: Version 0.2.3 |
|||
|
|||
Improved performance of garbage collection by moving the |
|||
function that updates pointers during compacting collection |
|||
into the updating visitor. This gives the compiler a better |
|||
chance to inline and avoid a function call per (potential) |
|||
pointer. |
|||
|
|||
Extended the shell sample with a --runtime-flags option. |
|||
|
|||
Added Visual Studio project files for the shell.cc and |
|||
process.cc samples. |
|||
|
|||
|
|||
2008-08-13: Version 0.2.2 |
|||
|
|||
Improved performance of garbage collection by changing the way |
|||
we use the marking stack in the event of stack overflow during |
|||
full garbage collection and by changing the way we mark roots. |
|||
|
|||
Cleaned up ARM version by removing top of stack caching and by |
|||
introducing push/pop elimination. |
|||
|
|||
Cleaned up the way runtime functions are called to allow |
|||
runtime calls with no arguments. |
|||
|
|||
Changed Windows build options to make sure that exceptions are |
|||
disabled and that optimization flags are enabled. |
|||
|
|||
Added first version of Visual Studio project files. |
|||
|
|||
|
|||
2008-08-06: Version 0.2.1 |
|||
|
|||
Improved performance of unary addition by avoiding runtime calls. |
|||
|
|||
Fixed the handling of '>' and '<=' to use right-to-left conversion |
|||
and left-to-right evaluation as specified by ECMA-262. |
|||
|
|||
Fixed a branch elimination bug on the ARM platform where incorrect |
|||
code was generated because of overly aggressive branch |
|||
elimination. |
|||
|
|||
Improved performance of code that repeatedly assigns the same |
|||
function to the same property of different objects with the same |
|||
map. |
|||
|
|||
Untangled DEBUG and ENABLE_DISASSEMBLER defines. The disassembler |
|||
no longer expects DEBUG to be defined. |
|||
|
|||
Added platform-nullos.cc to serve as the basis for new platform |
|||
implementations. |
|||
|
|||
|
|||
2008-07-30: Version 0.2.0 |
|||
|
|||
Changed all text files to have native svn:eol-style. |
|||
|
|||
Added a few samples and support for building them. The samples |
|||
include a simple shell that can be used to benchmark and test V8. |
|||
|
|||
Changed V8::GetVersion to return the version as a string. |
|||
|
|||
Added source for lazily loaded scripts to snapshots and made |
|||
serialization non-destructive. |
|||
|
|||
Improved ARM support by fixing the write barrier code to use |
|||
aligned loads and stores and by removing premature locals |
|||
optimization that relied on broken support for callee-saved |
|||
registers (removed). |
|||
|
|||
Refactored the code for marking live objects during garbage |
|||
collection and the code for allocating objects in paged |
|||
spaces. Introduced an abstraction for the map word of a heap- |
|||
allocated object and changed the memory allocator to allocate |
|||
executable memory only for spaces that may contain code objects. |
|||
|
|||
Moved StringBuilder to utils.h and ScopedLock to platform.h, where |
|||
they can be used by debugging and logging modules. Added |
|||
thread-safe message queues for dealing with debugger events. |
|||
|
|||
Fixed the source code reported by toString for certain builtin |
|||
empty functions and made sure that the prototype property of a |
|||
function is enumerable. |
|||
|
|||
Improved performance of converting values to condition flags in |
|||
generated code. |
|||
|
|||
Merged disassembler-{arch} files. |
|||
|
|||
|
|||
2008-07-28: Version 0.1.4 |
|||
|
|||
Added support for storing JavaScript stack traces in a stack |
|||
allocated buffer to make it visible in shallow core dumps. |
|||
Controlled by the --preallocate-message-memory flag which is |
|||
disabled by default. |
|||
|
|||
|
|||
2008-07-25: Version 0.1.3 |
|||
|
|||
Fixed bug in JSObject::GetPropertyAttributePostInterceptor where |
|||
map transitions would count as properties. |
|||
|
|||
Allowed aliased eval invocations by treating them as evals in the |
|||
global context. This may change in the future. |
|||
|
|||
Added support for accessing the last entered context through the |
|||
API and renamed Context::Current to Context::GetCurrent and |
|||
Context::GetSecurityContext to Context::GetCurrentSecurityContext. |
|||
|
|||
Fixed bug in the debugger that would cause the debugger scripts to |
|||
be recursively loaded and changed all disabling of interrupts to |
|||
be block-structured. |
|||
|
|||
Made snapshot data read-only to allow it to be more easily shared |
|||
across multiple users of V8 when linked as a shared library. |
|||
|
|||
|
|||
2008-07-16: Version 0.1.2 |
|||
|
|||
Fixed building on Mac OS X by recognizing i386 and friends as |
|||
IA-32 platforms. |
|||
|
|||
Added propagation of stack overflow exceptions that occur while |
|||
compiling nested functions. |
|||
|
|||
Improved debugger with support for recursive break points and |
|||
handling of exceptions that occur in the debugger JavaScript code. |
|||
|
|||
Renamed GetInternal to GetInternalField and SetInternal to |
|||
SetInternalField in the API and moved InternalFieldCount and |
|||
SetInternalFieldCount from FunctionTemplate to ObjectTemplate. |
|||
|
|||
|
|||
2008-07-09: Version 0.1.1 |
|||
|
|||
Fixed bug in stack overflow check code for IA-32 targets where a |
|||
non-tagged value in register eax was pushed to the stack. |
|||
|
|||
Fixed potential quadratic behavior when converting strings to |
|||
numbers. |
|||
|
|||
Fixed bug where the return value from Object::SetProperty could |
|||
end up being the property holder instead of the written value. |
|||
|
|||
Improved debugger support by allowing nested break points and by |
|||
dealing with stack-overflows when compiling functions before |
|||
setting break points in them. |
|||
|
|||
|
|||
2008-07-03: Version 0.1.0 |
|||
|
|||
Initial export. |
|||
|
@ -0,0 +1,51 @@ |
|||
This license applies to all parts of V8 that are not externally |
|||
maintained libraries. The externally maintained libraries used by V8 |
|||
are: |
|||
|
|||
- PCRE test suite, located in test/mjsunit/regexp-pcre.js. This is |
|||
based on the test suite from PCRE-7.3, which is copyrighted by the |
|||
University of Cambridge and Google, Inc. The copyright notice and |
|||
license are embedded in regexp-pcre.js. |
|||
|
|||
- Dtoa, located under third_party/dtoa. This code is copyrighted by |
|||
David M. Gay and released under an MIT license. |
|||
|
|||
- Strongtalk assembler, the basis of the files assembler-arm-inl.h, |
|||
assembler-arm.cc, assembler-arm.h, assembler-ia32-inl.h, |
|||
assembler-ia32.cc, assembler-ia32.h, assembler.cc and assembler.h. |
|||
This code is copyrighted by Sun Microsystems Inc. and released |
|||
under a 3-clause BSD license. |
|||
|
|||
- JSMin JavaScript minifier, located at tools/jsmin.py. This code is |
|||
copyrighted by Douglas Crockford and Baruch Even and released under |
|||
an MIT license. |
|||
|
|||
These libraries have their own licenses; we recommend you read them, |
|||
as their terms may differ from the terms below. |
|||
|
|||
Copyright 2006-2009, Google Inc. All rights reserved. |
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are |
|||
met: |
|||
|
|||
* Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
* Redistributions in binary form must reproduce the above |
|||
copyright notice, this list of conditions and the following |
|||
disclaimer in the documentation and/or other materials provided |
|||
with the distribution. |
|||
* Neither the name of Google Inc. nor the names of its |
|||
contributors may be used to endorse or promote products derived |
|||
from this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,775 @@ |
|||
# Copyright 2008 the V8 project authors. All rights reserved. |
|||
# Redistribution and use in source and binary forms, with or without |
|||
# modification, are permitted provided that the following conditions are |
|||
# met: |
|||
# |
|||
# * Redistributions of source code must retain the above copyright |
|||
# notice, this list of conditions and the following disclaimer. |
|||
# * Redistributions in binary form must reproduce the above |
|||
# copyright notice, this list of conditions and the following |
|||
# disclaimer in the documentation and/or other materials provided |
|||
# with the distribution. |
|||
# * Neither the name of Google Inc. nor the names of its |
|||
# contributors may be used to endorse or promote products derived |
|||
# from this software without specific prior written permission. |
|||
# |
|||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
|
|||
import platform |
|||
import re |
|||
import sys |
|||
import os |
|||
from os.path import join, dirname, abspath |
|||
from types import DictType, StringTypes |
|||
root_dir = dirname(File('SConstruct').rfile().abspath) |
|||
sys.path.append(join(root_dir, 'tools')) |
|||
import js2c, utils |
|||
|
|||
# ANDROID_TOP is the top of the Android checkout, fetched from the environment |
|||
# variable 'TOP'. You will also need to set the CXX, CC, AR and RANLIB |
|||
# environment variables to the cross-compiling tools. |
|||
ANDROID_TOP = os.environ.get('TOP') |
|||
if ANDROID_TOP is None: |
|||
ANDROID_TOP="" |
|||
|
|||
ANDROID_FLAGS = ['-march=armv5te', |
|||
'-mtune=xscale', |
|||
'-msoft-float', |
|||
'-fpic', |
|||
'-mthumb-interwork', |
|||
'-funwind-tables', |
|||
'-fstack-protector', |
|||
'-fno-short-enums', |
|||
'-fmessage-length=0', |
|||
'-finline-functions', |
|||
'-fno-inline-functions-called-once', |
|||
'-fgcse-after-reload', |
|||
'-frerun-cse-after-loop', |
|||
'-frename-registers', |
|||
'-fomit-frame-pointer', |
|||
'-fno-strict-aliasing', |
|||
'-finline-limit=64', |
|||
'-MD'] |
|||
|
|||
ANDROID_INCLUDES = [ANDROID_TOP + '/bionic/libc/arch-arm/include', |
|||
ANDROID_TOP + '/bionic/libc/include', |
|||
ANDROID_TOP + '/bionic/libstdc++/include', |
|||
ANDROID_TOP + '/bionic/libc/kernel/common', |
|||
ANDROID_TOP + '/bionic/libc/kernel/arch-arm', |
|||
ANDROID_TOP + '/bionic/libm/include', |
|||
ANDROID_TOP + '/bionic/libm/include/arch/arm', |
|||
ANDROID_TOP + '/bionic/libthread_db/include'] |
|||
|
|||
ANDROID_LINKFLAGS = ['-nostdlib', |
|||
'-Bdynamic', |
|||
'-Wl,-T,' + ANDROID_TOP + '/build/core/armelf.x', |
|||
'-Wl,-dynamic-linker,/system/bin/linker', |
|||
'-Wl,--gc-sections', |
|||
'-Wl,-z,nocopyreloc', |
|||
'-Wl,-rpath-link=' + ANDROID_TOP + '/out/target/product/generic/obj/lib', |
|||
ANDROID_TOP + '/out/target/product/generic/obj/lib/crtbegin_dynamic.o', |
|||
ANDROID_TOP + '/prebuilt/linux-x86/toolchain/arm-eabi-4.2.1/lib/gcc/arm-eabi/4.2.1/interwork/libgcc.a', |
|||
ANDROID_TOP + '/out/target/product/generic/obj/lib/crtend_android.o']; |
|||
|
|||
LIBRARY_FLAGS = { |
|||
'all': { |
|||
'CPPDEFINES': ['ENABLE_LOGGING_AND_PROFILING'] |
|||
}, |
|||
'gcc': { |
|||
'all': { |
|||
'CCFLAGS': ['$DIALECTFLAGS', '$WARNINGFLAGS'], |
|||
'CXXFLAGS': ['$CCFLAGS', '-fno-rtti', '-fno-exceptions'], |
|||
}, |
|||
'mode:debug': { |
|||
'CCFLAGS': ['-g', '-O0'], |
|||
'CPPDEFINES': ['ENABLE_DISASSEMBLER', 'DEBUG'], |
|||
'os:android': { |
|||
'CCFLAGS': ['-mthumb'] |
|||
} |
|||
}, |
|||
'mode:release': { |
|||
'CCFLAGS': ['-O3', '-fomit-frame-pointer', '-fdata-sections', |
|||
'-ffunction-sections'], |
|||
'os:android': { |
|||
'CCFLAGS': ['-mthumb', '-Os'], |
|||
'CPPDEFINES': ['SK_RELEASE', 'NDEBUG'] |
|||
} |
|||
}, |
|||
'os:linux': { |
|||
'CCFLAGS': ['-ansi'], |
|||
'library:shared': { |
|||
'LIBS': ['pthread', 'rt'] |
|||
} |
|||
}, |
|||
'os:macos': { |
|||
'CCFLAGS': ['-ansi'], |
|||
}, |
|||
'os:freebsd': { |
|||
'CCFLAGS': ['-ansi'], |
|||
}, |
|||
'os:win32': { |
|||
'CCFLAGS': ['-DWIN32'], |
|||
'CXXFLAGS': ['-DWIN32'], |
|||
}, |
|||
'os:android': { |
|||
'CPPDEFINES': ['ANDROID', '__ARM_ARCH_5__', '__ARM_ARCH_5T__', |
|||
'__ARM_ARCH_5E__', '__ARM_ARCH_5TE__'], |
|||
'CCFLAGS': ANDROID_FLAGS, |
|||
'WARNINGFLAGS': ['-Wall', '-Wno-unused', '-Werror=return-type', |
|||
'-Wstrict-aliasing=2'], |
|||
'CPPPATH': ANDROID_INCLUDES, |
|||
}, |
|||
'wordsize:64': { |
|||
'CCFLAGS': ['-m32'], |
|||
'LINKFLAGS': ['-m32'] |
|||
}, |
|||
'prof:oprofile': { |
|||
'CPPDEFINES': ['ENABLE_OPROFILE_AGENT'] |
|||
} |
|||
}, |
|||
'msvc': { |
|||
'all': { |
|||
'DIALECTFLAGS': ['/nologo'], |
|||
'CCFLAGS': ['$DIALECTFLAGS', '$WARNINGFLAGS'], |
|||
'CXXFLAGS': ['$CCFLAGS', '/GR-', '/Gy'], |
|||
'CPPDEFINES': ['WIN32', '_USE_32BIT_TIME_T'], |
|||
'LINKFLAGS': ['/NOLOGO', '/MACHINE:X86', '/INCREMENTAL:NO', |
|||
'/NXCOMPAT', '/IGNORE:4221'], |
|||
'ARFLAGS': ['/NOLOGO'], |
|||
'CCPDBFLAGS': ['/Zi'] |
|||
}, |
|||
'mode:debug': { |
|||
'CCFLAGS': ['/Od', '/Gm'], |
|||
'CPPDEFINES': ['_DEBUG', 'ENABLE_DISASSEMBLER', 'DEBUG'], |
|||
'LINKFLAGS': ['/DEBUG'], |
|||
'msvcrt:static': { |
|||
'CCFLAGS': ['/MTd'] |
|||
}, |
|||
'msvcrt:shared': { |
|||
'CCFLAGS': ['/MDd'] |
|||
} |
|||
}, |
|||
'mode:release': { |
|||
'CCFLAGS': ['/O2', '/GL'], |
|||
'LINKFLAGS': ['/OPT:REF', '/OPT:ICF', '/LTCG'], |
|||
'ARFLAGS': ['/LTCG'], |
|||
'msvcrt:static': { |
|||
'CCFLAGS': ['/MT'] |
|||
}, |
|||
'msvcrt:shared': { |
|||
'CCFLAGS': ['/MD'] |
|||
} |
|||
}, |
|||
} |
|||
} |
|||
|
|||
|
|||
V8_EXTRA_FLAGS = { |
|||
'gcc': { |
|||
'all': { |
|||
'CXXFLAGS': [], #['-fvisibility=hidden'], |
|||
'WARNINGFLAGS': ['-Wall', '-Werror', '-W', |
|||
'-Wno-unused-parameter'] |
|||
}, |
|||
'arch:arm': { |
|||
'CPPDEFINES': ['ARM'] |
|||
}, |
|||
'arch:android': { |
|||
'CPPDEFINES': ['ARM'] |
|||
}, |
|||
'os:win32': { |
|||
'WARNINGFLAGS': ['-pedantic', '-Wno-long-long'] |
|||
}, |
|||
'os:linux': { |
|||
'WARNINGFLAGS': ['-pedantic'] |
|||
}, |
|||
'os:macos': { |
|||
'WARNINGFLAGS': ['-pedantic'] |
|||
}, |
|||
'disassembler:on': { |
|||
'CPPDEFINES': ['ENABLE_DISASSEMBLER'] |
|||
} |
|||
}, |
|||
'msvc': { |
|||
'all': { |
|||
'WARNINGFLAGS': ['/W3', '/WX', '/wd4355', '/wd4800'] |
|||
}, |
|||
'library:shared': { |
|||
'CPPDEFINES': ['BUILDING_V8_SHARED'], |
|||
'LIBS': ['winmm', 'ws2_32'] |
|||
}, |
|||
'arch:arm': { |
|||
'CPPDEFINES': ['ARM'], |
|||
# /wd4996 is to silence the warning about sscanf |
|||
# used by the arm simulator. |
|||
'WARNINGFLAGS': ['/wd4996'] |
|||
}, |
|||
'disassembler:on': { |
|||
'CPPDEFINES': ['ENABLE_DISASSEMBLER'] |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
MKSNAPSHOT_EXTRA_FLAGS = { |
|||
'gcc': { |
|||
'os:linux': { |
|||
'LIBS': ['pthread', 'rt'], |
|||
}, |
|||
'os:macos': { |
|||
'LIBS': ['pthread'], |
|||
}, |
|||
'os:freebsd': { |
|||
'LIBS': ['pthread'], |
|||
}, |
|||
'os:win32': { |
|||
'LIBS': ['winmm', 'ws2_32'], |
|||
}, |
|||
}, |
|||
'msvc': { |
|||
'all': { |
|||
'LIBS': ['winmm', 'ws2_32'] |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
DTOA_EXTRA_FLAGS = { |
|||
'gcc': { |
|||
'all': { |
|||
'WARNINGFLAGS': ['-Werror', '-Wno-uninitialized'] |
|||
} |
|||
}, |
|||
'msvc': { |
|||
'all': { |
|||
'WARNINGFLAGS': ['/WX', '/wd4018', '/wd4244'] |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
CCTEST_EXTRA_FLAGS = { |
|||
'all': { |
|||
'CPPPATH': [join(root_dir, 'src')], |
|||
'LIBS': ['$LIBRARY'] |
|||
}, |
|||
'gcc': { |
|||
'all': { |
|||
'LIBPATH': [abspath('.')] |
|||
}, |
|||
'os:linux': { |
|||
'LIBS': ['pthread', 'rt'], |
|||
}, |
|||
'os:macos': { |
|||
'LIBS': ['pthread'], |
|||
}, |
|||
'os:freebsd': { |
|||
'LIBS': ['execinfo', 'pthread'] |
|||
}, |
|||
'os:win32': { |
|||
'LIBS': ['winmm', 'ws2_32'] |
|||
}, |
|||
'wordsize:64': { |
|||
'CCFLAGS': ['-m32'], |
|||
'LINKFLAGS': ['-m32'] |
|||
}, |
|||
}, |
|||
'msvc': { |
|||
'all': { |
|||
'CPPDEFINES': ['_HAS_EXCEPTIONS=0'], |
|||
'LIBS': ['winmm', 'ws2_32'] |
|||
}, |
|||
'library:shared': { |
|||
'CPPDEFINES': ['USING_V8_SHARED'] |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
SAMPLE_FLAGS = { |
|||
'all': { |
|||
'CPPPATH': [join(abspath('.'), 'include')], |
|||
'LIBS': ['$LIBRARY'], |
|||
}, |
|||
'gcc': { |
|||
'all': { |
|||
'LIBPATH': ['.'], |
|||
'CCFLAGS': ['-fno-rtti', '-fno-exceptions'] |
|||
}, |
|||
'os:linux': { |
|||
'LIBS': ['pthread', 'rt'], |
|||
}, |
|||
'os:macos': { |
|||
'LIBS': ['pthread'], |
|||
}, |
|||
'os:freebsd': { |
|||
'LIBS': ['execinfo', 'pthread'] |
|||
}, |
|||
'os:win32': { |
|||
'LIBS': ['winmm', 'ws2_32'] |
|||
}, |
|||
'os:android': { |
|||
'CPPDEFINES': ['ANDROID', '__ARM_ARCH_5__', '__ARM_ARCH_5T__', |
|||
'__ARM_ARCH_5E__', '__ARM_ARCH_5TE__'], |
|||
'CCFLAGS': ANDROID_FLAGS, |
|||
'CPPPATH': ANDROID_INCLUDES, |
|||
'LIBPATH': [ANDROID_TOP + '/out/target/product/generic/obj/lib'], |
|||
'LINKFLAGS': ANDROID_LINKFLAGS, |
|||
'LIBS': ['c', 'stdc++', 'm'], |
|||
'mode:release': { |
|||
'CPPDEFINES': ['SK_RELEASE', 'NDEBUG'] |
|||
} |
|||
}, |
|||
'wordsize:64': { |
|||
'CCFLAGS': ['-m32'], |
|||
'LINKFLAGS': ['-m32'] |
|||
}, |
|||
'mode:release': { |
|||
'CCFLAGS': ['-O2'] |
|||
}, |
|||
'mode:debug': { |
|||
'CCFLAGS': ['-g', '-O0'] |
|||
}, |
|||
'prof:oprofile': { |
|||
'LIBPATH': ['/usr/lib32', '/usr/lib32/oprofile'], |
|||
'LIBS': ['opagent'] |
|||
} |
|||
}, |
|||
'msvc': { |
|||
'all': { |
|||
'CCFLAGS': ['/nologo'], |
|||
'LINKFLAGS': ['/nologo'], |
|||
'LIBS': ['winmm', 'ws2_32'] |
|||
}, |
|||
'library:shared': { |
|||
'CPPDEFINES': ['USING_V8_SHARED'] |
|||
}, |
|||
'prof:on': { |
|||
'LINKFLAGS': ['/MAP'] |
|||
}, |
|||
'mode:release': { |
|||
'CCFLAGS': ['/O2'], |
|||
'LINKFLAGS': ['/OPT:REF', '/OPT:ICF', '/LTCG'], |
|||
'msvcrt:static': { |
|||
'CCFLAGS': ['/MT'] |
|||
}, |
|||
'msvcrt:shared': { |
|||
'CCFLAGS': ['/MD'] |
|||
} |
|||
}, |
|||
'mode:debug': { |
|||
'CCFLAGS': ['/Od'], |
|||
'LINKFLAGS': ['/DEBUG'], |
|||
'msvcrt:static': { |
|||
'CCFLAGS': ['/MTd'] |
|||
}, |
|||
'msvcrt:shared': { |
|||
'CCFLAGS': ['/MDd'] |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
D8_FLAGS = { |
|||
'gcc': { |
|||
'console:readline': { |
|||
'LIBS': ['readline'] |
|||
}, |
|||
'os:linux': { |
|||
'LIBS': ['pthread', 'rt'], |
|||
}, |
|||
'os:macos': { |
|||
'LIBS': ['pthread'], |
|||
}, |
|||
'os:freebsd': { |
|||
'LIBS': ['pthread'], |
|||
}, |
|||
'os:android': { |
|||
'LIBPATH': [ANDROID_TOP + '/out/target/product/generic/obj/lib'], |
|||
'LINKFLAGS': ANDROID_LINKFLAGS, |
|||
'LIBS': ['c', 'stdc++', 'm'], |
|||
}, |
|||
'os:win32': { |
|||
'LIBS': ['winmm', 'ws2_32'], |
|||
}, |
|||
}, |
|||
'msvc': { |
|||
'all': { |
|||
'LIBS': ['winmm', 'ws2_32'] |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
SUFFIXES = { |
|||
'release': '', |
|||
'debug': '_g' |
|||
} |
|||
|
|||
|
|||
def Abort(message): |
|||
print message |
|||
sys.exit(1) |
|||
|
|||
|
|||
def GuessToolchain(os): |
|||
tools = Environment()['TOOLS'] |
|||
if 'gcc' in tools: |
|||
return 'gcc' |
|||
elif 'msvc' in tools: |
|||
return 'msvc' |
|||
else: |
|||
return None |
|||
|
|||
|
|||
OS_GUESS = utils.GuessOS() |
|||
TOOLCHAIN_GUESS = GuessToolchain(OS_GUESS) |
|||
ARCH_GUESS = utils.GuessArchitecture() |
|||
WORDSIZE_GUESS = utils.GuessWordsize() |
|||
|
|||
|
|||
SIMPLE_OPTIONS = { |
|||
'toolchain': { |
|||
'values': ['gcc', 'msvc'], |
|||
'default': TOOLCHAIN_GUESS, |
|||
'help': 'the toolchain to use' |
|||
}, |
|||
'os': { |
|||
'values': ['freebsd', 'linux', 'macos', 'win32', 'android'], |
|||
'default': OS_GUESS, |
|||
'help': 'the os to build for' |
|||
}, |
|||
'arch': { |
|||
'values':['arm', 'ia32'], |
|||
'default': ARCH_GUESS, |
|||
'help': 'the architecture to build for' |
|||
}, |
|||
'snapshot': { |
|||
'values': ['on', 'off', 'nobuild'], |
|||
'default': 'off', |
|||
'help': 'build using snapshots for faster start-up' |
|||
}, |
|||
'prof': { |
|||
'values': ['on', 'off', 'oprofile'], |
|||
'default': 'off', |
|||
'help': 'enable profiling of build target' |
|||
}, |
|||
'library': { |
|||
'values': ['static', 'shared'], |
|||
'default': 'static', |
|||
'help': 'the type of library to produce' |
|||
}, |
|||
'msvcrt': { |
|||
'values': ['static', 'shared'], |
|||
'default': 'static', |
|||
'help': 'the type of MSVCRT library to use' |
|||
}, |
|||
'wordsize': { |
|||
'values': ['64', '32'], |
|||
'default': WORDSIZE_GUESS, |
|||
'help': 'the word size' |
|||
}, |
|||
'simulator': { |
|||
'values': ['arm', 'none'], |
|||
'default': 'none', |
|||
'help': 'build with simulator' |
|||
}, |
|||
'disassembler': { |
|||
'values': ['on', 'off'], |
|||
'default': 'off', |
|||
'help': 'enable the disassembler to inspect generated code' |
|||
}, |
|||
'sourcesignatures': { |
|||
'values': ['MD5', 'timestamp'], |
|||
'default': 'MD5', |
|||
'help': 'set how the build system detects file changes' |
|||
}, |
|||
'console': { |
|||
'values': ['dumb', 'readline'], |
|||
'default': 'dumb', |
|||
'help': 'the console to use for the d8 shell' |
|||
} |
|||
} |
|||
|
|||
|
|||
def GetOptions(): |
|||
result = Options() |
|||
result.Add('mode', 'compilation mode (debug, release)', 'release') |
|||
result.Add('sample', 'build sample (shell, process)', '') |
|||
result.Add('env', 'override environment settings (NAME0:value0,NAME1:value1,...)', '') |
|||
result.Add('importenv', 'import environment settings (NAME0,NAME1,...)', '') |
|||
for (name, option) in SIMPLE_OPTIONS.iteritems(): |
|||
help = '%s (%s)' % (name, ", ".join(option['values'])) |
|||
result.Add(name, help, option.get('default')) |
|||
return result |
|||
|
|||
|
|||
def SplitList(str): |
|||
return [ s for s in str.split(",") if len(s) > 0 ] |
|||
|
|||
|
|||
def IsLegal(env, option, values): |
|||
str = env[option] |
|||
for s in SplitList(str): |
|||
if not s in values: |
|||
Abort("Illegal value for option %s '%s'." % (option, s)) |
|||
return False |
|||
return True |
|||
|
|||
|
|||
def VerifyOptions(env): |
|||
if not IsLegal(env, 'mode', ['debug', 'release']): |
|||
return False |
|||
if not IsLegal(env, 'sample', ["shell", "process"]): |
|||
return False |
|||
if env['os'] == 'win32' and env['library'] == 'shared' and env['prof'] == 'on': |
|||
Abort("Profiling on windows only supported for static library.") |
|||
if env['prof'] == 'oprofile' and env['os'] != 'linux': |
|||
Abort("OProfile is only supported on Linux.") |
|||
for (name, option) in SIMPLE_OPTIONS.iteritems(): |
|||
if (not option.get('default')) and (name not in ARGUMENTS): |
|||
message = ("A value for option %s must be specified (%s)." % |
|||
(name, ", ".join(option['values']))) |
|||
Abort(message) |
|||
if not env[name] in option['values']: |
|||
message = ("Unknown %s value '%s'. Possible values are (%s)." % |
|||
(name, env[name], ", ".join(option['values']))) |
|||
Abort(message) |
|||
|
|||
|
|||
class BuildContext(object): |
|||
|
|||
def __init__(self, options, env_overrides, samples): |
|||
self.library_targets = [] |
|||
self.mksnapshot_targets = [] |
|||
self.cctest_targets = [] |
|||
self.sample_targets = [] |
|||
self.d8_targets = [] |
|||
self.options = options |
|||
self.env_overrides = env_overrides |
|||
self.samples = samples |
|||
self.use_snapshot = (options['snapshot'] != 'off') |
|||
self.build_snapshot = (options['snapshot'] == 'on') |
|||
self.flags = None |
|||
|
|||
def AddRelevantFlags(self, initial, flags): |
|||
result = initial.copy() |
|||
self.AppendFlags(result, flags.get('all')) |
|||
toolchain = self.options['toolchain'] |
|||
if toolchain in flags: |
|||
self.AppendFlags(result, flags[toolchain].get('all')) |
|||
for option in sorted(self.options.keys()): |
|||
value = self.options[option] |
|||
self.AppendFlags(result, flags[toolchain].get(option + ':' + value)) |
|||
return result |
|||
|
|||
def AddRelevantSubFlags(self, options, flags): |
|||
self.AppendFlags(options, flags.get('all')) |
|||
for option in sorted(self.options.keys()): |
|||
value = self.options[option] |
|||
self.AppendFlags(options, flags.get(option + ':' + value)) |
|||
|
|||
def GetRelevantSources(self, source): |
|||
result = [] |
|||
result += source.get('all', []) |
|||
for (name, value) in self.options.iteritems(): |
|||
result += source.get(name + ':' + value, []) |
|||
return sorted(result) |
|||
|
|||
def AppendFlags(self, options, added): |
|||
if not added: |
|||
return |
|||
for (key, value) in added.iteritems(): |
|||
if key.find(':') != -1: |
|||
self.AddRelevantSubFlags(options, { key: value }) |
|||
else: |
|||
if not key in options: |
|||
options[key] = value |
|||
else: |
|||
prefix = options[key] |
|||
if isinstance(prefix, StringTypes): prefix = prefix.split() |
|||
options[key] = prefix + value |
|||
|
|||
def ConfigureObject(self, env, input, **kw): |
|||
if (kw.has_key('CPPPATH') and env.has_key('CPPPATH')): |
|||
kw['CPPPATH'] += env['CPPPATH'] |
|||
if self.options['library'] == 'static': |
|||
return env.StaticObject(input, **kw) |
|||
else: |
|||
return env.SharedObject(input, **kw) |
|||
|
|||
def ApplyEnvOverrides(self, env): |
|||
if not self.env_overrides: |
|||
return |
|||
if type(env['ENV']) == DictType: |
|||
env['ENV'].update(**self.env_overrides) |
|||
else: |
|||
env['ENV'] = self.env_overrides |
|||
|
|||
|
|||
def PostprocessOptions(options): |
|||
# Adjust architecture if the simulator option has been set |
|||
if (options['simulator'] != 'none') and (options['arch'] != options['simulator']): |
|||
if 'arch' in ARGUMENTS: |
|||
# Print a warning if arch has explicitly been set |
|||
print "Warning: forcing architecture to match simulator (%s)" % options['simulator'] |
|||
options['arch'] = options['simulator'] |
|||
|
|||
|
|||
def ParseEnvOverrides(arg, imports): |
|||
# The environment overrides are in the format NAME0:value0,NAME1:value1,... |
|||
# The environment imports are in the format NAME0,NAME1,... |
|||
overrides = {} |
|||
for var in imports.split(','): |
|||
if var in os.environ: |
|||
overrides[var] = os.environ[var] |
|||
for override in arg.split(','): |
|||
pos = override.find(':') |
|||
if pos == -1: |
|||
continue |
|||
overrides[override[:pos].strip()] = override[pos+1:].strip() |
|||
return overrides |
|||
|
|||
|
|||
def BuildSpecific(env, mode, env_overrides): |
|||
options = {'mode': mode} |
|||
for option in SIMPLE_OPTIONS: |
|||
options[option] = env[option] |
|||
PostprocessOptions(options) |
|||
|
|||
context = BuildContext(options, env_overrides, samples=SplitList(env['sample'])) |
|||
|
|||
library_flags = context.AddRelevantFlags(os.environ, LIBRARY_FLAGS) |
|||
v8_flags = context.AddRelevantFlags(library_flags, V8_EXTRA_FLAGS) |
|||
mksnapshot_flags = context.AddRelevantFlags(library_flags, MKSNAPSHOT_EXTRA_FLAGS) |
|||
dtoa_flags = context.AddRelevantFlags(library_flags, DTOA_EXTRA_FLAGS) |
|||
cctest_flags = context.AddRelevantFlags(v8_flags, CCTEST_EXTRA_FLAGS) |
|||
sample_flags = context.AddRelevantFlags(os.environ, SAMPLE_FLAGS) |
|||
d8_flags = context.AddRelevantFlags(library_flags, D8_FLAGS) |
|||
|
|||
context.flags = { |
|||
'v8': v8_flags, |
|||
'mksnapshot': mksnapshot_flags, |
|||
'dtoa': dtoa_flags, |
|||
'cctest': cctest_flags, |
|||
'sample': sample_flags, |
|||
'd8': d8_flags |
|||
} |
|||
|
|||
target_id = mode |
|||
suffix = SUFFIXES[target_id] |
|||
library_name = 'v8' + suffix |
|||
env['LIBRARY'] = library_name |
|||
|
|||
# Build the object files by invoking SCons recursively. |
|||
(object_files, shell_files, mksnapshot) = env.SConscript( |
|||
join('src', 'SConscript'), |
|||
build_dir=join('obj', target_id), |
|||
exports='context', |
|||
duplicate=False |
|||
) |
|||
|
|||
context.mksnapshot_targets.append(mksnapshot) |
|||
|
|||
# Link the object files into a library. |
|||
env.Replace(**context.flags['v8']) |
|||
context.ApplyEnvOverrides(env) |
|||
if context.options['library'] == 'static': |
|||
library = env.StaticLibrary(library_name, object_files) |
|||
else: |
|||
# There seems to be a glitch in the way scons decides where to put |
|||
# PDB files when compiling using MSVC so we specify it manually. |
|||
# This should not affect any other platforms. |
|||
pdb_name = library_name + '.dll.pdb' |
|||
library = env.SharedLibrary(library_name, object_files, PDB=pdb_name) |
|||
context.library_targets.append(library) |
|||
|
|||
d8_env = Environment() |
|||
d8_env.Replace(**context.flags['d8']) |
|||
shell = d8_env.Program('d8' + suffix, object_files + shell_files) |
|||
context.d8_targets.append(shell) |
|||
|
|||
for sample in context.samples: |
|||
sample_env = Environment(LIBRARY=library_name) |
|||
sample_env.Replace(**context.flags['sample']) |
|||
context.ApplyEnvOverrides(sample_env) |
|||
sample_object = sample_env.SConscript( |
|||
join('samples', 'SConscript'), |
|||
build_dir=join('obj', 'sample', sample, target_id), |
|||
exports='sample context', |
|||
duplicate=False |
|||
) |
|||
sample_name = sample + suffix |
|||
sample_program = sample_env.Program(sample_name, sample_object) |
|||
sample_env.Depends(sample_program, library) |
|||
context.sample_targets.append(sample_program) |
|||
|
|||
cctest_program = env.SConscript( |
|||
join('test', 'cctest', 'SConscript'), |
|||
build_dir=join('obj', 'test', target_id), |
|||
exports='context object_files', |
|||
duplicate=False |
|||
) |
|||
context.cctest_targets.append(cctest_program) |
|||
|
|||
return context |
|||
|
|||
|
|||
def Build(): |
|||
opts = GetOptions() |
|||
env = Environment(options=opts) |
|||
Help(opts.GenerateHelpText(env)) |
|||
VerifyOptions(env) |
|||
env_overrides = ParseEnvOverrides(env['env'], env['importenv']) |
|||
|
|||
SourceSignatures(env['sourcesignatures']) |
|||
|
|||
libraries = [] |
|||
mksnapshots = [] |
|||
cctests = [] |
|||
samples = [] |
|||
d8s = [] |
|||
modes = SplitList(env['mode']) |
|||
for mode in modes: |
|||
context = BuildSpecific(env.Copy(), mode, env_overrides) |
|||
libraries += context.library_targets |
|||
mksnapshots += context.mksnapshot_targets |
|||
cctests += context.cctest_targets |
|||
samples += context.sample_targets |
|||
d8s += context.d8_targets |
|||
|
|||
env.Alias('library', libraries) |
|||
env.Alias('mksnapshot', mksnapshots) |
|||
env.Alias('cctests', cctests) |
|||
env.Alias('sample', samples) |
|||
env.Alias('d8', d8s) |
|||
|
|||
if env['sample']: |
|||
env.Default('sample') |
|||
else: |
|||
env.Default('library') |
|||
|
|||
|
|||
# We disable deprecation warnings because we need to be able to use |
|||
# env.Copy without getting warnings for compatibility with older |
|||
# version of scons. Also, there's a bug in some revisions that |
|||
# doesn't allow this flag to be set, so we swallow any exceptions. |
|||
# Lovely. |
|||
try: |
|||
SetOption('warn', 'no-deprecated') |
|||
except: |
|||
pass |
|||
|
|||
|
|||
Build() |
@ -0,0 +1,42 @@ |
|||
V8 Benchmark Suite |
|||
================== |
|||
|
|||
This is the V8 benchmark suite: A collection of pure JavaScript |
|||
benchmarks that we have used to tune V8. The licenses for the |
|||
individual benchmarks are included in the JavaScript files. |
|||
|
|||
In addition to the benchmarks, the suite consists of the benchmark |
|||
framework (base.js), which must be loaded before any of the individual |
|||
benchmark files, and two benchmark runners: An HTML version (run.html) |
|||
and a standalone JavaScript version (run.js). |
|||
|
|||
|
|||
Changes From Version 1 To Version 2 |
|||
=================================== |
|||
|
|||
For version 2 the crypto benchmark was fixed. Previously, the |
|||
decryption stage was given plaintext as input, which resulted in an |
|||
error. Now, the decryption stage is given the output of the |
|||
encryption stage as input. The result is checked against the original |
|||
plaintext. For this to give the correct results the crypto objects |
|||
are reset for each iteration of the benchmark. In addition, the size |
|||
of the plain text has been increased a little and the use of |
|||
Math.random() and new Date() to build an RNG pool has been removed. |
|||
|
|||
Other benchmarks were fixed to do elementary verification of the |
|||
results of their calculations. This is to avoid accidentally |
|||
obtaining scores that are the result of an incorrect JavaScript engine |
|||
optimization. |
|||
|
|||
|
|||
Changes From Version 2 To Version 3 |
|||
=================================== |
|||
|
|||
Version 3 adds a new benchmark, RegExp. The RegExp benchmark is |
|||
generated by loading 50 of the most popular pages on the web and |
|||
logging all regexp operations performed. Each operation is given a |
|||
weight that is calculated from an estimate of the popularity of the |
|||
pages where it occurs and the number of times it is executed while |
|||
loading each page. Finally the literal letters in the data are |
|||
encoded using ROT13 in a way that does not affect how the regexps |
|||
match their input. |
@ -0,0 +1,233 @@ |
|||
// Copyright 2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
|
|||
// Simple framework for running the benchmark suites and
|
|||
// computing a score based on the timing measurements.
|
|||
|
|||
|
|||
// A benchmark has a name (string) and a function that will be run to
|
|||
// do the performance measurement.
|
|||
function Benchmark(name, run) { |
|||
this.name = name; |
|||
this.run = run; |
|||
} |
|||
|
|||
|
|||
// Benchmark results hold the benchmark and the measured time used to
|
|||
// run the benchmark. The benchmark score is computed later once a
|
|||
// full benchmark suite has run to completion.
|
|||
function BenchmarkResult(benchmark, time) { |
|||
this.benchmark = benchmark; |
|||
this.time = time; |
|||
} |
|||
|
|||
|
|||
// Automatically convert results to numbers. Used by the geometric
|
|||
// mean computation.
|
|||
BenchmarkResult.prototype.valueOf = function() { |
|||
return this.time; |
|||
} |
|||
|
|||
|
|||
// Suites of benchmarks consist of a name and the set of benchmarks in
|
|||
// addition to the reference timing that the final score will be based
|
|||
// on. This way, all scores are relative to a reference run and higher
|
|||
// scores implies better performance.
|
|||
function BenchmarkSuite(name, reference, benchmarks) { |
|||
this.name = name; |
|||
this.reference = reference; |
|||
this.benchmarks = benchmarks; |
|||
BenchmarkSuite.suites.push(this); |
|||
} |
|||
|
|||
|
|||
// Keep track of all declared benchmark suites.
|
|||
BenchmarkSuite.suites = []; |
|||
|
|||
|
|||
// Scores are not comparable across versions. Bump the version if
|
|||
// you're making changes that will affect that scores, e.g. if you add
|
|||
// a new benchmark or change an existing one.
|
|||
BenchmarkSuite.version = '3'; |
|||
|
|||
|
|||
// To make the benchmark results predictable, we replace Math.random
|
|||
// with a 100% deterministic alternative.
|
|||
Math.random = (function() { |
|||
var seed = 49734321; |
|||
return function() { |
|||
// Robert Jenkins' 32 bit integer hash function.
|
|||
seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff; |
|||
seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff; |
|||
seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff; |
|||
seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; |
|||
seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff; |
|||
seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff; |
|||
seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff; |
|||
return (seed & 0xfffffff) / 0x10000000; |
|||
}; |
|||
})(); |
|||
|
|||
|
|||
// Runs all registered benchmark suites and optionally yields between
|
|||
// each individual benchmark to avoid running for too long in the
|
|||
// context of browsers. Once done, the final score is reported to the
|
|||
// runner.
|
|||
BenchmarkSuite.RunSuites = function(runner) { |
|||
var continuation = null; |
|||
var suites = BenchmarkSuite.suites; |
|||
var length = suites.length; |
|||
BenchmarkSuite.scores = []; |
|||
var index = 0; |
|||
function RunStep() { |
|||
while (continuation || index < length) { |
|||
if (continuation) { |
|||
continuation = continuation(); |
|||
} else { |
|||
var suite = suites[index++]; |
|||
if (runner.NotifyStart) runner.NotifyStart(suite.name); |
|||
continuation = suite.RunStep(runner); |
|||
} |
|||
if (continuation && typeof window != 'undefined' && window.setTimeout) { |
|||
window.setTimeout(RunStep, 100); |
|||
return; |
|||
} |
|||
} |
|||
if (runner.NotifyScore) { |
|||
var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores); |
|||
var formatted = BenchmarkSuite.FormatScore(100 * score); |
|||
runner.NotifyScore(formatted); |
|||
} |
|||
} |
|||
RunStep(); |
|||
} |
|||
|
|||
|
|||
// Counts the total number of registered benchmarks. Useful for
|
|||
// showing progress as a percentage.
|
|||
BenchmarkSuite.CountBenchmarks = function() { |
|||
var result = 0; |
|||
var suites = BenchmarkSuite.suites; |
|||
for (var i = 0; i < suites.length; i++) { |
|||
result += suites[i].benchmarks.length; |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
|
|||
// Computes the geometric mean of a set of numbers.
|
|||
BenchmarkSuite.GeometricMean = function(numbers) { |
|||
var log = 0; |
|||
for (var i = 0; i < numbers.length; i++) { |
|||
log += Math.log(numbers[i]); |
|||
} |
|||
return Math.pow(Math.E, log / numbers.length); |
|||
} |
|||
|
|||
|
|||
// Converts a score value to a string with at least three significant
|
|||
// digits.
|
|||
BenchmarkSuite.FormatScore = function(value) { |
|||
if (value > 100) { |
|||
return value.toFixed(0); |
|||
} else { |
|||
return value.toPrecision(3); |
|||
} |
|||
} |
|||
|
|||
// Notifies the runner that we're done running a single benchmark in
|
|||
// the benchmark suite. This can be useful to report progress.
|
|||
BenchmarkSuite.prototype.NotifyStep = function(result) { |
|||
this.results.push(result); |
|||
if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name); |
|||
} |
|||
|
|||
|
|||
// Notifies the runner that we're done with running a suite and that
|
|||
// we have a result which can be reported to the user if needed.
|
|||
BenchmarkSuite.prototype.NotifyResult = function() { |
|||
var mean = BenchmarkSuite.GeometricMean(this.results); |
|||
var score = this.reference / mean; |
|||
BenchmarkSuite.scores.push(score); |
|||
if (this.runner.NotifyResult) { |
|||
var formatted = BenchmarkSuite.FormatScore(100 * score); |
|||
this.runner.NotifyResult(this.name, formatted); |
|||
} |
|||
} |
|||
|
|||
|
|||
// Notifies the runner that running a benchmark resulted in an error.
|
|||
BenchmarkSuite.prototype.NotifyError = function(error) { |
|||
if (this.runner.NotifyError) { |
|||
this.runner.NotifyError(this.name, error); |
|||
} |
|||
if (this.runner.NotifyStep) { |
|||
this.runner.NotifyStep(this.name); |
|||
} |
|||
} |
|||
|
|||
|
|||
// Runs a single benchmark for at least a second and computes the
|
|||
// average time it takes to run a single iteration.
|
|||
BenchmarkSuite.prototype.RunSingle = function(benchmark) { |
|||
var elapsed = 0; |
|||
var start = new Date(); |
|||
for (var n = 0; elapsed < 1000; n++) { |
|||
benchmark.run(); |
|||
elapsed = new Date() - start; |
|||
} |
|||
var usec = (elapsed * 1000) / n; |
|||
this.NotifyStep(new BenchmarkResult(benchmark, usec)); |
|||
} |
|||
|
|||
|
|||
// This function starts running a suite, but stops between each
|
|||
// individual benchmark in the suite and returns a continuation
|
|||
// function which can be invoked to run the next benchmark. Once the
|
|||
// last benchmark has been executed, null is returned.
|
|||
BenchmarkSuite.prototype.RunStep = function(runner) { |
|||
this.results = []; |
|||
this.runner = runner; |
|||
var length = this.benchmarks.length; |
|||
var index = 0; |
|||
var suite = this; |
|||
function RunNext() { |
|||
if (index < length) { |
|||
try { |
|||
suite.RunSingle(suite.benchmarks[index++]); |
|||
} catch (e) { |
|||
suite.NotifyError(e); |
|||
return null; |
|||
} |
|||
return RunNext; |
|||
} |
|||
suite.NotifyResult(); |
|||
return null; |
|||
} |
|||
return RunNext(); |
|||
} |
File diff suppressed because it is too large
@ -0,0 +1,880 @@ |
|||
// Copyright 2008 the V8 project authors. All rights reserved.
|
|||
// Copyright 1996 John Maloney and Mario Wolczko.
|
|||
|
|||
// This program is free software; you can redistribute it and/or modify
|
|||
// it under the terms of the GNU General Public License as published by
|
|||
// the Free Software Foundation; either version 2 of the License, or
|
|||
// (at your option) any later version.
|
|||
//
|
|||
// This program is distributed in the hope that it will be useful,
|
|||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
// GNU General Public License for more details.
|
|||
//
|
|||
// You should have received a copy of the GNU General Public License
|
|||
// along with this program; if not, write to the Free Software
|
|||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|||
|
|||
|
|||
// This implementation of the DeltaBlue benchmark is derived
|
|||
// from the Smalltalk implementation by John Maloney and Mario
|
|||
// Wolczko. Some parts have been translated directly, whereas
|
|||
// others have been modified more aggresively to make it feel
|
|||
// more like a JavaScript program.
|
|||
|
|||
|
|||
var DeltaBlue = new BenchmarkSuite('DeltaBlue', 71104, [ |
|||
new Benchmark('DeltaBlue', deltaBlue) |
|||
]); |
|||
|
|||
|
|||
/** |
|||
* A JavaScript implementation of the DeltaBlue constrain-solving |
|||
* algorithm, as described in: |
|||
* |
|||
* "The DeltaBlue Algorithm: An Incremental Constraint Hierarchy Solver" |
|||
* Bjorn N. Freeman-Benson and John Maloney |
|||
* January 1990 Communications of the ACM, |
|||
* also available as University of Washington TR 89-08-06. |
|||
* |
|||
* Beware: this benchmark is written in a grotesque style where |
|||
* the constraint model is built by side-effects from constructors. |
|||
* I've kept it this way to avoid deviating too much from the original |
|||
* implementation. |
|||
*/ |
|||
|
|||
|
|||
/* --- O b j e c t M o d e l --- */ |
|||
|
|||
Object.prototype.inherits = function (shuper) { |
|||
function Inheriter() { } |
|||
Inheriter.prototype = shuper.prototype; |
|||
this.prototype = new Inheriter(); |
|||
this.superConstructor = shuper; |
|||
} |
|||
|
|||
function OrderedCollection() { |
|||
this.elms = new Array(); |
|||
} |
|||
|
|||
OrderedCollection.prototype.add = function (elm) { |
|||
this.elms.push(elm); |
|||
} |
|||
|
|||
OrderedCollection.prototype.at = function (index) { |
|||
return this.elms[index]; |
|||
} |
|||
|
|||
OrderedCollection.prototype.size = function () { |
|||
return this.elms.length; |
|||
} |
|||
|
|||
OrderedCollection.prototype.removeFirst = function () { |
|||
return this.elms.pop(); |
|||
} |
|||
|
|||
OrderedCollection.prototype.remove = function (elm) { |
|||
var index = 0, skipped = 0; |
|||
for (var i = 0; i < this.elms.length; i++) { |
|||
var value = this.elms[i]; |
|||
if (value != elm) { |
|||
this.elms[index] = value; |
|||
index++; |
|||
} else { |
|||
skipped++; |
|||
} |
|||
} |
|||
for (var i = 0; i < skipped; i++) |
|||
this.elms.pop(); |
|||
} |
|||
|
|||
/* --- * |
|||
* S t r e n g t h |
|||
* --- */ |
|||
|
|||
/** |
|||
* Strengths are used to measure the relative importance of constraints. |
|||
* New strengths may be inserted in the strength hierarchy without |
|||
* disrupting current constraints. Strengths cannot be created outside |
|||
* this class, so pointer comparison can be used for value comparison. |
|||
*/ |
|||
function Strength(strengthValue, name) { |
|||
this.strengthValue = strengthValue; |
|||
this.name = name; |
|||
} |
|||
|
|||
Strength.stronger = function (s1, s2) { |
|||
return s1.strengthValue < s2.strengthValue; |
|||
} |
|||
|
|||
Strength.weaker = function (s1, s2) { |
|||
return s1.strengthValue > s2.strengthValue; |
|||
} |
|||
|
|||
Strength.weakestOf = function (s1, s2) { |
|||
return this.weaker(s1, s2) ? s1 : s2; |
|||
} |
|||
|
|||
Strength.strongest = function (s1, s2) { |
|||
return this.stronger(s1, s2) ? s1 : s2; |
|||
} |
|||
|
|||
Strength.prototype.nextWeaker = function () { |
|||
switch (this.strengthValue) { |
|||
case 0: return Strength.WEAKEST; |
|||
case 1: return Strength.WEAK_DEFAULT; |
|||
case 2: return Strength.NORMAL; |
|||
case 3: return Strength.STRONG_DEFAULT; |
|||
case 4: return Strength.PREFERRED; |
|||
case 5: return Strength.REQUIRED; |
|||
} |
|||
} |
|||
|
|||
// Strength constants.
|
|||
Strength.REQUIRED = new Strength(0, "required"); |
|||
Strength.STONG_PREFERRED = new Strength(1, "strongPreferred"); |
|||
Strength.PREFERRED = new Strength(2, "preferred"); |
|||
Strength.STRONG_DEFAULT = new Strength(3, "strongDefault"); |
|||
Strength.NORMAL = new Strength(4, "normal"); |
|||
Strength.WEAK_DEFAULT = new Strength(5, "weakDefault"); |
|||
Strength.WEAKEST = new Strength(6, "weakest"); |
|||
|
|||
/* --- * |
|||
* C o n s t r a i n t |
|||
* --- */ |
|||
|
|||
/** |
|||
* An abstract class representing a system-maintainable relationship |
|||
* (or "constraint") between a set of variables. A constraint supplies |
|||
* a strength instance variable; concrete subclasses provide a means |
|||
* of storing the constrained variables and other information required |
|||
* to represent a constraint. |
|||
*/ |
|||
function Constraint(strength) { |
|||
this.strength = strength; |
|||
} |
|||
|
|||
/** |
|||
* Activate this constraint and attempt to satisfy it. |
|||
*/ |
|||
Constraint.prototype.addConstraint = function () { |
|||
this.addToGraph(); |
|||
planner.incrementalAdd(this); |
|||
} |
|||
|
|||
/** |
|||
* Attempt to find a way to enforce this constraint. If successful, |
|||
* record the solution, perhaps modifying the current dataflow |
|||
* graph. Answer the constraint that this constraint overrides, if |
|||
* there is one, or nil, if there isn't. |
|||
* Assume: I am not already satisfied. |
|||
*/ |
|||
Constraint.prototype.satisfy = function (mark) { |
|||
this.chooseMethod(mark); |
|||
if (!this.isSatisfied()) { |
|||
if (this.strength == Strength.REQUIRED) |
|||
alert("Could not satisfy a required constraint!"); |
|||
return null; |
|||
} |
|||
this.markInputs(mark); |
|||
var out = this.output(); |
|||
var overridden = out.determinedBy; |
|||
if (overridden != null) overridden.markUnsatisfied(); |
|||
out.determinedBy = this; |
|||
if (!planner.addPropagate(this, mark)) |
|||
alert("Cycle encountered"); |
|||
out.mark = mark; |
|||
return overridden; |
|||
} |
|||
|
|||
Constraint.prototype.destroyConstraint = function () { |
|||
if (this.isSatisfied()) planner.incrementalRemove(this); |
|||
else this.removeFromGraph(); |
|||
} |
|||
|
|||
/** |
|||
* Normal constraints are not input constraints. An input constraint |
|||
* is one that depends on external state, such as the mouse, the |
|||
* keybord, a clock, or some arbitraty piece of imperative code. |
|||
*/ |
|||
Constraint.prototype.isInput = function () { |
|||
return false; |
|||
} |
|||
|
|||
/* --- * |
|||
* U n a r y C o n s t r a i n t |
|||
* --- */ |
|||
|
|||
/** |
|||
* Abstract superclass for constraints having a single possible output |
|||
* variable. |
|||
*/ |
|||
function UnaryConstraint(v, strength) { |
|||
UnaryConstraint.superConstructor.call(this, strength); |
|||
this.myOutput = v; |
|||
this.satisfied = false; |
|||
this.addConstraint(); |
|||
} |
|||
|
|||
UnaryConstraint.inherits(Constraint); |
|||
|
|||
/** |
|||
* Adds this constraint to the constraint graph |
|||
*/ |
|||
UnaryConstraint.prototype.addToGraph = function () { |
|||
this.myOutput.addConstraint(this); |
|||
this.satisfied = false; |
|||
} |
|||
|
|||
/** |
|||
* Decides if this constraint can be satisfied and records that |
|||
* decision. |
|||
*/ |
|||
UnaryConstraint.prototype.chooseMethod = function (mark) { |
|||
this.satisfied = (this.myOutput.mark != mark) |
|||
&& Strength.stronger(this.strength, this.myOutput.walkStrength); |
|||
} |
|||
|
|||
/** |
|||
* Returns true if this constraint is satisfied in the current solution. |
|||
*/ |
|||
UnaryConstraint.prototype.isSatisfied = function () { |
|||
return this.satisfied; |
|||
} |
|||
|
|||
UnaryConstraint.prototype.markInputs = function (mark) { |
|||
// has no inputs
|
|||
} |
|||
|
|||
/** |
|||
* Returns the current output variable. |
|||
*/ |
|||
UnaryConstraint.prototype.output = function () { |
|||
return this.myOutput; |
|||
} |
|||
|
|||
/** |
|||
* Calculate the walkabout strength, the stay flag, and, if it is |
|||
* 'stay', the value for the current output of this constraint. Assume |
|||
* this constraint is satisfied. |
|||
*/ |
|||
UnaryConstraint.prototype.recalculate = function () { |
|||
this.myOutput.walkStrength = this.strength; |
|||
this.myOutput.stay = !this.isInput(); |
|||
if (this.myOutput.stay) this.execute(); // Stay optimization
|
|||
} |
|||
|
|||
/** |
|||
* Records that this constraint is unsatisfied |
|||
*/ |
|||
UnaryConstraint.prototype.markUnsatisfied = function () { |
|||
this.satisfied = false; |
|||
} |
|||
|
|||
UnaryConstraint.prototype.inputsKnown = function () { |
|||
return true; |
|||
} |
|||
|
|||
UnaryConstraint.prototype.removeFromGraph = function () { |
|||
if (this.myOutput != null) this.myOutput.removeConstraint(this); |
|||
this.satisfied = false; |
|||
} |
|||
|
|||
/* --- * |
|||
* S t a y C o n s t r a i n t |
|||
* --- */ |
|||
|
|||
/** |
|||
* Variables that should, with some level of preference, stay the same. |
|||
* Planners may exploit the fact that instances, if satisfied, will not |
|||
* change their output during plan execution. This is called "stay |
|||
* optimization". |
|||
*/ |
|||
function StayConstraint(v, str) { |
|||
StayConstraint.superConstructor.call(this, v, str); |
|||
} |
|||
|
|||
StayConstraint.inherits(UnaryConstraint); |
|||
|
|||
StayConstraint.prototype.execute = function () { |
|||
// Stay constraints do nothing
|
|||
} |
|||
|
|||
/* --- * |
|||
* E d i t C o n s t r a i n t |
|||
* --- */ |
|||
|
|||
/** |
|||
* A unary input constraint used to mark a variable that the client |
|||
* wishes to change. |
|||
*/ |
|||
function EditConstraint(v, str) { |
|||
EditConstraint.superConstructor.call(this, v, str); |
|||
} |
|||
|
|||
EditConstraint.inherits(UnaryConstraint); |
|||
|
|||
/** |
|||
* Edits indicate that a variable is to be changed by imperative code. |
|||
*/ |
|||
EditConstraint.prototype.isInput = function () { |
|||
return true; |
|||
} |
|||
|
|||
EditConstraint.prototype.execute = function () { |
|||
// Edit constraints do nothing
|
|||
} |
|||
|
|||
/* --- * |
|||
* B i n a r y C o n s t r a i n t |
|||
* --- */ |
|||
|
|||
var Direction = new Object(); |
|||
Direction.NONE = 0; |
|||
Direction.FORWARD = 1; |
|||
Direction.BACKWARD = -1; |
|||
|
|||
/** |
|||
* Abstract superclass for constraints having two possible output |
|||
* variables. |
|||
*/ |
|||
function BinaryConstraint(var1, var2, strength) { |
|||
BinaryConstraint.superConstructor.call(this, strength); |
|||
this.v1 = var1; |
|||
this.v2 = var2; |
|||
this.direction = Direction.NONE; |
|||
this.addConstraint(); |
|||
} |
|||
|
|||
BinaryConstraint.inherits(Constraint); |
|||
|
|||
/** |
|||
* Decides if this constratint can be satisfied and which way it |
|||
* should flow based on the relative strength of the variables related, |
|||
* and record that decision. |
|||
*/ |
|||
BinaryConstraint.prototype.chooseMethod = function (mark) { |
|||
if (this.v1.mark == mark) { |
|||
this.direction = (this.v1.mark != mark && Strength.stronger(this.strength, this.v2.walkStrength)) |
|||
? Direction.FORWARD |
|||
: Direction.NONE; |
|||
} |
|||
if (this.v2.mark == mark) { |
|||
this.direction = (this.v1.mark != mark && Strength.stronger(this.strength, this.v1.walkStrength)) |
|||
? Direction.BACKWARD |
|||
: Direction.NONE; |
|||
} |
|||
if (Strength.weaker(this.v1.walkStrength, this.v2.walkStrength)) { |
|||
this.direction = Strength.stronger(this.strength, this.v1.walkStrength) |
|||
? Direction.BACKWARD |
|||
: Direction.NONE; |
|||
} else { |
|||
this.direction = Strength.stronger(this.strength, this.v2.walkStrength) |
|||
? Direction.FORWARD |
|||
: Direction.BACKWARD |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Add this constraint to the constraint graph |
|||
*/ |
|||
BinaryConstraint.prototype.addToGraph = function () { |
|||
this.v1.addConstraint(this); |
|||
this.v2.addConstraint(this); |
|||
this.direction = Direction.NONE; |
|||
} |
|||
|
|||
/** |
|||
* Answer true if this constraint is satisfied in the current solution. |
|||
*/ |
|||
BinaryConstraint.prototype.isSatisfied = function () { |
|||
return this.direction != Direction.NONE; |
|||
} |
|||
|
|||
/** |
|||
* Mark the input variable with the given mark. |
|||
*/ |
|||
BinaryConstraint.prototype.markInputs = function (mark) { |
|||
this.input().mark = mark; |
|||
} |
|||
|
|||
/** |
|||
* Returns the current input variable |
|||
*/ |
|||
BinaryConstraint.prototype.input = function () { |
|||
return (this.direction == Direction.FORWARD) ? this.v1 : this.v2; |
|||
} |
|||
|
|||
/** |
|||
* Returns the current output variable |
|||
*/ |
|||
BinaryConstraint.prototype.output = function () { |
|||
return (this.direction == Direction.FORWARD) ? this.v2 : this.v1; |
|||
} |
|||
|
|||
/** |
|||
* Calculate the walkabout strength, the stay flag, and, if it is |
|||
* 'stay', the value for the current output of this |
|||
* constraint. Assume this constraint is satisfied. |
|||
*/ |
|||
BinaryConstraint.prototype.recalculate = function () { |
|||
var ihn = this.input(), out = this.output(); |
|||
out.walkStrength = Strength.weakestOf(this.strength, ihn.walkStrength); |
|||
out.stay = ihn.stay; |
|||
if (out.stay) this.execute(); |
|||
} |
|||
|
|||
/** |
|||
* Record the fact that this constraint is unsatisfied. |
|||
*/ |
|||
BinaryConstraint.prototype.markUnsatisfied = function () { |
|||
this.direction = Direction.NONE; |
|||
} |
|||
|
|||
BinaryConstraint.prototype.inputsKnown = function (mark) { |
|||
var i = this.input(); |
|||
return i.mark == mark || i.stay || i.determinedBy == null; |
|||
} |
|||
|
|||
BinaryConstraint.prototype.removeFromGraph = function () { |
|||
if (this.v1 != null) this.v1.removeConstraint(this); |
|||
if (this.v2 != null) this.v2.removeConstraint(this); |
|||
this.direction = Direction.NONE; |
|||
} |
|||
|
|||
/* --- * |
|||
* S c a l e C o n s t r a i n t |
|||
* --- */ |
|||
|
|||
/** |
|||
* Relates two variables by the linear scaling relationship: "v2 = |
|||
* (v1 * scale) + offset". Either v1 or v2 may be changed to maintain |
|||
* this relationship but the scale factor and offset are considered |
|||
* read-only. |
|||
*/ |
|||
function ScaleConstraint(src, scale, offset, dest, strength) { |
|||
this.direction = Direction.NONE; |
|||
this.scale = scale; |
|||
this.offset = offset; |
|||
ScaleConstraint.superConstructor.call(this, src, dest, strength); |
|||
} |
|||
|
|||
ScaleConstraint.inherits(BinaryConstraint); |
|||
|
|||
/** |
|||
* Adds this constraint to the constraint graph. |
|||
*/ |
|||
ScaleConstraint.prototype.addToGraph = function () { |
|||
ScaleConstraint.superConstructor.prototype.addToGraph.call(this); |
|||
this.scale.addConstraint(this); |
|||
this.offset.addConstraint(this); |
|||
} |
|||
|
|||
ScaleConstraint.prototype.removeFromGraph = function () { |
|||
ScaleConstraint.superConstructor.prototype.removeFromGraph.call(this); |
|||
if (this.scale != null) this.scale.removeConstraint(this); |
|||
if (this.offset != null) this.offset.removeConstraint(this); |
|||
} |
|||
|
|||
ScaleConstraint.prototype.markInputs = function (mark) { |
|||
ScaleConstraint.superConstructor.prototype.markInputs.call(this, mark); |
|||
this.scale.mark = this.offset.mark = mark; |
|||
} |
|||
|
|||
/** |
|||
* Enforce this constraint. Assume that it is satisfied. |
|||
*/ |
|||
ScaleConstraint.prototype.execute = function () { |
|||
if (this.direction == Direction.FORWARD) { |
|||
this.v2.value = this.v1.value * this.scale.value + this.offset.value; |
|||
} else { |
|||
this.v1.value = (this.v2.value - this.offset.value) / this.scale.value; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Calculate the walkabout strength, the stay flag, and, if it is |
|||
* 'stay', the value for the current output of this constraint. Assume |
|||
* this constraint is satisfied. |
|||
*/ |
|||
ScaleConstraint.prototype.recalculate = function () { |
|||
var ihn = this.input(), out = this.output(); |
|||
out.walkStrength = Strength.weakestOf(this.strength, ihn.walkStrength); |
|||
out.stay = ihn.stay && this.scale.stay && this.offset.stay; |
|||
if (out.stay) this.execute(); |
|||
} |
|||
|
|||
/* --- * |
|||
* E q u a l i t y C o n s t r a i n t |
|||
* --- */ |
|||
|
|||
/** |
|||
* Constrains two variables to have the same value. |
|||
*/ |
|||
function EqualityConstraint(var1, var2, strength) { |
|||
EqualityConstraint.superConstructor.call(this, var1, var2, strength); |
|||
} |
|||
|
|||
EqualityConstraint.inherits(BinaryConstraint); |
|||
|
|||
/** |
|||
* Enforce this constraint. Assume that it is satisfied. |
|||
*/ |
|||
EqualityConstraint.prototype.execute = function () { |
|||
this.output().value = this.input().value; |
|||
} |
|||
|
|||
/* --- * |
|||
* V a r i a b l e |
|||
* --- */ |
|||
|
|||
/** |
|||
* A constrained variable. In addition to its value, it maintain the |
|||
* structure of the constraint graph, the current dataflow graph, and |
|||
* various parameters of interest to the DeltaBlue incremental |
|||
* constraint solver. |
|||
**/ |
|||
function Variable(name, initialValue) { |
|||
this.value = initialValue || 0; |
|||
this.constraints = new OrderedCollection(); |
|||
this.determinedBy = null; |
|||
this.mark = 0; |
|||
this.walkStrength = Strength.WEAKEST; |
|||
this.stay = true; |
|||
this.name = name; |
|||
} |
|||
|
|||
/** |
|||
* Add the given constraint to the set of all constraints that refer |
|||
* this variable. |
|||
*/ |
|||
Variable.prototype.addConstraint = function (c) { |
|||
this.constraints.add(c); |
|||
} |
|||
|
|||
/** |
|||
* Removes all traces of c from this variable. |
|||
*/ |
|||
Variable.prototype.removeConstraint = function (c) { |
|||
this.constraints.remove(c); |
|||
if (this.determinedBy == c) this.determinedBy = null; |
|||
} |
|||
|
|||
/* --- * |
|||
* P l a n n e r |
|||
* --- */ |
|||
|
|||
/** |
|||
* The DeltaBlue planner |
|||
*/ |
|||
function Planner() { |
|||
this.currentMark = 0; |
|||
} |
|||
|
|||
/** |
|||
* Attempt to satisfy the given constraint and, if successful, |
|||
* incrementally update the dataflow graph. Details: If satifying |
|||
* the constraint is successful, it may override a weaker constraint |
|||
* on its output. The algorithm attempts to resatisfy that |
|||
* constraint using some other method. This process is repeated |
|||
* until either a) it reaches a variable that was not previously |
|||
* determined by any constraint or b) it reaches a constraint that |
|||
* is too weak to be satisfied using any of its methods. The |
|||
* variables of constraints that have been processed are marked with |
|||
* a unique mark value so that we know where we've been. This allows |
|||
* the algorithm to avoid getting into an infinite loop even if the |
|||
* constraint graph has an inadvertent cycle. |
|||
*/ |
|||
Planner.prototype.incrementalAdd = function (c) { |
|||
var mark = this.newMark(); |
|||
var overridden = c.satisfy(mark); |
|||
while (overridden != null) |
|||
overridden = overridden.satisfy(mark); |
|||
} |
|||
|
|||
/** |
|||
* Entry point for retracting a constraint. Remove the given |
|||
* constraint and incrementally update the dataflow graph. |
|||
* Details: Retracting the given constraint may allow some currently |
|||
* unsatisfiable downstream constraint to be satisfied. We therefore collect |
|||
* a list of unsatisfied downstream constraints and attempt to |
|||
* satisfy each one in turn. This list is traversed by constraint |
|||
* strength, strongest first, as a heuristic for avoiding |
|||
* unnecessarily adding and then overriding weak constraints. |
|||
* Assume: c is satisfied. |
|||
*/ |
|||
Planner.prototype.incrementalRemove = function (c) { |
|||
var out = c.output(); |
|||
c.markUnsatisfied(); |
|||
c.removeFromGraph(); |
|||
var unsatisfied = this.removePropagateFrom(out); |
|||
var strength = Strength.REQUIRED; |
|||
do { |
|||
for (var i = 0; i < unsatisfied.size(); i++) { |
|||
var u = unsatisfied.at(i); |
|||
if (u.strength == strength) |
|||
this.incrementalAdd(u); |
|||
} |
|||
strength = strength.nextWeaker(); |
|||
} while (strength != Strength.WEAKEST); |
|||
} |
|||
|
|||
/** |
|||
* Select a previously unused mark value. |
|||
*/ |
|||
Planner.prototype.newMark = function () { |
|||
return ++this.currentMark; |
|||
} |
|||
|
|||
/** |
|||
* Extract a plan for resatisfaction starting from the given source |
|||
* constraints, usually a set of input constraints. This method |
|||
* assumes that stay optimization is desired; the plan will contain |
|||
* only constraints whose output variables are not stay. Constraints |
|||
* that do no computation, such as stay and edit constraints, are |
|||
* not included in the plan. |
|||
* Details: The outputs of a constraint are marked when it is added |
|||
* to the plan under construction. A constraint may be appended to |
|||
* the plan when all its input variables are known. A variable is |
|||
* known if either a) the variable is marked (indicating that has |
|||
* been computed by a constraint appearing earlier in the plan), b) |
|||
* the variable is 'stay' (i.e. it is a constant at plan execution |
|||
* time), or c) the variable is not determined by any |
|||
* constraint. The last provision is for past states of history |
|||
* variables, which are not stay but which are also not computed by |
|||
* any constraint. |
|||
* Assume: sources are all satisfied. |
|||
*/ |
|||
Planner.prototype.makePlan = function (sources) { |
|||
var mark = this.newMark(); |
|||
var plan = new Plan(); |
|||
var todo = sources; |
|||
while (todo.size() > 0) { |
|||
var c = todo.removeFirst(); |
|||
if (c.output().mark != mark && c.inputsKnown(mark)) { |
|||
plan.addConstraint(c); |
|||
c.output().mark = mark; |
|||
this.addConstraintsConsumingTo(c.output(), todo); |
|||
} |
|||
} |
|||
return plan; |
|||
} |
|||
|
|||
/** |
|||
* Extract a plan for resatisfying starting from the output of the |
|||
* given constraints, usually a set of input constraints. |
|||
*/ |
|||
Planner.prototype.extractPlanFromConstraints = function (constraints) { |
|||
var sources = new OrderedCollection(); |
|||
for (var i = 0; i < constraints.size(); i++) { |
|||
var c = constraints.at(i); |
|||
if (c.isInput() && c.isSatisfied()) |
|||
// not in plan already and eligible for inclusion
|
|||
sources.add(c); |
|||
} |
|||
return this.makePlan(sources); |
|||
} |
|||
|
|||
/** |
|||
* Recompute the walkabout strengths and stay flags of all variables |
|||
* downstream of the given constraint and recompute the actual |
|||
* values of all variables whose stay flag is true. If a cycle is |
|||
* detected, remove the given constraint and answer |
|||
* false. Otherwise, answer true. |
|||
* Details: Cycles are detected when a marked variable is |
|||
* encountered downstream of the given constraint. The sender is |
|||
* assumed to have marked the inputs of the given constraint with |
|||
* the given mark. Thus, encountering a marked node downstream of |
|||
* the output constraint means that there is a path from the |
|||
* constraint's output to one of its inputs. |
|||
*/ |
|||
Planner.prototype.addPropagate = function (c, mark) { |
|||
var todo = new OrderedCollection(); |
|||
todo.add(c); |
|||
while (todo.size() > 0) { |
|||
var d = todo.removeFirst(); |
|||
if (d.output().mark == mark) { |
|||
this.incrementalRemove(c); |
|||
return false; |
|||
} |
|||
d.recalculate(); |
|||
this.addConstraintsConsumingTo(d.output(), todo); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Update the walkabout strengths and stay flags of all variables |
|||
* downstream of the given constraint. Answer a collection of |
|||
* unsatisfied constraints sorted in order of decreasing strength. |
|||
*/ |
|||
Planner.prototype.removePropagateFrom = function (out) { |
|||
out.determinedBy = null; |
|||
out.walkStrength = Strength.WEAKEST; |
|||
out.stay = true; |
|||
var unsatisfied = new OrderedCollection(); |
|||
var todo = new OrderedCollection(); |
|||
todo.add(out); |
|||
while (todo.size() > 0) { |
|||
var v = todo.removeFirst(); |
|||
for (var i = 0; i < v.constraints.size(); i++) { |
|||
var c = v.constraints.at(i); |
|||
if (!c.isSatisfied()) |
|||
unsatisfied.add(c); |
|||
} |
|||
var determining = v.determinedBy; |
|||
for (var i = 0; i < v.constraints.size(); i++) { |
|||
var next = v.constraints.at(i); |
|||
if (next != determining && next.isSatisfied()) { |
|||
next.recalculate(); |
|||
todo.add(next.output()); |
|||
} |
|||
} |
|||
} |
|||
return unsatisfied; |
|||
} |
|||
|
|||
Planner.prototype.addConstraintsConsumingTo = function (v, coll) { |
|||
var determining = v.determinedBy; |
|||
var cc = v.constraints; |
|||
for (var i = 0; i < cc.size(); i++) { |
|||
var c = cc.at(i); |
|||
if (c != determining && c.isSatisfied()) |
|||
coll.add(c); |
|||
} |
|||
} |
|||
|
|||
/* --- * |
|||
* P l a n |
|||
* --- */ |
|||
|
|||
/** |
|||
* A Plan is an ordered list of constraints to be executed in sequence |
|||
* to resatisfy all currently satisfiable constraints in the face of |
|||
* one or more changing inputs. |
|||
*/ |
|||
function Plan() { |
|||
this.v = new OrderedCollection(); |
|||
} |
|||
|
|||
Plan.prototype.addConstraint = function (c) { |
|||
this.v.add(c); |
|||
} |
|||
|
|||
Plan.prototype.size = function () { |
|||
return this.v.size(); |
|||
} |
|||
|
|||
Plan.prototype.constraintAt = function (index) { |
|||
return this.v.at(index); |
|||
} |
|||
|
|||
Plan.prototype.execute = function () { |
|||
for (var i = 0; i < this.size(); i++) { |
|||
var c = this.constraintAt(i); |
|||
c.execute(); |
|||
} |
|||
} |
|||
|
|||
/* --- * |
|||
* M a i n |
|||
* --- */ |
|||
|
|||
/** |
|||
* This is the standard DeltaBlue benchmark. A long chain of equality |
|||
* constraints is constructed with a stay constraint on one end. An |
|||
* edit constraint is then added to the opposite end and the time is |
|||
* measured for adding and removing this constraint, and extracting |
|||
* and executing a constraint satisfaction plan. There are two cases. |
|||
* In case 1, the added constraint is stronger than the stay |
|||
* constraint and values must propagate down the entire length of the |
|||
* chain. In case 2, the added constraint is weaker than the stay |
|||
* constraint so it cannot be accomodated. The cost in this case is, |
|||
* of course, very low. Typical situations lie somewhere between these |
|||
* two extremes. |
|||
*/ |
|||
function chainTest(n) { |
|||
planner = new Planner(); |
|||
var prev = null, first = null, last = null; |
|||
|
|||
// Build chain of n equality constraints
|
|||
for (var i = 0; i <= n; i++) { |
|||
var name = "v" + i; |
|||
var v = new Variable(name); |
|||
if (prev != null) |
|||
new EqualityConstraint(prev, v, Strength.REQUIRED); |
|||
if (i == 0) first = v; |
|||
if (i == n) last = v; |
|||
prev = v; |
|||
} |
|||
|
|||
new StayConstraint(last, Strength.STRONG_DEFAULT); |
|||
var edit = new EditConstraint(first, Strength.PREFERRED); |
|||
var edits = new OrderedCollection(); |
|||
edits.add(edit); |
|||
var plan = planner.extractPlanFromConstraints(edits); |
|||
for (var i = 0; i < 100; i++) { |
|||
first.value = i; |
|||
plan.execute(); |
|||
if (last.value != i) |
|||
alert("Chain test failed."); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* This test constructs a two sets of variables related to each |
|||
* other by a simple linear transformation (scale and offset). The |
|||
* time is measured to change a variable on either side of the |
|||
* mapping and to change the scale and offset factors. |
|||
*/ |
|||
function projectionTest(n) { |
|||
planner = new Planner(); |
|||
var scale = new Variable("scale", 10); |
|||
var offset = new Variable("offset", 1000); |
|||
var src = null, dst = null; |
|||
|
|||
var dests = new OrderedCollection(); |
|||
for (var i = 0; i < n; i++) { |
|||
src = new Variable("src" + i, i); |
|||
dst = new Variable("dst" + i, i); |
|||
dests.add(dst); |
|||
new StayConstraint(src, Strength.NORMAL); |
|||
new ScaleConstraint(src, scale, offset, dst, Strength.REQUIRED); |
|||
} |
|||
|
|||
change(src, 17); |
|||
if (dst.value != 1170) alert("Projection 1 failed"); |
|||
change(dst, 1050); |
|||
if (src.value != 5) alert("Projection 2 failed"); |
|||
change(scale, 5); |
|||
for (var i = 0; i < n - 1; i++) { |
|||
if (dests.at(i).value != i * 5 + 1000) |
|||
alert("Projection 3 failed"); |
|||
} |
|||
change(offset, 2000); |
|||
for (var i = 0; i < n - 1; i++) { |
|||
if (dests.at(i).value != i * 5 + 2000) |
|||
alert("Projection 4 failed"); |
|||
} |
|||
} |
|||
|
|||
function change(v, newValue) { |
|||
var edit = new EditConstraint(v, Strength.PREFERRED); |
|||
var edits = new OrderedCollection(); |
|||
edits.add(edit); |
|||
var plan = planner.extractPlanFromConstraints(edits); |
|||
for (var i = 0; i < 10; i++) { |
|||
v.value = newValue; |
|||
plan.execute(); |
|||
} |
|||
edit.destroyConstraint(); |
|||
} |
|||
|
|||
// Global variable holding the current planner.
|
|||
var planner = null; |
|||
|
|||
function deltaBlue() { |
|||
chainTest(100); |
|||
projectionTest(100); |
|||
} |
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,62 @@ |
|||
<html> |
|||
<head> |
|||
<title>V8 Benchmark Suite Revisions</title> |
|||
<link type="text/css" rel="stylesheet" href="style.css"></link> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
<div class="title"><h1>V8 Benchmark Suite Revisions</h1></div> |
|||
<table> |
|||
<tr> |
|||
<td class="contents"> |
|||
|
|||
<p> |
|||
|
|||
The V8 benchmark suite is changed from time to time as we fix bugs or |
|||
expand the scope of the benchmarks. Here is a list of revisions, with |
|||
a description of the changes made. Note that benchmark results are |
|||
not comparable unless both results are run with the same revision of |
|||
the benchmark suite. |
|||
|
|||
</p> |
|||
|
|||
<div class="subtitle"><h3>Version 3 (<a href="http://v8.googlecode.com/svn/data/benchmarks/v3/run.html">link</a>)</h3></div> |
|||
|
|||
<p>Version 3 adds a new benchmark, <i>RegExp</i>. The RegExp |
|||
benchmark is generated by loading 50 of the most popular pages on the |
|||
web and logging all regexp operations performed. Each operation is |
|||
given a weight that is calculated from an estimate of the popularity |
|||
of the pages where it occurs and the number of times it is executed |
|||
while loading each page. Finally the literal letters in the data are |
|||
encoded using ROT13 in a way that does not affect how the regexps |
|||
match their input. |
|||
</p> |
|||
|
|||
<div class="subtitle"><h3>Version 2 (<a href="http://v8.googlecode.com/svn/data/benchmarks/v2/run.html">link</a>)</h3></div> |
|||
|
|||
<p>For version 2 the crypto benchmark was fixed. Previously, the |
|||
decryption stage was given plaintext as input, which resulted in an |
|||
error. Now, the decryption stage is given the output of the |
|||
encryption stage as input. The result is checked against the original |
|||
plaintext. For this to give the correct results the crypto objects |
|||
are reset for each iteration of the benchmark. In addition, the size |
|||
of the plain text has been increased a little and the use of |
|||
Math.random() and new Date() to build an RNG pool has been |
|||
removed. </p> |
|||
|
|||
<p>Other benchmarks were fixed to do elementary verification of the |
|||
results of their calculations. This is to avoid accidentally |
|||
obtaining scores that are the result of an incorrect JavaScript engine |
|||
optimization.</p> |
|||
|
|||
<div class="subtitle"><h3>Version 1 (<a href="http://v8.googlecode.com/svn/data/benchmarks/v1/run.html">link</a>)</h3></div> |
|||
|
|||
<p>Initial release.</p> |
|||
|
|||
</td><td style="text-align: center"> |
|||
</td></tr></table> |
|||
|
|||
</div> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,539 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
|
|||
// This is a JavaScript implementation of the Richards
|
|||
// benchmark from:
|
|||
//
|
|||
// http://www.cl.cam.ac.uk/~mr10/Bench.html
|
|||
//
|
|||
// The benchmark was originally implemented in BCPL by
|
|||
// Martin Richards.
|
|||
|
|||
|
|||
var Richards = new BenchmarkSuite('Richards', 34886, [ |
|||
new Benchmark("Richards", runRichards) |
|||
]); |
|||
|
|||
|
|||
/** |
|||
* The Richards benchmark simulates the task dispatcher of an |
|||
* operating system. |
|||
**/ |
|||
function runRichards() { |
|||
var scheduler = new Scheduler(); |
|||
scheduler.addIdleTask(ID_IDLE, 0, null, COUNT); |
|||
|
|||
var queue = new Packet(null, ID_WORKER, KIND_WORK); |
|||
queue = new Packet(queue, ID_WORKER, KIND_WORK); |
|||
scheduler.addWorkerTask(ID_WORKER, 1000, queue); |
|||
|
|||
queue = new Packet(null, ID_DEVICE_A, KIND_DEVICE); |
|||
queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE); |
|||
queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE); |
|||
scheduler.addHandlerTask(ID_HANDLER_A, 2000, queue); |
|||
|
|||
queue = new Packet(null, ID_DEVICE_B, KIND_DEVICE); |
|||
queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE); |
|||
queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE); |
|||
scheduler.addHandlerTask(ID_HANDLER_B, 3000, queue); |
|||
|
|||
scheduler.addDeviceTask(ID_DEVICE_A, 4000, null); |
|||
|
|||
scheduler.addDeviceTask(ID_DEVICE_B, 5000, null); |
|||
|
|||
scheduler.schedule(); |
|||
|
|||
if (scheduler.queueCount != EXPECTED_QUEUE_COUNT || |
|||
scheduler.holdCount != EXPECTED_HOLD_COUNT) { |
|||
var msg = |
|||
"Error during execution: queueCount = " + scheduler.queueCount + |
|||
", holdCount = " + scheduler.holdCount + "."; |
|||
throw new Error(msg); |
|||
} |
|||
} |
|||
|
|||
var COUNT = 1000; |
|||
|
|||
/** |
|||
* These two constants specify how many times a packet is queued and |
|||
* how many times a task is put on hold in a correct run of richards. |
|||
* They don't have any meaning a such but are characteristic of a |
|||
* correct run so if the actual queue or hold count is different from |
|||
* the expected there must be a bug in the implementation. |
|||
**/ |
|||
var EXPECTED_QUEUE_COUNT = 2322; |
|||
var EXPECTED_HOLD_COUNT = 928; |
|||
|
|||
|
|||
/** |
|||
* A scheduler can be used to schedule a set of tasks based on their relative |
|||
* priorities. Scheduling is done by maintaining a list of task control blocks |
|||
* which holds tasks and the data queue they are processing. |
|||
* @constructor |
|||
*/ |
|||
function Scheduler() { |
|||
this.queueCount = 0; |
|||
this.holdCount = 0; |
|||
this.blocks = new Array(NUMBER_OF_IDS); |
|||
this.list = null; |
|||
this.currentTcb = null; |
|||
this.currentId = null; |
|||
} |
|||
|
|||
var ID_IDLE = 0; |
|||
var ID_WORKER = 1; |
|||
var ID_HANDLER_A = 2; |
|||
var ID_HANDLER_B = 3; |
|||
var ID_DEVICE_A = 4; |
|||
var ID_DEVICE_B = 5; |
|||
var NUMBER_OF_IDS = 6; |
|||
|
|||
var KIND_DEVICE = 0; |
|||
var KIND_WORK = 1; |
|||
|
|||
/** |
|||
* Add an idle task to this scheduler. |
|||
* @param {int} id the identity of the task |
|||
* @param {int} priority the task's priority |
|||
* @param {Packet} queue the queue of work to be processed by the task |
|||
* @param {int} count the number of times to schedule the task |
|||
*/ |
|||
Scheduler.prototype.addIdleTask = function (id, priority, queue, count) { |
|||
this.addRunningTask(id, priority, queue, new IdleTask(this, 1, count)); |
|||
}; |
|||
|
|||
/** |
|||
* Add a work task to this scheduler. |
|||
* @param {int} id the identity of the task |
|||
* @param {int} priority the task's priority |
|||
* @param {Packet} queue the queue of work to be processed by the task |
|||
*/ |
|||
Scheduler.prototype.addWorkerTask = function (id, priority, queue) { |
|||
this.addTask(id, priority, queue, new WorkerTask(this, ID_HANDLER_A, 0)); |
|||
}; |
|||
|
|||
/** |
|||
* Add a handler task to this scheduler. |
|||
* @param {int} id the identity of the task |
|||
* @param {int} priority the task's priority |
|||
* @param {Packet} queue the queue of work to be processed by the task |
|||
*/ |
|||
Scheduler.prototype.addHandlerTask = function (id, priority, queue) { |
|||
this.addTask(id, priority, queue, new HandlerTask(this)); |
|||
}; |
|||
|
|||
/** |
|||
* Add a handler task to this scheduler. |
|||
* @param {int} id the identity of the task |
|||
* @param {int} priority the task's priority |
|||
* @param {Packet} queue the queue of work to be processed by the task |
|||
*/ |
|||
Scheduler.prototype.addDeviceTask = function (id, priority, queue) { |
|||
this.addTask(id, priority, queue, new DeviceTask(this)) |
|||
}; |
|||
|
|||
/** |
|||
* Add the specified task and mark it as running. |
|||
* @param {int} id the identity of the task |
|||
* @param {int} priority the task's priority |
|||
* @param {Packet} queue the queue of work to be processed by the task |
|||
* @param {Task} task the task to add |
|||
*/ |
|||
Scheduler.prototype.addRunningTask = function (id, priority, queue, task) { |
|||
this.addTask(id, priority, queue, task); |
|||
this.currentTcb.setRunning(); |
|||
}; |
|||
|
|||
/** |
|||
* Add the specified task to this scheduler. |
|||
* @param {int} id the identity of the task |
|||
* @param {int} priority the task's priority |
|||
* @param {Packet} queue the queue of work to be processed by the task |
|||
* @param {Task} task the task to add |
|||
*/ |
|||
Scheduler.prototype.addTask = function (id, priority, queue, task) { |
|||
this.currentTcb = new TaskControlBlock(this.list, id, priority, queue, task); |
|||
this.list = this.currentTcb; |
|||
this.blocks[id] = this.currentTcb; |
|||
}; |
|||
|
|||
/** |
|||
* Execute the tasks managed by this scheduler. |
|||
*/ |
|||
Scheduler.prototype.schedule = function () { |
|||
this.currentTcb = this.list; |
|||
while (this.currentTcb != null) { |
|||
if (this.currentTcb.isHeldOrSuspended()) { |
|||
this.currentTcb = this.currentTcb.link; |
|||
} else { |
|||
this.currentId = this.currentTcb.id; |
|||
this.currentTcb = this.currentTcb.run(); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* Release a task that is currently blocked and return the next block to run. |
|||
* @param {int} id the id of the task to suspend |
|||
*/ |
|||
Scheduler.prototype.release = function (id) { |
|||
var tcb = this.blocks[id]; |
|||
if (tcb == null) return tcb; |
|||
tcb.markAsNotHeld(); |
|||
if (tcb.priority > this.currentTcb.priority) { |
|||
return tcb; |
|||
} else { |
|||
return this.currentTcb; |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* Block the currently executing task and return the next task control block |
|||
* to run. The blocked task will not be made runnable until it is explicitly |
|||
* released, even if new work is added to it. |
|||
*/ |
|||
Scheduler.prototype.holdCurrent = function () { |
|||
this.holdCount++; |
|||
this.currentTcb.markAsHeld(); |
|||
return this.currentTcb.link; |
|||
}; |
|||
|
|||
/** |
|||
* Suspend the currently executing task and return the next task control block |
|||
* to run. If new work is added to the suspended task it will be made runnable. |
|||
*/ |
|||
Scheduler.prototype.suspendCurrent = function () { |
|||
this.currentTcb.markAsSuspended(); |
|||
return this.currentTcb; |
|||
}; |
|||
|
|||
/** |
|||
* Add the specified packet to the end of the worklist used by the task |
|||
* associated with the packet and make the task runnable if it is currently |
|||
* suspended. |
|||
* @param {Packet} packet the packet to add |
|||
*/ |
|||
Scheduler.prototype.queue = function (packet) { |
|||
var t = this.blocks[packet.id]; |
|||
if (t == null) return t; |
|||
this.queueCount++; |
|||
packet.link = null; |
|||
packet.id = this.currentId; |
|||
return t.checkPriorityAdd(this.currentTcb, packet); |
|||
}; |
|||
|
|||
/** |
|||
* A task control block manages a task and the queue of work packages associated |
|||
* with it. |
|||
* @param {TaskControlBlock} link the preceding block in the linked block list |
|||
* @param {int} id the id of this block |
|||
* @param {int} priority the priority of this block |
|||
* @param {Packet} queue the queue of packages to be processed by the task |
|||
* @param {Task} task the task |
|||
* @constructor |
|||
*/ |
|||
function TaskControlBlock(link, id, priority, queue, task) { |
|||
this.link = link; |
|||
this.id = id; |
|||
this.priority = priority; |
|||
this.queue = queue; |
|||
this.task = task; |
|||
if (queue == null) { |
|||
this.state = STATE_SUSPENDED; |
|||
} else { |
|||
this.state = STATE_SUSPENDED_RUNNABLE; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* The task is running and is currently scheduled. |
|||
*/ |
|||
var STATE_RUNNING = 0; |
|||
|
|||
/** |
|||
* The task has packets left to process. |
|||
*/ |
|||
var STATE_RUNNABLE = 1; |
|||
|
|||
/** |
|||
* The task is not currently running. The task is not blocked as such and may |
|||
* be started by the scheduler. |
|||
*/ |
|||
var STATE_SUSPENDED = 2; |
|||
|
|||
/** |
|||
* The task is blocked and cannot be run until it is explicitly released. |
|||
*/ |
|||
var STATE_HELD = 4; |
|||
|
|||
var STATE_SUSPENDED_RUNNABLE = STATE_SUSPENDED | STATE_RUNNABLE; |
|||
var STATE_NOT_HELD = ~STATE_HELD; |
|||
|
|||
TaskControlBlock.prototype.setRunning = function () { |
|||
this.state = STATE_RUNNING; |
|||
}; |
|||
|
|||
TaskControlBlock.prototype.markAsNotHeld = function () { |
|||
this.state = this.state & STATE_NOT_HELD; |
|||
}; |
|||
|
|||
TaskControlBlock.prototype.markAsHeld = function () { |
|||
this.state = this.state | STATE_HELD; |
|||
}; |
|||
|
|||
TaskControlBlock.prototype.isHeldOrSuspended = function () { |
|||
return (this.state & STATE_HELD) != 0 || (this.state == STATE_SUSPENDED); |
|||
}; |
|||
|
|||
TaskControlBlock.prototype.markAsSuspended = function () { |
|||
this.state = this.state | STATE_SUSPENDED; |
|||
}; |
|||
|
|||
TaskControlBlock.prototype.markAsRunnable = function () { |
|||
this.state = this.state | STATE_RUNNABLE; |
|||
}; |
|||
|
|||
/** |
|||
* Runs this task, if it is ready to be run, and returns the next task to run. |
|||
*/ |
|||
TaskControlBlock.prototype.run = function () { |
|||
var packet; |
|||
if (this.state == STATE_SUSPENDED_RUNNABLE) { |
|||
packet = this.queue; |
|||
this.queue = packet.link; |
|||
if (this.queue == null) { |
|||
this.state = STATE_RUNNING; |
|||
} else { |
|||
this.state = STATE_RUNNABLE; |
|||
} |
|||
} else { |
|||
packet = null; |
|||
} |
|||
return this.task.run(packet); |
|||
}; |
|||
|
|||
/** |
|||
* Adds a packet to the worklist of this block's task, marks this as runnable if |
|||
* necessary, and returns the next runnable object to run (the one |
|||
* with the highest priority). |
|||
*/ |
|||
TaskControlBlock.prototype.checkPriorityAdd = function (task, packet) { |
|||
if (this.queue == null) { |
|||
this.queue = packet; |
|||
this.markAsRunnable(); |
|||
if (this.priority > task.priority) return this; |
|||
} else { |
|||
this.queue = packet.addTo(this.queue); |
|||
} |
|||
return task; |
|||
}; |
|||
|
|||
TaskControlBlock.prototype.toString = function () { |
|||
return "tcb { " + this.task + "@" + this.state + " }"; |
|||
}; |
|||
|
|||
/** |
|||
* An idle task doesn't do any work itself but cycles control between the two |
|||
* device tasks. |
|||
* @param {Scheduler} scheduler the scheduler that manages this task |
|||
* @param {int} v1 a seed value that controls how the device tasks are scheduled |
|||
* @param {int} count the number of times this task should be scheduled |
|||
* @constructor |
|||
*/ |
|||
function IdleTask(scheduler, v1, count) { |
|||
this.scheduler = scheduler; |
|||
this.v1 = v1; |
|||
this.count = count; |
|||
} |
|||
|
|||
IdleTask.prototype.run = function (packet) { |
|||
this.count--; |
|||
if (this.count == 0) return this.scheduler.holdCurrent(); |
|||
if ((this.v1 & 1) == 0) { |
|||
this.v1 = this.v1 >> 1; |
|||
return this.scheduler.release(ID_DEVICE_A); |
|||
} else { |
|||
this.v1 = (this.v1 >> 1) ^ 0xD008; |
|||
return this.scheduler.release(ID_DEVICE_B); |
|||
} |
|||
}; |
|||
|
|||
IdleTask.prototype.toString = function () { |
|||
return "IdleTask" |
|||
}; |
|||
|
|||
/** |
|||
* A task that suspends itself after each time it has been run to simulate |
|||
* waiting for data from an external device. |
|||
* @param {Scheduler} scheduler the scheduler that manages this task |
|||
* @constructor |
|||
*/ |
|||
function DeviceTask(scheduler) { |
|||
this.scheduler = scheduler; |
|||
this.v1 = null; |
|||
} |
|||
|
|||
DeviceTask.prototype.run = function (packet) { |
|||
if (packet == null) { |
|||
if (this.v1 == null) return this.scheduler.suspendCurrent(); |
|||
var v = this.v1; |
|||
this.v1 = null; |
|||
return this.scheduler.queue(v); |
|||
} else { |
|||
this.v1 = packet; |
|||
return this.scheduler.holdCurrent(); |
|||
} |
|||
}; |
|||
|
|||
DeviceTask.prototype.toString = function () { |
|||
return "DeviceTask"; |
|||
}; |
|||
|
|||
/** |
|||
* A task that manipulates work packets. |
|||
* @param {Scheduler} scheduler the scheduler that manages this task |
|||
* @param {int} v1 a seed used to specify how work packets are manipulated |
|||
* @param {int} v2 another seed used to specify how work packets are manipulated |
|||
* @constructor |
|||
*/ |
|||
function WorkerTask(scheduler, v1, v2) { |
|||
this.scheduler = scheduler; |
|||
this.v1 = v1; |
|||
this.v2 = v2; |
|||
} |
|||
|
|||
WorkerTask.prototype.run = function (packet) { |
|||
if (packet == null) { |
|||
return this.scheduler.suspendCurrent(); |
|||
} else { |
|||
if (this.v1 == ID_HANDLER_A) { |
|||
this.v1 = ID_HANDLER_B; |
|||
} else { |
|||
this.v1 = ID_HANDLER_A; |
|||
} |
|||
packet.id = this.v1; |
|||
packet.a1 = 0; |
|||
for (var i = 0; i < DATA_SIZE; i++) { |
|||
this.v2++; |
|||
if (this.v2 > 26) this.v2 = 1; |
|||
packet.a2[i] = this.v2; |
|||
} |
|||
return this.scheduler.queue(packet); |
|||
} |
|||
}; |
|||
|
|||
WorkerTask.prototype.toString = function () { |
|||
return "WorkerTask"; |
|||
}; |
|||
|
|||
/** |
|||
* A task that manipulates work packets and then suspends itself. |
|||
* @param {Scheduler} scheduler the scheduler that manages this task |
|||
* @constructor |
|||
*/ |
|||
function HandlerTask(scheduler) { |
|||
this.scheduler = scheduler; |
|||
this.v1 = null; |
|||
this.v2 = null; |
|||
} |
|||
|
|||
HandlerTask.prototype.run = function (packet) { |
|||
if (packet != null) { |
|||
if (packet.kind == KIND_WORK) { |
|||
this.v1 = packet.addTo(this.v1); |
|||
} else { |
|||
this.v2 = packet.addTo(this.v2); |
|||
} |
|||
} |
|||
if (this.v1 != null) { |
|||
var count = this.v1.a1; |
|||
var v; |
|||
if (count < DATA_SIZE) { |
|||
if (this.v2 != null) { |
|||
v = this.v2; |
|||
this.v2 = this.v2.link; |
|||
v.a1 = this.v1.a2[count]; |
|||
this.v1.a1 = count + 1; |
|||
return this.scheduler.queue(v); |
|||
} |
|||
} else { |
|||
v = this.v1; |
|||
this.v1 = this.v1.link; |
|||
return this.scheduler.queue(v); |
|||
} |
|||
} |
|||
return this.scheduler.suspendCurrent(); |
|||
}; |
|||
|
|||
HandlerTask.prototype.toString = function () { |
|||
return "HandlerTask"; |
|||
}; |
|||
|
|||
/* --- * |
|||
* P a c k e t |
|||
* --- */ |
|||
|
|||
var DATA_SIZE = 4; |
|||
|
|||
/** |
|||
* A simple package of data that is manipulated by the tasks. The exact layout |
|||
* of the payload data carried by a packet is not importaint, and neither is the |
|||
* nature of the work performed on packets by the tasks. |
|||
* |
|||
* Besides carrying data, packets form linked lists and are hence used both as |
|||
* data and worklists. |
|||
* @param {Packet} link the tail of the linked list of packets |
|||
* @param {int} id an ID for this packet |
|||
* @param {int} kind the type of this packet |
|||
* @constructor |
|||
*/ |
|||
function Packet(link, id, kind) { |
|||
this.link = link; |
|||
this.id = id; |
|||
this.kind = kind; |
|||
this.a1 = 0; |
|||
this.a2 = new Array(DATA_SIZE); |
|||
} |
|||
|
|||
/** |
|||
* Add this packet to the end of a worklist, and return the worklist. |
|||
* @param {Packet} queue the worklist to add this packet to |
|||
*/ |
|||
Packet.prototype.addTo = function (queue) { |
|||
this.link = null; |
|||
if (queue == null) return this; |
|||
var peek, next = queue; |
|||
while ((peek = next.link) != null) |
|||
next = peek; |
|||
next.link = this; |
|||
return queue; |
|||
}; |
|||
|
|||
Packet.prototype.toString = function () { |
|||
return "Packet"; |
|||
}; |
@ -0,0 +1,102 @@ |
|||
<html> |
|||
<head> |
|||
<title>V8 Benchmark Suite</title> |
|||
<script type="text/javascript" src="base.js"></script> |
|||
<script type="text/javascript" src="richards.js"></script> |
|||
<script type="text/javascript" src="deltablue.js"></script> |
|||
<script type="text/javascript" src="crypto.js"></script> |
|||
<script type="text/javascript" src="raytrace.js"></script> |
|||
<script type="text/javascript" src="earley-boyer.js"></script> |
|||
<script type="text/javascript" src="regexp.js"></script> |
|||
<link type="text/css" rel="stylesheet" href="style.css"></link> |
|||
<script type="text/javascript"> |
|||
var completed = 0; |
|||
var benchmarks = BenchmarkSuite.CountBenchmarks(); |
|||
var success = true; |
|||
|
|||
function ShowProgress(name) { |
|||
var status = document.getElementById("status"); |
|||
var percentage = ((++completed) / benchmarks) * 100; |
|||
status.innerHTML = "Running: " + Math.round(percentage) + "% completed."; |
|||
} |
|||
|
|||
|
|||
function AddResult(name, result) { |
|||
var text = name + ': ' + result; |
|||
var results = document.getElementById("results"); |
|||
results.innerHTML += (text + "<br/>"); |
|||
} |
|||
|
|||
|
|||
function AddError(name, error) { |
|||
AddResult(name, '<b>error</b>'); |
|||
success = false; |
|||
} |
|||
|
|||
|
|||
function AddScore(score) { |
|||
var status = document.getElementById("status"); |
|||
if (success) { |
|||
status.innerHTML = "Score: " + score; |
|||
} |
|||
} |
|||
|
|||
|
|||
function Run() { |
|||
BenchmarkSuite.RunSuites({ NotifyStep: ShowProgress, |
|||
NotifyError: AddError, |
|||
NotifyResult: AddResult, |
|||
NotifyScore: AddScore }); |
|||
} |
|||
|
|||
function Load() { |
|||
var version = BenchmarkSuite.version; |
|||
document.getElementById("version").innerHTML = version; |
|||
window.setTimeout(Run, 200); |
|||
} |
|||
</script> |
|||
</head> |
|||
<body onLoad="Load()"> |
|||
<div> |
|||
<div class="title"><h1>V8 Benchmark Suite - version <span id="version">?</span></h1></div> |
|||
<table> |
|||
<tr> |
|||
<td class="contents"> |
|||
This page contains a suite of pure JavaScript benchmarks that we have |
|||
used to tune V8. The final score is computed as the geometric mean of |
|||
the individual results to make it independent of the running times of |
|||
the individual benchmarks and of a reference system (score |
|||
100). Scores are not comparable across benchmark suite versions and |
|||
higher scores means better performance: <em>Bigger is better!</em> |
|||
|
|||
<ul> |
|||
<li><b>Richards</b><br/>OS kernel simulation benchmark, originally written in BCPL by Martin Richards (<i>539 lines</i>).</li> |
|||
<li><b>DeltaBlue</b><br/>One-way constraint solver, originally written in Smalltalk by John Maloney and Mario Wolczko (<i>880 lines</i>).</li> |
|||
<li><b>Crypto</b><br/>Encryption and decryption benchmark based on code by Tom Wu (<i>1689 lines</i>).</li> |
|||
<li><b>RayTrace</b><br/>Ray tracer benchmark based on code by <a href="http://flog.co.nz/">Adam Burmister</a> (<i>3418 lines</i>).</li> |
|||
<li><b>EarleyBoyer</b><br/>Classic Scheme benchmarks, translated to JavaScript by Florian Loitsch's Scheme2Js compiler (<i>4682 lines</i>).</li> |
|||
<li><b>RegExp</b><br/>Regular expression benchmark generated by extracting regular expression operations from 50 of the most popular web pages |
|||
(<i>4758 lines</i>). |
|||
</li> |
|||
</ul> |
|||
|
|||
<p> |
|||
Note that benchmark results are not comparable unless both results are |
|||
run with the same revision of the benchmark suite. We will be making |
|||
revisions from time to time in order to fix bugs or expand the scope |
|||
of the benchmark suite. For previous revisions and the change log see |
|||
the <a href="http://v8.googlecode.com/svn/data/benchmarks/current/revisions.html">revisions</a> page. |
|||
</p> |
|||
|
|||
</td><td style="text-align: center"> |
|||
<div class="run"> |
|||
<div id="status" style="text-align: center; margin-top: 60px; font-size: 120%; font-weight: bold;">Starting...</div> |
|||
<div style="text-align: left; margin: 30px 0 0 90px;" id="results"> |
|||
<div> |
|||
</div> |
|||
</td></tr></table> |
|||
|
|||
</div> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,60 @@ |
|||
// Copyright 2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
|
|||
load('base.js'); |
|||
load('richards.js'); |
|||
load('deltablue.js'); |
|||
load('crypto.js'); |
|||
load('raytrace.js'); |
|||
load('earley-boyer.js'); |
|||
load('regexp.js'); |
|||
|
|||
var success = true; |
|||
|
|||
function PrintResult(name, result) { |
|||
print(name + ': ' + result); |
|||
} |
|||
|
|||
|
|||
function PrintError(name, error) { |
|||
PrintResult(name, error); |
|||
success = false; |
|||
} |
|||
|
|||
|
|||
function PrintScore(score) { |
|||
if (success) { |
|||
print('----'); |
|||
print('Score (version ' + BenchmarkSuite.version + '): ' + score); |
|||
} |
|||
} |
|||
|
|||
|
|||
BenchmarkSuite.RunSuites({ NotifyResult: PrintResult, |
|||
NotifyError: PrintError, |
|||
NotifyScore: PrintScore }); |
@ -0,0 +1,70 @@ |
|||
body { |
|||
font-family: sans-serif; |
|||
} |
|||
|
|||
hr{ |
|||
border: 1px solid; |
|||
border-color: #36C; |
|||
margin: 1em 0 |
|||
} |
|||
|
|||
h1, h2, h3, h4 { |
|||
margin: 0; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
h1 { |
|||
font-size: 190%; |
|||
height: 1.2em; |
|||
} |
|||
|
|||
|
|||
h2{ |
|||
font-size: 140%; |
|||
height: 1.2em; |
|||
} |
|||
|
|||
h3{ |
|||
font-size: 100%; |
|||
} |
|||
|
|||
li{ |
|||
margin: .3em 0 1em 0; |
|||
} |
|||
|
|||
body{ |
|||
font-family: Helvetica,Arial,sans-serif; |
|||
font-size: small; |
|||
color: #000; |
|||
background-color: #fff; |
|||
} |
|||
|
|||
div.title { |
|||
background-color: rgb(229, 236, 249); |
|||
border-top: 1px solid rgb(51, 102, 204); |
|||
text-align: center; |
|||
padding-top: 0.2em; |
|||
padding-bottom: 0.2em; |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
div.subtitle { |
|||
border-bottom: 1px solid rgb(51, 102, 204); |
|||
margin-top: 2em; |
|||
} |
|||
|
|||
td.contents { |
|||
text-align: start; |
|||
} |
|||
|
|||
div.run { |
|||
margin: 20px; |
|||
width: 300px; |
|||
height: 300px; |
|||
float: right; |
|||
background-color: rgb(229, 236, 249); |
|||
background-image: url(v8-logo.png); |
|||
background-position: center center; |
|||
background-repeat: no-repeat; |
|||
border: 1px solid rgb(51, 102, 204); |
|||
} |
After Width: | Height: | Size: 24 KiB |
@ -0,0 +1,179 @@ |
|||
// Copyright 2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#ifndef V8_DEBUG_H_ |
|||
#define V8_DEBUG_H_ |
|||
|
|||
#include "v8.h" |
|||
|
|||
#ifdef _WIN32 |
|||
typedef int int32_t; |
|||
typedef unsigned int uint32_t; |
|||
typedef unsigned short uint16_t; // NOLINT
|
|||
typedef long long int64_t; // NOLINT
|
|||
|
|||
// Setup for Windows DLL export/import. See v8.h in this directory for
|
|||
// information on how to build/use V8 as a DLL.
|
|||
#if defined(BUILDING_V8_SHARED) && defined(USING_V8_SHARED) |
|||
#error both BUILDING_V8_SHARED and USING_V8_SHARED are set - please check the\ |
|||
build configuration to ensure that at most one of these is set |
|||
#endif |
|||
|
|||
#ifdef BUILDING_V8_SHARED |
|||
#define EXPORT __declspec(dllexport) |
|||
#elif USING_V8_SHARED |
|||
#define EXPORT __declspec(dllimport) |
|||
#else |
|||
#define EXPORT |
|||
#endif |
|||
|
|||
#else // _WIN32
|
|||
|
|||
// Setup for Linux shared library export. See v8.h in this directory for
|
|||
// information on how to build/use V8 as shared library.
|
|||
#if defined(__GNUC__) && (__GNUC__ >= 4) |
|||
#define EXPORT __attribute__ ((visibility("default"))) |
|||
#else // defined(__GNUC__) && (__GNUC__ >= 4)
|
|||
#define EXPORT |
|||
#endif // defined(__GNUC__) && (__GNUC__ >= 4)
|
|||
|
|||
#endif // _WIN32
|
|||
|
|||
|
|||
/**
|
|||
* Debugger support for the V8 JavaScript engine. |
|||
*/ |
|||
namespace v8 { |
|||
|
|||
// Debug events which can occur in the V8 JavaScript engine.
|
|||
enum DebugEvent { |
|||
Break = 1, |
|||
Exception = 2, |
|||
NewFunction = 3, |
|||
BeforeCompile = 4, |
|||
AfterCompile = 5 |
|||
}; |
|||
|
|||
|
|||
/**
|
|||
* Debug event callback function. |
|||
* |
|||
* \param event the type of the debug event that triggered the callback |
|||
* (enum DebugEvent) |
|||
* \param exec_state execution state (JavaScript object) |
|||
* \param event_data event specific data (JavaScript object) |
|||
* \param data value passed by the user to SetDebugEventListener |
|||
*/ |
|||
typedef void (*DebugEventCallback)(DebugEvent event, |
|||
Handle<Object> exec_state, |
|||
Handle<Object> event_data, |
|||
Handle<Value> data); |
|||
|
|||
|
|||
/**
|
|||
* Debug message callback function. |
|||
* |
|||
* \param message the debug message |
|||
* \param length length of the message |
|||
* \param data the data value passed when registering the message handler |
|||
* A DebugMessageHandler does not take posession of the message string, |
|||
* and must not rely on the data persisting after the handler returns. |
|||
*/ |
|||
typedef void (*DebugMessageHandler)(const uint16_t* message, int length, |
|||
void* data); |
|||
|
|||
/**
|
|||
* Debug host dispatch callback function. |
|||
* |
|||
* \param dispatch the dispatch value |
|||
* \param data the data value passed when registering the dispatch handler |
|||
*/ |
|||
typedef void (*DebugHostDispatchHandler)(void* dispatch, |
|||
void* data); |
|||
|
|||
|
|||
|
|||
class EXPORT Debug { |
|||
public: |
|||
// Set a C debug event listener.
|
|||
static bool SetDebugEventListener(DebugEventCallback that, |
|||
Handle<Value> data = Handle<Value>()); |
|||
|
|||
// Set a JavaScript debug event listener.
|
|||
static bool SetDebugEventListener(v8::Handle<v8::Object> that, |
|||
Handle<Value> data = Handle<Value>()); |
|||
|
|||
// Break execution of JavaScript.
|
|||
static void DebugBreak(); |
|||
|
|||
// Message based interface. The message protocol is JSON.
|
|||
static void SetMessageHandler(DebugMessageHandler handler, void* data = NULL, |
|||
bool message_handler_thread = true); |
|||
static void SendCommand(const uint16_t* command, int length); |
|||
|
|||
// Dispatch interface.
|
|||
static void SetHostDispatchHandler(DebugHostDispatchHandler handler, |
|||
void* data = NULL); |
|||
static void SendHostDispatch(void* dispatch); |
|||
|
|||
/**
|
|||
* Run a JavaScript function in the debugger. |
|||
* \param fun the function to call |
|||
* \param data passed as second argument to the function |
|||
* With this call the debugger is entered and the function specified is called |
|||
* with the execution state as the first argument. This makes it possible to |
|||
* get access to information otherwise not available during normal JavaScript |
|||
* execution e.g. details on stack frames. The following example show a |
|||
* JavaScript function which when passed to v8::Debug::Call will return the |
|||
* current line of JavaScript execution. |
|||
* |
|||
* \code |
|||
* function frame_source_line(exec_state) { |
|||
* return exec_state.frame(0).sourceLine(); |
|||
* } |
|||
* \endcode |
|||
*/ |
|||
static Handle<Value> Call(v8::Handle<v8::Function> fun, |
|||
Handle<Value> data = Handle<Value>()); |
|||
|
|||
/**
|
|||
* Enable the V8 builtin debug agent. The debugger agent will listen on the |
|||
* supplied TCP/IP port for remote debugger connection. |
|||
* \param name the name of the embedding application |
|||
* \param port the TCP/IP port to listen on |
|||
*/ |
|||
static bool EnableAgent(const char* name, int port); |
|||
}; |
|||
|
|||
|
|||
} // namespace v8
|
|||
|
|||
|
|||
#undef EXPORT |
|||
|
|||
|
|||
#endif // V8_DEBUG_H_
|
File diff suppressed because it is too large
@ -0,0 +1,38 @@ |
|||
# Copyright 2008 the V8 project authors. All rights reserved. |
|||
# Redistribution and use in source and binary forms, with or without |
|||
# modification, are permitted provided that the following conditions are |
|||
# met: |
|||
# |
|||
# * Redistributions of source code must retain the above copyright |
|||
# notice, this list of conditions and the following disclaimer. |
|||
# * Redistributions in binary form must reproduce the above |
|||
# copyright notice, this list of conditions and the following |
|||
# disclaimer in the documentation and/or other materials provided |
|||
# with the distribution. |
|||
# * Neither the name of Google Inc. nor the names of its |
|||
# contributors may be used to endorse or promote products derived |
|||
# from this software without specific prior written permission. |
|||
# |
|||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
|
|||
from os.path import join |
|||
Import('sample context') |
|||
|
|||
def ConfigureObjectFiles(): |
|||
env = Environment() |
|||
env.Replace(**context.flags['sample']) |
|||
context.ApplyEnvOverrides(env) |
|||
return env.Object(sample + '.cc') |
|||
|
|||
sample_object = ConfigureObjectFiles() |
|||
Return('sample_object') |
@ -0,0 +1,42 @@ |
|||
// Copyright 2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
function Initialize() { } |
|||
|
|||
function Process(request) { |
|||
if (options.verbose) { |
|||
log("Processing " + request.host + request.path + |
|||
" from " + request.referrer + "@" + request.userAgent); |
|||
} |
|||
if (!output[request.host]) { |
|||
output[request.host] = 1; |
|||
} else { |
|||
output[request.host]++ |
|||
} |
|||
} |
|||
|
|||
Initialize(); |
@ -0,0 +1,622 @@ |
|||
// Copyright 2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#include <v8.h> |
|||
|
|||
#include <string> |
|||
#include <map> |
|||
|
|||
using namespace std; |
|||
using namespace v8; |
|||
|
|||
// These interfaces represent an existing request processing interface.
|
|||
// The idea is to imagine a real application that uses these interfaces
|
|||
// and then add scripting capabilities that allow you to interact with
|
|||
// the objects through JavaScript.
|
|||
|
|||
/**
|
|||
* A simplified http request. |
|||
*/ |
|||
class HttpRequest { |
|||
public: |
|||
virtual ~HttpRequest() { } |
|||
virtual const string& Path() = 0; |
|||
virtual const string& Referrer() = 0; |
|||
virtual const string& Host() = 0; |
|||
virtual const string& UserAgent() = 0; |
|||
}; |
|||
|
|||
/**
|
|||
* The abstract superclass of http request processors. |
|||
*/ |
|||
class HttpRequestProcessor { |
|||
public: |
|||
virtual ~HttpRequestProcessor() { } |
|||
|
|||
// Initialize this processor. The map contains options that control
|
|||
// how requests should be processed.
|
|||
virtual bool Initialize(map<string, string>* options, |
|||
map<string, string>* output) = 0; |
|||
|
|||
// Process a single request.
|
|||
virtual bool Process(HttpRequest* req) = 0; |
|||
|
|||
static void Log(const char* event); |
|||
}; |
|||
|
|||
/**
|
|||
* An http request processor that is scriptable using JavaScript. |
|||
*/ |
|||
class JsHttpRequestProcessor : public HttpRequestProcessor { |
|||
public: |
|||
|
|||
// Creates a new processor that processes requests by invoking the
|
|||
// Process function of the JavaScript script given as an argument.
|
|||
explicit JsHttpRequestProcessor(Handle<String> script) : script_(script) { } |
|||
virtual ~JsHttpRequestProcessor(); |
|||
|
|||
virtual bool Initialize(map<string, string>* opts, |
|||
map<string, string>* output); |
|||
virtual bool Process(HttpRequest* req); |
|||
|
|||
private: |
|||
|
|||
// Execute the script associated with this processor and extract the
|
|||
// Process function. Returns true if this succeeded, otherwise false.
|
|||
bool ExecuteScript(Handle<String> script); |
|||
|
|||
// Wrap the options and output map in a JavaScript objects and
|
|||
// install it in the global namespace as 'options' and 'output'.
|
|||
bool InstallMaps(map<string, string>* opts, map<string, string>* output); |
|||
|
|||
// Constructs the template that describes the JavaScript wrapper
|
|||
// type for requests.
|
|||
static Handle<ObjectTemplate> MakeRequestTemplate(); |
|||
static Handle<ObjectTemplate> MakeMapTemplate(); |
|||
|
|||
// Callbacks that access the individual fields of request objects.
|
|||
static Handle<Value> GetPath(Local<String> name, const AccessorInfo& info); |
|||
static Handle<Value> GetReferrer(Local<String> name, |
|||
const AccessorInfo& info); |
|||
static Handle<Value> GetHost(Local<String> name, const AccessorInfo& info); |
|||
static Handle<Value> GetUserAgent(Local<String> name, |
|||
const AccessorInfo& info); |
|||
|
|||
// Callbacks that access maps
|
|||
static Handle<Value> MapGet(Local<String> name, const AccessorInfo& info); |
|||
static Handle<Value> MapSet(Local<String> name, |
|||
Local<Value> value, |
|||
const AccessorInfo& info); |
|||
|
|||
// Utility methods for wrapping C++ objects as JavaScript objects,
|
|||
// and going back again.
|
|||
static Handle<Object> WrapMap(map<string, string>* obj); |
|||
static map<string, string>* UnwrapMap(Handle<Object> obj); |
|||
static Handle<Object> WrapRequest(HttpRequest* obj); |
|||
static HttpRequest* UnwrapRequest(Handle<Object> obj); |
|||
|
|||
Handle<String> script_; |
|||
Persistent<Context> context_; |
|||
Persistent<Function> process_; |
|||
static Persistent<ObjectTemplate> request_template_; |
|||
static Persistent<ObjectTemplate> map_template_; |
|||
}; |
|||
|
|||
// -------------------------
|
|||
// --- P r o c e s s o r ---
|
|||
// -------------------------
|
|||
|
|||
|
|||
static Handle<Value> LogCallback(const Arguments& args) { |
|||
if (args.Length() < 1) return v8::Undefined(); |
|||
HandleScope scope; |
|||
Handle<Value> arg = args[0]; |
|||
String::Utf8Value value(arg); |
|||
HttpRequestProcessor::Log(*value); |
|||
return v8::Undefined(); |
|||
} |
|||
|
|||
|
|||
// Execute the script and fetch the Process method.
|
|||
bool JsHttpRequestProcessor::Initialize(map<string, string>* opts, |
|||
map<string, string>* output) { |
|||
// Create a handle scope to hold the temporary references.
|
|||
HandleScope handle_scope; |
|||
|
|||
// Create a template for the global object where we set the
|
|||
// built-in global functions.
|
|||
Handle<ObjectTemplate> global = ObjectTemplate::New(); |
|||
global->Set(String::New("log"), FunctionTemplate::New(LogCallback)); |
|||
|
|||
// Each processor gets its own context so different processors
|
|||
// don't affect each other (ignore the first three lines).
|
|||
Handle<Context> context = Context::New(NULL, global); |
|||
|
|||
// Store the context in the processor object in a persistent handle,
|
|||
// since we want the reference to remain after we return from this
|
|||
// method.
|
|||
context_ = Persistent<Context>::New(context); |
|||
|
|||
// Enter the new context so all the following operations take place
|
|||
// within it.
|
|||
Context::Scope context_scope(context); |
|||
|
|||
// Make the options mapping available within the context
|
|||
if (!InstallMaps(opts, output)) |
|||
return false; |
|||
|
|||
// Compile and run the script
|
|||
if (!ExecuteScript(script_)) |
|||
return false; |
|||
|
|||
// The script compiled and ran correctly. Now we fetch out the
|
|||
// Process function from the global object.
|
|||
Handle<String> process_name = String::New("Process"); |
|||
Handle<Value> process_val = context->Global()->Get(process_name); |
|||
|
|||
// If there is no Process function, or if it is not a function,
|
|||
// bail out
|
|||
if (!process_val->IsFunction()) return false; |
|||
|
|||
// It is a function; cast it to a Function
|
|||
Handle<Function> process_fun = Handle<Function>::Cast(process_val); |
|||
|
|||
// Store the function in a Persistent handle, since we also want
|
|||
// that to remain after this call returns
|
|||
process_ = Persistent<Function>::New(process_fun); |
|||
|
|||
// All done; all went well
|
|||
return true; |
|||
} |
|||
|
|||
|
|||
bool JsHttpRequestProcessor::ExecuteScript(Handle<String> script) { |
|||
HandleScope handle_scope; |
|||
|
|||
// We're just about to compile the script; set up an error handler to
|
|||
// catch any exceptions the script might throw.
|
|||
TryCatch try_catch; |
|||
|
|||
// Compile the script and check for errors.
|
|||
Handle<Script> compiled_script = Script::Compile(script); |
|||
if (compiled_script.IsEmpty()) { |
|||
String::Utf8Value error(try_catch.Exception()); |
|||
Log(*error); |
|||
// The script failed to compile; bail out.
|
|||
return false; |
|||
} |
|||
|
|||
// Run the script!
|
|||
Handle<Value> result = compiled_script->Run(); |
|||
if (result.IsEmpty()) { |
|||
// The TryCatch above is still in effect and will have caught the error.
|
|||
String::Utf8Value error(try_catch.Exception()); |
|||
Log(*error); |
|||
// Running the script failed; bail out.
|
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
|
|||
bool JsHttpRequestProcessor::InstallMaps(map<string, string>* opts, |
|||
map<string, string>* output) { |
|||
HandleScope handle_scope; |
|||
|
|||
// Wrap the map object in a JavaScript wrapper
|
|||
Handle<Object> opts_obj = WrapMap(opts); |
|||
|
|||
// Set the options object as a property on the global object.
|
|||
context_->Global()->Set(String::New("options"), opts_obj); |
|||
|
|||
Handle<Object> output_obj = WrapMap(output); |
|||
context_->Global()->Set(String::New("output"), output_obj); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
|
|||
bool JsHttpRequestProcessor::Process(HttpRequest* request) { |
|||
// Create a handle scope to keep the temporary object references.
|
|||
HandleScope handle_scope; |
|||
|
|||
// Enter this processor's context so all the remaining operations
|
|||
// take place there
|
|||
Context::Scope context_scope(context_); |
|||
|
|||
// Wrap the C++ request object in a JavaScript wrapper
|
|||
Handle<Object> request_obj = WrapRequest(request); |
|||
|
|||
// Set up an exception handler before calling the Process function
|
|||
TryCatch try_catch; |
|||
|
|||
// Invoke the process function, giving the global object as 'this'
|
|||
// and one argument, the request.
|
|||
const int argc = 1; |
|||
Handle<Value> argv[argc] = { request_obj }; |
|||
Handle<Value> result = process_->Call(context_->Global(), argc, argv); |
|||
if (result.IsEmpty()) { |
|||
String::Utf8Value error(try_catch.Exception()); |
|||
Log(*error); |
|||
return false; |
|||
} else { |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
|
|||
JsHttpRequestProcessor::~JsHttpRequestProcessor() { |
|||
// Dispose the persistent handles. When noone else has any
|
|||
// references to the objects stored in the handles they will be
|
|||
// automatically reclaimed.
|
|||
context_.Dispose(); |
|||
process_.Dispose(); |
|||
} |
|||
|
|||
|
|||
Persistent<ObjectTemplate> JsHttpRequestProcessor::request_template_; |
|||
Persistent<ObjectTemplate> JsHttpRequestProcessor::map_template_; |
|||
|
|||
|
|||
// -----------------------------------
|
|||
// --- A c c e s s i n g M a p s ---
|
|||
// -----------------------------------
|
|||
|
|||
// Utility function that wraps a C++ http request object in a
|
|||
// JavaScript object.
|
|||
Handle<Object> JsHttpRequestProcessor::WrapMap(map<string, string>* obj) { |
|||
// Handle scope for temporary handles.
|
|||
HandleScope handle_scope; |
|||
|
|||
// Fetch the template for creating JavaScript map wrappers.
|
|||
// It only has to be created once, which we do on demand.
|
|||
if (request_template_.IsEmpty()) { |
|||
Handle<ObjectTemplate> raw_template = MakeMapTemplate(); |
|||
map_template_ = Persistent<ObjectTemplate>::New(raw_template); |
|||
} |
|||
Handle<ObjectTemplate> templ = map_template_; |
|||
|
|||
// Create an empty map wrapper.
|
|||
Handle<Object> result = templ->NewInstance(); |
|||
|
|||
// Wrap the raw C++ pointer in an External so it can be referenced
|
|||
// from within JavaScript.
|
|||
Handle<External> map_ptr = External::New(obj); |
|||
|
|||
// Store the map pointer in the JavaScript wrapper.
|
|||
result->SetInternalField(0, map_ptr); |
|||
|
|||
// Return the result through the current handle scope. Since each
|
|||
// of these handles will go away when the handle scope is deleted
|
|||
// we need to call Close to let one, the result, escape into the
|
|||
// outer handle scope.
|
|||
return handle_scope.Close(result); |
|||
} |
|||
|
|||
|
|||
// Utility function that extracts the C++ map pointer from a wrapper
|
|||
// object.
|
|||
map<string, string>* JsHttpRequestProcessor::UnwrapMap(Handle<Object> obj) { |
|||
Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0)); |
|||
void* ptr = field->Value(); |
|||
return static_cast<map<string, string>*>(ptr); |
|||
} |
|||
|
|||
|
|||
// Convert a JavaScript string to a std::string. To not bother too
|
|||
// much with string encodings we just use ascii.
|
|||
string ObjectToString(Local<Value> value) { |
|||
String::Utf8Value utf8_value(value); |
|||
return string(*utf8_value); |
|||
} |
|||
|
|||
|
|||
Handle<Value> JsHttpRequestProcessor::MapGet(Local<String> name, |
|||
const AccessorInfo& info) { |
|||
// Fetch the map wrapped by this object.
|
|||
map<string, string>* obj = UnwrapMap(info.Holder()); |
|||
|
|||
// Convert the JavaScript string to a std::string.
|
|||
string key = ObjectToString(name); |
|||
|
|||
// Look up the value if it exists using the standard STL ideom.
|
|||
map<string, string>::iterator iter = obj->find(key); |
|||
|
|||
// If the key is not present return an empty handle as signal
|
|||
if (iter == obj->end()) return Handle<Value>(); |
|||
|
|||
// Otherwise fetch the value and wrap it in a JavaScript string
|
|||
const string& value = (*iter).second; |
|||
return String::New(value.c_str(), value.length()); |
|||
} |
|||
|
|||
|
|||
Handle<Value> JsHttpRequestProcessor::MapSet(Local<String> name, |
|||
Local<Value> value_obj, |
|||
const AccessorInfo& info) { |
|||
// Fetch the map wrapped by this object.
|
|||
map<string, string>* obj = UnwrapMap(info.Holder()); |
|||
|
|||
// Convert the key and value to std::strings.
|
|||
string key = ObjectToString(name); |
|||
string value = ObjectToString(value_obj); |
|||
|
|||
// Update the map.
|
|||
(*obj)[key] = value; |
|||
|
|||
// Return the value; any non-empty handle will work.
|
|||
return value_obj; |
|||
} |
|||
|
|||
|
|||
Handle<ObjectTemplate> JsHttpRequestProcessor::MakeMapTemplate() { |
|||
HandleScope handle_scope; |
|||
|
|||
Handle<ObjectTemplate> result = ObjectTemplate::New(); |
|||
result->SetInternalFieldCount(1); |
|||
result->SetNamedPropertyHandler(MapGet, MapSet); |
|||
|
|||
// Again, return the result through the current handle scope.
|
|||
return handle_scope.Close(result); |
|||
} |
|||
|
|||
|
|||
// -------------------------------------------
|
|||
// --- A c c e s s i n g R e q u e s t s ---
|
|||
// -------------------------------------------
|
|||
|
|||
/**
|
|||
* Utility function that wraps a C++ http request object in a |
|||
* JavaScript object. |
|||
*/ |
|||
Handle<Object> JsHttpRequestProcessor::WrapRequest(HttpRequest* request) { |
|||
// Handle scope for temporary handles.
|
|||
HandleScope handle_scope; |
|||
|
|||
// Fetch the template for creating JavaScript http request wrappers.
|
|||
// It only has to be created once, which we do on demand.
|
|||
if (request_template_.IsEmpty()) { |
|||
Handle<ObjectTemplate> raw_template = MakeRequestTemplate(); |
|||
request_template_ = Persistent<ObjectTemplate>::New(raw_template); |
|||
} |
|||
Handle<ObjectTemplate> templ = request_template_; |
|||
|
|||
// Create an empty http request wrapper.
|
|||
Handle<Object> result = templ->NewInstance(); |
|||
|
|||
// Wrap the raw C++ pointer in an External so it can be referenced
|
|||
// from within JavaScript.
|
|||
Handle<External> request_ptr = External::New(request); |
|||
|
|||
// Store the request pointer in the JavaScript wrapper.
|
|||
result->SetInternalField(0, request_ptr); |
|||
|
|||
// Return the result through the current handle scope. Since each
|
|||
// of these handles will go away when the handle scope is deleted
|
|||
// we need to call Close to let one, the result, escape into the
|
|||
// outer handle scope.
|
|||
return handle_scope.Close(result); |
|||
} |
|||
|
|||
|
|||
/**
|
|||
* Utility function that extracts the C++ http request object from a |
|||
* wrapper object. |
|||
*/ |
|||
HttpRequest* JsHttpRequestProcessor::UnwrapRequest(Handle<Object> obj) { |
|||
Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0)); |
|||
void* ptr = field->Value(); |
|||
return static_cast<HttpRequest*>(ptr); |
|||
} |
|||
|
|||
|
|||
Handle<Value> JsHttpRequestProcessor::GetPath(Local<String> name, |
|||
const AccessorInfo& info) { |
|||
// Extract the C++ request object from the JavaScript wrapper.
|
|||
HttpRequest* request = UnwrapRequest(info.Holder()); |
|||
|
|||
// Fetch the path.
|
|||
const string& path = request->Path(); |
|||
|
|||
// Wrap the result in a JavaScript string and return it.
|
|||
return String::New(path.c_str(), path.length()); |
|||
} |
|||
|
|||
|
|||
Handle<Value> JsHttpRequestProcessor::GetReferrer(Local<String> name, |
|||
const AccessorInfo& info) { |
|||
HttpRequest* request = UnwrapRequest(info.Holder()); |
|||
const string& path = request->Referrer(); |
|||
return String::New(path.c_str(), path.length()); |
|||
} |
|||
|
|||
|
|||
Handle<Value> JsHttpRequestProcessor::GetHost(Local<String> name, |
|||
const AccessorInfo& info) { |
|||
HttpRequest* request = UnwrapRequest(info.Holder()); |
|||
const string& path = request->Host(); |
|||
return String::New(path.c_str(), path.length()); |
|||
} |
|||
|
|||
|
|||
Handle<Value> JsHttpRequestProcessor::GetUserAgent(Local<String> name, |
|||
const AccessorInfo& info) { |
|||
HttpRequest* request = UnwrapRequest(info.Holder()); |
|||
const string& path = request->UserAgent(); |
|||
return String::New(path.c_str(), path.length()); |
|||
} |
|||
|
|||
|
|||
Handle<ObjectTemplate> JsHttpRequestProcessor::MakeRequestTemplate() { |
|||
HandleScope handle_scope; |
|||
|
|||
Handle<ObjectTemplate> result = ObjectTemplate::New(); |
|||
result->SetInternalFieldCount(1); |
|||
|
|||
// Add accessors for each of the fields of the request.
|
|||
result->SetAccessor(String::NewSymbol("path"), GetPath); |
|||
result->SetAccessor(String::NewSymbol("referrer"), GetReferrer); |
|||
result->SetAccessor(String::NewSymbol("host"), GetHost); |
|||
result->SetAccessor(String::NewSymbol("userAgent"), GetUserAgent); |
|||
|
|||
// Again, return the result through the current handle scope.
|
|||
return handle_scope.Close(result); |
|||
} |
|||
|
|||
|
|||
// --- Test ---
|
|||
|
|||
|
|||
void HttpRequestProcessor::Log(const char* event) { |
|||
printf("Logged: %s\n", event); |
|||
} |
|||
|
|||
|
|||
/**
|
|||
* A simplified http request. |
|||
*/ |
|||
class StringHttpRequest : public HttpRequest { |
|||
public: |
|||
StringHttpRequest(const string& path, |
|||
const string& referrer, |
|||
const string& host, |
|||
const string& user_agent); |
|||
virtual const string& Path() { return path_; } |
|||
virtual const string& Referrer() { return referrer_; } |
|||
virtual const string& Host() { return host_; } |
|||
virtual const string& UserAgent() { return user_agent_; } |
|||
private: |
|||
string path_; |
|||
string referrer_; |
|||
string host_; |
|||
string user_agent_; |
|||
}; |
|||
|
|||
|
|||
StringHttpRequest::StringHttpRequest(const string& path, |
|||
const string& referrer, |
|||
const string& host, |
|||
const string& user_agent) |
|||
: path_(path), |
|||
referrer_(referrer), |
|||
host_(host), |
|||
user_agent_(user_agent) { } |
|||
|
|||
|
|||
void ParseOptions(int argc, |
|||
char* argv[], |
|||
map<string, string>& options, |
|||
string* file) { |
|||
for (int i = 1; i < argc; i++) { |
|||
string arg = argv[i]; |
|||
int index = arg.find('=', 0); |
|||
if (index == string::npos) { |
|||
*file = arg; |
|||
} else { |
|||
string key = arg.substr(0, index); |
|||
string value = arg.substr(index+1); |
|||
options[key] = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
// Reads a file into a v8 string.
|
|||
Handle<String> ReadFile(const string& name) { |
|||
FILE* file = fopen(name.c_str(), "rb"); |
|||
if (file == NULL) return Handle<String>(); |
|||
|
|||
fseek(file, 0, SEEK_END); |
|||
int size = ftell(file); |
|||
rewind(file); |
|||
|
|||
char* chars = new char[size + 1]; |
|||
chars[size] = '\0'; |
|||
for (int i = 0; i < size;) { |
|||
int read = fread(&chars[i], 1, size - i, file); |
|||
i += read; |
|||
} |
|||
fclose(file); |
|||
Handle<String> result = String::New(chars, size); |
|||
delete[] chars; |
|||
return result; |
|||
} |
|||
|
|||
|
|||
const int kSampleSize = 6; |
|||
StringHttpRequest kSampleRequests[kSampleSize] = { |
|||
StringHttpRequest("/process.cc", "localhost", "google.com", "firefox"), |
|||
StringHttpRequest("/", "localhost", "google.net", "firefox"), |
|||
StringHttpRequest("/", "localhost", "google.org", "safari"), |
|||
StringHttpRequest("/", "localhost", "yahoo.com", "ie"), |
|||
StringHttpRequest("/", "localhost", "yahoo.com", "safari"), |
|||
StringHttpRequest("/", "localhost", "yahoo.com", "firefox") |
|||
}; |
|||
|
|||
|
|||
bool ProcessEntries(HttpRequestProcessor* processor, int count, |
|||
StringHttpRequest* reqs) { |
|||
for (int i = 0; i < count; i++) { |
|||
if (!processor->Process(&reqs[i])) |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
|
|||
void PrintMap(map<string, string>* m) { |
|||
for (map<string, string>::iterator i = m->begin(); i != m->end(); i++) { |
|||
pair<string, string> entry = *i; |
|||
printf("%s: %s\n", entry.first.c_str(), entry.second.c_str()); |
|||
} |
|||
} |
|||
|
|||
|
|||
int main(int argc, char* argv[]) { |
|||
map<string, string> options; |
|||
string file; |
|||
ParseOptions(argc, argv, options, &file); |
|||
if (file.empty()) { |
|||
fprintf(stderr, "No script was specified.\n"); |
|||
return 1; |
|||
} |
|||
HandleScope scope; |
|||
Handle<String> source = ReadFile(file); |
|||
if (source.IsEmpty()) { |
|||
fprintf(stderr, "Error reading '%s'.\n", file.c_str()); |
|||
return 1; |
|||
} |
|||
JsHttpRequestProcessor processor(source); |
|||
map<string, string> output; |
|||
if (!processor.Initialize(&options, &output)) { |
|||
fprintf(stderr, "Error initializing processor.\n"); |
|||
return 1; |
|||
} |
|||
if (!ProcessEntries(&processor, kSampleSize, kSampleRequests)) |
|||
return 1; |
|||
PrintMap(&output); |
|||
} |
@ -0,0 +1,281 @@ |
|||
// Copyright 2009 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#include <v8.h> |
|||
#include <fcntl.h> |
|||
#include <string.h> |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
|
|||
|
|||
void RunShell(v8::Handle<v8::Context> context); |
|||
bool ExecuteString(v8::Handle<v8::String> source, |
|||
v8::Handle<v8::Value> name, |
|||
bool print_result, |
|||
bool report_exceptions); |
|||
v8::Handle<v8::Value> Print(const v8::Arguments& args); |
|||
v8::Handle<v8::Value> Load(const v8::Arguments& args); |
|||
v8::Handle<v8::Value> Quit(const v8::Arguments& args); |
|||
v8::Handle<v8::Value> Version(const v8::Arguments& args); |
|||
v8::Handle<v8::String> ReadFile(const char* name); |
|||
void ReportException(v8::TryCatch* handler); |
|||
|
|||
|
|||
int RunMain(int argc, char* argv[]) { |
|||
v8::V8::SetFlagsFromCommandLine(&argc, argv, true); |
|||
v8::HandleScope handle_scope; |
|||
// Create a template for the global object.
|
|||
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(); |
|||
// Bind the global 'print' function to the C++ Print callback.
|
|||
global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print)); |
|||
// Bind the global 'load' function to the C++ Load callback.
|
|||
global->Set(v8::String::New("load"), v8::FunctionTemplate::New(Load)); |
|||
// Bind the 'quit' function
|
|||
global->Set(v8::String::New("quit"), v8::FunctionTemplate::New(Quit)); |
|||
// Bind the 'version' function
|
|||
global->Set(v8::String::New("version"), v8::FunctionTemplate::New(Version)); |
|||
// Create a new execution environment containing the built-in
|
|||
// functions
|
|||
v8::Handle<v8::Context> context = v8::Context::New(NULL, global); |
|||
// Enter the newly created execution environment.
|
|||
v8::Context::Scope context_scope(context); |
|||
bool run_shell = (argc == 1); |
|||
for (int i = 1; i < argc; i++) { |
|||
const char* str = argv[i]; |
|||
if (strcmp(str, "--shell") == 0) { |
|||
run_shell = true; |
|||
} else if (strcmp(str, "-f") == 0) { |
|||
// Ignore any -f flags for compatibility with the other stand-
|
|||
// alone JavaScript engines.
|
|||
continue; |
|||
} else if (strncmp(str, "--", 2) == 0) { |
|||
printf("Warning: unknown flag %s.\nTry --help for options\n", str); |
|||
} else if (strcmp(str, "-e") == 0 && i + 1 < argc) { |
|||
// Execute argument given to -e option directly
|
|||
v8::HandleScope handle_scope; |
|||
v8::Handle<v8::String> file_name = v8::String::New("unnamed"); |
|||
v8::Handle<v8::String> source = v8::String::New(argv[i + 1]); |
|||
if (!ExecuteString(source, file_name, false, true)) |
|||
return 1; |
|||
i++; |
|||
} else { |
|||
// Use all other arguments as names of files to load and run.
|
|||
v8::HandleScope handle_scope; |
|||
v8::Handle<v8::String> file_name = v8::String::New(str); |
|||
v8::Handle<v8::String> source = ReadFile(str); |
|||
if (source.IsEmpty()) { |
|||
printf("Error reading '%s'\n", str); |
|||
return 1; |
|||
} |
|||
if (!ExecuteString(source, file_name, false, true)) |
|||
return 1; |
|||
} |
|||
} |
|||
if (run_shell) RunShell(context); |
|||
return 0; |
|||
} |
|||
|
|||
|
|||
int main(int argc, char* argv[]) { |
|||
int result = RunMain(argc, argv); |
|||
v8::V8::Dispose(); |
|||
return result; |
|||
} |
|||
|
|||
|
|||
// Extracts a C string from a V8 Utf8Value.
|
|||
const char* ToCString(const v8::String::Utf8Value& value) { |
|||
return *value ? *value : "<string conversion failed>"; |
|||
} |
|||
|
|||
|
|||
// The callback that is invoked by v8 whenever the JavaScript 'print'
|
|||
// function is called. Prints its arguments on stdout separated by
|
|||
// spaces and ending with a newline.
|
|||
v8::Handle<v8::Value> Print(const v8::Arguments& args) { |
|||
bool first = true; |
|||
for (int i = 0; i < args.Length(); i++) { |
|||
v8::HandleScope handle_scope; |
|||
if (first) { |
|||
first = false; |
|||
} else { |
|||
printf(" "); |
|||
} |
|||
v8::String::Utf8Value str(args[i]); |
|||
const char* cstr = ToCString(str); |
|||
printf("%s", cstr); |
|||
} |
|||
printf("\n"); |
|||
fflush(stdout); |
|||
return v8::Undefined(); |
|||
} |
|||
|
|||
|
|||
// The callback that is invoked by v8 whenever the JavaScript 'load'
|
|||
// function is called. Loads, compiles and executes its argument
|
|||
// JavaScript file.
|
|||
v8::Handle<v8::Value> Load(const v8::Arguments& args) { |
|||
for (int i = 0; i < args.Length(); i++) { |
|||
v8::HandleScope handle_scope; |
|||
v8::String::Utf8Value file(args[i]); |
|||
if (*file == NULL) { |
|||
return v8::ThrowException(v8::String::New("Error loading file")); |
|||
} |
|||
v8::Handle<v8::String> source = ReadFile(*file); |
|||
if (source.IsEmpty()) { |
|||
return v8::ThrowException(v8::String::New("Error loading file")); |
|||
} |
|||
if (!ExecuteString(source, v8::String::New(*file), false, false)) { |
|||
return v8::ThrowException(v8::String::New("Error executing file")); |
|||
} |
|||
} |
|||
return v8::Undefined(); |
|||
} |
|||
|
|||
|
|||
// The callback that is invoked by v8 whenever the JavaScript 'quit'
|
|||
// function is called. Quits.
|
|||
v8::Handle<v8::Value> Quit(const v8::Arguments& args) { |
|||
// If not arguments are given args[0] will yield undefined which
|
|||
// converts to the integer value 0.
|
|||
int exit_code = args[0]->Int32Value(); |
|||
exit(exit_code); |
|||
return v8::Undefined(); |
|||
} |
|||
|
|||
|
|||
v8::Handle<v8::Value> Version(const v8::Arguments& args) { |
|||
return v8::String::New(v8::V8::GetVersion()); |
|||
} |
|||
|
|||
|
|||
// Reads a file into a v8 string.
|
|||
v8::Handle<v8::String> ReadFile(const char* name) { |
|||
FILE* file = fopen(name, "rb"); |
|||
if (file == NULL) return v8::Handle<v8::String>(); |
|||
|
|||
fseek(file, 0, SEEK_END); |
|||
int size = ftell(file); |
|||
rewind(file); |
|||
|
|||
char* chars = new char[size + 1]; |
|||
chars[size] = '\0'; |
|||
for (int i = 0; i < size;) { |
|||
int read = fread(&chars[i], 1, size - i, file); |
|||
i += read; |
|||
} |
|||
fclose(file); |
|||
v8::Handle<v8::String> result = v8::String::New(chars, size); |
|||
delete[] chars; |
|||
return result; |
|||
} |
|||
|
|||
|
|||
// The read-eval-execute loop of the shell.
|
|||
void RunShell(v8::Handle<v8::Context> context) { |
|||
printf("V8 version %s\n", v8::V8::GetVersion()); |
|||
static const int kBufferSize = 256; |
|||
while (true) { |
|||
char buffer[kBufferSize]; |
|||
printf("> "); |
|||
char* str = fgets(buffer, kBufferSize, stdin); |
|||
if (str == NULL) break; |
|||
v8::HandleScope handle_scope; |
|||
ExecuteString(v8::String::New(str), |
|||
v8::String::New("(shell)"), |
|||
true, |
|||
true); |
|||
} |
|||
printf("\n"); |
|||
} |
|||
|
|||
|
|||
// Executes a string within the current v8 context.
|
|||
bool ExecuteString(v8::Handle<v8::String> source, |
|||
v8::Handle<v8::Value> name, |
|||
bool print_result, |
|||
bool report_exceptions) { |
|||
v8::HandleScope handle_scope; |
|||
v8::TryCatch try_catch; |
|||
v8::Handle<v8::Script> script = v8::Script::Compile(source, name); |
|||
if (script.IsEmpty()) { |
|||
// Print errors that happened during compilation.
|
|||
if (report_exceptions) |
|||
ReportException(&try_catch); |
|||
return false; |
|||
} else { |
|||
v8::Handle<v8::Value> result = script->Run(); |
|||
if (result.IsEmpty()) { |
|||
// Print errors that happened during execution.
|
|||
if (report_exceptions) |
|||
ReportException(&try_catch); |
|||
return false; |
|||
} else { |
|||
if (print_result && !result->IsUndefined()) { |
|||
// If all went well and the result wasn't undefined then print
|
|||
// the returned value.
|
|||
v8::String::Utf8Value str(result); |
|||
const char* cstr = ToCString(str); |
|||
printf("%s\n", cstr); |
|||
} |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
void ReportException(v8::TryCatch* try_catch) { |
|||
v8::HandleScope handle_scope; |
|||
v8::String::Utf8Value exception(try_catch->Exception()); |
|||
const char* exception_string = ToCString(exception); |
|||
v8::Handle<v8::Message> message = try_catch->Message(); |
|||
if (message.IsEmpty()) { |
|||
// V8 didn't provide any extra information about this error; just
|
|||
// print the exception.
|
|||
printf("%s\n", exception_string); |
|||
} else { |
|||
// Print (filename):(line number): (message).
|
|||
v8::String::Utf8Value filename(message->GetScriptResourceName()); |
|||
const char* filename_string = ToCString(filename); |
|||
int linenum = message->GetLineNumber(); |
|||
printf("%s:%i: %s\n", filename_string, linenum, exception_string); |
|||
// Print line of source code.
|
|||
v8::String::Utf8Value sourceline(message->GetSourceLine()); |
|||
const char* sourceline_string = ToCString(sourceline); |
|||
printf("%s\n", sourceline_string); |
|||
// Print wavy underline (GetUnderline is deprecated).
|
|||
int start = message->GetStartColumn(); |
|||
for (int i = 0; i < start; i++) { |
|||
printf(" "); |
|||
} |
|||
int end = message->GetEndColumn(); |
|||
for (int i = start; i < end; i++) { |
|||
printf("^"); |
|||
} |
|||
printf("\n"); |
|||
} |
|||
} |
@ -0,0 +1,25 @@ |
|||
Copyright and license for SCons - a software construction tool |
|||
|
|||
This copyright and license do not apply to any other software |
|||
with which this software may have been included. |
|||
|
|||
Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining |
|||
a copy of this software and associated documentation files (the |
|||
"Software"), to deal in the Software without restriction, including |
|||
without limitation the rights to use, copy, modify, merge, publish, |
|||
distribute, sublicense, and/or sell copies of the Software, and to |
|||
permit persons to whom the Software is furnished to do so, subject to |
|||
the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included |
|||
in all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY |
|||
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
|||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,204 @@ |
|||
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation |
|||
|
|||
SCons - a software construction tool |
|||
|
|||
This is the scons-README file for a version of SCons packaged for local |
|||
execution--that is, execution out of a specific local directory, without |
|||
having to install SCons as a system-wide utility. |
|||
|
|||
You are likely reading this file in one of the following two situations: |
|||
|
|||
1) You have unpacked an scons-local-{version} package and are |
|||
examining the contents. |
|||
|
|||
In this case, you are presumably interested in using this |
|||
package to include a local copy of SCons with some other |
|||
software that you package, so that you can use SCons to build |
|||
your software without forcing all of your users to have it fully |
|||
installed. Instructions for this can be found below. |
|||
|
|||
If you are not looking to use SCons in this way, then please |
|||
use either the scons-{version} package to install SCons on your |
|||
system, or the scons-src-{version} package if you want the full |
|||
source to SCons, including its packaging code and underlying |
|||
tests and testing infrastructure. |
|||
|
|||
2) This file was included in some other software package so that |
|||
the package could be built using SCons. |
|||
|
|||
In this case, follow the instructions provided with the |
|||
rest of the software package for how to use SCons to build |
|||
and/or install the software. The file containing build and |
|||
installation instructions will typically be named README or |
|||
INSTALL. |
|||
|
|||
LATEST VERSION |
|||
============== |
|||
|
|||
Before going further, you can check for the latest version of the |
|||
scons-local package, or any SCons package, at the SCons download page: |
|||
|
|||
http://www.scons.org/download.html |
|||
|
|||
|
|||
EXECUTION REQUIREMENTS |
|||
====================== |
|||
|
|||
Running SCons requires Python version 1.5.2 or later. There should be |
|||
no other dependencies or requirements to run SCons. |
|||
|
|||
The default SCons configuration assumes use of the Microsoft Visual C++ |
|||
compiler suite on WIN32 systems, and assumes a C compiler named 'cc', |
|||
a C++ compiler named 'c++', and a Fortran compiler named 'g77' (such |
|||
as found in the GNU C compiler suite) on any other type of system. |
|||
You may, of course, override these default values by appropriate |
|||
configuration of Environment construction variables. |
|||
|
|||
|
|||
INSTALLATION |
|||
============ |
|||
|
|||
Installation of this package should be as simple as unpacking the |
|||
archive (either .tar.gz or .zip) in any directory (top-level or a |
|||
subdirectory) within the software package with which you want to ship |
|||
SCons. |
|||
|
|||
Once you have installed this package, you should write an SConstruct |
|||
file at the top level of your source tree to build your software as you |
|||
see fit. |
|||
|
|||
Then modify the build/install instructions for your package to instruct |
|||
your users to execute SCons as follows (if you installed this package in |
|||
your top-level directory): |
|||
|
|||
$ python scons.py |
|||
|
|||
Or (if, for example, you installed this package in a subdirectory named |
|||
"scons"): |
|||
|
|||
$ python scons/scons.py |
|||
|
|||
That should be all you have to do. (If it isn't that simple, please let |
|||
us know!) |
|||
|
|||
|
|||
CONTENTS OF THIS PACKAGE |
|||
======================== |
|||
|
|||
This scons-local package consists of the following: |
|||
|
|||
scons-LICENSE |
|||
A copy of the copyright and terms under which SCons is |
|||
distributed (the Open Source Initiative-approved MIT license). |
|||
|
|||
A disclaimer has been added to the beginning to make clear that |
|||
this license applies only to SCons, and not to any separate |
|||
software you've written with which you're planning to package |
|||
SCons. |
|||
|
|||
scons-README |
|||
What you're looking at right now. |
|||
|
|||
scons-local-{version}/ |
|||
The SCons build engine. This is structured as a Python |
|||
library. |
|||
|
|||
scons.py |
|||
The SCons script itself. The script sets up the Python |
|||
sys.path variable to use the build engine found in the |
|||
scons-local-{version}/ directory in preference to any other |
|||
SCons build engine installed on your system. |
|||
|
|||
|
|||
DOCUMENTATION |
|||
============= |
|||
|
|||
Because this package is intended to be included with other software by |
|||
experienced users, we have not included any SCons documentation in this |
|||
package (other than this scons-README file you're reading right now). |
|||
|
|||
If, however, you need documentation about SCons, then consult any of the |
|||
following from the corresponding scons-{version} or scons-src-{version} |
|||
package: |
|||
|
|||
The RELEASE.txt file (src/RELEASE.txt file in the |
|||
scons-src-{version} package), which contains notes about this |
|||
specific release, including known problems. |
|||
|
|||
The CHANGES.txt file (src/CHANGES.txt file in the |
|||
scons-src-{version} package), which contains a list of changes |
|||
since the previous release. |
|||
|
|||
The scons.1 man page (doc/man/scons.1 in the scons-src-{version} |
|||
package), which contains a section of small examples for getting |
|||
started using SCons. |
|||
|
|||
Additional documentation for SCons is available at: |
|||
|
|||
http://www.scons.org/doc.html |
|||
|
|||
|
|||
LICENSING |
|||
========= |
|||
|
|||
SCons is distributed under the MIT license, a full copy of which is |
|||
available in the scons-LICENSE file in this package. The MIT license is |
|||
an approved Open Source license, which means: |
|||
|
|||
This software is OSI Certified Open Source Software. OSI |
|||
Certified is a certification mark of the Open Source Initiative. |
|||
|
|||
More information about OSI certifications and Open Source software is |
|||
available at: |
|||
|
|||
http://www.opensource.org/ |
|||
|
|||
|
|||
REPORTING BUGS |
|||
============== |
|||
|
|||
You can report bugs either by following the "Tracker - Bugs" link |
|||
on the SCons project page: |
|||
|
|||
http://sourceforge.net/projects/scons/ |
|||
|
|||
or by sending mail to the SCons developers mailing list: |
|||
|
|||
scons-devel@lists.sourceforge.net |
|||
|
|||
|
|||
MAILING LISTS |
|||
============= |
|||
|
|||
A mailing list for users of SCons is available. You may send questions |
|||
or comments to the list at: |
|||
|
|||
scons-users@lists.sourceforge.net |
|||
|
|||
You may subscribe to the scons-users mailing list at: |
|||
|
|||
http://lists.sourceforge.net/lists/listinfo/scons-users |
|||
|
|||
|
|||
FOR MORE INFORMATION |
|||
==================== |
|||
|
|||
Check the SCons web site at: |
|||
|
|||
http://www.scons.org/ |
|||
|
|||
|
|||
AUTHOR INFO |
|||
=========== |
|||
|
|||
Steven Knight |
|||
knight at baldmt dot com |
|||
http://www.baldmt.com/~knight/ |
|||
|
|||
With plenty of help from the SCons Development team: |
|||
Chad Austin |
|||
Charles Crain |
|||
Steve Leblanc |
|||
Anthony Roach |
|||
Terrel Shumway |
|||
|
File diff suppressed because it is too large
@ -0,0 +1,165 @@ |
|||
#! /usr/bin/env python |
|||
# |
|||
# SCons - a Software Constructor |
|||
# |
|||
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation |
|||
# |
|||
# Permission is hereby granted, free of charge, to any person obtaining |
|||
# a copy of this software and associated documentation files (the |
|||
# "Software"), to deal in the Software without restriction, including |
|||
# without limitation the rights to use, copy, modify, merge, publish, |
|||
# distribute, sublicense, and/or sell copies of the Software, and to |
|||
# permit persons to whom the Software is furnished to do so, subject to |
|||
# the following conditions: |
|||
# |
|||
# The above copyright notice and this permission notice shall be included |
|||
# in all copies or substantial portions of the Software. |
|||
# |
|||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY |
|||
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
|||
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
# |
|||
|
|||
__revision__ = "src/script/scons.py 3842 2008/12/20 22:59:52 scons" |
|||
|
|||
__version__ = "1.2.0" |
|||
|
|||
__build__ = "r3842" |
|||
|
|||
__buildsys__ = "scons-dev" |
|||
|
|||
__date__ = "2008/12/20 22:59:52" |
|||
|
|||
__developer__ = "scons" |
|||
|
|||
import os |
|||
import os.path |
|||
import sys |
|||
|
|||
############################################################################## |
|||
# BEGIN STANDARD SCons SCRIPT HEADER |
|||
# |
|||
# This is the cut-and-paste logic so that a self-contained script can |
|||
# interoperate correctly with different SCons versions and installation |
|||
# locations for the engine. If you modify anything in this section, you |
|||
# should also change other scripts that use this same header. |
|||
############################################################################## |
|||
|
|||
# Strip the script directory from sys.path() so on case-insensitive |
|||
# (WIN32) systems Python doesn't think that the "scons" script is the |
|||
# "SCons" package. Replace it with our own library directories |
|||
# (version-specific first, in case they installed by hand there, |
|||
# followed by generic) so we pick up the right version of the build |
|||
# engine modules if they're in either directory. |
|||
|
|||
script_dir = sys.path[0] |
|||
|
|||
if script_dir in sys.path: |
|||
sys.path.remove(script_dir) |
|||
|
|||
libs = [] |
|||
|
|||
if os.environ.has_key("SCONS_LIB_DIR"): |
|||
libs.append(os.environ["SCONS_LIB_DIR"]) |
|||
|
|||
local_version = 'scons-local-' + __version__ |
|||
local = 'scons-local' |
|||
if script_dir: |
|||
local_version = os.path.join(script_dir, local_version) |
|||
local = os.path.join(script_dir, local) |
|||
libs.append(os.path.abspath(local_version)) |
|||
libs.append(os.path.abspath(local)) |
|||
|
|||
scons_version = 'scons-%s' % __version__ |
|||
|
|||
prefs = [] |
|||
|
|||
if sys.platform == 'win32': |
|||
# sys.prefix is (likely) C:\Python*; |
|||
# check only C:\Python*. |
|||
prefs.append(sys.prefix) |
|||
prefs.append(os.path.join(sys.prefix, 'Lib', 'site-packages')) |
|||
else: |
|||
# On other (POSIX) platforms, things are more complicated due to |
|||
# the variety of path names and library locations. Try to be smart |
|||
# about it. |
|||
if script_dir == 'bin': |
|||
# script_dir is `pwd`/bin; |
|||
# check `pwd`/lib/scons*. |
|||
prefs.append(os.getcwd()) |
|||
else: |
|||
if script_dir == '.' or script_dir == '': |
|||
script_dir = os.getcwd() |
|||
head, tail = os.path.split(script_dir) |
|||
if tail == "bin": |
|||
# script_dir is /foo/bin; |
|||
# check /foo/lib/scons*. |
|||
prefs.append(head) |
|||
|
|||
head, tail = os.path.split(sys.prefix) |
|||
if tail == "usr": |
|||
# sys.prefix is /foo/usr; |
|||
# check /foo/usr/lib/scons* first, |
|||
# then /foo/usr/local/lib/scons*. |
|||
prefs.append(sys.prefix) |
|||
prefs.append(os.path.join(sys.prefix, "local")) |
|||
elif tail == "local": |
|||
h, t = os.path.split(head) |
|||
if t == "usr": |
|||
# sys.prefix is /foo/usr/local; |
|||
# check /foo/usr/local/lib/scons* first, |
|||
# then /foo/usr/lib/scons*. |
|||
prefs.append(sys.prefix) |
|||
prefs.append(head) |
|||
else: |
|||
# sys.prefix is /foo/local; |
|||
# check only /foo/local/lib/scons*. |
|||
prefs.append(sys.prefix) |
|||
else: |
|||
# sys.prefix is /foo (ends in neither /usr or /local); |
|||
# check only /foo/lib/scons*. |
|||
prefs.append(sys.prefix) |
|||
|
|||
temp = map(lambda x: os.path.join(x, 'lib'), prefs) |
|||
temp.extend(map(lambda x: os.path.join(x, |
|||
'lib', |
|||
'python' + sys.version[:3], |
|||
'site-packages'), |
|||
prefs)) |
|||
prefs = temp |
|||
|
|||
# Add the parent directory of the current python's library to the |
|||
# preferences. On SuSE-91/AMD64, for example, this is /usr/lib64, |
|||
# not /usr/lib. |
|||
try: |
|||
libpath = os.__file__ |
|||
except AttributeError: |
|||
pass |
|||
else: |
|||
# Split /usr/libfoo/python*/os.py to /usr/libfoo/python*. |
|||
libpath, tail = os.path.split(libpath) |
|||
# Split /usr/libfoo/python* to /usr/libfoo |
|||
libpath, tail = os.path.split(libpath) |
|||
# Check /usr/libfoo/scons*. |
|||
prefs.append(libpath) |
|||
|
|||
# Look first for 'scons-__version__' in all of our preference libs, |
|||
# then for 'scons'. |
|||
libs.extend(map(lambda x: os.path.join(x, scons_version), prefs)) |
|||
libs.extend(map(lambda x: os.path.join(x, 'scons'), prefs)) |
|||
|
|||
sys.path = libs + sys.path |
|||
|
|||
############################################################################## |
|||
# END STANDARD SCons SCRIPT HEADER |
|||
############################################################################## |
|||
|
|||
if __name__ == "__main__": |
|||
import SCons.Script |
|||
# this does all the work, and calls sys.exit |
|||
# with the proper exit status when done. |
|||
SCons.Script.main() |
@ -0,0 +1,502 @@ |
|||
#! /usr/bin/env python |
|||
# |
|||
# SCons - a Software Constructor |
|||
# |
|||
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 The SCons Foundation |
|||
# |
|||
# Permission is hereby granted, free of charge, to any person obtaining |
|||
# a copy of this software and associated documentation files (the |
|||
# "Software"), to deal in the Software without restriction, including |
|||
# without limitation the rights to use, copy, modify, merge, publish, |
|||
# distribute, sublicense, and/or sell copies of the Software, and to |
|||
# permit persons to whom the Software is furnished to do so, subject to |
|||
# the following conditions: |
|||
# |
|||
# The above copyright notice and this permission notice shall be included |
|||
# in all copies or substantial portions of the Software. |
|||
# |
|||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY |
|||
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
|||
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
# |
|||
|
|||
__revision__ = "src/script/sconsign.py 3842 2008/12/20 22:59:52 scons" |
|||
|
|||
__version__ = "1.2.0" |
|||
|
|||
__build__ = "r3842" |
|||
|
|||
__buildsys__ = "scons-dev" |
|||
|
|||
__date__ = "2008/12/20 22:59:52" |
|||
|
|||
__developer__ = "scons" |
|||
|
|||
import os |
|||
import os.path |
|||
import sys |
|||
import time |
|||
|
|||
############################################################################## |
|||
# BEGIN STANDARD SCons SCRIPT HEADER |
|||
# |
|||
# This is the cut-and-paste logic so that a self-contained script can |
|||
# interoperate correctly with different SCons versions and installation |
|||
# locations for the engine. If you modify anything in this section, you |
|||
# should also change other scripts that use this same header. |
|||
############################################################################## |
|||
|
|||
# Strip the script directory from sys.path() so on case-insensitive |
|||
# (WIN32) systems Python doesn't think that the "scons" script is the |
|||
# "SCons" package. Replace it with our own library directories |
|||
# (version-specific first, in case they installed by hand there, |
|||
# followed by generic) so we pick up the right version of the build |
|||
# engine modules if they're in either directory. |
|||
|
|||
script_dir = sys.path[0] |
|||
|
|||
if script_dir in sys.path: |
|||
sys.path.remove(script_dir) |
|||
|
|||
libs = [] |
|||
|
|||
if os.environ.has_key("SCONS_LIB_DIR"): |
|||
libs.append(os.environ["SCONS_LIB_DIR"]) |
|||
|
|||
local_version = 'scons-local-' + __version__ |
|||
local = 'scons-local' |
|||
if script_dir: |
|||
local_version = os.path.join(script_dir, local_version) |
|||
local = os.path.join(script_dir, local) |
|||
libs.append(os.path.abspath(local_version)) |
|||
libs.append(os.path.abspath(local)) |
|||
|
|||
scons_version = 'scons-%s' % __version__ |
|||
|
|||
prefs = [] |
|||
|
|||
if sys.platform == 'win32': |
|||
# sys.prefix is (likely) C:\Python*; |
|||
# check only C:\Python*. |
|||
prefs.append(sys.prefix) |
|||
prefs.append(os.path.join(sys.prefix, 'Lib', 'site-packages')) |
|||
else: |
|||
# On other (POSIX) platforms, things are more complicated due to |
|||
# the variety of path names and library locations. Try to be smart |
|||
# about it. |
|||
if script_dir == 'bin': |
|||
# script_dir is `pwd`/bin; |
|||
# check `pwd`/lib/scons*. |
|||
prefs.append(os.getcwd()) |
|||
else: |
|||
if script_dir == '.' or script_dir == '': |
|||
script_dir = os.getcwd() |
|||
head, tail = os.path.split(script_dir) |
|||
if tail == "bin": |
|||
# script_dir is /foo/bin; |
|||
# check /foo/lib/scons*. |
|||
prefs.append(head) |
|||
|
|||
head, tail = os.path.split(sys.prefix) |
|||
if tail == "usr": |
|||
# sys.prefix is /foo/usr; |
|||
# check /foo/usr/lib/scons* first, |
|||
# then /foo/usr/local/lib/scons*. |
|||
prefs.append(sys.prefix) |
|||
prefs.append(os.path.join(sys.prefix, "local")) |
|||
elif tail == "local": |
|||
h, t = os.path.split(head) |
|||
if t == "usr": |
|||
# sys.prefix is /foo/usr/local; |
|||
# check /foo/usr/local/lib/scons* first, |
|||
# then /foo/usr/lib/scons*. |
|||
prefs.append(sys.prefix) |
|||
prefs.append(head) |
|||
else: |
|||
# sys.prefix is /foo/local; |
|||
# check only /foo/local/lib/scons*. |
|||
prefs.append(sys.prefix) |
|||
else: |
|||
# sys.prefix is /foo (ends in neither /usr or /local); |
|||
# check only /foo/lib/scons*. |
|||
prefs.append(sys.prefix) |
|||
|
|||
temp = map(lambda x: os.path.join(x, 'lib'), prefs) |
|||
temp.extend(map(lambda x: os.path.join(x, |
|||
'lib', |
|||
'python' + sys.version[:3], |
|||
'site-packages'), |
|||
prefs)) |
|||
prefs = temp |
|||
|
|||
# Add the parent directory of the current python's library to the |
|||
# preferences. On SuSE-91/AMD64, for example, this is /usr/lib64, |
|||
# not /usr/lib. |
|||
try: |
|||
libpath = os.__file__ |
|||
except AttributeError: |
|||
pass |
|||
else: |
|||
# Split /usr/libfoo/python*/os.py to /usr/libfoo/python*. |
|||
libpath, tail = os.path.split(libpath) |
|||
# Split /usr/libfoo/python* to /usr/libfoo |
|||
libpath, tail = os.path.split(libpath) |
|||
# Check /usr/libfoo/scons*. |
|||
prefs.append(libpath) |
|||
|
|||
# Look first for 'scons-__version__' in all of our preference libs, |
|||
# then for 'scons'. |
|||
libs.extend(map(lambda x: os.path.join(x, scons_version), prefs)) |
|||
libs.extend(map(lambda x: os.path.join(x, 'scons'), prefs)) |
|||
|
|||
sys.path = libs + sys.path |
|||
|
|||
############################################################################## |
|||
# END STANDARD SCons SCRIPT HEADER |
|||
############################################################################## |
|||
|
|||
import cPickle |
|||
import imp |
|||
import string |
|||
import whichdb |
|||
|
|||
import SCons.SConsign |
|||
|
|||
def my_whichdb(filename): |
|||
if filename[-7:] == ".dblite": |
|||
return "SCons.dblite" |
|||
try: |
|||
f = open(filename + ".dblite", "rb") |
|||
f.close() |
|||
return "SCons.dblite" |
|||
except IOError: |
|||
pass |
|||
return _orig_whichdb(filename) |
|||
|
|||
_orig_whichdb = whichdb.whichdb |
|||
whichdb.whichdb = my_whichdb |
|||
|
|||
def my_import(mname): |
|||
if '.' in mname: |
|||
i = string.rfind(mname, '.') |
|||
parent = my_import(mname[:i]) |
|||
fp, pathname, description = imp.find_module(mname[i+1:], |
|||
parent.__path__) |
|||
else: |
|||
fp, pathname, description = imp.find_module(mname) |
|||
return imp.load_module(mname, fp, pathname, description) |
|||
|
|||
class Flagger: |
|||
default_value = 1 |
|||
def __setitem__(self, item, value): |
|||
self.__dict__[item] = value |
|||
self.default_value = 0 |
|||
def __getitem__(self, item): |
|||
return self.__dict__.get(item, self.default_value) |
|||
|
|||
Do_Call = None |
|||
Print_Directories = [] |
|||
Print_Entries = [] |
|||
Print_Flags = Flagger() |
|||
Verbose = 0 |
|||
Readable = 0 |
|||
|
|||
def default_mapper(entry, name): |
|||
try: |
|||
val = eval("entry."+name) |
|||
except: |
|||
val = None |
|||
return str(val) |
|||
|
|||
def map_action(entry, name): |
|||
try: |
|||
bact = entry.bact |
|||
bactsig = entry.bactsig |
|||
except AttributeError: |
|||
return None |
|||
return '%s [%s]' % (bactsig, bact) |
|||
|
|||
def map_timestamp(entry, name): |
|||
try: |
|||
timestamp = entry.timestamp |
|||
except AttributeError: |
|||
timestamp = None |
|||
if Readable and timestamp: |
|||
return "'" + time.ctime(timestamp) + "'" |
|||
else: |
|||
return str(timestamp) |
|||
|
|||
def map_bkids(entry, name): |
|||
try: |
|||
bkids = entry.bsources + entry.bdepends + entry.bimplicit |
|||
bkidsigs = entry.bsourcesigs + entry.bdependsigs + entry.bimplicitsigs |
|||
except AttributeError: |
|||
return None |
|||
result = [] |
|||
for i in xrange(len(bkids)): |
|||
result.append(nodeinfo_string(bkids[i], bkidsigs[i], " ")) |
|||
if result == []: |
|||
return None |
|||
return string.join(result, "\n ") |
|||
|
|||
map_field = { |
|||
'action' : map_action, |
|||
'timestamp' : map_timestamp, |
|||
'bkids' : map_bkids, |
|||
} |
|||
|
|||
map_name = { |
|||
'implicit' : 'bkids', |
|||
} |
|||
|
|||
def field(name, entry, verbose=Verbose): |
|||
if not Print_Flags[name]: |
|||
return None |
|||
fieldname = map_name.get(name, name) |
|||
mapper = map_field.get(fieldname, default_mapper) |
|||
val = mapper(entry, name) |
|||
if verbose: |
|||
val = name + ": " + val |
|||
return val |
|||
|
|||
def nodeinfo_raw(name, ninfo, prefix=""): |
|||
# This just formats the dictionary, which we would normally use str() |
|||
# to do, except that we want the keys sorted for deterministic output. |
|||
d = ninfo.__dict__ |
|||
try: |
|||
keys = ninfo.field_list + ['_version_id'] |
|||
except AttributeError: |
|||
keys = d.keys() |
|||
keys.sort() |
|||
l = [] |
|||
for k in keys: |
|||
l.append('%s: %s' % (repr(k), repr(d.get(k)))) |
|||
if '\n' in name: |
|||
name = repr(name) |
|||
return name + ': {' + string.join(l, ', ') + '}' |
|||
|
|||
def nodeinfo_cooked(name, ninfo, prefix=""): |
|||
try: |
|||
field_list = ninfo.field_list |
|||
except AttributeError: |
|||
field_list = [] |
|||
f = lambda x, ni=ninfo, v=Verbose: field(x, ni, v) |
|||
if '\n' in name: |
|||
name = repr(name) |
|||
outlist = [name+':'] + filter(None, map(f, field_list)) |
|||
if Verbose: |
|||
sep = '\n ' + prefix |
|||
else: |
|||
sep = ' ' |
|||
return string.join(outlist, sep) |
|||
|
|||
nodeinfo_string = nodeinfo_cooked |
|||
|
|||
def printfield(name, entry, prefix=""): |
|||
outlist = field("implicit", entry, 0) |
|||
if outlist: |
|||
if Verbose: |
|||
print " implicit:" |
|||
print " " + outlist |
|||
outact = field("action", entry, 0) |
|||
if outact: |
|||
if Verbose: |
|||
print " action: " + outact |
|||
else: |
|||
print " " + outact |
|||
|
|||
def printentries(entries, location): |
|||
if Print_Entries: |
|||
for name in Print_Entries: |
|||
try: |
|||
entry = entries[name] |
|||
except KeyError: |
|||
sys.stderr.write("sconsign: no entry `%s' in `%s'\n" % (name, location)) |
|||
else: |
|||
try: |
|||
ninfo = entry.ninfo |
|||
except AttributeError: |
|||
print name + ":" |
|||
else: |
|||
print nodeinfo_string(name, entry.ninfo) |
|||
printfield(name, entry.binfo) |
|||
else: |
|||
names = entries.keys() |
|||
names.sort() |
|||
for name in names: |
|||
entry = entries[name] |
|||
try: |
|||
ninfo = entry.ninfo |
|||
except AttributeError: |
|||
print name + ":" |
|||
else: |
|||
print nodeinfo_string(name, entry.ninfo) |
|||
printfield(name, entry.binfo) |
|||
|
|||
class Do_SConsignDB: |
|||
def __init__(self, dbm_name, dbm): |
|||
self.dbm_name = dbm_name |
|||
self.dbm = dbm |
|||
|
|||
def __call__(self, fname): |
|||
# The *dbm modules stick their own file suffixes on the names |
|||
# that are passed in. This is causes us to jump through some |
|||
# hoops here to be able to allow the user |
|||
try: |
|||
# Try opening the specified file name. Example: |
|||
# SPECIFIED OPENED BY self.dbm.open() |
|||
# --------- ------------------------- |
|||
# .sconsign => .sconsign.dblite |
|||
# .sconsign.dblite => .sconsign.dblite.dblite |
|||
db = self.dbm.open(fname, "r") |
|||
except (IOError, OSError), e: |
|||
print_e = e |
|||
try: |
|||
# That didn't work, so try opening the base name, |
|||
# so that if the actually passed in 'sconsign.dblite' |
|||
# (for example), the dbm module will put the suffix back |
|||
# on for us and open it anyway. |
|||
db = self.dbm.open(os.path.splitext(fname)[0], "r") |
|||
except (IOError, OSError): |
|||
# That didn't work either. See if the file name |
|||
# they specified just exists (independent of the dbm |
|||
# suffix-mangling). |
|||
try: |
|||
open(fname, "r") |
|||
except (IOError, OSError), e: |
|||
# Nope, that file doesn't even exist, so report that |
|||
# fact back. |
|||
print_e = e |
|||
sys.stderr.write("sconsign: %s\n" % (print_e)) |
|||
return |
|||
except KeyboardInterrupt: |
|||
raise |
|||
except cPickle.UnpicklingError: |
|||
sys.stderr.write("sconsign: ignoring invalid `%s' file `%s'\n" % (self.dbm_name, fname)) |
|||
return |
|||
except Exception, e: |
|||
sys.stderr.write("sconsign: ignoring invalid `%s' file `%s': %s\n" % (self.dbm_name, fname, e)) |
|||
return |
|||
|
|||
if Print_Directories: |
|||
for dir in Print_Directories: |
|||
try: |
|||
val = db[dir] |
|||
except KeyError: |
|||
sys.stderr.write("sconsign: no dir `%s' in `%s'\n" % (dir, args[0])) |
|||
else: |
|||
self.printentries(dir, val) |
|||
else: |
|||
keys = db.keys() |
|||
keys.sort() |
|||
for dir in keys: |
|||
self.printentries(dir, db[dir]) |
|||
|
|||
def printentries(self, dir, val): |
|||
print '=== ' + dir + ':' |
|||
printentries(cPickle.loads(val), dir) |
|||
|
|||
def Do_SConsignDir(name): |
|||
try: |
|||
fp = open(name, 'rb') |
|||
except (IOError, OSError), e: |
|||
sys.stderr.write("sconsign: %s\n" % (e)) |
|||
return |
|||
try: |
|||
sconsign = SCons.SConsign.Dir(fp) |
|||
except KeyboardInterrupt: |
|||
raise |
|||
except cPickle.UnpicklingError: |
|||
sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s'\n" % (name)) |
|||
return |
|||
except Exception, e: |
|||
sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s': %s\n" % (name, e)) |
|||
return |
|||
printentries(sconsign.entries, args[0]) |
|||
|
|||
############################################################################## |
|||
|
|||
import getopt |
|||
|
|||
helpstr = """\ |
|||
Usage: sconsign [OPTIONS] FILE [...] |
|||
Options: |
|||
-a, --act, --action Print build action information. |
|||
-c, --csig Print content signature information. |
|||
-d DIR, --dir=DIR Print only info about DIR. |
|||
-e ENTRY, --entry=ENTRY Print only info about ENTRY. |
|||
-f FORMAT, --format=FORMAT FILE is in the specified FORMAT. |
|||
-h, --help Print this message and exit. |
|||
-i, --implicit Print implicit dependency information. |
|||
-r, --readable Print timestamps in human-readable form. |
|||
--raw Print raw Python object representations. |
|||
-s, --size Print file sizes. |
|||
-t, --timestamp Print timestamp information. |
|||
-v, --verbose Verbose, describe each field. |
|||
""" |
|||
|
|||
opts, args = getopt.getopt(sys.argv[1:], "acd:e:f:hirstv", |
|||
['act', 'action', |
|||
'csig', 'dir=', 'entry=', |
|||
'format=', 'help', 'implicit', |
|||
'raw', 'readable', |
|||
'size', 'timestamp', 'verbose']) |
|||
|
|||
|
|||
for o, a in opts: |
|||
if o in ('-a', '--act', '--action'): |
|||
Print_Flags['action'] = 1 |
|||
elif o in ('-c', '--csig'): |
|||
Print_Flags['csig'] = 1 |
|||
elif o in ('-d', '--dir'): |
|||
Print_Directories.append(a) |
|||
elif o in ('-e', '--entry'): |
|||
Print_Entries.append(a) |
|||
elif o in ('-f', '--format'): |
|||
Module_Map = {'dblite' : 'SCons.dblite', |
|||
'sconsign' : None} |
|||
dbm_name = Module_Map.get(a, a) |
|||
if dbm_name: |
|||
try: |
|||
dbm = my_import(dbm_name) |
|||
except: |
|||
sys.stderr.write("sconsign: illegal file format `%s'\n" % a) |
|||
print helpstr |
|||
sys.exit(2) |
|||
Do_Call = Do_SConsignDB(a, dbm) |
|||
else: |
|||
Do_Call = Do_SConsignDir |
|||
elif o in ('-h', '--help'): |
|||
print helpstr |
|||
sys.exit(0) |
|||
elif o in ('-i', '--implicit'): |
|||
Print_Flags['implicit'] = 1 |
|||
elif o in ('--raw',): |
|||
nodeinfo_string = nodeinfo_raw |
|||
elif o in ('-r', '--readable'): |
|||
Readable = 1 |
|||
elif o in ('-s', '--size'): |
|||
Print_Flags['size'] = 1 |
|||
elif o in ('-t', '--timestamp'): |
|||
Print_Flags['timestamp'] = 1 |
|||
elif o in ('-v', '--verbose'): |
|||
Verbose = 1 |
|||
|
|||
if Do_Call: |
|||
for a in args: |
|||
Do_Call(a) |
|||
else: |
|||
for a in args: |
|||
dbm_name = whichdb.whichdb(a) |
|||
if dbm_name: |
|||
Map_Module = {'SCons.dblite' : 'dblite'} |
|||
dbm = my_import(dbm_name) |
|||
Do_SConsignDB(Map_Module.get(dbm_name, dbm_name), dbm)(a) |
|||
else: |
|||
Do_SConsignDir(a) |
|||
|
|||
sys.exit(0) |
@ -0,0 +1,183 @@ |
|||
# Copyright 2008 the V8 project authors. All rights reserved. |
|||
# Redistribution and use in source and binary forms, with or without |
|||
# modification, are permitted provided that the following conditions are |
|||
# met: |
|||
# |
|||
# * Redistributions of source code must retain the above copyright |
|||
# notice, this list of conditions and the following disclaimer. |
|||
# * Redistributions in binary form must reproduce the above |
|||
# copyright notice, this list of conditions and the following |
|||
# disclaimer in the documentation and/or other materials provided |
|||
# with the distribution. |
|||
# * Neither the name of Google Inc. nor the names of its |
|||
# contributors may be used to endorse or promote products derived |
|||
# from this software without specific prior written permission. |
|||
# |
|||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
|
|||
import sys |
|||
from os.path import join, dirname, abspath |
|||
root_dir = dirname(File('SConstruct').rfile().abspath) |
|||
sys.path.append(join(root_dir, 'tools')) |
|||
import js2c |
|||
Import('context') |
|||
|
|||
|
|||
SOURCES = { |
|||
'all': [ |
|||
'accessors.cc', 'allocation.cc', 'api.cc', 'assembler.cc', 'ast.cc', |
|||
'bootstrapper.cc', 'builtins.cc', 'checks.cc', 'code-stubs.cc', |
|||
'codegen.cc', 'compilation-cache.cc', 'compiler.cc', 'contexts.cc', |
|||
'conversions.cc', 'counters.cc', 'dateparser.cc', 'debug.cc', |
|||
'debug-agent.cc', 'disassembler.cc', 'execution.cc', 'factory.cc', |
|||
'flags.cc', 'frames.cc', 'func-name-inferrer.cc', |
|||
'global-handles.cc', 'handles.cc', 'hashmap.cc', |
|||
'heap.cc', 'ic.cc', 'interpreter-irregexp.cc', 'jsregexp.cc', |
|||
'jump-target.cc', 'log.cc', 'mark-compact.cc', 'messages.cc', 'objects.cc', |
|||
'oprofile-agent.cc', 'parser.cc', 'property.cc', 'regexp-macro-assembler.cc', |
|||
'regexp-macro-assembler-irregexp.cc', 'regexp-stack.cc', |
|||
'register-allocator.cc', 'rewriter.cc', 'runtime.cc', 'scanner.cc', |
|||
'scopeinfo.cc', 'scopes.cc', 'serialize.cc', 'snapshot-common.cc', |
|||
'spaces.cc', 'string-stream.cc', 'stub-cache.cc', 'token.cc', 'top.cc', |
|||
'unicode.cc', 'usage-analyzer.cc', 'utils.cc', 'v8-counters.cc', |
|||
'v8.cc', 'v8threads.cc', 'variables.cc', 'virtual-frame.cc', 'zone.cc' |
|||
], |
|||
'arch:arm': [ |
|||
'assembler-arm.cc', 'builtins-arm.cc', 'codegen-arm.cc', 'cpu-arm.cc', |
|||
'disasm-arm.cc', 'debug-arm.cc', 'frames-arm.cc', 'ic-arm.cc', |
|||
'jump-target-arm.cc', 'macro-assembler-arm.cc', |
|||
'regexp-macro-assembler-arm.cc', 'register-allocator-arm.cc', |
|||
'stub-cache-arm.cc', 'virtual-frame-arm.cc' |
|||
], |
|||
'arch:ia32': [ |
|||
'assembler-ia32.cc', 'builtins-ia32.cc', 'codegen-ia32.cc', |
|||
'cpu-ia32.cc', 'disasm-ia32.cc', 'debug-ia32.cc', 'frames-ia32.cc', |
|||
'ic-ia32.cc', 'jump-target-ia32.cc', 'macro-assembler-ia32.cc', |
|||
'regexp-macro-assembler-ia32.cc', 'register-allocator-ia32.cc', |
|||
'stub-cache-ia32.cc', 'virtual-frame-ia32.cc' |
|||
], |
|||
'simulator:arm': ['simulator-arm.cc'], |
|||
'os:freebsd': ['platform-freebsd.cc', 'platform-posix.cc'], |
|||
'os:linux': ['platform-linux.cc', 'platform-posix.cc'], |
|||
'os:android': ['platform-linux.cc', 'platform-posix.cc'], |
|||
'os:macos': ['platform-macos.cc', 'platform-posix.cc'], |
|||
'os:nullos': ['platform-nullos.cc'], |
|||
'os:win32': ['platform-win32.cc'], |
|||
'mode:release': [], |
|||
'mode:debug': [ |
|||
'objects-debug.cc', 'prettyprinter.cc', 'regexp-macro-assembler-tracer.cc' |
|||
] |
|||
} |
|||
|
|||
|
|||
D8_FILES = { |
|||
'all': [ |
|||
'd8.cc', 'd8-debug.cc' |
|||
], |
|||
'os:linux': [ |
|||
'd8-posix.cc' |
|||
], |
|||
'os:macos': [ |
|||
'd8-posix.cc' |
|||
], |
|||
'os:android': [ |
|||
'd8-posix.cc' |
|||
], |
|||
'os:freebsd': [ |
|||
'd8-posix.cc' |
|||
], |
|||
'os:win32': [ |
|||
'd8-windows.cc' |
|||
], |
|||
'os:nullos': [ |
|||
'd8-windows.cc' # Empty implementation at the moment. |
|||
], |
|||
'console:readline': [ |
|||
'd8-readline.cc' |
|||
] |
|||
} |
|||
|
|||
|
|||
LIBRARY_FILES = ''' |
|||
runtime.js |
|||
v8natives.js |
|||
array.js |
|||
string.js |
|||
uri.js |
|||
math.js |
|||
messages.js |
|||
apinatives.js |
|||
debug-delay.js |
|||
mirror-delay.js |
|||
date-delay.js |
|||
regexp-delay.js |
|||
'''.split() |
|||
|
|||
|
|||
def Abort(message): |
|||
print message |
|||
sys.exit(1) |
|||
|
|||
|
|||
def ConfigureObjectFiles(): |
|||
env = Environment() |
|||
env.Replace(**context.flags['v8']) |
|||
context.ApplyEnvOverrides(env) |
|||
env['BUILDERS']['JS2C'] = Builder(action=js2c.JS2C) |
|||
env['BUILDERS']['Snapshot'] = Builder(action='$SOURCE $TARGET --logfile "$LOGFILE"') |
|||
|
|||
# Build the standard platform-independent source files. |
|||
source_files = context.GetRelevantSources(SOURCES) |
|||
|
|||
d8_files = context.GetRelevantSources(D8_FILES) |
|||
d8_js = env.JS2C('d8-js.cc', 'd8.js', TYPE='D8') |
|||
d8_js_obj = context.ConfigureObject(env, d8_js, CPPPATH=['.']) |
|||
d8_objs = [context.ConfigureObject(env, [d8_files]), d8_js_obj] |
|||
|
|||
# Combine the JavaScript library files into a single C++ file and |
|||
# compile it. |
|||
library_files = [s for s in LIBRARY_FILES] |
|||
library_files.append('macros.py') |
|||
libraries_src, libraries_empty_src = env.JS2C(['libraries.cc', 'libraries-empty.cc'], library_files, TYPE='CORE') |
|||
libraries_obj = context.ConfigureObject(env, libraries_src, CPPPATH=['.']) |
|||
|
|||
# Build dtoa. |
|||
dtoa_env = env.Copy() |
|||
dtoa_env.Replace(**context.flags['dtoa']) |
|||
dtoa_files = ['dtoa-config.c'] |
|||
dtoa_obj = context.ConfigureObject(dtoa_env, dtoa_files) |
|||
|
|||
source_objs = context.ConfigureObject(env, source_files) |
|||
non_snapshot_files = [dtoa_obj, source_objs] |
|||
|
|||
# Create snapshot if necessary. |
|||
empty_snapshot_obj = context.ConfigureObject(env, 'snapshot-empty.cc') |
|||
mksnapshot_env = env.Copy() |
|||
mksnapshot_env.Replace(**context.flags['mksnapshot']) |
|||
mksnapshot_src = 'mksnapshot.cc' |
|||
mksnapshot = mksnapshot_env.Program('mksnapshot', [mksnapshot_src, libraries_obj, non_snapshot_files, empty_snapshot_obj], PDB='mksnapshot.exe.pdb') |
|||
if context.use_snapshot: |
|||
if context.build_snapshot: |
|||
snapshot_cc = env.Snapshot('snapshot.cc', mksnapshot, LOGFILE=File('snapshot.log').abspath) |
|||
else: |
|||
snapshot_cc = Command('snapshot.cc', [], []) |
|||
snapshot_obj = context.ConfigureObject(env, snapshot_cc, CPPPATH=['.']) |
|||
libraries_obj = context.ConfigureObject(env, libraries_empty_src, CPPPATH=['.']) |
|||
else: |
|||
snapshot_obj = empty_snapshot_obj |
|||
library_objs = [non_snapshot_files, libraries_obj, snapshot_obj] |
|||
return (library_objs, d8_objs, [mksnapshot]) |
|||
|
|||
|
|||
(library_objs, d8_objs, mksnapshot) = ConfigureObjectFiles() |
|||
Return('library_objs d8_objs mksnapshot') |
@ -0,0 +1,548 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#include "v8.h" |
|||
|
|||
#include "accessors.h" |
|||
#include "execution.h" |
|||
#include "factory.h" |
|||
#include "scopeinfo.h" |
|||
#include "top.h" |
|||
#include "zone-inl.h" |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
|
|||
template <class C> |
|||
static C* FindInPrototypeChain(Object* obj, bool* found_it) { |
|||
ASSERT(!*found_it); |
|||
while (!Is<C>(obj)) { |
|||
if (obj == Heap::null_value()) return NULL; |
|||
obj = obj->GetPrototype(); |
|||
} |
|||
*found_it = true; |
|||
return C::cast(obj); |
|||
} |
|||
|
|||
|
|||
// Entry point that never should be called.
|
|||
Object* Accessors::IllegalSetter(JSObject*, Object*, void*) { |
|||
UNREACHABLE(); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
Object* Accessors::IllegalGetAccessor(Object* object, void*) { |
|||
UNREACHABLE(); |
|||
return object; |
|||
} |
|||
|
|||
|
|||
Object* Accessors::ReadOnlySetAccessor(JSObject*, Object* value, void*) { |
|||
// According to ECMA-262, section 8.6.2.2, page 28, setting
|
|||
// read-only properties must be silently ignored.
|
|||
return value; |
|||
} |
|||
|
|||
|
|||
//
|
|||
// Accessors::ArrayLength
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::ArrayGetLength(Object* object, void*) { |
|||
// Traverse the prototype chain until we reach an array.
|
|||
bool found_it = false; |
|||
JSArray* holder = FindInPrototypeChain<JSArray>(object, &found_it); |
|||
if (!found_it) return Smi::FromInt(0); |
|||
return holder->length(); |
|||
} |
|||
|
|||
|
|||
// The helper function will 'flatten' Number objects.
|
|||
Object* Accessors::FlattenNumber(Object* value) { |
|||
if (value->IsNumber() || !value->IsJSValue()) return value; |
|||
JSValue* wrapper = JSValue::cast(value); |
|||
ASSERT( |
|||
Top::context()->global_context()->number_function()->has_initial_map()); |
|||
Map* number_map = |
|||
Top::context()->global_context()->number_function()->initial_map(); |
|||
if (wrapper->map() == number_map) return wrapper->value(); |
|||
return value; |
|||
} |
|||
|
|||
|
|||
Object* Accessors::ArraySetLength(JSObject* object, Object* value, void*) { |
|||
value = FlattenNumber(value); |
|||
|
|||
// Need to call methods that may trigger GC.
|
|||
HandleScope scope; |
|||
|
|||
// Protect raw pointers.
|
|||
Handle<JSObject> object_handle(object); |
|||
Handle<Object> value_handle(value); |
|||
|
|||
bool has_exception; |
|||
Handle<Object> uint32_v = Execution::ToUint32(value_handle, &has_exception); |
|||
if (has_exception) return Failure::Exception(); |
|||
Handle<Object> number_v = Execution::ToNumber(value_handle, &has_exception); |
|||
if (has_exception) return Failure::Exception(); |
|||
|
|||
// Restore raw pointers,
|
|||
object = *object_handle; |
|||
value = *value_handle; |
|||
|
|||
if (uint32_v->Number() == number_v->Number()) { |
|||
if (object->IsJSArray()) { |
|||
return JSArray::cast(object)->SetElementsLength(*uint32_v); |
|||
} else { |
|||
// This means one of the object's prototypes is a JSArray and
|
|||
// the object does not have a 'length' property.
|
|||
// Calling SetProperty causes an infinite loop.
|
|||
return object->IgnoreAttributesAndSetLocalProperty(Heap::length_symbol(), |
|||
value, NONE); |
|||
} |
|||
} |
|||
return Top::Throw(*Factory::NewRangeError("invalid_array_length", |
|||
HandleVector<Object>(NULL, 0))); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::ArrayLength = { |
|||
ArrayGetLength, |
|||
ArraySetLength, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::StringLength
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::StringGetLength(Object* object, void*) { |
|||
Object* value = object; |
|||
if (object->IsJSValue()) value = JSValue::cast(object)->value(); |
|||
if (value->IsString()) return Smi::FromInt(String::cast(value)->length()); |
|||
// If object is not a string we return 0 to be compatible with WebKit.
|
|||
// Note: Firefox returns the length of ToString(object).
|
|||
return Smi::FromInt(0); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::StringLength = { |
|||
StringGetLength, |
|||
IllegalSetter, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::ScriptSource
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::ScriptGetSource(Object* object, void*) { |
|||
Object* script = JSValue::cast(object)->value(); |
|||
return Script::cast(script)->source(); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::ScriptSource = { |
|||
ScriptGetSource, |
|||
IllegalSetter, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::ScriptName
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::ScriptGetName(Object* object, void*) { |
|||
Object* script = JSValue::cast(object)->value(); |
|||
return Script::cast(script)->name(); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::ScriptName = { |
|||
ScriptGetName, |
|||
IllegalSetter, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::ScriptId
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::ScriptGetId(Object* object, void*) { |
|||
Object* script = JSValue::cast(object)->value(); |
|||
return Script::cast(script)->id(); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::ScriptId = { |
|||
ScriptGetId, |
|||
IllegalSetter, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::ScriptLineOffset
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::ScriptGetLineOffset(Object* object, void*) { |
|||
Object* script = JSValue::cast(object)->value(); |
|||
return Script::cast(script)->line_offset(); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::ScriptLineOffset = { |
|||
ScriptGetLineOffset, |
|||
IllegalSetter, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::ScriptColumnOffset
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::ScriptGetColumnOffset(Object* object, void*) { |
|||
Object* script = JSValue::cast(object)->value(); |
|||
return Script::cast(script)->column_offset(); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::ScriptColumnOffset = { |
|||
ScriptGetColumnOffset, |
|||
IllegalSetter, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::ScriptType
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::ScriptGetType(Object* object, void*) { |
|||
Object* script = JSValue::cast(object)->value(); |
|||
return Script::cast(script)->type(); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::ScriptType = { |
|||
ScriptGetType, |
|||
IllegalSetter, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::ScriptGetLineEnds
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::ScriptGetLineEnds(Object* object, void*) { |
|||
HandleScope scope; |
|||
Handle<Script> script(Script::cast(JSValue::cast(object)->value())); |
|||
InitScriptLineEnds(script); |
|||
return script->line_ends(); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::ScriptLineEnds = { |
|||
ScriptGetLineEnds, |
|||
IllegalSetter, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::FunctionPrototype
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::FunctionGetPrototype(Object* object, void*) { |
|||
bool found_it = false; |
|||
JSFunction* function = FindInPrototypeChain<JSFunction>(object, &found_it); |
|||
if (!found_it) return Heap::undefined_value(); |
|||
if (!function->has_prototype()) { |
|||
Object* prototype = Heap::AllocateFunctionPrototype(function); |
|||
if (prototype->IsFailure()) return prototype; |
|||
Object* result = function->SetPrototype(prototype); |
|||
if (result->IsFailure()) return result; |
|||
} |
|||
return function->prototype(); |
|||
} |
|||
|
|||
|
|||
Object* Accessors::FunctionSetPrototype(JSObject* object, |
|||
Object* value, |
|||
void*) { |
|||
bool found_it = false; |
|||
JSFunction* function = FindInPrototypeChain<JSFunction>(object, &found_it); |
|||
if (!found_it) return Heap::undefined_value(); |
|||
if (function->has_initial_map()) { |
|||
// If the function has allocated the initial map
|
|||
// replace it with a copy containing the new prototype.
|
|||
Object* new_map = function->initial_map()->CopyDropTransitions(); |
|||
if (new_map->IsFailure()) return new_map; |
|||
function->set_initial_map(Map::cast(new_map)); |
|||
} |
|||
Object* prototype = function->SetPrototype(value); |
|||
if (prototype->IsFailure()) return prototype; |
|||
ASSERT(function->prototype() == value); |
|||
return function; |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::FunctionPrototype = { |
|||
FunctionGetPrototype, |
|||
FunctionSetPrototype, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::FunctionLength
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::FunctionGetLength(Object* object, void*) { |
|||
bool found_it = false; |
|||
JSFunction* function = FindInPrototypeChain<JSFunction>(object, &found_it); |
|||
if (!found_it) return Smi::FromInt(0); |
|||
// Check if already compiled.
|
|||
if (!function->is_compiled()) { |
|||
// If the function isn't compiled yet, the length is not computed
|
|||
// correctly yet. Compile it now and return the right length.
|
|||
HandleScope scope; |
|||
Handle<JSFunction> function_handle(function); |
|||
if (!CompileLazy(function_handle, KEEP_EXCEPTION)) { |
|||
return Failure::Exception(); |
|||
} |
|||
return Smi::FromInt(function_handle->shared()->length()); |
|||
} else { |
|||
return Smi::FromInt(function->shared()->length()); |
|||
} |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::FunctionLength = { |
|||
FunctionGetLength, |
|||
ReadOnlySetAccessor, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::FunctionName
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::FunctionGetName(Object* object, void*) { |
|||
bool found_it = false; |
|||
JSFunction* holder = FindInPrototypeChain<JSFunction>(object, &found_it); |
|||
if (!found_it) return Heap::undefined_value(); |
|||
return holder->shared()->name(); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::FunctionName = { |
|||
FunctionGetName, |
|||
ReadOnlySetAccessor, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::FunctionArguments
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::FunctionGetArguments(Object* object, void*) { |
|||
HandleScope scope; |
|||
bool found_it = false; |
|||
JSFunction* holder = FindInPrototypeChain<JSFunction>(object, &found_it); |
|||
if (!found_it) return Heap::undefined_value(); |
|||
Handle<JSFunction> function(holder); |
|||
|
|||
// Find the top invocation of the function by traversing frames.
|
|||
for (JavaScriptFrameIterator it; !it.done(); it.Advance()) { |
|||
// Skip all frames that aren't invocations of the given function.
|
|||
JavaScriptFrame* frame = it.frame(); |
|||
if (frame->function() != *function) continue; |
|||
|
|||
// If there is an arguments variable in the stack, we return that.
|
|||
int index = ScopeInfo<>::StackSlotIndex(frame->code(), |
|||
Heap::arguments_symbol()); |
|||
if (index >= 0) return frame->GetExpression(index); |
|||
|
|||
// If there isn't an arguments variable in the stack, we need to
|
|||
// find the frame that holds the actual arguments passed to the
|
|||
// function on the stack.
|
|||
it.AdvanceToArgumentsFrame(); |
|||
frame = it.frame(); |
|||
|
|||
// Get the number of arguments and construct an arguments object
|
|||
// mirror for the right frame.
|
|||
const int length = frame->GetProvidedParametersCount(); |
|||
Handle<JSObject> arguments = Factory::NewArgumentsObject(function, length); |
|||
Handle<FixedArray> array = Factory::NewFixedArray(length); |
|||
|
|||
// Copy the parameters to the arguments object.
|
|||
ASSERT(array->length() == length); |
|||
for (int i = 0; i < length; i++) array->set(i, frame->GetParameter(i)); |
|||
arguments->set_elements(*array); |
|||
|
|||
// Return the freshly allocated arguments object.
|
|||
return *arguments; |
|||
} |
|||
|
|||
// No frame corresponding to the given function found. Return null.
|
|||
return Heap::null_value(); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::FunctionArguments = { |
|||
FunctionGetArguments, |
|||
ReadOnlySetAccessor, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::FunctionCaller
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::FunctionGetCaller(Object* object, void*) { |
|||
HandleScope scope; |
|||
bool found_it = false; |
|||
JSFunction* holder = FindInPrototypeChain<JSFunction>(object, &found_it); |
|||
if (!found_it) return Heap::undefined_value(); |
|||
Handle<JSFunction> function(holder); |
|||
|
|||
// Find the top invocation of the function by traversing frames.
|
|||
for (JavaScriptFrameIterator it; !it.done(); it.Advance()) { |
|||
// Skip all frames that aren't invocations of the given function.
|
|||
if (it.frame()->function() != *function) continue; |
|||
// Once we have found the frame, we need to go to the caller
|
|||
// frame. This may require skipping through a number of top-level
|
|||
// frames, e.g. frames for scripts not functions.
|
|||
while (true) { |
|||
it.Advance(); |
|||
if (it.done()) return Heap::null_value(); |
|||
JSFunction* caller = JSFunction::cast(it.frame()->function()); |
|||
if (!caller->shared()->is_toplevel()) return caller; |
|||
} |
|||
} |
|||
|
|||
// No frame corresponding to the given function found. Return null.
|
|||
return Heap::null_value(); |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::FunctionCaller = { |
|||
FunctionGetCaller, |
|||
ReadOnlySetAccessor, |
|||
0 |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// Accessors::ObjectPrototype
|
|||
//
|
|||
|
|||
|
|||
Object* Accessors::ObjectGetPrototype(Object* receiver, void*) { |
|||
Object* current = receiver->GetPrototype(); |
|||
while (current->IsJSObject() && |
|||
JSObject::cast(current)->map()->is_hidden_prototype()) { |
|||
current = current->GetPrototype(); |
|||
} |
|||
return current; |
|||
} |
|||
|
|||
|
|||
Object* Accessors::ObjectSetPrototype(JSObject* receiver, |
|||
Object* value, |
|||
void*) { |
|||
// Before we can set the prototype we need to be sure
|
|||
// prototype cycles are prevented.
|
|||
// It is sufficient to validate that the receiver is not in the new prototype
|
|||
// chain.
|
|||
|
|||
// Silently ignore the change if value is not a JSObject or null.
|
|||
// SpiderMonkey behaves this way.
|
|||
if (!value->IsJSObject() && !value->IsNull()) return value; |
|||
|
|||
for (Object* pt = value; pt != Heap::null_value(); pt = pt->GetPrototype()) { |
|||
if (JSObject::cast(pt) == receiver) { |
|||
// Cycle detected.
|
|||
HandleScope scope; |
|||
return Top::Throw(*Factory::NewError("cyclic_proto", |
|||
HandleVector<Object>(NULL, 0))); |
|||
} |
|||
} |
|||
|
|||
// Find the first object in the chain whose prototype object is not
|
|||
// hidden and set the new prototype on that object.
|
|||
JSObject* current = receiver; |
|||
Object* current_proto = receiver->GetPrototype(); |
|||
while (current_proto->IsJSObject() && |
|||
JSObject::cast(current_proto)->map()->is_hidden_prototype()) { |
|||
current = JSObject::cast(current_proto); |
|||
current_proto = current_proto->GetPrototype(); |
|||
} |
|||
|
|||
// Set the new prototype of the object.
|
|||
Object* new_map = current->map()->CopyDropTransitions(); |
|||
if (new_map->IsFailure()) return new_map; |
|||
Map::cast(new_map)->set_prototype(value); |
|||
current->set_map(Map::cast(new_map)); |
|||
|
|||
// To be consistent with other Set functions, return the value.
|
|||
return value; |
|||
} |
|||
|
|||
|
|||
const AccessorDescriptor Accessors::ObjectPrototype = { |
|||
ObjectGetPrototype, |
|||
ObjectSetPrototype, |
|||
0 |
|||
}; |
|||
|
|||
} } // namespace v8::internal
|
@ -0,0 +1,101 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#ifndef V8_ACCESSORS_H_ |
|||
#define V8_ACCESSORS_H_ |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
// The list of accessor descriptors. This is a second-order macro
|
|||
// taking a macro to be applied to all accessor descriptor names.
|
|||
#define ACCESSOR_DESCRIPTOR_LIST(V) \ |
|||
V(FunctionPrototype) \ |
|||
V(FunctionLength) \ |
|||
V(FunctionName) \ |
|||
V(FunctionArguments) \ |
|||
V(FunctionCaller) \ |
|||
V(ArrayLength) \ |
|||
V(StringLength) \ |
|||
V(ScriptSource) \ |
|||
V(ScriptName) \ |
|||
V(ScriptId) \ |
|||
V(ScriptLineOffset) \ |
|||
V(ScriptColumnOffset) \ |
|||
V(ScriptType) \ |
|||
V(ScriptLineEnds) \ |
|||
V(ObjectPrototype) |
|||
|
|||
// Accessors contains all predefined proxy accessors.
|
|||
|
|||
class Accessors : public AllStatic { |
|||
public: |
|||
// Accessor descriptors.
|
|||
#define ACCESSOR_DESCRIPTOR_DECLARATION(name) \ |
|||
static const AccessorDescriptor name; |
|||
ACCESSOR_DESCRIPTOR_LIST(ACCESSOR_DESCRIPTOR_DECLARATION) |
|||
#undef ACCESSOR_DESCRIPTOR_DECLARATION |
|||
|
|||
enum DescriptorId { |
|||
#define ACCESSOR_DESCRIPTOR_DECLARATION(name) \ |
|||
k##name, |
|||
ACCESSOR_DESCRIPTOR_LIST(ACCESSOR_DESCRIPTOR_DECLARATION) |
|||
#undef ACCESSOR_DESCRIPTOR_DECLARATION |
|||
descriptorCount |
|||
}; |
|||
|
|||
// Accessor functions called directly from the runtime system.
|
|||
static Object* FunctionGetPrototype(Object* object, void*); |
|||
static Object* FunctionSetPrototype(JSObject* object, Object* value, void*); |
|||
private: |
|||
// Accessor functions only used through the descriptor.
|
|||
static Object* FunctionGetLength(Object* object, void*); |
|||
static Object* FunctionGetName(Object* object, void*); |
|||
static Object* FunctionGetArguments(Object* object, void*); |
|||
static Object* FunctionGetCaller(Object* object, void*); |
|||
static Object* ArraySetLength(JSObject* object, Object* value, void*); |
|||
static Object* ArrayGetLength(Object* object, void*); |
|||
static Object* StringGetLength(Object* object, void*); |
|||
static Object* ScriptGetName(Object* object, void*); |
|||
static Object* ScriptGetId(Object* object, void*); |
|||
static Object* ScriptGetSource(Object* object, void*); |
|||
static Object* ScriptGetLineOffset(Object* object, void*); |
|||
static Object* ScriptGetColumnOffset(Object* object, void*); |
|||
static Object* ScriptGetType(Object* object, void*); |
|||
static Object* ScriptGetLineEnds(Object* object, void*); |
|||
static Object* ObjectGetPrototype(Object* receiver, void*); |
|||
static Object* ObjectSetPrototype(JSObject* receiver, Object* value, void*); |
|||
|
|||
// Helper functions.
|
|||
static Object* FlattenNumber(Object* value); |
|||
static Object* IllegalSetter(JSObject*, Object*, void*); |
|||
static Object* IllegalGetAccessor(Object* object, void*); |
|||
static Object* ReadOnlySetAccessor(JSObject*, Object* value, void*); |
|||
}; |
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_ACCESSORS_H_
|
@ -0,0 +1,197 @@ |
|||
// Copyright 2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#include <stdlib.h> |
|||
|
|||
#include "v8.h" |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
|
|||
void* Malloced::New(size_t size) { |
|||
ASSERT(NativeAllocationChecker::allocation_allowed()); |
|||
void* result = malloc(size); |
|||
if (result == NULL) V8::FatalProcessOutOfMemory("Malloced operator new"); |
|||
return result; |
|||
} |
|||
|
|||
|
|||
void Malloced::Delete(void* p) { |
|||
free(p); |
|||
} |
|||
|
|||
|
|||
void Malloced::FatalProcessOutOfMemory() { |
|||
V8::FatalProcessOutOfMemory("Out of memory"); |
|||
} |
|||
|
|||
|
|||
#ifdef DEBUG |
|||
|
|||
static void* invalid = static_cast<void*>(NULL); |
|||
|
|||
void* Embedded::operator new(size_t size) { |
|||
UNREACHABLE(); |
|||
return invalid; |
|||
} |
|||
|
|||
|
|||
void Embedded::operator delete(void* p) { |
|||
UNREACHABLE(); |
|||
} |
|||
|
|||
|
|||
void* AllStatic::operator new(size_t size) { |
|||
UNREACHABLE(); |
|||
return invalid; |
|||
} |
|||
|
|||
|
|||
void AllStatic::operator delete(void* p) { |
|||
UNREACHABLE(); |
|||
} |
|||
|
|||
#endif |
|||
|
|||
|
|||
char* StrDup(const char* str) { |
|||
int length = strlen(str); |
|||
char* result = NewArray<char>(length + 1); |
|||
memcpy(result, str, length * kCharSize); |
|||
result[length] = '\0'; |
|||
return result; |
|||
} |
|||
|
|||
|
|||
char* StrNDup(const char* str, size_t n) { |
|||
size_t length = strlen(str); |
|||
if (n < length) length = n; |
|||
char* result = NewArray<char>(length + 1); |
|||
memcpy(result, str, length * kCharSize); |
|||
result[length] = '\0'; |
|||
return result; |
|||
} |
|||
|
|||
|
|||
int NativeAllocationChecker::allocation_disallowed_ = 0; |
|||
|
|||
|
|||
PreallocatedStorage PreallocatedStorage::in_use_list_(0); |
|||
PreallocatedStorage PreallocatedStorage::free_list_(0); |
|||
bool PreallocatedStorage::preallocated_ = false; |
|||
|
|||
|
|||
void PreallocatedStorage::Init(size_t size) { |
|||
ASSERT(free_list_.next_ == &free_list_); |
|||
ASSERT(free_list_.previous_ == &free_list_); |
|||
PreallocatedStorage* free_chunk = |
|||
reinterpret_cast<PreallocatedStorage*>(new char[size]); |
|||
free_list_.next_ = free_list_.previous_ = free_chunk; |
|||
free_chunk->next_ = free_chunk->previous_ = &free_list_; |
|||
free_chunk->size_ = size - sizeof(PreallocatedStorage); |
|||
preallocated_ = true; |
|||
} |
|||
|
|||
|
|||
void* PreallocatedStorage::New(size_t size) { |
|||
if (!preallocated_) { |
|||
return FreeStoreAllocationPolicy::New(size); |
|||
} |
|||
ASSERT(free_list_.next_ != &free_list_); |
|||
ASSERT(free_list_.previous_ != &free_list_); |
|||
size = (size + kPointerSize - 1) & ~(kPointerSize - 1); |
|||
// Search for exact fit.
|
|||
for (PreallocatedStorage* storage = free_list_.next_; |
|||
storage != &free_list_; |
|||
storage = storage->next_) { |
|||
if (storage->size_ == size) { |
|||
storage->Unlink(); |
|||
storage->LinkTo(&in_use_list_); |
|||
return reinterpret_cast<void*>(storage + 1); |
|||
} |
|||
} |
|||
// Search for first fit.
|
|||
for (PreallocatedStorage* storage = free_list_.next_; |
|||
storage != &free_list_; |
|||
storage = storage->next_) { |
|||
if (storage->size_ >= size + sizeof(PreallocatedStorage)) { |
|||
storage->Unlink(); |
|||
storage->LinkTo(&in_use_list_); |
|||
PreallocatedStorage* left_over = |
|||
reinterpret_cast<PreallocatedStorage*>( |
|||
reinterpret_cast<char*>(storage + 1) + size); |
|||
left_over->size_ = storage->size_ - size - sizeof(PreallocatedStorage); |
|||
ASSERT(size + left_over->size_ + sizeof(PreallocatedStorage) == |
|||
storage->size_); |
|||
storage->size_ = size; |
|||
left_over->LinkTo(&free_list_); |
|||
return reinterpret_cast<void*>(storage + 1); |
|||
} |
|||
} |
|||
// Allocation failure.
|
|||
ASSERT(false); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
// We don't attempt to coalesce.
|
|||
void PreallocatedStorage::Delete(void* p) { |
|||
if (p == NULL) { |
|||
return; |
|||
} |
|||
if (!preallocated_) { |
|||
FreeStoreAllocationPolicy::Delete(p); |
|||
return; |
|||
} |
|||
PreallocatedStorage* storage = reinterpret_cast<PreallocatedStorage*>(p) - 1; |
|||
ASSERT(storage->next_->previous_ == storage); |
|||
ASSERT(storage->previous_->next_ == storage); |
|||
storage->Unlink(); |
|||
storage->LinkTo(&free_list_); |
|||
} |
|||
|
|||
|
|||
void PreallocatedStorage::LinkTo(PreallocatedStorage* other) { |
|||
next_ = other->next_; |
|||
other->next_->previous_ = this; |
|||
previous_ = other; |
|||
other->next_ = this; |
|||
} |
|||
|
|||
|
|||
void PreallocatedStorage::Unlink() { |
|||
next_->previous_ = previous_; |
|||
previous_->next_ = next_; |
|||
} |
|||
|
|||
|
|||
PreallocatedStorage::PreallocatedStorage(size_t size) |
|||
: size_(size) { |
|||
previous_ = next_ = this; |
|||
} |
|||
|
|||
} } // namespace v8::internal
|
@ -0,0 +1,168 @@ |
|||
// Copyright 2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#ifndef V8_ALLOCATION_H_ |
|||
#define V8_ALLOCATION_H_ |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
|
|||
// A class that controls whether allocation is allowed. This is for
|
|||
// the C++ heap only!
|
|||
class NativeAllocationChecker { |
|||
public: |
|||
typedef enum { ALLOW, DISALLOW } NativeAllocationAllowed; |
|||
explicit inline NativeAllocationChecker(NativeAllocationAllowed allowed) |
|||
: allowed_(allowed) { |
|||
#ifdef DEBUG |
|||
if (allowed == DISALLOW) { |
|||
allocation_disallowed_++; |
|||
} |
|||
#endif |
|||
} |
|||
~NativeAllocationChecker() { |
|||
#ifdef DEBUG |
|||
if (allowed_ == DISALLOW) { |
|||
allocation_disallowed_--; |
|||
} |
|||
#endif |
|||
ASSERT(allocation_disallowed_ >= 0); |
|||
} |
|||
static inline bool allocation_allowed() { |
|||
return allocation_disallowed_ == 0; |
|||
} |
|||
private: |
|||
// This static counter ensures that NativeAllocationCheckers can be nested.
|
|||
static int allocation_disallowed_; |
|||
// This flag applies to this particular instance.
|
|||
NativeAllocationAllowed allowed_; |
|||
}; |
|||
|
|||
|
|||
// Superclass for classes managed with new & delete.
|
|||
class Malloced { |
|||
public: |
|||
void* operator new(size_t size) { return New(size); } |
|||
void operator delete(void* p) { Delete(p); } |
|||
|
|||
static void FatalProcessOutOfMemory(); |
|||
static void* New(size_t size); |
|||
static void Delete(void* p); |
|||
}; |
|||
|
|||
|
|||
// A macro is used for defining the base class used for embedded instances.
|
|||
// The reason is some compilers allocate a minimum of one word for the
|
|||
// superclass. The macro prevents the use of new & delete in debug mode.
|
|||
// In release mode we are not willing to pay this overhead.
|
|||
|
|||
#ifdef DEBUG |
|||
// Superclass for classes with instances allocated inside stack
|
|||
// activations or inside other objects.
|
|||
class Embedded { |
|||
public: |
|||
void* operator new(size_t size); |
|||
void operator delete(void* p); |
|||
}; |
|||
#define BASE_EMBEDDED : public Embedded |
|||
#else |
|||
#define BASE_EMBEDDED |
|||
#endif |
|||
|
|||
|
|||
// Superclass for classes only using statics.
|
|||
class AllStatic { |
|||
#ifdef DEBUG |
|||
public: |
|||
void* operator new(size_t size); |
|||
void operator delete(void* p); |
|||
#endif |
|||
}; |
|||
|
|||
|
|||
template <typename T> |
|||
static T* NewArray(int size) { |
|||
ASSERT(NativeAllocationChecker::allocation_allowed()); |
|||
T* result = new T[size]; |
|||
if (result == NULL) Malloced::FatalProcessOutOfMemory(); |
|||
return result; |
|||
} |
|||
|
|||
|
|||
template <typename T> |
|||
static void DeleteArray(T* array) { |
|||
delete[] array; |
|||
} |
|||
|
|||
|
|||
// The normal strdup functions use malloc. These versions of StrDup
|
|||
// and StrNDup uses new and calls the FatalProcessOutOfMemory handler
|
|||
// if allocation fails.
|
|||
char* StrDup(const char* str); |
|||
char* StrNDup(const char* str, size_t n); |
|||
|
|||
|
|||
// Allocation policy for allocating in the C free store using malloc
|
|||
// and free. Used as the default policy for lists.
|
|||
class FreeStoreAllocationPolicy { |
|||
public: |
|||
INLINE(static void* New(size_t size)) { return Malloced::New(size); } |
|||
INLINE(static void Delete(void* p)) { Malloced::Delete(p); } |
|||
}; |
|||
|
|||
|
|||
// Allocation policy for allocating in preallocated space.
|
|||
// Used as an allocation policy for ScopeInfo when generating
|
|||
// stack traces.
|
|||
class PreallocatedStorage : public AllStatic { |
|||
public: |
|||
explicit PreallocatedStorage(size_t size); |
|||
size_t size() { return size_; } |
|||
static void* New(size_t size); |
|||
static void Delete(void* p); |
|||
|
|||
// Preallocate a set number of bytes.
|
|||
static void Init(size_t size); |
|||
|
|||
private: |
|||
size_t size_; |
|||
PreallocatedStorage* previous_; |
|||
PreallocatedStorage* next_; |
|||
static bool preallocated_; |
|||
|
|||
static PreallocatedStorage in_use_list_; |
|||
static PreallocatedStorage free_list_; |
|||
|
|||
void LinkTo(PreallocatedStorage* other); |
|||
void Unlink(); |
|||
DISALLOW_IMPLICIT_CONSTRUCTORS(PreallocatedStorage); |
|||
}; |
|||
|
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_ALLOCATION_H_
|
File diff suppressed because it is too large
@ -0,0 +1,454 @@ |
|||
// Copyright 2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#ifndef V8_API_H_ |
|||
#define V8_API_H_ |
|||
|
|||
#include "apiutils.h" |
|||
#include "factory.h" |
|||
|
|||
namespace v8 { |
|||
|
|||
// Constants used in the implementation of the API. The most natural thing
|
|||
// would usually be to place these with the classes that use them, but
|
|||
// we want to keep them out of v8.h because it is an externally
|
|||
// visible file.
|
|||
class Consts { |
|||
public: |
|||
enum TemplateType { |
|||
FUNCTION_TEMPLATE = 0, |
|||
OBJECT_TEMPLATE = 1 |
|||
}; |
|||
}; |
|||
|
|||
|
|||
// Utilities for working with neander-objects, primitive
|
|||
// env-independent JSObjects used by the api.
|
|||
class NeanderObject { |
|||
public: |
|||
explicit NeanderObject(int size); |
|||
inline NeanderObject(v8::internal::Handle<v8::internal::Object> obj); |
|||
inline NeanderObject(v8::internal::Object* obj); |
|||
inline v8::internal::Object* get(int index); |
|||
inline void set(int index, v8::internal::Object* value); |
|||
inline v8::internal::Handle<v8::internal::JSObject> value() { return value_; } |
|||
int size(); |
|||
private: |
|||
v8::internal::Handle<v8::internal::JSObject> value_; |
|||
}; |
|||
|
|||
|
|||
// Utilities for working with neander-arrays, a simple extensible
|
|||
// array abstraction built on neander-objects.
|
|||
class NeanderArray { |
|||
public: |
|||
NeanderArray(); |
|||
inline NeanderArray(v8::internal::Handle<v8::internal::Object> obj); |
|||
inline v8::internal::Handle<v8::internal::JSObject> value() { |
|||
return obj_.value(); |
|||
} |
|||
|
|||
void add(v8::internal::Handle<v8::internal::Object> value); |
|||
|
|||
int length(); |
|||
|
|||
v8::internal::Object* get(int index); |
|||
// Change the value at an index to undefined value. If the index is
|
|||
// out of bounds, the request is ignored. Returns the old value.
|
|||
void set(int index, v8::internal::Object* value); |
|||
private: |
|||
NeanderObject obj_; |
|||
}; |
|||
|
|||
|
|||
NeanderObject::NeanderObject(v8::internal::Handle<v8::internal::Object> obj) |
|||
: value_(v8::internal::Handle<v8::internal::JSObject>::cast(obj)) { } |
|||
|
|||
|
|||
NeanderObject::NeanderObject(v8::internal::Object* obj) |
|||
: value_(v8::internal::Handle<v8::internal::JSObject>( |
|||
v8::internal::JSObject::cast(obj))) { } |
|||
|
|||
|
|||
NeanderArray::NeanderArray(v8::internal::Handle<v8::internal::Object> obj) |
|||
: obj_(obj) { } |
|||
|
|||
|
|||
v8::internal::Object* NeanderObject::get(int offset) { |
|||
ASSERT(value()->HasFastElements()); |
|||
return v8::internal::FixedArray::cast(value()->elements())->get(offset); |
|||
} |
|||
|
|||
|
|||
void NeanderObject::set(int offset, v8::internal::Object* value) { |
|||
ASSERT(value_->HasFastElements()); |
|||
v8::internal::FixedArray::cast(value_->elements())->set(offset, value); |
|||
} |
|||
|
|||
|
|||
template <typename T> static inline T ToCData(v8::internal::Object* obj) { |
|||
STATIC_ASSERT(sizeof(T) == sizeof(v8::internal::Address)); |
|||
return reinterpret_cast<T>( |
|||
reinterpret_cast<intptr_t>(v8::internal::Proxy::cast(obj)->proxy())); |
|||
} |
|||
|
|||
|
|||
template <typename T> |
|||
static inline v8::internal::Handle<v8::internal::Object> FromCData(T obj) { |
|||
STATIC_ASSERT(sizeof(T) == sizeof(v8::internal::Address)); |
|||
return v8::internal::Factory::NewProxy( |
|||
reinterpret_cast<v8::internal::Address>(reinterpret_cast<intptr_t>(obj))); |
|||
} |
|||
|
|||
|
|||
v8::Arguments::Arguments(v8::Local<v8::Value> data, |
|||
v8::Local<v8::Object> holder, |
|||
v8::Local<v8::Function> callee, |
|||
bool is_construct_call, |
|||
void** values, int length) |
|||
: data_(data), holder_(holder), callee_(callee), |
|||
is_construct_call_(is_construct_call), |
|||
values_(values), length_(length) { } |
|||
|
|||
|
|||
enum ExtensionTraversalState { |
|||
UNVISITED, VISITED, INSTALLED |
|||
}; |
|||
|
|||
|
|||
class RegisteredExtension { |
|||
public: |
|||
explicit RegisteredExtension(Extension* extension); |
|||
static void Register(RegisteredExtension* that); |
|||
Extension* extension() { return extension_; } |
|||
RegisteredExtension* next() { return next_; } |
|||
RegisteredExtension* next_auto() { return next_auto_; } |
|||
ExtensionTraversalState state() { return state_; } |
|||
void set_state(ExtensionTraversalState value) { state_ = value; } |
|||
static RegisteredExtension* first_extension() { return first_extension_; } |
|||
private: |
|||
Extension* extension_; |
|||
RegisteredExtension* next_; |
|||
RegisteredExtension* next_auto_; |
|||
ExtensionTraversalState state_; |
|||
static RegisteredExtension* first_extension_; |
|||
static RegisteredExtension* first_auto_extension_; |
|||
}; |
|||
|
|||
|
|||
class Utils { |
|||
public: |
|||
static bool ReportApiFailure(const char* location, const char* message); |
|||
|
|||
static Local<FunctionTemplate> ToFunctionTemplate(NeanderObject obj); |
|||
static Local<ObjectTemplate> ToObjectTemplate(NeanderObject obj); |
|||
|
|||
static inline Local<Context> ToLocal( |
|||
v8::internal::Handle<v8::internal::Context> obj); |
|||
static inline Local<Value> ToLocal( |
|||
v8::internal::Handle<v8::internal::Object> obj); |
|||
static inline Local<Function> ToLocal( |
|||
v8::internal::Handle<v8::internal::JSFunction> obj); |
|||
static inline Local<String> ToLocal( |
|||
v8::internal::Handle<v8::internal::String> obj); |
|||
static inline Local<Object> ToLocal( |
|||
v8::internal::Handle<v8::internal::JSObject> obj); |
|||
static inline Local<Array> ToLocal( |
|||
v8::internal::Handle<v8::internal::JSArray> obj); |
|||
static inline Local<External> ToLocal( |
|||
v8::internal::Handle<v8::internal::Proxy> obj); |
|||
static inline Local<Message> MessageToLocal( |
|||
v8::internal::Handle<v8::internal::Object> obj); |
|||
static inline Local<Number> NumberToLocal( |
|||
v8::internal::Handle<v8::internal::Object> obj); |
|||
static inline Local<Integer> IntegerToLocal( |
|||
v8::internal::Handle<v8::internal::Object> obj); |
|||
static inline Local<Uint32> Uint32ToLocal( |
|||
v8::internal::Handle<v8::internal::Object> obj); |
|||
static inline Local<FunctionTemplate> ToLocal( |
|||
v8::internal::Handle<v8::internal::FunctionTemplateInfo> obj); |
|||
static inline Local<ObjectTemplate> ToLocal( |
|||
v8::internal::Handle<v8::internal::ObjectTemplateInfo> obj); |
|||
static inline Local<Signature> ToLocal( |
|||
v8::internal::Handle<v8::internal::SignatureInfo> obj); |
|||
static inline Local<TypeSwitch> ToLocal( |
|||
v8::internal::Handle<v8::internal::TypeSwitchInfo> obj); |
|||
|
|||
static inline v8::internal::Handle<v8::internal::TemplateInfo> |
|||
OpenHandle(const Template* that); |
|||
static inline v8::internal::Handle<v8::internal::FunctionTemplateInfo> |
|||
OpenHandle(const FunctionTemplate* that); |
|||
static inline v8::internal::Handle<v8::internal::ObjectTemplateInfo> |
|||
OpenHandle(const ObjectTemplate* that); |
|||
static inline v8::internal::Handle<v8::internal::Object> |
|||
OpenHandle(const Data* data); |
|||
static inline v8::internal::Handle<v8::internal::JSObject> |
|||
OpenHandle(const v8::Object* data); |
|||
static inline v8::internal::Handle<v8::internal::JSArray> |
|||
OpenHandle(const v8::Array* data); |
|||
static inline v8::internal::Handle<v8::internal::String> |
|||
OpenHandle(const String* data); |
|||
static inline v8::internal::Handle<v8::internal::JSFunction> |
|||
OpenHandle(const Script* data); |
|||
static inline v8::internal::Handle<v8::internal::JSFunction> |
|||
OpenHandle(const Function* data); |
|||
static inline v8::internal::Handle<v8::internal::JSObject> |
|||
OpenHandle(const Message* message); |
|||
static inline v8::internal::Handle<v8::internal::Context> |
|||
OpenHandle(const v8::Context* context); |
|||
static inline v8::internal::Handle<v8::internal::SignatureInfo> |
|||
OpenHandle(const v8::Signature* sig); |
|||
static inline v8::internal::Handle<v8::internal::TypeSwitchInfo> |
|||
OpenHandle(const v8::TypeSwitch* that); |
|||
static inline v8::internal::Handle<v8::internal::Proxy> |
|||
OpenHandle(const v8::External* that); |
|||
}; |
|||
|
|||
|
|||
template <class T> |
|||
static inline T* ToApi(v8::internal::Handle<v8::internal::Object> obj) { |
|||
return reinterpret_cast<T*>(obj.location()); |
|||
} |
|||
|
|||
|
|||
template <class T> |
|||
v8::internal::Handle<T> v8::internal::Handle<T>::EscapeFrom( |
|||
v8::HandleScope* scope) { |
|||
return Utils::OpenHandle(*scope->Close(Utils::ToLocal(*this))); |
|||
} |
|||
|
|||
|
|||
// Implementations of ToLocal
|
|||
|
|||
#define MAKE_TO_LOCAL(Name, From, To) \ |
|||
Local<v8::To> Utils::Name(v8::internal::Handle<v8::internal::From> obj) { \ |
|||
return Local<To>(reinterpret_cast<To*>(obj.location())); \ |
|||
} |
|||
|
|||
MAKE_TO_LOCAL(ToLocal, Context, Context) |
|||
MAKE_TO_LOCAL(ToLocal, Object, Value) |
|||
MAKE_TO_LOCAL(ToLocal, JSFunction, Function) |
|||
MAKE_TO_LOCAL(ToLocal, String, String) |
|||
MAKE_TO_LOCAL(ToLocal, JSObject, Object) |
|||
MAKE_TO_LOCAL(ToLocal, JSArray, Array) |
|||
MAKE_TO_LOCAL(ToLocal, Proxy, External) |
|||
MAKE_TO_LOCAL(ToLocal, FunctionTemplateInfo, FunctionTemplate) |
|||
MAKE_TO_LOCAL(ToLocal, ObjectTemplateInfo, ObjectTemplate) |
|||
MAKE_TO_LOCAL(ToLocal, SignatureInfo, Signature) |
|||
MAKE_TO_LOCAL(ToLocal, TypeSwitchInfo, TypeSwitch) |
|||
MAKE_TO_LOCAL(MessageToLocal, Object, Message) |
|||
MAKE_TO_LOCAL(NumberToLocal, Object, Number) |
|||
MAKE_TO_LOCAL(IntegerToLocal, Object, Integer) |
|||
MAKE_TO_LOCAL(Uint32ToLocal, Object, Uint32) |
|||
|
|||
#undef MAKE_TO_LOCAL |
|||
|
|||
|
|||
// Implementations of OpenHandle
|
|||
|
|||
#define MAKE_OPEN_HANDLE(From, To) \ |
|||
v8::internal::Handle<v8::internal::To> Utils::OpenHandle(\ |
|||
const v8::From* that) { \ |
|||
return v8::internal::Handle<v8::internal::To>( \ |
|||
reinterpret_cast<v8::internal::To**>(const_cast<v8::From*>(that))); \ |
|||
} |
|||
|
|||
MAKE_OPEN_HANDLE(Template, TemplateInfo) |
|||
MAKE_OPEN_HANDLE(FunctionTemplate, FunctionTemplateInfo) |
|||
MAKE_OPEN_HANDLE(ObjectTemplate, ObjectTemplateInfo) |
|||
MAKE_OPEN_HANDLE(Signature, SignatureInfo) |
|||
MAKE_OPEN_HANDLE(TypeSwitch, TypeSwitchInfo) |
|||
MAKE_OPEN_HANDLE(Data, Object) |
|||
MAKE_OPEN_HANDLE(Object, JSObject) |
|||
MAKE_OPEN_HANDLE(Array, JSArray) |
|||
MAKE_OPEN_HANDLE(String, String) |
|||
MAKE_OPEN_HANDLE(Script, JSFunction) |
|||
MAKE_OPEN_HANDLE(Function, JSFunction) |
|||
MAKE_OPEN_HANDLE(Message, JSObject) |
|||
MAKE_OPEN_HANDLE(Context, Context) |
|||
MAKE_OPEN_HANDLE(External, Proxy) |
|||
|
|||
#undef MAKE_OPEN_HANDLE |
|||
|
|||
|
|||
namespace internal { |
|||
|
|||
// This class is here in order to be able to declare it a friend of
|
|||
// HandleScope. Moving these methods to be members of HandleScope would be
|
|||
// neat in some ways, but it would expose external implementation details in
|
|||
// our public header file, which is undesirable.
|
|||
//
|
|||
// There is a singleton instance of this class to hold the per-thread data.
|
|||
// For multithreaded V8 programs this data is copied in and out of storage
|
|||
// so that the currently executing thread always has its own copy of this
|
|||
// data.
|
|||
class HandleScopeImplementer { |
|||
public: |
|||
|
|||
HandleScopeImplementer() |
|||
: blocks(0), |
|||
entered_contexts_(0), |
|||
saved_contexts_(0) { |
|||
Initialize(); |
|||
} |
|||
|
|||
void Initialize() { |
|||
blocks.Initialize(0); |
|||
entered_contexts_.Initialize(0); |
|||
saved_contexts_.Initialize(0); |
|||
spare = NULL; |
|||
ignore_out_of_memory = false; |
|||
call_depth = 0; |
|||
} |
|||
|
|||
static HandleScopeImplementer* instance(); |
|||
|
|||
// Threading support for handle data.
|
|||
static int ArchiveSpacePerThread(); |
|||
static char* RestoreThread(char* from); |
|||
static char* ArchiveThread(char* to); |
|||
|
|||
// Garbage collection support.
|
|||
static void Iterate(v8::internal::ObjectVisitor* v); |
|||
static char* Iterate(v8::internal::ObjectVisitor* v, char* data); |
|||
|
|||
|
|||
inline void** GetSpareOrNewBlock(); |
|||
inline void DeleteExtensions(int extensions); |
|||
|
|||
inline void IncrementCallDepth() {call_depth++;} |
|||
inline void DecrementCallDepth() {call_depth--;} |
|||
inline bool CallDepthIsZero() { return call_depth == 0; } |
|||
|
|||
inline void EnterContext(Handle<Object> context); |
|||
inline bool LeaveLastContext(); |
|||
|
|||
// Returns the last entered context or an empty handle if no
|
|||
// contexts have been entered.
|
|||
inline Handle<Object> LastEnteredContext(); |
|||
|
|||
inline void SaveContext(Handle<Object> context); |
|||
inline Handle<Object> RestoreContext(); |
|||
inline bool HasSavedContexts(); |
|||
|
|||
inline List<void**>* Blocks() { return &blocks; } |
|||
|
|||
inline bool IgnoreOutOfMemory() { return ignore_out_of_memory; } |
|||
inline void SetIgnoreOutOfMemory(bool value) { ignore_out_of_memory = value; } |
|||
|
|||
private: |
|||
List<void**> blocks; |
|||
Object** spare; |
|||
int call_depth; |
|||
// Used as a stack to keep track of entered contexts.
|
|||
List<Handle<Object> > entered_contexts_; |
|||
// Used as a stack to keep track of saved contexts.
|
|||
List<Handle<Object> > saved_contexts_; |
|||
bool ignore_out_of_memory; |
|||
// This is only used for threading support.
|
|||
v8::ImplementationUtilities::HandleScopeData handle_scope_data_; |
|||
|
|||
static void Iterate(ObjectVisitor* v, |
|||
List<void**>* blocks, |
|||
v8::ImplementationUtilities::HandleScopeData* handle_data); |
|||
char* RestoreThreadHelper(char* from); |
|||
char* ArchiveThreadHelper(char* to); |
|||
|
|||
DISALLOW_COPY_AND_ASSIGN(HandleScopeImplementer); |
|||
}; |
|||
|
|||
|
|||
static const int kHandleBlockSize = v8::internal::KB - 2; // fit in one page
|
|||
|
|||
|
|||
void HandleScopeImplementer::SaveContext(Handle<Object> context) { |
|||
saved_contexts_.Add(context); |
|||
} |
|||
|
|||
|
|||
Handle<Object> HandleScopeImplementer::RestoreContext() { |
|||
return saved_contexts_.RemoveLast(); |
|||
} |
|||
|
|||
|
|||
bool HandleScopeImplementer::HasSavedContexts() { |
|||
return !saved_contexts_.is_empty(); |
|||
} |
|||
|
|||
|
|||
void HandleScopeImplementer::EnterContext(Handle<Object> context) { |
|||
entered_contexts_.Add(context); |
|||
} |
|||
|
|||
|
|||
bool HandleScopeImplementer::LeaveLastContext() { |
|||
if (entered_contexts_.is_empty()) return false; |
|||
entered_contexts_.RemoveLast(); |
|||
return true; |
|||
} |
|||
|
|||
|
|||
Handle<Object> HandleScopeImplementer::LastEnteredContext() { |
|||
if (entered_contexts_.is_empty()) return Handle<Object>::null(); |
|||
return entered_contexts_.last(); |
|||
} |
|||
|
|||
|
|||
// If there's a spare block, use it for growing the current scope.
|
|||
void** HandleScopeImplementer::GetSpareOrNewBlock() { |
|||
void** block = (spare != NULL) ? |
|||
reinterpret_cast<void**>(spare) : |
|||
NewArray<void*>(kHandleBlockSize); |
|||
spare = NULL; |
|||
return block; |
|||
} |
|||
|
|||
|
|||
void HandleScopeImplementer::DeleteExtensions(int extensions) { |
|||
if (spare != NULL) { |
|||
DeleteArray(spare); |
|||
spare = NULL; |
|||
} |
|||
for (int i = extensions; i > 1; --i) { |
|||
void** block = blocks.RemoveLast(); |
|||
#ifdef DEBUG |
|||
v8::ImplementationUtilities::ZapHandleRange(block, |
|||
&block[kHandleBlockSize]); |
|||
#endif |
|||
DeleteArray(block); |
|||
} |
|||
spare = reinterpret_cast<Object**>(blocks.RemoveLast()); |
|||
#ifdef DEBUG |
|||
v8::ImplementationUtilities::ZapHandleRange( |
|||
reinterpret_cast<void**>(spare), |
|||
reinterpret_cast<void**>(&spare[kHandleBlockSize])); |
|||
#endif |
|||
} |
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_API_H_
|
@ -0,0 +1,109 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
// This file contains infrastructure used by the API. See
|
|||
// v8natives.js for an explanation of these files are processed and
|
|||
// loaded.
|
|||
|
|||
|
|||
function CreateDate(time) { |
|||
var date = new ORIGINAL_DATE(); |
|||
date.setTime(time); |
|||
return date; |
|||
} |
|||
|
|||
|
|||
const kApiFunctionCache = {}; |
|||
const functionCache = kApiFunctionCache; |
|||
|
|||
|
|||
function Instantiate(data, name) { |
|||
if (!%IsTemplate(data)) return data; |
|||
var tag = %GetTemplateField(data, kApiTagOffset); |
|||
switch (tag) { |
|||
case kFunctionTag: |
|||
return InstantiateFunction(data, name); |
|||
case kNewObjectTag: |
|||
var Constructor = %GetTemplateField(data, kApiConstructorOffset); |
|||
var result = Constructor ? new (Instantiate(Constructor))() : {}; |
|||
ConfigureTemplateInstance(result, data); |
|||
return result; |
|||
default: |
|||
throw 'Unknown API tag <' + tag + '>'; |
|||
} |
|||
} |
|||
|
|||
|
|||
function InstantiateFunction(data, name) { |
|||
// We need a reference to kApiFunctionCache in the stack frame
|
|||
// if we need to bail out from a stack overflow.
|
|||
var cache = kApiFunctionCache; |
|||
var serialNumber = %GetTemplateField(data, kApiSerialNumberOffset); |
|||
var isFunctionCached = |
|||
(serialNumber in cache) && (cache[serialNumber] != kUninitialized); |
|||
if (!isFunctionCached) { |
|||
try { |
|||
cache[serialNumber] = null; |
|||
var fun = %CreateApiFunction(data); |
|||
if (name) %FunctionSetName(fun, name); |
|||
cache[serialNumber] = fun; |
|||
var prototype = %GetTemplateField(data, kApiPrototypeTemplateOffset); |
|||
fun.prototype = prototype ? Instantiate(prototype) : {}; |
|||
%SetProperty(fun.prototype, "constructor", fun, DONT_ENUM); |
|||
var parent = %GetTemplateField(data, kApiParentTemplateOffset); |
|||
if (parent) { |
|||
var parent_fun = Instantiate(parent); |
|||
fun.prototype.__proto__ = parent_fun.prototype; |
|||
} |
|||
ConfigureTemplateInstance(fun, data); |
|||
} catch (e) { |
|||
cache[serialNumber] = kUninitialized; |
|||
throw e; |
|||
} |
|||
} |
|||
return cache[serialNumber]; |
|||
} |
|||
|
|||
|
|||
function ConfigureTemplateInstance(obj, data) { |
|||
var properties = %GetTemplateField(data, kApiPropertyListOffset); |
|||
if (properties) { |
|||
// Disable access checks while instantiating the object.
|
|||
var requires_access_checks = %DisableAccessChecks(obj); |
|||
try { |
|||
for (var i = 0; i < properties[0]; i += 3) { |
|||
var name = properties[i + 1]; |
|||
var prop_data = properties[i + 2]; |
|||
var attributes = properties[i + 3]; |
|||
var value = Instantiate(prop_data, name); |
|||
%SetProperty(obj, name, value, attributes); |
|||
} |
|||
} finally { |
|||
if (requires_access_checks) %EnableAccessChecks(obj); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,69 @@ |
|||
// Copyright 2009 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#ifndef V8_APIUTILS_H_ |
|||
#define V8_APIUTILS_H_ |
|||
|
|||
namespace v8 { |
|||
|
|||
class ImplementationUtilities { |
|||
public: |
|||
static v8::Handle<v8::Primitive> Undefined(); |
|||
static v8::Handle<v8::Primitive> Null(); |
|||
static v8::Handle<v8::Boolean> True(); |
|||
static v8::Handle<v8::Boolean> False(); |
|||
|
|||
static int GetNameCount(ExtensionConfiguration* that) { |
|||
return that->name_count_; |
|||
} |
|||
|
|||
static const char** GetNames(ExtensionConfiguration* that) { |
|||
return that->names_; |
|||
} |
|||
|
|||
static v8::Arguments NewArguments(Local<Value> data, |
|||
Local<Object> holder, |
|||
Local<Function> callee, |
|||
bool is_construct_call, |
|||
void** argv, int argc) { |
|||
return v8::Arguments(data, holder, callee, is_construct_call, argv, argc); |
|||
} |
|||
|
|||
// Introduce an alias for the handle scope data to allow non-friends
|
|||
// to access the HandleScope data.
|
|||
typedef v8::HandleScope::Data HandleScopeData; |
|||
|
|||
static HandleScopeData* CurrentHandleScope(); |
|||
|
|||
#ifdef DEBUG |
|||
static void ZapHandleRange(void** begin, void** end); |
|||
#endif |
|||
}; |
|||
|
|||
} // namespace v8
|
|||
|
|||
#endif // V8_APIUTILS_H_
|
@ -0,0 +1,70 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#ifndef V8_ARGUMENTS_H_ |
|||
#define V8_ARGUMENTS_H_ |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
// Arguments provides access to runtime call parameters.
|
|||
//
|
|||
// It uses the fact that the instance fields of Arguments
|
|||
// (length_, arguments_) are "overlayed" with the parameters
|
|||
// (no. of parameters, and the parameter pointer) passed so
|
|||
// that inside the C++ function, the parameters passed can
|
|||
// be accessed conveniently:
|
|||
//
|
|||
// Object* Runtime_function(Arguments args) {
|
|||
// ... use args[i] here ...
|
|||
// }
|
|||
|
|||
class Arguments BASE_EMBEDDED { |
|||
public: |
|||
Object*& operator[] (int index) { |
|||
ASSERT(0 <= index && index < length_); |
|||
return arguments_[-index]; |
|||
} |
|||
|
|||
template <class S> Handle<S> at(int index) { |
|||
Object** value = &((*this)[index]); |
|||
// This cast checks that the object we're accessing does indeed have the
|
|||
// expected type.
|
|||
S::cast(*value); |
|||
return Handle<S>(reinterpret_cast<S**>(value)); |
|||
} |
|||
|
|||
// Get the total number of arguments including the receiver.
|
|||
int length() const { return length_; } |
|||
|
|||
private: |
|||
int length_; |
|||
Object** arguments_; |
|||
}; |
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_ARGUMENTS_H_
|
@ -0,0 +1,938 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
// This file relies on the fact that the following declarations have been made
|
|||
// in runtime.js:
|
|||
// const $Array = global.Array;
|
|||
|
|||
// -------------------------------------------------------------------
|
|||
|
|||
// Global list of arrays visited during toString, toLocaleString and
|
|||
// join invocations.
|
|||
var visited_arrays = new $Array(); |
|||
|
|||
|
|||
// Gets a sorted array of array keys. Useful for operations on sparse
|
|||
// arrays. Dupes have not been removed.
|
|||
function GetSortedArrayKeys(array, intervals) { |
|||
var length = intervals.length; |
|||
var keys = []; |
|||
for (var k = 0; k < length; k++) { |
|||
var key = intervals[k]; |
|||
if (key < 0) { |
|||
var j = -1 - key; |
|||
var limit = j + intervals[++k]; |
|||
for (; j < limit; j++) { |
|||
var e = array[j]; |
|||
if (!IS_UNDEFINED(e) || j in array) { |
|||
keys.push(j); |
|||
} |
|||
} |
|||
} else { |
|||
// The case where key is undefined also ends here.
|
|||
if (!IS_UNDEFINED(key)) { |
|||
var e = array[key]; |
|||
if (!IS_UNDEFINED(e) || key in array) { |
|||
keys.push(key); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
keys.sort(function(a, b) { return a - b; }); |
|||
return keys; |
|||
} |
|||
|
|||
|
|||
// Optimized for sparse arrays if separator is ''.
|
|||
function SparseJoin(array, len, convert) { |
|||
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len)); |
|||
var builder = new StringBuilder(); |
|||
var last_key = -1; |
|||
var keys_length = keys.length; |
|||
for (var i = 0; i < keys_length; i++) { |
|||
var key = keys[i]; |
|||
if (key != last_key) { |
|||
var e = array[key]; |
|||
builder.add(convert(e)); |
|||
last_key = key; |
|||
} |
|||
} |
|||
return builder.generate(); |
|||
} |
|||
|
|||
|
|||
function UseSparseVariant(object, length, is_array) { |
|||
return is_array && |
|||
length > 1000 && |
|||
(!%_IsSmi(length) || |
|||
%EstimateNumberOfElements(object) < (length >> 2)); |
|||
} |
|||
|
|||
|
|||
function Join(array, length, separator, convert) { |
|||
if (length == 0) return ''; |
|||
|
|||
var is_array = IS_ARRAY(array); |
|||
|
|||
if (is_array) { |
|||
// If the array is cyclic, return the empty string for already
|
|||
// visited arrays.
|
|||
if (!%PushIfAbsent(visited_arrays, array)) return ''; |
|||
} |
|||
|
|||
// Attempt to convert the elements.
|
|||
try { |
|||
if (UseSparseVariant(array, length, is_array) && separator === '') { |
|||
return SparseJoin(array, length, convert); |
|||
} |
|||
|
|||
// Fast case for one-element arrays.
|
|||
if (length == 1) { |
|||
var e = array[0]; |
|||
if (!IS_UNDEFINED(e) || (0 in array)) { |
|||
return convert(e); |
|||
} |
|||
} |
|||
|
|||
var builder = new StringBuilder(); |
|||
|
|||
for (var i = 0; i < length; i++) { |
|||
var e = array[i]; |
|||
if (i != 0) builder.add(separator); |
|||
if (!IS_UNDEFINED(e) || (i in array)) { |
|||
builder.add(convert(e)); |
|||
} |
|||
} |
|||
return builder.generate(); |
|||
} finally { |
|||
// Make sure to pop the visited array no matter what happens.
|
|||
if (is_array) visited_arrays.pop(); |
|||
} |
|||
} |
|||
|
|||
|
|||
function ConvertToString(e) { |
|||
if (e == null) return ''; |
|||
else return ToString(e); |
|||
} |
|||
|
|||
|
|||
function ConvertToLocaleString(e) { |
|||
if (e == null) return ''; |
|||
else { |
|||
// e_obj's toLocaleString might be overwritten, check if it is a function.
|
|||
// Call ToString if toLocaleString is not a function.
|
|||
// See issue 877615.
|
|||
var e_obj = ToObject(e); |
|||
if (IS_FUNCTION(e_obj.toLocaleString)) |
|||
return e_obj.toLocaleString(); |
|||
else |
|||
return ToString(e); |
|||
} |
|||
} |
|||
|
|||
|
|||
// This function implements the optimized splice implementation that can use
|
|||
// special array operations to handle sparse arrays in a sensible fashion.
|
|||
function SmartSlice(array, start_i, del_count, len, deleted_elements) { |
|||
// Move deleted elements to a new array (the return value from splice).
|
|||
// Intervals array can contain keys and intervals. See comment in Concat.
|
|||
var intervals = %GetArrayKeys(array, start_i + del_count); |
|||
var length = intervals.length; |
|||
for (var k = 0; k < length; k++) { |
|||
var key = intervals[k]; |
|||
if (key < 0) { |
|||
var j = -1 - key; |
|||
var interval_limit = j + intervals[++k]; |
|||
if (j < start_i) { |
|||
j = start_i; |
|||
} |
|||
for (; j < interval_limit; j++) { |
|||
// ECMA-262 15.4.4.12 line 10. The spec could also be
|
|||
// interpreted such that %HasLocalProperty would be the
|
|||
// appropriate test. We follow KJS in consulting the
|
|||
// prototype.
|
|||
var current = array[j]; |
|||
if (!IS_UNDEFINED(current) || j in array) { |
|||
deleted_elements[j - start_i] = current; |
|||
} |
|||
} |
|||
} else { |
|||
if (!IS_UNDEFINED(key)) { |
|||
if (key >= start_i) { |
|||
// ECMA-262 15.4.4.12 line 10. The spec could also be
|
|||
// interpreted such that %HasLocalProperty would be the
|
|||
// appropriate test. We follow KJS in consulting the
|
|||
// prototype.
|
|||
var current = array[key]; |
|||
if (!IS_UNDEFINED(current) || key in array) { |
|||
deleted_elements[key - start_i] = current; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
// This function implements the optimized splice implementation that can use
|
|||
// special array operations to handle sparse arrays in a sensible fashion.
|
|||
function SmartMove(array, start_i, del_count, len, num_additional_args) { |
|||
// Move data to new array.
|
|||
var new_array = new $Array(len - del_count + num_additional_args); |
|||
var intervals = %GetArrayKeys(array, len); |
|||
var length = intervals.length; |
|||
for (var k = 0; k < length; k++) { |
|||
var key = intervals[k]; |
|||
if (key < 0) { |
|||
var j = -1 - key; |
|||
var interval_limit = j + intervals[++k]; |
|||
while (j < start_i && j < interval_limit) { |
|||
// The spec could also be interpreted such that
|
|||
// %HasLocalProperty would be the appropriate test. We follow
|
|||
// KJS in consulting the prototype.
|
|||
var current = array[j]; |
|||
if (!IS_UNDEFINED(current) || j in array) { |
|||
new_array[j] = current; |
|||
} |
|||
j++; |
|||
} |
|||
j = start_i + del_count; |
|||
while (j < interval_limit) { |
|||
// ECMA-262 15.4.4.12 lines 24 and 41. The spec could also be
|
|||
// interpreted such that %HasLocalProperty would be the
|
|||
// appropriate test. We follow KJS in consulting the
|
|||
// prototype.
|
|||
var current = array[j]; |
|||
if (!IS_UNDEFINED(current) || j in array) { |
|||
new_array[j - del_count + num_additional_args] = current; |
|||
} |
|||
j++; |
|||
} |
|||
} else { |
|||
if (!IS_UNDEFINED(key)) { |
|||
if (key < start_i) { |
|||
// The spec could also be interpreted such that
|
|||
// %HasLocalProperty would be the appropriate test. We follow
|
|||
// KJS in consulting the prototype.
|
|||
var current = array[key]; |
|||
if (!IS_UNDEFINED(current) || key in array) { |
|||
new_array[key] = current; |
|||
} |
|||
} else if (key >= start_i + del_count) { |
|||
// ECMA-262 15.4.4.12 lines 24 and 41. The spec could also
|
|||
// be interpreted such that %HasLocalProperty would be the
|
|||
// appropriate test. We follow KJS in consulting the
|
|||
// prototype.
|
|||
var current = array[key]; |
|||
if (!IS_UNDEFINED(current) || key in array) { |
|||
new_array[key - del_count + num_additional_args] = current; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
// Move contents of new_array into this array
|
|||
%MoveArrayContents(new_array, array); |
|||
} |
|||
|
|||
|
|||
// This is part of the old simple-minded splice. We are using it either
|
|||
// because the receiver is not an array (so we have no choice) or because we
|
|||
// know we are not deleting or moving a lot of elements.
|
|||
function SimpleSlice(array, start_i, del_count, len, deleted_elements) { |
|||
for (var i = 0; i < del_count; i++) { |
|||
var index = start_i + i; |
|||
// The spec could also be interpreted such that %HasLocalProperty
|
|||
// would be the appropriate test. We follow KJS in consulting the
|
|||
// prototype.
|
|||
var current = array[index]; |
|||
if (!IS_UNDEFINED(current) || index in array) |
|||
deleted_elements[i] = current; |
|||
} |
|||
} |
|||
|
|||
|
|||
function SimpleMove(array, start_i, del_count, len, num_additional_args) { |
|||
if (num_additional_args !== del_count) { |
|||
// Move the existing elements after the elements to be deleted
|
|||
// to the right position in the resulting array.
|
|||
if (num_additional_args > del_count) { |
|||
for (var i = len - del_count; i > start_i; i--) { |
|||
var from_index = i + del_count - 1; |
|||
var to_index = i + num_additional_args - 1; |
|||
// The spec could also be interpreted such that
|
|||
// %HasLocalProperty would be the appropriate test. We follow
|
|||
// KJS in consulting the prototype.
|
|||
var current = array[from_index]; |
|||
if (!IS_UNDEFINED(current) || from_index in array) { |
|||
array[to_index] = current; |
|||
} else { |
|||
delete array[to_index]; |
|||
} |
|||
} |
|||
} else { |
|||
for (var i = start_i; i < len - del_count; i++) { |
|||
var from_index = i + del_count; |
|||
var to_index = i + num_additional_args; |
|||
// The spec could also be interpreted such that
|
|||
// %HasLocalProperty would be the appropriate test. We follow
|
|||
// KJS in consulting the prototype.
|
|||
var current = array[from_index]; |
|||
if (!IS_UNDEFINED(current) || from_index in array) { |
|||
array[to_index] = current; |
|||
} else { |
|||
delete array[to_index]; |
|||
} |
|||
} |
|||
for (var i = len; i > len - del_count + num_additional_args; i--) { |
|||
delete array[i - 1]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
// -------------------------------------------------------------------
|
|||
|
|||
|
|||
function ArrayToString() { |
|||
if (!IS_ARRAY(this)) { |
|||
throw new $TypeError('Array.prototype.toString is not generic'); |
|||
} |
|||
return Join(this, this.length, ',', ConvertToString); |
|||
} |
|||
|
|||
|
|||
function ArrayToLocaleString() { |
|||
if (!IS_ARRAY(this)) { |
|||
throw new $TypeError('Array.prototype.toString is not generic'); |
|||
} |
|||
return Join(this, this.length, ',', ConvertToLocaleString); |
|||
} |
|||
|
|||
|
|||
function ArrayJoin(separator) { |
|||
if (IS_UNDEFINED(separator)) separator = ','; |
|||
else separator = ToString(separator); |
|||
return Join(this, ToUint32(this.length), separator, ConvertToString); |
|||
} |
|||
|
|||
|
|||
// Removes the last element from the array and returns it. See
|
|||
// ECMA-262, section 15.4.4.6.
|
|||
function ArrayPop() { |
|||
var n = ToUint32(this.length); |
|||
if (n == 0) { |
|||
this.length = n; |
|||
return; |
|||
} |
|||
n--; |
|||
var value = this[n]; |
|||
this.length = n; |
|||
delete this[n]; |
|||
return value; |
|||
} |
|||
|
|||
|
|||
// Appends the arguments to the end of the array and returns the new
|
|||
// length of the array. See ECMA-262, section 15.4.4.7.
|
|||
function ArrayPush() { |
|||
var n = ToUint32(this.length); |
|||
var m = %_ArgumentsLength(); |
|||
for (var i = 0; i < m; i++) { |
|||
this[i+n] = %_Arguments(i); |
|||
} |
|||
this.length = n + m; |
|||
return this.length; |
|||
} |
|||
|
|||
|
|||
function ArrayConcat(arg1) { // length == 1
|
|||
// TODO: can we just use arguments?
|
|||
var arg_count = %_ArgumentsLength(); |
|||
var arrays = new $Array(1 + arg_count); |
|||
arrays[0] = this; |
|||
for (var i = 0; i < arg_count; i++) { |
|||
arrays[i + 1] = %_Arguments(i); |
|||
} |
|||
|
|||
return %ArrayConcat(arrays); |
|||
} |
|||
|
|||
|
|||
// For implementing reverse() on large, sparse arrays.
|
|||
function SparseReverse(array, len) { |
|||
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len)); |
|||
var high_counter = keys.length - 1; |
|||
var low_counter = 0; |
|||
while (low_counter <= high_counter) { |
|||
var i = keys[low_counter]; |
|||
var j = keys[high_counter]; |
|||
|
|||
var j_complement = len - j - 1; |
|||
var low, high; |
|||
|
|||
if (j_complement <= i) { |
|||
high = j; |
|||
while (keys[--high_counter] == j); |
|||
low = j_complement; |
|||
} |
|||
if (j_complement >= i) { |
|||
low = i; |
|||
while (keys[++low_counter] == i); |
|||
high = len - i - 1; |
|||
} |
|||
|
|||
var current_i = array[low]; |
|||
if (!IS_UNDEFINED(current_i) || low in array) { |
|||
var current_j = array[high]; |
|||
if (!IS_UNDEFINED(current_j) || high in array) { |
|||
array[low] = current_j; |
|||
array[high] = current_i; |
|||
} else { |
|||
array[high] = current_i; |
|||
delete array[low]; |
|||
} |
|||
} else { |
|||
var current_j = array[high]; |
|||
if (!IS_UNDEFINED(current_j) || high in array) { |
|||
array[low] = current_j; |
|||
delete array[high]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
function ArrayReverse() { |
|||
var j = ToUint32(this.length) - 1; |
|||
|
|||
if (UseSparseVariant(this, j, IS_ARRAY(this))) { |
|||
SparseReverse(this, j+1); |
|||
return this; |
|||
} |
|||
|
|||
for (var i = 0; i < j; i++, j--) { |
|||
var current_i = this[i]; |
|||
if (!IS_UNDEFINED(current_i) || i in this) { |
|||
var current_j = this[j]; |
|||
if (!IS_UNDEFINED(current_j) || j in this) { |
|||
this[i] = current_j; |
|||
this[j] = current_i; |
|||
} else { |
|||
this[j] = current_i; |
|||
delete this[i]; |
|||
} |
|||
} else { |
|||
var current_j = this[j]; |
|||
if (!IS_UNDEFINED(current_j) || j in this) { |
|||
this[i] = current_j; |
|||
delete this[j]; |
|||
} |
|||
} |
|||
} |
|||
return this; |
|||
} |
|||
|
|||
|
|||
function ArrayShift() { |
|||
var len = ToUint32(this.length); |
|||
|
|||
if (len === 0) { |
|||
this.length = 0; |
|||
return; |
|||
} |
|||
|
|||
var first = this[0]; |
|||
|
|||
if (IS_ARRAY(this)) |
|||
SmartMove(this, 0, 1, len, 0); |
|||
else |
|||
SimpleMove(this, 0, 1, len, 0); |
|||
|
|||
this.length = len - 1; |
|||
|
|||
return first; |
|||
} |
|||
|
|||
|
|||
function ArrayUnshift(arg1) { // length == 1
|
|||
var len = ToUint32(this.length); |
|||
var num_arguments = %_ArgumentsLength(); |
|||
|
|||
if (IS_ARRAY(this)) |
|||
SmartMove(this, 0, 0, len, num_arguments); |
|||
else |
|||
SimpleMove(this, 0, 0, len, num_arguments); |
|||
|
|||
for (var i = 0; i < num_arguments; i++) { |
|||
this[i] = %_Arguments(i); |
|||
} |
|||
|
|||
this.length = len + num_arguments; |
|||
|
|||
return len + num_arguments; |
|||
} |
|||
|
|||
|
|||
function ArraySlice(start, end) { |
|||
var len = ToUint32(this.length); |
|||
var start_i = TO_INTEGER(start); |
|||
var end_i = len; |
|||
|
|||
if (end !== void 0) end_i = TO_INTEGER(end); |
|||
|
|||
if (start_i < 0) { |
|||
start_i += len; |
|||
if (start_i < 0) start_i = 0; |
|||
} else { |
|||
if (start_i > len) start_i = len; |
|||
} |
|||
|
|||
if (end_i < 0) { |
|||
end_i += len; |
|||
if (end_i < 0) end_i = 0; |
|||
} else { |
|||
if (end_i > len) end_i = len; |
|||
} |
|||
|
|||
var result = []; |
|||
|
|||
if (end_i < start_i) return result; |
|||
|
|||
if (IS_ARRAY(this)) { |
|||
SmartSlice(this, start_i, end_i - start_i, len, result); |
|||
} else { |
|||
SimpleSlice(this, start_i, end_i - start_i, len, result); |
|||
} |
|||
|
|||
result.length = end_i - start_i; |
|||
|
|||
return result; |
|||
} |
|||
|
|||
|
|||
function ArraySplice(start, delete_count) { |
|||
var num_arguments = %_ArgumentsLength(); |
|||
|
|||
// SpiderMonkey and KJS return undefined in the case where no
|
|||
// arguments are given instead of using the implicit undefined
|
|||
// arguments. This does not follow ECMA-262, but we do the same for
|
|||
// compatibility.
|
|||
if (num_arguments == 0) return; |
|||
|
|||
var len = ToUint32(this.length); |
|||
var start_i = TO_INTEGER(start); |
|||
|
|||
if (start_i < 0) { |
|||
start_i += len; |
|||
if (start_i < 0) start_i = 0; |
|||
} else { |
|||
if (start_i > len) start_i = len; |
|||
} |
|||
|
|||
// SpiderMonkey and KJS treat the case where no delete count is
|
|||
// given differently from when an undefined delete count is given.
|
|||
// This does not follow ECMA-262, but we do the same for
|
|||
// compatibility.
|
|||
var del_count = 0; |
|||
if (num_arguments > 1) { |
|||
del_count = TO_INTEGER(delete_count); |
|||
if (del_count < 0) del_count = 0; |
|||
if (del_count > len - start_i) del_count = len - start_i; |
|||
} else { |
|||
del_count = len - start_i; |
|||
} |
|||
|
|||
var deleted_elements = []; |
|||
deleted_elements.length = del_count; |
|||
|
|||
// Number of elements to add.
|
|||
var num_additional_args = 0; |
|||
if (num_arguments > 2) { |
|||
num_additional_args = num_arguments - 2; |
|||
} |
|||
|
|||
var use_simple_splice = true; |
|||
|
|||
if (IS_ARRAY(this) && num_additional_args !== del_count) { |
|||
// If we are only deleting/moving a few things near the end of the
|
|||
// array then the simple version is going to be faster, because it
|
|||
// doesn't touch most of the array.
|
|||
var estimated_non_hole_elements = %EstimateNumberOfElements(this); |
|||
if (len > 20 && (estimated_non_hole_elements >> 2) < (len - start_i)) { |
|||
use_simple_splice = false; |
|||
} |
|||
} |
|||
|
|||
if (use_simple_splice) { |
|||
SimpleSlice(this, start_i, del_count, len, deleted_elements); |
|||
SimpleMove(this, start_i, del_count, len, num_additional_args); |
|||
} else { |
|||
SmartSlice(this, start_i, del_count, len, deleted_elements); |
|||
SmartMove(this, start_i, del_count, len, num_additional_args); |
|||
} |
|||
|
|||
// Insert the arguments into the resulting array in
|
|||
// place of the deleted elements.
|
|||
var i = start_i; |
|||
var arguments_index = 2; |
|||
var arguments_length = %_ArgumentsLength(); |
|||
while (arguments_index < arguments_length) { |
|||
this[i++] = %_Arguments(arguments_index++); |
|||
} |
|||
this.length = len - del_count + num_additional_args; |
|||
|
|||
// Return the deleted elements.
|
|||
return deleted_elements; |
|||
} |
|||
|
|||
|
|||
function ArraySort(comparefn) { |
|||
// In-place QuickSort algorithm.
|
|||
// For short (length <= 22) arrays, insertion sort is used for efficiency.
|
|||
|
|||
var custom_compare = IS_FUNCTION(comparefn); |
|||
|
|||
function Compare(x,y) { |
|||
// Assume the comparefn, if any, is a consistent comparison function.
|
|||
// If it isn't, we are allowed arbitrary behavior by ECMA 15.4.4.11.
|
|||
if (x === y) return 0; |
|||
if (custom_compare) { |
|||
// Don't call directly to avoid exposing the builtin's global object.
|
|||
return comparefn.call(null, x, y); |
|||
} |
|||
if (%_IsSmi(x) && %_IsSmi(y)) { |
|||
return %SmiLexicographicCompare(x, y); |
|||
} |
|||
x = ToString(x); |
|||
y = ToString(y); |
|||
if (x == y) return 0; |
|||
else return x < y ? -1 : 1; |
|||
}; |
|||
|
|||
function InsertionSort(a, from, to) { |
|||
for (var i = from + 1; i < to; i++) { |
|||
var element = a[i]; |
|||
// Pre-convert the element to a string for comparison if we know
|
|||
// it will happen on each compare anyway.
|
|||
var key = |
|||
(custom_compare || %_IsSmi(element)) ? element : ToString(element); |
|||
// place element in a[from..i[
|
|||
// binary search
|
|||
var min = from; |
|||
var max = i; |
|||
// The search interval is a[min..max[
|
|||
while (min < max) { |
|||
var mid = min + ((max - min) >> 1); |
|||
var order = Compare(a[mid], key); |
|||
if (order == 0) { |
|||
min = max = mid; |
|||
break; |
|||
} |
|||
if (order < 0) { |
|||
min = mid + 1; |
|||
} else { |
|||
max = mid; |
|||
} |
|||
} |
|||
// place element at position min==max.
|
|||
for (var j = i; j > min; j--) { |
|||
a[j] = a[j - 1]; |
|||
} |
|||
a[min] = element; |
|||
} |
|||
} |
|||
|
|||
function QuickSort(a, from, to) { |
|||
// Insertion sort is faster for short arrays.
|
|||
if (to - from <= 22) { |
|||
InsertionSort(a, from, to); |
|||
return; |
|||
} |
|||
var pivot_index = $floor($random() * (to - from)) + from; |
|||
var pivot = a[pivot_index]; |
|||
// Pre-convert the element to a string for comparison if we know
|
|||
// it will happen on each compare anyway.
|
|||
var pivot_key = |
|||
(custom_compare || %_IsSmi(pivot)) ? pivot : ToString(pivot); |
|||
// Issue 95: Keep the pivot element out of the comparisons to avoid
|
|||
// infinite recursion if comparefn(pivot, pivot) != 0.
|
|||
a[pivot_index] = a[from]; |
|||
a[from] = pivot; |
|||
var low_end = from; // Upper bound of the elements lower than pivot.
|
|||
var high_start = to; // Lower bound of the elements greater than pivot.
|
|||
// From low_end to i are elements equal to pivot.
|
|||
// From i to high_start are elements that haven't been compared yet.
|
|||
for (var i = from + 1; i < high_start; ) { |
|||
var element = a[i]; |
|||
var order = Compare(element, pivot_key); |
|||
if (order < 0) { |
|||
a[i] = a[low_end]; |
|||
a[low_end] = element; |
|||
i++; |
|||
low_end++; |
|||
} else if (order > 0) { |
|||
high_start--; |
|||
a[i] = a[high_start]; |
|||
a[high_start] = element; |
|||
} else { // order == 0
|
|||
i++; |
|||
} |
|||
} |
|||
QuickSort(a, from, low_end); |
|||
QuickSort(a, high_start, to); |
|||
} |
|||
|
|||
var old_length = ToUint32(this.length); |
|||
if (old_length < 2) return this; |
|||
|
|||
%RemoveArrayHoles(this); |
|||
|
|||
var length = ToUint32(this.length); |
|||
|
|||
// Move undefined elements to the end of the array.
|
|||
for (var i = 0; i < length; ) { |
|||
if (IS_UNDEFINED(this[i])) { |
|||
length--; |
|||
this[i] = this[length]; |
|||
this[length] = void 0; |
|||
} else { |
|||
i++; |
|||
} |
|||
} |
|||
|
|||
QuickSort(this, 0, length); |
|||
|
|||
// We only changed the length of the this object (in
|
|||
// RemoveArrayHoles) if it was an array. We are not allowed to set
|
|||
// the length of the this object if it is not an array because this
|
|||
// might introduce a new length property.
|
|||
if (IS_ARRAY(this)) { |
|||
this.length = old_length; |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
|
|||
// The following functions cannot be made efficient on sparse arrays while
|
|||
// preserving the semantics, since the calls to the receiver function can add
|
|||
// or delete elements from the array.
|
|||
function ArrayFilter(f, receiver) { |
|||
if (!IS_FUNCTION(f)) { |
|||
throw MakeTypeError('called_non_callable', [ f ]); |
|||
} |
|||
// Pull out the length so that modifications to the length in the
|
|||
// loop will not affect the looping.
|
|||
var length = this.length; |
|||
var result = []; |
|||
var result_length = 0; |
|||
for (var i = 0; i < length; i++) { |
|||
var current = this[i]; |
|||
if (!IS_UNDEFINED(current) || i in this) { |
|||
if (f.call(receiver, current, i, this)) result[result_length++] = current; |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
|
|||
function ArrayForEach(f, receiver) { |
|||
if (!IS_FUNCTION(f)) { |
|||
throw MakeTypeError('called_non_callable', [ f ]); |
|||
} |
|||
// Pull out the length so that modifications to the length in the
|
|||
// loop will not affect the looping.
|
|||
var length = this.length; |
|||
for (var i = 0; i < length; i++) { |
|||
var current = this[i]; |
|||
if (!IS_UNDEFINED(current) || i in this) { |
|||
f.call(receiver, current, i, this); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
// Executes the function once for each element present in the
|
|||
// array until it finds one where callback returns true.
|
|||
function ArraySome(f, receiver) { |
|||
if (!IS_FUNCTION(f)) { |
|||
throw MakeTypeError('called_non_callable', [ f ]); |
|||
} |
|||
// Pull out the length so that modifications to the length in the
|
|||
// loop will not affect the looping.
|
|||
var length = this.length; |
|||
for (var i = 0; i < length; i++) { |
|||
var current = this[i]; |
|||
if (!IS_UNDEFINED(current) || i in this) { |
|||
if (f.call(receiver, current, i, this)) return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
|
|||
function ArrayEvery(f, receiver) { |
|||
if (!IS_FUNCTION(f)) { |
|||
throw MakeTypeError('called_non_callable', [ f ]); |
|||
} |
|||
// Pull out the length so that modifications to the length in the
|
|||
// loop will not affect the looping.
|
|||
var length = this.length; |
|||
for (var i = 0; i < length; i++) { |
|||
var current = this[i]; |
|||
if (!IS_UNDEFINED(current) || i in this) { |
|||
if (!f.call(receiver, current, i, this)) return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
|
|||
function ArrayMap(f, receiver) { |
|||
if (!IS_FUNCTION(f)) { |
|||
throw MakeTypeError('called_non_callable', [ f ]); |
|||
} |
|||
// Pull out the length so that modifications to the length in the
|
|||
// loop will not affect the looping.
|
|||
var length = this.length; |
|||
var result = new $Array(length); |
|||
for (var i = 0; i < length; i++) { |
|||
var current = this[i]; |
|||
if (!IS_UNDEFINED(current) || i in this) { |
|||
result[i] = f.call(receiver, current, i, this); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
|
|||
function ArrayIndexOf(element, index) { |
|||
var length = this.length; |
|||
if (index == null) { |
|||
index = 0; |
|||
} else { |
|||
index = TO_INTEGER(index); |
|||
// If index is negative, index from the end of the array.
|
|||
if (index < 0) index = length + index; |
|||
// If index is still negative, search the entire array.
|
|||
if (index < 0) index = 0; |
|||
} |
|||
// Lookup through the array.
|
|||
for (var i = index; i < length; i++) { |
|||
var current = this[i]; |
|||
if (!IS_UNDEFINED(current) || i in this) { |
|||
if (current === element) return i; |
|||
} |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
|
|||
function ArrayLastIndexOf(element, index) { |
|||
var length = this.length; |
|||
if (index == null) { |
|||
index = length - 1; |
|||
} else { |
|||
index = TO_INTEGER(index); |
|||
// If index is negative, index from end of the array.
|
|||
if (index < 0) index = length + index; |
|||
// If index is still negative, do not search the array.
|
|||
if (index < 0) index = -1; |
|||
else if (index >= length) index = length - 1; |
|||
} |
|||
// Lookup through the array.
|
|||
for (var i = index; i >= 0; i--) { |
|||
var current = this[i]; |
|||
if (!IS_UNDEFINED(current) || i in this) { |
|||
if (current === element) return i; |
|||
} |
|||
} |
|||
return -1; |
|||
} |
|||
|
|||
|
|||
// -------------------------------------------------------------------
|
|||
|
|||
|
|||
function UpdateFunctionLengths(lengths) { |
|||
for (var key in lengths) { |
|||
%FunctionSetLength(this[key], lengths[key]); |
|||
} |
|||
} |
|||
|
|||
|
|||
// -------------------------------------------------------------------
|
|||
|
|||
function SetupArray() { |
|||
// Setup non-enumerable constructor property on the Array.prototype
|
|||
// object.
|
|||
%SetProperty($Array.prototype, "constructor", $Array, DONT_ENUM); |
|||
|
|||
// Setup non-enumerable functions of the Array.prototype object and
|
|||
// set their names.
|
|||
InstallFunctions($Array.prototype, DONT_ENUM, $Array( |
|||
"toString", ArrayToString, |
|||
"toLocaleString", ArrayToLocaleString, |
|||
"join", ArrayJoin, |
|||
"pop", ArrayPop, |
|||
"push", ArrayPush, |
|||
"concat", ArrayConcat, |
|||
"reverse", ArrayReverse, |
|||
"shift", ArrayShift, |
|||
"unshift", ArrayUnshift, |
|||
"slice", ArraySlice, |
|||
"splice", ArraySplice, |
|||
"sort", ArraySort, |
|||
"filter", ArrayFilter, |
|||
"forEach", ArrayForEach, |
|||
"some", ArraySome, |
|||
"every", ArrayEvery, |
|||
"map", ArrayMap, |
|||
"indexOf", ArrayIndexOf, |
|||
"lastIndexOf", ArrayLastIndexOf |
|||
)); |
|||
|
|||
// Manipulate the length of some of the functions to meet
|
|||
// expectations set by ECMA-262 or Mozilla.
|
|||
UpdateFunctionLengths({ |
|||
ArrayFilter: 1, |
|||
ArrayForEach: 1, |
|||
ArraySome: 1, |
|||
ArrayEvery: 1, |
|||
ArrayMap: 1, |
|||
ArrayIndexOf: 1, |
|||
ArrayLastIndexOf: 1, |
|||
ArrayPush: 1 |
|||
}); |
|||
} |
|||
|
|||
|
|||
SetupArray(); |
@ -0,0 +1,249 @@ |
|||
// Copyright (c) 1994-2006 Sun Microsystems Inc.
|
|||
// All Rights Reserved.
|
|||
//
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions
|
|||
// are met:
|
|||
//
|
|||
// - Redistributions of source code must retain the above copyright notice,
|
|||
// this list of conditions and the following disclaimer.
|
|||
//
|
|||
// - Redistribution in binary form must reproduce the above copyright
|
|||
// notice, this list of conditions and the following disclaimer in the
|
|||
// documentation and/or other materials provided with the
|
|||
// distribution.
|
|||
//
|
|||
// - Neither the name of Sun Microsystems or the names of contributors may
|
|||
// be used to endorse or promote products derived from this software without
|
|||
// specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|||
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|||
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|||
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
|||
// OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
// The original source code covered by the above license above has been modified
|
|||
// significantly by Google Inc.
|
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
|
|||
#ifndef V8_ASSEMBLER_ARM_INL_H_ |
|||
#define V8_ASSEMBLER_ARM_INL_H_ |
|||
|
|||
#include "assembler-arm.h" |
|||
#include "cpu.h" |
|||
|
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
Condition NegateCondition(Condition cc) { |
|||
ASSERT(cc != al); |
|||
return static_cast<Condition>(cc ^ ne); |
|||
} |
|||
|
|||
|
|||
void RelocInfo::apply(int delta) { |
|||
if (RelocInfo::IsInternalReference(rmode_)) { |
|||
// absolute code pointer inside code object moves with the code object.
|
|||
int32_t* p = reinterpret_cast<int32_t*>(pc_); |
|||
*p += delta; // relocate entry
|
|||
} |
|||
// We do not use pc relative addressing on ARM, so there is
|
|||
// nothing else to do.
|
|||
} |
|||
|
|||
|
|||
Address RelocInfo::target_address() { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == RUNTIME_ENTRY); |
|||
return Assembler::target_address_at(pc_); |
|||
} |
|||
|
|||
|
|||
Address RelocInfo::target_address_address() { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == RUNTIME_ENTRY); |
|||
return reinterpret_cast<Address>(Assembler::target_address_address_at(pc_)); |
|||
} |
|||
|
|||
|
|||
void RelocInfo::set_target_address(Address target) { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == RUNTIME_ENTRY); |
|||
Assembler::set_target_address_at(pc_, target); |
|||
} |
|||
|
|||
|
|||
Object* RelocInfo::target_object() { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == EMBEDDED_OBJECT); |
|||
return reinterpret_cast<Object*>(Assembler::target_address_at(pc_)); |
|||
} |
|||
|
|||
|
|||
Object** RelocInfo::target_object_address() { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == EMBEDDED_OBJECT); |
|||
return reinterpret_cast<Object**>(Assembler::target_address_address_at(pc_)); |
|||
} |
|||
|
|||
|
|||
void RelocInfo::set_target_object(Object* target) { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == EMBEDDED_OBJECT); |
|||
Assembler::set_target_address_at(pc_, reinterpret_cast<Address>(target)); |
|||
} |
|||
|
|||
|
|||
Address* RelocInfo::target_reference_address() { |
|||
ASSERT(rmode_ == EXTERNAL_REFERENCE); |
|||
return reinterpret_cast<Address*>(Assembler::target_address_address_at(pc_)); |
|||
} |
|||
|
|||
|
|||
Address RelocInfo::call_address() { |
|||
ASSERT(IsCallInstruction()); |
|||
UNIMPLEMENTED(); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void RelocInfo::set_call_address(Address target) { |
|||
ASSERT(IsCallInstruction()); |
|||
UNIMPLEMENTED(); |
|||
} |
|||
|
|||
|
|||
Object* RelocInfo::call_object() { |
|||
ASSERT(IsCallInstruction()); |
|||
UNIMPLEMENTED(); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
Object** RelocInfo::call_object_address() { |
|||
ASSERT(IsCallInstruction()); |
|||
UNIMPLEMENTED(); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void RelocInfo::set_call_object(Object* target) { |
|||
ASSERT(IsCallInstruction()); |
|||
UNIMPLEMENTED(); |
|||
} |
|||
|
|||
|
|||
bool RelocInfo::IsCallInstruction() { |
|||
UNIMPLEMENTED(); |
|||
return false; |
|||
} |
|||
|
|||
|
|||
Operand::Operand(int32_t immediate, RelocInfo::Mode rmode) { |
|||
rm_ = no_reg; |
|||
imm32_ = immediate; |
|||
rmode_ = rmode; |
|||
} |
|||
|
|||
|
|||
Operand::Operand(const char* s) { |
|||
rm_ = no_reg; |
|||
imm32_ = reinterpret_cast<int32_t>(s); |
|||
rmode_ = RelocInfo::EMBEDDED_STRING; |
|||
} |
|||
|
|||
|
|||
Operand::Operand(const ExternalReference& f) { |
|||
rm_ = no_reg; |
|||
imm32_ = reinterpret_cast<int32_t>(f.address()); |
|||
rmode_ = RelocInfo::EXTERNAL_REFERENCE; |
|||
} |
|||
|
|||
|
|||
Operand::Operand(Object** opp) { |
|||
rm_ = no_reg; |
|||
imm32_ = reinterpret_cast<int32_t>(opp); |
|||
rmode_ = RelocInfo::NONE; |
|||
} |
|||
|
|||
|
|||
Operand::Operand(Context** cpp) { |
|||
rm_ = no_reg; |
|||
imm32_ = reinterpret_cast<int32_t>(cpp); |
|||
rmode_ = RelocInfo::NONE; |
|||
} |
|||
|
|||
|
|||
Operand::Operand(Smi* value) { |
|||
rm_ = no_reg; |
|||
imm32_ = reinterpret_cast<intptr_t>(value); |
|||
rmode_ = RelocInfo::NONE; |
|||
} |
|||
|
|||
|
|||
Operand::Operand(Register rm) { |
|||
rm_ = rm; |
|||
rs_ = no_reg; |
|||
shift_op_ = LSL; |
|||
shift_imm_ = 0; |
|||
} |
|||
|
|||
|
|||
bool Operand::is_reg() const { |
|||
return rm_.is_valid() && |
|||
rs_.is(no_reg) && |
|||
shift_op_ == LSL && |
|||
shift_imm_ == 0; |
|||
} |
|||
|
|||
|
|||
void Assembler::CheckBuffer() { |
|||
if (buffer_space() <= kGap) { |
|||
GrowBuffer(); |
|||
} |
|||
if (pc_offset() > next_buffer_check_) { |
|||
CheckConstPool(false, true); |
|||
} |
|||
} |
|||
|
|||
|
|||
void Assembler::emit(Instr x) { |
|||
CheckBuffer(); |
|||
*reinterpret_cast<Instr*>(pc_) = x; |
|||
pc_ += kInstrSize; |
|||
} |
|||
|
|||
|
|||
Address Assembler::target_address_address_at(Address pc) { |
|||
Instr instr = Memory::int32_at(pc); |
|||
// Verify that the instruction at pc is a ldr<cond> <Rd>, [pc +/- offset_12].
|
|||
ASSERT((instr & 0x0f7f0000) == 0x051f0000); |
|||
int offset = instr & 0xfff; // offset_12 is unsigned
|
|||
if ((instr & (1 << 23)) == 0) offset = -offset; // U bit defines offset sign
|
|||
// Verify that the constant pool comes after the instruction referencing it.
|
|||
ASSERT(offset >= -4); |
|||
return pc + offset + 8; |
|||
} |
|||
|
|||
|
|||
Address Assembler::target_address_at(Address pc) { |
|||
return Memory::Address_at(target_address_address_at(pc)); |
|||
} |
|||
|
|||
|
|||
void Assembler::set_target_address_at(Address pc, Address target) { |
|||
Memory::Address_at(target_address_address_at(pc)) = target; |
|||
// Intuitively, we would think it is necessary to flush the instruction cache
|
|||
// after patching a target address in the code as follows:
|
|||
// CPU::FlushICache(pc, sizeof(target));
|
|||
// However, on ARM, no instruction was actually patched by the assignment
|
|||
// above; the target address is not part of an instruction, it is patched in
|
|||
// the constant pool and is read via a data access; the instruction accessing
|
|||
// this address in the constant pool remains unchanged.
|
|||
} |
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_ASSEMBLER_ARM_INL_H_
|
File diff suppressed because it is too large
@ -0,0 +1,789 @@ |
|||
// Copyright (c) 1994-2006 Sun Microsystems Inc.
|
|||
// All Rights Reserved.
|
|||
//
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions
|
|||
// are met:
|
|||
//
|
|||
// - Redistributions of source code must retain the above copyright notice,
|
|||
// this list of conditions and the following disclaimer.
|
|||
//
|
|||
// - Redistribution in binary form must reproduce the above copyright
|
|||
// notice, this list of conditions and the following disclaimer in the
|
|||
// documentation and/or other materials provided with the
|
|||
// distribution.
|
|||
//
|
|||
// - Neither the name of Sun Microsystems or the names of contributors may
|
|||
// be used to endorse or promote products derived from this software without
|
|||
// specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|||
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|||
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|||
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
|||
// OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
// The original source code covered by the above license above has been modified
|
|||
// significantly by Google Inc.
|
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
|
|||
// A light-weight ARM Assembler
|
|||
// Generates user mode instructions for the ARM architecture up to version 5
|
|||
|
|||
#ifndef V8_ASSEMBLER_ARM_H_ |
|||
#define V8_ASSEMBLER_ARM_H_ |
|||
|
|||
#include "assembler.h" |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
// CPU Registers.
|
|||
//
|
|||
// 1) We would prefer to use an enum, but enum values are assignment-
|
|||
// compatible with int, which has caused code-generation bugs.
|
|||
//
|
|||
// 2) We would prefer to use a class instead of a struct but we don't like
|
|||
// the register initialization to depend on the particular initialization
|
|||
// order (which appears to be different on OS X, Linux, and Windows for the
|
|||
// installed versions of C++ we tried). Using a struct permits C-style
|
|||
// "initialization". Also, the Register objects cannot be const as this
|
|||
// forces initialization stubs in MSVC, making us dependent on initialization
|
|||
// order.
|
|||
//
|
|||
// 3) By not using an enum, we are possibly preventing the compiler from
|
|||
// doing certain constant folds, which may significantly reduce the
|
|||
// code generated for some assembly instructions (because they boil down
|
|||
// to a few constants). If this is a problem, we could change the code
|
|||
// such that we use an enum in optimized mode, and the struct in debug
|
|||
// mode. This way we get the compile-time error checking in debug mode
|
|||
// and best performance in optimized code.
|
|||
//
|
|||
// Core register
|
|||
struct Register { |
|||
bool is_valid() const { return 0 <= code_ && code_ < 16; } |
|||
bool is(Register reg) const { return code_ == reg.code_; } |
|||
int code() const { |
|||
ASSERT(is_valid()); |
|||
return code_; |
|||
} |
|||
int bit() const { |
|||
ASSERT(is_valid()); |
|||
return 1 << code_; |
|||
} |
|||
|
|||
// (unfortunately we can't make this private in a struct)
|
|||
int code_; |
|||
}; |
|||
|
|||
|
|||
const int kNumRegisters = 16; |
|||
|
|||
extern Register no_reg; |
|||
extern Register r0; |
|||
extern Register r1; |
|||
extern Register r2; |
|||
extern Register r3; |
|||
extern Register r4; |
|||
extern Register r5; |
|||
extern Register r6; |
|||
extern Register r7; |
|||
extern Register r8; |
|||
extern Register r9; |
|||
extern Register r10; |
|||
extern Register fp; |
|||
extern Register ip; |
|||
extern Register sp; |
|||
extern Register lr; |
|||
extern Register pc; |
|||
|
|||
|
|||
// Coprocessor register
|
|||
struct CRegister { |
|||
bool is_valid() const { return 0 <= code_ && code_ < 16; } |
|||
bool is(CRegister creg) const { return code_ == creg.code_; } |
|||
int code() const { |
|||
ASSERT(is_valid()); |
|||
return code_; |
|||
} |
|||
int bit() const { |
|||
ASSERT(is_valid()); |
|||
return 1 << code_; |
|||
} |
|||
|
|||
// (unfortunately we can't make this private in a struct)
|
|||
int code_; |
|||
}; |
|||
|
|||
|
|||
extern CRegister no_creg; |
|||
extern CRegister cr0; |
|||
extern CRegister cr1; |
|||
extern CRegister cr2; |
|||
extern CRegister cr3; |
|||
extern CRegister cr4; |
|||
extern CRegister cr5; |
|||
extern CRegister cr6; |
|||
extern CRegister cr7; |
|||
extern CRegister cr8; |
|||
extern CRegister cr9; |
|||
extern CRegister cr10; |
|||
extern CRegister cr11; |
|||
extern CRegister cr12; |
|||
extern CRegister cr13; |
|||
extern CRegister cr14; |
|||
extern CRegister cr15; |
|||
|
|||
|
|||
// Coprocessor number
|
|||
enum Coprocessor { |
|||
p0 = 0, |
|||
p1 = 1, |
|||
p2 = 2, |
|||
p3 = 3, |
|||
p4 = 4, |
|||
p5 = 5, |
|||
p6 = 6, |
|||
p7 = 7, |
|||
p8 = 8, |
|||
p9 = 9, |
|||
p10 = 10, |
|||
p11 = 11, |
|||
p12 = 12, |
|||
p13 = 13, |
|||
p14 = 14, |
|||
p15 = 15 |
|||
}; |
|||
|
|||
|
|||
// Condition field in instructions
|
|||
enum Condition { |
|||
eq = 0 << 28, |
|||
ne = 1 << 28, |
|||
cs = 2 << 28, |
|||
hs = 2 << 28, |
|||
cc = 3 << 28, |
|||
lo = 3 << 28, |
|||
mi = 4 << 28, |
|||
pl = 5 << 28, |
|||
vs = 6 << 28, |
|||
vc = 7 << 28, |
|||
hi = 8 << 28, |
|||
ls = 9 << 28, |
|||
ge = 10 << 28, |
|||
lt = 11 << 28, |
|||
gt = 12 << 28, |
|||
le = 13 << 28, |
|||
al = 14 << 28 |
|||
}; |
|||
|
|||
|
|||
// Returns the equivalent of !cc.
|
|||
INLINE(Condition NegateCondition(Condition cc)); |
|||
|
|||
|
|||
// Corresponds to transposing the operands of a comparison.
|
|||
inline Condition ReverseCondition(Condition cc) { |
|||
switch (cc) { |
|||
case lo: |
|||
return hi; |
|||
case hi: |
|||
return lo; |
|||
case hs: |
|||
return ls; |
|||
case ls: |
|||
return hs; |
|||
case lt: |
|||
return gt; |
|||
case gt: |
|||
return lt; |
|||
case ge: |
|||
return le; |
|||
case le: |
|||
return ge; |
|||
default: |
|||
return cc; |
|||
}; |
|||
} |
|||
|
|||
|
|||
// Branch hints are not used on the ARM. They are defined so that they can
|
|||
// appear in shared function signatures, but will be ignored in ARM
|
|||
// implementations.
|
|||
enum Hint { no_hint }; |
|||
|
|||
// Hints are not used on the arm. Negating is trivial.
|
|||
inline Hint NegateHint(Hint ignored) { return no_hint; } |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// Addressing modes and instruction variants
|
|||
|
|||
// Shifter operand shift operation
|
|||
enum ShiftOp { |
|||
LSL = 0 << 5, |
|||
LSR = 1 << 5, |
|||
ASR = 2 << 5, |
|||
ROR = 3 << 5, |
|||
RRX = -1 |
|||
}; |
|||
|
|||
|
|||
// Condition code updating mode
|
|||
enum SBit { |
|||
SetCC = 1 << 20, // set condition code
|
|||
LeaveCC = 0 << 20 // leave condition code unchanged
|
|||
}; |
|||
|
|||
|
|||
// Status register selection
|
|||
enum SRegister { |
|||
CPSR = 0 << 22, |
|||
SPSR = 1 << 22 |
|||
}; |
|||
|
|||
|
|||
// Status register fields
|
|||
enum SRegisterField { |
|||
CPSR_c = CPSR | 1 << 16, |
|||
CPSR_x = CPSR | 1 << 17, |
|||
CPSR_s = CPSR | 1 << 18, |
|||
CPSR_f = CPSR | 1 << 19, |
|||
SPSR_c = SPSR | 1 << 16, |
|||
SPSR_x = SPSR | 1 << 17, |
|||
SPSR_s = SPSR | 1 << 18, |
|||
SPSR_f = SPSR | 1 << 19 |
|||
}; |
|||
|
|||
// Status register field mask (or'ed SRegisterField enum values)
|
|||
typedef uint32_t SRegisterFieldMask; |
|||
|
|||
|
|||
// Memory operand addressing mode
|
|||
enum AddrMode { |
|||
// bit encoding P U W
|
|||
Offset = (8|4|0) << 21, // offset (without writeback to base)
|
|||
PreIndex = (8|4|1) << 21, // pre-indexed addressing with writeback
|
|||
PostIndex = (0|4|0) << 21, // post-indexed addressing with writeback
|
|||
NegOffset = (8|0|0) << 21, // negative offset (without writeback to base)
|
|||
NegPreIndex = (8|0|1) << 21, // negative pre-indexed with writeback
|
|||
NegPostIndex = (0|0|0) << 21 // negative post-indexed with writeback
|
|||
}; |
|||
|
|||
|
|||
// Load/store multiple addressing mode
|
|||
enum BlockAddrMode { |
|||
// bit encoding P U W
|
|||
da = (0|0|0) << 21, // decrement after
|
|||
ia = (0|4|0) << 21, // increment after
|
|||
db = (8|0|0) << 21, // decrement before
|
|||
ib = (8|4|0) << 21, // increment before
|
|||
da_w = (0|0|1) << 21, // decrement after with writeback to base
|
|||
ia_w = (0|4|1) << 21, // increment after with writeback to base
|
|||
db_w = (8|0|1) << 21, // decrement before with writeback to base
|
|||
ib_w = (8|4|1) << 21 // increment before with writeback to base
|
|||
}; |
|||
|
|||
|
|||
// Coprocessor load/store operand size
|
|||
enum LFlag { |
|||
Long = 1 << 22, // long load/store coprocessor
|
|||
Short = 0 << 22 // short load/store coprocessor
|
|||
}; |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// Machine instruction Operands
|
|||
|
|||
// Class Operand represents a shifter operand in data processing instructions
|
|||
class Operand BASE_EMBEDDED { |
|||
public: |
|||
// immediate
|
|||
INLINE(explicit Operand(int32_t immediate, |
|||
RelocInfo::Mode rmode = RelocInfo::NONE)); |
|||
INLINE(explicit Operand(const ExternalReference& f)); |
|||
INLINE(explicit Operand(const char* s)); |
|||
INLINE(explicit Operand(Object** opp)); |
|||
INLINE(explicit Operand(Context** cpp)); |
|||
explicit Operand(Handle<Object> handle); |
|||
INLINE(explicit Operand(Smi* value)); |
|||
|
|||
// rm
|
|||
INLINE(explicit Operand(Register rm)); |
|||
|
|||
// rm <shift_op> shift_imm
|
|||
explicit Operand(Register rm, ShiftOp shift_op, int shift_imm); |
|||
|
|||
// rm <shift_op> rs
|
|||
explicit Operand(Register rm, ShiftOp shift_op, Register rs); |
|||
|
|||
// Return true if this is a register operand.
|
|||
INLINE(bool is_reg() const); |
|||
|
|||
Register rm() const { return rm_; } |
|||
|
|||
private: |
|||
Register rm_; |
|||
Register rs_; |
|||
ShiftOp shift_op_; |
|||
int shift_imm_; // valid if rm_ != no_reg && rs_ == no_reg
|
|||
int32_t imm32_; // valid if rm_ == no_reg
|
|||
RelocInfo::Mode rmode_; |
|||
|
|||
friend class Assembler; |
|||
}; |
|||
|
|||
|
|||
// Class MemOperand represents a memory operand in load and store instructions
|
|||
class MemOperand BASE_EMBEDDED { |
|||
public: |
|||
// [rn +/- offset] Offset/NegOffset
|
|||
// [rn +/- offset]! PreIndex/NegPreIndex
|
|||
// [rn], +/- offset PostIndex/NegPostIndex
|
|||
// offset is any signed 32-bit value; offset is first loaded to register ip if
|
|||
// it does not fit the addressing mode (12-bit unsigned and sign bit)
|
|||
explicit MemOperand(Register rn, int32_t offset = 0, AddrMode am = Offset); |
|||
|
|||
// [rn +/- rm] Offset/NegOffset
|
|||
// [rn +/- rm]! PreIndex/NegPreIndex
|
|||
// [rn], +/- rm PostIndex/NegPostIndex
|
|||
explicit MemOperand(Register rn, Register rm, AddrMode am = Offset); |
|||
|
|||
// [rn +/- rm <shift_op> shift_imm] Offset/NegOffset
|
|||
// [rn +/- rm <shift_op> shift_imm]! PreIndex/NegPreIndex
|
|||
// [rn], +/- rm <shift_op> shift_imm PostIndex/NegPostIndex
|
|||
explicit MemOperand(Register rn, Register rm, |
|||
ShiftOp shift_op, int shift_imm, AddrMode am = Offset); |
|||
|
|||
private: |
|||
Register rn_; // base
|
|||
Register rm_; // register offset
|
|||
int32_t offset_; // valid if rm_ == no_reg
|
|||
ShiftOp shift_op_; |
|||
int shift_imm_; // valid if rm_ != no_reg && rs_ == no_reg
|
|||
AddrMode am_; // bits P, U, and W
|
|||
|
|||
friend class Assembler; |
|||
}; |
|||
|
|||
|
|||
typedef int32_t Instr; |
|||
|
|||
|
|||
class Assembler : public Malloced { |
|||
public: |
|||
// Create an assembler. Instructions and relocation information are emitted
|
|||
// into a buffer, with the instructions starting from the beginning and the
|
|||
// relocation information starting from the end of the buffer. See CodeDesc
|
|||
// for a detailed comment on the layout (globals.h).
|
|||
//
|
|||
// If the provided buffer is NULL, the assembler allocates and grows its own
|
|||
// buffer, and buffer_size determines the initial buffer size. The buffer is
|
|||
// owned by the assembler and deallocated upon destruction of the assembler.
|
|||
//
|
|||
// If the provided buffer is not NULL, the assembler uses the provided buffer
|
|||
// for code generation and assumes its size to be buffer_size. If the buffer
|
|||
// is too small, a fatal error occurs. No deallocation of the buffer is done
|
|||
// upon destruction of the assembler.
|
|||
Assembler(void* buffer, int buffer_size); |
|||
~Assembler(); |
|||
|
|||
// GetCode emits any pending (non-emitted) code and fills the descriptor
|
|||
// desc. GetCode() is idempotent; it returns the same result if no other
|
|||
// Assembler functions are invoked in between GetCode() calls.
|
|||
void GetCode(CodeDesc* desc); |
|||
|
|||
// Label operations & relative jumps (PPUM Appendix D)
|
|||
//
|
|||
// Takes a branch opcode (cc) and a label (L) and generates
|
|||
// either a backward branch or a forward branch and links it
|
|||
// to the label fixup chain. Usage:
|
|||
//
|
|||
// Label L; // unbound label
|
|||
// j(cc, &L); // forward branch to unbound label
|
|||
// bind(&L); // bind label to the current pc
|
|||
// j(cc, &L); // backward branch to bound label
|
|||
// bind(&L); // illegal: a label may be bound only once
|
|||
//
|
|||
// Note: The same Label can be used for forward and backward branches
|
|||
// but it may be bound only once.
|
|||
|
|||
void bind(Label* L); // binds an unbound label L to the current code position
|
|||
|
|||
// Returns the branch offset to the given label from the current code position
|
|||
// Links the label to the current position if it is still unbound
|
|||
// Manages the jump elimination optimization if the second parameter is true.
|
|||
int branch_offset(Label* L, bool jump_elimination_allowed); |
|||
|
|||
// Return the address in the constant pool of the code target address used by
|
|||
// the branch/call instruction at pc.
|
|||
INLINE(static Address target_address_address_at(Address pc)); |
|||
|
|||
// Read/Modify the code target address in the branch/call instruction at pc.
|
|||
INLINE(static Address target_address_at(Address pc)); |
|||
INLINE(static void set_target_address_at(Address pc, Address target)); |
|||
|
|||
// Distance between the instruction referring to the address of the call
|
|||
// target (ldr pc, [target addr in const pool]) and the return address
|
|||
static const int kTargetAddrToReturnAddrDist = sizeof(Instr); |
|||
|
|||
|
|||
// ---------------------------------------------------------------------------
|
|||
// Code generation
|
|||
|
|||
// Insert the smallest number of nop instructions
|
|||
// possible to align the pc offset to a multiple
|
|||
// of m. m must be a power of 2 (>= 4).
|
|||
void Align(int m); |
|||
|
|||
// Branch instructions
|
|||
void b(int branch_offset, Condition cond = al); |
|||
void bl(int branch_offset, Condition cond = al); |
|||
void blx(int branch_offset); // v5 and above
|
|||
void blx(Register target, Condition cond = al); // v5 and above
|
|||
void bx(Register target, Condition cond = al); // v5 and above, plus v4t
|
|||
|
|||
// Convenience branch instructions using labels
|
|||
void b(Label* L, Condition cond = al) { |
|||
b(branch_offset(L, cond == al), cond); |
|||
} |
|||
void b(Condition cond, Label* L) { b(branch_offset(L, cond == al), cond); } |
|||
void bl(Label* L, Condition cond = al) { bl(branch_offset(L, false), cond); } |
|||
void bl(Condition cond, Label* L) { bl(branch_offset(L, false), cond); } |
|||
void blx(Label* L) { blx(branch_offset(L, false)); } // v5 and above
|
|||
|
|||
// Data-processing instructions
|
|||
void and_(Register dst, Register src1, const Operand& src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void eor(Register dst, Register src1, const Operand& src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void sub(Register dst, Register src1, const Operand& src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
void sub(Register dst, Register src1, Register src2, |
|||
SBit s = LeaveCC, Condition cond = al) { |
|||
sub(dst, src1, Operand(src2), s, cond); |
|||
} |
|||
|
|||
void rsb(Register dst, Register src1, const Operand& src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void add(Register dst, Register src1, const Operand& src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void adc(Register dst, Register src1, const Operand& src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void sbc(Register dst, Register src1, const Operand& src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void rsc(Register dst, Register src1, const Operand& src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void tst(Register src1, const Operand& src2, Condition cond = al); |
|||
void tst(Register src1, Register src2, Condition cond = al) { |
|||
tst(src1, Operand(src2), cond); |
|||
} |
|||
|
|||
void teq(Register src1, const Operand& src2, Condition cond = al); |
|||
|
|||
void cmp(Register src1, const Operand& src2, Condition cond = al); |
|||
void cmp(Register src1, Register src2, Condition cond = al) { |
|||
cmp(src1, Operand(src2), cond); |
|||
} |
|||
|
|||
void cmn(Register src1, const Operand& src2, Condition cond = al); |
|||
|
|||
void orr(Register dst, Register src1, const Operand& src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
void orr(Register dst, Register src1, Register src2, |
|||
SBit s = LeaveCC, Condition cond = al) { |
|||
orr(dst, src1, Operand(src2), s, cond); |
|||
} |
|||
|
|||
void mov(Register dst, const Operand& src, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
void mov(Register dst, Register src, SBit s = LeaveCC, Condition cond = al) { |
|||
mov(dst, Operand(src), s, cond); |
|||
} |
|||
|
|||
void bic(Register dst, Register src1, const Operand& src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void mvn(Register dst, const Operand& src, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
// Multiply instructions
|
|||
|
|||
void mla(Register dst, Register src1, Register src2, Register srcA, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void mul(Register dst, Register src1, Register src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void smlal(Register dstL, Register dstH, Register src1, Register src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void smull(Register dstL, Register dstH, Register src1, Register src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void umlal(Register dstL, Register dstH, Register src1, Register src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
void umull(Register dstL, Register dstH, Register src1, Register src2, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
// Miscellaneous arithmetic instructions
|
|||
|
|||
void clz(Register dst, Register src, Condition cond = al); // v5 and above
|
|||
|
|||
// Status register access instructions
|
|||
|
|||
void mrs(Register dst, SRegister s, Condition cond = al); |
|||
void msr(SRegisterFieldMask fields, const Operand& src, Condition cond = al); |
|||
|
|||
// Load/Store instructions
|
|||
void ldr(Register dst, const MemOperand& src, Condition cond = al); |
|||
void str(Register src, const MemOperand& dst, Condition cond = al); |
|||
void ldrb(Register dst, const MemOperand& src, Condition cond = al); |
|||
void strb(Register src, const MemOperand& dst, Condition cond = al); |
|||
void ldrh(Register dst, const MemOperand& src, Condition cond = al); |
|||
void strh(Register src, const MemOperand& dst, Condition cond = al); |
|||
void ldrsb(Register dst, const MemOperand& src, Condition cond = al); |
|||
void ldrsh(Register dst, const MemOperand& src, Condition cond = al); |
|||
|
|||
// Load/Store multiple instructions
|
|||
void ldm(BlockAddrMode am, Register base, RegList dst, Condition cond = al); |
|||
void stm(BlockAddrMode am, Register base, RegList src, Condition cond = al); |
|||
|
|||
// Semaphore instructions
|
|||
void swp(Register dst, Register src, Register base, Condition cond = al); |
|||
void swpb(Register dst, Register src, Register base, Condition cond = al); |
|||
|
|||
// Exception-generating instructions and debugging support
|
|||
void stop(const char* msg); |
|||
|
|||
void bkpt(uint32_t imm16); // v5 and above
|
|||
void swi(uint32_t imm24, Condition cond = al); |
|||
|
|||
// Coprocessor instructions
|
|||
|
|||
void cdp(Coprocessor coproc, int opcode_1, |
|||
CRegister crd, CRegister crn, CRegister crm, |
|||
int opcode_2, Condition cond = al); |
|||
|
|||
void cdp2(Coprocessor coproc, int opcode_1, |
|||
CRegister crd, CRegister crn, CRegister crm, |
|||
int opcode_2); // v5 and above
|
|||
|
|||
void mcr(Coprocessor coproc, int opcode_1, |
|||
Register rd, CRegister crn, CRegister crm, |
|||
int opcode_2 = 0, Condition cond = al); |
|||
|
|||
void mcr2(Coprocessor coproc, int opcode_1, |
|||
Register rd, CRegister crn, CRegister crm, |
|||
int opcode_2 = 0); // v5 and above
|
|||
|
|||
void mrc(Coprocessor coproc, int opcode_1, |
|||
Register rd, CRegister crn, CRegister crm, |
|||
int opcode_2 = 0, Condition cond = al); |
|||
|
|||
void mrc2(Coprocessor coproc, int opcode_1, |
|||
Register rd, CRegister crn, CRegister crm, |
|||
int opcode_2 = 0); // v5 and above
|
|||
|
|||
void ldc(Coprocessor coproc, CRegister crd, const MemOperand& src, |
|||
LFlag l = Short, Condition cond = al); |
|||
void ldc(Coprocessor coproc, CRegister crd, Register base, int option, |
|||
LFlag l = Short, Condition cond = al); |
|||
|
|||
void ldc2(Coprocessor coproc, CRegister crd, const MemOperand& src, |
|||
LFlag l = Short); // v5 and above
|
|||
void ldc2(Coprocessor coproc, CRegister crd, Register base, int option, |
|||
LFlag l = Short); // v5 and above
|
|||
|
|||
void stc(Coprocessor coproc, CRegister crd, const MemOperand& dst, |
|||
LFlag l = Short, Condition cond = al); |
|||
void stc(Coprocessor coproc, CRegister crd, Register base, int option, |
|||
LFlag l = Short, Condition cond = al); |
|||
|
|||
void stc2(Coprocessor coproc, CRegister crd, const MemOperand& dst, |
|||
LFlag l = Short); // v5 and above
|
|||
void stc2(Coprocessor coproc, CRegister crd, Register base, int option, |
|||
LFlag l = Short); // v5 and above
|
|||
|
|||
// Pseudo instructions
|
|||
void nop() { mov(r0, Operand(r0)); } |
|||
|
|||
void push(Register src) { |
|||
str(src, MemOperand(sp, 4, NegPreIndex), al); |
|||
} |
|||
|
|||
void pop(Register dst) { |
|||
ldr(dst, MemOperand(sp, 4, PostIndex), al); |
|||
} |
|||
|
|||
void pop() { |
|||
add(sp, sp, Operand(kPointerSize)); |
|||
} |
|||
|
|||
// Load effective address of memory operand x into register dst
|
|||
void lea(Register dst, const MemOperand& x, |
|||
SBit s = LeaveCC, Condition cond = al); |
|||
|
|||
// Jump unconditionally to given label.
|
|||
void jmp(Label* L) { b(L, al); } |
|||
|
|||
|
|||
// Debugging
|
|||
|
|||
// Record a comment relocation entry that can be used by a disassembler.
|
|||
// Use --debug_code to enable.
|
|||
void RecordComment(const char* msg); |
|||
|
|||
void RecordPosition(int pos); |
|||
void RecordStatementPosition(int pos); |
|||
void WriteRecordedPositions(); |
|||
|
|||
int pc_offset() const { return pc_ - buffer_; } |
|||
int current_position() const { return current_position_; } |
|||
int current_statement_position() const { return current_position_; } |
|||
|
|||
protected: |
|||
int buffer_space() const { return reloc_info_writer.pos() - pc_; } |
|||
|
|||
// Read/patch instructions
|
|||
Instr instr_at(byte* pc) { return *reinterpret_cast<Instr*>(pc); } |
|||
void instr_at_put(byte* pc, Instr instr) { |
|||
*reinterpret_cast<Instr*>(pc) = instr; |
|||
} |
|||
Instr instr_at(int pos) { return *reinterpret_cast<Instr*>(buffer_ + pos); } |
|||
void instr_at_put(int pos, Instr instr) { |
|||
*reinterpret_cast<Instr*>(buffer_ + pos) = instr; |
|||
} |
|||
|
|||
// Decode branch instruction at pos and return branch target pos
|
|||
int target_at(int pos); |
|||
|
|||
// Patch branch instruction at pos to branch to given branch target pos
|
|||
void target_at_put(int pos, int target_pos); |
|||
|
|||
// Check if is time to emit a constant pool for pending reloc info entries
|
|||
void CheckConstPool(bool force_emit, bool require_jump); |
|||
|
|||
// Block the emission of the constant pool before pc_offset
|
|||
void BlockConstPoolBefore(int pc_offset) { |
|||
if (no_const_pool_before_ < pc_offset) no_const_pool_before_ = pc_offset; |
|||
} |
|||
|
|||
private: |
|||
// Code buffer:
|
|||
// The buffer into which code and relocation info are generated.
|
|||
byte* buffer_; |
|||
int buffer_size_; |
|||
// True if the assembler owns the buffer, false if buffer is external.
|
|||
bool own_buffer_; |
|||
|
|||
// Buffer size and constant pool distance are checked together at regular
|
|||
// intervals of kBufferCheckInterval emitted bytes
|
|||
static const int kBufferCheckInterval = 1*KB/2; |
|||
int next_buffer_check_; // pc offset of next buffer check
|
|||
|
|||
// Code generation
|
|||
static const int kInstrSize = sizeof(Instr); // signed size
|
|||
// The relocation writer's position is at least kGap bytes below the end of
|
|||
// the generated instructions. This is so that multi-instruction sequences do
|
|||
// not have to check for overflow. The same is true for writes of large
|
|||
// relocation info entries.
|
|||
static const int kGap = 32; |
|||
byte* pc_; // the program counter; moves forward
|
|||
|
|||
// Constant pool generation
|
|||
// Pools are emitted in the instruction stream, preferably after unconditional
|
|||
// jumps or after returns from functions (in dead code locations).
|
|||
// If a long code sequence does not contain unconditional jumps, it is
|
|||
// necessary to emit the constant pool before the pool gets too far from the
|
|||
// location it is accessed from. In this case, we emit a jump over the emitted
|
|||
// constant pool.
|
|||
// Constants in the pool may be addresses of functions that gets relocated;
|
|||
// if so, a relocation info entry is associated to the constant pool entry.
|
|||
|
|||
// Repeated checking whether the constant pool should be emitted is rather
|
|||
// expensive. By default we only check again once a number of instructions
|
|||
// has been generated. That also means that the sizing of the buffers is not
|
|||
// an exact science, and that we rely on some slop to not overrun buffers.
|
|||
static const int kCheckConstIntervalInst = 32; |
|||
static const int kCheckConstInterval = kCheckConstIntervalInst * kInstrSize; |
|||
|
|||
|
|||
// Pools are emitted after function return and in dead code at (more or less)
|
|||
// regular intervals of kDistBetweenPools bytes
|
|||
static const int kDistBetweenPools = 1*KB; |
|||
|
|||
// Constants in pools are accessed via pc relative addressing, which can
|
|||
// reach +/-4KB thereby defining a maximum distance between the instruction
|
|||
// and the accessed constant. We satisfy this constraint by limiting the
|
|||
// distance between pools.
|
|||
static const int kMaxDistBetweenPools = 4*KB - 2*kBufferCheckInterval; |
|||
|
|||
// Emission of the constant pool may be blocked in some code sequences
|
|||
int no_const_pool_before_; // block emission before this pc offset
|
|||
|
|||
// Keep track of the last emitted pool to guarantee a maximal distance
|
|||
int last_const_pool_end_; // pc offset following the last constant pool
|
|||
|
|||
// Relocation info generation
|
|||
// Each relocation is encoded as a variable size value
|
|||
static const int kMaxRelocSize = RelocInfoWriter::kMaxSize; |
|||
RelocInfoWriter reloc_info_writer; |
|||
// Relocation info records are also used during code generation as temporary
|
|||
// containers for constants and code target addresses until they are emitted
|
|||
// to the constant pool. These pending relocation info records are temporarily
|
|||
// stored in a separate buffer until a constant pool is emitted.
|
|||
// If every instruction in a long sequence is accessing the pool, we need one
|
|||
// pending relocation entry per instruction.
|
|||
static const int kMaxNumPRInfo = kMaxDistBetweenPools/kInstrSize; |
|||
RelocInfo prinfo_[kMaxNumPRInfo]; // the buffer of pending relocation info
|
|||
int num_prinfo_; // number of pending reloc info entries in the buffer
|
|||
|
|||
// The bound position, before this we cannot do instruction elimination.
|
|||
int last_bound_pos_; |
|||
|
|||
// source position information
|
|||
int current_position_; |
|||
int current_statement_position_; |
|||
int written_position_; |
|||
int written_statement_position_; |
|||
|
|||
// Code emission
|
|||
inline void CheckBuffer(); |
|||
void GrowBuffer(); |
|||
inline void emit(Instr x); |
|||
|
|||
// Instruction generation
|
|||
void addrmod1(Instr instr, Register rn, Register rd, const Operand& x); |
|||
void addrmod2(Instr instr, Register rd, const MemOperand& x); |
|||
void addrmod3(Instr instr, Register rd, const MemOperand& x); |
|||
void addrmod4(Instr instr, Register rn, RegList rl); |
|||
void addrmod5(Instr instr, CRegister crd, const MemOperand& x); |
|||
|
|||
// Labels
|
|||
void print(Label* L); |
|||
void bind_to(Label* L, int pos); |
|||
void link_to(Label* L, Label* appendix); |
|||
void next(Label* L); |
|||
|
|||
// Record reloc info for current pc_
|
|||
void RecordRelocInfo(RelocInfo::Mode rmode, intptr_t data = 0); |
|||
}; |
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_ASSEMBLER_ARM_H_
|
@ -0,0 +1,302 @@ |
|||
// Copyright (c) 1994-2006 Sun Microsystems Inc.
|
|||
// All Rights Reserved.
|
|||
//
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// - Redistributions of source code must retain the above copyright notice,
|
|||
// this list of conditions and the following disclaimer.
|
|||
//
|
|||
// - Redistribution in binary form must reproduce the above copyright
|
|||
// notice, this list of conditions and the following disclaimer in the
|
|||
// documentation and/or other materials provided with the distribution.
|
|||
//
|
|||
// - Neither the name of Sun Microsystems or the names of contributors may
|
|||
// be used to endorse or promote products derived from this software without
|
|||
// specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|||
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|||
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|||
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|||
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|||
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|||
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
// The original source code covered by the above license above has been
|
|||
// modified significantly by Google Inc.
|
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
|
|||
// A light-weight IA32 Assembler.
|
|||
|
|||
#ifndef V8_ASSEMBLER_IA32_INL_H_ |
|||
#define V8_ASSEMBLER_IA32_INL_H_ |
|||
|
|||
#include "cpu.h" |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
Condition NegateCondition(Condition cc) { |
|||
return static_cast<Condition>(cc ^ 1); |
|||
} |
|||
|
|||
|
|||
// The modes possibly affected by apply must be in kApplyMask.
|
|||
void RelocInfo::apply(int delta) { |
|||
if (rmode_ == RUNTIME_ENTRY || IsCodeTarget(rmode_)) { |
|||
int32_t* p = reinterpret_cast<int32_t*>(pc_); |
|||
*p -= delta; // relocate entry
|
|||
} else if (rmode_ == JS_RETURN && IsCallInstruction()) { |
|||
// Special handling of js_return when a break point is set (call
|
|||
// instruction has been inserted).
|
|||
int32_t* p = reinterpret_cast<int32_t*>(pc_ + 1); |
|||
*p -= delta; // relocate entry
|
|||
} else if (IsInternalReference(rmode_)) { |
|||
// absolute code pointer inside code object moves with the code object.
|
|||
int32_t* p = reinterpret_cast<int32_t*>(pc_); |
|||
*p += delta; // relocate entry
|
|||
} |
|||
} |
|||
|
|||
|
|||
Address RelocInfo::target_address() { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == RUNTIME_ENTRY); |
|||
return Assembler::target_address_at(pc_); |
|||
} |
|||
|
|||
|
|||
Address RelocInfo::target_address_address() { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == RUNTIME_ENTRY); |
|||
return reinterpret_cast<Address>(pc_); |
|||
} |
|||
|
|||
|
|||
void RelocInfo::set_target_address(Address target) { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == RUNTIME_ENTRY); |
|||
Assembler::set_target_address_at(pc_, target); |
|||
} |
|||
|
|||
|
|||
Object* RelocInfo::target_object() { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == EMBEDDED_OBJECT); |
|||
return *reinterpret_cast<Object**>(pc_); |
|||
} |
|||
|
|||
|
|||
Object** RelocInfo::target_object_address() { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == EMBEDDED_OBJECT); |
|||
return reinterpret_cast<Object**>(pc_); |
|||
} |
|||
|
|||
|
|||
void RelocInfo::set_target_object(Object* target) { |
|||
ASSERT(IsCodeTarget(rmode_) || rmode_ == EMBEDDED_OBJECT); |
|||
*reinterpret_cast<Object**>(pc_) = target; |
|||
} |
|||
|
|||
|
|||
Address* RelocInfo::target_reference_address() { |
|||
ASSERT(rmode_ == RelocInfo::EXTERNAL_REFERENCE); |
|||
return reinterpret_cast<Address*>(pc_); |
|||
} |
|||
|
|||
|
|||
Address RelocInfo::call_address() { |
|||
ASSERT(IsCallInstruction()); |
|||
return Assembler::target_address_at(pc_ + 1); |
|||
} |
|||
|
|||
|
|||
void RelocInfo::set_call_address(Address target) { |
|||
ASSERT(IsCallInstruction()); |
|||
Assembler::set_target_address_at(pc_ + 1, target); |
|||
} |
|||
|
|||
|
|||
Object* RelocInfo::call_object() { |
|||
ASSERT(IsCallInstruction()); |
|||
return *call_object_address(); |
|||
} |
|||
|
|||
|
|||
Object** RelocInfo::call_object_address() { |
|||
ASSERT(IsCallInstruction()); |
|||
return reinterpret_cast<Object**>(pc_ + 1); |
|||
} |
|||
|
|||
|
|||
void RelocInfo::set_call_object(Object* target) { |
|||
ASSERT(IsCallInstruction()); |
|||
*call_object_address() = target; |
|||
} |
|||
|
|||
|
|||
bool RelocInfo::IsCallInstruction() { |
|||
return *pc_ == 0xE8; |
|||
} |
|||
|
|||
|
|||
Immediate::Immediate(int x) { |
|||
x_ = x; |
|||
rmode_ = RelocInfo::NONE; |
|||
} |
|||
|
|||
|
|||
Immediate::Immediate(const ExternalReference& ext) { |
|||
x_ = reinterpret_cast<int32_t>(ext.address()); |
|||
rmode_ = RelocInfo::EXTERNAL_REFERENCE; |
|||
} |
|||
|
|||
Immediate::Immediate(const char* s) { |
|||
x_ = reinterpret_cast<int32_t>(s); |
|||
rmode_ = RelocInfo::EMBEDDED_STRING; |
|||
} |
|||
|
|||
|
|||
Immediate::Immediate(Label *internal_offset) { |
|||
x_ = reinterpret_cast<int32_t>(internal_offset); |
|||
rmode_ = RelocInfo::INTERNAL_REFERENCE; |
|||
} |
|||
|
|||
|
|||
Immediate::Immediate(Handle<Object> handle) { |
|||
// Verify all Objects referred by code are NOT in new space.
|
|||
Object* obj = *handle; |
|||
ASSERT(!Heap::InNewSpace(obj)); |
|||
if (obj->IsHeapObject()) { |
|||
x_ = reinterpret_cast<intptr_t>(handle.location()); |
|||
rmode_ = RelocInfo::EMBEDDED_OBJECT; |
|||
} else { |
|||
// no relocation needed
|
|||
x_ = reinterpret_cast<intptr_t>(obj); |
|||
rmode_ = RelocInfo::NONE; |
|||
} |
|||
} |
|||
|
|||
|
|||
Immediate::Immediate(Smi* value) { |
|||
x_ = reinterpret_cast<intptr_t>(value); |
|||
rmode_ = RelocInfo::NONE; |
|||
} |
|||
|
|||
|
|||
void Assembler::emit(uint32_t x) { |
|||
*reinterpret_cast<uint32_t*>(pc_) = x; |
|||
pc_ += sizeof(uint32_t); |
|||
} |
|||
|
|||
|
|||
void Assembler::emit(Handle<Object> handle) { |
|||
// Verify all Objects referred by code are NOT in new space.
|
|||
Object* obj = *handle; |
|||
ASSERT(!Heap::InNewSpace(obj)); |
|||
if (obj->IsHeapObject()) { |
|||
emit(reinterpret_cast<intptr_t>(handle.location()), |
|||
RelocInfo::EMBEDDED_OBJECT); |
|||
} else { |
|||
// no relocation needed
|
|||
emit(reinterpret_cast<intptr_t>(obj)); |
|||
} |
|||
} |
|||
|
|||
|
|||
void Assembler::emit(uint32_t x, RelocInfo::Mode rmode) { |
|||
if (rmode != RelocInfo::NONE) RecordRelocInfo(rmode); |
|||
emit(x); |
|||
} |
|||
|
|||
|
|||
void Assembler::emit(const Immediate& x) { |
|||
if (x.rmode_ == RelocInfo::INTERNAL_REFERENCE) { |
|||
Label* label = reinterpret_cast<Label*>(x.x_); |
|||
emit_code_relative_offset(label); |
|||
return; |
|||
} |
|||
if (x.rmode_ != RelocInfo::NONE) RecordRelocInfo(x.rmode_); |
|||
emit(x.x_); |
|||
} |
|||
|
|||
|
|||
void Assembler::emit_code_relative_offset(Label* label) { |
|||
if (label->is_bound()) { |
|||
int32_t pos; |
|||
pos = label->pos() + Code::kHeaderSize - kHeapObjectTag; |
|||
emit(pos); |
|||
} else { |
|||
emit_disp(label, Displacement::CODE_RELATIVE); |
|||
} |
|||
} |
|||
|
|||
|
|||
void Assembler::emit_w(const Immediate& x) { |
|||
ASSERT(x.rmode_ == RelocInfo::NONE); |
|||
uint16_t value = static_cast<uint16_t>(x.x_); |
|||
reinterpret_cast<uint16_t*>(pc_)[0] = value; |
|||
pc_ += sizeof(uint16_t); |
|||
} |
|||
|
|||
|
|||
Address Assembler::target_address_at(Address pc) { |
|||
return pc + sizeof(int32_t) + *reinterpret_cast<int32_t*>(pc); |
|||
} |
|||
|
|||
|
|||
void Assembler::set_target_address_at(Address pc, Address target) { |
|||
int32_t* p = reinterpret_cast<int32_t*>(pc); |
|||
*p = target - (pc + sizeof(int32_t)); |
|||
CPU::FlushICache(p, sizeof(int32_t)); |
|||
} |
|||
|
|||
|
|||
Displacement Assembler::disp_at(Label* L) { |
|||
return Displacement(long_at(L->pos())); |
|||
} |
|||
|
|||
|
|||
void Assembler::disp_at_put(Label* L, Displacement disp) { |
|||
long_at_put(L->pos(), disp.data()); |
|||
} |
|||
|
|||
|
|||
void Assembler::emit_disp(Label* L, Displacement::Type type) { |
|||
Displacement disp(L, type); |
|||
L->link_to(pc_offset()); |
|||
emit(static_cast<int>(disp.data())); |
|||
} |
|||
|
|||
|
|||
void Operand::set_modrm(int mod, Register rm) { |
|||
ASSERT((mod & -4) == 0); |
|||
buf_[0] = mod << 6 | rm.code(); |
|||
len_ = 1; |
|||
} |
|||
|
|||
|
|||
void Operand::set_dispr(int32_t disp, RelocInfo::Mode rmode) { |
|||
ASSERT(len_ == 1 || len_ == 2); |
|||
int32_t* p = reinterpret_cast<int32_t*>(&buf_[len_]); |
|||
*p = disp; |
|||
len_ += sizeof(int32_t); |
|||
rmode_ = rmode; |
|||
} |
|||
|
|||
Operand::Operand(Register reg) { |
|||
// reg
|
|||
set_modrm(3, reg); |
|||
} |
|||
|
|||
|
|||
Operand::Operand(int32_t disp, RelocInfo::Mode rmode) { |
|||
// [disp/r]
|
|||
set_modrm(0, ebp); |
|||
set_dispr(disp, rmode); |
|||
} |
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_ASSEMBLER_IA32_INL_H_
|
File diff suppressed because it is too large
@ -0,0 +1,863 @@ |
|||
// Copyright (c) 1994-2006 Sun Microsystems Inc.
|
|||
// All Rights Reserved.
|
|||
//
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// - Redistributions of source code must retain the above copyright notice,
|
|||
// this list of conditions and the following disclaimer.
|
|||
//
|
|||
// - Redistribution in binary form must reproduce the above copyright
|
|||
// notice, this list of conditions and the following disclaimer in the
|
|||
// documentation and/or other materials provided with the distribution.
|
|||
//
|
|||
// - Neither the name of Sun Microsystems or the names of contributors may
|
|||
// be used to endorse or promote products derived from this software without
|
|||
// specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|||
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|||
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|||
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|||
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|||
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|||
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
// The original source code covered by the above license above has been
|
|||
// modified significantly by Google Inc.
|
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
|
|||
// A light-weight IA32 Assembler.
|
|||
|
|||
#ifndef V8_ASSEMBLER_IA32_H_ |
|||
#define V8_ASSEMBLER_IA32_H_ |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
// CPU Registers.
|
|||
//
|
|||
// 1) We would prefer to use an enum, but enum values are assignment-
|
|||
// compatible with int, which has caused code-generation bugs.
|
|||
//
|
|||
// 2) We would prefer to use a class instead of a struct but we don't like
|
|||
// the register initialization to depend on the particular initialization
|
|||
// order (which appears to be different on OS X, Linux, and Windows for the
|
|||
// installed versions of C++ we tried). Using a struct permits C-style
|
|||
// "initialization". Also, the Register objects cannot be const as this
|
|||
// forces initialization stubs in MSVC, making us dependent on initialization
|
|||
// order.
|
|||
//
|
|||
// 3) By not using an enum, we are possibly preventing the compiler from
|
|||
// doing certain constant folds, which may significantly reduce the
|
|||
// code generated for some assembly instructions (because they boil down
|
|||
// to a few constants). If this is a problem, we could change the code
|
|||
// such that we use an enum in optimized mode, and the struct in debug
|
|||
// mode. This way we get the compile-time error checking in debug mode
|
|||
// and best performance in optimized code.
|
|||
//
|
|||
struct Register { |
|||
bool is_valid() const { return 0 <= code_ && code_ < 8; } |
|||
bool is(Register reg) const { return code_ == reg.code_; } |
|||
// eax, ebx, ecx and edx are byte registers, the rest are not.
|
|||
bool is_byte_register() const { return code_ <= 3; } |
|||
int code() const { |
|||
ASSERT(is_valid()); |
|||
return code_; |
|||
} |
|||
int bit() const { |
|||
ASSERT(is_valid()); |
|||
return 1 << code_; |
|||
} |
|||
|
|||
// (unfortunately we can't make this private in a struct)
|
|||
int code_; |
|||
}; |
|||
|
|||
const int kNumRegisters = 8; |
|||
|
|||
extern Register eax; |
|||
extern Register ecx; |
|||
extern Register edx; |
|||
extern Register ebx; |
|||
extern Register esp; |
|||
extern Register ebp; |
|||
extern Register esi; |
|||
extern Register edi; |
|||
extern Register no_reg; |
|||
|
|||
|
|||
struct XMMRegister { |
|||
bool is_valid() const { return 0 <= code_ && code_ < 2; } // currently
|
|||
int code() const { |
|||
ASSERT(is_valid()); |
|||
return code_; |
|||
} |
|||
|
|||
int code_; |
|||
}; |
|||
|
|||
extern XMMRegister xmm0; |
|||
extern XMMRegister xmm1; |
|||
extern XMMRegister xmm2; |
|||
extern XMMRegister xmm3; |
|||
extern XMMRegister xmm4; |
|||
extern XMMRegister xmm5; |
|||
extern XMMRegister xmm6; |
|||
extern XMMRegister xmm7; |
|||
|
|||
enum Condition { |
|||
// any value < 0 is considered no_condition
|
|||
no_condition = -1, |
|||
|
|||
overflow = 0, |
|||
no_overflow = 1, |
|||
below = 2, |
|||
above_equal = 3, |
|||
equal = 4, |
|||
not_equal = 5, |
|||
below_equal = 6, |
|||
above = 7, |
|||
negative = 8, |
|||
positive = 9, |
|||
parity_even = 10, |
|||
parity_odd = 11, |
|||
less = 12, |
|||
greater_equal = 13, |
|||
less_equal = 14, |
|||
greater = 15, |
|||
|
|||
// aliases
|
|||
carry = below, |
|||
not_carry = above_equal, |
|||
zero = equal, |
|||
not_zero = not_equal, |
|||
sign = negative, |
|||
not_sign = positive |
|||
}; |
|||
|
|||
|
|||
// Returns the equivalent of !cc.
|
|||
// Negation of the default no_condition (-1) results in a non-default
|
|||
// no_condition value (-2). As long as tests for no_condition check
|
|||
// for condition < 0, this will work as expected.
|
|||
inline Condition NegateCondition(Condition cc); |
|||
|
|||
// Corresponds to transposing the operands of a comparison.
|
|||
inline Condition ReverseCondition(Condition cc) { |
|||
switch (cc) { |
|||
case below: |
|||
return above; |
|||
case above: |
|||
return below; |
|||
case above_equal: |
|||
return below_equal; |
|||
case below_equal: |
|||
return above_equal; |
|||
case less: |
|||
return greater; |
|||
case greater: |
|||
return less; |
|||
case greater_equal: |
|||
return less_equal; |
|||
case less_equal: |
|||
return greater_equal; |
|||
default: |
|||
return cc; |
|||
}; |
|||
} |
|||
|
|||
enum Hint { |
|||
no_hint = 0, |
|||
not_taken = 0x2e, |
|||
taken = 0x3e |
|||
}; |
|||
|
|||
// The result of negating a hint is as if the corresponding condition
|
|||
// were negated by NegateCondition. That is, no_hint is mapped to
|
|||
// itself and not_taken and taken are mapped to each other.
|
|||
inline Hint NegateHint(Hint hint) { |
|||
return (hint == no_hint) |
|||
? no_hint |
|||
: ((hint == not_taken) ? taken : not_taken); |
|||
} |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// Machine instruction Immediates
|
|||
|
|||
class Immediate BASE_EMBEDDED { |
|||
public: |
|||
inline explicit Immediate(int x); |
|||
inline explicit Immediate(const char* s); |
|||
inline explicit Immediate(const ExternalReference& ext); |
|||
inline explicit Immediate(Handle<Object> handle); |
|||
inline explicit Immediate(Smi* value); |
|||
|
|||
static Immediate CodeRelativeOffset(Label* label) { |
|||
return Immediate(label); |
|||
} |
|||
|
|||
bool is_zero() const { return x_ == 0 && rmode_ == RelocInfo::NONE; } |
|||
bool is_int8() const { |
|||
return -128 <= x_ && x_ < 128 && rmode_ == RelocInfo::NONE; |
|||
} |
|||
bool is_int16() const { |
|||
return -32768 <= x_ && x_ < 32768 && rmode_ == RelocInfo::NONE; |
|||
} |
|||
|
|||
private: |
|||
inline explicit Immediate(Label* value); |
|||
|
|||
int x_; |
|||
RelocInfo::Mode rmode_; |
|||
|
|||
friend class Assembler; |
|||
}; |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// Machine instruction Operands
|
|||
|
|||
enum ScaleFactor { |
|||
times_1 = 0, |
|||
times_2 = 1, |
|||
times_4 = 2, |
|||
times_8 = 3 |
|||
}; |
|||
|
|||
|
|||
class Operand BASE_EMBEDDED { |
|||
public: |
|||
// reg
|
|||
INLINE(explicit Operand(Register reg)); |
|||
|
|||
// [disp/r]
|
|||
INLINE(explicit Operand(int32_t disp, RelocInfo::Mode rmode)); |
|||
// disp only must always be relocated
|
|||
|
|||
// [base + disp/r]
|
|||
explicit Operand(Register base, int32_t disp, |
|||
RelocInfo::Mode rmode = RelocInfo::NONE); |
|||
|
|||
// [base + index*scale + disp/r]
|
|||
explicit Operand(Register base, |
|||
Register index, |
|||
ScaleFactor scale, |
|||
int32_t disp, |
|||
RelocInfo::Mode rmode = RelocInfo::NONE); |
|||
|
|||
// [index*scale + disp/r]
|
|||
explicit Operand(Register index, |
|||
ScaleFactor scale, |
|||
int32_t disp, |
|||
RelocInfo::Mode rmode = RelocInfo::NONE); |
|||
|
|||
static Operand StaticVariable(const ExternalReference& ext) { |
|||
return Operand(reinterpret_cast<int32_t>(ext.address()), |
|||
RelocInfo::EXTERNAL_REFERENCE); |
|||
} |
|||
|
|||
static Operand StaticArray(Register index, |
|||
ScaleFactor scale, |
|||
const ExternalReference& arr) { |
|||
return Operand(index, scale, reinterpret_cast<int32_t>(arr.address()), |
|||
RelocInfo::EXTERNAL_REFERENCE); |
|||
} |
|||
|
|||
// Returns true if this Operand is a wrapper for the specified register.
|
|||
bool is_reg(Register reg) const; |
|||
|
|||
private: |
|||
byte buf_[6]; |
|||
// The number of bytes in buf_.
|
|||
unsigned int len_; |
|||
// Only valid if len_ > 4.
|
|||
RelocInfo::Mode rmode_; |
|||
|
|||
// Set the ModRM byte without an encoded 'reg' register. The
|
|||
// register is encoded later as part of the emit_operand operation.
|
|||
inline void set_modrm(int mod, Register rm); |
|||
|
|||
inline void set_sib(ScaleFactor scale, Register index, Register base); |
|||
inline void set_disp8(int8_t disp); |
|||
inline void set_dispr(int32_t disp, RelocInfo::Mode rmode); |
|||
|
|||
friend class Assembler; |
|||
}; |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// A Displacement describes the 32bit immediate field of an instruction which
|
|||
// may be used together with a Label in order to refer to a yet unknown code
|
|||
// position. Displacements stored in the instruction stream are used to describe
|
|||
// the instruction and to chain a list of instructions using the same Label.
|
|||
// A Displacement contains 2 different fields:
|
|||
//
|
|||
// next field: position of next displacement in the chain (0 = end of list)
|
|||
// type field: instruction type
|
|||
//
|
|||
// A next value of null (0) indicates the end of a chain (note that there can
|
|||
// be no displacement at position zero, because there is always at least one
|
|||
// instruction byte before the displacement).
|
|||
//
|
|||
// Displacement _data field layout
|
|||
//
|
|||
// |31.....2|1......0|
|
|||
// [ next | type |
|
|||
|
|||
class Displacement BASE_EMBEDDED { |
|||
public: |
|||
enum Type { |
|||
UNCONDITIONAL_JUMP, |
|||
CODE_RELATIVE, |
|||
OTHER |
|||
}; |
|||
|
|||
int data() const { return data_; } |
|||
Type type() const { return TypeField::decode(data_); } |
|||
void next(Label* L) const { |
|||
int n = NextField::decode(data_); |
|||
n > 0 ? L->link_to(n) : L->Unuse(); |
|||
} |
|||
void link_to(Label* L) { init(L, type()); } |
|||
|
|||
explicit Displacement(int data) { data_ = data; } |
|||
|
|||
Displacement(Label* L, Type type) { init(L, type); } |
|||
|
|||
void print() { |
|||
PrintF("%s (%x) ", (type() == UNCONDITIONAL_JUMP ? "jmp" : "[other]"), |
|||
NextField::decode(data_)); |
|||
} |
|||
|
|||
private: |
|||
int data_; |
|||
|
|||
class TypeField: public BitField<Type, 0, 2> {}; |
|||
class NextField: public BitField<int, 2, 32-2> {}; |
|||
|
|||
void init(Label* L, Type type); |
|||
}; |
|||
|
|||
|
|||
|
|||
// CpuFeatures keeps track of which features are supported by the target CPU.
|
|||
// Supported features must be enabled by a Scope before use.
|
|||
// Example:
|
|||
// if (CpuFeatures::IsSupported(SSE2)) {
|
|||
// CpuFeatures::Scope fscope(SSE2);
|
|||
// // Generate SSE2 floating point code.
|
|||
// } else {
|
|||
// // Generate standard x87 floating point code.
|
|||
// }
|
|||
class CpuFeatures : public AllStatic { |
|||
public: |
|||
// Feature flags bit positions. They are mostly based on the CPUID spec.
|
|||
// (We assign CPUID itself to one of the currently reserved bits --
|
|||
// feel free to change this if needed.)
|
|||
enum Feature { SSE3 = 32, SSE2 = 26, CMOV = 15, RDTSC = 4, CPUID = 10 }; |
|||
// Detect features of the target CPU. Set safe defaults if the serializer
|
|||
// is enabled (snapshots must be portable).
|
|||
static void Probe(); |
|||
// Check whether a feature is supported by the target CPU.
|
|||
static bool IsSupported(Feature f) { |
|||
return (supported_ & (static_cast<uint64_t>(1) << f)) != 0; |
|||
} |
|||
// Check whether a feature is currently enabled.
|
|||
static bool IsEnabled(Feature f) { |
|||
return (enabled_ & (static_cast<uint64_t>(1) << f)) != 0; |
|||
} |
|||
// Enable a specified feature within a scope.
|
|||
class Scope BASE_EMBEDDED { |
|||
#ifdef DEBUG |
|||
public: |
|||
explicit Scope(Feature f) { |
|||
ASSERT(CpuFeatures::IsSupported(f)); |
|||
old_enabled_ = CpuFeatures::enabled_; |
|||
CpuFeatures::enabled_ |= (static_cast<uint64_t>(1) << f); |
|||
} |
|||
~Scope() { CpuFeatures::enabled_ = old_enabled_; } |
|||
private: |
|||
uint64_t old_enabled_; |
|||
#else |
|||
public: |
|||
explicit Scope(Feature f) {} |
|||
#endif |
|||
}; |
|||
private: |
|||
static uint64_t supported_; |
|||
static uint64_t enabled_; |
|||
}; |
|||
|
|||
|
|||
class Assembler : public Malloced { |
|||
private: |
|||
// The relocation writer's position is kGap bytes below the end of
|
|||
// the generated instructions. This leaves enough space for the
|
|||
// longest possible ia32 instruction (17 bytes as of 9/26/06) and
|
|||
// allows for a single, fast space check per instruction.
|
|||
static const int kGap = 32; |
|||
|
|||
public: |
|||
// Create an assembler. Instructions and relocation information are emitted
|
|||
// into a buffer, with the instructions starting from the beginning and the
|
|||
// relocation information starting from the end of the buffer. See CodeDesc
|
|||
// for a detailed comment on the layout (globals.h).
|
|||
//
|
|||
// If the provided buffer is NULL, the assembler allocates and grows its own
|
|||
// buffer, and buffer_size determines the initial buffer size. The buffer is
|
|||
// owned by the assembler and deallocated upon destruction of the assembler.
|
|||
//
|
|||
// If the provided buffer is not NULL, the assembler uses the provided buffer
|
|||
// for code generation and assumes its size to be buffer_size. If the buffer
|
|||
// is too small, a fatal error occurs. No deallocation of the buffer is done
|
|||
// upon destruction of the assembler.
|
|||
Assembler(void* buffer, int buffer_size); |
|||
~Assembler(); |
|||
|
|||
// GetCode emits any pending (non-emitted) code and fills the descriptor
|
|||
// desc. GetCode() is idempotent; it returns the same result if no other
|
|||
// Assembler functions are invoked in between GetCode() calls.
|
|||
void GetCode(CodeDesc* desc); |
|||
|
|||
// Read/Modify the code target in the branch/call instruction at pc.
|
|||
inline static Address target_address_at(Address pc); |
|||
inline static void set_target_address_at(Address pc, Address target); |
|||
|
|||
// Distance between the address of the code target in the call instruction
|
|||
// and the return address
|
|||
static const int kTargetAddrToReturnAddrDist = kPointerSize; |
|||
|
|||
|
|||
// ---------------------------------------------------------------------------
|
|||
// Code generation
|
|||
//
|
|||
// - function names correspond one-to-one to ia32 instruction mnemonics
|
|||
// - unless specified otherwise, instructions operate on 32bit operands
|
|||
// - instructions on 8bit (byte) operands/registers have a trailing '_b'
|
|||
// - instructions on 16bit (word) operands/registers have a trailing '_w'
|
|||
// - naming conflicts with C++ keywords are resolved via a trailing '_'
|
|||
|
|||
// NOTE ON INTERFACE: Currently, the interface is not very consistent
|
|||
// in the sense that some operations (e.g. mov()) can be called in more
|
|||
// the one way to generate the same instruction: The Register argument
|
|||
// can in some cases be replaced with an Operand(Register) argument.
|
|||
// This should be cleaned up and made more orthogonal. The questions
|
|||
// is: should we always use Operands instead of Registers where an
|
|||
// Operand is possible, or should we have a Register (overloaded) form
|
|||
// instead? We must be careful to make sure that the selected instruction
|
|||
// is obvious from the parameters to avoid hard-to-find code generation
|
|||
// bugs.
|
|||
|
|||
// Insert the smallest number of nop instructions
|
|||
// possible to align the pc offset to a multiple
|
|||
// of m. m must be a power of 2.
|
|||
void Align(int m); |
|||
|
|||
// Stack
|
|||
void pushad(); |
|||
void popad(); |
|||
|
|||
void pushfd(); |
|||
void popfd(); |
|||
|
|||
void push(const Immediate& x); |
|||
void push(Register src); |
|||
void push(const Operand& src); |
|||
void push(Label* label, RelocInfo::Mode relocation_mode); |
|||
|
|||
void pop(Register dst); |
|||
void pop(const Operand& dst); |
|||
|
|||
void enter(const Immediate& size); |
|||
void leave(); |
|||
|
|||
// Moves
|
|||
void mov_b(Register dst, const Operand& src); |
|||
void mov_b(const Operand& dst, int8_t imm8); |
|||
void mov_b(const Operand& dst, Register src); |
|||
|
|||
void mov_w(Register dst, const Operand& src); |
|||
void mov_w(const Operand& dst, Register src); |
|||
|
|||
void mov(Register dst, int32_t imm32); |
|||
void mov(Register dst, const Immediate& x); |
|||
void mov(Register dst, Handle<Object> handle); |
|||
void mov(Register dst, const Operand& src); |
|||
void mov(Register dst, Register src); |
|||
void mov(const Operand& dst, const Immediate& x); |
|||
void mov(const Operand& dst, Handle<Object> handle); |
|||
void mov(const Operand& dst, Register src); |
|||
|
|||
void movsx_b(Register dst, const Operand& src); |
|||
|
|||
void movsx_w(Register dst, const Operand& src); |
|||
|
|||
void movzx_b(Register dst, const Operand& src); |
|||
|
|||
void movzx_w(Register dst, const Operand& src); |
|||
|
|||
// Conditional moves
|
|||
void cmov(Condition cc, Register dst, int32_t imm32); |
|||
void cmov(Condition cc, Register dst, Handle<Object> handle); |
|||
void cmov(Condition cc, Register dst, const Operand& src); |
|||
|
|||
// Exchange two registers
|
|||
void xchg(Register dst, Register src); |
|||
|
|||
// Arithmetics
|
|||
void adc(Register dst, int32_t imm32); |
|||
void adc(Register dst, const Operand& src); |
|||
|
|||
void add(Register dst, const Operand& src); |
|||
void add(const Operand& dst, const Immediate& x); |
|||
|
|||
void and_(Register dst, int32_t imm32); |
|||
void and_(Register dst, const Operand& src); |
|||
void and_(const Operand& src, Register dst); |
|||
void and_(const Operand& dst, const Immediate& x); |
|||
|
|||
void cmpb(const Operand& op, int8_t imm8); |
|||
void cmpb_al(const Operand& op); |
|||
void cmpw_ax(const Operand& op); |
|||
void cmpw(const Operand& op, Immediate imm16); |
|||
void cmp(Register reg, int32_t imm32); |
|||
void cmp(Register reg, Handle<Object> handle); |
|||
void cmp(Register reg, const Operand& op); |
|||
void cmp(const Operand& op, const Immediate& imm); |
|||
|
|||
void dec_b(Register dst); |
|||
|
|||
void dec(Register dst); |
|||
void dec(const Operand& dst); |
|||
|
|||
void cdq(); |
|||
|
|||
void idiv(Register src); |
|||
|
|||
void imul(Register dst, const Operand& src); |
|||
void imul(Register dst, Register src, int32_t imm32); |
|||
|
|||
void inc(Register dst); |
|||
void inc(const Operand& dst); |
|||
|
|||
void lea(Register dst, const Operand& src); |
|||
|
|||
void mul(Register src); |
|||
|
|||
void neg(Register dst); |
|||
|
|||
void not_(Register dst); |
|||
|
|||
void or_(Register dst, int32_t imm32); |
|||
void or_(Register dst, const Operand& src); |
|||
void or_(const Operand& dst, Register src); |
|||
void or_(const Operand& dst, const Immediate& x); |
|||
|
|||
void rcl(Register dst, uint8_t imm8); |
|||
|
|||
void sar(Register dst, uint8_t imm8); |
|||
void sar(Register dst); |
|||
|
|||
void sbb(Register dst, const Operand& src); |
|||
|
|||
void shld(Register dst, const Operand& src); |
|||
|
|||
void shl(Register dst, uint8_t imm8); |
|||
void shl(Register dst); |
|||
|
|||
void shrd(Register dst, const Operand& src); |
|||
|
|||
void shr(Register dst, uint8_t imm8); |
|||
void shr(Register dst); |
|||
void shr_cl(Register dst); |
|||
|
|||
void sub(const Operand& dst, const Immediate& x); |
|||
void sub(Register dst, const Operand& src); |
|||
void sub(const Operand& dst, Register src); |
|||
|
|||
void test(Register reg, const Immediate& imm); |
|||
void test(Register reg, const Operand& op); |
|||
void test(const Operand& op, const Immediate& imm); |
|||
|
|||
void xor_(Register dst, int32_t imm32); |
|||
void xor_(Register dst, const Operand& src); |
|||
void xor_(const Operand& src, Register dst); |
|||
void xor_(const Operand& dst, const Immediate& x); |
|||
|
|||
// Bit operations.
|
|||
void bt(const Operand& dst, Register src); |
|||
void bts(const Operand& dst, Register src); |
|||
|
|||
// Miscellaneous
|
|||
void hlt(); |
|||
void int3(); |
|||
void nop(); |
|||
void rdtsc(); |
|||
void ret(int imm16); |
|||
|
|||
// Label operations & relative jumps (PPUM Appendix D)
|
|||
//
|
|||
// Takes a branch opcode (cc) and a label (L) and generates
|
|||
// either a backward branch or a forward branch and links it
|
|||
// to the label fixup chain. Usage:
|
|||
//
|
|||
// Label L; // unbound label
|
|||
// j(cc, &L); // forward branch to unbound label
|
|||
// bind(&L); // bind label to the current pc
|
|||
// j(cc, &L); // backward branch to bound label
|
|||
// bind(&L); // illegal: a label may be bound only once
|
|||
//
|
|||
// Note: The same Label can be used for forward and backward branches
|
|||
// but it may be bound only once.
|
|||
|
|||
void bind(Label* L); // binds an unbound label L to the current code position
|
|||
|
|||
// Calls
|
|||
void call(Label* L); |
|||
void call(byte* entry, RelocInfo::Mode rmode); |
|||
void call(const Operand& adr); |
|||
void call(Handle<Code> code, RelocInfo::Mode rmode); |
|||
|
|||
// Jumps
|
|||
void jmp(Label* L); // unconditional jump to L
|
|||
void jmp(byte* entry, RelocInfo::Mode rmode); |
|||
void jmp(const Operand& adr); |
|||
void jmp(Handle<Code> code, RelocInfo::Mode rmode); |
|||
|
|||
// Conditional jumps
|
|||
void j(Condition cc, Label* L, Hint hint = no_hint); |
|||
void j(Condition cc, byte* entry, RelocInfo::Mode rmode, Hint hint = no_hint); |
|||
void j(Condition cc, Handle<Code> code, Hint hint = no_hint); |
|||
|
|||
// Floating-point operations
|
|||
void fld(int i); |
|||
|
|||
void fld1(); |
|||
void fldz(); |
|||
|
|||
void fld_s(const Operand& adr); |
|||
void fld_d(const Operand& adr); |
|||
|
|||
void fstp_s(const Operand& adr); |
|||
void fstp_d(const Operand& adr); |
|||
|
|||
void fild_s(const Operand& adr); |
|||
void fild_d(const Operand& adr); |
|||
|
|||
void fist_s(const Operand& adr); |
|||
|
|||
void fistp_s(const Operand& adr); |
|||
void fistp_d(const Operand& adr); |
|||
|
|||
void fisttp_s(const Operand& adr); |
|||
|
|||
void fabs(); |
|||
void fchs(); |
|||
|
|||
void fadd(int i); |
|||
void fsub(int i); |
|||
void fmul(int i); |
|||
void fdiv(int i); |
|||
|
|||
void fisub_s(const Operand& adr); |
|||
|
|||
void faddp(int i = 1); |
|||
void fsubp(int i = 1); |
|||
void fsubrp(int i = 1); |
|||
void fmulp(int i = 1); |
|||
void fdivp(int i = 1); |
|||
void fprem(); |
|||
void fprem1(); |
|||
|
|||
void fxch(int i = 1); |
|||
void fincstp(); |
|||
void ffree(int i = 0); |
|||
|
|||
void ftst(); |
|||
void fucomp(int i); |
|||
void fucompp(); |
|||
void fcompp(); |
|||
void fnstsw_ax(); |
|||
void fwait(); |
|||
void fnclex(); |
|||
|
|||
void frndint(); |
|||
|
|||
void sahf(); |
|||
void setcc(Condition cc, Register reg); |
|||
|
|||
void cpuid(); |
|||
|
|||
// SSE2 instructions
|
|||
void cvttss2si(Register dst, const Operand& src); |
|||
void cvttsd2si(Register dst, const Operand& src); |
|||
|
|||
void cvtsi2sd(XMMRegister dst, const Operand& src); |
|||
|
|||
void addsd(XMMRegister dst, XMMRegister src); |
|||
void subsd(XMMRegister dst, XMMRegister src); |
|||
void mulsd(XMMRegister dst, XMMRegister src); |
|||
void divsd(XMMRegister dst, XMMRegister src); |
|||
|
|||
// Use either movsd or movlpd.
|
|||
void movdbl(XMMRegister dst, const Operand& src); |
|||
void movdbl(const Operand& dst, XMMRegister src); |
|||
|
|||
// Debugging
|
|||
void Print(); |
|||
|
|||
// Check the code size generated from label to here.
|
|||
int SizeOfCodeGeneratedSince(Label* l) { return pc_offset() - l->pos(); } |
|||
|
|||
// Mark address of the ExitJSFrame code.
|
|||
void RecordJSReturn(); |
|||
|
|||
// Record a comment relocation entry that can be used by a disassembler.
|
|||
// Use --debug_code to enable.
|
|||
void RecordComment(const char* msg); |
|||
|
|||
void RecordPosition(int pos); |
|||
void RecordStatementPosition(int pos); |
|||
void WriteRecordedPositions(); |
|||
|
|||
// Writes a single word of data in the code stream.
|
|||
// Used for inline tables, e.g., jump-tables.
|
|||
void dd(uint32_t data, RelocInfo::Mode reloc_info); |
|||
|
|||
// Writes the absolute address of a bound label at the given position in
|
|||
// the generated code. That positions should have the relocation mode
|
|||
// internal_reference!
|
|||
void WriteInternalReference(int position, const Label& bound_label); |
|||
|
|||
int pc_offset() const { return pc_ - buffer_; } |
|||
int current_statement_position() const { return current_statement_position_; } |
|||
int current_position() const { return current_position_; } |
|||
|
|||
// Check if there is less than kGap bytes available in the buffer.
|
|||
// If this is the case, we need to grow the buffer before emitting
|
|||
// an instruction or relocation information.
|
|||
inline bool overflow() const { return pc_ >= reloc_info_writer.pos() - kGap; } |
|||
|
|||
// Get the number of bytes available in the buffer.
|
|||
inline int available_space() const { return reloc_info_writer.pos() - pc_; } |
|||
|
|||
// Avoid overflows for displacements etc.
|
|||
static const int kMaximalBufferSize = 512*MB; |
|||
static const int kMinimalBufferSize = 4*KB; |
|||
|
|||
protected: |
|||
void movsd(XMMRegister dst, const Operand& src); |
|||
void movsd(const Operand& dst, XMMRegister src); |
|||
|
|||
void emit_sse_operand(XMMRegister reg, const Operand& adr); |
|||
void emit_sse_operand(XMMRegister dst, XMMRegister src); |
|||
|
|||
|
|||
private: |
|||
byte* addr_at(int pos) { return buffer_ + pos; } |
|||
byte byte_at(int pos) { return buffer_[pos]; } |
|||
uint32_t long_at(int pos) { |
|||
return *reinterpret_cast<uint32_t*>(addr_at(pos)); |
|||
} |
|||
void long_at_put(int pos, uint32_t x) { |
|||
*reinterpret_cast<uint32_t*>(addr_at(pos)) = x; |
|||
} |
|||
|
|||
// code emission
|
|||
void GrowBuffer(); |
|||
inline void emit(uint32_t x); |
|||
inline void emit(Handle<Object> handle); |
|||
inline void emit(uint32_t x, RelocInfo::Mode rmode); |
|||
inline void emit(const Immediate& x); |
|||
inline void emit_w(const Immediate& x); |
|||
|
|||
// Emit the code-object-relative offset of the label's position
|
|||
inline void emit_code_relative_offset(Label* label); |
|||
|
|||
// instruction generation
|
|||
void emit_arith_b(int op1, int op2, Register dst, int imm8); |
|||
|
|||
// Emit a basic arithmetic instruction (i.e. first byte of the family is 0x81)
|
|||
// with a given destination expression and an immediate operand. It attempts
|
|||
// to use the shortest encoding possible.
|
|||
// sel specifies the /n in the modrm byte (see the Intel PRM).
|
|||
void emit_arith(int sel, Operand dst, const Immediate& x); |
|||
|
|||
void emit_operand(Register reg, const Operand& adr); |
|||
|
|||
void emit_farith(int b1, int b2, int i); |
|||
|
|||
// labels
|
|||
void print(Label* L); |
|||
void bind_to(Label* L, int pos); |
|||
void link_to(Label* L, Label* appendix); |
|||
|
|||
// displacements
|
|||
inline Displacement disp_at(Label* L); |
|||
inline void disp_at_put(Label* L, Displacement disp); |
|||
inline void emit_disp(Label* L, Displacement::Type type); |
|||
|
|||
// record reloc info for current pc_
|
|||
void RecordRelocInfo(RelocInfo::Mode rmode, intptr_t data = 0); |
|||
|
|||
friend class CodePatcher; |
|||
friend class EnsureSpace; |
|||
|
|||
// Code buffer:
|
|||
// The buffer into which code and relocation info are generated.
|
|||
byte* buffer_; |
|||
int buffer_size_; |
|||
// True if the assembler owns the buffer, false if buffer is external.
|
|||
bool own_buffer_; |
|||
|
|||
// code generation
|
|||
byte* pc_; // the program counter; moves forward
|
|||
RelocInfoWriter reloc_info_writer; |
|||
|
|||
// push-pop elimination
|
|||
byte* last_pc_; |
|||
|
|||
// source position information
|
|||
int current_statement_position_; |
|||
int current_position_; |
|||
int written_statement_position_; |
|||
int written_position_; |
|||
}; |
|||
|
|||
|
|||
// Helper class that ensures that there is enough space for generating
|
|||
// instructions and relocation information. The constructor makes
|
|||
// sure that there is enough space and (in debug mode) the destructor
|
|||
// checks that we did not generate too much.
|
|||
class EnsureSpace BASE_EMBEDDED { |
|||
public: |
|||
explicit EnsureSpace(Assembler* assembler) : assembler_(assembler) { |
|||
if (assembler_->overflow()) assembler_->GrowBuffer(); |
|||
#ifdef DEBUG |
|||
space_before_ = assembler_->available_space(); |
|||
#endif |
|||
} |
|||
|
|||
#ifdef DEBUG |
|||
~EnsureSpace() { |
|||
int bytes_generated = space_before_ - assembler_->available_space(); |
|||
ASSERT(bytes_generated < assembler_->kGap); |
|||
} |
|||
#endif |
|||
|
|||
private: |
|||
Assembler* assembler_; |
|||
#ifdef DEBUG |
|||
int space_before_; |
|||
#endif |
|||
}; |
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_ASSEMBLER_IA32_H_
|
@ -0,0 +1,585 @@ |
|||
// Copyright (c) 1994-2006 Sun Microsystems Inc.
|
|||
// All Rights Reserved.
|
|||
//
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// - Redistributions of source code must retain the above copyright notice,
|
|||
// this list of conditions and the following disclaimer.
|
|||
//
|
|||
// - Redistribution in binary form must reproduce the above copyright
|
|||
// notice, this list of conditions and the following disclaimer in the
|
|||
// documentation and/or other materials provided with the distribution.
|
|||
//
|
|||
// - Neither the name of Sun Microsystems or the names of contributors may
|
|||
// be used to endorse or promote products derived from this software without
|
|||
// specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|||
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|||
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|||
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|||
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|||
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|||
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
// The original source code covered by the above license above has been
|
|||
// modified significantly by Google Inc.
|
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
|
|||
#include "v8.h" |
|||
|
|||
#include "arguments.h" |
|||
#include "execution.h" |
|||
#include "ic-inl.h" |
|||
#include "factory.h" |
|||
#include "runtime.h" |
|||
#include "serialize.h" |
|||
#include "stub-cache.h" |
|||
#include "regexp-stack.h" |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// Implementation of Label
|
|||
|
|||
int Label::pos() const { |
|||
if (pos_ < 0) return -pos_ - 1; |
|||
if (pos_ > 0) return pos_ - 1; |
|||
UNREACHABLE(); |
|||
return 0; |
|||
} |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// Implementation of RelocInfoWriter and RelocIterator
|
|||
//
|
|||
// Encoding
|
|||
//
|
|||
// The most common modes are given single-byte encodings. Also, it is
|
|||
// easy to identify the type of reloc info and skip unwanted modes in
|
|||
// an iteration.
|
|||
//
|
|||
// The encoding relies on the fact that there are less than 14
|
|||
// different relocation modes.
|
|||
//
|
|||
// embedded_object: [6 bits pc delta] 00
|
|||
//
|
|||
// code_taget: [6 bits pc delta] 01
|
|||
//
|
|||
// position: [6 bits pc delta] 10,
|
|||
// [7 bits signed data delta] 0
|
|||
//
|
|||
// statement_position: [6 bits pc delta] 10,
|
|||
// [7 bits signed data delta] 1
|
|||
//
|
|||
// any nondata mode: 00 [4 bits rmode] 11, // rmode: 0..13 only
|
|||
// 00 [6 bits pc delta]
|
|||
//
|
|||
// pc-jump: 00 1111 11,
|
|||
// 00 [6 bits pc delta]
|
|||
//
|
|||
// pc-jump: 01 1111 11,
|
|||
// (variable length) 7 - 26 bit pc delta, written in chunks of 7
|
|||
// bits, the lowest 7 bits written first.
|
|||
//
|
|||
// data-jump + pos: 00 1110 11,
|
|||
// signed int, lowest byte written first
|
|||
//
|
|||
// data-jump + st.pos: 01 1110 11,
|
|||
// signed int, lowest byte written first
|
|||
//
|
|||
// data-jump + comm.: 10 1110 11,
|
|||
// signed int, lowest byte written first
|
|||
//
|
|||
const int kMaxRelocModes = 14; |
|||
|
|||
const int kTagBits = 2; |
|||
const int kTagMask = (1 << kTagBits) - 1; |
|||
const int kExtraTagBits = 4; |
|||
const int kPositionTypeTagBits = 1; |
|||
const int kSmallDataBits = kBitsPerByte - kPositionTypeTagBits; |
|||
|
|||
const int kEmbeddedObjectTag = 0; |
|||
const int kCodeTargetTag = 1; |
|||
const int kPositionTag = 2; |
|||
const int kDefaultTag = 3; |
|||
|
|||
const int kPCJumpTag = (1 << kExtraTagBits) - 1; |
|||
|
|||
const int kSmallPCDeltaBits = kBitsPerByte - kTagBits; |
|||
const int kSmallPCDeltaMask = (1 << kSmallPCDeltaBits) - 1; |
|||
|
|||
const int kVariableLengthPCJumpTopTag = 1; |
|||
const int kChunkBits = 7; |
|||
const int kChunkMask = (1 << kChunkBits) - 1; |
|||
const int kLastChunkTagBits = 1; |
|||
const int kLastChunkTagMask = 1; |
|||
const int kLastChunkTag = 1; |
|||
|
|||
|
|||
const int kDataJumpTag = kPCJumpTag - 1; |
|||
|
|||
const int kNonstatementPositionTag = 0; |
|||
const int kStatementPositionTag = 1; |
|||
const int kCommentTag = 2; |
|||
|
|||
|
|||
uint32_t RelocInfoWriter::WriteVariableLengthPCJump(uint32_t pc_delta) { |
|||
// Return if the pc_delta can fit in kSmallPCDeltaBits bits.
|
|||
// Otherwise write a variable length PC jump for the bits that do
|
|||
// not fit in the kSmallPCDeltaBits bits.
|
|||
if (is_uintn(pc_delta, kSmallPCDeltaBits)) return pc_delta; |
|||
WriteExtraTag(kPCJumpTag, kVariableLengthPCJumpTopTag); |
|||
uint32_t pc_jump = pc_delta >> kSmallPCDeltaBits; |
|||
ASSERT(pc_jump > 0); |
|||
// Write kChunkBits size chunks of the pc_jump.
|
|||
for (; pc_jump > 0; pc_jump = pc_jump >> kChunkBits) { |
|||
byte b = pc_jump & kChunkMask; |
|||
*--pos_ = b << kLastChunkTagBits; |
|||
} |
|||
// Tag the last chunk so it can be identified.
|
|||
*pos_ = *pos_ | kLastChunkTag; |
|||
// Return the remaining kSmallPCDeltaBits of the pc_delta.
|
|||
return pc_delta & kSmallPCDeltaMask; |
|||
} |
|||
|
|||
|
|||
void RelocInfoWriter::WriteTaggedPC(uint32_t pc_delta, int tag) { |
|||
// Write a byte of tagged pc-delta, possibly preceded by var. length pc-jump.
|
|||
pc_delta = WriteVariableLengthPCJump(pc_delta); |
|||
*--pos_ = pc_delta << kTagBits | tag; |
|||
} |
|||
|
|||
|
|||
void RelocInfoWriter::WriteTaggedData(int32_t data_delta, int tag) { |
|||
*--pos_ = data_delta << kPositionTypeTagBits | tag; |
|||
} |
|||
|
|||
|
|||
void RelocInfoWriter::WriteExtraTag(int extra_tag, int top_tag) { |
|||
*--pos_ = top_tag << (kTagBits + kExtraTagBits) | |
|||
extra_tag << kTagBits | |
|||
kDefaultTag; |
|||
} |
|||
|
|||
|
|||
void RelocInfoWriter::WriteExtraTaggedPC(uint32_t pc_delta, int extra_tag) { |
|||
// Write two-byte tagged pc-delta, possibly preceded by var. length pc-jump.
|
|||
pc_delta = WriteVariableLengthPCJump(pc_delta); |
|||
WriteExtraTag(extra_tag, 0); |
|||
*--pos_ = pc_delta; |
|||
} |
|||
|
|||
|
|||
void RelocInfoWriter::WriteExtraTaggedData(int32_t data_delta, int top_tag) { |
|||
WriteExtraTag(kDataJumpTag, top_tag); |
|||
for (int i = 0; i < kIntSize; i++) { |
|||
*--pos_ = data_delta; |
|||
data_delta = ArithmeticShiftRight(data_delta, kBitsPerByte); |
|||
} |
|||
} |
|||
|
|||
|
|||
void RelocInfoWriter::Write(const RelocInfo* rinfo) { |
|||
#ifdef DEBUG |
|||
byte* begin_pos = pos_; |
|||
#endif |
|||
Counters::reloc_info_count.Increment(); |
|||
ASSERT(rinfo->pc() - last_pc_ >= 0); |
|||
ASSERT(RelocInfo::NUMBER_OF_MODES < kMaxRelocModes); |
|||
// Use unsigned delta-encoding for pc.
|
|||
uint32_t pc_delta = rinfo->pc() - last_pc_; |
|||
RelocInfo::Mode rmode = rinfo->rmode(); |
|||
|
|||
// The two most common modes are given small tags, and usually fit in a byte.
|
|||
if (rmode == RelocInfo::EMBEDDED_OBJECT) { |
|||
WriteTaggedPC(pc_delta, kEmbeddedObjectTag); |
|||
} else if (rmode == RelocInfo::CODE_TARGET) { |
|||
WriteTaggedPC(pc_delta, kCodeTargetTag); |
|||
} else if (RelocInfo::IsPosition(rmode)) { |
|||
// Use signed delta-encoding for data.
|
|||
int32_t data_delta = rinfo->data() - last_data_; |
|||
int pos_type_tag = rmode == RelocInfo::POSITION ? kNonstatementPositionTag |
|||
: kStatementPositionTag; |
|||
// Check if data is small enough to fit in a tagged byte.
|
|||
if (is_intn(data_delta, kSmallDataBits)) { |
|||
WriteTaggedPC(pc_delta, kPositionTag); |
|||
WriteTaggedData(data_delta, pos_type_tag); |
|||
last_data_ = rinfo->data(); |
|||
} else { |
|||
// Otherwise, use costly encoding.
|
|||
WriteExtraTaggedPC(pc_delta, kPCJumpTag); |
|||
WriteExtraTaggedData(data_delta, pos_type_tag); |
|||
last_data_ = rinfo->data(); |
|||
} |
|||
} else if (RelocInfo::IsComment(rmode)) { |
|||
// Comments are normally not generated, so we use the costly encoding.
|
|||
WriteExtraTaggedPC(pc_delta, kPCJumpTag); |
|||
WriteExtraTaggedData(rinfo->data() - last_data_, kCommentTag); |
|||
last_data_ = rinfo->data(); |
|||
} else { |
|||
// For all other modes we simply use the mode as the extra tag.
|
|||
// None of these modes need a data component.
|
|||
ASSERT(rmode < kPCJumpTag && rmode < kDataJumpTag); |
|||
WriteExtraTaggedPC(pc_delta, rmode); |
|||
} |
|||
last_pc_ = rinfo->pc(); |
|||
#ifdef DEBUG |
|||
ASSERT(begin_pos - pos_ <= kMaxSize); |
|||
#endif |
|||
} |
|||
|
|||
|
|||
inline int RelocIterator::AdvanceGetTag() { |
|||
return *--pos_ & kTagMask; |
|||
} |
|||
|
|||
|
|||
inline int RelocIterator::GetExtraTag() { |
|||
return (*pos_ >> kTagBits) & ((1 << kExtraTagBits) - 1); |
|||
} |
|||
|
|||
|
|||
inline int RelocIterator::GetTopTag() { |
|||
return *pos_ >> (kTagBits + kExtraTagBits); |
|||
} |
|||
|
|||
|
|||
inline void RelocIterator::ReadTaggedPC() { |
|||
rinfo_.pc_ += *pos_ >> kTagBits; |
|||
} |
|||
|
|||
|
|||
inline void RelocIterator::AdvanceReadPC() { |
|||
rinfo_.pc_ += *--pos_; |
|||
} |
|||
|
|||
|
|||
void RelocIterator::AdvanceReadData() { |
|||
int32_t x = 0; |
|||
for (int i = 0; i < kIntSize; i++) { |
|||
x |= *--pos_ << i * kBitsPerByte; |
|||
} |
|||
rinfo_.data_ += x; |
|||
} |
|||
|
|||
|
|||
void RelocIterator::AdvanceReadVariableLengthPCJump() { |
|||
// Read the 32-kSmallPCDeltaBits most significant bits of the
|
|||
// pc jump in kChunkBits bit chunks and shift them into place.
|
|||
// Stop when the last chunk is encountered.
|
|||
uint32_t pc_jump = 0; |
|||
for (int i = 0; i < kIntSize; i++) { |
|||
byte pc_jump_part = *--pos_; |
|||
pc_jump |= (pc_jump_part >> kLastChunkTagBits) << i * kChunkBits; |
|||
if ((pc_jump_part & kLastChunkTagMask) == 1) break; |
|||
} |
|||
// The least significant kSmallPCDeltaBits bits will be added
|
|||
// later.
|
|||
rinfo_.pc_ += pc_jump << kSmallPCDeltaBits; |
|||
} |
|||
|
|||
|
|||
inline int RelocIterator::GetPositionTypeTag() { |
|||
return *pos_ & ((1 << kPositionTypeTagBits) - 1); |
|||
} |
|||
|
|||
|
|||
inline void RelocIterator::ReadTaggedData() { |
|||
int8_t signed_b = *pos_; |
|||
rinfo_.data_ += ArithmeticShiftRight(signed_b, kPositionTypeTagBits); |
|||
} |
|||
|
|||
|
|||
inline RelocInfo::Mode RelocIterator::DebugInfoModeFromTag(int tag) { |
|||
if (tag == kStatementPositionTag) { |
|||
return RelocInfo::STATEMENT_POSITION; |
|||
} else if (tag == kNonstatementPositionTag) { |
|||
return RelocInfo::POSITION; |
|||
} else { |
|||
ASSERT(tag == kCommentTag); |
|||
return RelocInfo::COMMENT; |
|||
} |
|||
} |
|||
|
|||
|
|||
void RelocIterator::next() { |
|||
ASSERT(!done()); |
|||
// Basically, do the opposite of RelocInfoWriter::Write.
|
|||
// Reading of data is as far as possible avoided for unwanted modes,
|
|||
// but we must always update the pc.
|
|||
//
|
|||
// We exit this loop by returning when we find a mode we want.
|
|||
while (pos_ > end_) { |
|||
int tag = AdvanceGetTag(); |
|||
if (tag == kEmbeddedObjectTag) { |
|||
ReadTaggedPC(); |
|||
if (SetMode(RelocInfo::EMBEDDED_OBJECT)) return; |
|||
} else if (tag == kCodeTargetTag) { |
|||
ReadTaggedPC(); |
|||
if (*(reinterpret_cast<int*>(rinfo_.pc())) == 0x61) { |
|||
tag = 0; |
|||
} |
|||
if (SetMode(RelocInfo::CODE_TARGET)) return; |
|||
} else if (tag == kPositionTag) { |
|||
ReadTaggedPC(); |
|||
Advance(); |
|||
// Check if we want source positions.
|
|||
if (mode_mask_ & RelocInfo::kPositionMask) { |
|||
// Check if we want this type of source position.
|
|||
if (SetMode(DebugInfoModeFromTag(GetPositionTypeTag()))) { |
|||
// Finally read the data before returning.
|
|||
ReadTaggedData(); |
|||
return; |
|||
} |
|||
} |
|||
} else { |
|||
ASSERT(tag == kDefaultTag); |
|||
int extra_tag = GetExtraTag(); |
|||
if (extra_tag == kPCJumpTag) { |
|||
int top_tag = GetTopTag(); |
|||
if (top_tag == kVariableLengthPCJumpTopTag) { |
|||
AdvanceReadVariableLengthPCJump(); |
|||
} else { |
|||
AdvanceReadPC(); |
|||
} |
|||
} else if (extra_tag == kDataJumpTag) { |
|||
// Check if we want debug modes (the only ones with data).
|
|||
if (mode_mask_ & RelocInfo::kDebugMask) { |
|||
int top_tag = GetTopTag(); |
|||
AdvanceReadData(); |
|||
if (SetMode(DebugInfoModeFromTag(top_tag))) return; |
|||
} else { |
|||
// Otherwise, just skip over the data.
|
|||
Advance(kIntSize); |
|||
} |
|||
} else { |
|||
AdvanceReadPC(); |
|||
if (SetMode(static_cast<RelocInfo::Mode>(extra_tag))) return; |
|||
} |
|||
} |
|||
} |
|||
done_ = true; |
|||
} |
|||
|
|||
|
|||
RelocIterator::RelocIterator(Code* code, int mode_mask) { |
|||
rinfo_.pc_ = code->instruction_start(); |
|||
rinfo_.data_ = 0; |
|||
// relocation info is read backwards
|
|||
pos_ = code->relocation_start() + code->relocation_size(); |
|||
end_ = code->relocation_start(); |
|||
done_ = false; |
|||
mode_mask_ = mode_mask; |
|||
if (mode_mask_ == 0) pos_ = end_; |
|||
next(); |
|||
} |
|||
|
|||
|
|||
RelocIterator::RelocIterator(const CodeDesc& desc, int mode_mask) { |
|||
rinfo_.pc_ = desc.buffer; |
|||
rinfo_.data_ = 0; |
|||
// relocation info is read backwards
|
|||
pos_ = desc.buffer + desc.buffer_size; |
|||
end_ = pos_ - desc.reloc_size; |
|||
done_ = false; |
|||
mode_mask_ = mode_mask; |
|||
if (mode_mask_ == 0) pos_ = end_; |
|||
next(); |
|||
} |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// Implementation of RelocInfo
|
|||
|
|||
|
|||
#ifdef ENABLE_DISASSEMBLER |
|||
const char* RelocInfo::RelocModeName(RelocInfo::Mode rmode) { |
|||
switch (rmode) { |
|||
case RelocInfo::NONE: |
|||
return "no reloc"; |
|||
case RelocInfo::EMBEDDED_OBJECT: |
|||
return "embedded object"; |
|||
case RelocInfo::EMBEDDED_STRING: |
|||
return "embedded string"; |
|||
case RelocInfo::CONSTRUCT_CALL: |
|||
return "code target (js construct call)"; |
|||
case RelocInfo::CODE_TARGET_CONTEXT: |
|||
return "code target (context)"; |
|||
case RelocInfo::CODE_TARGET: |
|||
return "code target"; |
|||
case RelocInfo::RUNTIME_ENTRY: |
|||
return "runtime entry"; |
|||
case RelocInfo::JS_RETURN: |
|||
return "js return"; |
|||
case RelocInfo::COMMENT: |
|||
return "comment"; |
|||
case RelocInfo::POSITION: |
|||
return "position"; |
|||
case RelocInfo::STATEMENT_POSITION: |
|||
return "statement position"; |
|||
case RelocInfo::EXTERNAL_REFERENCE: |
|||
return "external reference"; |
|||
case RelocInfo::INTERNAL_REFERENCE: |
|||
return "internal reference"; |
|||
case RelocInfo::NUMBER_OF_MODES: |
|||
UNREACHABLE(); |
|||
return "number_of_modes"; |
|||
} |
|||
return "unknown relocation type"; |
|||
} |
|||
|
|||
|
|||
void RelocInfo::Print() { |
|||
PrintF("%p %s", pc_, RelocModeName(rmode_)); |
|||
if (IsComment(rmode_)) { |
|||
PrintF(" (%s)", data_); |
|||
} else if (rmode_ == EMBEDDED_OBJECT) { |
|||
PrintF(" ("); |
|||
target_object()->ShortPrint(); |
|||
PrintF(")"); |
|||
} else if (rmode_ == EXTERNAL_REFERENCE) { |
|||
ExternalReferenceEncoder ref_encoder; |
|||
PrintF(" (%s) (%p)", |
|||
ref_encoder.NameOfAddress(*target_reference_address()), |
|||
*target_reference_address()); |
|||
} else if (IsCodeTarget(rmode_)) { |
|||
Code* code = Code::GetCodeFromTargetAddress(target_address()); |
|||
PrintF(" (%s) (%p)", Code::Kind2String(code->kind()), target_address()); |
|||
} else if (IsPosition(rmode_)) { |
|||
PrintF(" (%d)", data()); |
|||
} |
|||
|
|||
PrintF("\n"); |
|||
} |
|||
#endif // ENABLE_DISASSEMBLER
|
|||
|
|||
|
|||
#ifdef DEBUG |
|||
void RelocInfo::Verify() { |
|||
switch (rmode_) { |
|||
case EMBEDDED_OBJECT: |
|||
Object::VerifyPointer(target_object()); |
|||
break; |
|||
case CONSTRUCT_CALL: |
|||
case CODE_TARGET_CONTEXT: |
|||
case CODE_TARGET: { |
|||
// convert inline target address to code object
|
|||
Address addr = target_address(); |
|||
ASSERT(addr != NULL); |
|||
// Check that we can find the right code object.
|
|||
HeapObject* code = HeapObject::FromAddress(addr - Code::kHeaderSize); |
|||
Object* found = Heap::FindCodeObject(addr); |
|||
ASSERT(found->IsCode()); |
|||
ASSERT(code->address() == HeapObject::cast(found)->address()); |
|||
break; |
|||
} |
|||
case RelocInfo::EMBEDDED_STRING: |
|||
case RUNTIME_ENTRY: |
|||
case JS_RETURN: |
|||
case COMMENT: |
|||
case POSITION: |
|||
case STATEMENT_POSITION: |
|||
case EXTERNAL_REFERENCE: |
|||
case INTERNAL_REFERENCE: |
|||
case NONE: |
|||
break; |
|||
case NUMBER_OF_MODES: |
|||
UNREACHABLE(); |
|||
break; |
|||
} |
|||
} |
|||
#endif // DEBUG
|
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// Implementation of ExternalReference
|
|||
|
|||
ExternalReference::ExternalReference(Builtins::CFunctionId id) |
|||
: address_(Builtins::c_function_address(id)) {} |
|||
|
|||
|
|||
ExternalReference::ExternalReference(Builtins::Name name) |
|||
: address_(Builtins::builtin_address(name)) {} |
|||
|
|||
|
|||
ExternalReference::ExternalReference(Runtime::FunctionId id) |
|||
: address_(Runtime::FunctionForId(id)->entry) {} |
|||
|
|||
|
|||
ExternalReference::ExternalReference(Runtime::Function* f) |
|||
: address_(f->entry) {} |
|||
|
|||
|
|||
ExternalReference::ExternalReference(const IC_Utility& ic_utility) |
|||
: address_(ic_utility.address()) {} |
|||
|
|||
|
|||
ExternalReference::ExternalReference(const Debug_Address& debug_address) |
|||
: address_(debug_address.address()) {} |
|||
|
|||
|
|||
ExternalReference::ExternalReference(StatsCounter* counter) |
|||
: address_(reinterpret_cast<Address>(counter->GetInternalPointer())) {} |
|||
|
|||
|
|||
ExternalReference::ExternalReference(Top::AddressId id) |
|||
: address_(Top::get_address_from_id(id)) {} |
|||
|
|||
|
|||
ExternalReference::ExternalReference(const SCTableReference& table_ref) |
|||
: address_(table_ref.address()) {} |
|||
|
|||
|
|||
ExternalReference ExternalReference::builtin_passed_function() { |
|||
return ExternalReference(&Builtins::builtin_passed_function); |
|||
} |
|||
|
|||
ExternalReference ExternalReference::the_hole_value_location() { |
|||
return ExternalReference(Factory::the_hole_value().location()); |
|||
} |
|||
|
|||
|
|||
ExternalReference ExternalReference::address_of_stack_guard_limit() { |
|||
return ExternalReference(StackGuard::address_of_jslimit()); |
|||
} |
|||
|
|||
|
|||
ExternalReference ExternalReference::address_of_regexp_stack_limit() { |
|||
return ExternalReference(RegExpStack::limit_address()); |
|||
} |
|||
|
|||
|
|||
ExternalReference ExternalReference::debug_break() { |
|||
return ExternalReference(FUNCTION_ADDR(Debug::Break)); |
|||
} |
|||
|
|||
|
|||
ExternalReference ExternalReference::new_space_start() { |
|||
return ExternalReference(Heap::NewSpaceStart()); |
|||
} |
|||
|
|||
ExternalReference ExternalReference::new_space_allocation_top_address() { |
|||
return ExternalReference(Heap::NewSpaceAllocationTopAddress()); |
|||
} |
|||
|
|||
ExternalReference ExternalReference::heap_always_allocate_scope_depth() { |
|||
return ExternalReference(Heap::always_allocate_scope_depth_address()); |
|||
} |
|||
|
|||
ExternalReference ExternalReference::new_space_allocation_limit_address() { |
|||
return ExternalReference(Heap::NewSpaceAllocationLimitAddress()); |
|||
} |
|||
|
|||
ExternalReference ExternalReference::debug_step_in_fp_address() { |
|||
return ExternalReference(Debug::step_in_fp_addr()); |
|||
} |
|||
|
|||
} } // namespace v8::internal
|
@ -0,0 +1,456 @@ |
|||
// Copyright (c) 1994-2006 Sun Microsystems Inc.
|
|||
// All Rights Reserved.
|
|||
//
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// - Redistributions of source code must retain the above copyright notice,
|
|||
// this list of conditions and the following disclaimer.
|
|||
//
|
|||
// - Redistribution in binary form must reproduce the above copyright
|
|||
// notice, this list of conditions and the following disclaimer in the
|
|||
// documentation and/or other materials provided with the distribution.
|
|||
//
|
|||
// - Neither the name of Sun Microsystems or the names of contributors may
|
|||
// be used to endorse or promote products derived from this software without
|
|||
// specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|||
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|||
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|||
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|||
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|||
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|||
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|||
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
// The original source code covered by the above license above has been
|
|||
// modified significantly by Google Inc.
|
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
|
|||
#ifndef V8_ASSEMBLER_H_ |
|||
#define V8_ASSEMBLER_H_ |
|||
|
|||
#include "runtime.h" |
|||
#include "top.h" |
|||
#include "zone-inl.h" |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// Labels represent pc locations; they are typically jump or call targets.
|
|||
// After declaration, a label can be freely used to denote known or (yet)
|
|||
// unknown pc location. Assembler::bind() is used to bind a label to the
|
|||
// current pc. A label can be bound only once.
|
|||
|
|||
class Label BASE_EMBEDDED { |
|||
public: |
|||
INLINE(Label()) { Unuse(); } |
|||
INLINE(~Label()) { ASSERT(!is_linked()); } |
|||
|
|||
INLINE(void Unuse()) { pos_ = 0; } |
|||
|
|||
INLINE(bool is_bound() const) { return pos_ < 0; } |
|||
INLINE(bool is_unused() const) { return pos_ == 0; } |
|||
INLINE(bool is_linked() const) { return pos_ > 0; } |
|||
|
|||
// Returns the position of bound or linked labels. Cannot be used
|
|||
// for unused labels.
|
|||
int pos() const; |
|||
|
|||
private: |
|||
// pos_ encodes both the binding state (via its sign)
|
|||
// and the binding position (via its value) of a label.
|
|||
//
|
|||
// pos_ < 0 bound label, pos() returns the jump target position
|
|||
// pos_ == 0 unused label
|
|||
// pos_ > 0 linked label, pos() returns the last reference position
|
|||
int pos_; |
|||
|
|||
void bind_to(int pos) { |
|||
pos_ = -pos - 1; |
|||
ASSERT(is_bound()); |
|||
} |
|||
void link_to(int pos) { |
|||
pos_ = pos + 1; |
|||
ASSERT(is_linked()); |
|||
} |
|||
|
|||
friend class Assembler; |
|||
friend class RegexpAssembler; |
|||
friend class Displacement; |
|||
friend class ShadowTarget; |
|||
friend class RegExpMacroAssemblerIrregexp; |
|||
}; |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// Relocation information
|
|||
|
|||
|
|||
// Relocation information consists of the address (pc) of the datum
|
|||
// to which the relocation information applies, the relocation mode
|
|||
// (rmode), and an optional data field. The relocation mode may be
|
|||
// "descriptive" and not indicate a need for relocation, but simply
|
|||
// describe a property of the datum. Such rmodes are useful for GC
|
|||
// and nice disassembly output.
|
|||
|
|||
class RelocInfo BASE_EMBEDDED { |
|||
public: |
|||
// The constant kNoPosition is used with the collecting of source positions
|
|||
// in the relocation information. Two types of source positions are collected
|
|||
// "position" (RelocMode position) and "statement position" (RelocMode
|
|||
// statement_position). The "position" is collected at places in the source
|
|||
// code which are of interest when making stack traces to pin-point the source
|
|||
// location of a stack frame as close as possible. The "statement position" is
|
|||
// collected at the beginning at each statement, and is used to indicate
|
|||
// possible break locations. kNoPosition is used to indicate an
|
|||
// invalid/uninitialized position value.
|
|||
static const int kNoPosition = -1; |
|||
|
|||
enum Mode { |
|||
// Please note the order is important (see IsCodeTarget, IsGCRelocMode).
|
|||
CONSTRUCT_CALL, // code target that is a call to a JavaScript constructor.
|
|||
CODE_TARGET_CONTEXT, // code target used for contextual loads.
|
|||
CODE_TARGET, // code target which is not any of the above.
|
|||
EMBEDDED_OBJECT, |
|||
EMBEDDED_STRING, |
|||
|
|||
// Everything after runtime_entry (inclusive) is not GC'ed.
|
|||
RUNTIME_ENTRY, |
|||
JS_RETURN, // Marks start of the ExitJSFrame code.
|
|||
COMMENT, |
|||
POSITION, // See comment for kNoPosition above.
|
|||
STATEMENT_POSITION, // See comment for kNoPosition above.
|
|||
EXTERNAL_REFERENCE, // The address of an external C++ function.
|
|||
INTERNAL_REFERENCE, // An address inside the same function.
|
|||
|
|||
// add more as needed
|
|||
// Pseudo-types
|
|||
NUMBER_OF_MODES, // must be no greater than 14 - see RelocInfoWriter
|
|||
NONE, // never recorded
|
|||
LAST_CODE_ENUM = CODE_TARGET, |
|||
LAST_GCED_ENUM = EMBEDDED_STRING |
|||
}; |
|||
|
|||
|
|||
RelocInfo() {} |
|||
RelocInfo(byte* pc, Mode rmode, intptr_t data) |
|||
: pc_(pc), rmode_(rmode), data_(data) { |
|||
} |
|||
|
|||
static inline bool IsConstructCall(Mode mode) { |
|||
return mode == CONSTRUCT_CALL; |
|||
} |
|||
static inline bool IsCodeTarget(Mode mode) { |
|||
return mode <= LAST_CODE_ENUM; |
|||
} |
|||
// Is the relocation mode affected by GC?
|
|||
static inline bool IsGCRelocMode(Mode mode) { |
|||
return mode <= LAST_GCED_ENUM; |
|||
} |
|||
static inline bool IsJSReturn(Mode mode) { |
|||
return mode == JS_RETURN; |
|||
} |
|||
static inline bool IsComment(Mode mode) { |
|||
return mode == COMMENT; |
|||
} |
|||
static inline bool IsPosition(Mode mode) { |
|||
return mode == POSITION || mode == STATEMENT_POSITION; |
|||
} |
|||
static inline bool IsStatementPosition(Mode mode) { |
|||
return mode == STATEMENT_POSITION; |
|||
} |
|||
static inline bool IsExternalReference(Mode mode) { |
|||
return mode == EXTERNAL_REFERENCE; |
|||
} |
|||
static inline bool IsInternalReference(Mode mode) { |
|||
return mode == INTERNAL_REFERENCE; |
|||
} |
|||
static inline int ModeMask(Mode mode) { return 1 << mode; } |
|||
|
|||
// Accessors
|
|||
byte* pc() const { return pc_; } |
|||
void set_pc(byte* pc) { pc_ = pc; } |
|||
Mode rmode() const { return rmode_; } |
|||
intptr_t data() const { return data_; } |
|||
|
|||
// Apply a relocation by delta bytes
|
|||
INLINE(void apply(int delta)); |
|||
|
|||
// Read/modify the code target in the branch/call instruction
|
|||
// this relocation applies to;
|
|||
// can only be called if IsCodeTarget(rmode_) || rmode_ == RUNTIME_ENTRY
|
|||
INLINE(Address target_address()); |
|||
INLINE(void set_target_address(Address target)); |
|||
INLINE(Object* target_object()); |
|||
INLINE(Object** target_object_address()); |
|||
INLINE(void set_target_object(Object* target)); |
|||
|
|||
// Read the address of the word containing the target_address. Can only
|
|||
// be called if IsCodeTarget(rmode_) || rmode_ == RUNTIME_ENTRY.
|
|||
INLINE(Address target_address_address()); |
|||
|
|||
// Read/modify the reference in the instruction this relocation
|
|||
// applies to; can only be called if rmode_ is external_reference
|
|||
INLINE(Address* target_reference_address()); |
|||
|
|||
// Read/modify the address of a call instruction. This is used to relocate
|
|||
// the break points where straight-line code is patched with a call
|
|||
// instruction.
|
|||
INLINE(Address call_address()); |
|||
INLINE(void set_call_address(Address target)); |
|||
INLINE(Object* call_object()); |
|||
INLINE(Object** call_object_address()); |
|||
INLINE(void set_call_object(Object* target)); |
|||
|
|||
// Patch the code with some other code.
|
|||
void PatchCode(byte* instructions, int instruction_count); |
|||
|
|||
// Patch the code with a call.
|
|||
void PatchCodeWithCall(Address target, int guard_bytes); |
|||
INLINE(bool IsCallInstruction()); |
|||
|
|||
#ifdef ENABLE_DISASSEMBLER |
|||
// Printing
|
|||
static const char* RelocModeName(Mode rmode); |
|||
void Print(); |
|||
#endif // ENABLE_DISASSEMBLER
|
|||
#ifdef DEBUG |
|||
// Debugging
|
|||
void Verify(); |
|||
#endif |
|||
|
|||
static const int kCodeTargetMask = (1 << (LAST_CODE_ENUM + 1)) - 1; |
|||
static const int kPositionMask = 1 << POSITION | 1 << STATEMENT_POSITION; |
|||
static const int kDebugMask = kPositionMask | 1 << COMMENT; |
|||
static const int kApplyMask; // Modes affected by apply. Depends on arch.
|
|||
|
|||
private: |
|||
// On ARM, note that pc_ is the address of the constant pool entry
|
|||
// to be relocated and not the address of the instruction
|
|||
// referencing the constant pool entry (except when rmode_ ==
|
|||
// comment).
|
|||
byte* pc_; |
|||
Mode rmode_; |
|||
intptr_t data_; |
|||
friend class RelocIterator; |
|||
}; |
|||
|
|||
|
|||
// RelocInfoWriter serializes a stream of relocation info. It writes towards
|
|||
// lower addresses.
|
|||
class RelocInfoWriter BASE_EMBEDDED { |
|||
public: |
|||
RelocInfoWriter() : pos_(NULL), last_pc_(NULL), last_data_(0) {} |
|||
RelocInfoWriter(byte* pos, byte* pc) : pos_(pos), last_pc_(pc), |
|||
last_data_(0) {} |
|||
|
|||
byte* pos() const { return pos_; } |
|||
byte* last_pc() const { return last_pc_; } |
|||
|
|||
void Write(const RelocInfo* rinfo); |
|||
|
|||
// Update the state of the stream after reloc info buffer
|
|||
// and/or code is moved while the stream is active.
|
|||
void Reposition(byte* pos, byte* pc) { |
|||
pos_ = pos; |
|||
last_pc_ = pc; |
|||
} |
|||
|
|||
// Max size (bytes) of a written RelocInfo.
|
|||
static const int kMaxSize = 12; |
|||
|
|||
private: |
|||
inline uint32_t WriteVariableLengthPCJump(uint32_t pc_delta); |
|||
inline void WriteTaggedPC(uint32_t pc_delta, int tag); |
|||
inline void WriteExtraTaggedPC(uint32_t pc_delta, int extra_tag); |
|||
inline void WriteExtraTaggedData(int32_t data_delta, int top_tag); |
|||
inline void WriteTaggedData(int32_t data_delta, int tag); |
|||
inline void WriteExtraTag(int extra_tag, int top_tag); |
|||
|
|||
byte* pos_; |
|||
byte* last_pc_; |
|||
intptr_t last_data_; |
|||
DISALLOW_COPY_AND_ASSIGN(RelocInfoWriter); |
|||
}; |
|||
|
|||
|
|||
// A RelocIterator iterates over relocation information.
|
|||
// Typical use:
|
|||
//
|
|||
// for (RelocIterator it(code); !it.done(); it.next()) {
|
|||
// // do something with it.rinfo() here
|
|||
// }
|
|||
//
|
|||
// A mask can be specified to skip unwanted modes.
|
|||
class RelocIterator: public Malloced { |
|||
public: |
|||
// Create a new iterator positioned at
|
|||
// the beginning of the reloc info.
|
|||
// Relocation information with mode k is included in the
|
|||
// iteration iff bit k of mode_mask is set.
|
|||
explicit RelocIterator(Code* code, int mode_mask = -1); |
|||
explicit RelocIterator(const CodeDesc& desc, int mode_mask = -1); |
|||
|
|||
// Iteration
|
|||
bool done() const { return done_; } |
|||
void next(); |
|||
|
|||
// Return pointer valid until next next().
|
|||
RelocInfo* rinfo() { |
|||
ASSERT(!done()); |
|||
return &rinfo_; |
|||
} |
|||
|
|||
private: |
|||
// Advance* moves the position before/after reading.
|
|||
// *Read* reads from current byte(s) into rinfo_.
|
|||
// *Get* just reads and returns info on current byte.
|
|||
void Advance(int bytes = 1) { pos_ -= bytes; } |
|||
int AdvanceGetTag(); |
|||
int GetExtraTag(); |
|||
int GetTopTag(); |
|||
void ReadTaggedPC(); |
|||
void AdvanceReadPC(); |
|||
void AdvanceReadData(); |
|||
void AdvanceReadVariableLengthPCJump(); |
|||
int GetPositionTypeTag(); |
|||
void ReadTaggedData(); |
|||
|
|||
static RelocInfo::Mode DebugInfoModeFromTag(int tag); |
|||
|
|||
// If the given mode is wanted, set it in rinfo_ and return true.
|
|||
// Else return false. Used for efficiently skipping unwanted modes.
|
|||
bool SetMode(RelocInfo::Mode mode) { |
|||
return (mode_mask_ & 1 << mode) ? (rinfo_.rmode_ = mode, true) : false; |
|||
} |
|||
|
|||
byte* pos_; |
|||
byte* end_; |
|||
RelocInfo rinfo_; |
|||
bool done_; |
|||
int mode_mask_; |
|||
DISALLOW_COPY_AND_ASSIGN(RelocIterator); |
|||
}; |
|||
|
|||
|
|||
// A stack-allocated code region logs a name for the code generated
|
|||
// while the region is in effect. This information is used by the
|
|||
// profiler to categorize ticks within generated code.
|
|||
class CodeRegion BASE_EMBEDDED { |
|||
public: |
|||
inline CodeRegion(Assembler* assm, const char *name) : assm_(assm) { |
|||
LOG(BeginCodeRegionEvent(this, assm, name)); |
|||
} |
|||
inline ~CodeRegion() { |
|||
LOG(EndCodeRegionEvent(this, assm_)); |
|||
} |
|||
private: |
|||
Assembler* assm_; |
|||
}; |
|||
|
|||
|
|||
//------------------------------------------------------------------------------
|
|||
// External function
|
|||
|
|||
//----------------------------------------------------------------------------
|
|||
class IC_Utility; |
|||
class Debug_Address; |
|||
class SCTableReference; |
|||
|
|||
// An ExternalReference represents a C++ address called from the generated
|
|||
// code. All references to C++ functions and must be encapsulated in an
|
|||
// ExternalReference instance. This is done in order to track the origin of
|
|||
// all external references in the code.
|
|||
class ExternalReference BASE_EMBEDDED { |
|||
public: |
|||
explicit ExternalReference(Builtins::CFunctionId id); |
|||
|
|||
explicit ExternalReference(Builtins::Name name); |
|||
|
|||
explicit ExternalReference(Runtime::FunctionId id); |
|||
|
|||
explicit ExternalReference(Runtime::Function* f); |
|||
|
|||
explicit ExternalReference(const IC_Utility& ic_utility); |
|||
|
|||
explicit ExternalReference(const Debug_Address& debug_address); |
|||
|
|||
explicit ExternalReference(StatsCounter* counter); |
|||
|
|||
explicit ExternalReference(Top::AddressId id); |
|||
|
|||
explicit ExternalReference(const SCTableReference& table_ref); |
|||
|
|||
// One-of-a-kind references. These references are not part of a general
|
|||
// pattern. This means that they have to be added to the
|
|||
// ExternalReferenceTable in serialize.cc manually.
|
|||
|
|||
static ExternalReference builtin_passed_function(); |
|||
|
|||
// Static variable Factory::the_hole_value.location()
|
|||
static ExternalReference the_hole_value_location(); |
|||
|
|||
// Static variable StackGuard::address_of_jslimit()
|
|||
static ExternalReference address_of_stack_guard_limit(); |
|||
|
|||
// Static variable RegExpStack::limit_address()
|
|||
static ExternalReference address_of_regexp_stack_limit(); |
|||
|
|||
// Function Debug::Break()
|
|||
static ExternalReference debug_break(); |
|||
|
|||
// Static variable Heap::NewSpaceStart()
|
|||
static ExternalReference new_space_start(); |
|||
static ExternalReference heap_always_allocate_scope_depth(); |
|||
|
|||
// Used for fast allocation in generated code.
|
|||
static ExternalReference new_space_allocation_top_address(); |
|||
static ExternalReference new_space_allocation_limit_address(); |
|||
|
|||
// Used to check if single stepping is enabled in generated code.
|
|||
static ExternalReference debug_step_in_fp_address(); |
|||
|
|||
Address address() const {return address_;} |
|||
|
|||
private: |
|||
explicit ExternalReference(void* address) |
|||
: address_(reinterpret_cast<Address>(address)) {} |
|||
|
|||
Address address_; |
|||
}; |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
// Utility functions
|
|||
|
|||
// Move these into inline file?
|
|||
|
|||
static inline bool is_intn(int x, int n) { |
|||
return -(1 << (n-1)) <= x && x < (1 << (n-1)); |
|||
} |
|||
|
|||
static inline bool is_int24(int x) { return is_intn(x, 24); } |
|||
static inline bool is_int8(int x) { return is_intn(x, 8); } |
|||
|
|||
static inline bool is_uintn(int x, int n) { |
|||
return (x & -(1 << n)) == 0; |
|||
} |
|||
|
|||
static inline bool is_uint3(int x) { return is_uintn(x, 3); } |
|||
static inline bool is_uint4(int x) { return is_uintn(x, 4); } |
|||
static inline bool is_uint5(int x) { return is_uintn(x, 5); } |
|||
static inline bool is_uint8(int x) { return is_uintn(x, 8); } |
|||
static inline bool is_uint12(int x) { return is_uintn(x, 12); } |
|||
static inline bool is_uint16(int x) { return is_uintn(x, 16); } |
|||
static inline bool is_uint24(int x) { return is_uintn(x, 24); } |
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_ASSEMBLER_H_
|
@ -0,0 +1,490 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#include "v8.h" |
|||
|
|||
#include "ast.h" |
|||
#include "scopes.h" |
|||
#include "string-stream.h" |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
|
|||
VariableProxySentinel VariableProxySentinel::this_proxy_(true); |
|||
VariableProxySentinel VariableProxySentinel::identifier_proxy_(false); |
|||
ValidLeftHandSideSentinel ValidLeftHandSideSentinel::instance_; |
|||
Property Property::this_property_(VariableProxySentinel::this_proxy(), NULL, 0); |
|||
Call Call::sentinel_(NULL, NULL, 0); |
|||
CallEval CallEval::sentinel_(NULL, NULL, 0); |
|||
|
|||
|
|||
// ----------------------------------------------------------------------------
|
|||
// All the Accept member functions for each syntax tree node type.
|
|||
|
|||
#define DECL_ACCEPT(type) \ |
|||
void type::Accept(AstVisitor* v) { \ |
|||
if (v->CheckStackOverflow()) return; \ |
|||
v->Visit##type(this); \ |
|||
} |
|||
NODE_LIST(DECL_ACCEPT) |
|||
#undef DECL_ACCEPT |
|||
|
|||
|
|||
// ----------------------------------------------------------------------------
|
|||
// Implementation of other node functionality.
|
|||
|
|||
VariableProxy::VariableProxy(Handle<String> name, |
|||
bool is_this, |
|||
bool inside_with) |
|||
: name_(name), |
|||
var_(NULL), |
|||
is_this_(is_this), |
|||
inside_with_(inside_with) { |
|||
// names must be canonicalized for fast equality checks
|
|||
ASSERT(name->IsSymbol()); |
|||
// at least one access, otherwise no need for a VariableProxy
|
|||
var_uses_.RecordAccess(1); |
|||
} |
|||
|
|||
|
|||
VariableProxy::VariableProxy(bool is_this) |
|||
: is_this_(is_this) { |
|||
} |
|||
|
|||
|
|||
void VariableProxy::BindTo(Variable* var) { |
|||
ASSERT(var_ == NULL); // must be bound only once
|
|||
ASSERT(var != NULL); // must bind
|
|||
ASSERT((is_this() && var->is_this()) || name_.is_identical_to(var->name())); |
|||
// Ideally CONST-ness should match. However, this is very hard to achieve
|
|||
// because we don't know the exact semantics of conflicting (const and
|
|||
// non-const) multiple variable declarations, const vars introduced via
|
|||
// eval() etc. Const-ness and variable declarations are a complete mess
|
|||
// in JS. Sigh...
|
|||
var_ = var; |
|||
var->var_uses()->RecordUses(&var_uses_); |
|||
var->obj_uses()->RecordUses(&obj_uses_); |
|||
} |
|||
|
|||
|
|||
#ifdef DEBUG |
|||
|
|||
const char* LoopStatement::OperatorString() const { |
|||
switch (type()) { |
|||
case DO_LOOP: return "DO"; |
|||
case FOR_LOOP: return "FOR"; |
|||
case WHILE_LOOP: return "WHILE"; |
|||
} |
|||
return NULL; |
|||
} |
|||
|
|||
#endif // DEBUG
|
|||
|
|||
|
|||
Token::Value Assignment::binary_op() const { |
|||
switch (op_) { |
|||
case Token::ASSIGN_BIT_OR: return Token::BIT_OR; |
|||
case Token::ASSIGN_BIT_XOR: return Token::BIT_XOR; |
|||
case Token::ASSIGN_BIT_AND: return Token::BIT_AND; |
|||
case Token::ASSIGN_SHL: return Token::SHL; |
|||
case Token::ASSIGN_SAR: return Token::SAR; |
|||
case Token::ASSIGN_SHR: return Token::SHR; |
|||
case Token::ASSIGN_ADD: return Token::ADD; |
|||
case Token::ASSIGN_SUB: return Token::SUB; |
|||
case Token::ASSIGN_MUL: return Token::MUL; |
|||
case Token::ASSIGN_DIV: return Token::DIV; |
|||
case Token::ASSIGN_MOD: return Token::MOD; |
|||
default: UNREACHABLE(); |
|||
} |
|||
return Token::ILLEGAL; |
|||
} |
|||
|
|||
|
|||
bool FunctionLiteral::AllowsLazyCompilation() { |
|||
return scope()->AllowsLazyCompilation(); |
|||
} |
|||
|
|||
|
|||
ObjectLiteral::Property::Property(Literal* key, Expression* value) { |
|||
key_ = key; |
|||
value_ = value; |
|||
Object* k = *key->handle(); |
|||
if (k->IsSymbol() && Heap::Proto_symbol()->Equals(String::cast(k))) { |
|||
kind_ = PROTOTYPE; |
|||
} else if (value_->AsMaterializedLiteral() != NULL) { |
|||
kind_ = MATERIALIZED_LITERAL; |
|||
} else if (value_->AsLiteral() != NULL) { |
|||
kind_ = CONSTANT; |
|||
} else { |
|||
kind_ = COMPUTED; |
|||
} |
|||
} |
|||
|
|||
|
|||
ObjectLiteral::Property::Property(bool is_getter, FunctionLiteral* value) { |
|||
key_ = new Literal(value->name()); |
|||
value_ = value; |
|||
kind_ = is_getter ? GETTER : SETTER; |
|||
} |
|||
|
|||
|
|||
void TargetCollector::AddTarget(BreakTarget* target) { |
|||
// Add the label to the collector, but discard duplicates.
|
|||
int length = targets_->length(); |
|||
for (int i = 0; i < length; i++) { |
|||
if (targets_->at(i) == target) return; |
|||
} |
|||
targets_->Add(target); |
|||
} |
|||
|
|||
|
|||
// ----------------------------------------------------------------------------
|
|||
// Implementation of AstVisitor
|
|||
|
|||
|
|||
void AstVisitor::VisitStatements(ZoneList<Statement*>* statements) { |
|||
for (int i = 0; i < statements->length(); i++) { |
|||
Visit(statements->at(i)); |
|||
} |
|||
} |
|||
|
|||
|
|||
void AstVisitor::VisitExpressions(ZoneList<Expression*>* expressions) { |
|||
for (int i = 0; i < expressions->length(); i++) { |
|||
// The variable statement visiting code may pass NULL expressions
|
|||
// to this code. Maybe this should be handled by introducing an
|
|||
// undefined expression or literal? Revisit this code if this
|
|||
// changes
|
|||
Expression* expression = expressions->at(i); |
|||
if (expression != NULL) Visit(expression); |
|||
} |
|||
} |
|||
|
|||
|
|||
// ----------------------------------------------------------------------------
|
|||
// Regular expressions
|
|||
|
|||
#define MAKE_ACCEPT(Name) \ |
|||
void* RegExp##Name::Accept(RegExpVisitor* visitor, void* data) { \ |
|||
return visitor->Visit##Name(this, data); \ |
|||
} |
|||
FOR_EACH_REG_EXP_TREE_TYPE(MAKE_ACCEPT) |
|||
#undef MAKE_ACCEPT |
|||
|
|||
#define MAKE_TYPE_CASE(Name) \ |
|||
RegExp##Name* RegExpTree::As##Name() { \ |
|||
return NULL; \ |
|||
} \ |
|||
bool RegExpTree::Is##Name() { return false; } |
|||
FOR_EACH_REG_EXP_TREE_TYPE(MAKE_TYPE_CASE) |
|||
#undef MAKE_TYPE_CASE |
|||
|
|||
#define MAKE_TYPE_CASE(Name) \ |
|||
RegExp##Name* RegExp##Name::As##Name() { \ |
|||
return this; \ |
|||
} \ |
|||
bool RegExp##Name::Is##Name() { return true; } |
|||
FOR_EACH_REG_EXP_TREE_TYPE(MAKE_TYPE_CASE) |
|||
#undef MAKE_TYPE_CASE |
|||
|
|||
RegExpEmpty RegExpEmpty::kInstance; |
|||
|
|||
|
|||
static Interval ListCaptureRegisters(ZoneList<RegExpTree*>* children) { |
|||
Interval result = Interval::Empty(); |
|||
for (int i = 0; i < children->length(); i++) |
|||
result = result.Union(children->at(i)->CaptureRegisters()); |
|||
return result; |
|||
} |
|||
|
|||
|
|||
Interval RegExpAlternative::CaptureRegisters() { |
|||
return ListCaptureRegisters(nodes()); |
|||
} |
|||
|
|||
|
|||
Interval RegExpDisjunction::CaptureRegisters() { |
|||
return ListCaptureRegisters(alternatives()); |
|||
} |
|||
|
|||
|
|||
Interval RegExpLookahead::CaptureRegisters() { |
|||
return body()->CaptureRegisters(); |
|||
} |
|||
|
|||
|
|||
Interval RegExpCapture::CaptureRegisters() { |
|||
Interval self(StartRegister(index()), EndRegister(index())); |
|||
return self.Union(body()->CaptureRegisters()); |
|||
} |
|||
|
|||
|
|||
Interval RegExpQuantifier::CaptureRegisters() { |
|||
return body()->CaptureRegisters(); |
|||
} |
|||
|
|||
|
|||
bool RegExpAssertion::IsAnchored() { |
|||
return type() == RegExpAssertion::START_OF_INPUT; |
|||
} |
|||
|
|||
|
|||
bool RegExpAlternative::IsAnchored() { |
|||
ZoneList<RegExpTree*>* nodes = this->nodes(); |
|||
for (int i = 0; i < nodes->length(); i++) { |
|||
RegExpTree* node = nodes->at(i); |
|||
if (node->IsAnchored()) { return true; } |
|||
if (node->max_match() > 0) { return false; } |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
|
|||
bool RegExpDisjunction::IsAnchored() { |
|||
ZoneList<RegExpTree*>* alternatives = this->alternatives(); |
|||
for (int i = 0; i < alternatives->length(); i++) { |
|||
if (!alternatives->at(i)->IsAnchored()) |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
|
|||
bool RegExpLookahead::IsAnchored() { |
|||
return is_positive() && body()->IsAnchored(); |
|||
} |
|||
|
|||
|
|||
bool RegExpCapture::IsAnchored() { |
|||
return body()->IsAnchored(); |
|||
} |
|||
|
|||
|
|||
// Convert regular expression trees to a simple sexp representation.
|
|||
// This representation should be different from the input grammar
|
|||
// in as many cases as possible, to make it more difficult for incorrect
|
|||
// parses to look as correct ones which is likely if the input and
|
|||
// output formats are alike.
|
|||
class RegExpUnparser: public RegExpVisitor { |
|||
public: |
|||
RegExpUnparser(); |
|||
void VisitCharacterRange(CharacterRange that); |
|||
SmartPointer<const char> ToString() { return stream_.ToCString(); } |
|||
#define MAKE_CASE(Name) virtual void* Visit##Name(RegExp##Name*, void* data); |
|||
FOR_EACH_REG_EXP_TREE_TYPE(MAKE_CASE) |
|||
#undef MAKE_CASE |
|||
private: |
|||
StringStream* stream() { return &stream_; } |
|||
HeapStringAllocator alloc_; |
|||
StringStream stream_; |
|||
}; |
|||
|
|||
|
|||
RegExpUnparser::RegExpUnparser() : stream_(&alloc_) { |
|||
} |
|||
|
|||
|
|||
void* RegExpUnparser::VisitDisjunction(RegExpDisjunction* that, void* data) { |
|||
stream()->Add("(|"); |
|||
for (int i = 0; i < that->alternatives()->length(); i++) { |
|||
stream()->Add(" "); |
|||
that->alternatives()->at(i)->Accept(this, data); |
|||
} |
|||
stream()->Add(")"); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void* RegExpUnparser::VisitAlternative(RegExpAlternative* that, void* data) { |
|||
stream()->Add("(:"); |
|||
for (int i = 0; i < that->nodes()->length(); i++) { |
|||
stream()->Add(" "); |
|||
that->nodes()->at(i)->Accept(this, data); |
|||
} |
|||
stream()->Add(")"); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void RegExpUnparser::VisitCharacterRange(CharacterRange that) { |
|||
stream()->Add("%k", that.from()); |
|||
if (!that.IsSingleton()) { |
|||
stream()->Add("-%k", that.to()); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
void* RegExpUnparser::VisitCharacterClass(RegExpCharacterClass* that, |
|||
void* data) { |
|||
if (that->is_negated()) |
|||
stream()->Add("^"); |
|||
stream()->Add("["); |
|||
for (int i = 0; i < that->ranges()->length(); i++) { |
|||
if (i > 0) stream()->Add(" "); |
|||
VisitCharacterRange(that->ranges()->at(i)); |
|||
} |
|||
stream()->Add("]"); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void* RegExpUnparser::VisitAssertion(RegExpAssertion* that, void* data) { |
|||
switch (that->type()) { |
|||
case RegExpAssertion::START_OF_INPUT: |
|||
stream()->Add("@^i"); |
|||
break; |
|||
case RegExpAssertion::END_OF_INPUT: |
|||
stream()->Add("@$i"); |
|||
break; |
|||
case RegExpAssertion::START_OF_LINE: |
|||
stream()->Add("@^l"); |
|||
break; |
|||
case RegExpAssertion::END_OF_LINE: |
|||
stream()->Add("@$l"); |
|||
break; |
|||
case RegExpAssertion::BOUNDARY: |
|||
stream()->Add("@b"); |
|||
break; |
|||
case RegExpAssertion::NON_BOUNDARY: |
|||
stream()->Add("@B"); |
|||
break; |
|||
} |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void* RegExpUnparser::VisitAtom(RegExpAtom* that, void* data) { |
|||
stream()->Add("'"); |
|||
Vector<const uc16> chardata = that->data(); |
|||
for (int i = 0; i < chardata.length(); i++) { |
|||
stream()->Add("%k", chardata[i]); |
|||
} |
|||
stream()->Add("'"); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void* RegExpUnparser::VisitText(RegExpText* that, void* data) { |
|||
if (that->elements()->length() == 1) { |
|||
that->elements()->at(0).data.u_atom->Accept(this, data); |
|||
} else { |
|||
stream()->Add("(!"); |
|||
for (int i = 0; i < that->elements()->length(); i++) { |
|||
stream()->Add(" "); |
|||
that->elements()->at(i).data.u_atom->Accept(this, data); |
|||
} |
|||
stream()->Add(")"); |
|||
} |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void* RegExpUnparser::VisitQuantifier(RegExpQuantifier* that, void* data) { |
|||
stream()->Add("(# %i ", that->min()); |
|||
if (that->max() == RegExpTree::kInfinity) { |
|||
stream()->Add("- "); |
|||
} else { |
|||
stream()->Add("%i ", that->max()); |
|||
} |
|||
stream()->Add(that->is_greedy() ? "g " : "n "); |
|||
that->body()->Accept(this, data); |
|||
stream()->Add(")"); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void* RegExpUnparser::VisitCapture(RegExpCapture* that, void* data) { |
|||
stream()->Add("(^ "); |
|||
that->body()->Accept(this, data); |
|||
stream()->Add(")"); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void* RegExpUnparser::VisitLookahead(RegExpLookahead* that, void* data) { |
|||
stream()->Add("(-> "); |
|||
stream()->Add(that->is_positive() ? "+ " : "- "); |
|||
that->body()->Accept(this, data); |
|||
stream()->Add(")"); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void* RegExpUnparser::VisitBackReference(RegExpBackReference* that, |
|||
void* data) { |
|||
stream()->Add("(<- %i)", that->index()); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
void* RegExpUnparser::VisitEmpty(RegExpEmpty* that, void* data) { |
|||
stream()->Put('%'); |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
SmartPointer<const char> RegExpTree::ToString() { |
|||
RegExpUnparser unparser; |
|||
Accept(&unparser, NULL); |
|||
return unparser.ToString(); |
|||
} |
|||
|
|||
|
|||
RegExpDisjunction::RegExpDisjunction(ZoneList<RegExpTree*>* alternatives) |
|||
: alternatives_(alternatives) { |
|||
ASSERT(alternatives->length() > 1); |
|||
RegExpTree* first_alternative = alternatives->at(0); |
|||
min_match_ = first_alternative->min_match(); |
|||
max_match_ = first_alternative->max_match(); |
|||
for (int i = 1; i < alternatives->length(); i++) { |
|||
RegExpTree* alternative = alternatives->at(i); |
|||
min_match_ = Min(min_match_, alternative->min_match()); |
|||
max_match_ = Max(max_match_, alternative->max_match()); |
|||
} |
|||
} |
|||
|
|||
|
|||
RegExpAlternative::RegExpAlternative(ZoneList<RegExpTree*>* nodes) |
|||
: nodes_(nodes) { |
|||
ASSERT(nodes->length() > 1); |
|||
min_match_ = 0; |
|||
max_match_ = 0; |
|||
for (int i = 0; i < nodes->length(); i++) { |
|||
RegExpTree* node = nodes->at(i); |
|||
min_match_ += node->min_match(); |
|||
int node_max_match = node->max_match(); |
|||
if (kInfinity - max_match_ < node_max_match) { |
|||
max_match_ = kInfinity; |
|||
} else { |
|||
max_match_ += node->max_match(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
} } // namespace v8::internal
|
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,80 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
|
|||
#ifndef V8_BOOTSTRAPPER_H_ |
|||
#define V8_BOOTSTRAPPER_H_ |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
// The Boostrapper is the public interface for creating a JavaScript global
|
|||
// context.
|
|||
class Bootstrapper : public AllStatic { |
|||
public: |
|||
// Requires: Heap::Setup has been called.
|
|||
static void Initialize(bool create_heap_objects); |
|||
static void TearDown(); |
|||
|
|||
// Creates a JavaScript Global Context with initial object graph.
|
|||
// The returned value is a global handle casted to V8Environment*.
|
|||
static Handle<Context> CreateEnvironment( |
|||
Handle<Object> global_object, |
|||
v8::Handle<v8::ObjectTemplate> global_template, |
|||
v8::ExtensionConfiguration* extensions); |
|||
|
|||
// Detach the environment from its outer global object.
|
|||
static void DetachGlobal(Handle<Context> env); |
|||
|
|||
// Traverses the pointers for memory management.
|
|||
static void Iterate(ObjectVisitor* v); |
|||
|
|||
// Accessors for the native scripts cache. Used in lazy loading.
|
|||
static Handle<String> NativesSourceLookup(int index); |
|||
static bool NativesCacheLookup(Vector<const char> name, |
|||
Handle<JSFunction>* handle); |
|||
static void NativesCacheAdd(Vector<const char> name, Handle<JSFunction> fun); |
|||
|
|||
// Append code that needs fixup at the end of boot strapping.
|
|||
static void AddFixup(Code* code, MacroAssembler* masm); |
|||
|
|||
// Tells whether bootstrapping is active.
|
|||
static bool IsActive(); |
|||
|
|||
// Encoding/decoding support for fixup flags.
|
|||
class FixupFlagsIsPCRelative: public BitField<bool, 0, 1> {}; |
|||
class FixupFlagsUseCodeObject: public BitField<bool, 1, 1> {}; |
|||
class FixupFlagsArgumentsCount: public BitField<uint32_t, 2, 32-2> {}; |
|||
|
|||
// Support for thread preemption.
|
|||
static int ArchiveSpacePerThread(); |
|||
static char* ArchiveState(char* to); |
|||
static char* RestoreState(char* from); |
|||
}; |
|||
|
|||
}} // namespace v8::internal
|
|||
|
|||
#endif // V8_BOOTSTRAPPER_H_
|
@ -0,0 +1,658 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#include "v8.h" |
|||
|
|||
#include "codegen-inl.h" |
|||
#include "debug.h" |
|||
#include "runtime.h" |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
|
|||
#define __ masm-> |
|||
|
|||
|
|||
void Builtins::Generate_Adaptor(MacroAssembler* masm, CFunctionId id) { |
|||
// TODO(1238487): Don't pass the function in a static variable.
|
|||
__ mov(ip, Operand(ExternalReference::builtin_passed_function())); |
|||
__ str(r1, MemOperand(ip, 0)); |
|||
|
|||
// The actual argument count has already been loaded into register
|
|||
// r0, but JumpToBuiltin expects r0 to contain the number of
|
|||
// arguments including the receiver.
|
|||
__ add(r0, r0, Operand(1)); |
|||
__ JumpToBuiltin(ExternalReference(id)); |
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_JSConstructCall(MacroAssembler* masm) { |
|||
// ----------- S t a t e -------------
|
|||
// -- r0 : number of arguments
|
|||
// -- r1 : constructor function
|
|||
// -- lr : return address
|
|||
// -- sp[...]: constructor arguments
|
|||
// -----------------------------------
|
|||
|
|||
// Enter a construct frame.
|
|||
__ EnterConstructFrame(); |
|||
|
|||
// Preserve the two incoming parameters
|
|||
__ mov(r0, Operand(r0, LSL, kSmiTagSize)); |
|||
__ push(r0); // smi-tagged arguments count
|
|||
__ push(r1); // constructor function
|
|||
|
|||
// Allocate the new receiver object.
|
|||
__ push(r1); // argument for Runtime_NewObject
|
|||
__ CallRuntime(Runtime::kNewObject, 1); |
|||
__ push(r0); // save the receiver
|
|||
|
|||
// Push the function and the allocated receiver from the stack.
|
|||
// sp[0]: receiver (newly allocated object)
|
|||
// sp[1]: constructor function
|
|||
// sp[2]: number of arguments (smi-tagged)
|
|||
__ ldr(r1, MemOperand(sp, kPointerSize)); |
|||
__ push(r1); // function
|
|||
__ push(r0); // receiver
|
|||
|
|||
// Reload the number of arguments from the stack.
|
|||
// r1: constructor function
|
|||
// sp[0]: receiver
|
|||
// sp[1]: constructor function
|
|||
// sp[2]: receiver
|
|||
// sp[3]: constructor function
|
|||
// sp[4]: number of arguments (smi-tagged)
|
|||
__ ldr(r3, MemOperand(sp, 4 * kPointerSize)); |
|||
|
|||
// Setup pointer to last argument.
|
|||
__ add(r2, fp, Operand(StandardFrameConstants::kCallerSPOffset)); |
|||
|
|||
// Setup number of arguments for function call below
|
|||
__ mov(r0, Operand(r3, LSR, kSmiTagSize)); |
|||
|
|||
// Copy arguments and receiver to the expression stack.
|
|||
// r0: number of arguments
|
|||
// r2: address of last argument (caller sp)
|
|||
// r1: constructor function
|
|||
// r3: number of arguments (smi-tagged)
|
|||
// sp[0]: receiver
|
|||
// sp[1]: constructor function
|
|||
// sp[2]: receiver
|
|||
// sp[3]: constructor function
|
|||
// sp[4]: number of arguments (smi-tagged)
|
|||
Label loop, entry; |
|||
__ b(&entry); |
|||
__ bind(&loop); |
|||
__ ldr(ip, MemOperand(r2, r3, LSL, kPointerSizeLog2 - 1)); |
|||
__ push(ip); |
|||
__ bind(&entry); |
|||
__ sub(r3, r3, Operand(2), SetCC); |
|||
__ b(ge, &loop); |
|||
|
|||
// Call the function.
|
|||
// r0: number of arguments
|
|||
// r1: constructor function
|
|||
ParameterCount actual(r0); |
|||
__ InvokeFunction(r1, actual, CALL_FUNCTION); |
|||
|
|||
// Pop the function from the stack.
|
|||
// sp[0]: constructor function
|
|||
// sp[2]: receiver
|
|||
// sp[3]: constructor function
|
|||
// sp[4]: number of arguments (smi-tagged)
|
|||
__ pop(); |
|||
|
|||
// Restore context from the frame.
|
|||
// r0: result
|
|||
// sp[0]: receiver
|
|||
// sp[1]: constructor function
|
|||
// sp[2]: number of arguments (smi-tagged)
|
|||
__ ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset)); |
|||
|
|||
// If the result is an object (in the ECMA sense), we should get rid
|
|||
// of the receiver and use the result; see ECMA-262 section 13.2.2-7
|
|||
// on page 74.
|
|||
Label use_receiver, exit; |
|||
|
|||
// If the result is a smi, it is *not* an object in the ECMA sense.
|
|||
// r0: result
|
|||
// sp[0]: receiver (newly allocated object)
|
|||
// sp[1]: constructor function
|
|||
// sp[2]: number of arguments (smi-tagged)
|
|||
__ tst(r0, Operand(kSmiTagMask)); |
|||
__ b(eq, &use_receiver); |
|||
|
|||
// If the type of the result (stored in its map) is less than
|
|||
// FIRST_JS_OBJECT_TYPE, it is not an object in the ECMA sense.
|
|||
__ ldr(r3, FieldMemOperand(r0, HeapObject::kMapOffset)); |
|||
__ ldrb(r3, FieldMemOperand(r3, Map::kInstanceTypeOffset)); |
|||
__ cmp(r3, Operand(FIRST_JS_OBJECT_TYPE)); |
|||
__ b(ge, &exit); |
|||
|
|||
// Throw away the result of the constructor invocation and use the
|
|||
// on-stack receiver as the result.
|
|||
__ bind(&use_receiver); |
|||
__ ldr(r0, MemOperand(sp)); |
|||
|
|||
// Remove receiver from the stack, remove caller arguments, and
|
|||
// return.
|
|||
__ bind(&exit); |
|||
// r0: result
|
|||
// sp[0]: receiver (newly allocated object)
|
|||
// sp[1]: constructor function
|
|||
// sp[2]: number of arguments (smi-tagged)
|
|||
__ ldr(r1, MemOperand(sp, 2 * kPointerSize)); |
|||
__ LeaveConstructFrame(); |
|||
__ add(sp, sp, Operand(r1, LSL, kPointerSizeLog2 - 1)); |
|||
__ add(sp, sp, Operand(kPointerSize)); |
|||
__ mov(pc, Operand(lr)); |
|||
} |
|||
|
|||
|
|||
static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm, |
|||
bool is_construct) { |
|||
// Called from Generate_JS_Entry
|
|||
// r0: code entry
|
|||
// r1: function
|
|||
// r2: receiver
|
|||
// r3: argc
|
|||
// r4: argv
|
|||
// r5-r7, cp may be clobbered
|
|||
|
|||
// Clear the context before we push it when entering the JS frame.
|
|||
__ mov(cp, Operand(0)); |
|||
|
|||
// Enter an internal frame.
|
|||
__ EnterInternalFrame(); |
|||
|
|||
// Setup the context from the function argument.
|
|||
__ ldr(cp, FieldMemOperand(r1, JSFunction::kContextOffset)); |
|||
|
|||
// Push the function and the receiver onto the stack.
|
|||
__ push(r1); |
|||
__ push(r2); |
|||
|
|||
// Copy arguments to the stack in a loop.
|
|||
// r1: function
|
|||
// r3: argc
|
|||
// r4: argv, i.e. points to first arg
|
|||
Label loop, entry; |
|||
__ add(r2, r4, Operand(r3, LSL, kPointerSizeLog2)); |
|||
// r2 points past last arg.
|
|||
__ b(&entry); |
|||
__ bind(&loop); |
|||
__ ldr(r0, MemOperand(r4, kPointerSize, PostIndex)); // read next parameter
|
|||
__ ldr(r0, MemOperand(r0)); // dereference handle
|
|||
__ push(r0); // push parameter
|
|||
__ bind(&entry); |
|||
__ cmp(r4, Operand(r2)); |
|||
__ b(ne, &loop); |
|||
|
|||
// Initialize all JavaScript callee-saved registers, since they will be seen
|
|||
// by the garbage collector as part of handlers.
|
|||
__ mov(r4, Operand(Factory::undefined_value())); |
|||
__ mov(r5, Operand(r4)); |
|||
__ mov(r6, Operand(r4)); |
|||
__ mov(r7, Operand(r4)); |
|||
if (kR9Available == 1) |
|||
__ mov(r9, Operand(r4)); |
|||
|
|||
// Invoke the code and pass argc as r0.
|
|||
__ mov(r0, Operand(r3)); |
|||
if (is_construct) { |
|||
__ Call(Handle<Code>(Builtins::builtin(Builtins::JSConstructCall)), |
|||
RelocInfo::CODE_TARGET); |
|||
} else { |
|||
ParameterCount actual(r0); |
|||
__ InvokeFunction(r1, actual, CALL_FUNCTION); |
|||
} |
|||
|
|||
// Exit the JS frame and remove the parameters (except function), and return.
|
|||
// Respect ABI stack constraint.
|
|||
__ LeaveInternalFrame(); |
|||
__ mov(pc, lr); |
|||
|
|||
// r0: result
|
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_JSEntryTrampoline(MacroAssembler* masm) { |
|||
Generate_JSEntryTrampolineHelper(masm, false); |
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_JSConstructEntryTrampoline(MacroAssembler* masm) { |
|||
Generate_JSEntryTrampolineHelper(masm, true); |
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_FunctionCall(MacroAssembler* masm) { |
|||
// 1. Make sure we have at least one argument.
|
|||
// r0: actual number of argument
|
|||
{ Label done; |
|||
__ tst(r0, Operand(r0)); |
|||
__ b(ne, &done); |
|||
__ mov(r2, Operand(Factory::undefined_value())); |
|||
__ push(r2); |
|||
__ add(r0, r0, Operand(1)); |
|||
__ bind(&done); |
|||
} |
|||
|
|||
// 2. Get the function to call from the stack.
|
|||
// r0: actual number of argument
|
|||
{ Label done, non_function, function; |
|||
__ ldr(r1, MemOperand(sp, r0, LSL, kPointerSizeLog2)); |
|||
__ tst(r1, Operand(kSmiTagMask)); |
|||
__ b(eq, &non_function); |
|||
__ ldr(r2, FieldMemOperand(r1, HeapObject::kMapOffset)); |
|||
__ ldrb(r2, FieldMemOperand(r2, Map::kInstanceTypeOffset)); |
|||
__ cmp(r2, Operand(JS_FUNCTION_TYPE)); |
|||
__ b(eq, &function); |
|||
|
|||
// Non-function called: Clear the function to force exception.
|
|||
__ bind(&non_function); |
|||
__ mov(r1, Operand(0)); |
|||
__ b(&done); |
|||
|
|||
// Change the context eagerly because it will be used below to get the
|
|||
// right global object.
|
|||
__ bind(&function); |
|||
__ ldr(cp, FieldMemOperand(r1, JSFunction::kContextOffset)); |
|||
|
|||
__ bind(&done); |
|||
} |
|||
|
|||
// 3. Make sure first argument is an object; convert if necessary.
|
|||
// r0: actual number of arguments
|
|||
// r1: function
|
|||
{ Label call_to_object, use_global_receiver, patch_receiver, done; |
|||
__ add(r2, sp, Operand(r0, LSL, kPointerSizeLog2)); |
|||
__ ldr(r2, MemOperand(r2, -kPointerSize)); |
|||
|
|||
// r0: actual number of arguments
|
|||
// r1: function
|
|||
// r2: first argument
|
|||
__ tst(r2, Operand(kSmiTagMask)); |
|||
__ b(eq, &call_to_object); |
|||
|
|||
__ mov(r3, Operand(Factory::null_value())); |
|||
__ cmp(r2, r3); |
|||
__ b(eq, &use_global_receiver); |
|||
__ mov(r3, Operand(Factory::undefined_value())); |
|||
__ cmp(r2, r3); |
|||
__ b(eq, &use_global_receiver); |
|||
|
|||
__ ldr(r3, FieldMemOperand(r2, HeapObject::kMapOffset)); |
|||
__ ldrb(r3, FieldMemOperand(r3, Map::kInstanceTypeOffset)); |
|||
__ cmp(r3, Operand(FIRST_JS_OBJECT_TYPE)); |
|||
__ b(lt, &call_to_object); |
|||
__ cmp(r3, Operand(LAST_JS_OBJECT_TYPE)); |
|||
__ b(le, &done); |
|||
|
|||
__ bind(&call_to_object); |
|||
__ EnterInternalFrame(); |
|||
|
|||
// Store number of arguments and function across the call into the runtime.
|
|||
__ mov(r0, Operand(r0, LSL, kSmiTagSize)); |
|||
__ push(r0); |
|||
__ push(r1); |
|||
|
|||
__ push(r2); |
|||
__ InvokeBuiltin(Builtins::TO_OBJECT, CALL_JS); |
|||
__ mov(r2, r0); |
|||
|
|||
// Restore number of arguments and function.
|
|||
__ pop(r1); |
|||
__ pop(r0); |
|||
__ mov(r0, Operand(r0, ASR, kSmiTagSize)); |
|||
|
|||
__ LeaveInternalFrame(); |
|||
__ b(&patch_receiver); |
|||
|
|||
// Use the global receiver object from the called function as the receiver.
|
|||
__ bind(&use_global_receiver); |
|||
const int kGlobalIndex = |
|||
Context::kHeaderSize + Context::GLOBAL_INDEX * kPointerSize; |
|||
__ ldr(r2, FieldMemOperand(cp, kGlobalIndex)); |
|||
__ ldr(r2, FieldMemOperand(r2, GlobalObject::kGlobalReceiverOffset)); |
|||
|
|||
__ bind(&patch_receiver); |
|||
__ add(r3, sp, Operand(r0, LSL, kPointerSizeLog2)); |
|||
__ str(r2, MemOperand(r3, -kPointerSize)); |
|||
|
|||
__ bind(&done); |
|||
} |
|||
|
|||
// 4. Shift stuff one slot down the stack
|
|||
// r0: actual number of arguments (including call() receiver)
|
|||
// r1: function
|
|||
{ Label loop; |
|||
// Calculate the copy start address (destination). Copy end address is sp.
|
|||
__ add(r2, sp, Operand(r0, LSL, kPointerSizeLog2)); |
|||
__ add(r2, r2, Operand(kPointerSize)); // copy receiver too
|
|||
|
|||
__ bind(&loop); |
|||
__ ldr(ip, MemOperand(r2, -kPointerSize)); |
|||
__ str(ip, MemOperand(r2)); |
|||
__ sub(r2, r2, Operand(kPointerSize)); |
|||
__ cmp(r2, sp); |
|||
__ b(ne, &loop); |
|||
} |
|||
|
|||
// 5. Adjust the actual number of arguments and remove the top element.
|
|||
// r0: actual number of arguments (including call() receiver)
|
|||
// r1: function
|
|||
__ sub(r0, r0, Operand(1)); |
|||
__ add(sp, sp, Operand(kPointerSize)); |
|||
|
|||
// 6. Get the code for the function or the non-function builtin.
|
|||
// If number of expected arguments matches, then call. Otherwise restart
|
|||
// the arguments adaptor stub.
|
|||
// r0: actual number of arguments
|
|||
// r1: function
|
|||
{ Label invoke; |
|||
__ tst(r1, r1); |
|||
__ b(ne, &invoke); |
|||
__ mov(r2, Operand(0)); // expected arguments is 0 for CALL_NON_FUNCTION
|
|||
__ GetBuiltinEntry(r3, Builtins::CALL_NON_FUNCTION); |
|||
__ Jump(Handle<Code>(builtin(ArgumentsAdaptorTrampoline)), |
|||
RelocInfo::CODE_TARGET); |
|||
|
|||
__ bind(&invoke); |
|||
__ ldr(r3, FieldMemOperand(r1, JSFunction::kSharedFunctionInfoOffset)); |
|||
__ ldr(r2, |
|||
FieldMemOperand(r3, |
|||
SharedFunctionInfo::kFormalParameterCountOffset)); |
|||
__ ldr(r3, |
|||
MemOperand(r3, SharedFunctionInfo::kCodeOffset - kHeapObjectTag)); |
|||
__ add(r3, r3, Operand(Code::kHeaderSize - kHeapObjectTag)); |
|||
__ cmp(r2, r0); // Check formal and actual parameter counts.
|
|||
__ Jump(Handle<Code>(builtin(ArgumentsAdaptorTrampoline)), |
|||
RelocInfo::CODE_TARGET, ne); |
|||
|
|||
// 7. Jump to the code in r3 without checking arguments.
|
|||
ParameterCount expected(0); |
|||
__ InvokeCode(r3, expected, expected, JUMP_FUNCTION); |
|||
} |
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_FunctionApply(MacroAssembler* masm) { |
|||
const int kIndexOffset = -5 * kPointerSize; |
|||
const int kLimitOffset = -4 * kPointerSize; |
|||
const int kArgsOffset = 2 * kPointerSize; |
|||
const int kRecvOffset = 3 * kPointerSize; |
|||
const int kFunctionOffset = 4 * kPointerSize; |
|||
|
|||
__ EnterInternalFrame(); |
|||
|
|||
__ ldr(r0, MemOperand(fp, kFunctionOffset)); // get the function
|
|||
__ push(r0); |
|||
__ ldr(r0, MemOperand(fp, kArgsOffset)); // get the args array
|
|||
__ push(r0); |
|||
__ InvokeBuiltin(Builtins::APPLY_PREPARE, CALL_JS); |
|||
|
|||
// Eagerly check for stack-overflow before starting to push the arguments.
|
|||
// r0: number of arguments
|
|||
Label okay; |
|||
ExternalReference stack_guard_limit_address = |
|||
ExternalReference::address_of_stack_guard_limit(); |
|||
__ mov(r2, Operand(stack_guard_limit_address)); |
|||
__ ldr(r2, MemOperand(r2)); |
|||
__ sub(r2, sp, r2); |
|||
__ sub(r2, r2, Operand(3 * kPointerSize)); // limit, index, receiver
|
|||
|
|||
__ cmp(r2, Operand(r0, LSL, kPointerSizeLog2 - kSmiTagSize)); |
|||
__ b(hi, &okay); |
|||
|
|||
// Out of stack space.
|
|||
__ ldr(r1, MemOperand(fp, kFunctionOffset)); |
|||
__ push(r1); |
|||
__ push(r0); |
|||
__ InvokeBuiltin(Builtins::APPLY_OVERFLOW, CALL_JS); |
|||
|
|||
// Push current limit and index.
|
|||
__ bind(&okay); |
|||
__ push(r0); // limit
|
|||
__ mov(r1, Operand(0)); // initial index
|
|||
__ push(r1); |
|||
|
|||
// Change context eagerly to get the right global object if necessary.
|
|||
__ ldr(r0, MemOperand(fp, kFunctionOffset)); |
|||
__ ldr(cp, FieldMemOperand(r0, JSFunction::kContextOffset)); |
|||
|
|||
// Compute the receiver.
|
|||
Label call_to_object, use_global_receiver, push_receiver; |
|||
__ ldr(r0, MemOperand(fp, kRecvOffset)); |
|||
__ tst(r0, Operand(kSmiTagMask)); |
|||
__ b(eq, &call_to_object); |
|||
__ mov(r1, Operand(Factory::null_value())); |
|||
__ cmp(r0, r1); |
|||
__ b(eq, &use_global_receiver); |
|||
__ mov(r1, Operand(Factory::undefined_value())); |
|||
__ cmp(r0, r1); |
|||
__ b(eq, &use_global_receiver); |
|||
|
|||
// Check if the receiver is already a JavaScript object.
|
|||
// r0: receiver
|
|||
__ ldr(r1, FieldMemOperand(r0, HeapObject::kMapOffset)); |
|||
__ ldrb(r1, FieldMemOperand(r1, Map::kInstanceTypeOffset)); |
|||
__ cmp(r1, Operand(FIRST_JS_OBJECT_TYPE)); |
|||
__ b(lt, &call_to_object); |
|||
__ cmp(r1, Operand(LAST_JS_OBJECT_TYPE)); |
|||
__ b(le, &push_receiver); |
|||
|
|||
// Convert the receiver to a regular object.
|
|||
// r0: receiver
|
|||
__ bind(&call_to_object); |
|||
__ push(r0); |
|||
__ InvokeBuiltin(Builtins::TO_OBJECT, CALL_JS); |
|||
__ b(&push_receiver); |
|||
|
|||
// Use the current global receiver object as the receiver.
|
|||
__ bind(&use_global_receiver); |
|||
const int kGlobalOffset = |
|||
Context::kHeaderSize + Context::GLOBAL_INDEX * kPointerSize; |
|||
__ ldr(r0, FieldMemOperand(cp, kGlobalOffset)); |
|||
__ ldr(r0, FieldMemOperand(r0, GlobalObject::kGlobalReceiverOffset)); |
|||
|
|||
// Push the receiver.
|
|||
// r0: receiver
|
|||
__ bind(&push_receiver); |
|||
__ push(r0); |
|||
|
|||
// Copy all arguments from the array to the stack.
|
|||
Label entry, loop; |
|||
__ ldr(r0, MemOperand(fp, kIndexOffset)); |
|||
__ b(&entry); |
|||
|
|||
// Load the current argument from the arguments array and push it to the
|
|||
// stack.
|
|||
// r0: current argument index
|
|||
__ bind(&loop); |
|||
__ ldr(r1, MemOperand(fp, kArgsOffset)); |
|||
__ push(r1); |
|||
__ push(r0); |
|||
|
|||
// Call the runtime to access the property in the arguments array.
|
|||
__ CallRuntime(Runtime::kGetProperty, 2); |
|||
__ push(r0); |
|||
|
|||
// Use inline caching to access the arguments.
|
|||
__ ldr(r0, MemOperand(fp, kIndexOffset)); |
|||
__ add(r0, r0, Operand(1 << kSmiTagSize)); |
|||
__ str(r0, MemOperand(fp, kIndexOffset)); |
|||
|
|||
// Test if the copy loop has finished copying all the elements from the
|
|||
// arguments object.
|
|||
__ bind(&entry); |
|||
__ ldr(r1, MemOperand(fp, kLimitOffset)); |
|||
__ cmp(r0, r1); |
|||
__ b(ne, &loop); |
|||
|
|||
// Invoke the function.
|
|||
ParameterCount actual(r0); |
|||
__ mov(r0, Operand(r0, ASR, kSmiTagSize)); |
|||
__ ldr(r1, MemOperand(fp, kFunctionOffset)); |
|||
__ InvokeFunction(r1, actual, CALL_FUNCTION); |
|||
|
|||
// Tear down the internal frame and remove function, receiver and args.
|
|||
__ LeaveInternalFrame(); |
|||
__ add(sp, sp, Operand(3 * kPointerSize)); |
|||
__ mov(pc, lr); |
|||
} |
|||
|
|||
|
|||
static void EnterArgumentsAdaptorFrame(MacroAssembler* masm) { |
|||
__ mov(r0, Operand(r0, LSL, kSmiTagSize)); |
|||
__ mov(r4, Operand(ArgumentsAdaptorFrame::SENTINEL)); |
|||
__ stm(db_w, sp, r0.bit() | r1.bit() | r4.bit() | fp.bit() | lr.bit()); |
|||
__ add(fp, sp, Operand(3 * kPointerSize)); |
|||
} |
|||
|
|||
|
|||
static void LeaveArgumentsAdaptorFrame(MacroAssembler* masm) { |
|||
// ----------- S t a t e -------------
|
|||
// -- r0 : result being passed through
|
|||
// -----------------------------------
|
|||
// Get the number of arguments passed (as a smi), tear down the frame and
|
|||
// then tear down the parameters.
|
|||
__ ldr(r1, MemOperand(fp, -3 * kPointerSize)); |
|||
__ mov(sp, fp); |
|||
__ ldm(ia_w, sp, fp.bit() | lr.bit()); |
|||
__ add(sp, sp, Operand(r1, LSL, kPointerSizeLog2 - kSmiTagSize)); |
|||
__ add(sp, sp, Operand(kPointerSize)); // adjust for receiver
|
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) { |
|||
// ----------- S t a t e -------------
|
|||
// -- r0 : actual number of arguments
|
|||
// -- r1 : function (passed through to callee)
|
|||
// -- r2 : expected number of arguments
|
|||
// -- r3 : code entry to call
|
|||
// -----------------------------------
|
|||
|
|||
Label invoke, dont_adapt_arguments; |
|||
|
|||
Label enough, too_few; |
|||
__ cmp(r0, Operand(r2)); |
|||
__ b(lt, &too_few); |
|||
__ cmp(r2, Operand(SharedFunctionInfo::kDontAdaptArgumentsSentinel)); |
|||
__ b(eq, &dont_adapt_arguments); |
|||
|
|||
{ // Enough parameters: actual >= expected
|
|||
__ bind(&enough); |
|||
EnterArgumentsAdaptorFrame(masm); |
|||
|
|||
// Calculate copy start address into r0 and copy end address into r2.
|
|||
// r0: actual number of arguments as a smi
|
|||
// r1: function
|
|||
// r2: expected number of arguments
|
|||
// r3: code entry to call
|
|||
__ add(r0, fp, Operand(r0, LSL, kPointerSizeLog2 - kSmiTagSize)); |
|||
// adjust for return address and receiver
|
|||
__ add(r0, r0, Operand(2 * kPointerSize)); |
|||
__ sub(r2, r0, Operand(r2, LSL, kPointerSizeLog2)); |
|||
|
|||
// Copy the arguments (including the receiver) to the new stack frame.
|
|||
// r0: copy start address
|
|||
// r1: function
|
|||
// r2: copy end address
|
|||
// r3: code entry to call
|
|||
|
|||
Label copy; |
|||
__ bind(©); |
|||
__ ldr(ip, MemOperand(r0, 0)); |
|||
__ push(ip); |
|||
__ cmp(r0, r2); // Compare before moving to next argument.
|
|||
__ sub(r0, r0, Operand(kPointerSize)); |
|||
__ b(ne, ©); |
|||
|
|||
__ b(&invoke); |
|||
} |
|||
|
|||
{ // Too few parameters: Actual < expected
|
|||
__ bind(&too_few); |
|||
EnterArgumentsAdaptorFrame(masm); |
|||
|
|||
// Calculate copy start address into r0 and copy end address is fp.
|
|||
// r0: actual number of arguments as a smi
|
|||
// r1: function
|
|||
// r2: expected number of arguments
|
|||
// r3: code entry to call
|
|||
__ add(r0, fp, Operand(r0, LSL, kPointerSizeLog2 - kSmiTagSize)); |
|||
|
|||
// Copy the arguments (including the receiver) to the new stack frame.
|
|||
// r0: copy start address
|
|||
// r1: function
|
|||
// r2: expected number of arguments
|
|||
// r3: code entry to call
|
|||
Label copy; |
|||
__ bind(©); |
|||
// Adjust load for return address and receiver.
|
|||
__ ldr(ip, MemOperand(r0, 2 * kPointerSize)); |
|||
__ push(ip); |
|||
__ cmp(r0, fp); // Compare before moving to next argument.
|
|||
__ sub(r0, r0, Operand(kPointerSize)); |
|||
__ b(ne, ©); |
|||
|
|||
// Fill the remaining expected arguments with undefined.
|
|||
// r1: function
|
|||
// r2: expected number of arguments
|
|||
// r3: code entry to call
|
|||
__ mov(ip, Operand(Factory::undefined_value())); |
|||
__ sub(r2, fp, Operand(r2, LSL, kPointerSizeLog2)); |
|||
__ sub(r2, r2, Operand(4 * kPointerSize)); // Adjust for frame.
|
|||
|
|||
Label fill; |
|||
__ bind(&fill); |
|||
__ push(ip); |
|||
__ cmp(sp, r2); |
|||
__ b(ne, &fill); |
|||
} |
|||
|
|||
// Call the entry point.
|
|||
__ bind(&invoke); |
|||
__ Call(r3); |
|||
|
|||
// Exit frame and return.
|
|||
LeaveArgumentsAdaptorFrame(masm); |
|||
__ mov(pc, lr); |
|||
|
|||
|
|||
// -------------------------------------------
|
|||
// Dont adapt arguments.
|
|||
// -------------------------------------------
|
|||
__ bind(&dont_adapt_arguments); |
|||
__ mov(pc, r3); |
|||
} |
|||
|
|||
|
|||
#undef __ |
|||
|
|||
} } // namespace v8::internal
|
@ -0,0 +1,756 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#include "v8.h" |
|||
|
|||
#include "codegen-inl.h" |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
|
|||
#define __ masm-> |
|||
|
|||
|
|||
void Builtins::Generate_Adaptor(MacroAssembler* masm, CFunctionId id) { |
|||
// TODO(1238487): Don't pass the function in a static variable.
|
|||
ExternalReference passed = ExternalReference::builtin_passed_function(); |
|||
__ mov(Operand::StaticVariable(passed), edi); |
|||
|
|||
// The actual argument count has already been loaded into register
|
|||
// eax, but JumpToBuiltin expects eax to contain the number of
|
|||
// arguments including the receiver.
|
|||
__ inc(eax); |
|||
__ JumpToBuiltin(ExternalReference(id)); |
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_JSConstructCall(MacroAssembler* masm) { |
|||
// ----------- S t a t e -------------
|
|||
// -- eax: number of arguments
|
|||
// -- edi: constructor function
|
|||
// -----------------------------------
|
|||
|
|||
// Enter a construct frame.
|
|||
__ EnterConstructFrame(); |
|||
|
|||
// Store a smi-tagged arguments count on the stack.
|
|||
__ shl(eax, kSmiTagSize); |
|||
__ push(eax); |
|||
|
|||
// Push the function to invoke on the stack.
|
|||
__ push(edi); |
|||
|
|||
// Try to allocate the object without transitioning into C code. If any of the
|
|||
// preconditions is not met, the code bails out to the runtime call.
|
|||
Label rt_call, allocated; |
|||
if (FLAG_inline_new) { |
|||
Label undo_allocation; |
|||
ExternalReference debug_step_in_fp = |
|||
ExternalReference::debug_step_in_fp_address(); |
|||
__ cmp(Operand::StaticVariable(debug_step_in_fp), Immediate(0)); |
|||
__ j(not_equal, &rt_call); |
|||
// Check that function is not a Smi.
|
|||
__ test(edi, Immediate(kSmiTagMask)); |
|||
__ j(zero, &rt_call); |
|||
// Check that function is a JSFunction
|
|||
__ CmpObjectType(edi, JS_FUNCTION_TYPE, eax); |
|||
__ j(not_equal, &rt_call); |
|||
|
|||
// Verified that the constructor is a JSFunction.
|
|||
// Load the initial map and verify that it is in fact a map.
|
|||
// edi: constructor
|
|||
__ mov(eax, FieldOperand(edi, JSFunction::kPrototypeOrInitialMapOffset)); |
|||
// Will both indicate a NULL and a Smi
|
|||
__ test(eax, Immediate(kSmiTagMask)); |
|||
__ j(zero, &rt_call); |
|||
// edi: constructor
|
|||
// eax: initial map (if proven valid below)
|
|||
__ CmpObjectType(eax, MAP_TYPE, ebx); |
|||
__ j(not_equal, &rt_call); |
|||
|
|||
// Check that the constructor is not constructing a JSFunction (see comments
|
|||
// in Runtime_NewObject in runtime.cc). In which case the initial map's
|
|||
// instance type would be JS_FUNCTION_TYPE.
|
|||
// edi: constructor
|
|||
// eax: initial map
|
|||
__ CmpInstanceType(eax, JS_FUNCTION_TYPE); |
|||
__ j(equal, &rt_call); |
|||
|
|||
// Now allocate the JSObject on the heap.
|
|||
// edi: constructor
|
|||
// eax: initial map
|
|||
__ movzx_b(edi, FieldOperand(eax, Map::kInstanceSizeOffset)); |
|||
__ shl(edi, kPointerSizeLog2); |
|||
// Make sure that the maximum heap object size will never cause us
|
|||
// problem here, because it is always greater than the maximum
|
|||
// instance size that can be represented in a byte.
|
|||
ASSERT(Heap::MaxHeapObjectSize() >= (1 << kBitsPerByte)); |
|||
ExternalReference new_space_allocation_top = |
|||
ExternalReference::new_space_allocation_top_address(); |
|||
__ mov(ebx, Operand::StaticVariable(new_space_allocation_top)); |
|||
__ add(edi, Operand(ebx)); // Calculate new top
|
|||
ExternalReference new_space_allocation_limit = |
|||
ExternalReference::new_space_allocation_limit_address(); |
|||
__ cmp(edi, Operand::StaticVariable(new_space_allocation_limit)); |
|||
__ j(greater_equal, &rt_call); |
|||
// Allocated the JSObject, now initialize the fields.
|
|||
// eax: initial map
|
|||
// ebx: JSObject
|
|||
// edi: start of next object
|
|||
__ mov(Operand(ebx, JSObject::kMapOffset), eax); |
|||
__ mov(ecx, Factory::empty_fixed_array()); |
|||
__ mov(Operand(ebx, JSObject::kPropertiesOffset), ecx); |
|||
__ mov(Operand(ebx, JSObject::kElementsOffset), ecx); |
|||
// Set extra fields in the newly allocated object.
|
|||
// eax: initial map
|
|||
// ebx: JSObject
|
|||
// edi: start of next object
|
|||
{ Label loop, entry; |
|||
__ mov(edx, Factory::undefined_value()); |
|||
__ lea(ecx, Operand(ebx, JSObject::kHeaderSize)); |
|||
__ jmp(&entry); |
|||
__ bind(&loop); |
|||
__ mov(Operand(ecx, 0), edx); |
|||
__ add(Operand(ecx), Immediate(kPointerSize)); |
|||
__ bind(&entry); |
|||
__ cmp(ecx, Operand(edi)); |
|||
__ j(less, &loop); |
|||
} |
|||
|
|||
// Mostly done with the JSObject. Add the heap tag and store the new top, so
|
|||
// that we can continue and jump into the continuation code at any time from
|
|||
// now on. Any failures need to undo the setting of the new top, so that the
|
|||
// heap is in a consistent state and verifiable.
|
|||
// eax: initial map
|
|||
// ebx: JSObject
|
|||
// edi: start of next object
|
|||
__ or_(Operand(ebx), Immediate(kHeapObjectTag)); |
|||
__ mov(Operand::StaticVariable(new_space_allocation_top), edi); |
|||
|
|||
// Check if a properties array should be setup and allocate one if needed.
|
|||
// Otherwise initialize the properties to the empty_fixed_array as well.
|
|||
// eax: initial map
|
|||
// ebx: JSObject
|
|||
// edi: start of next object
|
|||
__ movzx_b(edx, FieldOperand(eax, Map::kUnusedPropertyFieldsOffset)); |
|||
__ movzx_b(ecx, FieldOperand(eax, Map::kInObjectPropertiesOffset)); |
|||
// Calculate unused properties past the end of the in-object properties.
|
|||
__ sub(edx, Operand(ecx)); |
|||
__ test(edx, Operand(edx)); |
|||
// Done if no extra properties are to be allocated.
|
|||
__ j(zero, &allocated); |
|||
|
|||
// Scale the number of elements by pointer size and add the header for
|
|||
// FixedArrays to the start of the next object calculation from above.
|
|||
// eax: initial map
|
|||
// ebx: JSObject
|
|||
// edi: start of next object (will be start of FixedArray)
|
|||
// edx: number of elements in properties array
|
|||
ASSERT(Heap::MaxHeapObjectSize() > |
|||
(FixedArray::kHeaderSize + 255*kPointerSize)); |
|||
__ lea(ecx, Operand(edi, edx, times_4, FixedArray::kHeaderSize)); |
|||
__ cmp(ecx, Operand::StaticVariable(new_space_allocation_limit)); |
|||
__ j(greater_equal, &undo_allocation); |
|||
__ mov(Operand::StaticVariable(new_space_allocation_top), ecx); |
|||
|
|||
// Initialize the FixedArray.
|
|||
// ebx: JSObject
|
|||
// edi: FixedArray
|
|||
// edx: number of elements
|
|||
// ecx: start of next object
|
|||
__ mov(eax, Factory::fixed_array_map()); |
|||
__ mov(Operand(edi, JSObject::kMapOffset), eax); // setup the map
|
|||
__ mov(Operand(edi, Array::kLengthOffset), edx); // and length
|
|||
|
|||
// Initialize the fields to undefined.
|
|||
// ebx: JSObject
|
|||
// edi: FixedArray
|
|||
// ecx: start of next object
|
|||
{ Label loop, entry; |
|||
__ mov(edx, Factory::undefined_value()); |
|||
__ lea(eax, Operand(edi, FixedArray::kHeaderSize)); |
|||
__ jmp(&entry); |
|||
__ bind(&loop); |
|||
__ mov(Operand(eax, 0), edx); |
|||
__ add(Operand(eax), Immediate(kPointerSize)); |
|||
__ bind(&entry); |
|||
__ cmp(eax, Operand(ecx)); |
|||
__ j(less, &loop); |
|||
} |
|||
|
|||
// Store the initialized FixedArray into the properties field of
|
|||
// the JSObject
|
|||
// ebx: JSObject
|
|||
// edi: FixedArray
|
|||
__ or_(Operand(edi), Immediate(kHeapObjectTag)); // add the heap tag
|
|||
__ mov(FieldOperand(ebx, JSObject::kPropertiesOffset), edi); |
|||
|
|||
|
|||
// Continue with JSObject being successfully allocated
|
|||
// ebx: JSObject
|
|||
__ jmp(&allocated); |
|||
|
|||
// Undo the setting of the new top so that the heap is verifiable. For
|
|||
// example, the map's unused properties potentially do not match the
|
|||
// allocated objects unused properties.
|
|||
// ebx: JSObject (previous new top)
|
|||
__ bind(&undo_allocation); |
|||
__ xor_(Operand(ebx), Immediate(kHeapObjectTag)); // clear the heap tag
|
|||
__ mov(Operand::StaticVariable(new_space_allocation_top), ebx); |
|||
} |
|||
|
|||
// Allocate the new receiver object using the runtime call.
|
|||
// edi: function (constructor)
|
|||
__ bind(&rt_call); |
|||
// Must restore edi (constructor) before calling runtime.
|
|||
__ mov(edi, Operand(esp, 0)); |
|||
__ push(edi); |
|||
__ CallRuntime(Runtime::kNewObject, 1); |
|||
__ mov(ebx, Operand(eax)); // store result in ebx
|
|||
|
|||
// New object allocated.
|
|||
// ebx: newly allocated object
|
|||
__ bind(&allocated); |
|||
// Retrieve the function from the stack.
|
|||
__ pop(edi); |
|||
|
|||
// Retrieve smi-tagged arguments count from the stack.
|
|||
__ mov(eax, Operand(esp, 0)); |
|||
__ shr(eax, kSmiTagSize); |
|||
|
|||
// Push the allocated receiver to the stack. We need two copies
|
|||
// because we may have to return the original one and the calling
|
|||
// conventions dictate that the called function pops the receiver.
|
|||
__ push(ebx); |
|||
__ push(ebx); |
|||
|
|||
// Setup pointer to last argument.
|
|||
__ lea(ebx, Operand(ebp, StandardFrameConstants::kCallerSPOffset)); |
|||
|
|||
// Copy arguments and receiver to the expression stack.
|
|||
Label loop, entry; |
|||
__ mov(ecx, Operand(eax)); |
|||
__ jmp(&entry); |
|||
__ bind(&loop); |
|||
__ push(Operand(ebx, ecx, times_4, 0)); |
|||
__ bind(&entry); |
|||
__ dec(ecx); |
|||
__ j(greater_equal, &loop); |
|||
|
|||
// Call the function.
|
|||
ParameterCount actual(eax); |
|||
__ InvokeFunction(edi, actual, CALL_FUNCTION); |
|||
|
|||
// Restore context from the frame.
|
|||
__ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset)); |
|||
|
|||
// If the result is an object (in the ECMA sense), we should get rid
|
|||
// of the receiver and use the result; see ECMA-262 section 13.2.2-7
|
|||
// on page 74.
|
|||
Label use_receiver, exit; |
|||
|
|||
// If the result is a smi, it is *not* an object in the ECMA sense.
|
|||
__ test(eax, Immediate(kSmiTagMask)); |
|||
__ j(zero, &use_receiver, not_taken); |
|||
|
|||
// If the type of the result (stored in its map) is less than
|
|||
// FIRST_JS_OBJECT_TYPE, it is not an object in the ECMA sense.
|
|||
__ mov(ecx, FieldOperand(eax, HeapObject::kMapOffset)); |
|||
__ movzx_b(ecx, FieldOperand(ecx, Map::kInstanceTypeOffset)); |
|||
__ cmp(ecx, FIRST_JS_OBJECT_TYPE); |
|||
__ j(greater_equal, &exit, not_taken); |
|||
|
|||
// Throw away the result of the constructor invocation and use the
|
|||
// on-stack receiver as the result.
|
|||
__ bind(&use_receiver); |
|||
__ mov(eax, Operand(esp, 0)); |
|||
|
|||
// Restore the arguments count and leave the construct frame.
|
|||
__ bind(&exit); |
|||
__ mov(ebx, Operand(esp, kPointerSize)); // get arguments count
|
|||
__ LeaveConstructFrame(); |
|||
|
|||
// Remove caller arguments from the stack and return.
|
|||
ASSERT(kSmiTagSize == 1 && kSmiTag == 0); |
|||
__ pop(ecx); |
|||
__ lea(esp, Operand(esp, ebx, times_2, 1 * kPointerSize)); // 1 ~ receiver
|
|||
__ push(ecx); |
|||
__ ret(0); |
|||
} |
|||
|
|||
|
|||
static void Generate_JSEntryTrampolineHelper(MacroAssembler* masm, |
|||
bool is_construct) { |
|||
// Clear the context before we push it when entering the JS frame.
|
|||
__ xor_(esi, Operand(esi)); // clear esi
|
|||
|
|||
// Enter an internal frame.
|
|||
__ EnterInternalFrame(); |
|||
|
|||
// Load the previous frame pointer (ebx) to access C arguments
|
|||
__ mov(ebx, Operand(ebp, 0)); |
|||
|
|||
// Get the function from the frame and setup the context.
|
|||
__ mov(ecx, Operand(ebx, EntryFrameConstants::kFunctionArgOffset)); |
|||
__ mov(esi, FieldOperand(ecx, JSFunction::kContextOffset)); |
|||
|
|||
// Push the function and the receiver onto the stack.
|
|||
__ push(ecx); |
|||
__ push(Operand(ebx, EntryFrameConstants::kReceiverArgOffset)); |
|||
|
|||
// Load the number of arguments and setup pointer to the arguments.
|
|||
__ mov(eax, Operand(ebx, EntryFrameConstants::kArgcOffset)); |
|||
__ mov(ebx, Operand(ebx, EntryFrameConstants::kArgvOffset)); |
|||
|
|||
// Copy arguments to the stack in a loop.
|
|||
Label loop, entry; |
|||
__ xor_(ecx, Operand(ecx)); // clear ecx
|
|||
__ jmp(&entry); |
|||
__ bind(&loop); |
|||
__ mov(edx, Operand(ebx, ecx, times_4, 0)); // push parameter from argv
|
|||
__ push(Operand(edx, 0)); // dereference handle
|
|||
__ inc(Operand(ecx)); |
|||
__ bind(&entry); |
|||
__ cmp(ecx, Operand(eax)); |
|||
__ j(not_equal, &loop); |
|||
|
|||
// Get the function from the stack and call it.
|
|||
__ mov(edi, Operand(esp, eax, times_4, +1 * kPointerSize)); // +1 ~ receiver
|
|||
|
|||
// Invoke the code.
|
|||
if (is_construct) { |
|||
__ call(Handle<Code>(Builtins::builtin(Builtins::JSConstructCall)), |
|||
RelocInfo::CODE_TARGET); |
|||
} else { |
|||
ParameterCount actual(eax); |
|||
__ InvokeFunction(edi, actual, CALL_FUNCTION); |
|||
} |
|||
|
|||
// Exit the JS frame. Notice that this also removes the empty
|
|||
// context and the function left on the stack by the code
|
|||
// invocation.
|
|||
__ LeaveInternalFrame(); |
|||
__ ret(1 * kPointerSize); // remove receiver
|
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_JSEntryTrampoline(MacroAssembler* masm) { |
|||
Generate_JSEntryTrampolineHelper(masm, false); |
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_JSConstructEntryTrampoline(MacroAssembler* masm) { |
|||
Generate_JSEntryTrampolineHelper(masm, true); |
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_FunctionCall(MacroAssembler* masm) { |
|||
// 1. Make sure we have at least one argument.
|
|||
{ Label done; |
|||
__ test(eax, Operand(eax)); |
|||
__ j(not_zero, &done, taken); |
|||
__ pop(ebx); |
|||
__ push(Immediate(Factory::undefined_value())); |
|||
__ push(ebx); |
|||
__ inc(eax); |
|||
__ bind(&done); |
|||
} |
|||
|
|||
// 2. Get the function to call from the stack.
|
|||
{ Label done, non_function, function; |
|||
// +1 ~ return address.
|
|||
__ mov(edi, Operand(esp, eax, times_4, +1 * kPointerSize)); |
|||
__ test(edi, Immediate(kSmiTagMask)); |
|||
__ j(zero, &non_function, not_taken); |
|||
__ CmpObjectType(edi, JS_FUNCTION_TYPE, ecx); |
|||
__ j(equal, &function, taken); |
|||
|
|||
// Non-function called: Clear the function to force exception.
|
|||
__ bind(&non_function); |
|||
__ xor_(edi, Operand(edi)); |
|||
__ jmp(&done); |
|||
|
|||
// Function called: Change context eagerly to get the right global object.
|
|||
__ bind(&function); |
|||
__ mov(esi, FieldOperand(edi, JSFunction::kContextOffset)); |
|||
|
|||
__ bind(&done); |
|||
} |
|||
|
|||
// 3. Make sure first argument is an object; convert if necessary.
|
|||
{ Label call_to_object, use_global_receiver, patch_receiver, done; |
|||
__ mov(ebx, Operand(esp, eax, times_4, 0)); |
|||
|
|||
__ test(ebx, Immediate(kSmiTagMask)); |
|||
__ j(zero, &call_to_object); |
|||
|
|||
__ cmp(ebx, Factory::null_value()); |
|||
__ j(equal, &use_global_receiver); |
|||
__ cmp(ebx, Factory::undefined_value()); |
|||
__ j(equal, &use_global_receiver); |
|||
|
|||
__ mov(ecx, FieldOperand(ebx, HeapObject::kMapOffset)); |
|||
__ movzx_b(ecx, FieldOperand(ecx, Map::kInstanceTypeOffset)); |
|||
__ cmp(ecx, FIRST_JS_OBJECT_TYPE); |
|||
__ j(less, &call_to_object); |
|||
__ cmp(ecx, LAST_JS_OBJECT_TYPE); |
|||
__ j(less_equal, &done); |
|||
|
|||
__ bind(&call_to_object); |
|||
__ EnterInternalFrame(); // preserves eax, ebx, edi
|
|||
|
|||
// Store the arguments count on the stack (smi tagged).
|
|||
ASSERT(kSmiTag == 0); |
|||
__ shl(eax, kSmiTagSize); |
|||
__ push(eax); |
|||
|
|||
__ push(edi); // save edi across the call
|
|||
__ push(ebx); |
|||
__ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION); |
|||
__ mov(ebx, eax); |
|||
__ pop(edi); // restore edi after the call
|
|||
|
|||
// Get the arguments count and untag it.
|
|||
__ pop(eax); |
|||
__ shr(eax, kSmiTagSize); |
|||
|
|||
__ LeaveInternalFrame(); |
|||
__ jmp(&patch_receiver); |
|||
|
|||
// Use the global receiver object from the called function as the receiver.
|
|||
__ bind(&use_global_receiver); |
|||
const int kGlobalIndex = |
|||
Context::kHeaderSize + Context::GLOBAL_INDEX * kPointerSize; |
|||
__ mov(ebx, FieldOperand(esi, kGlobalIndex)); |
|||
__ mov(ebx, FieldOperand(ebx, GlobalObject::kGlobalReceiverOffset)); |
|||
|
|||
__ bind(&patch_receiver); |
|||
__ mov(Operand(esp, eax, times_4, 0), ebx); |
|||
|
|||
__ bind(&done); |
|||
} |
|||
|
|||
// 4. Shift stuff one slot down the stack.
|
|||
{ Label loop; |
|||
__ lea(ecx, Operand(eax, +1)); // +1 ~ copy receiver too
|
|||
__ bind(&loop); |
|||
__ mov(ebx, Operand(esp, ecx, times_4, 0)); |
|||
__ mov(Operand(esp, ecx, times_4, kPointerSize), ebx); |
|||
__ dec(ecx); |
|||
__ j(not_zero, &loop); |
|||
} |
|||
|
|||
// 5. Remove TOS (copy of last arguments), but keep return address.
|
|||
__ pop(ebx); |
|||
__ pop(ecx); |
|||
__ push(ebx); |
|||
__ dec(eax); |
|||
|
|||
// 6. Check that function really was a function and get the code to
|
|||
// call from the function and check that the number of expected
|
|||
// arguments matches what we're providing.
|
|||
{ Label invoke; |
|||
__ test(edi, Operand(edi)); |
|||
__ j(not_zero, &invoke, taken); |
|||
__ xor_(ebx, Operand(ebx)); |
|||
__ GetBuiltinEntry(edx, Builtins::CALL_NON_FUNCTION); |
|||
__ jmp(Handle<Code>(builtin(ArgumentsAdaptorTrampoline)), |
|||
RelocInfo::CODE_TARGET); |
|||
|
|||
__ bind(&invoke); |
|||
__ mov(edx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset)); |
|||
__ mov(ebx, |
|||
FieldOperand(edx, SharedFunctionInfo::kFormalParameterCountOffset)); |
|||
__ mov(edx, FieldOperand(edx, SharedFunctionInfo::kCodeOffset)); |
|||
__ lea(edx, FieldOperand(edx, Code::kHeaderSize)); |
|||
__ cmp(eax, Operand(ebx)); |
|||
__ j(not_equal, Handle<Code>(builtin(ArgumentsAdaptorTrampoline))); |
|||
} |
|||
|
|||
// 7. Jump (tail-call) to the code in register edx without checking arguments.
|
|||
ParameterCount expected(0); |
|||
__ InvokeCode(Operand(edx), expected, expected, JUMP_FUNCTION); |
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_FunctionApply(MacroAssembler* masm) { |
|||
__ EnterInternalFrame(); |
|||
|
|||
__ push(Operand(ebp, 4 * kPointerSize)); // push this
|
|||
__ push(Operand(ebp, 2 * kPointerSize)); // push arguments
|
|||
__ InvokeBuiltin(Builtins::APPLY_PREPARE, CALL_FUNCTION); |
|||
|
|||
if (FLAG_check_stack) { |
|||
// We need to catch preemptions right here, otherwise an unlucky preemption
|
|||
// could show up as a failed apply.
|
|||
ExternalReference stack_guard_limit = |
|||
ExternalReference::address_of_stack_guard_limit(); |
|||
Label retry_preemption; |
|||
Label no_preemption; |
|||
__ bind(&retry_preemption); |
|||
__ mov(edi, Operand::StaticVariable(stack_guard_limit)); |
|||
__ cmp(esp, Operand(edi)); |
|||
__ j(above, &no_preemption, taken); |
|||
|
|||
// Preemption!
|
|||
// Because builtins always remove the receiver from the stack, we
|
|||
// have to fake one to avoid underflowing the stack.
|
|||
__ push(eax); |
|||
__ push(Immediate(Smi::FromInt(0))); |
|||
|
|||
// Do call to runtime routine.
|
|||
__ CallRuntime(Runtime::kStackGuard, 1); |
|||
__ pop(eax); |
|||
__ jmp(&retry_preemption); |
|||
|
|||
__ bind(&no_preemption); |
|||
|
|||
Label okay; |
|||
// Make ecx the space we have left.
|
|||
__ mov(ecx, Operand(esp)); |
|||
__ sub(ecx, Operand(edi)); |
|||
// Make edx the space we need for the array when it is unrolled onto the
|
|||
// stack.
|
|||
__ mov(edx, Operand(eax)); |
|||
__ shl(edx, kPointerSizeLog2 - kSmiTagSize); |
|||
__ cmp(ecx, Operand(edx)); |
|||
__ j(greater, &okay, taken); |
|||
|
|||
// Too bad: Out of stack space.
|
|||
__ push(Operand(ebp, 4 * kPointerSize)); // push this
|
|||
__ push(eax); |
|||
__ InvokeBuiltin(Builtins::APPLY_OVERFLOW, CALL_FUNCTION); |
|||
__ bind(&okay); |
|||
} |
|||
|
|||
// Push current index and limit.
|
|||
const int kLimitOffset = |
|||
StandardFrameConstants::kExpressionsOffset - 1 * kPointerSize; |
|||
const int kIndexOffset = kLimitOffset - 1 * kPointerSize; |
|||
__ push(eax); // limit
|
|||
__ push(Immediate(0)); // index
|
|||
|
|||
// Change context eagerly to get the right global object if
|
|||
// necessary.
|
|||
__ mov(edi, Operand(ebp, 4 * kPointerSize)); |
|||
__ mov(esi, FieldOperand(edi, JSFunction::kContextOffset)); |
|||
|
|||
// Compute the receiver.
|
|||
Label call_to_object, use_global_receiver, push_receiver; |
|||
__ mov(ebx, Operand(ebp, 3 * kPointerSize)); |
|||
__ test(ebx, Immediate(kSmiTagMask)); |
|||
__ j(zero, &call_to_object); |
|||
__ cmp(ebx, Factory::null_value()); |
|||
__ j(equal, &use_global_receiver); |
|||
__ cmp(ebx, Factory::undefined_value()); |
|||
__ j(equal, &use_global_receiver); |
|||
|
|||
// If given receiver is already a JavaScript object then there's no
|
|||
// reason for converting it.
|
|||
__ mov(ecx, FieldOperand(ebx, HeapObject::kMapOffset)); |
|||
__ movzx_b(ecx, FieldOperand(ecx, Map::kInstanceTypeOffset)); |
|||
__ cmp(ecx, FIRST_JS_OBJECT_TYPE); |
|||
__ j(less, &call_to_object); |
|||
__ cmp(ecx, LAST_JS_OBJECT_TYPE); |
|||
__ j(less_equal, &push_receiver); |
|||
|
|||
// Convert the receiver to an object.
|
|||
__ bind(&call_to_object); |
|||
__ push(ebx); |
|||
__ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION); |
|||
__ mov(ebx, Operand(eax)); |
|||
__ jmp(&push_receiver); |
|||
|
|||
// Use the current global receiver object as the receiver.
|
|||
__ bind(&use_global_receiver); |
|||
const int kGlobalOffset = |
|||
Context::kHeaderSize + Context::GLOBAL_INDEX * kPointerSize; |
|||
__ mov(ebx, FieldOperand(esi, kGlobalOffset)); |
|||
__ mov(ebx, FieldOperand(ebx, GlobalObject::kGlobalReceiverOffset)); |
|||
|
|||
// Push the receiver.
|
|||
__ bind(&push_receiver); |
|||
__ push(ebx); |
|||
|
|||
// Copy all arguments from the array to the stack.
|
|||
Label entry, loop; |
|||
__ mov(eax, Operand(ebp, kIndexOffset)); |
|||
__ jmp(&entry); |
|||
__ bind(&loop); |
|||
__ mov(ecx, Operand(ebp, 2 * kPointerSize)); // load arguments
|
|||
__ push(ecx); |
|||
__ push(eax); |
|||
|
|||
// Use inline caching to speed up access to arguments.
|
|||
Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize)); |
|||
__ call(ic, RelocInfo::CODE_TARGET); |
|||
// It is important that we do not have a test instruction after the
|
|||
// call. A test instruction after the call is used to indicate that
|
|||
// we have generated an inline version of the keyed load. In this
|
|||
// case, we know that we are not generating a test instruction next.
|
|||
|
|||
// Remove IC arguments from the stack and push the nth argument.
|
|||
__ add(Operand(esp), Immediate(2 * kPointerSize)); |
|||
__ push(eax); |
|||
|
|||
// Update the index on the stack and in register eax.
|
|||
__ mov(eax, Operand(ebp, kIndexOffset)); |
|||
__ add(Operand(eax), Immediate(1 << kSmiTagSize)); |
|||
__ mov(Operand(ebp, kIndexOffset), eax); |
|||
|
|||
__ bind(&entry); |
|||
__ cmp(eax, Operand(ebp, kLimitOffset)); |
|||
__ j(not_equal, &loop); |
|||
|
|||
// Invoke the function.
|
|||
ParameterCount actual(eax); |
|||
__ shr(eax, kSmiTagSize); |
|||
__ mov(edi, Operand(ebp, 4 * kPointerSize)); |
|||
__ InvokeFunction(edi, actual, CALL_FUNCTION); |
|||
|
|||
__ LeaveInternalFrame(); |
|||
__ ret(3 * kPointerSize); // remove this, receiver, and arguments
|
|||
} |
|||
|
|||
|
|||
static void EnterArgumentsAdaptorFrame(MacroAssembler* masm) { |
|||
__ push(ebp); |
|||
__ mov(ebp, Operand(esp)); |
|||
|
|||
// Store the arguments adaptor context sentinel.
|
|||
__ push(Immediate(ArgumentsAdaptorFrame::SENTINEL)); |
|||
|
|||
// Push the function on the stack.
|
|||
__ push(edi); |
|||
|
|||
// Preserve the number of arguments on the stack. Must preserve both
|
|||
// eax and ebx because these registers are used when copying the
|
|||
// arguments and the receiver.
|
|||
ASSERT(kSmiTagSize == 1); |
|||
__ lea(ecx, Operand(eax, eax, times_1, kSmiTag)); |
|||
__ push(ecx); |
|||
} |
|||
|
|||
|
|||
static void LeaveArgumentsAdaptorFrame(MacroAssembler* masm) { |
|||
// Retrieve the number of arguments from the stack.
|
|||
__ mov(ebx, Operand(ebp, ArgumentsAdaptorFrameConstants::kLengthOffset)); |
|||
|
|||
// Leave the frame.
|
|||
__ leave(); |
|||
|
|||
// Remove caller arguments from the stack.
|
|||
ASSERT(kSmiTagSize == 1 && kSmiTag == 0); |
|||
__ pop(ecx); |
|||
__ lea(esp, Operand(esp, ebx, times_2, 1 * kPointerSize)); // 1 ~ receiver
|
|||
__ push(ecx); |
|||
} |
|||
|
|||
|
|||
void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) { |
|||
// ----------- S t a t e -------------
|
|||
// -- eax : actual number of arguments
|
|||
// -- ebx : expected number of arguments
|
|||
// -- edx : code entry to call
|
|||
// -----------------------------------
|
|||
|
|||
Label invoke, dont_adapt_arguments; |
|||
__ IncrementCounter(&Counters::arguments_adaptors, 1); |
|||
|
|||
Label enough, too_few; |
|||
__ cmp(eax, Operand(ebx)); |
|||
__ j(less, &too_few); |
|||
__ cmp(ebx, SharedFunctionInfo::kDontAdaptArgumentsSentinel); |
|||
__ j(equal, &dont_adapt_arguments); |
|||
|
|||
{ // Enough parameters: Actual >= expected.
|
|||
__ bind(&enough); |
|||
EnterArgumentsAdaptorFrame(masm); |
|||
|
|||
// Copy receiver and all expected arguments.
|
|||
const int offset = StandardFrameConstants::kCallerSPOffset; |
|||
__ lea(eax, Operand(ebp, eax, times_4, offset)); |
|||
__ mov(ecx, -1); // account for receiver
|
|||
|
|||
Label copy; |
|||
__ bind(©); |
|||
__ inc(ecx); |
|||
__ push(Operand(eax, 0)); |
|||
__ sub(Operand(eax), Immediate(kPointerSize)); |
|||
__ cmp(ecx, Operand(ebx)); |
|||
__ j(less, ©); |
|||
__ jmp(&invoke); |
|||
} |
|||
|
|||
{ // Too few parameters: Actual < expected.
|
|||
__ bind(&too_few); |
|||
EnterArgumentsAdaptorFrame(masm); |
|||
|
|||
// Copy receiver and all actual arguments.
|
|||
const int offset = StandardFrameConstants::kCallerSPOffset; |
|||
__ lea(edi, Operand(ebp, eax, times_4, offset)); |
|||
__ mov(ecx, -1); // account for receiver
|
|||
|
|||
Label copy; |
|||
__ bind(©); |
|||
__ inc(ecx); |
|||
__ push(Operand(edi, 0)); |
|||
__ sub(Operand(edi), Immediate(kPointerSize)); |
|||
__ cmp(ecx, Operand(eax)); |
|||
__ j(less, ©); |
|||
|
|||
// Fill remaining expected arguments with undefined values.
|
|||
Label fill; |
|||
__ bind(&fill); |
|||
__ inc(ecx); |
|||
__ push(Immediate(Factory::undefined_value())); |
|||
__ cmp(ecx, Operand(ebx)); |
|||
__ j(less, &fill); |
|||
|
|||
// Restore function pointer.
|
|||
__ mov(edi, Operand(ebp, JavaScriptFrameConstants::kFunctionOffset)); |
|||
} |
|||
|
|||
// Call the entry point.
|
|||
__ bind(&invoke); |
|||
__ call(Operand(edx)); |
|||
|
|||
// Leave frame and return.
|
|||
LeaveArgumentsAdaptorFrame(masm); |
|||
__ ret(0); |
|||
|
|||
// -------------------------------------------
|
|||
// Dont adapt arguments.
|
|||
// -------------------------------------------
|
|||
__ bind(&dont_adapt_arguments); |
|||
__ jmp(Operand(edx)); |
|||
} |
|||
|
|||
|
|||
#undef __ |
|||
|
|||
} } // namespace v8::internal
|
@ -0,0 +1,744 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#include "v8.h" |
|||
|
|||
#include "api.h" |
|||
#include "bootstrapper.h" |
|||
#include "builtins.h" |
|||
#include "ic-inl.h" |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
// ----------------------------------------------------------------------------
|
|||
// Support macros for defining builtins in C.
|
|||
// ----------------------------------------------------------------------------
|
|||
//
|
|||
// A builtin function is defined by writing:
|
|||
//
|
|||
// BUILTIN(name) {
|
|||
// ...
|
|||
// }
|
|||
// BUILTIN_END
|
|||
//
|
|||
// In the body of the builtin function, the variable 'receiver' is visible.
|
|||
// The arguments can be accessed through:
|
|||
//
|
|||
// BUILTIN_ARG(0): Receiver (also available as 'receiver')
|
|||
// BUILTIN_ARG(1): First argument
|
|||
// ...
|
|||
// BUILTIN_ARG(n): Last argument
|
|||
//
|
|||
// and they evaluate to undefined values if too few arguments were
|
|||
// passed to the builtin function invocation.
|
|||
//
|
|||
// __argc__ is the number of arguments including the receiver.
|
|||
// ----------------------------------------------------------------------------
|
|||
|
|||
|
|||
// TODO(1238487): We should consider passing whether or not the
|
|||
// builtin was invoked as a constructor as part of the
|
|||
// arguments. Maybe we also want to pass the called function?
|
|||
#define BUILTIN(name) \ |
|||
static Object* Builtin_##name(int __argc__, Object** __argv__) { \ |
|||
Handle<Object> receiver(&__argv__[0]); |
|||
|
|||
|
|||
// Use an inline function to avoid evaluating the index (n) more than
|
|||
// once in the BUILTIN_ARG macro.
|
|||
static inline Object* __builtin_arg__(int n, int argc, Object** argv) { |
|||
ASSERT(n >= 0); |
|||
return (argc > n) ? argv[-n] : Heap::undefined_value(); |
|||
} |
|||
|
|||
|
|||
// NOTE: Argument 0 is the receiver. The first 'real' argument is
|
|||
// argument 1 - BUILTIN_ARG(1).
|
|||
#define BUILTIN_ARG(n) (__builtin_arg__(n, __argc__, __argv__)) |
|||
|
|||
|
|||
#define BUILTIN_END \ |
|||
return Heap::undefined_value(); \ |
|||
} |
|||
|
|||
|
|||
// TODO(1238487): Get rid of this function that determines if the
|
|||
// builtin is called as a constructor. This may be a somewhat slow
|
|||
// operation due to the stack frame iteration.
|
|||
static inline bool CalledAsConstructor() { |
|||
StackFrameIterator it; |
|||
ASSERT(it.frame()->is_exit()); |
|||
it.Advance(); |
|||
StackFrame* frame = it.frame(); |
|||
return frame->is_construct(); |
|||
} |
|||
|
|||
|
|||
// ----------------------------------------------------------------------------
|
|||
|
|||
|
|||
Handle<Code> Builtins::GetCode(JavaScript id, bool* resolved) { |
|||
Code* code = Builtins::builtin(Builtins::Illegal); |
|||
*resolved = false; |
|||
|
|||
if (Top::context() != NULL) { |
|||
Object* object = Top::builtins()->javascript_builtin(id); |
|||
if (object->IsJSFunction()) { |
|||
Handle<JSFunction> function(JSFunction::cast(object)); |
|||
// Make sure the number of parameters match the formal parameter count.
|
|||
ASSERT(function->shared()->formal_parameter_count() == |
|||
Builtins::GetArgumentsCount(id)); |
|||
if (function->is_compiled() || CompileLazy(function, CLEAR_EXCEPTION)) { |
|||
code = function->code(); |
|||
*resolved = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return Handle<Code>(code); |
|||
} |
|||
|
|||
|
|||
BUILTIN(Illegal) { |
|||
UNREACHABLE(); |
|||
} |
|||
BUILTIN_END |
|||
|
|||
|
|||
BUILTIN(EmptyFunction) { |
|||
} |
|||
BUILTIN_END |
|||
|
|||
|
|||
BUILTIN(ArrayCode) { |
|||
JSArray* array; |
|||
if (CalledAsConstructor()) { |
|||
array = JSArray::cast(*receiver); |
|||
} else { |
|||
// Allocate the JS Array
|
|||
JSFunction* constructor = |
|||
Top::context()->global_context()->array_function(); |
|||
Object* obj = Heap::AllocateJSObject(constructor); |
|||
if (obj->IsFailure()) return obj; |
|||
array = JSArray::cast(obj); |
|||
} |
|||
|
|||
// 'array' now contains the JSArray we should initialize.
|
|||
|
|||
// Optimize the case where there is one argument and the argument is a
|
|||
// small smi.
|
|||
if (__argc__ == 2) { |
|||
Object* obj = BUILTIN_ARG(1); |
|||
if (obj->IsSmi()) { |
|||
int len = Smi::cast(obj)->value(); |
|||
if (len >= 0 && len < JSObject::kInitialMaxFastElementArray) { |
|||
Object* obj = Heap::AllocateFixedArrayWithHoles(len); |
|||
if (obj->IsFailure()) return obj; |
|||
array->SetContent(FixedArray::cast(obj)); |
|||
return array; |
|||
} |
|||
} |
|||
// Take the argument as the length.
|
|||
obj = array->Initialize(0); |
|||
if (obj->IsFailure()) return obj; |
|||
if (__argc__ == 2) return array->SetElementsLength(BUILTIN_ARG(1)); |
|||
} |
|||
|
|||
// Optimize the case where there are no parameters passed.
|
|||
if (__argc__ == 1) return array->Initialize(4); |
|||
|
|||
// Take the arguments as elements.
|
|||
int number_of_elements = __argc__ - 1; |
|||
Smi* len = Smi::FromInt(number_of_elements); |
|||
Object* obj = Heap::AllocateFixedArrayWithHoles(len->value()); |
|||
if (obj->IsFailure()) return obj; |
|||
FixedArray* elms = FixedArray::cast(obj); |
|||
WriteBarrierMode mode = elms->GetWriteBarrierMode(); |
|||
// Fill in the content
|
|||
for (int index = 0; index < number_of_elements; index++) { |
|||
elms->set(index, BUILTIN_ARG(index+1), mode); |
|||
} |
|||
|
|||
// Set length and elements on the array.
|
|||
array->set_elements(FixedArray::cast(obj)); |
|||
array->set_length(len, SKIP_WRITE_BARRIER); |
|||
|
|||
return array; |
|||
} |
|||
BUILTIN_END |
|||
|
|||
|
|||
BUILTIN(ArrayPush) { |
|||
JSArray* array = JSArray::cast(*receiver); |
|||
ASSERT(array->HasFastElements()); |
|||
|
|||
// Make sure we have space for the elements.
|
|||
int len = Smi::cast(array->length())->value(); |
|||
|
|||
// Set new length.
|
|||
int new_length = len + __argc__ - 1; |
|||
FixedArray* elms = FixedArray::cast(array->elements()); |
|||
|
|||
if (new_length <= elms->length()) { |
|||
// Backing storage has extra space for the provided values.
|
|||
for (int index = 0; index < __argc__ - 1; index++) { |
|||
elms->set(index + len, BUILTIN_ARG(index+1)); |
|||
} |
|||
} else { |
|||
// New backing storage is needed.
|
|||
int capacity = new_length + (new_length >> 1) + 16; |
|||
Object* obj = Heap::AllocateFixedArrayWithHoles(capacity); |
|||
if (obj->IsFailure()) return obj; |
|||
FixedArray* new_elms = FixedArray::cast(obj); |
|||
WriteBarrierMode mode = new_elms->GetWriteBarrierMode(); |
|||
// Fill out the new array with old elements.
|
|||
for (int i = 0; i < len; i++) new_elms->set(i, elms->get(i), mode); |
|||
// Add the provided values.
|
|||
for (int index = 0; index < __argc__ - 1; index++) { |
|||
new_elms->set(index + len, BUILTIN_ARG(index+1), mode); |
|||
} |
|||
// Set the new backing storage.
|
|||
array->set_elements(new_elms); |
|||
} |
|||
// Set the length.
|
|||
array->set_length(Smi::FromInt(new_length), SKIP_WRITE_BARRIER); |
|||
return array->length(); |
|||
} |
|||
BUILTIN_END |
|||
|
|||
|
|||
BUILTIN(ArrayPop) { |
|||
JSArray* array = JSArray::cast(*receiver); |
|||
ASSERT(array->HasFastElements()); |
|||
Object* undefined = Heap::undefined_value(); |
|||
|
|||
int len = Smi::cast(array->length())->value(); |
|||
if (len == 0) return undefined; |
|||
|
|||
// Get top element
|
|||
FixedArray* elms = FixedArray::cast(array->elements()); |
|||
Object* top = elms->get(len - 1); |
|||
|
|||
// Set the length.
|
|||
array->set_length(Smi::FromInt(len - 1), SKIP_WRITE_BARRIER); |
|||
|
|||
if (!top->IsTheHole()) { |
|||
// Delete the top element.
|
|||
elms->set_the_hole(len - 1); |
|||
return top; |
|||
} |
|||
|
|||
// Remember to check the prototype chain.
|
|||
JSFunction* array_function = |
|||
Top::context()->global_context()->array_function(); |
|||
JSObject* prototype = JSObject::cast(array_function->prototype()); |
|||
top = prototype->GetElement(len - 1); |
|||
|
|||
return top; |
|||
} |
|||
BUILTIN_END |
|||
|
|||
|
|||
// -----------------------------------------------------------------------------
|
|||
//
|
|||
|
|||
|
|||
// Returns the holder JSObject if the function can legally be called
|
|||
// with this receiver. Returns Heap::null_value() if the call is
|
|||
// illegal. Any arguments that don't fit the expected type is
|
|||
// overwritten with undefined. Arguments that do fit the expected
|
|||
// type is overwritten with the object in the prototype chain that
|
|||
// actually has that type.
|
|||
static inline Object* TypeCheck(int argc, |
|||
Object** argv, |
|||
FunctionTemplateInfo* info) { |
|||
Object* recv = argv[0]; |
|||
Object* sig_obj = info->signature(); |
|||
if (sig_obj->IsUndefined()) return recv; |
|||
SignatureInfo* sig = SignatureInfo::cast(sig_obj); |
|||
// If necessary, check the receiver
|
|||
Object* recv_type = sig->receiver(); |
|||
|
|||
Object* holder = recv; |
|||
if (!recv_type->IsUndefined()) { |
|||
for (; holder != Heap::null_value(); holder = holder->GetPrototype()) { |
|||
if (holder->IsInstanceOf(FunctionTemplateInfo::cast(recv_type))) { |
|||
break; |
|||
} |
|||
} |
|||
if (holder == Heap::null_value()) return holder; |
|||
} |
|||
Object* args_obj = sig->args(); |
|||
// If there is no argument signature we're done
|
|||
if (args_obj->IsUndefined()) return holder; |
|||
FixedArray* args = FixedArray::cast(args_obj); |
|||
int length = args->length(); |
|||
if (argc <= length) length = argc - 1; |
|||
for (int i = 0; i < length; i++) { |
|||
Object* argtype = args->get(i); |
|||
if (argtype->IsUndefined()) continue; |
|||
Object** arg = &argv[-1 - i]; |
|||
Object* current = *arg; |
|||
for (; current != Heap::null_value(); current = current->GetPrototype()) { |
|||
if (current->IsInstanceOf(FunctionTemplateInfo::cast(argtype))) { |
|||
*arg = current; |
|||
break; |
|||
} |
|||
} |
|||
if (current == Heap::null_value()) *arg = Heap::undefined_value(); |
|||
} |
|||
return holder; |
|||
} |
|||
|
|||
|
|||
BUILTIN(HandleApiCall) { |
|||
HandleScope scope; |
|||
bool is_construct = CalledAsConstructor(); |
|||
|
|||
// TODO(1238487): This is not nice. We need to get rid of this
|
|||
// kludgy behavior and start handling API calls in a more direct
|
|||
// way - maybe compile specialized stubs lazily?.
|
|||
Handle<JSFunction> function = |
|||
Handle<JSFunction>(JSFunction::cast(Builtins::builtin_passed_function)); |
|||
|
|||
if (is_construct) { |
|||
Handle<FunctionTemplateInfo> desc = |
|||
Handle<FunctionTemplateInfo>( |
|||
FunctionTemplateInfo::cast(function->shared()->function_data())); |
|||
bool pending_exception = false; |
|||
Factory::ConfigureInstance(desc, Handle<JSObject>::cast(receiver), |
|||
&pending_exception); |
|||
ASSERT(Top::has_pending_exception() == pending_exception); |
|||
if (pending_exception) return Failure::Exception(); |
|||
} |
|||
|
|||
FunctionTemplateInfo* fun_data = |
|||
FunctionTemplateInfo::cast(function->shared()->function_data()); |
|||
Object* raw_holder = TypeCheck(__argc__, __argv__, fun_data); |
|||
|
|||
if (raw_holder->IsNull()) { |
|||
// This function cannot be called with the given receiver. Abort!
|
|||
Handle<Object> obj = |
|||
Factory::NewTypeError("illegal_invocation", HandleVector(&function, 1)); |
|||
return Top::Throw(*obj); |
|||
} |
|||
|
|||
Object* raw_call_data = fun_data->call_code(); |
|||
if (!raw_call_data->IsUndefined()) { |
|||
CallHandlerInfo* call_data = CallHandlerInfo::cast(raw_call_data); |
|||
Object* callback_obj = call_data->callback(); |
|||
v8::InvocationCallback callback = |
|||
v8::ToCData<v8::InvocationCallback>(callback_obj); |
|||
Object* data_obj = call_data->data(); |
|||
Object* result; |
|||
|
|||
v8::Local<v8::Object> self = |
|||
v8::Utils::ToLocal(Handle<JSObject>::cast(receiver)); |
|||
Handle<Object> data_handle(data_obj); |
|||
v8::Local<v8::Value> data = v8::Utils::ToLocal(data_handle); |
|||
ASSERT(raw_holder->IsJSObject()); |
|||
v8::Local<v8::Function> callee = v8::Utils::ToLocal(function); |
|||
Handle<JSObject> holder_handle(JSObject::cast(raw_holder)); |
|||
v8::Local<v8::Object> holder = v8::Utils::ToLocal(holder_handle); |
|||
LOG(ApiObjectAccess("call", JSObject::cast(*receiver))); |
|||
v8::Arguments args = v8::ImplementationUtilities::NewArguments( |
|||
data, |
|||
holder, |
|||
callee, |
|||
is_construct, |
|||
reinterpret_cast<void**>(__argv__ - 1), |
|||
__argc__ - 1); |
|||
|
|||
v8::Handle<v8::Value> value; |
|||
{ |
|||
// Leaving JavaScript.
|
|||
VMState state(EXTERNAL); |
|||
value = callback(args); |
|||
} |
|||
if (value.IsEmpty()) { |
|||
result = Heap::undefined_value(); |
|||
} else { |
|||
result = *reinterpret_cast<Object**>(*value); |
|||
} |
|||
|
|||
RETURN_IF_SCHEDULED_EXCEPTION(); |
|||
if (!is_construct || result->IsJSObject()) return result; |
|||
} |
|||
|
|||
return *receiver; |
|||
} |
|||
BUILTIN_END |
|||
|
|||
|
|||
// Handle calls to non-function objects created through the API that
|
|||
// support calls.
|
|||
BUILTIN(HandleApiCallAsFunction) { |
|||
// Non-functions are never called as constructors.
|
|||
ASSERT(!CalledAsConstructor()); |
|||
|
|||
// Get the object called.
|
|||
JSObject* obj = JSObject::cast(*receiver); |
|||
|
|||
// Get the invocation callback from the function descriptor that was
|
|||
// used to create the called object.
|
|||
ASSERT(obj->map()->has_instance_call_handler()); |
|||
JSFunction* constructor = JSFunction::cast(obj->map()->constructor()); |
|||
Object* template_info = constructor->shared()->function_data(); |
|||
Object* handler = |
|||
FunctionTemplateInfo::cast(template_info)->instance_call_handler(); |
|||
ASSERT(!handler->IsUndefined()); |
|||
CallHandlerInfo* call_data = CallHandlerInfo::cast(handler); |
|||
Object* callback_obj = call_data->callback(); |
|||
v8::InvocationCallback callback = |
|||
v8::ToCData<v8::InvocationCallback>(callback_obj); |
|||
|
|||
// Get the data for the call and perform the callback.
|
|||
Object* data_obj = call_data->data(); |
|||
Object* result; |
|||
{ HandleScope scope; |
|||
v8::Local<v8::Object> self = |
|||
v8::Utils::ToLocal(Handle<JSObject>::cast(receiver)); |
|||
Handle<Object> data_handle(data_obj); |
|||
v8::Local<v8::Value> data = v8::Utils::ToLocal(data_handle); |
|||
Handle<JSFunction> callee_handle(constructor); |
|||
v8::Local<v8::Function> callee = v8::Utils::ToLocal(callee_handle); |
|||
LOG(ApiObjectAccess("call non-function", JSObject::cast(*receiver))); |
|||
v8::Arguments args = v8::ImplementationUtilities::NewArguments( |
|||
data, |
|||
self, |
|||
callee, |
|||
false, |
|||
reinterpret_cast<void**>(__argv__ - 1), |
|||
__argc__ - 1); |
|||
v8::Handle<v8::Value> value; |
|||
{ |
|||
// Leaving JavaScript.
|
|||
VMState state(EXTERNAL); |
|||
value = callback(args); |
|||
} |
|||
if (value.IsEmpty()) { |
|||
result = Heap::undefined_value(); |
|||
} else { |
|||
result = *reinterpret_cast<Object**>(*value); |
|||
} |
|||
} |
|||
// Check for exceptions and return result.
|
|||
RETURN_IF_SCHEDULED_EXCEPTION(); |
|||
return result; |
|||
} |
|||
BUILTIN_END |
|||
|
|||
|
|||
// TODO(1238487): This is a nasty hack. We need to improve the way we
|
|||
// call builtins considerable to get rid of this and the hairy macros
|
|||
// in builtins.cc.
|
|||
Object* Builtins::builtin_passed_function; |
|||
|
|||
|
|||
|
|||
static void Generate_LoadIC_ArrayLength(MacroAssembler* masm) { |
|||
LoadIC::GenerateArrayLength(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_LoadIC_StringLength(MacroAssembler* masm) { |
|||
LoadIC::GenerateStringLength(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_LoadIC_FunctionPrototype(MacroAssembler* masm) { |
|||
LoadIC::GenerateFunctionPrototype(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_LoadIC_Initialize(MacroAssembler* masm) { |
|||
LoadIC::GenerateInitialize(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_LoadIC_PreMonomorphic(MacroAssembler* masm) { |
|||
LoadIC::GeneratePreMonomorphic(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_LoadIC_Miss(MacroAssembler* masm) { |
|||
LoadIC::GenerateMiss(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_LoadIC_Megamorphic(MacroAssembler* masm) { |
|||
LoadIC::GenerateMegamorphic(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_LoadIC_Normal(MacroAssembler* masm) { |
|||
LoadIC::GenerateNormal(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_KeyedLoadIC_Initialize(MacroAssembler* masm) { |
|||
KeyedLoadIC::GenerateInitialize(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_KeyedLoadIC_Miss(MacroAssembler* masm) { |
|||
KeyedLoadIC::GenerateMiss(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_KeyedLoadIC_Generic(MacroAssembler* masm) { |
|||
KeyedLoadIC::GenerateGeneric(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_KeyedLoadIC_PreMonomorphic(MacroAssembler* masm) { |
|||
KeyedLoadIC::GeneratePreMonomorphic(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_StoreIC_Initialize(MacroAssembler* masm) { |
|||
StoreIC::GenerateInitialize(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_StoreIC_Miss(MacroAssembler* masm) { |
|||
StoreIC::GenerateMiss(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_StoreIC_ExtendStorage(MacroAssembler* masm) { |
|||
StoreIC::GenerateExtendStorage(masm); |
|||
} |
|||
|
|||
static void Generate_StoreIC_Megamorphic(MacroAssembler* masm) { |
|||
StoreIC::GenerateMegamorphic(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_KeyedStoreIC_Generic(MacroAssembler* masm) { |
|||
KeyedStoreIC::GenerateGeneric(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_KeyedStoreIC_ExtendStorage(MacroAssembler* masm) { |
|||
KeyedStoreIC::GenerateExtendStorage(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_KeyedStoreIC_Miss(MacroAssembler* masm) { |
|||
KeyedStoreIC::GenerateMiss(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_KeyedStoreIC_Initialize(MacroAssembler* masm) { |
|||
KeyedStoreIC::GenerateInitialize(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_LoadIC_DebugBreak(MacroAssembler* masm) { |
|||
Debug::GenerateLoadICDebugBreak(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_StoreIC_DebugBreak(MacroAssembler* masm) { |
|||
Debug::GenerateStoreICDebugBreak(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_KeyedLoadIC_DebugBreak(MacroAssembler* masm) { |
|||
Debug::GenerateKeyedLoadICDebugBreak(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_KeyedStoreIC_DebugBreak(MacroAssembler* masm) { |
|||
Debug::GenerateKeyedStoreICDebugBreak(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_ConstructCall_DebugBreak(MacroAssembler* masm) { |
|||
Debug::GenerateConstructCallDebugBreak(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_Return_DebugBreak(MacroAssembler* masm) { |
|||
Debug::GenerateReturnDebugBreak(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_Return_DebugBreakEntry(MacroAssembler* masm) { |
|||
Debug::GenerateReturnDebugBreakEntry(masm); |
|||
} |
|||
|
|||
|
|||
static void Generate_StubNoRegisters_DebugBreak(MacroAssembler* masm) { |
|||
Debug::GenerateStubNoRegistersDebugBreak(masm); |
|||
} |
|||
|
|||
|
|||
Object* Builtins::builtins_[builtin_count] = { NULL, }; |
|||
const char* Builtins::names_[builtin_count] = { NULL, }; |
|||
|
|||
#define DEF_ENUM_C(name) FUNCTION_ADDR(Builtin_##name), |
|||
Address Builtins::c_functions_[cfunction_count] = { |
|||
BUILTIN_LIST_C(DEF_ENUM_C) |
|||
}; |
|||
#undef DEF_ENUM_C |
|||
|
|||
#define DEF_JS_NAME(name, ignore) #name, |
|||
#define DEF_JS_ARGC(ignore, argc) argc, |
|||
const char* Builtins::javascript_names_[id_count] = { |
|||
BUILTINS_LIST_JS(DEF_JS_NAME) |
|||
}; |
|||
|
|||
int Builtins::javascript_argc_[id_count] = { |
|||
BUILTINS_LIST_JS(DEF_JS_ARGC) |
|||
}; |
|||
#undef DEF_JS_NAME |
|||
#undef DEF_JS_ARGC |
|||
|
|||
static bool is_initialized = false; |
|||
void Builtins::Setup(bool create_heap_objects) { |
|||
ASSERT(!is_initialized); |
|||
|
|||
// Create a scope for the handles in the builtins.
|
|||
HandleScope scope; |
|||
|
|||
struct BuiltinDesc { |
|||
byte* generator; |
|||
byte* c_code; |
|||
const char* s_name; // name is only used for generating log information.
|
|||
int name; |
|||
Code::Flags flags; |
|||
}; |
|||
|
|||
#define DEF_FUNCTION_PTR_C(name) \ |
|||
{ FUNCTION_ADDR(Generate_Adaptor), \ |
|||
FUNCTION_ADDR(Builtin_##name), \ |
|||
#name, \ |
|||
c_##name, \ |
|||
Code::ComputeFlags(Code::BUILTIN) \ |
|||
}, |
|||
|
|||
#define DEF_FUNCTION_PTR_A(name, kind, state) \ |
|||
{ FUNCTION_ADDR(Generate_##name), \ |
|||
NULL, \ |
|||
#name, \ |
|||
name, \ |
|||
Code::ComputeFlags(Code::kind, state) \ |
|||
}, |
|||
|
|||
// Define array of pointers to generators and C builtin functions.
|
|||
static BuiltinDesc functions[] = { |
|||
BUILTIN_LIST_C(DEF_FUNCTION_PTR_C) |
|||
BUILTIN_LIST_A(DEF_FUNCTION_PTR_A) |
|||
BUILTIN_LIST_DEBUG_A(DEF_FUNCTION_PTR_A) |
|||
// Terminator:
|
|||
{ NULL, NULL, NULL, builtin_count, static_cast<Code::Flags>(0) } |
|||
}; |
|||
|
|||
#undef DEF_FUNCTION_PTR_C |
|||
#undef DEF_FUNCTION_PTR_A |
|||
|
|||
// For now we generate builtin adaptor code into a stack-allocated
|
|||
// buffer, before copying it into individual code objects.
|
|||
byte buffer[4*KB]; |
|||
|
|||
// Traverse the list of builtins and generate an adaptor in a
|
|||
// separate code object for each one.
|
|||
for (int i = 0; i < builtin_count; i++) { |
|||
if (create_heap_objects) { |
|||
MacroAssembler masm(buffer, sizeof buffer); |
|||
// Generate the code/adaptor.
|
|||
typedef void (*Generator)(MacroAssembler*, int); |
|||
Generator g = FUNCTION_CAST<Generator>(functions[i].generator); |
|||
// We pass all arguments to the generator, but it may not use all of
|
|||
// them. This works because the first arguments are on top of the
|
|||
// stack.
|
|||
g(&masm, functions[i].name); |
|||
// Move the code into the object heap.
|
|||
CodeDesc desc; |
|||
masm.GetCode(&desc); |
|||
Code::Flags flags = functions[i].flags; |
|||
Object* code; |
|||
{ |
|||
// During startup it's OK to always allocate and defer GC to later.
|
|||
// This simplifies things because we don't need to retry.
|
|||
AlwaysAllocateScope __scope__; |
|||
code = Heap::CreateCode(desc, NULL, flags, masm.CodeObject()); |
|||
if (code->IsFailure()) { |
|||
v8::internal::V8::FatalProcessOutOfMemory("CreateCode"); |
|||
} |
|||
} |
|||
// Add any unresolved jumps or calls to the fixup list in the
|
|||
// bootstrapper.
|
|||
Bootstrapper::AddFixup(Code::cast(code), &masm); |
|||
// Log the event and add the code to the builtins array.
|
|||
LOG(CodeCreateEvent("Builtin", Code::cast(code), functions[i].s_name)); |
|||
builtins_[i] = code; |
|||
#ifdef ENABLE_DISASSEMBLER |
|||
if (FLAG_print_builtin_code) { |
|||
PrintF("Builtin: %s\n", functions[i].s_name); |
|||
Code::cast(code)->Disassemble(functions[i].s_name); |
|||
PrintF("\n"); |
|||
} |
|||
#endif |
|||
} else { |
|||
// Deserializing. The values will be filled in during IterateBuiltins.
|
|||
builtins_[i] = NULL; |
|||
} |
|||
names_[i] = functions[i].s_name; |
|||
} |
|||
|
|||
// Mark as initialized.
|
|||
is_initialized = true; |
|||
} |
|||
|
|||
|
|||
void Builtins::TearDown() { |
|||
is_initialized = false; |
|||
} |
|||
|
|||
|
|||
void Builtins::IterateBuiltins(ObjectVisitor* v) { |
|||
v->VisitPointers(&builtins_[0], &builtins_[0] + builtin_count); |
|||
} |
|||
|
|||
|
|||
const char* Builtins::Lookup(byte* pc) { |
|||
if (is_initialized) { // may be called during initialization (disassembler!)
|
|||
for (int i = 0; i < builtin_count; i++) { |
|||
Code* entry = Code::cast(builtins_[i]); |
|||
if (entry->contains(pc)) { |
|||
return names_[i]; |
|||
} |
|||
} |
|||
} |
|||
return NULL; |
|||
} |
|||
|
|||
|
|||
} } // namespace v8::internal
|
@ -0,0 +1,217 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#ifndef V8_BUILTINS_H_ |
|||
#define V8_BUILTINS_H_ |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
// Define list of builtins implemented in C.
|
|||
#define BUILTIN_LIST_C(V) \ |
|||
V(Illegal) \ |
|||
\ |
|||
V(EmptyFunction) \ |
|||
\ |
|||
V(ArrayCode) \ |
|||
\ |
|||
V(ArrayPush) \ |
|||
V(ArrayPop) \ |
|||
\ |
|||
V(HandleApiCall) \ |
|||
V(HandleApiCallAsFunction) |
|||
|
|||
|
|||
// Define list of builtins implemented in assembly.
|
|||
#define BUILTIN_LIST_A(V) \ |
|||
V(ArgumentsAdaptorTrampoline, BUILTIN, UNINITIALIZED) \ |
|||
V(JSConstructCall, BUILTIN, UNINITIALIZED) \ |
|||
V(JSEntryTrampoline, BUILTIN, UNINITIALIZED) \ |
|||
V(JSConstructEntryTrampoline, BUILTIN, UNINITIALIZED) \ |
|||
\ |
|||
V(LoadIC_Miss, BUILTIN, UNINITIALIZED) \ |
|||
V(KeyedLoadIC_Miss, BUILTIN, UNINITIALIZED) \ |
|||
V(StoreIC_Miss, BUILTIN, UNINITIALIZED) \ |
|||
V(KeyedStoreIC_Miss, BUILTIN, UNINITIALIZED) \ |
|||
\ |
|||
V(StoreIC_ExtendStorage, BUILTIN, UNINITIALIZED) \ |
|||
V(KeyedStoreIC_ExtendStorage, BUILTIN, UNINITIALIZED) \ |
|||
\ |
|||
V(LoadIC_Initialize, LOAD_IC, UNINITIALIZED) \ |
|||
V(LoadIC_PreMonomorphic, LOAD_IC, PREMONOMORPHIC) \ |
|||
V(LoadIC_Normal, LOAD_IC, MONOMORPHIC) \ |
|||
V(LoadIC_ArrayLength, LOAD_IC, MONOMORPHIC) \ |
|||
V(LoadIC_StringLength, LOAD_IC, MONOMORPHIC) \ |
|||
V(LoadIC_FunctionPrototype, LOAD_IC, MONOMORPHIC) \ |
|||
V(LoadIC_Megamorphic, LOAD_IC, MEGAMORPHIC) \ |
|||
\ |
|||
V(KeyedLoadIC_Initialize, KEYED_LOAD_IC, UNINITIALIZED) \ |
|||
V(KeyedLoadIC_PreMonomorphic, KEYED_LOAD_IC, PREMONOMORPHIC) \ |
|||
V(KeyedLoadIC_Generic, KEYED_LOAD_IC, MEGAMORPHIC) \ |
|||
\ |
|||
V(StoreIC_Initialize, STORE_IC, UNINITIALIZED) \ |
|||
V(StoreIC_Megamorphic, STORE_IC, MEGAMORPHIC) \ |
|||
\ |
|||
V(KeyedStoreIC_Initialize, KEYED_STORE_IC, UNINITIALIZED) \ |
|||
V(KeyedStoreIC_Generic, KEYED_STORE_IC, MEGAMORPHIC) \ |
|||
\ |
|||
/* Uses KeyedLoadIC_Initialize; must be after in list. */ \ |
|||
V(FunctionCall, BUILTIN, UNINITIALIZED) \ |
|||
V(FunctionApply, BUILTIN, UNINITIALIZED) |
|||
|
|||
|
|||
// Define list of builtins used by the debugger implemented in assembly.
|
|||
#define BUILTIN_LIST_DEBUG_A(V) \ |
|||
V(Return_DebugBreak, BUILTIN, DEBUG_BREAK) \ |
|||
V(Return_DebugBreakEntry, BUILTIN, DEBUG_BREAK) \ |
|||
V(ConstructCall_DebugBreak, BUILTIN, DEBUG_BREAK) \ |
|||
V(StubNoRegisters_DebugBreak, BUILTIN, DEBUG_BREAK) \ |
|||
V(LoadIC_DebugBreak, LOAD_IC, DEBUG_BREAK) \ |
|||
V(KeyedLoadIC_DebugBreak, KEYED_LOAD_IC, DEBUG_BREAK) \ |
|||
V(StoreIC_DebugBreak, STORE_IC, DEBUG_BREAK) \ |
|||
V(KeyedStoreIC_DebugBreak, KEYED_STORE_IC, DEBUG_BREAK) |
|||
|
|||
|
|||
// Define list of builtins implemented in JavaScript.
|
|||
#define BUILTINS_LIST_JS(V) \ |
|||
V(EQUALS, 1) \ |
|||
V(STRICT_EQUALS, 1) \ |
|||
V(COMPARE, 2) \ |
|||
V(ADD, 1) \ |
|||
V(SUB, 1) \ |
|||
V(MUL, 1) \ |
|||
V(DIV, 1) \ |
|||
V(MOD, 1) \ |
|||
V(BIT_OR, 1) \ |
|||
V(BIT_AND, 1) \ |
|||
V(BIT_XOR, 1) \ |
|||
V(UNARY_MINUS, 0) \ |
|||
V(BIT_NOT, 0) \ |
|||
V(SHL, 1) \ |
|||
V(SAR, 1) \ |
|||
V(SHR, 1) \ |
|||
V(DELETE, 1) \ |
|||
V(IN, 1) \ |
|||
V(INSTANCE_OF, 1) \ |
|||
V(GET_KEYS, 0) \ |
|||
V(FILTER_KEY, 1) \ |
|||
V(CALL_NON_FUNCTION, 0) \ |
|||
V(TO_OBJECT, 0) \ |
|||
V(TO_NUMBER, 0) \ |
|||
V(TO_STRING, 0) \ |
|||
V(STRING_ADD_LEFT, 1) \ |
|||
V(STRING_ADD_RIGHT, 1) \ |
|||
V(APPLY_PREPARE, 1) \ |
|||
V(APPLY_OVERFLOW, 1) |
|||
|
|||
|
|||
class ObjectVisitor; |
|||
|
|||
|
|||
class Builtins : public AllStatic { |
|||
public: |
|||
// Generate all builtin code objects. Should be called once during
|
|||
// VM initialization.
|
|||
static void Setup(bool create_heap_objects); |
|||
static void TearDown(); |
|||
|
|||
// Garbage collection support.
|
|||
static void IterateBuiltins(ObjectVisitor* v); |
|||
|
|||
// Disassembler support.
|
|||
static const char* Lookup(byte* pc); |
|||
|
|||
enum Name { |
|||
#define DEF_ENUM_C(name) name, |
|||
#define DEF_ENUM_A(name, kind, state) name, |
|||
BUILTIN_LIST_C(DEF_ENUM_C) |
|||
BUILTIN_LIST_A(DEF_ENUM_A) |
|||
BUILTIN_LIST_DEBUG_A(DEF_ENUM_A) |
|||
#undef DEF_ENUM_C |
|||
#undef DEF_ENUM_A |
|||
builtin_count |
|||
}; |
|||
|
|||
enum CFunctionId { |
|||
#define DEF_ENUM_C(name) c_##name, |
|||
BUILTIN_LIST_C(DEF_ENUM_C) |
|||
#undef DEF_ENUM_C |
|||
cfunction_count |
|||
}; |
|||
|
|||
enum JavaScript { |
|||
#define DEF_ENUM(name, ignore) name, |
|||
BUILTINS_LIST_JS(DEF_ENUM) |
|||
#undef DEF_ENUM |
|||
id_count |
|||
}; |
|||
|
|||
static Code* builtin(Name name) { |
|||
// Code::cast cannot be used here since we access builtins
|
|||
// during the marking phase of mark sweep. See IC::Clear.
|
|||
return reinterpret_cast<Code*>(builtins_[name]); |
|||
} |
|||
|
|||
static Address builtin_address(Name name) { |
|||
return reinterpret_cast<Address>(&builtins_[name]); |
|||
} |
|||
|
|||
static Address c_function_address(CFunctionId id) { |
|||
return c_functions_[id]; |
|||
} |
|||
|
|||
static const char* GetName(JavaScript id) { return javascript_names_[id]; } |
|||
static int GetArgumentsCount(JavaScript id) { return javascript_argc_[id]; } |
|||
static Handle<Code> GetCode(JavaScript id, bool* resolved); |
|||
static int NumberOfJavaScriptBuiltins() { return id_count; } |
|||
|
|||
static Object* builtin_passed_function; |
|||
|
|||
private: |
|||
// The external C++ functions called from the code.
|
|||
static Address c_functions_[cfunction_count]; |
|||
|
|||
// Note: These are always Code objects, but to conform with
|
|||
// IterateBuiltins() above which assumes Object**'s for the callback
|
|||
// function f, we use an Object* array here.
|
|||
static Object* builtins_[builtin_count]; |
|||
static const char* names_[builtin_count]; |
|||
static const char* javascript_names_[id_count]; |
|||
static int javascript_argc_[id_count]; |
|||
|
|||
static void Generate_Adaptor(MacroAssembler* masm, CFunctionId id); |
|||
static void Generate_JSConstructCall(MacroAssembler* masm); |
|||
static void Generate_JSEntryTrampoline(MacroAssembler* masm); |
|||
static void Generate_JSConstructEntryTrampoline(MacroAssembler* masm); |
|||
static void Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm); |
|||
|
|||
static void Generate_FunctionCall(MacroAssembler* masm); |
|||
static void Generate_FunctionApply(MacroAssembler* masm); |
|||
}; |
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_BUILTINS_H_
|
@ -0,0 +1,103 @@ |
|||
// Copyright 2008-2009 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
|
|||
#ifndef V8_BYTECODES_IRREGEXP_H_ |
|||
#define V8_BYTECODES_IRREGEXP_H_ |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
|
|||
static const int BYTECODE_MASK = 0xff; |
|||
// The first argument is packed in with the byte code in one word, but so it
|
|||
// has 24 bits, but it can be positive and negative so only use 23 bits for
|
|||
// positive values.
|
|||
static const unsigned int MAX_FIRST_ARG = 0x7fffffu; |
|||
static const int BYTECODE_SHIFT = 8; |
|||
|
|||
#define BYTECODE_ITERATOR(V) \ |
|||
V(BREAK, 0, 4) /* bc8 */ \ |
|||
V(PUSH_CP, 1, 4) /* bc8 pad24 */ \ |
|||
V(PUSH_BT, 2, 8) /* bc8 pad24 offset32 */ \ |
|||
V(PUSH_REGISTER, 3, 4) /* bc8 reg_idx24 */ \ |
|||
V(SET_REGISTER_TO_CP, 4, 8) /* bc8 reg_idx24 offset32 */ \ |
|||
V(SET_CP_TO_REGISTER, 5, 4) /* bc8 reg_idx24 */ \ |
|||
V(SET_REGISTER_TO_SP, 6, 4) /* bc8 reg_idx24 */ \ |
|||
V(SET_SP_TO_REGISTER, 7, 4) /* bc8 reg_idx24 */ \ |
|||
V(SET_REGISTER, 8, 8) /* bc8 reg_idx24 value32 */ \ |
|||
V(ADVANCE_REGISTER, 9, 8) /* bc8 reg_idx24 value32 */ \ |
|||
V(POP_CP, 10, 4) /* bc8 pad24 */ \ |
|||
V(POP_BT, 11, 4) /* bc8 pad24 */ \ |
|||
V(POP_REGISTER, 12, 4) /* bc8 reg_idx24 */ \ |
|||
V(FAIL, 13, 4) /* bc8 pad24 */ \ |
|||
V(SUCCEED, 14, 4) /* bc8 pad24 */ \ |
|||
V(ADVANCE_CP, 15, 4) /* bc8 offset24 */ \ |
|||
V(GOTO, 16, 8) /* bc8 pad24 addr32 */ \ |
|||
V(LOAD_CURRENT_CHAR, 17, 8) /* bc8 offset24 addr32 */ \ |
|||
V(LOAD_CURRENT_CHAR_UNCHECKED, 18, 4) /* bc8 offset24 */ \ |
|||
V(LOAD_2_CURRENT_CHARS, 19, 8) /* bc8 offset24 addr32 */ \ |
|||
V(LOAD_2_CURRENT_CHARS_UNCHECKED, 20, 4) /* bc8 offset24 */ \ |
|||
V(LOAD_4_CURRENT_CHARS, 21, 8) /* bc8 offset24 addr32 */ \ |
|||
V(LOAD_4_CURRENT_CHARS_UNCHECKED, 22, 4) /* bc8 offset24 */ \ |
|||
V(CHECK_4_CHARS, 23, 12) /* bc8 pad24 uint32 addr32 */ \ |
|||
V(CHECK_CHAR, 24, 8) /* bc8 pad8 uint16 addr32 */ \ |
|||
V(CHECK_NOT_4_CHARS, 25, 12) /* bc8 pad24 uint32 addr32 */ \ |
|||
V(CHECK_NOT_CHAR, 26, 8) /* bc8 pad8 uint16 addr32 */ \ |
|||
V(AND_CHECK_4_CHARS, 27, 16) /* bc8 pad24 uint32 uint32 addr32 */ \ |
|||
V(AND_CHECK_CHAR, 28, 12) /* bc8 pad8 uint16 uint32 addr32 */ \ |
|||
V(AND_CHECK_NOT_4_CHARS, 29, 16) /* bc8 pad24 uint32 uint32 addr32 */ \ |
|||
V(AND_CHECK_NOT_CHAR, 30, 12) /* bc8 pad8 uint16 uint32 addr32 */ \ |
|||
V(MINUS_AND_CHECK_NOT_CHAR, 31, 12) /* bc8 pad8 uc16 uc16 addr32 */ \ |
|||
V(CHECK_LT, 32, 8) /* bc8 pad8 uc16 addr32 */ \ |
|||
V(CHECK_GT, 33, 8) /* bc8 pad8 uc16 addr32 */ \ |
|||
V(CHECK_NOT_BACK_REF, 34, 8) /* bc8 reg_idx24 addr32 */ \ |
|||
V(CHECK_NOT_BACK_REF_NO_CASE, 35, 8) /* bc8 reg_idx24 addr32 */ \ |
|||
V(CHECK_NOT_REGS_EQUAL, 36, 12) /* bc8 regidx24 reg_idx32 addr32 */ \ |
|||
V(LOOKUP_MAP1, 37, 12) /* bc8 pad8 start16 bit_map_addr32 addr32 */ \ |
|||
V(LOOKUP_MAP2, 38, 96) /* bc8 pad8 start16 half_nibble_map_addr32* */ \ |
|||
V(LOOKUP_MAP8, 39, 96) /* bc8 pad8 start16 byte_map addr32* */ \ |
|||
V(LOOKUP_HI_MAP8, 40, 96) /* bc8 start24 byte_map_addr32 addr32* */ \ |
|||
V(CHECK_REGISTER_LT, 41, 12) /* bc8 reg_idx24 value32 addr32 */ \ |
|||
V(CHECK_REGISTER_GE, 42, 12) /* bc8 reg_idx24 value32 addr32 */ \ |
|||
V(CHECK_REGISTER_EQ_POS, 43, 8) /* bc8 reg_idx24 addr32 */ \ |
|||
V(CHECK_AT_START, 44, 8) /* bc8 pad24 addr32 */ \ |
|||
V(CHECK_NOT_AT_START, 45, 8) /* bc8 pad24 addr32 */ \ |
|||
V(CHECK_GREEDY, 46, 8) /* bc8 pad24 addr32 */ \ |
|||
V(ADVANCE_CP_AND_GOTO, 47, 8) /* bc8 offset24 addr32 */ |
|||
|
|||
#define DECLARE_BYTECODES(name, code, length) \ |
|||
static const int BC_##name = code; |
|||
BYTECODE_ITERATOR(DECLARE_BYTECODES) |
|||
#undef DECLARE_BYTECODES |
|||
|
|||
#define DECLARE_BYTECODE_LENGTH(name, code, length) \ |
|||
static const int BC_##name##_LENGTH = length; |
|||
BYTECODE_ITERATOR(DECLARE_BYTECODE_LENGTH) |
|||
#undef DECLARE_BYTECODE_LENGTH |
|||
} } |
|||
|
|||
#endif // V8_BYTECODES_IRREGEXP_H_
|
@ -0,0 +1,85 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#ifndef V8_CHAR_PREDICATES_INL_H_ |
|||
#define V8_CHAR_PREDICATES_INL_H_ |
|||
|
|||
#include "char-predicates.h" |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
|
|||
inline bool IsCarriageReturn(uc32 c) { |
|||
return c == 0x000D; |
|||
} |
|||
|
|||
|
|||
inline bool IsLineFeed(uc32 c) { |
|||
return c == 0x000A; |
|||
} |
|||
|
|||
|
|||
static inline bool IsInRange(int value, int lower_limit, int higher_limit) { |
|||
ASSERT(lower_limit <= higher_limit); |
|||
return static_cast<unsigned int>(value - lower_limit) <= |
|||
static_cast<unsigned int>(higher_limit - lower_limit); |
|||
} |
|||
|
|||
|
|||
inline bool IsDecimalDigit(uc32 c) { |
|||
// ECMA-262, 3rd, 7.8.3 (p 16)
|
|||
return IsInRange(c, '0', '9'); |
|||
} |
|||
|
|||
|
|||
inline bool IsHexDigit(uc32 c) { |
|||
// ECMA-262, 3rd, 7.6 (p 15)
|
|||
return IsDecimalDigit(c) || IsInRange(c | 0x20, 'a', 'f'); |
|||
} |
|||
|
|||
|
|||
inline bool IsRegExpWord(uc16 c) { |
|||
return IsInRange(c | 0x20, 'a', 'z') |
|||
|| IsDecimalDigit(c) |
|||
|| (c == '_'); |
|||
} |
|||
|
|||
|
|||
inline bool IsRegExpNewline(uc16 c) { |
|||
switch (c) { |
|||
// CR LF LS PS
|
|||
case 0x000A: case 0x000D: case 0x2028: case 0x2029: |
|||
return false; |
|||
default: |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_CHAR_PREDICATES_INL_H_
|
@ -0,0 +1,64 @@ |
|||
// Copyright 2006-2008 the V8 project authors. All rights reserved.
|
|||
// Redistribution and use in source and binary forms, with or without
|
|||
// modification, are permitted provided that the following conditions are
|
|||
// met:
|
|||
//
|
|||
// * Redistributions of source code must retain the above copyright
|
|||
// notice, this list of conditions and the following disclaimer.
|
|||
// * Redistributions in binary form must reproduce the above
|
|||
// copyright notice, this list of conditions and the following
|
|||
// disclaimer in the documentation and/or other materials provided
|
|||
// with the distribution.
|
|||
// * Neither the name of Google Inc. nor the names of its
|
|||
// contributors may be used to endorse or promote products derived
|
|||
// from this software without specific prior written permission.
|
|||
//
|
|||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
|
|||
#ifndef V8_CHAR_PREDICATES_H_ |
|||
#define V8_CHAR_PREDICATES_H_ |
|||
|
|||
namespace v8 { namespace internal { |
|||
|
|||
// Unicode character predicates as defined by ECMA-262, 3rd,
|
|||
// used for lexical analysis.
|
|||
|
|||
inline bool IsCarriageReturn(uc32 c); |
|||
inline bool IsLineFeed(uc32 c); |
|||
inline bool IsDecimalDigit(uc32 c); |
|||
inline bool IsHexDigit(uc32 c); |
|||
inline bool IsRegExpWord(uc32 c); |
|||
inline bool IsRegExpNewline(uc32 c); |
|||
|
|||
struct IdentifierStart { |
|||
static inline bool Is(uc32 c) { |
|||
switch (c) { |
|||
case '$': case '_': case '\\': return true; |
|||
default: return unibrow::Letter::Is(c); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
|
|||
struct IdentifierPart { |
|||
static inline bool Is(uc32 c) { |
|||
return IdentifierStart::Is(c) |
|||
|| unibrow::Number::Is(c) |
|||
|| unibrow::CombiningMark::Is(c) |
|||
|| unibrow::ConnectorPunctuation::Is(c); |
|||
} |
|||
}; |
|||
|
|||
} } // namespace v8::internal
|
|||
|
|||
#endif // V8_CHAR_PREDICATES_H_
|
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue