Skip to content

Commit efcfb85

Browse files
committed
Address review comments
1 parent 0984442 commit efcfb85

2 files changed

Lines changed: 85 additions & 66 deletions

File tree

src/executable/bytecode/BytecodeProgram.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#include "Bytecode.hpp"
33
#include "executable/Function.hpp"
44
#include "executable/Mangler.hpp"
5-
#include "interpreter/InterpreterSession.hpp"
5+
#include "interpreter/Interpreter.hpp"
66
#include "runtime/PyCode.hpp"
77
#include "runtime/PyFrame.hpp"
88
#include "runtime/PyFunction.hpp"
@@ -136,6 +136,11 @@ int BytecodeProgram::execute(VirtualMachine *vm)
136136

137137
auto result = m_main_function->function()->call(*vm, interpreter);
138138

139+
{
140+
ScopedStack scoped_stack{ vm->push_frame(1, 0, 0) };
141+
[[maybe_unused]] auto final_result = interpreter.finalise();
142+
}
143+
139144
if (result.is_err()) {
140145
// The exception propagated all the way out; the eval loop already popped it
141146
// off the (now-clean) exception stack as it unwound, so use the result value
@@ -144,11 +149,6 @@ int BytecodeProgram::execute(VirtualMachine *vm)
144149
std::cout << exception->format_traceback() << std::endl;
145150
}
146151

147-
{
148-
[[maybe_unused]] auto push_result = vm->push_frame(1, 0, 0);
149-
[[maybe_unused]] auto final_result = interpreter.finalise();
150-
}
151-
152152
return result.is_ok() ? EXIT_SUCCESS : EXIT_FAILURE;
153153
}
154154

src/runtime/modules/IOModule.cpp

Lines changed: 79 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,26 @@ namespace fs = std::filesystem;
4141
namespace py {
4242

4343
namespace {
44-
static PyType *s_io_base = nullptr;
45-
static PyType *s_io_raw_iobase = nullptr;
46-
static PyType *s_io_buffered_io_base = nullptr;
47-
static PyType *s_io_buffered_reader = nullptr;
48-
static PyType *s_io_buffered_writer = nullptr;
49-
static PyType *s_io_buffered_rwpair = nullptr;
50-
static PyType *s_io_buffered_random = nullptr;
51-
static PyType *s_io_textiobase = nullptr;
52-
static PyType *s_io_incremental_newline_decoder = nullptr;
53-
static PyType *s_io_bytesio = nullptr;
54-
static PyType *s_io_fileio = nullptr;
55-
static PyType *s_io_stringio = nullptr;
56-
static PyType *s_io_textiowrapper = nullptr;
57-
58-
static PyType *s_blocking_io_error = nullptr;
59-
static PyType *s_unsupported_operation_type = nullptr;
44+
PyType *s_io_base = nullptr;
45+
PyType *s_io_raw_iobase = nullptr;
46+
PyType *s_io_buffered_io_base = nullptr;
47+
PyType *s_io_buffered_reader = nullptr;
48+
PyType *s_io_buffered_writer = nullptr;
49+
PyType *s_io_buffered_rwpair = nullptr;
50+
PyType *s_io_buffered_random = nullptr;
51+
PyType *s_io_textiobase = nullptr;
52+
PyType *s_io_incremental_newline_decoder = nullptr;
53+
PyType *s_io_bytesio = nullptr;
54+
PyType *s_io_fileio = nullptr;
55+
PyType *s_io_stringio = nullptr;
56+
PyType *s_io_textiowrapper = nullptr;
57+
58+
PyType *s_blocking_io_error = nullptr;
59+
PyType *s_unsupported_operation_type = nullptr;
6060
}// namespace
6161

62+
static constexpr size_t s_default_buffer_size = 8192;
63+
6264
Exception *unsupported_operation(PyTuple *args, PyDict *kwargs)
6365
{
6466
ASSERT(s_unsupported_operation_type);
@@ -1359,11 +1361,12 @@ class BufferedWriter
13591361
BufferedWriter(PyType *type, PyObject *raw, int buffer_size) : BufferedIOBase(type)
13601362
{
13611363
this->raw = raw;
1362-
(void)buffer_size;
1364+
if (buffer_size > 0) { m_buffer_size = static_cast<size_t>(buffer_size); }
13631365
this->readable_ = false;
13641366
this->writable_ = true;
13651367
this->fast_closed_checks = false;
13661368
this->ok = true;
1369+
this->buffer = std::make_unique<std::stringbuf>();
13671370
}
13681371

13691372
BufferedWriter(PyType *type) : BufferedWriter(type, nullptr, 0) {}
@@ -1397,7 +1400,7 @@ class BufferedWriter
13971400
static PyResult<BufferedWriter *> create(PyObject *raw, int buffer_size)
13981401
{
13991402
auto &heap = VirtualMachine::the().heap();
1400-
if (auto *obj = heap.allocate<BufferedWriter>(s_io_buffered_reader, raw, buffer_size)) {
1403+
if (auto *obj = heap.allocate<BufferedWriter>(s_io_buffered_writer, raw, buffer_size)) {
14011404
return Ok(obj);
14021405
}
14031406
return Err(memory_error(sizeof(BufferedWriter)));
@@ -1410,21 +1413,24 @@ class BufferedWriter
14101413

14111414
PyResult<int32_t> __init__(PyTuple *args, PyDict *kwargs)
14121415
{
1413-
ASSERT(!kwargs || kwargs->map().empty());
1414-
if (!args || args->elements().empty()) {
1415-
return Err(type_error("missing required argument 'raw'"));
1416-
}
1417-
if (args->elements().size() > 1) { TODO(); }
1418-
return PyObject::from(args->elements()[0])
1419-
.and_then([this](PyObject *raw) -> PyResult<int32_t> {
1420-
this->raw = raw;
1421-
this->readable_ = false;
1422-
this->writable_ = true;
1423-
this->fast_closed_checks = false;
1424-
this->ok = true;
1425-
this->buffer = std::make_unique<std::stringbuf>();
1426-
return Ok(0);
1427-
});
1416+
auto parse_result = PyArgsParser<PyObject *, int64_t>::unpack_tuple(args,
1417+
kwargs,
1418+
"BufferedWriter",
1419+
std::integral_constant<size_t, 1>{},
1420+
std::integral_constant<size_t, 2>{},
1421+
static_cast<int64_t>(s_default_buffer_size));
1422+
if (parse_result.is_err()) { return Err(parse_result.unwrap_err()); }
1423+
auto [raw, buffer_size] = parse_result.unwrap();
1424+
if (buffer_size <= 0) { return Err(value_error("buffer size must be strictly positive")); }
1425+
this->raw = raw;
1426+
this->readable_ = false;
1427+
this->writable_ = true;
1428+
this->fast_closed_checks = false;
1429+
this->ok = true;
1430+
this->buffer = std::make_unique<std::stringbuf>();
1431+
m_buffer_size = static_cast<size_t>(buffer_size);
1432+
m_buffered_bytes = 0;
1433+
return Ok(0);
14281434
}
14291435

14301436
PyResult<PyObject *> __repr__() const
@@ -1453,8 +1459,7 @@ class BufferedWriter
14531459
return raw->get_method(PyString::create("write").unwrap())
14541460
.and_then([memobj](PyObject *write) {
14551461
return write->call(PyTuple::create(memobj.unwrap()).unwrap(), nullptr);
1456-
})
1457-
.and_then([](auto) { return Ok(py_none()); });
1462+
});
14581463
}
14591464

14601465
PyResult<PyObject *> write(PyTuple *args, PyDict *kwargs)
@@ -1473,41 +1478,51 @@ class BufferedWriter
14731478
"write() argument must be contiguous buffer, not {}", arg->type()->name()));
14741479
}
14751480

1476-
// implementation
14771481
if (Buffered<BufferedWriter>::is_closed()) {
14781482
return Err(value_error("write to closed file"));
14791483
}
14801484

1481-
auto written =
1482-
this->buffer->sputn(static_cast<char *>(buffer.buf->get_buffer()), buffer.len);
1483-
if (written == buffer.len) {
1484-
// fast path: everything was written - we are done
1485-
return PyInteger::create(written);
1485+
const char *start = static_cast<char *>(buffer.buf->get_buffer());
1486+
const auto len = static_cast<size_t>(buffer.len);
1487+
1488+
// fast path: the data fits in the buffer's free space, so it is only buffered and does
1489+
// not reach the raw stream
1490+
if (m_buffered_bytes + len <= m_buffer_size) {
1491+
const auto written = this->buffer->sputn(start, len);
1492+
ASSERT(static_cast<size_t>(written) == len);
1493+
m_buffered_bytes += len;
1494+
return PyInteger::create(len);
14861495
}
14871496

1488-
// flush our internal buffer to raw
1497+
// the data does not fit: drain our buffer to raw first
14891498
if (auto r = flush(); r.is_err()) { return r; }
14901499

1491-
// TODO: this currently writes to internal buffer and then immediately makes a copy in
1492-
// write_raw. This is because streambuf doesn't tell us how much capacity it has left, so we
1493-
// just give it as much as we can each time
1494-
ssize_t remaining = buffer.len;
1495-
char *start = static_cast<char *>(buffer.buf->get_buffer());
1496-
// TODO: use a buffer that actually tells us how much we can write to it, rather than
1497-
// hardcode 4096
1498-
while (remaining > 4096) {
1499-
// flush our internal buffer to raw
1500+
// write oversized data directly to raw, bypassing the buffer
1501+
size_t written = 0;
1502+
size_t remaining = len;
1503+
while (remaining > m_buffer_size) {
15001504
auto r = raw_write(start + written, remaining);
15011505
if (r.is_err()) { return r; }
15021506
ASSERT(as<PyInteger>(r.unwrap()));
1503-
const auto count = as<PyInteger>(r.unwrap())->as_size_t();
1504-
written += count;
1505-
remaining -= count;
1507+
const auto &count = as<PyInteger>(r.unwrap())->as_big_int();
1508+
if (count < 0 || count > remaining) {
1509+
return Err(
1510+
os_error("raw write() returned invalid length {} (should have been "
1511+
"between 0 and {})",
1512+
count.get_str(),
1513+
remaining));
1514+
}
1515+
written += count.get_ui();
1516+
remaining -= count.get_ui();
15061517
}
15071518

1508-
// put the rest in our buffer. Assumes that we can write 4096 bytes
1509-
const auto count = this->buffer->sputn(start + written, remaining);
1510-
ASSERT(remaining - count == 0);
1519+
// buffer the tail, which is now guaranteed to fit
1520+
if (remaining > 0) {
1521+
const auto count = this->buffer->sputn(start + written, remaining);
1522+
ASSERT(static_cast<size_t>(count) == remaining);
1523+
m_buffered_bytes += remaining;
1524+
written += remaining;
1525+
}
15111526

15121527
return PyInteger::create(written);
15131528
}
@@ -1538,6 +1553,10 @@ class BufferedWriter
15381553
PyObject::visit_graph(visitor);
15391554
visit_graph_buffered(visitor);
15401555
}
1556+
1557+
private:
1558+
size_t m_buffer_size{ s_default_buffer_size };
1559+
size_t m_buffered_bytes{ 0 };
15411560
};
15421561

15431562
template<> PyResult<PyObject *> Buffered<BufferedWriter>::flush_and_rewind()
@@ -1568,6 +1587,7 @@ PyResult<PyObject *> BufferedWriter::flush()
15681587
return r;
15691588
}
15701589
}
1590+
m_buffered_bytes = 0;
15711591
return Ok(py_none());
15721592
}
15731593

@@ -3462,9 +3482,8 @@ PyModule *io_module()
34623482
return open(arg0, "rb");
34633483
}));
34643484

3465-
// C++ standard streams currently do not provide an API to get default buffer size, and C's
3466-
// BUFSIZE doesn't have to be respected
3467-
s_io_module->add_symbol(PyString::create("DEFAULT_BUFFER_SIZE").unwrap(), Number{ 0 });
3485+
s_io_module->add_symbol(PyString::create("DEFAULT_BUFFER_SIZE").unwrap(),
3486+
Number{ static_cast<int64_t>(s_default_buffer_size) });
34683487

34693488
// >> type("UnsupportedOperation", (_io.OSError, ValueError), {})
34703489
auto unsupported_operation_type = types::type()->call(

0 commit comments

Comments
 (0)